/*
 * ssexec_all.c
 *
 * Copyright (c) 2018-2020 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 <sys/stat.h>//S_IFREG,umask
#include <sys/types.h>//pid_t
#include <fcntl.h>//O_RDWR
#include <unistd.h>//dup2,setsid,chdir,fork
#include <sys/ioctl.h>
#include <stdint.h>//uint8_t
#include <stddef.h>//size_t
#include <stdlib.h>//realpath

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

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

#include <66/ssexec.h>
#include <66/constants.h>
#include <66/tree.h>
#include <66/utils.h>//scandir_ok
#include <66/db.h>

#include <s6-rc/s6rc-servicedir.h>

static inline unsigned int lookup (char const *const *table, char const *signal)
{
    unsigned int i = 0 ;
    for (; table[i] ; i++) if (!strcmp(signal, table[i])) break ;
    return i ;
}

static inline unsigned int parse_signal (char const *signal)
{
    static char const *const signal_table[] =
    {
        "up"
        "down",
        "unsupervise",
        0
    } ;
  unsigned int i = lookup(signal_table, signal) ;
  if (!signal_table[i]) log_usage(usage_all) ;
  return i ;
}

int all_doit(ssexec_t *info, unsigned int what, char const *const *envp)
{
    int r ;

    stralloc salist = STRALLOC_ZERO ;

    char ownerstr[UID_FMT] ;
    size_t ownerlen = uid_fmt(ownerstr,info->owner) ;
    ownerstr[ownerlen] = 0 ;

    char src[info->live.len + SS_STATE_LEN + 1 + ownerlen + 1 + info->treename.len + 1 + SS_LIVETREE_INIT_LEN + 1] ;
    auto_strings(src,info->live.s,SS_STATE,"/",ownerstr,"/",info->treename.s,"/",SS_LIVETREE_INIT) ;

    r = scan_mode(src,S_IFREG) ;
    if (r == -1) log_die(LOG_EXIT_SYS,src," conflicted format") ;
    if (!r) {
        log_warn ("uninitialized tree: ", info->treename.s) ;
        goto freed ;
    }

    src[info->live.len + SS_STATE_LEN + 1 + ownerlen + 1 + info->treename.len] = 0 ;

    if (!sastr_dir_get(&salist,src,SS_LIVETREE_INIT,S_IFREG))
        log_dieusys(LOG_EXIT_SYS,"get contents of directory: ",src) ;

    if (salist.len)
    {
        size_t pos = 0, len = sastr_len(&salist) ;
        int n = what == 2 ? 3 : 2 ;
        int nargc = n + len ;
        char const *newargv[nargc] ;
        unsigned int m = 0 ;

        newargv[m++] = "fake_name" ;
        if (what == 2)
            newargv[m++] = "-u" ;

        FOREACH_SASTR(&salist,pos) {

            newargv[m++] = salist.s + pos ;
        }

        newargv[m++] = 0 ;

        if (!what) {

            if (ssexec_start(nargc,newargv,envp,info))
                goto err ;

        } else {

            if (ssexec_stop(nargc,newargv,envp,info))
                goto err ;
        }
    }
    else
        log_info("Empty tree: ",info->treename.s," -- nothing to do") ;

    freed:
        stralloc_free(&salist) ;
        return 1 ;
    err:
        stralloc_free(&salist) ;
        return 0 ;
}

static void all_redir_fd(void)
{
    int fd ;
    while((fd = open("/dev/tty",O_RDWR|O_NOCTTY)) >= 0)
    {
        if (fd >= 3) break ;
    }
    dup2 (fd,0) ;
    dup2 (fd,1) ;
    dup2 (fd,2) ;
    fd_close(fd) ;

    if (setsid() < 0)
        log_dieusys(LOG_EXIT_SYS,"setsid") ;

    if ((chdir("/")) < 0)
        log_dieusys(LOG_EXIT_SYS,"chdir") ;

    ioctl(0,TIOCSCTTY,1) ;

    umask(022) ;
}

void all_unsupervise(ssexec_t *info, char const *const *envp,int what)
{
    size_t newlen = info->livetree.len + 1, pos = 0 ;

    char ownerstr[UID_FMT] ;
    size_t ownerlen = uid_fmt(ownerstr,info->owner) ;
    ownerstr[ownerlen] = 0 ;

    stralloc salist = STRALLOC_ZERO ;

    /** set what we need */
    char prefix[info->treename.len + 2] ;
    auto_strings(prefix,info->treename.s,"-") ;

    char livestate[info->live.len + SS_STATE_LEN + 1 + ownerlen + 1 + info->treename.len + 1] ;
    auto_strings(livestate,info->live.s,SS_STATE + 1,"/",ownerstr,"/",info->treename.s) ;

    /** bring down service */
    if (!all_doit(info,what,envp))
        log_warnusys("stop services") ;

    if (db_find_compiled_state(info->livetree.s,info->treename.s) >=0)
    {
        salist.len = 0 ;
        char livetree[newlen + info->treename.len + SS_SVDIRS_LEN + 1] ;
        auto_strings(livetree,info->livetree.s,"/",info->treename.s,SS_SVDIRS) ;

        if (!sastr_dir_get(&salist,livetree,"",S_IFDIR)) log_dieusys(LOG_EXIT_SYS,"get service list at: ",livetree) ;

        livetree[newlen + info->treename.len] = 0 ;

        pos = 0 ;
        FOREACH_SASTR(&salist,pos) {

            s6rc_servicedir_unsupervise(livetree,prefix,salist.s + pos,0) ;
        }

        char *realsym = realpath(livetree, 0) ;
        if (!realsym)
            log_dieusys(LOG_EXIT_SYS,"find realpath of: ",livetree) ;

        if (rm_rf(realsym) == -1)
            log_dieusys(LOG_EXIT_SYS,"remove: ", realsym) ;

        free(realsym) ;

        if (rm_rf(livetree) == -1)
            log_dieusys(LOG_EXIT_SYS,"remove: ", livetree) ;

        /** remove the symlink itself */
        unlink_void(livetree) ;
    }

    if (scandir_send_signal(info->scandir.s,"an") <= 0)
        log_dieusys(LOG_EXIT_SYS,"reload scandir: ",info->scandir.s) ;

    /** remove /run/66/state/uid/treename directory */
    log_trace("delete: ",livestate,"..." ) ;
    if (rm_rf(livestate) < 0)
        log_dieusys(LOG_EXIT_SYS,"delete ",livestate) ;

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

    stralloc_free(&salist) ;
}

int ssexec_all(int argc, char const *const *argv,char const *const *envp,ssexec_t *info)
{

    int r, what, shut = 0, fd ;

    size_t statesize, pos = 0 ;

    stralloc contents = STRALLOC_ZERO ;

    {
        subgetopt_t l = SUBGETOPT_ZERO ;

        for (;;)
        {
            int opt = getopt_args(argc,argv, ">f", &l) ;
            if (opt == -1) break ;
            if (opt == -2) log_die(LOG_EXIT_USER,"options must be set first") ;
            switch (opt)
            {
                case 'f' :  shut = 1 ; break ;
                default :   log_usage(usage_all) ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }

    if (argc != 1) log_usage(usage_all) ;

    what = parse_signal(*argv) ;

    if ((scandir_ok(info->scandir.s)) <= 0)
        log_die(LOG_EXIT_SYS,"scandir: ", info->scandir.s," is not running") ;

    char ste[info->base.len + SS_SYSTEM_LEN + SS_STATE_LEN + 1] ;
    auto_strings(ste,info->base.s,SS_SYSTEM,SS_STATE) ;

    r = scan_mode(ste,S_IFREG) ;
    if (r < 0) log_die(LOG_EXIT_SYS,"conflict format for: ",ste) ;
    if (!r) log_dieusys(LOG_EXIT_SYS,"find: ",ste) ;

    /** only one tree?*/
    if (info->treename.len)
    {
        if (!auto_stra(&contents,info->treename.s))
            log_die_nomem("stralloc") ;
    }
    else
    {
        statesize = file_get_size(ste) ;

        r = openreadfileclose(ste,&contents,statesize) ;
        if(!r) log_dieusys(LOG_EXIT_SYS,"open: ", ste) ;

        /** ensure that we have an empty line at the end of the string*/
        if (!stralloc_cats(&contents,"\n") ||
        !stralloc_0(&contents)) log_die_nomem("stralloc") ;

        if (!sastr_clean_element(&contents)) {
            log_info("nothing to do") ;
            goto end ;
        }

    }

    if (shut)
    {
        pid_t dpid ;
        int wstat = 0 ;

        dpid = fork() ;

        if (dpid < 0) log_dieusys(LOG_EXIT_SYS,"fork") ;
        else if (dpid > 0)
        {
            if (waitpid_nointr(dpid,&wstat, 0) < 0)
                log_dieusys(LOG_EXIT_SYS,"wait for child") ;

            if (wstat)
                log_die(LOG_EXIT_SYS,"child fail") ;

            goto end ;
        }
        else all_redir_fd() ;
    }

    /** Down/unsupervise process? reverse in that case to respect tree start order*/
    if (what)
        if (!sastr_reverse(&contents)) log_dieu(LOG_EXIT_SYS,"reserve tree order") ;

    FOREACH_SASTR(&contents,pos) {

        info->treename.len = 0 ;

        if (!auto_stra(&info->treename,contents.s + pos))
            log_die_nomem("stralloc") ;

        info->tree.len = 0 ;

        if (!auto_stra(&info->tree,contents.s + pos))
            log_die_nomem("stralloc") ;

        r = tree_sethome(&info->tree,info->base.s,info->owner) ;
        if (r < 0 || !r) log_dieusys(LOG_EXIT_SYS,"find tree: ", info->treename.s) ;

        if (!tree_get_permissions(info->tree.s,info->owner))
            log_die(LOG_EXIT_USER,"You're not allowed to use the tree: ",info->tree.s) ;

        if (!what)
        {
            int nargc = 3 ;
            char const *newargv[nargc] ;
            unsigned int m = 0 ;

            newargv[m++] = "fake_name" ;
            newargv[m++] = "b" ;
            newargv[m++] = 0 ;

            if (ssexec_init(nargc,newargv,envp,info))
                log_dieu(LOG_EXIT_SYS,"initiate services of tree: ",info->treename.s) ;

            log_trace("reload scandir: ",info->scandir.s) ;
            if (scandir_send_signal(info->scandir.s,"an") <= 0)
                log_dieusys(LOG_EXIT_SYS,"reload scandir: ",info->scandir.s) ;
        }

        if (what < 2) {

            if (!all_doit(info,what,envp))
                log_dieu(LOG_EXIT_SYS,(what) ? "start" : "stop" , " services of tree: ",info->treename.s) ;

        } else {

            all_unsupervise(info,envp,what) ;
        }
    }
    end:
        if (shut)
        {
            while((fd = open("/dev/tty",O_RDWR|O_NOCTTY)) >= 0)
            {
                if (fd >= 3) break ;
            }
            dup2 (fd,0) ;
            dup2 (fd,1) ;
            dup2 (fd,2) ;
            fd_close(fd) ;
        }

        stralloc_free(&contents) ;

    return 0 ;
}