-/*-*- Mode: C; c-basic-offset: 8 -*-*/
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
#include <pwd.h>
#include <sys/mount.h>
#include <linux/fs.h>
+#include <linux/oom.h>
#ifdef HAVE_PAM
#include <security/pam_appl.h>
#include "cgroup.h"
#include "namespace.h"
#include "tcpwrap.h"
+#include "exit-status.h"
+#include "missing.h"
+#include "utmp-wtmp.h"
/* This assumes there is a 'tty' group */
#define TTY_MODE 0620
sa.sa.sa_family = AF_UNIX;
strncpy(sa.un.sun_path+1, LOGGER_SOCKET, sizeof(sa.un.sun_path)-1);
- if (connect(fd, &sa.sa, sizeof(sa)) < 0) {
+ if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + sizeof(LOGGER_SOCKET) - 1) < 0) {
close_nointr_nofail(fd);
return -errno;
}
"%i\n"
"%s\n"
"%i\n",
- output == EXEC_OUTPUT_KMSG ? "kmsg" : "syslog",
+ output == EXEC_OUTPUT_KMSG ? "kmsg" :
+ output == EXEC_OUTPUT_KMSG_AND_CONSOLE ? "kmsg+console" :
+ output == EXEC_OUTPUT_SYSLOG ? "syslog" :
+ "syslog+console",
context->syslog_priority,
context->syslog_identifier ? context->syslog_identifier : ident,
- !context->syslog_no_prefix);
+ context->syslog_level_prefix);
if (fd != nfd) {
r = dup2(fd, nfd) < 0 ? -errno : nfd;
i == EXEC_INPUT_TTY_FAIL;
}
-static int fixup_input(ExecInput std_input, int socket_fd) {
+static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) {
+
+ if (is_terminal_input(std_input) && !apply_tty_stdin)
+ return EXEC_INPUT_NULL;
if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0)
return EXEC_INPUT_NULL;
return std_output;
}
-static int setup_input(const ExecContext *context, int socket_fd) {
+static int setup_input(const ExecContext *context, int socket_fd, bool apply_tty_stdin) {
ExecInput i;
assert(context);
- i = fixup_input(context->std_input, socket_fd);
+ i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
switch (i) {
}
}
-static int setup_output(const ExecContext *context, int socket_fd, const char *ident) {
+static int setup_output(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) {
ExecOutput o;
ExecInput i;
assert(context);
assert(ident);
- i = fixup_input(context->std_input, socket_fd);
+ i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
o = fixup_output(context->std_output, socket_fd);
/* This expects the input is already set up */
case EXEC_OUTPUT_INHERIT:
- /* If the input is connected to a terminal, inherit that... */
+ /* If input got downgraded, inherit the original value */
+ if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input))
+ return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO);
+
+ /* If the input is connected to anything that's not a /dev/null, inherit that... */
if (i != EXEC_INPUT_NULL)
return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
- /* For PID 1 stdout is always connected to /dev/null,
- * hence reopen the console if out parent is PID1. */
- if (getppid() == 1)
- return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO);
+ /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */
+ if (getppid() != 1)
+ return STDOUT_FILENO;
- return STDOUT_FILENO;
+ /* We need to open /dev/null here anew, to get the
+ * right access mode. So we fall through */
case EXEC_OUTPUT_NULL:
return open_null_as(O_WRONLY, STDOUT_FILENO);
return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO);
case EXEC_OUTPUT_SYSLOG:
+ case EXEC_OUTPUT_SYSLOG_AND_CONSOLE:
case EXEC_OUTPUT_KMSG:
+ case EXEC_OUTPUT_KMSG_AND_CONSOLE:
return connect_logger_as(context, o, ident, STDOUT_FILENO);
case EXEC_OUTPUT_SOCKET:
}
}
-static int setup_error(const ExecContext *context, int socket_fd, const char *ident) {
+static int setup_error(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) {
ExecOutput o, e;
ExecInput i;
assert(context);
assert(ident);
- i = fixup_input(context->std_input, socket_fd);
+ i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
o = fixup_output(context->std_output, socket_fd);
e = fixup_output(context->std_error, socket_fd);
* the way and are not on a tty */
if (e == EXEC_OUTPUT_INHERIT &&
o == EXEC_OUTPUT_INHERIT &&
- i != EXEC_INPUT_NULL &&
+ i == EXEC_INPUT_NULL &&
+ !is_terminal_input(context->std_input) &&
getppid () != 1)
return STDERR_FILENO;
- /* Duplicate form stdout if possible */
+ /* Duplicate from stdout if possible */
if (e == o || e == EXEC_OUTPUT_INHERIT)
return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
return open_terminal_as(tty_path(context), O_WRONLY, STDERR_FILENO);
case EXEC_OUTPUT_SYSLOG:
+ case EXEC_OUTPUT_SYSLOG_AND_CONSOLE:
case EXEC_OUTPUT_KMSG:
+ case EXEC_OUTPUT_KMSG_AND_CONSOLE:
return connect_logger_as(context, e, ident, STDERR_FILENO);
case EXEC_OUTPUT_SOCKET:
/* If there are multiple users with the same id, make
* sure to leave $USER to the configured value instead
- * of the first occurence in the database. However if
+ * of the first occurrence in the database. However if
* the uid was configured by a numeric uid, then let's
* pick the real username from /etc/passwd. */
if (*username && p)
assert(context);
- /* Lookup and ser GID and supplementary group list. Here too
+ /* Lookup and set GID and supplementary group list. Here too
* we avoid NSS lookups for gid=0. */
if (context->group || username) {
/* First step: If we need to keep capabilities but
* drop privileges we need to make sure we keep our
- * caps, whiel we drop priviliges. */
+ * caps, whiel we drop privileges. */
if (uid != 0) {
int sb = context->secure_bits|SECURE_KEEP_CAPS;
return -errno;
}
- /* Second step: set the capabilites. This will reduce
+ /* Second step: set the capabilities. This will reduce
* the capabilities to the minimum we need. */
if (!(d = cap_dup(context->capabilities)))
assert(pam_env);
/* We set up PAM in the parent process, then fork. The child
- * will then stay around untill killed via PR_GET_PDEATHSIG or
+ * will then stay around until killed via PR_GET_PDEATHSIG or
* systemd via the cgroup logic. It will then remove the PAM
* session again. The parent process will exec() the actual
* daemon. We do things this way to ensure that the main PID
/* Wait until our parent died. This will most likely
* not work since the kernel does not allow
- * unpriviliged paretns kill their priviliged children
+ * unprivileged parents kill their privileged children
* this way. We rely on the control groups kill logic
* to do the rest for us. */
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
char **environment,
bool apply_permissions,
bool apply_chroot,
+ bool apply_tty_stdin,
bool confirm_spawn,
CGroupBonding *cgroup_bondings,
pid_t *ret) {
const char *username = NULL, *home = NULL;
uid_t uid = (uid_t) -1;
gid_t gid = (gid_t) -1;
- char **our_env = NULL, **pam_env = NULL, **final_env = NULL;
+ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
unsigned n_env = 0;
int saved_stdout = -1, saved_stdin = -1;
bool keep_stdout = false, keep_stdin = false;
goto fail;
}
- if (!context->no_setsid)
+ /* Close sockets very early to make sure we don't
+ * block init reexecution because it cannot bind its
+ * sockets */
+ if (close_all_fds(socket_fd >= 0 ? &socket_fd : fds,
+ socket_fd >= 0 ? 1 : n_fds) < 0) {
+ r = EXIT_FDS;
+ goto fail;
+ }
+
+ if (!context->same_pgrp)
if (setsid() < 0) {
r = EXIT_SETSID;
goto fail;
}
}
- if (confirm_spawn) {
+ /* We skip the confirmation step if we shall not apply the TTY */
+ if (confirm_spawn &&
+ (!is_terminal_input(context->std_input) || apply_tty_stdin)) {
char response;
/* Set up terminal for the question */
goto fail;
}
+ /* If a socket is connected to STDIN/STDOUT/STDERR, we
+ * must sure to drop O_NONBLOCK */
+ if (socket_fd >= 0)
+ fd_nonblock(socket_fd, false);
+
if (!keep_stdin)
- if (setup_input(context, socket_fd) < 0) {
+ if (setup_input(context, socket_fd, apply_tty_stdin) < 0) {
r = EXIT_STDIN;
goto fail;
}
if (!keep_stdout)
- if (setup_output(context, socket_fd, file_name_from_path(command->path)) < 0) {
+ if (setup_output(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin) < 0) {
r = EXIT_STDOUT;
goto fail;
}
- if (setup_error(context, socket_fd, file_name_from_path(command->path)) < 0) {
+ if (setup_error(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin) < 0) {
r = EXIT_STDERR;
goto fail;
}
if (cgroup_bondings)
- if ((r = cgroup_bonding_install_list(cgroup_bondings, 0)) < 0) {
+ if (cgroup_bonding_install_list(cgroup_bondings, 0) < 0) {
r = EXIT_CGROUP;
goto fail;
}
- if (context->oom_adjust_set) {
+ if (context->oom_score_adjust_set) {
char t[16];
- snprintf(t, sizeof(t), "%i", context->oom_adjust);
+ snprintf(t, sizeof(t), "%i", context->oom_score_adjust);
char_array_0(t);
- if (write_one_line_file("/proc/self/oom_adj", t) < 0) {
- r = EXIT_OOM_ADJUST;
- goto fail;
+ if (write_one_line_file("/proc/self/oom_score_adj", t) < 0) {
+ /* Compatibility with Linux <= 2.6.35 */
+
+ int adj;
+
+ adj = (context->oom_score_adjust * -OOM_DISABLE) / OOM_SCORE_ADJ_MAX;
+ adj = CLAMP(adj, OOM_DISABLE, OOM_ADJUST_MAX);
+
+ snprintf(t, sizeof(t), "%i", adj);
+ char_array_0(t);
+
+ if (write_one_line_file("/proc/self/oom_adj", t) < 0) {
+ r = EXIT_OOM_ADJUST;
+ goto fail;
+ }
}
}
goto fail;
}
- if (context->timer_slack_ns_set)
- if (prctl(PR_SET_TIMERSLACK, context->timer_slack_ns_set) < 0) {
+ if (context->timer_slack_nsec_set)
+ if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) {
r = EXIT_TIMERSLACK;
goto fail;
}
+ if (context->utmp_id)
+ utmp_put_init_process(0, context->utmp_id, getpid(), getsid(0), context->tty_path);
+
if (context->user) {
username = context->user;
if (get_user_creds(&username, &uid, &gid, &home) < 0) {
#ifdef HAVE_PAM
if (context->pam_name && username) {
- /* Make sure no fds leak into the PAM
- * supervisor process. We will call this later
- * on again to make sure that any fds leaked
- * by the PAM modules get closed before our
- * exec(). */
- if (close_all_fds(fds, n_fds) < 0) {
- r = EXIT_FDS;
- goto fail;
- }
-
if (setup_pam(context->pam_name, username, context->tty_path, &pam_env, fds, n_fds) < 0) {
r = EXIT_PAM;
goto fail;
free(d);
}
+ /* We repeat the fd closing here, to make sure that
+ * nothing is leaked from the PAM modules */
if (close_all_fds(fds, n_fds) < 0 ||
shift_fds(fds, n_fds) < 0 ||
flags_fds(fds, n_fds, context->non_blocking) < 0) {
goto fail;
}
- /* PR_GET_SECUREBITS is not priviliged, while
+ /* PR_GET_SECUREBITS is not privileged, while
* PR_SET_SECUREBITS is. So to suppress
* potential EPERMs we'll try not to call
* PR_SET_SECUREBITS unless necessary. */
}
}
- if (!(our_env = new0(char*, 6))) {
+ if (!(our_env = new0(char*, 7))) {
r = EXIT_MEMORY;
goto fail;
}
goto fail;
}
- assert(n_env <= 6);
+ if (is_terminal_input(context->std_input) ||
+ context->std_output == EXEC_OUTPUT_TTY ||
+ context->std_error == EXEC_OUTPUT_TTY)
+ if (!(our_env[n_env++] = strdup(default_term_for_tty(tty_path(context))))) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ assert(n_env <= 7);
if (!(final_env = strv_env_merge(
4,
goto fail;
}
- execve(command->path, argv, final_env);
+ if (!(final_argv = replace_env_argv(argv, final_env))) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ final_env = strv_env_clean(final_env);
+
+ execve(command->path, final_argv, final_env);
r = EXIT_EXEC;
fail:
strv_free(our_env);
strv_free(final_env);
strv_free(pam_env);
+ strv_free(final_argv);
if (saved_stdin >= 0)
close_nointr_nofail(saved_stdin);
c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0);
c->cpu_sched_policy = SCHED_OTHER;
c->syslog_priority = LOG_DAEMON|LOG_INFO;
+ c->syslog_level_prefix = true;
c->mount_flags = MS_SHARED;
+ c->kill_signal = SIGTERM;
+ c->send_sigkill = true;
}
void exec_context_done(ExecContext *c) {
if (c->cpuset)
CPU_FREE(c->cpuset);
+
+ free(c->utmp_id);
+ c->utmp_id = NULL;
}
void exec_command_done(ExecCommand *c) {
"%sNice: %i\n",
prefix, c->nice);
- if (c->oom_adjust_set)
+ if (c->oom_score_adjust_set)
fprintf(f,
- "%sOOMAdjust: %i\n",
- prefix, c->oom_adjust);
+ "%sOOMScoreAdjust: %i\n",
+ prefix, c->oom_score_adjust);
for (i = 0; i < RLIM_NLIMITS; i++)
if (c->rlimit[i])
fputs("\n", f);
}
- if (c->timer_slack_ns_set)
- fprintf(f, "%sTimerSlackNS: %lu\n", prefix, c->timer_slack_ns);
+ if (c->timer_slack_nsec_set)
+ fprintf(f, "%sTimerSlackNSec: %lu\n", prefix, c->timer_slack_nsec);
fprintf(f,
"%sStandardInput: %s\n"
prefix, c->tty_path);
if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KMSG ||
- c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KMSG)
+ c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE ||
+ c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KMSG ||
+ c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE)
fprintf(f,
"%sSyslogFacility: %s\n"
"%sSyslogLevel: %s\n",
strv_fprintf(f, c->inaccessible_dirs);
fputs("\n", f);
}
+
+ fprintf(f,
+ "%sKillMode: %s\n"
+ "%sKillSignal: SIG%s\n"
+ "%sSendSIGKILL: %s\n",
+ prefix, kill_mode_to_string(c->kill_mode),
+ prefix, signal_to_string(c->kill_signal),
+ prefix, yes_no(c->send_sigkill));
+
+ if (c->utmp_id)
+ fprintf(f,
+ "%sUtmpIdentifier: %s\n",
+ prefix, c->utmp_id);
}
void exec_status_start(ExecStatus *s, pid_t pid) {
dual_timestamp_get(&s->start_timestamp);
}
-void exec_status_exit(ExecStatus *s, pid_t pid, int code, int status) {
+void exec_status_exit(ExecStatus *s, pid_t pid, int code, int status, const char *utmp_id) {
assert(s);
if ((s->pid && s->pid != pid) ||
s->code = code;
s->status = status;
+
+ if (utmp_id)
+ utmp_put_dead_process(utmp_id, pid, code, status);
}
void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) {
assert(e);
if (*l) {
- /* It's kinda important that we keep the order here */
+ /* It's kind of important, that we keep the order here */
LIST_FIND_TAIL(ExecCommand, command, *l, end);
LIST_INSERT_AFTER(ExecCommand, command, *l, end, e);
} else
return 0;
}
-const char* exit_status_to_string(ExitStatus status) {
-
- /* We cast to int here, so that -Wenum doesn't complain that
- * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */
-
- switch ((int) status) {
-
- case EXIT_SUCCESS:
- return "SUCCESS";
-
- case EXIT_FAILURE:
- return "FAILURE";
-
- case EXIT_INVALIDARGUMENT:
- return "INVALIDARGUMENT";
-
- case EXIT_NOTIMPLEMENTED:
- return "NOTIMPLEMENTED";
-
- case EXIT_NOPERMISSION:
- return "NOPERMISSION";
-
- case EXIT_NOTINSTALLED:
- return "NOTINSSTALLED";
-
- case EXIT_NOTCONFIGURED:
- return "NOTCONFIGURED";
-
- case EXIT_NOTRUNNING:
- return "NOTRUNNING";
-
- case EXIT_CHDIR:
- return "CHDIR";
-
- case EXIT_NICE:
- return "NICE";
-
- case EXIT_FDS:
- return "FDS";
-
- case EXIT_EXEC:
- return "EXEC";
-
- case EXIT_MEMORY:
- return "MEMORY";
-
- case EXIT_LIMITS:
- return "LIMITS";
-
- case EXIT_OOM_ADJUST:
- return "OOM_ADJUST";
-
- case EXIT_SIGNAL_MASK:
- return "SIGNAL_MASK";
-
- case EXIT_STDIN:
- return "STDIN";
-
- case EXIT_STDOUT:
- return "STDOUT";
-
- case EXIT_CHROOT:
- return "CHROOT";
-
- case EXIT_IOPRIO:
- return "IOPRIO";
-
- case EXIT_TIMERSLACK:
- return "TIMERSLACK";
-
- case EXIT_SECUREBITS:
- return "SECUREBITS";
-
- case EXIT_SETSCHEDULER:
- return "SETSCHEDULER";
-
- case EXIT_CPUAFFINITY:
- return "CPUAFFINITY";
-
- case EXIT_GROUP:
- return "GROUP";
-
- case EXIT_USER:
- return "USER";
-
- case EXIT_CAPABILITIES:
- return "CAPABILITIES";
-
- case EXIT_CGROUP:
- return "CGROUP";
-
- case EXIT_SETSID:
- return "SETSID";
-
- case EXIT_CONFIRM:
- return "CONFIRM";
-
- case EXIT_STDERR:
- return "STDERR";
-
- case EXIT_TCPWRAP:
- return "TCPWRAP";
-
- case EXIT_PAM:
- return "PAM";
-
- default:
- return NULL;
- }
-}
-
static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
[EXEC_INPUT_NULL] = "null",
[EXEC_INPUT_TTY] = "tty",
[EXEC_INPUT_SOCKET] = "socket"
};
+DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
+
static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
[EXEC_OUTPUT_INHERIT] = "inherit",
[EXEC_OUTPUT_NULL] = "null",
[EXEC_OUTPUT_TTY] = "tty",
[EXEC_OUTPUT_SYSLOG] = "syslog",
+ [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console",
[EXEC_OUTPUT_KMSG] = "kmsg",
+ [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console",
[EXEC_OUTPUT_SOCKET] = "socket"
};
DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
-DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
+static const char* const kill_mode_table[_KILL_MODE_MAX] = {
+ [KILL_CONTROL_GROUP] = "control-group",
+ [KILL_PROCESS_GROUP] = "process-group",
+ [KILL_PROCESS] = "process",
+ [KILL_NONE] = "none"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode);
+
+static const char* const kill_who_table[_KILL_WHO_MAX] = {
+ [KILL_MAIN] = "main",
+ [KILL_CONTROL] = "control",
+ [KILL_ALL] = "all"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);