1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
/* Copyright © Triad National Security, LLC, and others. */
#define _GNU_SOURCE
#include "config.h"
#include "misc.h"
#include <pwd.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "all.h"
/** External variables **/
/* Path to host temporary directory. Set during command line processing. */
char *host_tmp = NULL;
/* Username of invoking user. Set during early initialization. */
char *username = NULL;
/** Functions **/
/** Run a command, wait for it to exit, then return. If the command fails,
exit with a fatal error. Similar to Python’s @c subprocess.run().
@param cmd Command to run. If a path contains a slash, run that file
directly; otherwise, search in @c $PATH; i.e. the same
behavior as @c execlp(3).
@param pathx Colon-separated string suitable for @c $PATH to append to
the existing @c $PATH (e.g. “@c /foo:/bar”)., or @c NULL if
none. Neither the child nor the current process” @c $PATH is
modified. May not begin/end with colon.
@param args2 Null-terminated array of arguments for the command, like
@c execv(3).
@param args1 Null-terminated variadic arguments for the command, like
@c execl(3). If not used, caller must provide @c NULL.
@warning
Both @p args1 and @p args2 can be non-NULL simultaneously. In this case,
@p args1 precedes @p args2 on the @p cmd command line, which is the
opposite order from the function call.
Design decisions:
#. Manual @c fork(2) / @c exec(2) rather than @c posix_spawn(3) for better
error reporting.
#. @c fork(2) rather than @c vfork(2) or @c clone(2) because I didn’t feel
like thinking hard enough to figure out whether one of the latter two
would be better.
#. @c fork(2) rather than @c fork_ch() because we don’t want garbage
collection overhead. */
void run_wait(char *cmd, char *pathx, char **args2, ...)
{
pid_t child;
T__ (pathx && pathx[0] != '\0');
T__ (pathx[0] != ':' && pathx[strlen(pathx)-1] != ':');
#undef fork
Tfe (0 <= (child = fork()), "can't fork: %s", cmd);
#define fork FN_BLOCKED
if (child) { // parent
int ws;
DEBUG("run_wait: waiting ...");
T_e (-1 != waitpid(child, &ws, 0));
if (WIFEXITED(ws)) {
Zf_ (WEXITSTATUS(ws), "%s: failed with %d", cmd, WEXITSTATUS(ws));
} else {
// no WUNTRACED so assume WIFSIGNALED
Tf_ (0, "%s: killed by signal %d", cmd, WTERMSIG(ws));
}
} else { // child
char **args, **env_saved;
char *arg, *path;
va_list ap;
// append pathx for execvpe(3)
env_saved = list_copy(environ, sizeof(environ[0]));
path = cats(3, getenv("PATH"), (pathx == NULL ? "" : ":"), pathx);
setenv("PATH", path, true);
DEBUG("run_wait: PATH=%s", path);
// prepare command arguments
args = NULL;
list_append((void **)&args, &cmd, sizeof(cmd));
va_start(ap, args2);
while ((arg = va_arg(ap, char *)))
list_append((void **)&args, &arg, sizeof(arg));
va_end(ap);
list_cat((void **)&args, args2, sizeof(char *));
// replace self with command
T__ (streq(cmd, args[0]));
DEBUG("run_wait: $ %s", argv_to_string(args));
execvpe(cmd, args, env_saved); // only returns on error
ERROR (errno, "can't exec: %s", cmd);
exit(EXIT_ERR_CMD);
}
}
/* Set the username global by looking up EUID in the password database. Logic
must match ch.user(). Formerly, we used $USER, but that’s not reliably set.
See #1162. This approach does require that EUID *have* a corresponding
username. */
void username_set(void)
{
struct passwd *pw;
errno = 0;
Tfe (pw = getpwuid(geteuid()), "can't get username for EUID %d", geteuid());
username = pw->pw_name;
}
/* Report the version number. */
void version(void)
{
fprintf(stderr, "%s\n", VERSION);
}
|