Invent systemvpe() function

It goes 5x faster than system() and it's safer too.
This commit is contained in:
Justine Tunney 2023-08-09 00:27:55 -07:00
parent 1a5ef5ba13
commit 50394064d7
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
5 changed files with 132 additions and 4 deletions

View file

@ -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) */

View file

@ -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;
}

89
libc/stdio/systemvpe.c Normal file
View file

@ -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;
}

View file

@ -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));
}

View file

@ -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"));
}