Systemd/src/basic/exec-util.c
Lennart Poettering 0c69794138 tree-wide: remove Lennart's copyright lines
These lines are generally out-of-date, incomplete and unnecessary. With
SPDX and git repository much more accurate and fine grained information
about licensing and authorship is available, hence let's drop the
per-file copyright notice. Of course, removing copyright lines of others
is problematic, hence this commit only removes my own lines and leaves
all others untouched. It might be nicer if sooner or later those could
go away too, making git the only and accurate source of authorship
information.
2018-06-14 10:20:20 +02:00

323 lines
9.5 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <dirent.h>
#include <errno.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include "alloc-util.h"
#include "conf-files.h"
#include "env-util.h"
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
#include "macro.h"
#include "process-util.h"
#include "set.h"
#include "signal-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "util.h"
/* Put this test here for a lack of better place */
assert_cc(EAGAIN == EWOULDBLOCK);
static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) {
pid_t _pid;
int r;
if (null_or_empty_path(path)) {
log_debug("%s is empty (a mask).", path);
return 0;
}
r = safe_fork("(direxec)", FORK_DEATHSIG|FORK_LOG, &_pid);
if (r < 0)
return r;
if (r == 0) {
char *_argv[2];
if (stdout_fd >= 0) {
r = rearrange_stdio(STDIN_FILENO, stdout_fd, STDERR_FILENO);
if (r < 0)
_exit(EXIT_FAILURE);
}
if (!argv) {
_argv[0] = (char*) path;
_argv[1] = NULL;
argv = _argv;
} else
argv[0] = (char*) path;
execv(path, argv);
log_error_errno(errno, "Failed to execute %s: %m", path);
_exit(EXIT_FAILURE);
}
*pid = _pid;
return 1;
}
static int do_execute(
char **directories,
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
int output_fd,
char *argv[]) {
_cleanup_hashmap_free_free_ Hashmap *pids = NULL;
_cleanup_strv_free_ char **paths = NULL;
char **path;
int r;
/* We fork this all off from a child process so that we can somewhat cleanly make
* use of SIGALRM to set a time limit.
*
* If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
*/
r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
if (r < 0)
return r;
if (!callbacks) {
pids = hashmap_new(NULL);
if (!pids)
return log_oom();
}
/* Abort execution of this process after the timout. We simply rely on SIGALRM as
* default action terminating the process, and turn on alarm(). */
if (timeout != USEC_INFINITY)
alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
STRV_FOREACH(path, paths) {
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -1;
pid_t pid;
t = strdup(*path);
if (!t)
return log_oom();
if (callbacks) {
fd = open_serialization_fd(basename(*path));
if (fd < 0)
return log_error_errno(fd, "Failed to open serialization file: %m");
}
r = do_spawn(t, argv, fd, &pid);
if (r <= 0)
continue;
if (pids) {
r = hashmap_put(pids, PID_TO_PTR(pid), t);
if (r < 0)
return log_oom();
t = NULL;
} else {
r = wait_for_terminate_and_check(t, pid, WAIT_LOG);
if (r < 0)
continue;
if (lseek(fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek on serialization fd: %m");
r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]);
fd = -1;
if (r < 0)
return log_error_errno(r, "Failed to process output from %s: %m", *path);
}
}
if (callbacks) {
r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]);
if (r < 0)
return log_error_errno(r, "Callback two failed: %m");
}
while (!hashmap_isempty(pids)) {
_cleanup_free_ char *t = NULL;
pid_t pid;
pid = PTR_TO_PID(hashmap_first_key(pids));
assert(pid > 0);
t = hashmap_remove(pids, PID_TO_PTR(pid));
assert(t);
(void) wait_for_terminate_and_check(t, pid, WAIT_LOG);
}
return 0;
}
int execute_directories(
const char* const* directories,
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
char *argv[]) {
char **dirs = (char**) directories;
_cleanup_close_ int fd = -1;
char *name;
int r;
assert(!strv_isempty(dirs));
name = basename(dirs[0]);
assert(!isempty(name));
if (callbacks) {
assert(callback_args);
assert(callbacks[STDOUT_GENERATE]);
assert(callbacks[STDOUT_COLLECT]);
assert(callbacks[STDOUT_CONSUME]);
fd = open_serialization_fd(name);
if (fd < 0)
return log_error_errno(fd, "Failed to open serialization file: %m");
}
/* Executes all binaries in the directories serially or in parallel and waits for
* them to finish. Optionally a timeout is applied. If a file with the same name
* exists in more than one directory, the earliest one wins. */
r = safe_fork("(sd-executor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
if (r < 0)
return r;
if (r == 0) {
r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv);
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
if (!callbacks)
return 0;
if (lseek(fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to rewind serialization fd: %m");
r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]);
fd = -1;
if (r < 0)
return log_error_errno(r, "Failed to parse returned data: %m");
return 0;
}
static int gather_environment_generate(int fd, void *arg) {
char ***env = arg, **x, **y;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **new = NULL;
int r;
/* Read a series of VAR=value assignments from fd, use them to update the list of
* variables in env. Also update the exported environment.
*
* fd is always consumed, even on error.
*/
assert(env);
f = fdopen(fd, "r");
if (!f) {
safe_close(fd);
return -errno;
}
r = load_env_file_pairs(f, NULL, NULL, &new);
if (r < 0)
return r;
STRV_FOREACH_PAIR(x, y, new) {
char *p;
if (!env_name_is_valid(*x)) {
log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x);
continue;
}
p = strjoin(*x, "=", *y);
if (!p)
return -ENOMEM;
r = strv_env_replace(env, p);
if (r < 0)
return r;
if (setenv(*x, *y, true) < 0)
return -errno;
}
return r;
}
static int gather_environment_collect(int fd, void *arg) {
char ***env = arg;
_cleanup_fclose_ FILE *f = NULL;
int r;
/* Write out a series of env=cescape(VAR=value) assignments to fd. */
assert(env);
f = fdopen(fd, "w");
if (!f) {
safe_close(fd);
return -errno;
}
r = serialize_environment(f, *env);
if (r < 0)
return r;
if (ferror(f))
return errno > 0 ? -errno : -EIO;
return 0;
}
static int gather_environment_consume(int fd, void *arg) {
char ***env = arg;
_cleanup_fclose_ FILE *f = NULL;
char line[LINE_MAX];
int r = 0, k;
/* Read a series of env=cescape(VAR=value) assignments from fd into env. */
assert(env);
f = fdopen(fd, "r");
if (!f) {
safe_close(fd);
return -errno;
}
FOREACH_LINE(line, f, return -EIO) {
truncate_nl(line);
k = deserialize_environment(env, line);
if (k < 0)
log_error_errno(k, "Invalid line \"%s\": %m", line);
if (k < 0 && r == 0)
r = k;
}
return r;
}
const gather_stdout_callback_t gather_environment[] = {
gather_environment_generate,
gather_environment_collect,
gather_environment_consume,
};