/* * 66-rebuild.c * * Copyright (c) 2018-2021 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 <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <oblibs/log.h> #include <oblibs/string.h> #include <oblibs/sastr.h> #include <oblibs/types.h> #include <oblibs/directory.h> #include <oblibs/files.h> #include <skalibs/stralloc.h> #include <skalibs/sgetopt.h> #include <skalibs/djbunix.h> #include <skalibs/unix-transactional.h> #include <skalibs/cdb.h> #include <66/ssexec.h> #include <66/constants.h> #include <66/utils.h> #include <66/db.h> #include <66/svc.h> #include <66/tree.h> #include <66/backup.h> #include <66/resolve.h> #include <66/parser.h> #define USAGE "66-rebuild [ -h ] [ -z ] [ -v verbosity ] [ -l live ] [ -d ] tree(s)" static stralloc WORKDIR = STRALLOC_ZERO ; static uint8_t DRYRUN = 0 ; static char *drun = "dry run do: " ; static inline void info_help (void) { DEFAULT_MSG = 0 ; static char const *help = "\n" "options :\n" " -h: print this help\n" " -z: use color\n" " -v: increase/decrease verbosity\n" " -l: live directory\n" " -d: dry run\n" "\n" "if no tree is given, all trees will be processed.\n" ; log_info(USAGE,"\n",help) ; } static void cleanup(void) { log_flow() ; int e = errno ; if (WORKDIR.len) { log_trace(DRYRUN ? drun : "","delete temporary directory: ",WORKDIR.s) ; rm_rf(WORKDIR.s) ; } errno = e ; } int tree_is_current(char const *base,char const *treename,uid_t owner) { log_flow() ; stralloc sacurr = STRALLOC_ZERO ; int current = 0 ; if (tree_find_current(&sacurr,base,owner)) { char name[sacurr.len + 1] ; if (!ob_basename(name,sacurr.s)) log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"basename of: ",sacurr.s) ; current = obstr_equal(treename,name) ; } stralloc_free(&sacurr) ; return current ; } int tree_is_enabled(char const *treename) { log_flow() ; return tree_cmd_state(VERBOSITY,"-s",treename) ; } void tree_allowed(stralloc *list,char const *base, char const *treename) { log_flow() ; stralloc sa = STRALLOC_ZERO ; size_t treenamelen = strlen(treename), baselen = strlen(base), pos ; char tmp[baselen + SS_SYSTEM_LEN + 1 + treenamelen + SS_RULES_LEN + 1] ; auto_strings(tmp,base,SS_SYSTEM,"/",treename,SS_RULES) ; if (!sastr_dir_get(&sa,tmp,"",S_IFREG)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"get permissions of tree at: ",tmp) ; for (pos = 0 ;pos < sa.len; pos += strlen(sa.s + pos) + 1) { char *suid = sa.s + pos ; uid_t uid = 0 ; if (!uid0_scan(suid, &uid)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"get uid of: ",suid) ; if (pos) if (!stralloc_cats(list,",")) log_die_nomem_nclean(&cleanup,"stralloc") ; if (!get_namebyuid(uid,list)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"get name of uid: ",suid) ; } if (!stralloc_0(list)) log_die_nomem_nclean(&cleanup,"stralloc") ; log_trace(DRYRUN ? drun : "","allowed user(s) for tree: ",treename," are: ",list->s) ; stralloc_free(&sa) ; } void tree_contents(stralloc *list,char const *tree,ssexec_t *info) { log_flow() ; size_t treelen = strlen(tree), pos ; stralloc sa = STRALLOC_ZERO ; char solve[treelen + SS_SVDIRS_LEN + SS_RESOLVE_LEN + SS_DB_LEN + SS_SRC_LEN + 1] ; ss_resolve_t res = RESOLVE_ZERO ; auto_strings(solve,tree,SS_SVDIRS,SS_RESOLVE) ; if (!sastr_dir_get(&sa,solve,SS_MASTER + 1,S_IFREG)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"get the service resolve files of: ",tree) ; auto_strings(solve,tree,SS_SVDIRS) ; for (pos = 0 ;pos < sa.len; pos += strlen(sa.s + pos) + 1) { char *name = sa.s + pos ; int logname = get_rstrlen_until(name,SS_LOG_SUFFIX) ; if (logname > 0) continue ; if (!ss_resolve_read(&res,solve,name)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"read resolve file of: ",solve,"/",name) ; if (res.disen) if (!stralloc_catb(list,res.sa.s + res.src,strlen(res.sa.s + res.src) + 1)) log_diesys_nclean(LOG_EXIT_SYS,&cleanup,"stralloc") ; log_trace(DRYRUN ? drun : "","tree: ",info->treename.s," contain service: ",res.sa.s + res.src) ; } stralloc_free(&sa) ; } static int run_cmdline(char const *prog,char const **add,int len,char const *const *envp) { log_flow() ; pid_t pid ; int wstat ; char fmt[UINT_FMT] ; fmt[uint_fmt(fmt, VERBOSITY)] = 0 ; int m = 4 + len, i = 0, n = 0 ; char const *newargv[m] ; newargv[n++] = prog ; newargv[n++] = "-v" ; newargv[n++] = fmt ; 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) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"wait for: ",newargv[0]) ; if (wstat) return 0 ; return 1 ; } int main(int argc, char const *const *argv,char const *const *envp) { int r ; unsigned int nclassic = 0, nlongrun = 0, nbsv = 0 ; size_t systemlen, optslen = 5, pos, len ; char tree_opts_create[optslen] ; char *fdir = 0 ; log_color = &log_color_disable ; stralloc satree = STRALLOC_ZERO ; stralloc allow = STRALLOC_ZERO ; stralloc contents = STRALLOC_ZERO ; stralloc tree_enabled = STRALLOC_ZERO ; ssexec_t info = SSEXEC_ZERO ; PROG = "66-rebuild" ; { subgetopt l = SUBGETOPT_ZERO ; for (;;) { int opt = subgetopt_r(argc,argv, "hzv:l:d", &l) ; if (opt == -1) break ; switch (opt) { case 'h' : info_help(); return 0 ; case 'v' : if (!uint0_scan(l.arg, &VERBOSITY)) log_usage(USAGE) ; break ; case 'l' : if (!stralloc_cats(&info.live,l.arg)) log_die_nomem("stralloc") ; if (!stralloc_0(&info.live)) log_die_nomem("stralloc") ; break ; case 'z' : log_color = !isatty(1) ? &log_color_disable : &log_color_enable ; break ; case 'd' : DRYRUN = 1 ; break ; default : log_usage(USAGE) ; } } argc -= l.ind ; argv += l.ind ; } info.owner = getuid() ; if (!set_ownersysdir(&info.base,info.owner)) log_dieusys(LOG_EXIT_SYS, "set owner directory") ; char system[info.base.len + SS_SYSTEM_LEN + 2] ; auto_strings(system,info.base.s,SS_SYSTEM,"/") ; systemlen = info.base.len + SS_SYSTEM_LEN + 1 ; if (!argc) { if (info.owner) { /** check if user system dir already exist */ if (!scan_mode(info.base.s,S_IFDIR)) { log_info(DRYRUN ? drun : "","No trees exist yet -- Nothing to do") ; goto exit ; } } if (!sastr_dir_get(&satree,system,SS_BACKUP + 1,S_IFDIR)) log_dieusys(LOG_EXIT_SYS,"get list of trees at: ",system) ; if (!satree.len) { log_info(DRYRUN ? drun : "","No trees exist yet -- Nothing to do") ; goto exit ; } } else { int i = 0 ; size_t arglen = 0 ; for (;i < argc ; i++) { arglen = strlen(argv[i]) ; char tree[systemlen + arglen + 1] ; auto_strings(tree,system,argv[i]) ; r = scan_mode(tree,S_IFDIR) ; if (r == -1) { errno = EEXIST ; log_diesys(LOG_EXIT_SYS,"conflicting format of: ",tree) ; } if (!r) log_die(LOG_EXIT_USER,"tree: ",tree," doesn't exist") ; if (!stralloc_catb(&satree,argv[i],strlen(argv[i]) + 1)) log_die_nomem("stralloc") ; } } /** keep start order of trees */ if (!file_readputsa(&tree_enabled,system,"state")) log_dieusys(LOG_EXIT_SYS,"read contents of file: ",system,"state") ; len = satree.len ; for (pos = 0 ; pos < len; pos += strlen(satree.s + pos) + 1) { int dbok = 0 ; nclassic = nlongrun = nbsv = 0 ; info.base.len = info.tree.len = info.treename.len = 0 ; allow.len = WORKDIR.len = contents.len = 0 ; auto_strings(tree_opts_create,"-n") ; optslen = 2 ; if (!auto_stra(&info.tree,satree.s + pos)) log_die_nomem("stralloc") ; set_ssinfo(&info) ; char tmp[systemlen + SS_BACKUP_LEN + info.treename.len + SS_SVDIRS_LEN + SS_DB_LEN + 1] ; char current[info.livetree.len + 1 + info.treename.len + 9 + 1] ; log_info(DRYRUN ? drun : "","save state of tree: ", info.treename.s) ; if (tree_is_current(info.base.s,info.treename.s,info.owner)) { log_trace(DRYRUN ? drun : "","tree: ",info.treename.s," is marked current") ; auto_string_from(tree_opts_create,optslen,"c") ; optslen +=1 ; } if (tree_is_enabled(info.treename.s) == 1) { log_trace(DRYRUN ? drun : "","tree: ",info.treename.s," is marked enabled") ; auto_string_from(tree_opts_create,optslen,"E") ; optslen +=1 ; } tree_allowed(&allow,info.base.s,info.treename.s) ; log_info(DRYRUN ? drun : "","save service(s) list of tree: ", info.treename.s) ; tree_contents(&contents,info.tree.s,&info) ; dbok = db_ok(info.livetree.s, info.treename.s) ; if (dbok) { fdir = 0 ; log_trace(DRYRUN ? drun : "","find current source of live db: ",info.livetree.s,"/",info.treename.s) ; r = db_find_compiled_state(info.livetree.s,info.treename.s) ; if (r == -1) log_die(LOG_EXIT_SYS,"inconsistent state of: ",info.livetree.s) ; if (r == 1) { auto_strings(tmp,system,SS_BACKUP + 1,"/",info.treename.s,SS_DB) ; } else auto_strings(tmp,system,info.treename.s,SS_SVDIRS,SS_DB) ; log_trace(DRYRUN ? drun : "","created temporary directory at: /tmp") ; fdir = dir_create_tmp(&WORKDIR,"/tmp",info.treename.s) ; if (!fdir) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"create temporary directory") ; log_trace(DRYRUN ? drun : "","copy contents of: ",tmp," to: ",WORKDIR.s) ; if (!hiercopy(tmp,fdir)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"copy: ",tmp," to: ",WORKDIR.s) ; char new[WORKDIR.len + 1 + info.treename.len + 1] ; auto_strings(current,info.livetree.s,"/",info.treename.s,"/compiled") ; auto_strings(new,WORKDIR.s,"/",info.treename.s) ; log_info(DRYRUN ? drun : "","update ",info.livetree.s,"/",info.treename.s," to: ",new) ; if (!DRYRUN) if (!atomic_symlink(new, current, PROG)) log_dieusys(LOG_EXIT_SYS,"update: ",current," to: ", new) ; if (DRYRUN) cleanup() ; } /** finally we can destroy the tree and recreate it*/ // destroy { char const *t[] = { "-l", info.live.s, "-R", info.treename.s } ; if (DRYRUN) { log_info(drun,"66-tree -l ",info.live.s," -R ",info.treename.s) ; } else { if (!run_cmdline(SS_EXTBINPREFIX "66-tree",t,4,envp)) log_dieu(LOG_EXIT_SYS,"delete tree: ", info.treename.s) ; } } //create { char const *t[] = { "-l", info.live.s, tree_opts_create,"-a",allow.s, info.treename.s } ; if (DRYRUN) { log_info(drun,"66-tree -l ",info.live.s," ",tree_opts_create," -a ",allow.s," ",info.treename.s) ; } else { if (!run_cmdline(SS_EXTBINPREFIX "66-tree",t,6,envp)) log_dieu(LOG_EXIT_SYS,"create tree: ", info.treename.s) ; } } // reorganize the trees start order if (tree_enabled.len) { if (!sastr_split_string_in_nline(&tree_enabled)) log_dieu(LOG_EXIT_SYS,"split elements") ; unsigned int enabled ; ssize_t r = sastr_find_element_byname(&tree_enabled,info.treename.s,&enabled) ; if (r >= 0) { char *after_tree = tree_enabled.s + sastr_find_element_byid(&tree_enabled,!enabled ? enabled : enabled - 1) ; char const *t[] = { "-S", after_tree, info.treename.s } ; if (DRYRUN) { log_info(drun,"66-tree -S ",after_tree," ",info.treename.s) ; } else { if (!run_cmdline(SS_EXTBINPREFIX "66-tree",t,3,envp)) log_dieu(LOG_EXIT_SYS,"orders the start order of tree: ", info.treename.s) ; } } } /* we must reimplement the enable process instead of * using directly 66-enable. The 66-enable program will use * is own workdir with an empty tree. At call of db_update(), * the service already running will be brought down. We don't * want this behavior. A nope update is necessary. * So remake the things with the enable API */ if (contents.len) { stralloc tostart = STRALLOC_ZERO ; log_info(DRYRUN ? drun : "","enable service(s) of tree: ",info.treename.s) ; if (!DRYRUN) { auto_strings(tmp,system,info.treename.s,SS_SVDIRS) ; } else { WORKDIR.len = 0 ; log_trace(drun,"copy: ", info.tree.s,SS_SVDIRS," to a temporary directory") ; if (!tree_copy(&WORKDIR,info.tree.s,info.treename.s)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"create tmp working directory") ; /* we need to remove the contain of .resolve directory * The write process will try to read it and obviously it * get a wrong information*/ auto_strings(tmp,WORKDIR.s,SS_RESOLVE) ; log_trace(drun,"remove directory: ", tmp) ; if (rm_rf(tmp) < 0) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"remove: ",tmp) ; log_trace(drun,"create directory: ", tmp) ; if (!dir_create_parent(tmp,0755)) log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"create directory: ",tmp) ; auto_string_from(tmp,0,WORKDIR.s) ; } start_parser(&contents,&info,&nbsv,1) ; start_write(&tostart,&nclassic,&nlongrun,tmp,&gasv,&info,1,0) ; /** we don't care about nclassic. Classic service are copies * of original and we retrieve the original at the end of the * process*/ if(nlongrun) { ss_resolve_graph_t graph = RESOLVE_GRAPH_ZERO ; r = ss_resolve_graph_src(&graph,tmp,0,1) ; if (!r) log_dieu(LOG_EXIT_SYS,"resolve source of graph for tree: ",info.treename.s) ; r = ss_resolve_graph_publish(&graph,0) ; if (r <= 0) { if (r < 0) log_die(LOG_EXIT_USER,"cyclic graph detected") ; log_dieusys(LOG_EXIT_SYS,"publish service graph") ; } if (!ss_resolve_write_master(&info,&graph,tmp,0)) log_dieusys(LOG_EXIT_SYS,"update inner bundle") ; ss_resolve_graph_free(&graph) ; if (!db_compile(tmp,info.tree.s,info.treename.s,envp)) log_dieu(LOG_EXIT_SYS,"compile ",tmp,"/",info.treename.s) ; } stralloc_free(&tostart) ; freed_parser() ; genalloc_free(sv_alltype,&gasv) ; } if (dbok) { auto_strings(tmp,system,info.treename.s,SS_SVDIRS,SS_DB) ; log_info(DRYRUN ? drun : "","update db: ",info.livetree.s,"/",info.treename.s, " to: ",tmp,"/",info.treename.s) ; /* Be paranoid here and use db_update instead of atomic_symlink. * db_update() allow to make a running test and to see * if the db match exactly the same state. * We prefer to die in this case instead of leaving an * inconsistent state. */ if (!DRYRUN) if (!db_update(tmp,&info,envp)) log_dieu(LOG_EXIT_SYS,"update: ",info.livetree.s,"/",info.treename.s," to: ", tmp,"/",info.treename.s) ; } cleanup() ; log_info(DRYRUN ? drun : "","tree: ",info.treename.s," rebuild successfully") ; } exit: stralloc_free(&satree) ; stralloc_free(&allow) ; stralloc_free(&WORKDIR) ; stralloc_free(&contents) ; stralloc_free(&tree_enabled) ; ssexec_free(&info) ; return 0 ; }