-
Eric Vidal authoredEric Vidal authored
ssexec_boot.c 18.57 KiB
/*
* ssexec_boot.c
*
* Copyright (c) 2018-2023 Eric Vidal <eric@obarun.org>
*
* All rights reserved.
*
* This file is part of Obarun. It is subject to the license terms in
* the LICENSE file found in the top-level directory of this
* distribution.
* This file may not be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file./
*/
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#ifdef __linux__
#include <linux/kd.h>
#include <skalibs/sig.h>
#endif
#include <oblibs/log.h>
#include <oblibs/files.h>
#include <oblibs/string.h>
#include <oblibs/environ.h>
#include <oblibs/sastr.h>
#include <skalibs/sgetopt.h>
#include <skalibs/djbunix.h>
#include <skalibs/stralloc.h>
#include <skalibs/types.h>
#include <skalibs/exec.h>
#include <skalibs/cspawn.h>
#include <66/config.h>
#include <66/constants.h>
#include <66/ssexec.h>
static mode_t mask = SS_BOOT_UMASK ;
static unsigned int rescan = SS_BOOT_RESCAN ;
static unsigned int container = SS_BOOT_CONTAINER ;
static unsigned int catch_log = SS_BOOT_CATCH_LOG ;
static char const *skel = SS_SKEL_DIR ;
static char const *live = SS_LIVE ;
static char const *path = SS_BOOT_PATH ;
static char const *tree = SS_BOOT_TREE ;
static char const *rcinit = SS_SKEL_DIR SS_BOOT_RCINIT ;
static char const *rcinit_container = SS_SKEL_DIR SS_BOOT_RCINIT_CONTAINER ;
static char const *banner = "\n[Starts stage1 process...]" ;
static char const *slashdev = 0 ;
static char const *envdir = 0 ;
static char const *fifo = 0 ;
static char const *log_user = SS_LOGGER_RUNNER ;
static char const *cver = 0 ;
static char tpath[SS_MAX_PATH_LEN + 1] ;
static char trcinit[SS_MAX_PATH_LEN + 1] ;
static char trcinit_container[SS_MAX_PATH_LEN + 1] ;
static char tlive[SS_MAX_PATH_LEN + 1] ;
static char ttree[SS_MAX_PATH_LEN + 1] ;
static char confile[SS_MAX_PATH_LEN + 1 + SS_BOOT_CONF_LEN + 1] ;
static char const *const *genv = 0 ;
static int fdin ;
static stralloc sacmdline = STRALLOC_ZERO ;
static int notifpipe[2] ;
#define MAXBUF 1024*64*2
static void sulogin(char const *msg,char const *arg)
{
static char const *const newarg[2] = { SS_EXTBINPREFIX "sulogin" , 0 } ;
pid_t pid ;
int wstat ;
close(0) ;
if (dup2(fdin,0) == -1) log_dieu(LOG_EXIT_SYS,"duplicate stdin -- you are on your own") ;
close(fdin) ;
if (*msg) log_warnu(msg,arg) ;
pid = child_spawn0(newarg[0],newarg,genv) ;
if (waitpid_nointr(pid,&wstat, 0) < 0)
log_dieusys(LOG_EXIT_SYS,"wait for sulogin -- you are on your own") ;
fdin=dup(0) ;
if (fdin == -1) log_dieu(LOG_EXIT_SYS,"duplicate stdin -- you are on your own") ;
close(0) ;
if (open("/dev/null",O_WRONLY)) log_dieu(LOG_EXIT_SYS,"open /dev/null -- you are on your own") ;
}
static int get_value(stralloc *val,char const *key)
{
if (!environ_get_val_of_key(val,key)) return 0 ;
/** value may be empty, in this case we use the default one */
if (!sastr_clean_element(val))
{
log_warnu("get value of: ",key," -- keeps the default") ;
return 0 ;
}
if (!sastr_rebuild_in_oneline(val)) sulogin("rebuild line of value: ",val->s) ;
return 1 ;
}
static inline void string_to_table(char *table,char const **pointer,char const *str, uint8_t empty)
{
log_flow() ;
if (!empty) {
auto_strings(table,str) ;
*pointer = table ;
}
}
static inline uint8_t string_to_uint(char const *str, unsigned int *ui, uint8_t empty)
{
log_flow() ;
if (!empty)
if (!uint0_oscan(str,ui))
return 0 ;
return 1 ;
}
static void parse_conf(void)
{
log_flow() ;
static char const *valid[] =
{ "VERBOSITY", "PATH", "LIVE", "TREE", "RCINIT", "UMASK", "RESCAN", "CONTAINER", "CATCHLOG", "RCINIT_CONTAINER", 0 } ;
int r ;
unsigned int j = 0 ;
uint8_t empty = 0 ;
size_t filesize = 0 ;
char u[UINT_FMT] ;
char *gvalue = 0 ;
stralloc src = STRALLOC_ZERO ;
stralloc val = STRALLOC_ZERO ;
if (skel[0] != '/')
sulogin("skeleton directory must be an aboslute path: ",skel) ;
auto_strings(confile, skel, "/", SS_BOOT_CONF) ;
filesize = file_get_size(confile) ;
/** skeleton file */
r = openreadfileclose(confile, &src, filesize) ;
if(!r)
sulogin("open configuration file: ",confile) ;
if (!stralloc_0(&src))
sulogin("append stralloc of file: ",confile) ;
for (char const *const *p = valid; *p; p++, j++)
{
empty = 0 ;
/** try first to read from kernel environment.
* If the key is not found, try to read the skeleton file.
* Finally keep the default value if we cannot get a correct
* key=value pair */
gvalue = getenv(*p) ;
if (gvalue) {
if (!auto_stra(&val, *p, "=", gvalue))
sulogin("copy value of key: ", *p) ;
} else {
if (!stralloc_copy(&val, &src))
sulogin("copy stralloc of file: ",confile) ;
if (!get_value(&val,*p))
empty = 1 ;
}
switch (j)
{
case 0:
if (!string_to_uint(val.s,&VERBOSITY,empty))
sulogin("parse VERBOSITY value: ",val.s) ;
u[uint_fmt(u, VERBOSITY)] = 0 ;
if (!auto_stra(&sacmdline,"VERBOSITY=",u,"\n"))
sulogin("append environment stralloc with key: VERBOSITY=",u) ;
break ;
case 1:
string_to_table(tpath,&path,val.s,empty) ;
if (!auto_stra(&sacmdline,"PATH=",path,"\n"))
sulogin("append environment stralloc with key: PATH=",path) ;
break ;
case 2:
string_to_table(tlive,&live,val.s,empty) ;
if (live[0] != '/')
sulogin ("LIVE must be an absolute path",live) ;
if (!auto_stra(&sacmdline,"LIVE=",live,"\n"))
sulogin("append environment stralloc with key: LIVE=",live) ;
break ;
case 3:
string_to_table(ttree,&tree,val.s,empty) ;
if (!auto_stra(&sacmdline,"TREE=",tree,"\n"))
sulogin("append environment stralloc with key: TREE=",tree) ;
break ;
case 4:
string_to_table(trcinit,&rcinit,val.s,empty) ;
if (rcinit[0] != '/')
sulogin ("RCINIT must be an absolute path: ",rcinit) ;
if (!auto_stra(&sacmdline,"RCINIT=",rcinit,"\n"))
sulogin("append environment stralloc with key: RCINIT=",rcinit) ;
break ;
case 5:
if (!string_to_uint(val.s,&mask,empty))
sulogin("invalid UMASK value: ",val.s) ;
u[uint_fmt(u, mask)] = 0 ;
if (!auto_stra(&sacmdline,"UMASK=",u,"\n"))
sulogin("append environment stralloc with key: UMASK=",u) ;
break ;
case 6:
if (!string_to_uint(val.s,&rescan,empty))
sulogin("invalid RESCAN value: ",val.s) ;
u[uint_fmt(u, rescan)] = 0 ;
if (!auto_stra(&sacmdline,"RESCAN=",u,"\n"))
sulogin("append environment stralloc with key: RESCAN=",u) ;
break ;
case 7:
if (!string_to_uint(val.s,&container,empty))
sulogin("invalid CONTAINER value: ",val.s) ;
u[uint_fmt(u,container)] = 0 ;
if (!auto_stra(&sacmdline,"CONTAINER=",u,"\n"))
sulogin("append environment stralloc with key: CONTAINER=",u) ;
break ;
case 8:
if (!string_to_uint(val.s,&catch_log,empty))
sulogin("invalid CATCHLOG value: ",val.s) ;
u[uint_fmt(u,catch_log)] = 0 ;
if (!auto_stra(&sacmdline,"CATCHLOG=",u,"\n"))
sulogin("append environment stralloc with key: CATCHLOG=",u) ;
break ;
case 9:
string_to_table(trcinit_container,&rcinit_container,val.s,empty) ;
if (rcinit_container[0] != '/')
sulogin ("RCINIT_CONTAINER must be an absolute path: ",rcinit_container) ;
if (!auto_stra(&sacmdline,"RCINIT_CONTAINER=",rcinit_container,"\n"))
sulogin("append environment stralloc with key: RCINIT_CONTAINER=",rcinit_container) ;
break ;
default: break ;
}
}
if (container) {
if (!auto_stra(&sacmdline,"CONTAINER_HALTCMD=",live,SS_BOOT_CONTAINER_DIR,"/0/",SS_BOOT_CONTAINER_HALTCMD,"\n")) {
char tmp[strlen(live) + SS_BOOT_CONTAINER_DIR_LEN + 1 + SS_BOOT_CONTAINER_HALTCMD_LEN +1] ;
auto_strings(tmp,live,SS_BOOT_CONTAINER_DIR,"/",SS_BOOT_CONTAINER_HALTCMD) ;
sulogin("append environment stralloc with key: CONTAINER_HALTCMD=",tmp) ;
}
}
if (!sastr_split_string_in_nline(&sacmdline)) sulogin("split string: ",sacmdline.s) ;
stralloc_free(&val) ;
stralloc_free(&src) ;
}
static inline void wait_for_notif (int fd)
{
log_flow() ;
char buf[16] ;
for (;;) {
ssize_t r = read(fd, buf, 16) ;
if (r < 0)
sulogin("read from notification pipe","") ;
if (!r) {
log_warn("s6-svscan failed to send a notification byte!") ;
break ;
}
if (memchr(buf, '\n', r))
break ;
}
close(fd) ;
}
static int is_mnt(char const *str)
{
log_flow() ;
struct stat st;
size_t slen = strlen(str) ;
int is_not_mnt = 0 ;
if (lstat(str,&st) < 0) sulogin("lstat: ",str) ;
if (S_ISDIR(st.st_mode))
{
dev_t st_dev = st.st_dev ; ino_t st_ino = st.st_ino ;
char p[slen+4] ;
memcpy(p,str,slen) ;
memcpy(p + slen,"/..",3) ;
p[slen+3] = 0 ;
if (!stat(p,&st))
is_not_mnt = (st_dev == st.st_dev) && (st_ino != st.st_ino) ;
}else return 0 ;
return is_not_mnt ? 0 : 1 ;
}
static void split_tmpfs(char *dst,char const *str)
{
log_flow() ;
size_t len = get_len_until(str+1,'/') ;
len++ ;
memcpy(dst,str,len) ;
dst[len] = 0 ;
}
static inline void run_stage2 (char const *const *envp, size_t envlen, char const *modifs, size_t modiflen)
{
log_flow() ;
size_t pos = 0 ;
char const *newargv[3] ;
if (container) {
newargv[0]= rcinit_container ;
} else {
newargv[0] = rcinit ;
}
newargv[1] = confile ;
newargv[2] = 0 ;
setsid() ;
if (!catch_log) {
close(notifpipe[1]) ;
wait_for_notif(notifpipe[0]) ;
} else {
close(1) ;
if (open(fifo, O_WRONLY) != 1) /* blocks until catch-all logger is up */
sulogin("open for writing fifo: ",fifo) ;
if (fd_copy(2, 1) == -1)
sulogin("copy stderr to stdout","") ;
}
close(fdin) ;
for (;pos < modiflen ; pos += strlen(modifs + pos) +1)
if (!stralloc_catb(&sacmdline,modifs + pos, strlen(modifs + pos) + 1))
sulogin("append environment stralloc with value: ",modifs + pos) ;
size_t tlen = sacmdline.len ;
char t[tlen + 1] ;
memcpy(t,sacmdline.s,tlen) ;
t[tlen] = 0 ;
stralloc_free(&sacmdline) ;
xmexec_fm(newargv, envp, envlen, t, tlen) ;
}
static inline void make_cmdline(char const *prog,char const **add,int len,char const *msg,char const *arg,char const *const *envp)
{
log_flow() ;
pid_t pid ;
int wstat ;
int m = 7 + len, i = 0, n = 0 ;
char const *newargv[m] ;
newargv[n++] = "66" ;
newargv[n++] = "-v" ;
newargv[n++] = cver ;
newargv[n++] = "-l" ;
newargv[n++] = live ;
newargv[n++] = prog ;
for (;i<len;i++)
newargv[n++] = add[i] ;
newargv[n] = 0 ;
pid = child_spawn0(newargv[0], newargv, envp) ;
if (waitpid_nointr(pid, &wstat, 0) < 0)
sulogin("wait for: ", newargv[0]) ;
if (wstat)
sulogin(msg, arg) ;
}
static void cad(void)
{
log_flow() ;
if (container)
return ;
#ifdef __linux__
int fd ;
fd = open("/dev/tty0", O_RDONLY | O_NOCTTY) ;
if (fd < 0) {
log_warnusys("open /dev/tty0 (kbrequest will not be handled)") ;
}
else {
if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0)
log_warnusys("ioctl KDSIGACCEPT on tty0 (kbrequest will not be handled)") ;
close(fd) ;
}
sig_block(SIGINT) ; /* don't panic on early cad before s6-svscan catches it */
#endif
if (reboot(RB_DISABLE_CAD) == -1)
log_warnusys("trap ctrl-alt-del") ;
}
int ssexec_boot(int argc, char const *const *argv, ssexec_t *info)
{
VERBOSITY = 0 ;
unsigned int r , tmpfs = 0, opened = 0 ;
size_t bannerlen, livelen ;
pid_t pid ;
char verbo[UINT_FMT] ;
cver = verbo ;
stralloc envmodifs = STRALLOC_ZERO ;
genv = (char const *const *)environ ;
{
subgetopt l = SUBGETOPT_ZERO ;
for (;;)
{
int opt = subgetopt_r(argc, argv, OPTS_BOOT, &l) ;
if (opt == -1) break ;
switch (opt)
{
case 'h' : VERBOSITY = 1 ; info_help(info->help, info->usage) ; return 0 ;
case 'm' : tmpfs = 1 ; break ;
case 's' : skel = l.arg ; break ;
case 'e' : envdir = l.arg ; break ;
case 'd' : slashdev = l.arg ; break ;
case 'b' : banner = l.arg ; break ;
case 'l' : log_user = l.arg ; break ;
default : log_usage(info->usage, "\n", info->help) ;
}
}
argc -= l.ind ; argv += l.ind ;
}
if (geteuid())
{
errno = EPERM ;
log_diesys(LOG_EXIT_USER, "nice try, peon") ;
}
fdin=dup(0) ;
parse_conf() ;
verbo[uint_fmt(verbo, VERBOSITY)] = 0 ;
bannerlen = strlen(banner) ;
livelen = strlen(live) ;
char tfifo[livelen + 1 + SS_BOOT_LOGFIFO_LEN + 1] ;
auto_strings(tfifo,live,"/",SS_BOOT_LOGFIFO) ;
fifo = tfifo ;
if (container) {
/* If there's a Docker synchronization pipe, wait on it */
char c ;
ssize_t r = read(3, &c, 1) ;
if (r < 0) {
if (errno != EBADF)
sulogin("read from fd 3","") ;
} else {
if (r)
log_warn("parent wrote to fd 3!") ;
close(3) ;
}
} else {
allwrite(1, banner, bannerlen) ;
allwrite(1, "\n", 2) ;
}
if (chdir("/") == -1) sulogin("chdir to ","/") ;
umask(mask) ;
setpgid(0, 0) ;
close(0) ;
if (container && slashdev)
log_1_warn("-d options asked for a boot inside a container; are you sure your container does not come with a pre-mounted /dev?") ;
if (slashdev)
{
log_info("Mount: ",slashdev) ;
close(1) ;
close(2) ;
if (mount("dev", slashdev, "devtmpfs", MS_NOSUID | MS_NOEXEC, "") == -1)
{ opened++ ; sulogin ("mount: ", slashdev) ; }
if (open("/dev/console", O_WRONLY) ||
fd_move(2, 0) == -1 ||
fd_copy(1, 2) == -1) return 111 ;
}
if (!opened) {
if (open("/dev/null", O_RDONLY)) {
/* ghetto /dev/null to the rescue */
int p[2] ;
log_1_warnusys("open /dev/null") ;
if (pipe(p) < 0)
sulogin("pipe for /dev/null","") ;
close(p[1]) ;
if (fd_move(0, p[0]) < 0)
sulogin("fd_move to stdin","") ;
}
}
char fs[livelen + 1] ;
split_tmpfs(fs,live) ;
r = is_mnt(fs) ;
if (!r || tmpfs)
{
if (r && tmpfs)
{
log_info("Umount: ",fs) ;
if (umount(fs) == -1) sulogin ("umount: ",fs ) ;
}
log_info("Mount: ",fs) ;
if (mount("tmpfs", fs, "tmpfs", MS_NODEV | MS_NOSUID, "mode=0755") == -1)
sulogin("mount: ",fs) ;
}
/** respect the path before run API*/
if (setenv("PATH", path, 1) == -1) sulogin("set initial PATH: ",path) ;
/** create scandir */
{
size_t ncatch = !catch_log ? 1 : 0 ;
size_t nargc = 6 + ncatch ;
unsigned int m = 0 ;
char const *t[nargc] ;
t[m++] = "create" ;
if (container) {
t[m++] = "-B" ;
} else {
t[m++] = "-b" ;
}
if (!catch_log)
t[m++] = "-c" ;
t[m++] = "-s" ;
t[m++] = skel ;
t[m++] = "-L" ;
t[m++] = log_user ;
log_info("Create live scandir at: ",live) ;
make_cmdline("scandir", t, nargc, "create live scandir at: ", live, genv) ;
}
/** initiate earlier service */
{
char const *t[] = { "init", tree } ;
log_info("Initiate earlier service of tree: ",tree) ;
make_cmdline("tree", t, 2, "initiate earlier service of tree: ", tree, genv) ;
}
if (envdir) {
if (!environ_clean_envfile_unexport(&envmodifs,envdir))
sulogin("prepare environment from: ",envdir) ;
}
if (catch_log)
{
log_info("Starts boot logger at: ",live,"/log/0") ;
int fdr = open_read(fifo) ;
if (fdr == -1) sulogin("open fifo: ",fifo) ;
fd_close(1) ;
if (open(fifo, O_WRONLY) != 1) sulogin("open fifo: ",fifo) ;
fd_close(fdr) ;
}
/** fork and starts scandir */
{
size_t pathlen = strlen(path) ;
char const *newenvp[2] = { 0, 0 } ;
char pathvar[6 + pathlen] ;
char fmtfd[2 + UINT_FMT] = "-" ;
size_t m = 0 ;
static char const *newargv[8] ;
newargv[m++] = "66" ;
newargv[m++] = "-v0" ;
newargv[m++] = "-l" ;
newargv[m++] = live ;
newargv[m++] = "scandir" ;
newargv[m++] = "start" ;
if (!catch_log)
newargv[m++] = fmtfd ;
newargv[m++] = 0 ;
memcpy(pathvar, "PATH=", 5) ;
memcpy(pathvar + 5, path, pathlen + 1) ;
newenvp[0] = pathvar ;
if (!catch_log && pipe(notifpipe) < 0)
sulogin("pipe","") ;
pid = fork() ;
if (pid == -1)
sulogin("fork: ",container ? rcinit_container : rcinit) ;
if (!pid)
run_stage2(newenvp, 1, envmodifs.s,envmodifs.len) ;
if (!catch_log) {
close(notifpipe[0]) ;
fmtfd[1] = 'd' ;
fmtfd[2 + uint_fmt(fmtfd + 2, notifpipe[1])] = 0 ;
cad() ;
} else {
cad() ;
if (fd_copy(2, 1) == -1)
sulogin("copy stderr to stdout","") ;
}
close(fdin) ;
xmexec_fm(newargv, newenvp, 1, envmodifs.s, envmodifs.len) ;
}
}