Skip to content
Snippets Groups Projects
execl-toc.c 34.29 KiB
/*
 * execl-toc.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./
 *
 * This program is inspired by https://github.com/skarnet/s6/blob/master/src/conn-tools/s6-ipcserver-socketbinder.c
 * and https://github.com/skarnet/s6-portable-utils/blob/master/src/skaembutils/s6-test.c.
 *
 */

#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>//getenv
#include <stdio.h>//getenv
#include <sys/mount.h>

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

#include <skalibs/buffer.h>
#include <skalibs/types.h>
#include <skalibs/sgetopt.h>
#include <skalibs/djbunix.h>
#include <skalibs/socket.h>
#include <skalibs/exec.h>

#define USAGE "execl-toc [ -h ] [ -v verbosity ] [ -n ] [ -t ] [ -D ] [ -X ] [ -d|p|S|m|L|e|b|c|k|n|g|r|s|t|u|w|x|f|z|O|U|N|V|E element ] [ -o opts ] [ -t type ] [ -d device ] [ -g gid ] [ -u uid ] [ -m mode ] [ -M mode ] [ -s|D|B|b ] [ -b backlog ] prog..."

static inline void info_help (void)
{
  static char const *help =
"execl-toc <main_options> <test_options> element <create_options> prog\n"
"\n"
"main_options :\n"
"   -h: print this help\n"
"   -v: increase/decrease verbosity\n"
"   -n: negate the test\n"
"   -t: exit 0 if the test fails\n"
"   -D: only test and disable creation\n"
"   -X: do not execute prog\n"
"\n"
"test_options :\n"
"\n"
"   test and create\n\n"
"   -d: true if file is a directory\n"
"   -p: true if file is a pipe\n"
"   -S: true if file is a socket\n"
"   -m: true if file is a mountpoint\n"
"   -L: true if file is a symlink\n"
"\n"
"   test only\n\n"
"   -e: true if file exists\n"
"   -b: true if file is a block\n"
"   -c: true if file is a character\n"
"   -k: true if sticky bit is set for file\n"
"   -n: true if the length of file is non-zero\n"
"   -g: true if file is set-group-id\n"
"   -r: true if file is readable\n"
"   -s: true if the size of file is greater than zero\n"
"   -t: true if file is open and refers to a terminal\n"
"   -u: true if set-user-id bit is set for file\n"
"   -w: true if file is writable\n"
"   -x: true if file is executable\n"
"   -f: true if file is regular file\n"
"   -z: true if the length of file is zero\n"
"   -O: true if file is owned by the effective user id\n"
"   -U: true if file is owned by the effective group id\n"
"   -N: true if file has been modified since it was last read\n"
"   -V: true if file exists on the environment\n"
"   -E: true if file is an empty directory\n"
"\n"
"create_options :\n"
"   -o: mount options(same as mount -o)\n"
"   -t: type mount options(same as mount -t), target for symlink\n"
"   -d: device mount options (same as mount -t type device /directory)\n"
"   -u: changes element's owner to (numeric/name) uid after the creation\n"
"   -g: changes element's owner to (numeric/name) gid after the creation\n"
"   -m: creates element's permissions to (numeric) mode.\n"
"   -M: creates parent directories of the *element* with permissions to (numeric) mode.\n"
"   -s: type of element will be SOCK_DGRAM instead of SOCK_STREAM\n"
"   -D: disallow instant rebinding at element to the same path\n"
"   -B: the element will be blocking.\n"
"   -b: set a maximum of backlog connections on the element.\n"
;

 if (buffer_putsflush(buffer_1, help) < 0)
    log_dieusys(LOG_EXIT_SYS, "write to stdout") ;
}

enum opts_e
{
  // can create
  T_DIR , // -d expect mode chown
  T_FIFO , // -p expect mode chown
  T_SOCKET , // -S expect mode chow style
  T_MNT ,// -m mountpoint expect mount_opts device type
  T_SYMLINK , // -L
   // just test
  T_EXIST , // -e create empty file
  T_BLOCK , // -b
  T_CHAR , // -c
  T_STICKY , // -k
  T_NONZERO , // -n
  T_SGID , // -g
  T_READABLE , // -r
  T_NONZEROFILE , // -s
  T_TERM , // -t fd
  T_SUID , // -u
  T_WRITABLE , // -w
  T_EXECUTABLE , // -x
  T_REGULAR , // -f
  T_ZERO , // -z
  T_EUID , // -O
  T_EGID , // -U
  T_MODIFIED , // -N
  T_ENV , // -V environment value
  T_EMPTY // -E empty directories
} ;

typedef struct opts_common_s opts_common_t, *opts_common_t_ref ;
struct opts_common_s
{
    char const *func_name ; // name of the function
    char const *test_on ; // argument to test
    uint8_t not_create ; // 0 create, 1 simply test
    uint8_t noprog ; // 0 execute prog, 1 do not execute prog
    int argc ;
    char **argv ;
    char const *const *envp ;
    enum opts_e test ;
    char const *minus_o ; // -o -> opt for mount
    char const *minus_t ; // -t -> type for mount, target for symlink
    char const *minus_d ; // -d -> device for mount
    gid_t minus_g ; // -g
    uid_t minus_u ; // -u
    mode_t minus_m ; // -m ou -a for socket
    mode_t minus_M ; // -M directory of the element permissions
    // socket option
    uint8_t minus_s ; // -s -> 0 inactive meaning SOCK_STREAM(default), 1 active SOCK_DGRAM
    int minus_D ; //-D -> 0 inactive(default), 1 active
    unsigned int minus_B ; //-B -> O_NONBLOCK(default), 0 blocking
    int minus_b ; // -b -> -1 inactive(default), other value active
} ;

#define OPTS_COMMON_ZERO \
{ \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    0 , \
    -1 , \
    -1 , \
    0 , \
    0 , \
    0 , \
    0 , \
    O_NONBLOCK , \
    -1  \
}

typedef int execl_func_t (opts_common_t *arguments, char **nargv) ;
typedef execl_func_t *execl_func_t_ref ;

execl_func_t execl_directory ;
execl_func_t execl_fifo ;
execl_func_t execl_socket ;
execl_func_t execl_mountpoint ;
execl_func_t execl_symlink ;
execl_func_t execl_common ;

void parse(opts_common_t *arguments,char **nargv)
{
    int n = 0, i = 0 ;
    int argc = arguments->argc ;
    char **argv = arguments->argv ;

    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;)
        {
            int opt = subgetopt_r(argc,(char const *const *)argv, "o:t:d:u:g:m:M:sDBb:", &l) ;
            if (opt == -1) break ;
            switch (opt)
            {
                case 'o' :  arguments->minus_o = l.arg ; break ;
                case 't' :  arguments->minus_t = l.arg ; break ;
                case 'd' :  arguments->minus_d = l.arg ; break ;
                case 'u' :  if (!uid0_scan(l.arg, &arguments->minus_u))
                                if (get_uidbyname(l.arg,&arguments->minus_u) == -1)
                                    log_dieusys(LOG_EXIT_SYS,"get uid of: ",l.arg) ;
                            break ;
                case 'g' :
                            {
                                if (!gid0_scan(l.arg, &arguments->minus_g))
                                {
                                    if (get_gidbyname(l.arg,&arguments->minus_g) == 1)
                                    {
                                        stralloc ngid = STRALLOC_ZERO ;
                                        if (get_groupbygid(arguments->minus_g,&ngid) == -1)
                                            log_dieusys(LOG_EXIT_SYS,"get gid of: ",l.arg) ;
                                        if (ngid.len)
                                        {
                                            if (!stralloc_0(&ngid)) log_die_nomem("stralloc") ;
                                            if (get_gidbygroup(ngid.s,&arguments->minus_g) == -1)
                                                log_dieusys(LOG_EXIT_SYS,"get gid of: ",l.arg) ;
                                        }else if (get_gidbygroup(l.arg,&arguments->minus_g) == -1)
                                            log_dieusys(LOG_EXIT_SYS,"get gid of: ",l.arg) ;
                                        stralloc_free(&ngid) ;
                                    }
                                    else if (get_gidbygroup(l.arg,&arguments->minus_g) == -1)
                                        log_dieusys(LOG_EXIT_SYS,"get gid of: ",l.arg) ;
                                }
                                break ;
                            }
                case 'm' :  if (!uint0_oscan(l.arg, &arguments->minus_m)) log_usage(USAGE) ; break ;
                case 'M' :  if (!uint0_oscan(l.arg, &arguments->minus_M)) log_usage(USAGE) ; break ;
                // socket special case
                case 's' :  arguments->minus_s = 1 ; break ;
                case 'D' :  arguments->minus_D = 1 ; break ;
                case 'B' :  arguments->minus_B = 0 ; break ;
                case 'b' :  if (!uint0_scan(l.arg,(unsigned int *)&arguments->minus_b)) log_usage(USAGE) ; break ;
                default :   log_usage(USAGE) ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }

    for (; i < argc ; i++)
        nargv[n++] = (char *)argv[i] ;

    nargv[n] = 0 ;
    arguments->argc = n ;
    arguments->argv = nargv ;
}

static int auto_dir(opts_common_t *arguments)
{
    int r ;
    char const *dest = arguments->test_on ;
    size_t len = strlen(dest) ;
    char dir[len + 1] ;
    if (!ob_dirname(dir,dest)) log_dieu(LOG_EXIT_SYS,"get dirname of: ",dest) ;

    mode_t mode = !arguments->minus_M ? 0755 : arguments->minus_M ;

    r = scan_mode(dir,S_IFDIR) ;
    if (r == -1) return 0 ;
    if (!r) {
        if (arguments->minus_M) umask(0) ;
        if (!dir_create_parent(dir,mode))
            log_dieusys(LOG_EXIT_SYS,"create dir: ",dir) ;

        /** dir_create_parent do not warn if the parent directory
         * can not be created, so check it */
        r = scan_mode(dir,S_IFDIR) ;
        if (!r) return 0 ;

        /** only the last element should match the uid and gid
         * given at commandline */
        //if (chown(dir, arguments->minus_u,arguments->minus_g) == -1)
        //  log_dieusys(LOG_EXIT_SYS,"chown: ",dir) ;
    }
    return 1 ;
}

/* @Return -1 doesn't exist
 * @Return 0 if mounted
 * @Return 1 if not mounted */
static int is_mnt(char const *str)
{
    struct stat st;
    size_t slen = strlen(str) ;
    int is_not_mnt = 0 ;
    if (lstat(str,&st) < 0) return -1 ;
    if (S_ISDIR(st.st_mode))
    {
        dev_t st_dev = st.st_dev ; ino_t st_ino = st.st_ino ;
        char p[slen+4] ;
        memcpy(p,str,slen) ;
        memcpy(p + slen,"/..",3) ;
        p[slen+3] = 0 ;
        if (!stat(p,&st))
            is_not_mnt = (st_dev == st.st_dev) && (st_ino != st.st_ino) ;
    }else return 0 ;
    return is_not_mnt ? 1 : 0 ;
}

int execl_directory(opts_common_t *arguments,char **nargv)
{
    int r ;
    parse(arguments,nargv) ;

    if (!arguments->argc && !arguments->noprog) log_die(LOG_EXIT_USER,"missing argument prog") ;

    mode_t mode = !arguments->minus_m ? 0755 : arguments->minus_m ;

    r = scan_mode(arguments->test_on,S_IFDIR) ;
    if (r == -1) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," exist but it is not a directory") ;
    if (!r && !arguments->not_create) {
        if (arguments->minus_m) umask(0) ;
        if (!dir_create_parent(arguments->test_on,mode))
            log_dieusys(LOG_EXIT_SYS,"create dir: ",arguments->test_on) ;

        if (chown(arguments->test_on, arguments->minus_u,arguments->minus_g) == -1)
            log_dieusys(LOG_EXIT_SYS,"chown: ",arguments->test_on) ;
        return 1 ;
    }
    if (!r) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," is not a directory") ;

    return 1 ;
}

int execl_fifo(opts_common_t *arguments,char **nargv)
{
    int r ;

    parse(arguments,nargv) ;

    if (!arguments->argc && !arguments->noprog) log_die(LOG_EXIT_USER,"missing argument prog") ;

    mode_t mode = !arguments->minus_m ? S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH : arguments->minus_m ;

    r = scan_mode(arguments->test_on,S_IFIFO) ;
    if (r == -1) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," exist but it is not a fifo") ;
    if (!r && !arguments->not_create) {
        if (!auto_dir(arguments)) {
            errno = EEXIST ;
            log_diesys(LOG_EXIT_SYS,"conflicting format in parent directories of: ",arguments->test_on) ;
        }

        umask(S_IXUSR|S_IXGRP|S_IXOTH) ;

        if (mkfifo(arguments->test_on,mode) < 0)
            log_dieusys(LOG_EXIT_SYS,"create fifo: ",arguments->test_on) ;

        if (chown(arguments->test_on, arguments->minus_u,arguments->minus_g) == -1)
            log_dieusys(LOG_EXIT_SYS,"chown: ",arguments->test_on) ;
        return 1 ;
    }
    if (!r) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," is not a fifo") ;

    return 1 ;
}

int execl_mountpoint(opts_common_t *arguments,char **nargv)
{
    pid_t pid ;
    int r, wstat ;
    char const *newargv[8] ;
    unsigned int m = 0 ;

    parse(arguments,nargv) ;

    if (!arguments->argc && !arguments->noprog) log_die(LOG_EXIT_USER,"missing argument prog") ;

    if ((!arguments->minus_t || !arguments->minus_d) && !arguments->not_create) log_usage(USAGE) ;

    mode_t mode = !arguments->minus_m ? 0755 : arguments->minus_m ;

    r = is_mnt(arguments->test_on) ;
    if (r == -1 || r == 1)
    {
        if (!arguments->not_create) {
            if (arguments->minus_m) umask(0) ;
            if (!dir_create_parent(arguments->test_on,mode))
                log_dieusys(LOG_EXIT_SYS,"create dir: ",arguments->test_on) ;
            goto mount ;
        }
        log_warn_return(LOG_EXIT_ZERO,arguments->test_on," is not a mountpoint") ;
    }
    if (!r) {
        // mounted, nothing to do
        return 1 ;
    }
    if (r == 1 && arguments->not_create) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," is not a mountpoint") ;

    mount:/*
    log_info("d->",arguments->minus_d) ;
    log_info("test->on",arguments->test_on) ;
    log_info("t->",arguments->minus_t) ;
    log_info("o->",arguments->minus_o) ;
    if (mount(0,arguments->test_on,arguments->minus_t,0,arguments->minus_o) < 0)
        log_dieusys(LOG_EXIT_SYS,"mount: ",arguments->test_on) ;
    */
    newargv[m++] = "mount" ;
    if (arguments->minus_o) {
        newargv[m++] = "-o" ;
        newargv[m++] = arguments->minus_o ;
    }
    newargv[m++] = "-t" ;
    newargv[m++] = arguments->minus_t ;
    newargv[m++] = arguments->minus_d ;
    newargv[m++] = arguments->test_on ;
    newargv[m++] = 0 ;


    pid = child_spawn0(newargv[0],newargv,arguments->envp) ;
    if (waitpid_nointr(pid,&wstat, 0) < 0)
            log_dieusys(LOG_EXIT_SYS,"wait for mount") ;

    if (wstat) log_dieu(LOG_EXIT_SYS,"mount: ",arguments->test_on) ;

    return 1 ;
}

int execl_socket(opts_common_t *arguments,char **nargv)
{
    int r ;
    struct stat st ;
    parse(arguments,nargv) ;

    if (!arguments->argc && !arguments->noprog) log_die(LOG_EXIT_USER,"missing argument prog") ;

    close(0) ;
    int flagdgram = arguments->minus_s ? 1 : 0 ;
    int flag = arguments->minus_B ;
    int flagreuse = arguments->minus_D ? 0 : 1 ;
    unsigned int backlog = arguments->minus_b > 0 ? arguments->minus_b : SOMAXCONN ;
    mode_t mode = !arguments->minus_m ? 0777 : arguments->minus_m ;

    r = stat(arguments->test_on, &st) ;
    if (!r && !S_ISSOCK(st.st_mode)) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," exist but it is not a socket") ;
    if (r == -1 && !arguments->not_create)
    {
        if (!auto_dir(arguments)) {
            errno = EEXIST ;
            log_diesys(LOG_EXIT_SYS,"conflicting format in parent directories of: ",arguments->test_on) ;
        }

        if (flagdgram ? ipc_datagram_internal(flag) : ipc_stream_internal(flag))
            log_dieusys(LOG_EXIT_SYS, "create socket") ;

        {
            mode_t m = umask(~mode & 0777) ;
            if ((flagreuse ? ipc_bind_reuse(0, arguments->test_on) : ipc_bind(0, arguments->test_on)) < 0)
                log_dieusys(LOG_EXIT_SYS,"bind to: ", arguments->test_on) ;
            umask(m) ;
        }
        if (backlog && ipc_listen(0, backlog) < 0)
            log_dieusys(LOG_EXIT_SYS, "listen to: ", arguments->test_on) ;

        return 1 ;
    }
    if (r == -1) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," is not a socket") ;

    return 1 ;
}

int execl_symlink(opts_common_t *arguments,char **nargv)
{
    int r ;
    parse(arguments,nargv) ;

    if (!arguments->argc && !arguments->noprog) log_die(LOG_EXIT_USER,"missing argument prog") ;

    if (!arguments->minus_t) log_usage(USAGE) ;
    struct stat st ;
    r = lstat(arguments->test_on, &st) ;
    if (r == -1 && !arguments->not_create) {
        if (!auto_dir(arguments)) {
            errno = EEXIST ;
            log_diesys(LOG_EXIT_SYS,"conflicting format in parent directories of: ",arguments->test_on) ;
        }
        if (symlink(arguments->minus_t,arguments->test_on) < 0)
            log_dieusys(LOG_EXIT_SYS,"create symlink: ",arguments->test_on) ;

        return 1 ;
    }

    if (!S_ISLNK(st.st_mode)) log_warn_return(LOG_EXIT_ZERO,arguments->test_on," is not a symlink") ;

    return 1 ;
}

int execl_common(opts_common_t *arguments, char **nargv)
{
    int r, n = 0, i = 0, e = 1 ;
    struct stat st ;
    char const *test = arguments->test_on ;
    int argc = arguments->argc ;
    char **argv = arguments->argv ;

    switch (arguments->test)
    {
        case T_EXIST :

                            if (stat(test, &st) == -1)
                                { log_warn(test," doesn't exist") ; e = 0 ; }
                            break ;

        case T_BLOCK :
                            r = scan_mode(test,S_IFBLK) ;
                            if (r == -1){ log_warn(test," exist but it is not a block") ; e = 0 ; }
                            if (!r) { log_warn(test," is not a block") ; e = 0 ; }
                            break ;

        case T_CHAR :
                            r = scan_mode(test,S_IFCHR) ;
                            if (r == -1){ log_warn(test," exist but it is not a character") ; e = 0 ; }
                            if (!r) { log_warn(test," is not a character") ; e = 0 ; }
                            break ;

        case T_REGULAR :
                            r = scan_mode(test,S_IFREG) ;
                            if (r == -1) { log_warn(test," exist but it is not a regular file") ; e = 0 ; }
                            if (!r) { log_warn(test," is not a regular file") ; e = 0 ; }
                            break ;

        case T_SGID :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (!(st.st_mode & S_ISGID))
                                { log_warn(test," is not set-group-id") ; e = 0 ; }
                            break ;

        case T_SUID :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (!(st.st_mode & S_ISUID))
                                { log_warn("set-user-id bit is not set for: ",test) ; e = 0 ; }
                            break ;

        case T_STICKY :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if(!(st.st_mode & S_ISVTX))
                                { log_warn("not sticky bit set for: ",test) ; e = 0 ; }
                            break ;

        case T_NONZEROFILE :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (!st.st_size)
                                { log_warn("file size of: ",test," is zero") ; e = 0 ; }
                            break ;

        case T_MODIFIED :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (st.st_mtime < st.st_atime)
                                { log_warn(test," was not modified") ; e = 0 ; }
                            break ;

        case T_EUID :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (st.st_uid != geteuid())
                                { log_warn(test," is not owned by the effective user id") ; e = 0 ; }
                            break ;

        case T_EGID :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (st.st_gid != getegid())
                                { log_warn(test," is not owned by the effective group id") ; e = 0 ; }
                            break ;

        case T_READABLE :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (st.st_uid == geteuid()) {
                                if (!(st.st_mode & S_IRUSR))
                                    { log_warn(test," is not readable") ; e = 0 ; }
                            }
                            else if (st.st_gid == getegid()) {
                                if (!(st.st_mode & S_IRGRP))
                                    { log_warn(test," is not writable") ; e = 0 ; }
                            }
                            else if (!(st.st_mode & S_IROTH)) {
                                { log_warn(test," is not writable") ; e = 0 ; }
                            }
                            break ;

        case T_WRITABLE :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (st.st_uid == geteuid()) {
                                if (!(st.st_mode & S_IWUSR))
                                    { log_warn(test," is not writable") ; e = 0 ; }
                            }
                            else if (st.st_gid == getegid()) {
                                if (!(st.st_mode & S_IWGRP))
                                    { log_warn(test," is not writable") ; e = 0 ; }
                            }
                            else if (!(st.st_mode & S_IWOTH)) {
                                { log_warn(test," is not writable") ; e = 0 ; }
                            }
                            break ;

        case T_EXECUTABLE :
                            if (stat(test, &st) == -1) {
                                log_warnusys("stat: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (st.st_uid == geteuid()) {
                                if (!(st.st_mode & S_IXUSR))
                                    { log_warn(test," is not executable") ; e = 0 ; }
                            }
                            else if (st.st_gid == getegid()) {
                                if (!(st.st_mode & S_IXGRP))
                                    { log_warn(test," is not executable") ; e = 0 ; }
                            }
                            else if (!(st.st_mode & S_IXOTH)) {
                                { log_warn(test," is not executable") ; e = 0 ; }
                            }
                            break ;

        case T_TERM :
                        {
                            unsigned int fd ;
                            if (!uint0_scan(test, &fd)) {
                                log_warnusys("scan: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (!isatty(fd)) { log_warn(test," do not refer to a terminal") ; e = 0 ; }
                            break ;
                        }

        case T_NONZERO :
                            if (!test[0]) { log_warn(test," is empty") ; e = 0 ; }
                            break ;

        case T_ZERO :
                            if (arguments->test_on[0]) { log_warn(test," is not empty") ; e = 0 ; }
                            break ;

        case T_ENV :
                            if (!getenv(arguments->test_on)) { log_warn("variable: ", test," is not set") ; e = 0 ; }
                            break ;

        case T_EMPTY :
                        {
                            stralloc satree = STRALLOC_ZERO ;
                            char const *exclude[1] = { 0 } ;
                            if (!sastr_dir_get(&satree,test,exclude,S_IFBLK|S_IFCHR|S_IFIFO|S_IFREG|S_IFDIR|S_IFLNK)) {
                                log_warnusys("get contain of directory: ",test) ;
                                e = 0 ;
                                break ;
                            }
                            if (satree.len) { log_warn("directory: ",test," is not empty") ; e = 0 ; }
                            break ;
                        }

        default:            log_die(LOG_EXIT_SYS, "operation not implemented") ;
    }

    argc -= 1 ; argv += 1 ;
    for (; i < argc ; i++)
        nargv[n++] = (char *)argv[i] ;

    nargv[n] = 0 ;
    arguments->argc = n ;
    arguments->argv = nargv ;

    if (!arguments->argc && !arguments->noprog) log_die(LOG_EXIT_USER,"missing argument prog") ;

    return e ;
}

int main(int argc,char const *const *argv, char const *const *envp)
{
    int r, f = 0, n = 0 ;
    uint8_t negat = 0, treat_zero = 0 ;
    char *nargv[argc + 2] ;

    execl_func_t_ref func = 0 ;
    opts_common_t arguments = OPTS_COMMON_ZERO ;

    // do not output nothing by default except system error
    VERBOSITY = 0 ;

    PROG = "execl-toc" ;
    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;)
        {
          int opt = subgetopt_r(argc, argv, "hv:ntDX", &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 'n' :  negat = 1 ; break ;
            case 't' :  treat_zero = 1 ; break ;
            case 'D' :  arguments.not_create = 1 ; break ;
            case 'X' :  arguments.noprog = 1 ; break ;
            default :   break ;
          }
        }
        argc -= l.ind ; argv += l.ind ;
    }
    argc++ ; argv-- ;
    nargv[n++] = "fake_opts" ;
    for (int i = 0 ; i < argc ; i++)
        nargv[n++] = (char *)argv[i] ;

    nargv[n] = 0 ;

    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;)
        {
            int opt = subgetopt_r(3,(char const *const *)nargv, "d:p:S:m:L:b:c:k:n:g:r:s:t:u:w:x:e:f:z:O:U:N:V:E:", &l) ;
            if (opt == -1) break ;
            switch (opt)
            {
                case 'd' :  func = &execl_directory ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_directory" ;
                            break ;
                case 'p' :  func = &execl_fifo ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_fifo" ;
                            break ;
                case 'S' :  func = &execl_socket ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_socket" ;
                            break ;
                case 'm' :  func = &execl_mountpoint ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_mountpoint" ;
                            break ;
                case 'L' :  func = &execl_symlink ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_symlink" ;
                            break ;
                // only test
                case 'b' :  arguments.test = T_BLOCK ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'c' :  arguments.test = T_CHAR ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'k' :  arguments.test = T_STICKY ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'n' :  arguments.test = T_NONZERO ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'g' :  arguments.test = T_SGID ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'r' :  arguments.test = T_READABLE ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 's' :  arguments.test = T_NONZEROFILE ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 't' :  arguments.test = T_TERM ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'u' :  arguments.test = T_SUID ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'w' :  arguments.test = T_WRITABLE ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'x' :  arguments.test = T_EXECUTABLE ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'e' :  arguments.test = T_EXIST ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'f' :  arguments.test = T_REGULAR ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'z' :  arguments.test = T_ZERO ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'O' :  arguments.test = T_EUID ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'U' :  arguments.test = T_EGID ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'N' :  arguments.test = T_MODIFIED ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'V' :  arguments.test = T_ENV ;
                            func = &execl_common ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                case 'E' :  func = &execl_common ;
                            arguments.test = T_EMPTY ;
                            arguments.test_on = l.arg ;
                            arguments.func_name = "execl_common" ;
                            break ;
                default :   for (int i = 0 ; i < n ; i++)
                            {
                                if (!argv[l.ind]) log_usage(USAGE) ;
                                if (obstr_equal(nargv[i],argv[l.ind]))
                                    f = 1 ;
                            }
                            if (!f) nargv[n++] = (char *)argv[l.ind] ;
                            f = 0 ;
                            break ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }
    if (argc <= 0 && !arguments.noprog) log_usage(USAGE) ;
    n = 0 ;
    argc++ ; argv-- ;
    nargv[n++] = (char *)arguments.func_name ;
    for (int i = 0 ; i < argc ; i++)
        nargv[n++] = (char *)argv[i] ;

    nargv[n] = 0 ;

    arguments.argc = n ;
    arguments.argv = nargv ;
    arguments.envp = envp ;
    // argument doesn't exit
    if (!arguments.func_name) log_die(LOG_EXIT_SYS, "operation not implemented") ;

    r = (*func)(&arguments,nargv) ;

    if (negat == r) return treat_zero ? LOG_EXIT_ZERO : LOG_EXIT_SYS ;
    if (arguments.noprog) return LOG_EXIT_ZERO ;

    xexec_ae(arguments.argv[0],(char const *const *)arguments.argv,arguments.envp) ;
}