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