skalibs

Mirror/fork of https://skarnet.org/software/skalibs/
git clone https://ccx.te2000.cz/git/skalibs
Log | Files | Refs | README | LICENSE

cspawn.c (10361B)


      1 /* ISC license. */
      2 
      3 #include <skalibs/sysdeps.h>
      4 
      5 #ifdef SKALIBS_HASPOSIXSPAWN
      6 #include <skalibs/nonposix.h>
      7 #endif
      8 
      9 #include <errno.h>
     10 #include <unistd.h>
     11 
     12 /* for CSPAWN_FLAGS_LINUX_NEWPID (using clone3 syscall) */
     13 #include <syscall.h>     /* For calling clone3 syscall (currently not in libc) */
     14 #include <linux/types.h> /* For vendored definition of struct clone_args */
     15 #include <sched.h>       /* Definition of CLONE_* constants */
     16 
     17 #include <skalibs/allreadwrite.h>
     18 #include <skalibs/sig.h>
     19 #include <skalibs/djbunix.h>
     20 #include <skalibs/selfpipe.h>
     21 #include <skalibs/exec.h>
     22 #include <skalibs/cspawn.h>
     23 
     24 static inline void cspawn_child_exec (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
     25 {
     26   for (size_t i = 0 ; i < n ; i++)
     27   {
     28     switch (fa[i].type)
     29     {
     30       case CSPAWN_FA_CLOSE : fd_close(fa[i].x.fd) ; break ;
     31       case CSPAWN_FA_COPY :
     32         if (fd_copy(fa[i].x.fd2[0], fa[i].x.fd2[1]) == -1) return ;
     33         break ;
     34       case CSPAWN_FA_MOVE :
     35         if (fd_move(fa[i].x.fd2[0], fa[i].x.fd2[1]) == -1) return ;
     36         if (fa[i].x.fd2[0] == fa[i].x.fd2[1] && uncoe(fa[i].x.fd2[0]) == -1) return ;
     37         break ;
     38       case CSPAWN_FA_OPEN :
     39       {
     40         int fd = open3(fa[i].x.openinfo.file, fa[i].x.openinfo.oflag, fa[i].x.openinfo.mode) ;
     41         if (fd == -1) return ;
     42         if (fd_move(fa[i].x.openinfo.fd, fd) == -1) return ;
     43         break ;
     44       }
     45       case CSPAWN_FA_CHDIR :
     46         if (chdir(fa[i].x.path) == -1) return ;
     47         break ;
     48       case CSPAWN_FA_FCHDIR :
     49         if (fchdir(fa[i].x.fd) == -1) return ;
     50         break ;
     51       default :
     52         errno = EINVAL ; return ;
     53     }
     54   }
     55 
     56   if (flags & CSPAWN_FLAGS_SELFPIPE_FINISH) selfpipe_finish() ;
     57   if (flags & CSPAWN_FLAGS_SIGBLOCKNONE) sig_blocknone() ;
     58   if (flags & CSPAWN_FLAGS_SETSID) setsid() ;
     59 
     60   exec_ae(prog, argv, envp) ;
     61 }
     62 
     63 static inline pid_t cspawn_fork (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
     64 {
     65   pid_t pid ;
     66   int p[2] ;
     67   char c ;
     68 
     69   if (pipecoe(p) == -1) return 0 ;
     70   pid = fork() ;
     71   if (pid == -1)
     72   {
     73     fd_close(p[1]) ;
     74     fd_close(p[0]) ;
     75     return 0 ;
     76   }
     77   if (!pid)
     78   {
     79     cspawn_child_exec(prog, argv, envp, flags, fa, n) ;
     80     c = errno ;
     81     fd_write(p[1], &c, 1) ;
     82     _exit(127) ;
     83   }
     84 
     85   fd_close(p[1]) ;
     86   p[1] = fd_read(p[0], &c, 1) ;
     87   if (p[1] < 0)
     88   {
     89     fd_close(p[0]) ;
     90     return 0 ;
     91   }
     92   fd_close(p[0]) ;
     93   if (p[1])
     94   {
     95     wait_pid(pid, &p[0]) ;
     96     errno = (unsigned char)c ;
     97     return 0 ;
     98   }
     99   return pid ;
    100 }
    101 
    102 static inline pid_t cspawn_newpid_fork(void)
    103 {
    104   struct clone_args {
    105     __aligned_u64 flags;
    106     __aligned_u64 pidfd;
    107     __aligned_u64 child_tid;
    108     __aligned_u64 parent_tid;
    109     __aligned_u64 exit_signal;
    110     __aligned_u64 stack;
    111     __aligned_u64 stack_size;
    112     __aligned_u64 tls;
    113     __aligned_u64 set_tid;
    114     __aligned_u64 set_tid_size;
    115     __aligned_u64 cgroup;
    116   } args = {
    117     .flags = CLONE_NEWPID,
    118     .exit_signal = SIGCHLD,
    119   };
    120 
    121   return syscall(__NR_clone3, &args, sizeof(args));
    122 }
    123 
    124 static inline pid_t cspawn_newpid (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
    125 {
    126   pid_t pid ;
    127   int p[2] ;
    128   char c ;
    129 
    130   if (pipecoe(p) == -1) return 0 ;
    131   pid = cspawn_newpid_fork() ;
    132   if (pid == -1)
    133   {
    134     fd_close(p[1]) ;
    135     fd_close(p[0]) ;
    136     return 0 ;
    137   }
    138   if (!pid)
    139   {
    140     cspawn_child_exec(prog, argv, envp, flags, fa, n) ;
    141     c = errno ;
    142     fd_write(p[1], &c, 1) ;
    143     _exit(127) ;
    144   }
    145 
    146   fd_close(p[1]) ;
    147   p[1] = fd_read(p[0], &c, 1) ;
    148   if (p[1] < 0)
    149   {
    150     fd_close(p[0]) ;
    151     return 0 ;
    152   }
    153   fd_close(p[0]) ;
    154   if (p[1])
    155   {
    156     wait_pid(pid, &p[0]) ;
    157     errno = (unsigned char)c ;
    158     return 0 ;
    159   }
    160   return pid ;
    161 }
    162 
    163  /*
    164     guess who has a buggy posix_spawn() *and* doesn't have waitid() to work around it?
    165     if you guessed OpenBSD, you're right!
    166  */
    167 
    168 #if defined(SKALIBS_HASPOSIXSPAWN) && (!defined(SKALIBS_HASPOSIXSPAWNEARLYRETURN) || defined(SKALIBS_HASWAITID))
    169 
    170 #include <signal.h>
    171 #include <stdlib.h>
    172 #include <spawn.h>
    173 
    174 #include <skalibs/config.h>
    175 #include <skalibs/djbunix.h>
    176 
    177 #ifdef SKALIBS_HASPOSIXSPAWNEARLYRETURN
    178 
    179 #include <sys/wait.h>
    180 
    181 static inline pid_t cspawn_workaround (pid_t pid, int const *p)
    182 {
    183   siginfo_t si ;
    184   int e ;
    185   ssize_t r ;
    186   char c ;
    187 
    188   fd_close(p[1]) ;
    189   r = fd_read(p[0], &c, 1) ;
    190   fd_close(p[0]) ;
    191   if (r == -1) return 0 ;
    192   if (r) return (errno = EILSEQ, 0) ;  /* child wrote, wtf */
    193 
    194   do e = waitid(P_PID, pid, &si, WEXITED | WNOHANG | WNOWAIT) ;
    195   while (e == -1 && errno == EINTR) ;
    196   if (e == -1) return pid ;  /* we're in trouble, but don't leak a child */
    197   if (!si.si_pid) return pid ;  /* child is running */
    198   if (si.si_code != CLD_EXITED || si.si_status != 127) return pid ; /* child died after execve(), let caller handle it */
    199   /*
    200     child exited 127, so either execve() failed, which is what we want to catch,
    201     or it raced like a mofo, execve()d and then exited 127 on its own, in which
    202     case, tough luck, it never existed.
    203   */
    204   wait_pid(pid, 0) ;
    205   return (errno = 0, 0) ;
    206 }
    207 
    208 #endif
    209 
    210 static inline pid_t cspawn_pspawn (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
    211 {
    212   pid_t pid ;
    213   posix_spawnattr_t attr ;
    214   posix_spawn_file_actions_t actions ;
    215   int e ;
    216   int nopath = !getenv("PATH") ;
    217 #ifdef SKALIBS_HASPOSIXSPAWNEARLYRETURN
    218   int p[2] ;
    219   if (pipecoe(p) == -1) return 0 ;
    220 #endif
    221 
    222   if (flags)
    223   {
    224     short pfff = 0 ;
    225     e = posix_spawnattr_init(&attr) ;
    226     if (e) goto err ;
    227     if (flags & (CSPAWN_FLAGS_SIGBLOCKNONE | CSPAWN_FLAGS_SELFPIPE_FINISH))
    228     {
    229       sigset_t set ;
    230       sigemptyset(&set) ;
    231       e = posix_spawnattr_setsigmask(&attr, &set) ;
    232       if (e) goto errattr ;
    233       pfff |= POSIX_SPAWN_SETSIGMASK ;
    234     }
    235 #ifdef SKALIBS_HASPOSIXSPAWNSETSID
    236     if (flags & CSPAWN_FLAGS_SETSID) pfff |= POSIX_SPAWN_SETSID ;
    237 #else
    238 #ifdef SKALIBS_HASPOSIXSPAWNSETSIDNP
    239     if (flags & CSPAWN_FLAGS_SETSID) pfff |= POSIX_SPAWN_SETSID_NP ;
    240 #endif
    241 #endif
    242     e = posix_spawnattr_setflags(&attr, pfff) ;
    243     if (e) goto errattr ;
    244   }
    245 
    246   if (n)
    247   {
    248     e = posix_spawn_file_actions_init(&actions) ;
    249     if (e) goto errattr ;
    250     for (size_t i = 0 ; i < n ; i++)
    251     {
    252       switch (fa[i].type)
    253       {
    254         case CSPAWN_FA_CLOSE :
    255           e = posix_spawn_file_actions_addclose(&actions, fa[i].x.fd) ;
    256           if (e) goto erractions ;
    257           break ;
    258         case CSPAWN_FA_COPY :
    259           e = posix_spawn_file_actions_adddup2(&actions, fa[i].x.fd2[1], fa[i].x.fd2[0]) ;
    260           if (e) goto erractions ;
    261           break ;
    262         case CSPAWN_FA_MOVE :
    263           e = posix_spawn_file_actions_adddup2(&actions, fa[i].x.fd2[1], fa[i].x.fd2[0]) ;
    264           if (e) goto erractions ;
    265           if (fa[i].x.fd2[0] != fa[i].x.fd2[1])
    266           {
    267             e = posix_spawn_file_actions_addclose(&actions, fa[i].x.fd2[1]) ;
    268             if (e) goto erractions ;
    269           }
    270           break ;
    271         case CSPAWN_FA_OPEN :
    272           e = posix_spawn_file_actions_addopen(&actions, fa[i].x.openinfo.fd, fa[i].x.openinfo.file, fa[i].x.openinfo.oflag, fa[i].x.openinfo.mode) ;
    273           if (e) goto erractions ;
    274           break ;
    275 #ifdef SKALIBS_HASPOSIXSPAWNCHDIR
    276         case CSPAWN_FA_CHDIR :
    277           e = posix_spawn_file_actions_addchdir(&actions, fa[i].x.path) ;
    278           if (e) goto erractions ;
    279           break ;
    280         case CSPAWN_FA_FCHDIR :
    281           e = posix_spawn_file_actions_addfchdir(&actions, fa[i].x.fd) ;
    282           if (e) goto erractions ;
    283           break ;
    284 #else
    285 #ifdef SKALIBS_HASPOSIXSPAWNCHDIRNP
    286         case CSPAWN_FA_CHDIR :
    287           e = posix_spawn_file_actions_addchdir_np(&actions, fa[i].x.path) ;
    288           if (e) goto erractions ;
    289           break ;
    290         case CSPAWN_FA_FCHDIR :
    291           e = posix_spawn_file_actions_addfchdir_np(&actions, fa[i].x.fd) ;
    292           if (e) goto erractions ;
    293           break ;
    294 #endif
    295 #endif
    296         default :
    297           e = EINVAL ;
    298           goto erractions ;
    299       }
    300     }
    301   }
    302 
    303   if (nopath && (setenv("PATH", SKALIBS_DEFAULTPATH, 0) == -1)) { e = errno ; goto erractions ; }
    304   e = posix_spawnp(&pid, prog, n ? &actions : 0, flags ? &attr : 0, (char *const *)argv, (char *const *)envp) ;
    305   if (nopath) unsetenv("PATH") ;
    306   if (e) goto erractions ;
    307 
    308   if (n) posix_spawn_file_actions_destroy(&actions) ;
    309   if (flags) posix_spawnattr_destroy(&attr) ;
    310 #ifdef SKALIBS_HASPOSIXSPAWNEARLYRETURN
    311   return cspawn_workaround(pid, p) ;
    312 #else
    313   return pid ;
    314 #endif
    315 
    316  erractions:
    317   if (n) posix_spawn_file_actions_destroy(&actions) ;
    318  errattr:
    319   if (flags) posix_spawnattr_destroy(&attr) ;
    320  err:
    321 #ifdef SKALIBS_HASPOSIXSPAWNEARLYRETURN
    322   fd_close(p[1]) ;
    323   fd_close(p[0]) ;
    324 #endif
    325   errno = e ;
    326   return 0 ;
    327 }
    328 
    329 #if (defined(SKALIBS_HASPOSIXSPAWNSETSID) || defined(SKALIBS_HASPOSIXSPAWNSETSIDNP)) && (defined(SKALIBS_HASPOSIXSPAWNCHDIR) || defined(SKALIBS_HASPOSIXSPAWNCHDIRNP))
    330 
    331 pid_t cspawn (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
    332 {
    333   if (flags & CSPAWN_FLAGS_LINUX_NEWPID)
    334     return cspawn_newpid(prog, argv, envp, flags, fa, n) ;
    335   return cspawn_pspawn(prog, argv, envp, flags, fa, n) ;
    336 }
    337 
    338 #else
    339 
    340 pid_t cspawn (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
    341 {
    342   if (flags & CSPAWN_FLAGS_LINUX_NEWPID)
    343     return cspawn_newpid(prog, argv, envp, flags, fa, n) ;
    344 
    345 #if !defined(SKALIBS_HASPOSIXSPAWNSETSID) && !defined(SKALIBS_HASPOSIXSPAWNSETSIDNP)
    346   if (flags & CSPAWN_FLAGS_SETSID) goto dofork ;
    347 #endif
    348 #if !defined(SKALIBS_HASPOSIXSPAWNCHDIR) && !defined(SKALIBS_HASPOSIXSPAWNCHDIRNP)
    349   for (size_t i = 0 ; i < n ; i++)
    350     if (fa[i].type == CSPAWN_FA_CHDIR || fa[i].type == CSPAWN_FA_FCHDIR)
    351       goto dofork ;
    352 #endif
    353   return cspawn_pspawn(prog, argv, envp, flags, fa, n) ;
    354 
    355  dofork:
    356   return cspawn_fork(prog, argv, envp, flags, fa, n) ;
    357 }
    358 
    359 #endif
    360 
    361 #else
    362 
    363 pid_t cspawn (char const *prog, char const *const *argv, char const *const *envp, uint16_t flags, cspawn_fileaction const *fa, size_t n)
    364 {
    365   if (flags & CSPAWN_FLAGS_LINUX_NEWPID)
    366     return cspawn_newpid(prog, argv, envp, flags, fa, n) ;
    367   return cspawn_fork(prog, argv, envp, flags, fa, n) ;
    368 }
    369 
    370 #endif