Skip to content
Snippets Groups Projects
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) ;

    }
}