/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1985-2009 AT&T Intellectual Property * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * David Korn * * Phong Vo * * * ***********************************************************************/ #pragma prototyped /* * Glenn Fowler * AT&T Research * * common process execution support with * proper sfio, signal and wait() syncronization * * _ contains the process path name and is * placed at the top of the environment */ #include "proclib.h" #include /* * not quite ready for _use_spawnveg */ #if _use_spawnveg && _lib_fork #undef _use_spawnveg #endif #ifndef DEBUG_PROC #define DEBUG_PROC 1 #endif #if _lib_socketpair #if _sys_socket #include #include #else #undef _lib_socketpair #endif #endif Proc_t proc_default = { -1 }; #if DEBUG_PROC #include #define PROC_ENV_OPTIONS "PROC_OPTIONS" #define PROC_OPT_ENVIRONMENT (1<<0) #define PROC_OPT_EXEC (1<<1) #define PROC_OPT_TRACE (1<<2) #define PROC_OPT_VERBOSE (1<<3) static const Namval_t options[] = { "debug", PROC_OPT_VERBOSE, "environment", PROC_OPT_ENVIRONMENT, "exec", PROC_OPT_EXEC, "trace", PROC_OPT_TRACE, "verbose", PROC_OPT_VERBOSE, 0, 0 }; /* * called by stropt() to set options */ static int setopt(register void* a, register const void* p, register int n, const char* v) { NoP(v); if (p) { if (n) *((int*)a) |= ((Namval_t*)p)->value; else *((int*)a) &= ~((Namval_t*)p)->value; } return 0; } #endif #if _use_spawnveg typedef struct Fd_s { short fd; short flag; } Fd_t; typedef struct Mod_s { struct Mod_s* next; short op; short save; union { struct { Fd_t parent; Fd_t child; } fd; Handler_t handler; } arg; } Modify_t; #endif #ifdef SIGPIPE /* * catch but ignore sig * avoids SIG_IGN being passed to children */ static void ignoresig(int sig) { signal(sig, ignoresig); } #endif /* * do modification op and save previous state for restore() */ static int modify(Proc_t* proc, int forked, int op, long arg1, long arg2) { #if _lib_fork if (forked) { switch (op) { case PROC_fd_dup: case PROC_fd_dup|PROC_FD_PARENT: case PROC_fd_dup|PROC_FD_CHILD: case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD: if (arg1 != arg2) { if (arg2 != PROC_ARG_NULL) { close(arg2); if (fcntl(arg1, F_DUPFD, arg2) != arg2) return -1; } if (op & PROC_FD_CHILD) close(arg1); } break; case PROC_sig_dfl: signal(arg1, SIG_DFL); break; case PROC_sig_ign: signal(arg1, SIG_IGN); break; case PROC_sys_pgrp: if (arg1 < 0) setsid(); else if (arg1 > 0) { if (arg1 == 1) arg1 = 0; if (setpgid(0, arg1) < 0 && arg1 && errno == EPERM) setpgid(0, 0); } break; case PROC_sys_umask: umask(arg1); break; default: return -1; } } #if _use_spawnveg else #endif #else NoP(forked); #endif #if _use_spawnveg { register Modify_t* m; if (!(m = newof(NiL, Modify_t, 1, 0))) return -1; m->next = proc->mods; proc->mods = m; switch (m->op = op) { case PROC_fd_dup: case PROC_fd_dup|PROC_FD_PARENT: case PROC_fd_dup|PROC_FD_CHILD: case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD: m->arg.fd.parent.fd = (short)arg1; m->arg.fd.parent.flag = fcntl(arg1, F_GETFD, 0); if ((m->arg.fd.child.fd = (short)arg2) != arg1) { if (arg2 != PROC_ARG_NULL) { m->arg.fd.child.flag = fcntl(arg2, F_GETFD, 0); if ((m->save = fcntl(arg2, F_DUPFD, 3)) < 0) { m->op = 0; return -1; } fcntl(m->save, F_SETFD, FD_CLOEXEC); close(arg2); if (fcntl(arg1, F_DUPFD, arg2) != arg2) return -1; if (op & PROC_FD_CHILD) close(arg1); } else if (op & PROC_FD_CHILD) { if (m->arg.fd.parent.flag) break; fcntl(arg1, F_SETFD, FD_CLOEXEC); } else if (!m->arg.fd.parent.flag) break; else fcntl(arg1, F_SETFD, 0); return 0; } break; case PROC_sig_dfl: if ((m->arg.handler = signal(arg1, SIG_DFL)) == SIG_DFL) break; m->save = (short)arg1; return 0; case PROC_sig_ign: if ((m->arg.handler = signal(arg1, SIG_IGN)) == SIG_IGN) break; m->save = (short)arg1; return 0; case PROC_sys_pgrp: proc->pgrp = arg1; break; case PROC_sys_umask: if ((m->save = (short)umask(arg1)) == arg1) break; return 0; default: proc->mods = m->next; free(m); return -1; } proc->mods = m->next; free(m); } #else NoP(proc); #endif return 0; } #if _use_spawnveg /* * restore modifications */ static void restore(Proc_t* proc) { register Modify_t* m; register Modify_t* p; int oerrno; NoP(proc); oerrno = errno; m = proc->mods; proc->mods = 0; while (m) { switch (m->op) { case PROC_fd_dup: case PROC_fd_dup|PROC_FD_PARENT: case PROC_fd_dup|PROC_FD_CHILD: case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD: if (m->op & PROC_FD_PARENT) close(m->arg.fd.parent.fd); if (m->arg.fd.child.fd != m->arg.fd.parent.fd && m->arg.fd.child.fd != PROC_ARG_NULL) { if (!(m->op & PROC_FD_PARENT)) { if (m->op & PROC_FD_CHILD) { close(m->arg.fd.parent.fd); fcntl(m->arg.fd.child.fd, F_DUPFD, m->arg.fd.parent.fd); } fcntl(m->arg.fd.parent.fd, F_SETFD, m->arg.fd.parent.flag); } close(m->arg.fd.child.fd); fcntl(m->save, F_DUPFD, m->arg.fd.child.fd); close(m->save); if (m->arg.fd.child.flag) fcntl(m->arg.fd.child.fd, F_SETFD, FD_CLOEXEC); } else if ((m->op & (PROC_FD_PARENT|PROC_FD_CHILD)) == PROC_FD_CHILD) fcntl(m->arg.fd.parent.fd, F_SETFD, 0); break; case PROC_sig_dfl: case PROC_sig_ign: signal(m->save, m->arg.handler); break; case PROC_sys_umask: umask(m->save); break; } p = m; m = m->next; free(p); } errno = oerrno; } #else #define restore(p) #endif /* * fork and exec or spawn proc(argv) and return a Proc_t handle * * pipe not used when PROC_READ|PROC_WRITE omitted * argv==0 duplicates current process if possible * cmd==0 names the current shell * cmd=="" does error cleanup * envv is the child environment * modv is the child modification vector of PROC_*() ops */ Proc_t* procopen(const char* cmd, char** argv, char** envv, long* modv, int flags) { register Proc_t* proc = 0; register int procfd; register char** p; char** v; int i; int forked = 0; int signalled = 0; long n; char path[PATH_MAX]; char env[PATH_MAX + 2]; int pio[2]; #if !_pipe_rw && !_lib_socketpair int poi[2]; #endif #if defined(SIGCHLD) && ( _lib_sigprocmask || _lib_sigsetmask ) Sig_mask_t mask; #endif #if _use_spawnveg int newenv = 0; #endif #if DEBUG_PROC int debug = PROC_OPT_EXEC; #endif #if _lib_fork if (!argv && (flags & PROC_OVERLAY)) #else if (!argv) #endif { errno = ENOEXEC; return 0; } pio[0] = pio[1] = -1; #if !_pipe_rw && !_lib_socketpair poi[0] = poi[1] = -1; #endif if (cmd && (!*cmd || !pathpath(path, cmd, NiL, PATH_REGULAR|PATH_EXECUTE))) goto bad; switch (flags & (PROC_READ|PROC_WRITE)) { case 0: procfd = -1; break; case PROC_READ: procfd = 1; break; case PROC_WRITE: procfd = 0; break; case PROC_READ|PROC_WRITE: procfd = 2; break; } if (proc_default.pid == -1) proc = &proc_default; else if (!(proc = newof(0, Proc_t, 1, 0))) goto bad; proc->pid = -1; proc->pgrp = 0; proc->rfd = -1; proc->wfd = -1; proc->flags = flags; sfsync(NiL); if (environ && envv != (char**)environ && (envv || (flags & PROC_PARANOID) || argv && (environ[0][0] != '_' || environ[0][1] != '='))) { if (!setenviron(NiL)) goto bad; #if _use_spawnveg newenv = 1; #endif } if (procfd >= 0) { #if _pipe_rw if (pipe(pio)) goto bad; #else if (procfd > 1) { #if _lib_socketpair if (socketpair(AF_UNIX, SOCK_STREAM, 0, pio)) goto bad; #else if (pipe(pio) || pipe(poi)) goto bad; #endif } else if (pipe(pio)) goto bad; #endif } if (flags & PROC_OVERLAY) { proc->pid = 0; forked = 1; } #if _use_spawnveg else if (argv) proc->pid = 0; #endif #if _lib_fork else { if (!(flags & PROC_FOREGROUND)) sigcritical(SIG_REG_EXEC|SIG_REG_PROC); else { signalled = 1; proc->sigint = signal(SIGINT, SIG_IGN); proc->sigquit = signal(SIGQUIT, SIG_IGN); #if defined(SIGCHLD) #if _lib_sigprocmask sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, &proc->mask); #else #if _lib_sigsetmask mask = sigmask(SIGCHLD); proc->mask = sigblock(mask); #else proc->sigchld = signal(SIGCHLD, SIG_DFL); #endif #endif #endif } proc->pid = fork(); if (!(flags & PROC_FOREGROUND)) sigcritical(0); else if (!proc->pid) { if (proc->sigint != SIG_IGN) { proc->sigint = SIG_DFL; signal(SIGINT, proc->sigint); } if (proc->sigquit != SIG_IGN) { proc->sigquit = SIG_DFL; signal(SIGQUIT, proc->sigquit); } #if defined(SIGCHLD) #if _lib_sigprocmask sigprocmask(SIG_SETMASK, &proc->mask, NiL); #else #if _lib_sigsetmask sigsetmask(proc->mask); #else if (proc->sigchld != SIG_IGN) signal(SIGCHLD, SIG_DFL); #endif #endif #endif } else if (proc->pid == -1) goto bad; forked = 1; } #endif if (!proc->pid) { #if _use_spawnveg char** oenviron = 0; char* oenviron0 = 0; v = 0; #endif #if DEBUG_PROC stropt(getenv(PROC_ENV_OPTIONS), options, sizeof(*options), setopt, &debug); #if _lib_fork if (debug & PROC_OPT_TRACE) { if (!fork()) { sfsprintf(path, sizeof(path), "%d", getppid()); execlp("trace", "trace", "-p", path, NiL); _exit(EXIT_NOTFOUND); } sleep(2); } #endif #endif if (flags & PROC_DAEMON) { #ifdef SIGHUP modify(proc, forked, PROC_sig_ign, SIGHUP, 0); #endif modify(proc, forked, PROC_sig_dfl, SIGTERM, 0); #ifdef SIGTSTP modify(proc, forked, PROC_sig_ign, SIGTSTP, 0); #endif #ifdef SIGTTIN modify(proc, forked, PROC_sig_ign, SIGTTIN, 0); #endif #ifdef SIGTTOU modify(proc, forked, PROC_sig_ign, SIGTTOU, 0); #endif } if (flags & (PROC_BACKGROUND|PROC_DAEMON)) { modify(proc, forked, PROC_sig_ign, SIGINT, 0); #ifdef SIGQUIT modify(proc, forked, PROC_sig_ign, SIGQUIT, 0); #endif } if (flags & (PROC_DAEMON|PROC_SESSION)) modify(proc, forked, PROC_sys_pgrp, -1, 0); if (forked || (flags & PROC_OVERLAY)) { if ((flags & PROC_PRIVELEGED) && !geteuid()) { setuid(geteuid()); setgid(getegid()); } if (flags & (PROC_PARANOID|PROC_GID)) setgid(getgid()); if (flags & (PROC_PARANOID|PROC_UID)) setuid(getuid()); } if (procfd > 1) { if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[0], PROC_ARG_NULL)) goto cleanup; if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[1], 1)) goto cleanup; #if _pipe_rw || _lib_socketpair if (modify(proc, forked, PROC_fd_dup, 1, 0)) goto cleanup; #else if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, poi[0], 0)) goto cleanup; if (poi[1] != 0 && modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, poi[1], PROC_ARG_NULL)) goto cleanup; #endif } else if (procfd >= 0) { if (modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[!!procfd], !!procfd)) goto cleanup; if (pio[!procfd] != !!procfd && modify(proc, forked, PROC_fd_dup|PROC_FD_CHILD, pio[!procfd], PROC_ARG_NULL)) goto cleanup; } if (modv) for (i = 0; n = modv[i]; i++) switch (PROC_OP(n)) { case PROC_fd_dup: case PROC_fd_dup|PROC_FD_PARENT: case PROC_fd_dup|PROC_FD_CHILD: case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD: if (modify(proc, forked, PROC_OP(n), PROC_ARG(n, 1), PROC_ARG(n, 2))) goto cleanup; break; default: if (modify(proc, forked, PROC_OP(n), PROC_ARG(n, 1), 0)) goto cleanup; break; } #if _lib_fork if (forked && (flags & PROC_ENVCLEAR)) environ = 0; #if _use_spawnveg else #endif #endif #if _use_spawnveg if (newenv) { p = environ; while (*p++); if (!(oenviron = (char**)memdup(environ, (p - environ) * sizeof(char*)))) goto cleanup; } #endif if (argv && envv != (char**)environ) { #if _use_spawnveg if (!newenv && environ[0][0] == '_' && environ[0][1] == '=') oenviron0 = environ[0]; #endif env[0] = '_'; env[1] = '='; env[2] = 0; if (!setenviron(env)) goto cleanup; } if ((flags & PROC_PARANOID) && setenv("PATH", astconf("PATH", NiL, NiL), 1)) goto cleanup; if ((p = envv) && p != (char**)environ) while (*p) if (!setenviron(*p++)) goto cleanup; p = argv; #if _lib_fork if (forked && !p) return proc; #endif #if DEBUG_PROC if (!(debug & PROC_OPT_EXEC) || (debug & PROC_OPT_VERBOSE)) { if ((debug & PROC_OPT_ENVIRONMENT) && (p = environ)) while (*p) sfprintf(sfstderr, "%s\n", *p++); sfprintf(sfstderr, "+ %s", cmd ? path : "sh"); if ((p = argv) && *p) while (*++p) sfprintf(sfstderr, " %s", *p); sfprintf(sfstderr, "\n"); sfsync(sfstderr); if (!(debug & PROC_OPT_EXEC)) _exit(0); p = argv; } #endif if (cmd) { strcpy(env + 2, path); if (forked || (flags & PROC_OVERLAY)) execve(path, p, environ); #if _use_spawnveg else if ((proc->pid = spawnveg(path, p, environ, proc->pgrp)) != -1) goto cleanup; #endif if (errno != ENOEXEC) goto cleanup; /* * try cmd as a shell script */ if (!(flags & PROC_ARGMOD)) { while (*p++); if (!(v = newof(0, char*, p - argv + 2, 0))) goto cleanup; p = v + 2; if (*argv) argv++; while (*p++ = *argv++); p = v + 1; } *p = path; *--p = "sh"; } strcpy(env + 2, (flags & PROC_PARANOID) ? astconf("SH", NiL, NiL) : pathshell()); if (forked || (flags & PROC_OVERLAY)) execve(env + 2, p, environ); #if _use_spawnveg else proc->pid = spawnveg(env + 2, p, environ, proc->pgrp); #endif cleanup: if (forked) { if (!(flags & PROC_OVERLAY)) _exit(errno == ENOENT ? EXIT_NOTFOUND : EXIT_NOEXEC); goto bad; } #if _use_spawnveg if (v) free(v); if (p = oenviron) { environ = 0; while (*p) if (!setenviron(*p++)) goto bad; free(oenviron); } else if (oenviron0) environ[0] = oenviron0; restore(proc); if (flags & PROC_OVERLAY) exit(0); #endif } if (proc->pid != -1) { if (!forked) { if (flags & PROC_FOREGROUND) { signalled = 1; proc->sigint = signal(SIGINT, SIG_IGN); proc->sigquit = signal(SIGQUIT, SIG_IGN); #if defined(SIGCHLD) #if _lib_sigprocmask sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, &proc->mask); #else #if _lib_sigsetmask mask = sigmask(SIGCHLD); proc->mask = sigblock(mask); #else proc->sigchld = signal(SIGCHLD, SIG_DFL); #endif #endif #endif } } else if (modv) for (i = 0; n = modv[i]; i++) switch (PROC_OP(n)) { case PROC_fd_dup|PROC_FD_PARENT: case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD: close(PROC_ARG(n, 1)); break; case PROC_sys_pgrp: if (proc->pgrp < 0) proc->pgrp = proc->pid; else if (proc->pgrp > 0) { if (proc->pgrp == 1) proc->pgrp = proc->pid; if (setpgid(proc->pid, proc->pgrp) < 0 && proc->pid != proc->pgrp && errno == EPERM) setpgid(proc->pid, proc->pid); } break; } if (procfd >= 0) { #ifdef SIGPIPE if ((flags & (PROC_WRITE|PROC_IGNORE)) == (PROC_WRITE|PROC_IGNORE)) { Handler_t handler; if ((handler = signal(SIGPIPE, ignoresig)) != SIG_DFL && handler != ignoresig) signal(SIGPIPE, handler); } #endif switch (procfd) { case 0: proc->wfd = pio[1]; close(pio[0]); break; default: #if _pipe_rw || _lib_socketpair proc->wfd = pio[0]; #else proc->wfd = poi[1]; close(poi[0]); #endif /*FALLTHROUGH*/ case 1: proc->rfd = pio[0]; close(pio[1]); break; } if (proc->rfd > 2) fcntl(proc->rfd, F_SETFD, FD_CLOEXEC); if (proc->wfd > 2) fcntl(proc->wfd, F_SETFD, FD_CLOEXEC); } if (!proc->pid) proc->pid = getpid(); return proc; } bad: if (signalled) { if (proc->sigint != SIG_IGN) signal(SIGINT, proc->sigint); if (proc->sigquit != SIG_IGN) signal(SIGQUIT, proc->sigquit); #if defined(SIGCHLD) #if _lib_sigprocmask sigprocmask(SIG_SETMASK, &proc->mask, NiL); #else #if _lib_sigsetmask sigsetmask(proc->mask); #else if (proc->sigchld != SIG_DFL) signal(SIGCHLD, proc->sigchld); #endif #endif #endif } if ((flags & PROC_CLEANUP) && modv) for (i = 0; n = modv[i]; i++) switch (PROC_OP(n)) { case PROC_fd_dup: case PROC_fd_dup|PROC_FD_PARENT: case PROC_fd_dup|PROC_FD_CHILD: case PROC_fd_dup|PROC_FD_PARENT|PROC_FD_CHILD: if (PROC_ARG(n, 2) != PROC_ARG_NULL) close(PROC_ARG(n, 1)); break; } if (pio[0] >= 0) close(pio[0]); if (pio[1] >= 0) close(pio[1]); #if !_pipe_rw && !_lib_socketpair if (poi[0] >= 0) close(poi[0]); if (poi[1] >= 0) close(poi[1]); #endif procfree(proc); return 0; }