Skip to content
Snippets Groups Projects
ssexec_disable.c 12.14 KiB
/*
 * ssexec_disable.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 <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>

#include <oblibs/obgetopt.h>
#include <oblibs/log.h>
#include <oblibs/string.h>
#include <oblibs/sastr.h>

#include <skalibs/stralloc.h>
#include <skalibs/genalloc.h>
#include <skalibs/djbunix.h>

#include <66/constants.h>
#include <66/tree.h>
#include <66/db.h>
#include <66/resolve.h>
#include <66/svc.h>
#include <66/state.h>
#include <66/utils.h>
#include <66/service.h>

static stralloc workdir = STRALLOC_ZERO ;
static uint8_t FORCE = 0 ;
static uint8_t REMOVE = 0 ;

static void cleanup(void)
{
    log_flow() ;

    int e = errno ;
    rm_rf(workdir.s) ;
    errno = e ;
}

int svc_remove(genalloc *tostop,resolve_service_t *res, char const *src,ssexec_t *info)
{
    log_flow() ;

    unsigned int i = 0 ;
    int r, e = 0 ;
    genalloc rdeps = GENALLOC_ZERO ;
    stralloc dst = STRALLOC_ZERO ;
    resolve_service_t cp = RESOLVE_SERVICE_ZERO ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_SERVICE, &cp) ;
    ss_state_t sta = STATE_ZERO ;

    size_t newlen ;
    char *name = res->sa.s + res->name ;
    if (!service_resolve_copy(&cp,res))
    {
        log_warnusys("copy resolve file") ;
        goto err ;
    }
    if (!stralloc_cats(&dst,src)) goto err ;
    if (cp.type == TYPE_CLASSIC)
    {
        if (!stralloc_cats(&dst,SS_SVC)) goto err ;
    }
    else if (!stralloc_cats(&dst,SS_DB SS_SRC)) goto err ;

    if (!stralloc_cats(&dst,"/")) goto err ;

    newlen = dst.len ;

    if (!FORCE)
    {
        if (!service_resolve_add_rdeps(&rdeps,&cp,src))
        {
            log_warnusys("resolve recursive dependencies of: ",name) ;
            goto err ;
        }
    }
    else
    {
        if (!resolve_append(&rdeps,wres)) goto err ;
    }

    if (!service_resolve_add_logger(&rdeps,src))
    {
        log_warnusys("resolve logger") ;
        goto err ;
    }

    resolve_free(wres) ;

    for (;i < genalloc_len(resolve_service_t,&rdeps) ; i++)
    {
        resolve_service_t_ref pres = &genalloc_s(resolve_service_t,&rdeps)[i] ;
        resolve_wrapper_t_ref dwres = resolve_set_struct(DATA_SERVICE, pres) ;
        char *str = pres->sa.s ;
        char *name = str + pres->name ;
        char *ste = str + pres->state ;
        dst.len = newlen ;
        if (!stralloc_cats(&dst,name)) goto err ;
        if (!stralloc_0(&dst)) goto err ;

        log_trace("delete source service file of: ",name) ;
        if (rm_rf(dst.s) < 0)
        {
            log_warnusys("delete source service file: ",dst.s) ;
            goto err ;
        }
        /** r == -1 means the state file is not present,
         * r > 0 means service need to be initialized,
         * so not initialized at all.*/
        r = state_check_flags(ste,name,SS_FLAGS_INIT) ;

        if (!r)
        {
            /** modify the resolve file for 66-stop*/
            pres->disen = 0 ;
            log_trace("Write resolve file of: ",name) ;
            if (!resolve_write(dwres,src,name))
            {
                log_warnusys("write resolve file of: ",name) ;
                goto err ;
            }
            if (!state_read(&sta,ste,name)) {
                log_warnusys("read state of: ",name) ;
                goto err ;
            }
            state_setflag(&sta,SS_FLAGS_RELOAD,SS_FLAGS_FALSE) ;
            state_setflag(&sta,SS_FLAGS_INIT,SS_FLAGS_FALSE) ;
            state_setflag(&sta,SS_FLAGS_UNSUPERVISE,SS_FLAGS_TRUE) ;
            log_trace("Write state file of: ",name) ;
            if (!state_write(&sta,ste,name))
            {
                log_warnusys("write state file of: ",name) ;
                goto err ;
            }
        }
        else
        {
           if (REMOVE)
            {
                // remove configuration file
                log_trace("Delete configuration file of: ",name) ;
                if (rm_rf(str + pres->srconf) == -1)
                    log_warnusys("remove configuration file of: ",name) ;

                // remove the logger directory
                log_trace("Delete logger directory of: ",name) ;
                if (rm_rf(str + pres->dstlog) == -1)
                    log_warnusys("remove logger directory of: ",name) ;

                if (pres->type == TYPE_MODULE) {

                    // remove service source file
                    log_trace("Delete service file of: ",name) ;
                    if (rm_rf(str + pres->src) == -1)
                        log_warnusys("remove configuration file of: ",name) ;
                }

            }

            log_trace("Delete resolve file of: ",name) ;
            resolve_rmfile(src,name) ;
        }
        if (!resolve_cmp(tostop, name, DATA_SERVICE))
            if (!resolve_append(tostop,wres)) goto err ;

        free(dwres) ;
    }

    e = 1 ;

    err:
        resolve_free(wres) ;
        resolve_deep_free(DATA_SERVICE, &rdeps) ;
        stralloc_free(&dst) ;
        return e ;
}

int ssexec_disable(int argc, char const *const *argv,char const *const *envp,ssexec_t *info)
{
    int r, logname ;
    unsigned int nlongrun, nclassic, stop, force ;

    genalloc tostop = GENALLOC_ZERO ;//resolve_service_t
    genalloc gares = GENALLOC_ZERO ; //resolve_service_t
    resolve_service_t res = RESOLVE_SERVICE_ZERO ;
    resolve_service_t_ref pres ;
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_SERVICE, &res) ;

    r = nclassic = nlongrun = stop = logname = force = 0 ;

    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;)
        {
            int opt = getopt_args(argc,argv, ">" OPTS_DISABLE, &l) ;
            if (opt == -1) break ;
            if (opt == -2) log_die(LOG_EXIT_USER,"options must be set first") ;
            switch (opt)
            {
                case 'S' :  if (FORCE) log_usage(usage_disable) ; stop = 1 ; break ;
                case 'F' :  if (stop) log_usage(usage_disable) ; FORCE = 1 ; break ;
                case 'R' :  if (stop) log_usage(usage_disable) ; REMOVE = 1 ; break ;
                default :   log_usage(usage_disable) ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }

    if (argc < 1) log_usage(usage_disable) ;

    if (!tree_copy(&workdir,info->tree.s,info->treename.s))
        log_dieusys(LOG_EXIT_SYS,"create tmp working directory") ;

    for (;*argv;argv++)
    {
        char const *name = *argv ;
        if (!resolve_check(workdir.s,name))
            log_info_nclean_return(LOG_EXIT_ZERO,&cleanup,name," is not enabled") ;

        if (!resolve_read(wres,workdir.s,name))
            log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"read resolve file of: ",name) ;

        if (REMOVE)
        {
            stralloc sa = STRALLOC_ZERO ;
            r = service_intree(&sa,name,0) ;
            if (r > 2)
                log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"use -R option -- ",name," is set on different tree") ;
            stralloc_free(&sa) ;
        }

        if (res.type == TYPE_MODULE)
        {
            if (!module_in_cmdline(&gares,&res,workdir.s))
                log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"add dependencies of module: ",name) ;
        }
        else
        {
            if (!resolve_append(&gares,wres))
                log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"append services selection with: ",name) ;
        }
    }

    for(unsigned int i = 0 ; i < genalloc_len(resolve_service_t,&gares) ; i++)
    {
        pres = &genalloc_s(resolve_service_t,&gares)[i] ;
        char *string = pres->sa.s ;
        char  *name = string + pres->name ;
        char *state = string + pres->state ;
        uint8_t found = 0 ;
        char module_name[256] ;

        /** The force options can be only used if the service is not marked initialized.
         * This option should only be used when we have a inconsistent state between
         * the /var/lib/66/system/<tree>/servicedirs/ and /var/lib/66/system/<tree>/.resolve
         * directory meaning a service which is not present in the compiled db but its resolve file
         * exist.
         * Also, the remove option can be only used if the service is not marked initialized too.*/
        if (FORCE || REMOVE) {

            r = state_check_flags(state,name,SS_FLAGS_INIT) ;

            if (!r)
                log_die_nclean(LOG_EXIT_USER,&cleanup,name," is marked initialized -- ",FORCE ? "-F" : "-R"," is not allowed") ;
        }

        if (!module_search_service(workdir.s,&gares,name,&found,module_name))
            log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"search in module") ;

        if (found && !FORCE)
            log_die_nclean(LOG_EXIT_USER,&cleanup,name," is a part of: ",module_name," module -- it's not allowed to disable it alone") ;

        if (found && REMOVE)
            log_die_nclean(LOG_EXIT_USER,&cleanup,name," is a part of: ",module_name," module -- -R options is not allowed") ;

        logname = 0 ;
        if (obstr_equal(name,SS_MASTER + 1))
            log_die_nclean(LOG_EXIT_USER,&cleanup,"nice try peon") ;

        logname = get_rstrlen_until(name,SS_LOG_SUFFIX) ;
        if (logname > 0 && (!resolve_cmp(&gares, string + pres->logassoc, DATA_SERVICE)))
            log_die_nclean(LOG_EXIT_USER,&cleanup,"logger detected - disabling is not allowed") ;

        if (!pres->disen && !FORCE)
        {
            log_info(name,": is already disabled") ;
            continue ;
        }
        if (!svc_remove(&tostop,pres,workdir.s,info))
            log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"remove service: ",name) ;

        if (pres->type == TYPE_CLASSIC) nclassic++ ;
        else nlongrun++ ;
    }

    if (nclassic)
    {
        if (!svc_switch_to(info,SS_SWBACK))
            log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"switch classic service to backup") ;
    }

    if (nlongrun)
    {
        ss_resolve_graph_t graph = RESOLVE_GRAPH_ZERO ;
        r = ss_resolve_graph_src(&graph,workdir.s,0,1) ;
        if (!r)
            log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"resolve source of graph for tree: ",info->treename.s) ;

        r= ss_resolve_graph_publish(&graph,0) ;
        if (r <= 0)
        {
            cleanup() ;
            if (r < 0) log_die(LOG_EXIT_USER,"cyclic graph detected") ;
            log_dieusys(LOG_EXIT_SYS,"publish service graph") ;
        }
        if (!service_resolve_master_write(info,&graph,workdir.s,1))
            log_dieusys_nclean(LOG_EXIT_SYS,&cleanup,"update inner bundle") ;

        ss_resolve_graph_free(&graph) ;
        if (!db_compile(workdir.s,info->tree.s, info->treename.s,envp))
            log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"compile ",workdir.s,"/",info->treename.s) ;

        /** this is an important part, we call s6-rc-update here */
        if (!db_switch_to(info,envp,SS_SWBACK))
            log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"switch ",info->treename.s," to backup") ;
    }

    if (!tree_copy_tmp(workdir.s,info))
        log_dieu_nclean(LOG_EXIT_SYS,&cleanup,"copy: ",workdir.s," to: ", info->tree.s) ;

    cleanup() ;

    stralloc_free(&workdir) ;
    resolve_free(wres) ;
    resolve_deep_free(DATA_SERVICE, &gares) ;

    for (unsigned int i = 0 ; i < genalloc_len(resolve_service_t,&tostop); i++)
        log_info("Disabled successfully: ",genalloc_s(resolve_service_t,&tostop)[i].sa.s + genalloc_s(resolve_service_t,&tostop)[i].name) ;

    if (stop && genalloc_len(resolve_service_t,&tostop))
    {
        int nargc = 3 + genalloc_len(resolve_service_t,&tostop) ;
        char const *newargv[nargc] ;
        unsigned int m = 0 ;

        newargv[m++] = "fake_name" ;
        newargv[m++] = "-u" ;

        for (unsigned int i = 0 ; i < genalloc_len(resolve_service_t,&tostop); i++)
            newargv[m++] = genalloc_s(resolve_service_t,&tostop)[i].sa.s + genalloc_s(resolve_service_t,&tostop)[i].name ;

        newargv[m++] = 0 ;

        if (ssexec_stop(nargc,newargv,envp,info))
        {
            resolve_deep_free(DATA_SERVICE, &tostop) ;
            return 111 ;
        }
    }

    resolve_deep_free(DATA_SERVICE, &tostop) ;

    return 0 ;
}