/*
 * ssexec_scanctl.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 <fcntl.h>

#include <oblibs/log.h>
#include <oblibs/string.h>
#include <oblibs/environ.h>

#include <skalibs/sgetopt.h>
#include <skalibs/stralloc.h>
#include <skalibs/types.h>
#include <skalibs/djbunix.h>
#include <skalibs/exec.h>
#include <skalibs/env.h>
#include <skalibs/bytestr.h>

#include <s6/config.h>

#include <66/svc.h>
#include <66/utils.h>
#include <66/ssexec.h>

static char TMPENV[MAXENV + 1] ;

static inline unsigned int lookup (char const *const *table, char const *signal)
{
    log_flow() ;

    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)
{
    log_flow() ;

    static char const *const signal_table[] =
    {
        "start",
        "stop",
        "reload",
        "quit",
        "nuke",
        "zombies",
        0
    } ;
  unsigned int i = lookup(signal_table, signal) ;
  if (!signal_table[i]) i = 6 ;
  return i ;
}

static int send_signal(char const *scandir, char const *signal)
{
    log_flow() ;

    unsigned int sig = 0 ;
    size_t siglen = strlen(signal) ;
    char csig[siglen + 1] ;
    sig = parse_signal(signal) ;
    if (sig < 6)
    {
        switch(sig)
        {
            /** start signal, should never happens */
            case 0:

                return 1 ;

            case 1:

                csig[0] = 't' ;
                csig[1] = 0 ;
                break ;

            case 2:

                csig[0] = 'h' ;
                csig[1] = 0 ;
                break ;

            case 3:

                csig[0] = 'q' ;
                csig[1] = 0 ;
                break ;

            case 4:

                csig[0] = 'n' ;
                csig[1] = 0 ;
                break ;

            case 5:

                csig[0] = 'z' ;
                csig[1] = 0 ;
                break ;

            default: break ;
        }
    }
    else {
        auto_strings(csig,signal) ;
    }

    return svc_scandir_send(scandir,csig) ;
}

static void scandir_up(char const *scandir, unsigned int timeout, unsigned int notif, char const *const *envp)
{
    int r ;
    r = svc_scandir_ok(scandir) ;
    if (r < 0) log_dieusys(LOG_EXIT_SYS, "check: ", scandir) ;
    if (r)
    {
        log_trace("scandir: ",scandir," already running") ;
        return ;
    }

    unsigned int no = notif ? 2 : 0 ;
    char const *newup[6 + no] ;
    unsigned int m = 0 ;
    char fmt[UINT_FMT] ;
    fmt[uint_fmt(fmt, timeout)] = 0 ;
    char snotif[UINT_FMT] ;
    snotif[uint_fmt(snotif, notif)] = 0 ;

    newup[m++] = S6_BINPREFIX "s6-svscan" ;
    if (no) {
        newup[m++] = "-d" ;
        newup[m++] = snotif ;
    }
    newup[m++] = "-t" ;
    newup[m++] = fmt ;
    newup[m++] = "--" ;
    newup[m++] = scandir ;
    newup[m++] = 0 ;

    xexec_ae(newup[0], newup, envp) ;
}

int ssexec_scanctl(int argc, char const *const *argv, ssexec_t *info)
{
    int r ;
    uid_t owner = MYUID ;
    unsigned int timeout = 0, notif = 0, sig = 0 ;

    char const *newenv[MAXENV+1] ;
    char const *const *genv = 0 ;
    char const *const *genvp = (char const *const *)environ ;
    char const *signal ;

    stralloc scandir = STRALLOC_ZERO ;
    stralloc envdir = STRALLOC_ZERO ;

    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;) {

            int opt = subgetopt_r(argc,argv, OPTS_SCANCTL, &l) ;
            if (opt == -1) break ;

            switch (opt) {

                case 'o' :

                    if (MYUID)
                        log_die(LOG_EXIT_USER, "only root can use -o option") ;

                    if (!youruid(&owner,l.arg))
                        log_dieusys(LOG_EXIT_SYS,"get uid of: ",l.arg) ;

                    break ;

                case 'd' :

                    if (!uint0_scan(l.arg, &notif))
                        log_usage(usage_scanctl) ;

                    if (notif < 3)
                        log_die(LOG_EXIT_USER, "notification fd must be 3 or more") ;

                    if (fcntl(notif, F_GETFD) < 0)
                        log_diesys(LOG_EXIT_USER, "invalid notification fd") ;

                    break ;

                case 't' :

                    if (!uint0_scan(l.arg, &timeout))
                        log_usage(usage_scanctl) ;

                    break ;

                case 'e' :

                    if(!auto_stra(&envdir,l.arg))
                        log_die_nomem("stralloc") ;

                    break ;

                default :

                    log_usage(usage_scanctl) ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }

    if (argc < 1) log_usage(usage_scanctl) ;
    signal = argv[0] ;
    r = set_livedir(&scandir) ;
    if (r < 0) log_die(LOG_EXIT_USER,"live: ",scandir.s," must be an absolute path") ;
    if (!r) log_dieusys(LOG_EXIT_SYS,"set live directory") ;
    r = set_livescan(&scandir,owner) ;
    if (r < 0) log_die(LOG_EXIT_USER,"scandir: ", scandir.s, " must be an absolute path") ;
    if (!r) log_dieusys(LOG_EXIT_SYS,"set scandir directory") ;

    if (envdir.len)
    {
        stralloc modifs = STRALLOC_ZERO ;
        if (envdir.s[0] != '/')
            log_die(LOG_EXIT_USER,"environment: ",envdir.s," must be an absolute path") ;

        if (!environ_clean_envfile_unexport(&modifs,envdir.s))
            log_dieu(LOG_EXIT_SYS,"clean environment file of: ",envdir.s) ;


        size_t envlen = env_len(genvp) ;
        size_t n = env_len(genvp) + 1 + byte_count(modifs.s,modifs.len,'\0') ;
        size_t mlen = modifs.len ;
        memcpy(TMPENV,modifs.s,mlen) ;
        TMPENV[mlen] = 0 ;

        if (!env_merge(newenv, n, genvp, envlen, TMPENV, mlen))
            log_dieu(LOG_EXIT_SYS,"merge environment") ;

        stralloc_free(&modifs) ;

        genv = newenv ;
    }
    else genv = genvp ;

    sig = parse_signal(signal) ;

    if (!sig) {

        char scan[scandir.len + 1] ;
        auto_strings(scan,scandir.s) ;

        stralloc_free(&envdir) ;
        stralloc_free(&scandir) ;

        scandir_up(scan,timeout,notif,genv) ;
        /** if already running, scandir_up() return */
        return 0 ;
    }

    r = svc_scandir_ok(scandir.s) ;
    if (!r) log_diesys(LOG_EXIT_SYS,"scandir: ",scandir.s," is not running") ;
    else if (r < 0) log_dieusys(LOG_EXIT_SYS, "check: ", scandir.s) ;

    if (send_signal(scandir.s,signal) <= 0) goto err ;

    stralloc_free(&scandir) ;
    return 0 ;
    err:
        stralloc_free(&scandir) ;
        return 111 ;
}