diff --git a/libc/cosmo.h b/libc/cosmo.h index f349d01c4..a35324e06 100644 --- a/libc/cosmo.h +++ b/libc/cosmo.h @@ -4,6 +4,7 @@ COSMOPOLITAN_C_START_ errno_t cosmo_once(_Atomic(uint32_t) *, void (*)(void)); +int systemvpe(const char *, char *const[], char *const[]); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/stdio/system.c b/libc/stdio/system.c index c340d5eda..57175e1b3 100644 --- a/libc/stdio/system.c +++ b/libc/stdio/system.c @@ -34,20 +34,37 @@ /** * Launches program with system command interpreter. * - * This embeds the Cosmopolitan Command Interpreter which provides - * Bourne-like syntax on all platforms including Windows. + * This implementation embeds the Cosmopolitan Command Interpreter which + * provides Bourne-like syntax on all platforms, including Windows. Many + * builtin commands are included, e.g. exit, cd, rm, [, cat, wait, exec, + * env, echo, read, true, test, kill, touch, rmdir, mkdir, false, mktemp + * and usleep. It's also possible to __static_yoink() the symbols `_tr`, + * `_sed`, `_awk`, and `_curl` for the tr, sed, awk and curl commands if + * you're using the Cosmopolitan mono-repo. + * + * If you just have a program name and arguments, and you don't need the + * full power of a UNIX-like shell, then consider using the Cosmopolitan + * provided API systemvpe() instead. It provides a safer alternative for + * variable arguments than shell script escaping. It lets you clean your + * environment variables, for even more safety. Finally it's 10x faster. + * + * It's important to check the returned status code. For example, if you + * press CTRL-C while running your program you'll expect it to terminate + * however that won't be the case if the SIGINT gets raised while inside + * the system() function. If the child process doesn't handle the signal + * then this will return e.g. WIFSIGNALED(ws) && WTERMSIG(ws) == SIGINT. * * @param cmdline is a unix shell script * @return -1 if child process couldn't be created, otherwise a wait * status that can be accessed using macros like WEXITSTATUS(s), * WIFSIGNALED(s), WTERMSIG(s), etc. + * @see systemve() * @threadsafe */ int system(const char *cmdline) { int pid, wstatus; sigset_t chldmask, savemask; if (!cmdline) return 1; - BLOCK_CANCELLATIONS; sigemptyset(&chldmask); sigaddset(&chldmask, SIGINT); sigaddset(&chldmask, SIGQUIT); @@ -65,16 +82,17 @@ int system(const char *cmdline) { sigemptyset(&ignore.sa_mask); sigaction(SIGINT, &ignore, &saveint); sigaction(SIGQUIT, &ignore, &savequit); + BLOCK_CANCELLATIONS; while (wait4(pid, &wstatus, 0, 0) == -1) { if (errno != EINTR) { wstatus = -1; break; } } + ALLOW_CANCELLATIONS; sigaction(SIGQUIT, &savequit, 0); sigaction(SIGINT, &saveint, 0); } sigprocmask(SIG_SETMASK, &savemask, 0); - ALLOW_CANCELLATIONS; return wstatus; } diff --git a/libc/stdio/systemvpe.c b/libc/stdio/systemvpe.c new file mode 100644 index 000000000..e5f10a244 --- /dev/null +++ b/libc/stdio/systemvpe.c @@ -0,0 +1,89 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/rusage.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/errno.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/sig.h" + +/** + * Executes program and waits for it to complete, e.g. + * + * systemvpe("ls", (char *[]){"ls", dir, 0}, environ); + * + * This function is designed to do the same thing as system() except + * rather than taking a shell script argument it accepts an array of + * strings which are safely passed directly to execve(). + * + * This function is 5x faster than system() and generally safer, for + * most command running use cases that don't need to control the i/o + * file descriptors. + * + * @param prog is path searched (if it doesn't contain a slash) from + * the $PATH environment variable in `environ` (not your `envp`) + * @return -1 if child process couldn't be created, otherwise a wait + * status that can be accessed using macros like WEXITSTATUS(s), + * WIFSIGNALED(s), WTERMSIG(s), etc. + * @see system() + * @threadsafe + */ +int systemvpe(const char *prog, char *const argv[], char *const envp[]) { + char *exe; + int pid, wstatus; + char pathbuf[PATH_MAX + 1]; + sigset_t chldmask, savemask; + if (!(exe = commandv(prog, pathbuf, sizeof(pathbuf)))) { + return -1; + } + sigemptyset(&chldmask); + sigaddset(&chldmask, SIGINT); + sigaddset(&chldmask, SIGQUIT); + sigaddset(&chldmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &chldmask, &savemask); + if (!(pid = vfork())) { + sigprocmask(SIG_SETMASK, &savemask, 0); + execve(prog, argv, envp); + _Exit(127); + } else if (pid == -1) { + wstatus = -1; + } else { + struct sigaction ignore, saveint, savequit; + ignore.sa_flags = 0; + ignore.sa_handler = SIG_IGN; + sigemptyset(&ignore.sa_mask); + sigaction(SIGINT, &ignore, &saveint); + sigaction(SIGQUIT, &ignore, &savequit); + BLOCK_CANCELLATIONS; + while (wait4(pid, &wstatus, 0, 0) == -1) { + if (errno != EINTR) { + wstatus = -1; + break; + } + } + ALLOW_CANCELLATIONS; + sigaction(SIGQUIT, &savequit, 0); + sigaction(SIGINT, &saveint, 0); + } + sigprocmask(SIG_SETMASK, &savemask, 0); + return wstatus; +} diff --git a/test/libc/calls/sigaction_test.c b/test/libc/calls/sigaction_test.c index c41e3818e..5c4bcf159 100644 --- a/test/libc/calls/sigaction_test.c +++ b/test/libc/calls/sigaction_test.c @@ -19,6 +19,7 @@ #include "libc/calls/struct/sigaction.h" #include "libc/calls/calls.h" #include "libc/calls/struct/rusage.h" +#include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" @@ -248,3 +249,19 @@ TEST(uc_sigmask, signalHandlerCanChangeSignalMaskOfTrappedThread) { sigdelset(&want, SIGUSR1); ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &want, 0)); } + +TEST(sig_ign, discardsPendingSignalsEvenIfBlocked) { + sigset_t block, oldmask; + struct sigaction sa, oldsa; + ASSERT_SYS(0, 0, sigemptyset(&block)); + ASSERT_SYS(0, 0, sigaddset(&block, SIGUSR1)); + ASSERT_SYS(0, 0, sigprocmask(SIG_BLOCK, &block, &oldmask)); + raise(SIGUSR1); // enqueue + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + ASSERT_SYS(0, 0, sigemptyset(&sa.sa_mask)); + ASSERT_SYS(0, 0, sigaction(SIGUSR1, &sa, &oldsa)); // discard + ASSERT_SYS(0, 0, sigprocmask(SIG_UNBLOCK, &block, 0)); + ASSERT_SYS(0, 0, sigaction(SIGUSR1, &oldsa, 0)); + ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &oldmask, 0)); +} diff --git a/test/libc/stdio/system_test.c b/test/libc/stdio/system_test.c index 0649a1c87..e6bc87353 100644 --- a/test/libc/stdio/system_test.c +++ b/test/libc/stdio/system_test.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/mem/gc.h" #include "libc/mem/gc.internal.h" @@ -243,6 +244,8 @@ int system2(const char *); BENCH(system, bench) { testlib_extract("/zip/echo.com", "echo.com", 0755); EZBENCH2("system cmd", donothing, system("./echo.com hi >/dev/null")); + EZBENCH2("systemvpe cmd", donothing, + systemvpe("./echo.com", (char *[]){"./echo.com", "hi", 0}, 0)); EZBENCH2("cocmd echo", donothing, system("echo hi >/dev/null")); EZBENCH2("cocmd exit", donothing, system("exit")); }