From fe905b5e69d363e194b4cd1401faf4b3c126d979 Mon Sep 17 00:00:00 2001
From: obarun <eric@obarun.org>
Date: Wed, 31 May 2023 17:18:12 +1100
Subject: [PATCH] add execl-toc from 66-tools

---
 src/extra-tools/deps-exe/execl-toc |   3 +
 src/extra-tools/execl-toc.c        | 913 +++++++++++++++++++++++++++++
 2 files changed, 916 insertions(+)
 create mode 100644 src/extra-tools/deps-exe/execl-toc
 create mode 100644 src/extra-tools/execl-toc.c

diff --git a/src/extra-tools/deps-exe/execl-toc b/src/extra-tools/deps-exe/execl-toc
new file mode 100644
index 00000000..aa08a149
--- /dev/null
+++ b/src/extra-tools/deps-exe/execl-toc
@@ -0,0 +1,3 @@
+-loblibs
+-lskarnet
+
diff --git a/src/extra-tools/execl-toc.c b/src/extra-tools/execl-toc.c
new file mode 100644
index 00000000..0ae97069
--- /dev/null
+++ b/src/extra-tools/execl-toc.c
@@ -0,0 +1,913 @@
+/*
+ * 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) ;
+}
-- 
GitLab