Skip to content
Snippets Groups Projects
ssexec_tree_admin.c 35 KiB
Newer Older
 * ssexec_tree_admin.c
Eric Vidal's avatar
Eric Vidal committed
 * Copyright (c) 2018-2024 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 <66/tree.h>

#include <string.h>
#include <stdint.h>//uintx_t
#include <sys/stat.h>
#include <stdio.h>//rename
#include <pwd.h>

#include <oblibs/log.h>
#include <oblibs/types.h>
#include <oblibs/directory.h>
#include <oblibs/files.h>
#include <oblibs/string.h>
#include <oblibs/sastr.h>
#include <skalibs/sgetopt.h>
#include <skalibs/stralloc.h>
#include <skalibs/djbunix.h>
#include <skalibs/bytestr.h>//byte_count
#include <skalibs/posixplz.h>//unlink_void

#include <66/config.h>
#include <66/utils.h>
#include <66/constants.h>
#include <66/enum.h>
#include <66/state.h>
#include <66/service.h>
#include <66/resolve.h>
#define TREE_COLON_DELIM ':'
#define TREE_COMMA_DELIM ','
#define tree_checkopts(n) if (n >= TREE_MAXOPTS) log_die(LOG_EXIT_USER, "too many -o options")
typedef struct tree_opts_map_s tree_opts_map_t ;
struct tree_opts_map_s
{
    char const *str ;
    int const id ;
} ;

typedef enum enum_tree_opts_e enum_tree_opts_t, *enum_tree_opts_t_ref ;
enum enum_ns_opts_e
{
    TREE_OPTS_DEPENDS = 0,
    TREE_OPTS_REQUIREDBY,
    TREE_OPTS_RENAME,
    TREE_OPTS_GROUPS,
    TREE_OPTS_NOSEED,
    TREE_OPTS_ALLOW,
    TREE_OPTS_DENY,
    TREE_OPTS_CLONE,
    TREE_OPTS_ENDOFKEY
} ;

tree_opts_map_t const tree_opts_table[] =
{
    { .str = "depends",     .id = TREE_OPTS_DEPENDS },
    { .str = "requiredby",  .id = TREE_OPTS_REQUIREDBY },
    { .str = "rename",      .id = TREE_OPTS_RENAME },
    { .str = "groups",      .id = TREE_OPTS_GROUPS },
    { .str = "noseed",      .id = TREE_OPTS_NOSEED },
    { .str = "allow",       .id = TREE_OPTS_ALLOW },
    { .str = "deny",        .id = TREE_OPTS_DENY },
    { .str = "clone",       .id = TREE_OPTS_CLONE },
typedef struct tree_what_s tree_what_t, *tree_what_t_ref ;
struct tree_what_s
    uint8_t create ;
    uint8_t depends ;
    uint8_t requiredby ;
    uint8_t allow ;
    uint8_t deny ;
    uint8_t enable ;
    uint8_t disable ;
    uint8_t remove ;
    uint8_t clone ;
    uint8_t groups ;
    uint8_t current ;
    uint8_t rename ;
    uint8_t noseed ;

    char gr[6] ;
    char sclone[100] ;
    uid_t auids[256] ;
    uid_t duids[256] ;
    uint8_t ndepends ; // only used if the term none is passed as dependencies
    uint8_t nrequiredby ; // only used if the term none is passed as dependencies

    uint8_t nopts ;
#define TREE_WHAT_ZERO { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, { 0 }, { 0 }, { 0 }, { 0 }, 1, 1, 0 }

tree_what_t what_init(void)
{
    log_flow() ;

    tree_what_t what = TREE_WHAT_ZERO ;

    memset(what.gr, 0, 6 * sizeof(char)); ;
    memset(what.auids, 0, 256 * sizeof(uid_t));
    memset(what.duids, 0, 256 * sizeof(uid_t)) ;

    return what ;
}

void tree_enable_disable(graph_t *g, char const *base, char const *treename, uint8_t action) ;
static void check_identifier(char const *name)
{
    if (!memcmp(name, SS_MASTER + 1, 6))
        log_die(LOG_EXIT_USER,"tree name: ",name,": starts with reserved prefix Master") ;

    char str[UINT_FMT] ;
    str[uint_fmt(str, SS_MAX_TREENAME)] = 0 ;

    if (strlen(name) > SS_MAX_TREENAME)
        log_die(LOG_EXIT_USER,"tree name is too long -- it can not exceed ", str) ;

static ssize_t tree_get_key(char *table,char const *str)
{
    ssize_t pos = -1 ;

    pos = get_len_until(str,'=') ;

    if (pos == -1)
        return -1 ;

    auto_strings(table,str) ;

    table[pos] = 0 ;

    pos++ ; // remove '='

    return pos ;
}

static void tree_parse_options_groups(char *store, char const *str)
    if (strcmp(str, TREE_GROUPS_BOOT) &&
        strcmp(str, TREE_GROUPS_ADM) &&
        strcmp(str, TREE_GROUPS_USER) &&
        strcmp(str, "none"))
        log_die(LOG_EXIT_SYS, "invalid group: ", str) ;
    if (!uid && (!strcmp(str, TREE_GROUPS_USER)))
        log_die(LOG_EXIT_SYS, "Only regular user can use this group") ;
    else if (uid && (!strcmp(str, TREE_GROUPS_ADM) || !strcmp(str, TREE_GROUPS_BOOT)))
        log_die(LOG_EXIT_SYS, "Only root user can use this group") ;
void tree_parse_uid_list(uid_t *uids, char const *str)
{
    log_flow() ;

    size_t pos = 0 ;
    stralloc sa = STRALLOC_ZERO ;

    if (!sastr_clean_string_wdelim(&sa, str, TREE_COMMA_DELIM))
        log_dieu(LOG_EXIT_SYS,"parse uid list") ;

    /** special case, we don't know which user want to use
     *  the tree, we need a general name to allow all users.
     *  The term "user" is took here to allow the current user*/
    ssize_t p = sastr_cmp(&sa, "user") ;

    FOREACH_SASTR(&sa, pos) {

        if (pos == (size_t)p) {

            struct passwd *pw = getpwuid(owner);

            if (!pw) {

                if (!errno) errno = ESRCH ;
                    log_dieu(LOG_EXIT_SYS,"get user name") ;
            }

            if (!scan_uidlist(pw->pw_name, uids))
                log_dieu(LOG_EXIT_USER,"scan account: ",pw->pw_name) ;

            continue ;
        }

        if (!scan_uidlist(sa.s + pos, uids))
            log_dieu(LOG_EXIT_USER,"scan account: ",sa.s + pos) ;

    }

static void tree_parse_options_depends(graph_t *g, ssexec_t *info, char const *str, uint8_t requiredby, tree_what_t *what)
Eric Vidal's avatar
Eric Vidal committed
    if (!*str)
        return ;

    stralloc sa = STRALLOC_ZERO ;

    if (!sastr_clean_string_wdelim(&sa, str, TREE_COMMA_DELIM))
        log_dieu(LOG_EXIT_SYS,"clean sub options") ;
    if (sastr_cmp(&sa, "none") >= 0) {
        if (!requiredby)
            what->ndepends = 0 ;
        else
            what->nrequiredby = 0 ;
        name = sa.s + pos ;

        r = tree_isvalid(info->base.s, name) ;
        if (r < 0)
            log_diesys(LOG_EXIT_SYS, "invalid treename") ;
        /** We only creates trees declared as dependency.
         * TreeA depends on TreeB, we create TreeB
         * if it doesn't exist yet */
        if (!r && !requiredby) {

            ssexec_t newinfo = SSEXEC_ZERO ;
            if (!auto_stra(&newinfo.base, info->base.s) ||
                !auto_stra(&newinfo.treename, name))
                    log_die_nomem("stralloc") ;
            newinfo.owner = info->owner ;
            newinfo.prog = info->prog ;
            newinfo.help = info->help ;
            newinfo.usage = info->usage ;
            newinfo.opt_color = info->opt_color ;
Eric Vidal's avatar
Eric Vidal committed
            newinfo.opt_tree = info->opt_tree ;


            int nwhat = what->noseed ? 2 : 0 ;
            int nargc = 3 + nwhat ;
Eric Vidal's avatar
Eric Vidal committed
            char const *prog = PROG ;
            char const *newargv[nargc] ;
            uint8_t m = 0 ;
            newargv[m++] = "tree" ;

            if (nwhat) {
                newargv[m++] = "-o" ;
                newargv[m++] = "noseed" ;
            }
Eric Vidal's avatar
Eric Vidal committed
            log_trace("launch 66 tree sub-process for tree: ", name) ;
            PROG = "tree (child)" ;
            if (ssexec_tree_admin(nargc, newargv, &newinfo))
                log_dieusys(LOG_EXIT_SYS, "create tree: ", name) ;
            if (!graph_vertex_add_with_edge(g, info->treename.s, name))
                log_die(LOG_EXIT_SYS,"add edge: ", name, " to vertex: ", info->treename.s) ;
        } else if (r) {
            /** if TreeA is requiredby TreeB, we don't want to create TreeB.
             * We only manages it if it exist yet */
            if (!graph_vertex_add_with_requiredby(g, info->treename.s, name))
                log_die(LOG_EXIT_SYS,"add requiredby: ", name, " to: ", info->treename.s) ;
        }
static void tree_parse_options(graph_t *g, char const *str, ssexec_t *info, tree_what_t *what)
Eric Vidal's avatar
Eric Vidal committed
    if (!*str)

    size_t pos = 0, len = 0 ;
    ssize_t r ;
    char *line = 0, *key = 0, *val = 0 ;
    stralloc sa = STRALLOC_ZERO ;
    if (!sastr_clean_string_wdelim(&sa, str, TREE_COLON_DELIM))
        log_dieu(LOG_EXIT_SYS,"clean options") ;

    unsigned int n = sastr_len(&sa), nopts = 0 , old ;

    tree_checkopts(n) ;

    FOREACH_SASTR(&sa, pos) {

        line = sa.s + pos ;
        t = tree_opts_table ;
        old = nopts ;

        for (; t->str ; t++) {

            len = strlen(line) ;
            char tmp[len + 1] ;

            r = tree_get_key(tmp,line) ;
                log_die(LOG_EXIT_USER,"invalid key: ", line) ;

            if (!strcmp(line, "noseed")) {
                key = line ;
            } else {
                key = tmp ;
                val = line + r ;
            }

            if (!strcmp(key, t->str)) {

                switch(t->id) {

                    case TREE_OPTS_DEPENDS :
                        tree_parse_options_depends(g, info, val, 0, what) ;
                        what->depends = 1 ;
                        break ;
                        tree_parse_options_depends(g, info, val, 1, what) ;
                        what->requiredby = 1 ;
                        break ;
                        tree_parse_options_groups(what->gr, val) ;
                        what->groups = 1 ;
                        what->noseed = 1 ;
                        break ;

                    case TREE_OPTS_ALLOW:

                        tree_parse_uid_list(what->auids, val) ;
                        what->allow = 1 ;
                        break ;

                    case TREE_OPTS_DENY:

                        tree_parse_uid_list(what->duids, val) ;
                        what->deny = 1 ;
                        break ;

                   case TREE_OPTS_CLONE:
                        if (strlen(val) > 99)
                            log_die(LOG_EXIT_USER, "clone name cannot exceed 100 characters") ;

                        auto_strings(what->sclone, val) ;
                        what->clone = 1 ;
                        break ;

                    default :

                        break ;
                }
                nopts++ ;
            }
        }

        if (old == nopts)
            log_die(LOG_EXIT_SYS,"invalid option: ",line) ;
    }
void tree_parse_seed(char const *treename, tree_seed_t *seed, tree_what_t *what)
    log_trace("checking seed file: ", treename, "..." ) ;
        if (!tree_seed_setseed(seed, treename))
            log_dieu(LOG_EXIT_SYS, "parse seed file: ", treename) ;
        if (seed->requiredby)
            what->requiredby = 1 ;
        if (seed->allow > 0) {

            tree_parse_uid_list(what->auids, seed->sa.s + seed->allow) ;
            what->allow = 1 ;
            tree_parse_uid_list(what->duids, seed->sa.s + seed->deny) ;
            what->deny = 1 ;
        }
            tree_parse_options_groups(what->gr, seed->sa.s + seed->groups) ;
            what->groups = 1 ;
        }
    }
}
void tree_groups(graph_t *graph, char const *base, char const *treename, char const *value)
    resolve_tree_t tres = RESOLVE_TREE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE, &tres) ;
    size_t nb = 0 ;
    log_trace("set: ", treename," to group ..." ) ;
    } else if (!strcmp(value, "boot")) {
        /** a tree on groups boot cannot be enabled */
        tree_enable_disable(graph, base, treename, 0) ;
    }
    nb = 1 ;
    val = value ;

    write:

    uint_pack(pack, nb) ;
    pack[uint_fmt(pack, nb)] = 0 ;

    if (resolve_read_g(wres, base, treename) <= 0)
        log_dieusys(LOG_EXIT_SYS, "read resolve file of: ", treename) ;
    if (!resolve_modify_field(wres, E_RESOLVE_TREE_GROUPS, val) ||
        !resolve_modify_field(wres, E_RESOLVE_TREE_NGROUPS, pack))
            log_dieusys(LOG_EXIT_SYS, "modify resolve file of: ", treename) ;

    if (!resolve_write_g(wres, base, treename))
        log_dieusys(LOG_EXIT_SYS, "write resolve file of: ", treename) ;

    resolve_free(wres) ;

    log_info("Set successfully: ", treename, " to group: ", value) ;
}

void tree_master_modify_contents(char const *base)
    stralloc sa = STRALLOC_ZERO ;
    resolve_tree_master_t mres = RESOLVE_TREE_MASTER_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE_MASTER, &mres) ;
    size_t baselen = strlen(base) ;
    char solve[baselen + SS_SYSTEM_LEN + SS_RESOLVE_LEN + 1] ;

    char const *exclude[2] = { SS_MASTER + 1, 0 } ;

    log_trace("modify field contents of resolve Master file of trees") ;

    auto_strings(solve, base, SS_SYSTEM, SS_RESOLVE) ;

    if (!sastr_dir_get(&sa, solve, exclude, S_IFREG))
        log_dieu(LOG_EXIT_SYS, "get resolve files of trees") ;
    size_t ncontents = sa.len ? sastr_nelement(&sa) : 0 ;

    if (ncontents)
        if (!sastr_rebuild_in_oneline(&sa))
            log_dieu(LOG_EXIT_SYS, "rebuild stralloc") ;
    if (resolve_read_g(wres, base, SS_MASTER + 1) <= 0)
        log_dieusys(LOG_EXIT_SYS, "read resolve Master file of trees") ;

    mres.ncontents = (uint32_t)ncontents ;

    if (ncontents)
        mres.contents = resolve_add_string(wres, sa.s) ;
    else
        mres.contents = resolve_add_string(wres, "") ;

    if (!resolve_write_g(wres, base, SS_MASTER + 1))
        log_dieusys(LOG_EXIT_SYS, "write resolve Master file of trees") ;

    stralloc_free(&sa) ;
    resolve_free(wres) ;
}

void tree_create(graph_t *g, ssexec_t *info, tree_what_t *what)
{
    log_flow() ;

    resolve_tree_t tres = RESOLVE_TREE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE, &tres) ;
    tree_seed_t seed = TREE_SEED_ZERO ;

    resolve_init(wres) ;

    /** check seed file */
    if (!what->noseed)
        tree_parse_seed(info->treename.s, &seed, what) ;

    log_trace("creating: ", info->treename.s, "..." ) ;

    // set permissions
    what->allow = 1 ;

    tres.name = resolve_add_string(wres, info->treename.s) ;
    tres.groups = resolve_add_string(wres, info->owner ? TREE_GROUPS_USER : TREE_GROUPS_ADM) ;
    tres.ngroups = 1 ;

    log_trace("write resolve file of: ", info->treename.s) ;
    if (!resolve_write_g(wres, info->base.s, info->treename.s))
        log_dieu(LOG_EXIT_SYS, "write resolve file of: ", info->treename.s) ;
    /** Check the length of seed.sa.len: If the seed file is not parsed at this point,
     * seed.sa.s + seed.depends is empty, which can lead to a segmentation fault
     * when the -o option is passed at the command line. However, we have already gone
     * through the tree_parse_options_depends in such cases. */
    if (what->depends && seed.sa.len)
        tree_parse_options_depends(g, info, seed.sa.s + seed.depends, 0, what) ;

    if (what->requiredby && seed.sa.len)
        tree_parse_options_depends(g, info, seed.sa.s + seed.requiredby, 1, what) ;

    tree_master_modify_contents(info->base.s) ;
    resolve_free(wres) ;
    tree_seed_free(&seed) ;

    log_info("Created successfully tree: ", info->treename.s) ;
}

void tree_enable_disable_deps(graph_t *g,char const *base, char const *treename, uint8_t action)
{
    log_flow() ;

Eric Vidal's avatar
Eric Vidal committed
    size_t pos = 0, element = 0 ;
    if (graph_matrix_get_edge_g_sa(&sa, g, treename, action ? 0 : 1, 0) < 0)
        log_dieu(LOG_EXIT_SYS, "get ", action ? "dependencies" : "required by" ," of: ", treename) ;

    size_t len = sastr_nelement(&sa) ;
Eric Vidal's avatar
Eric Vidal committed
    unsigned int v[len + 1] ;

    memset(v, 0, (len + 1) * sizeof(unsigned int)) ;
            if (!v[element]) {

                char *name = sa.s + pos ;

                tree_enable_disable(g, base, name, action) ;

Eric Vidal's avatar
Eric Vidal committed
            element++ ;
        }
    }

    stralloc_free(&sa) ;
}

/** @action -> 0 disable
 * @action -> 1 enable */
void tree_enable_disable(graph_t *g, char const *base, char const *treename, uint8_t action)
{
    log_flow() ;

    resolve_tree_t tres = RESOLVE_TREE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE, &tres) ;

    if (resolve_read_g(wres, base, treename) <= 0)
        log_dieusys(LOG_EXIT_SYS, "read resolve file of: ", treename) ;

    uint8_t disen = tres.enabled ;
    if ((disen && !action) || (!disen && action)){

        log_trace(!action ? "disable " : "enable ", treename, "...") ;

        if (tree_ongroups(base, treename, TREE_GROUPS_BOOT) && action) {
            log_1_warn(treename," is a part of group ", TREE_GROUPS_BOOT," -- ignoring enable request") ;
            return ;
        tres.enabled = action ;
        if (!resolve_write_g(wres, base, treename))
            log_dieusys(LOG_EXIT_SYS, "write resolve file of: ", treename) ;

        tree_enable_disable_deps(g, base, treename, action) ;
        log_info(!action ? "Disabled" : "Enabled"," successfully tree: ", treename) ;
        log_info("Already ",!action ? "disabled" : "enabled"," tree: ",treename) ;
/* !deps -> add
 * deps -> remove */
void tree_depends_requiredby(graph_t *g, char const *base, char const *treename, uint8_t requiredby, uint8_t none, char const *deps)
{
    log_flow() ;

    resolve_tree_t tres = RESOLVE_TREE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE, &tres) ;
    size_t pos = 0, len = 0, nb = 0, element = 0 ;
    uint8_t ewhat = !requiredby ? E_RESOLVE_TREE_DEPENDS : E_RESOLVE_TREE_REQUIREDBY ;
    uint8_t nwhat = !requiredby ? E_RESOLVE_TREE_NDEPENDS : E_RESOLVE_TREE_NREQUIREDBY ;
    stralloc sa = STRALLOC_ZERO ;
    char pack[UINT_FMT] ;

    log_trace("manage ", !requiredby ? "dependencies" : "required by", " for tree: ", treename, "..." ) ;

    if (graph_matrix_get_edge_g_sorted_sa(&sa, g, treename, requiredby, 0) < 0)
        log_dieu(LOG_EXIT_SYS,"get sorted ", requiredby ? "required by" : "dependency", " list of tree: ", treename) ;

    size_t vlen = sastr_nelement(&sa) ;
    unsigned int v[vlen + 1] ;
    memset(v, 0, (vlen + 1) * sizeof(unsigned int)) ;

    len = sa.len ;
    {
        char t[len + 1] ;

        sastr_to_char(t, &sa) ;

        sa.len = 0 ;
Eric Vidal's avatar
Eric Vidal committed
        for(; pos < len ; pos += strlen(t + pos) + 1, element++) {
            if (!v[element]) {
                    if (!graph_edge_remove_g(g, treename, name))
                       log_dieu(LOG_EXIT_SYS,"remove edge: ", name, " from vertex: ", treename);
                    if (!auto_stra(&sa, name, " "))
                        log_die_nomem("stralloc") ;
    if (sa.len)
        sa.len-- ; //remove last " "

    if (!stralloc_0(&sa))
        log_die_nomem("stralloc") ;
    uint_pack(pack, nb) ;
    pack[uint_fmt(pack, nb)] = 0 ;
    if (resolve_read_g(wres, base, treename) <= 0)
        log_dieusys(LOG_EXIT_SYS, "read resolve file of: ", treename) ;
    if (!resolve_modify_field(wres, ewhat, sa.s) ||
        !resolve_modify_field(wres, nwhat, pack))
            log_dieusys(LOG_EXIT_SYS, "modify resolve file of: ", treename) ;
    if (!resolve_write_g(wres, base, treename))
        log_dieusys(LOG_EXIT_SYS, "write resolve file of: ", treename) ;
        graph_free_matrix(g) ;
        graph_free_sort(g) ;

        if (!graph_matrix_build(g))
            log_die(LOG_EXIT_SYS, "build the graph") ;

        if (!graph_matrix_analyze_cycle(g))
            log_die(LOG_EXIT_SYS, "found cycle") ;

        if (!graph_matrix_sort(g))
            log_die(LOG_EXIT_SYS, "sort the graph") ;
    log_info(requiredby ? "Required by " : "Dependencies ", "successfully managed for tree: ", treename) ;
}
void tree_depends_requiredby_deps(graph_t *g, char const *base, char const *treename, uint8_t requiredby, uint8_t none, char const *deps)
{
    log_flow() ;
Eric Vidal's avatar
Eric Vidal committed
    size_t baselen = strlen(base), pos = 0, len = 0, element = 0 ;
    stralloc sa = STRALLOC_ZERO ;
    char solve[baselen + SS_SYSTEM_LEN + 1] ;
    if (graph_matrix_get_edge_g_sorted_sa(&sa, g, treename, requiredby, 0) < 0)
        log_dieu(LOG_EXIT_SYS,"get sorted ", requiredby ? "required by" : "dependency", " list of tree: ", treename) ;
    unsigned int v[vlen + 1] ;
    memset(v, 0, (vlen + 1) * sizeof(unsigned int)) ;
Eric Vidal's avatar
Eric Vidal committed
    for(; pos < len ; pos += strlen(t + pos) + 1, element++) {
        if (!v[element]) {
            tree_depends_requiredby(g, base, name, !requiredby, none, deps) ;
/** @what -> 0 deny
 * @what -> 1 allow */
void tree_rules(char const *base, char const *treename, uid_t *uids, uint8_t what)
{
    log_flow() ;
    size_t uidn = uids[0], pos = 0 ;
    uid_t owner = MYUID ;
    char pack[256] ;
    stralloc sa = STRALLOC_ZERO ;
    resolve_tree_t tres = RESOLVE_TREE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE, &tres) ;
    log_trace("set ", !what ? "denied" : "allowed", " user for tree: ", treename, "..." ) ;
    if (resolve_read_g(wres, base, treename)  <= 0)
        log_dieusys(LOG_EXIT_SYS, "read resolve file of: ", treename) ;

    if (tres.nallow)
        if (!sastr_clean_string(&sa, tres.sa.s + tres.allow))
            log_dieu(LOG_EXIT_SYS, "clean string") ;

    /** fresh creation of the tree */
    if (!tres.nallow) {

        if (!uids[0]) {

            uids[0] = 1 ;
            uids[1] = owner ;

        } else {
            /** command can be 66 tree -a <account> <tree>
             * where <tree> doesn't exist yet.
             * Keep the -a option value and append the owner
             * of the process at the end of the list. */
            uids[0]++ ;
            uids[uidn + 1] = owner ;
    for (; pos < uidn ; pos++) {
        uint32_pack(pack,uids[pos+1]) ;
        pack[uint_fmt(pack,uids[pos+1])] = 0 ;
        r = sastr_cmp(&sa, pack) ;
        if (r < 0 && what) {
            if (!sastr_add_string(&sa, pack))
                log_die_nomem("stralloc") ;
            tres.nallow++ ;
            log_trace("user: ", pack, " is allowed for tree: ", treename) ;
        } else if (r >= 0 && !what) {
                log_1_warn("you cannot deny yourself -- ignoring request") ;
            if (!sastr_remove_element(&sa, pack))
                log_dieu(LOG_EXIT_SYS, "remove: ", pack, " from list") ;
            tres.nallow-- ;
            log_trace("user: ", pack, " is denied for tree: ", treename) ;
    if (!sastr_rebuild_in_oneline(&sa))
        log_dieu(LOG_EXIT_SYS, "rebuild string") ;
    if (!resolve_modify_field(wres, E_RESOLVE_TREE_ALLOW, sa.s))
        log_dieusys(LOG_EXIT_SYS, "modify resolve file of: ", treename) ;

    if (!resolve_write_g(wres, base, treename))
        log_dieusys(LOG_EXIT_SYS, "write resolve file of: ", treename) ;

    stralloc_free(&sa) ;
    resolve_free(wres) ;

    log_info("Permissions rules set successfully for tree: ", treename) ;
}

static void tree_service_switch_contents(char const *base, char const *treesrc, char const *treedst, ssexec_t *info)
    size_t pos = 0 ;
    resolve_tree_t tres = RESOLVE_TREE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_TREE, &tres) ;
    resolve_service_t res = RESOLVE_SERVICE_ZERO ;
    resolve_wrapper_t_ref swres = resolve_set_struct(DATA_SERVICE, &res) ;
    stralloc sa = STRALLOC_ZERO ;
    if (!resolve_get_field_tosa_g(&sa, base, treesrc, DATA_TREE, E_RESOLVE_TREE_CONTENTS))
        log_dieu(LOG_EXIT_SYS, "get contents list of tree: ", treesrc) ;
    FOREACH_SASTR(&sa, pos) {
        log_trace("switch service: ", sa.s + pos, " to tree: ", treedst) ;

        tree_service_add(treedst, sa.s + pos, info) ;

        if (!resolve_modify_field_g(swres, base, sa.s + pos, E_RESOLVE_SERVICE_TREENAME, treedst))
            log_dieu(LOG_EXIT_SYS, "modify resolve file of: ", sa.s + pos) ;
    }
    stralloc_free(&sa) ;
    resolve_free(wres) ;
    resolve_free(swres) ;
void tree_remove(graph_t *g, char const *base, char const *treename, ssexec_t *info)
    int r ;
    char tree[SS_MAX_TREENAME + 1] ;
    char *current = SS_DEFAULT_TREENAME ;
    log_trace("delete: ", treename, "..." ) ;

    tree_enable_disable(g, base, treename, 0) ;

    /** depends */
    tree_depends_requiredby_deps(g, base, treename, 0, 1, treename) ;

    /** requiredby */
    tree_depends_requiredby_deps(g, base, treename, 1, 1, treename) ;
    if (tree_iscurrent(base, treename)) {
        /** This symlink must be valid in any case to avoid crashing the sanitize_system process.
         * If it is not valid, at least point it to the SS_DEFAULT_TREENAME,
         * as this tree is automatically created at every 66 command invocation
         * if it does not exist yet. */
        log_warn("tree ",treename, " is marked as default -- switch default to: ", SS_DEFAULT_TREENAME) ;

        if (!tree_switch_current(base, SS_DEFAULT_TREENAME))
            log_dieusys(LOG_EXIT_SYS,"set: ", SS_DEFAULT_TREENAME, " as default") ;

        log_info("Set successfully: ", SS_DEFAULT_TREENAME," as default") ;

    } else {

        r = tree_find_current(tree, base) ;
        if (r < 0)
            log_dieu(LOG_EXIT_SYS, "find default tree") ;

        if (r)
            current = tree ;
        else
            current = SS_DEFAULT_TREENAME ;
    tree_service_switch_contents(base, treename, current, info) ;

    log_trace("remove resolve file of tree: ", treename) ;
    resolve_remove_g(base, treename, DATA_TREE) ;

    tree_master_modify_contents(base) ;

    log_info("Deleted successfully: ", treename) ;
}

void tree_current(ssexec_t *info)
{
    log_trace("mark: ", info->treename.s," as default ..." ) ;

    if (!tree_switch_current(info->base.s, info->treename.s))
        log_dieusys(LOG_EXIT_SYS,"set: ", info->treename.s, " as default") ;

    log_info("Set successfully: ", info->treename.s," as default") ;

}

void tree_clone(char const *clone, ssexec_t *info)
{
    log_flow() ;