summaryrefslogtreecommitdiff
path: root/src/core/manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/manager.c')
-rw-r--r--src/core/manager.c1566
1 files changed, 1005 insertions, 561 deletions
diff --git a/src/core/manager.c b/src/core/manager.c
index a1f37bbbb3..3569249788 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1,5 +1,3 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
/***
This file is part of systemd.
@@ -19,19 +17,19 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <dirent.h>
#include <errno.h>
-#include <string.h>
+#include <fcntl.h>
+#include <linux/kd.h>
#include <signal.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <sys/inotify.h>
+#include <string.h>
#include <sys/epoll.h>
-#include <sys/reboot.h>
+#include <sys/inotify.h>
#include <sys/ioctl.h>
-#include <linux/kd.h>
-#include <fcntl.h>
-#include <dirent.h>
+#include <sys/reboot.h>
#include <sys/timerfd.h>
+#include <sys/wait.h>
+#include <unistd.h>
#ifdef HAVE_AUDIT
#include <libaudit.h>
@@ -40,40 +38,57 @@
#include "sd-daemon.h"
#include "sd-messages.h"
-#include "hashmap.h"
-#include "macro.h"
-#include "strv.h"
-#include "log.h"
-#include "util.h"
-#include "mkdir.h"
-#include "ratelimit.h"
-#include "locale-setup.h"
-#include "unit-name.h"
-#include "missing.h"
-#include "rm-rf.h"
-#include "path-lookup.h"
-#include "special.h"
-#include "exit-status.h"
-#include "virt.h"
-#include "watchdog.h"
-#include "path-util.h"
+#include "alloc-util.h"
#include "audit-fd.h"
#include "boot-timestamps.h"
-#include "env-util.h"
#include "bus-common-errors.h"
#include "bus-error.h"
-#include "bus-util.h"
#include "bus-kernel.h"
-#include "time-util.h"
-#include "process-util.h"
-#include "terminal-util.h"
-#include "signal-util.h"
-#include "dbus.h"
-#include "dbus-unit.h"
+#include "bus-util.h"
+#include "clean-ipc.h"
#include "dbus-job.h"
#include "dbus-manager.h"
+#include "dbus-unit.h"
+#include "dbus.h"
+#include "dirent-util.h"
+#include "env-util.h"
+#include "escape.h"
+#include "exit-status.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "io-util.h"
+#include "locale-setup.h"
+#include "log.h"
+#include "macro.h"
#include "manager.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "path-lookup.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "ratelimit.h"
+#include "rm-rf.h"
+#include "signal-util.h"
+#include "special.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "time-util.h"
#include "transaction.h"
+#include "umask-util.h"
+#include "unit-name.h"
+#include "user-util.h"
+#include "util.h"
+#include "virt.h"
+#include "watchdog.h"
+
+#define NOTIFY_RCVBUF_SIZE (8*1024*1024)
+#define CGROUPS_AGENT_RCVBUF_SIZE (8*1024*1024)
/* Initial delay and the interval for printing status messages about running jobs */
#define JOBS_IN_PROGRESS_WAIT_USEC (5*USEC_PER_SEC)
@@ -81,13 +96,14 @@
#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3
static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
+static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
+static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata);
static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata);
static int manager_dispatch_run_queue(sd_event_source *source, void *userdata);
static int manager_run_generators(Manager *m);
-static void manager_undo_generators(Manager *m);
static void manager_watch_jobs_in_progress(Manager *m) {
usec_t next;
@@ -111,7 +127,7 @@ static void manager_watch_jobs_in_progress(Manager *m) {
(void) sd_event_source_set_description(m->jobs_in_progress_event_source, "manager-jobs-in-progress");
}
-#define CYLON_BUFFER_EXTRA (2*(sizeof(ANSI_RED_ON)-1) + sizeof(ANSI_HIGHLIGHT_RED_ON)-1 + 2*(sizeof(ANSI_HIGHLIGHT_OFF)-1))
+#define CYLON_BUFFER_EXTRA (2*(sizeof(ANSI_RED)-1) + sizeof(ANSI_HIGHLIGHT_RED)-1 + 2*(sizeof(ANSI_NORMAL)-1))
static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) {
char *p = buffer;
@@ -122,23 +138,28 @@ static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned po
if (pos > 1) {
if (pos > 2)
p = mempset(p, ' ', pos-2);
- p = stpcpy(p, ANSI_RED_ON);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_RED);
*p++ = '*';
}
if (pos > 0 && pos <= width) {
- p = stpcpy(p, ANSI_HIGHLIGHT_RED_ON);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_HIGHLIGHT_RED);
*p++ = '*';
}
- p = stpcpy(p, ANSI_HIGHLIGHT_OFF);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_NORMAL);
if (pos < width) {
- p = stpcpy(p, ANSI_RED_ON);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_RED);
*p++ = '*';
if (pos < width-1)
p = mempset(p, ' ', width-1-pos);
- strcpy(p, ANSI_HIGHLIGHT_OFF);
+ if (log_get_show_color())
+ strcpy(p, ANSI_NORMAL);
}
}
@@ -220,7 +241,7 @@ static int have_ask_password(void) {
errno = 0;
de = readdir(dir);
- if (!de && errno != 0)
+ if (!de && errno > 0)
return -errno;
if (!de)
return false;
@@ -250,8 +271,8 @@ static int manager_dispatch_ask_password_fd(sd_event_source *source,
static void manager_close_ask_password(Manager *m) {
assert(m);
- m->ask_password_inotify_fd = safe_close(m->ask_password_inotify_fd);
m->ask_password_event_source = sd_event_source_unref(m->ask_password_event_source);
+ m->ask_password_inotify_fd = safe_close(m->ask_password_inotify_fd);
m->have_ask_password = -EINVAL;
}
@@ -317,6 +338,8 @@ static int manager_watch_idle_pipe(Manager *m) {
static void manager_close_idle_pipe(Manager *m) {
assert(m);
+ m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source);
+
safe_close_pair(m->idle_pipe);
safe_close_pair(m->idle_pipe + 2);
}
@@ -365,6 +388,9 @@ static int enable_special_signals(Manager *m) {
assert(m);
+ if (m->test_run)
+ return 0;
+
/* Enable that we get SIGINT on control-alt-del. In containers
* this will fail with EPERM (older) or EINVAL (newer), so
* ignore that. */
@@ -467,15 +493,15 @@ static int manager_setup_signals(Manager *m) {
(void) sd_event_source_set_description(m->signal_event_source, "manager-signal");
- /* Process signals a bit earlier than the rest of things, but
- * later than notify_fd processing, so that the notify
- * processing can still figure out to which process/service a
- * message belongs, before we reap the process. */
- r = sd_event_source_set_priority(m->signal_event_source, -5);
+ /* Process signals a bit earlier than the rest of things, but later than notify_fd processing, so that the
+ * notify processing can still figure out to which process/service a message belongs, before we reap the
+ * process. Also, process this before handling cgroup notifications, so that we always collect child exit
+ * status information before detecting that there's no process in a cgroup. */
+ r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6);
if (r < 0)
return r;
- if (m->running_as == MANAGER_SYSTEM)
+ if (MANAGER_IS_SYSTEM(m))
return enable_special_signals(m);
return 0;
@@ -493,15 +519,17 @@ static void manager_clean_environment(Manager *m) {
"MANAGERPID",
"LISTEN_PID",
"LISTEN_FDS",
+ "LISTEN_FDNAMES",
"WATCHDOG_PID",
"WATCHDOG_USEC",
+ "INVOCATION_ID",
NULL);
}
static int manager_default_environment(Manager *m) {
assert(m);
- if (m->running_as == MANAGER_SYSTEM) {
+ if (MANAGER_IS_SYSTEM(m)) {
/* The system manager always starts with a clean
* environment for its children. It does not import
* the kernel or the parents exported variables.
@@ -529,50 +557,55 @@ static int manager_default_environment(Manager *m) {
return 0;
}
-
-int manager_new(ManagerRunningAs running_as, bool test_run, Manager **_m) {
-
- static const char * const unit_log_fields[_MANAGER_RUNNING_AS_MAX] = {
- [MANAGER_SYSTEM] = "UNIT=",
- [MANAGER_USER] = "USER_UNIT=",
- };
-
- static const char * const unit_log_format_strings[_MANAGER_RUNNING_AS_MAX] = {
- [MANAGER_SYSTEM] = "UNIT=%s",
- [MANAGER_USER] = "USER_UNIT=%s",
- };
-
+int manager_new(UnitFileScope scope, bool test_run, Manager **_m) {
Manager *m;
int r;
assert(_m);
- assert(running_as >= 0);
- assert(running_as < _MANAGER_RUNNING_AS_MAX);
+ assert(IN_SET(scope, UNIT_FILE_SYSTEM, UNIT_FILE_USER));
m = new0(Manager, 1);
if (!m)
return -ENOMEM;
+ m->unit_file_scope = scope;
+ m->exit_code = _MANAGER_EXIT_CODE_INVALID;
+ m->default_timer_accuracy_usec = USEC_PER_MINUTE;
+ m->default_tasks_accounting = true;
+ m->default_tasks_max = UINT64_MAX;
+
#ifdef ENABLE_EFI
- if (running_as == MANAGER_SYSTEM && detect_container(NULL) <= 0)
+ if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0)
boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp);
#endif
- m->running_as = running_as;
- m->exit_code = _MANAGER_EXIT_CODE_INVALID;
- m->default_timer_accuracy_usec = USEC_PER_MINUTE;
-
/* Prepare log fields we can use for structured logging */
- m->unit_log_field = unit_log_fields[running_as];
- m->unit_log_format_string = unit_log_format_strings[running_as];
+ if (MANAGER_IS_SYSTEM(m)) {
+ m->unit_log_field = "UNIT=";
+ m->unit_log_format_string = "UNIT=%s";
+
+ m->invocation_log_field = "INVOCATION_ID=";
+ m->invocation_log_format_string = "INVOCATION_ID=" SD_ID128_FORMAT_STR;
+ } else {
+ m->unit_log_field = "USER_UNIT=";
+ m->unit_log_format_string = "USER_UNIT=%s";
+
+ m->invocation_log_field = "USER_INVOCATION_ID=";
+ m->invocation_log_format_string = "USER_INVOCATION_ID=" SD_ID128_FORMAT_STR;
+ }
m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1;
- m->pin_cgroupfs_fd = m->notify_fd = m->signal_fd = m->time_change_fd = m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->utab_inotify_fd = -1;
+ m->pin_cgroupfs_fd = m->notify_fd = m->cgroups_agent_fd = m->signal_fd = m->time_change_fd =
+ m->dev_autofs_fd = m->private_listen_fd = m->cgroup_inotify_fd =
+ m->ask_password_inotify_fd = -1;
+
+ m->user_lookup_fds[0] = m->user_lookup_fds[1] = -1;
+
m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
- m->ask_password_inotify_fd = -1;
m->have_ask_password = -EINVAL; /* we don't know */
+ m->first_boot = -1;
m->test_run = test_run;
@@ -599,14 +632,6 @@ int manager_new(ManagerRunningAs running_as, bool test_run, Manager **_m) {
if (r < 0)
goto fail;
- r = set_ensure_allocated(&m->startup_units, NULL);
- if (r < 0)
- goto fail;
-
- r = set_ensure_allocated(&m->failed_units, NULL);
- if (r < 0)
- goto fail;
-
r = sd_event_default(&m->event);
if (r < 0)
goto fail;
@@ -643,9 +668,8 @@ int manager_new(ManagerRunningAs running_as, bool test_run, Manager **_m) {
goto fail;
}
- /* Note that we set up neither kdbus, nor the notify fd
- * here. We do that after deserialization, since they might
- * have gotten serialized across the reexec. */
+ /* Note that we do not set up the notify fd here. We do that after deserialization,
+ * since they might have gotten serialized across the reexec. */
m->taint_usr = dir_is_empty("/usr") > 0;
@@ -669,29 +693,25 @@ static int manager_setup_notify(Manager *m) {
.sa.sa_family = AF_UNIX,
};
static const int one = 1;
+ const char *e;
/* First free all secondary fields */
- free(m->notify_socket);
- m->notify_socket = NULL;
+ m->notify_socket = mfree(m->notify_socket);
m->notify_event_source = sd_event_source_unref(m->notify_event_source);
fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to allocate notification socket: %m");
- if (m->running_as == MANAGER_SYSTEM)
- m->notify_socket = strdup("/run/systemd/notify");
- else {
- const char *e;
+ fd_inc_rcvbuf(fd, NOTIFY_RCVBUF_SIZE);
- e = getenv("XDG_RUNTIME_DIR");
- if (!e) {
- log_error_errno(errno, "XDG_RUNTIME_DIR is not set: %m");
- return -EINVAL;
- }
-
- m->notify_socket = strappend(e, "/systemd/notify");
+ e = manager_get_runtime_prefix(m);
+ if (!e) {
+ log_error("Failed to determine runtime prefix.");
+ return -EINVAL;
}
+
+ m->notify_socket = strappend(e, "/systemd/notify");
if (!m->notify_socket)
return log_oom();
@@ -699,7 +719,7 @@ static int manager_setup_notify(Manager *m) {
(void) unlink(m->notify_socket);
strncpy(sa.un.sun_path, m->notify_socket, sizeof(sa.un.sun_path)-1);
- r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
+ r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
if (r < 0)
return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
@@ -718,9 +738,9 @@ static int manager_setup_notify(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to allocate notify event source: %m");
- /* Process signals a bit earlier than SIGCHLD, so that we can
- * still identify to which service an exit message belongs */
- r = sd_event_source_set_priority(m->notify_event_source, -7);
+ /* Process notification messages a bit earlier than SIGCHLD, so that we can still identify to which
+ * service an exit message belongs. */
+ r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-7);
if (r < 0)
return log_error_errno(r, "Failed to set priority of notify event source: %m");
@@ -730,24 +750,128 @@ static int manager_setup_notify(Manager *m) {
return 0;
}
-static int manager_setup_kdbus(Manager *m) {
- _cleanup_free_ char *p = NULL;
+static int manager_setup_cgroups_agent(Manager *m) {
- assert(m);
+ static const union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/cgroups-agent",
+ };
+ int r;
+
+ /* This creates a listening socket we receive cgroups agent messages on. We do not use D-Bus for delivering
+ * these messages from the cgroups agent binary to PID 1, as the cgroups agent binary is very short-living, and
+ * each instance of it needs a new D-Bus connection. Since D-Bus connections are SOCK_STREAM/AF_UNIX, on
+ * overloaded systems the backlog of the D-Bus socket becomes relevant, as not more than the configured number
+ * of D-Bus connections may be queued until the kernel will start dropping further incoming connections,
+ * possibly resulting in lost cgroups agent messages. To avoid this, we'll use a private SOCK_DGRAM/AF_UNIX
+ * socket, where no backlog is relevant as communication may take place without an actual connect() cycle, and
+ * we thus won't lose messages.
+ *
+ * Note that PID 1 will forward the agent message to system bus, so that the user systemd instance may listen
+ * to it. The system instance hence listens on this special socket, but the user instances listen on the system
+ * bus for these messages. */
+
+ if (m->test_run)
+ return 0;
- if (m->test_run || m->kdbus_fd >= 0)
+ if (!MANAGER_IS_SYSTEM(m))
return 0;
- if (!is_kdbus_available())
- return -ESOCKTNOSUPPORT;
- m->kdbus_fd = bus_kernel_create_bus(
- m->running_as == MANAGER_SYSTEM ? "system" : "user",
- m->running_as == MANAGER_SYSTEM, &p);
+ if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) > 0) /* We don't need this anymore on the unified hierarchy */
+ return 0;
+
+ if (m->cgroups_agent_fd < 0) {
+ _cleanup_close_ int fd = -1;
+
+ /* First free all secondary fields */
+ m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source);
- if (m->kdbus_fd < 0)
- return log_debug_errno(m->kdbus_fd, "Failed to set up kdbus: %m");
+ fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to allocate cgroups agent socket: %m");
+
+ fd_inc_rcvbuf(fd, CGROUPS_AGENT_RCVBUF_SIZE);
- log_debug("Successfully set up kdbus on %s", p);
+ (void) unlink(sa.un.sun_path);
+
+ /* Only allow root to connect to this socket */
+ RUN_WITH_UMASK(0077)
+ r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
+
+ m->cgroups_agent_fd = fd;
+ fd = -1;
+ }
+
+ if (!m->cgroups_agent_event_source) {
+ r = sd_event_add_io(m->event, &m->cgroups_agent_event_source, m->cgroups_agent_fd, EPOLLIN, manager_dispatch_cgroups_agent_fd, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate cgroups agent event source: %m");
+
+ /* Process cgroups notifications early, but after having processed service notification messages or
+ * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of notification,
+ * and we collected the metadata the notification and SIGCHLD stuff offers first. Also see handling of
+ * cgroup inotify for the unified cgroup stuff. */
+ r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m");
+
+ (void) sd_event_source_set_description(m->cgroups_agent_event_source, "manager-cgroups-agent");
+ }
+
+ return 0;
+}
+
+static int manager_setup_user_lookup_fd(Manager *m) {
+ int r;
+
+ assert(m);
+
+ /* Set up the socket pair used for passing UID/GID resolution results from forked off processes to PID
+ * 1. Background: we can't do name lookups (NSS) from PID 1, since it might involve IPC and thus activation,
+ * and we might hence deadlock on ourselves. Hence we do all user/group lookups asynchronously from the forked
+ * off processes right before executing the binaries to start. In order to be able to clean up any IPC objects
+ * created by a unit (see RemoveIPC=) we need to know in PID 1 the used UID/GID of the executed processes,
+ * hence we establish this communication channel so that forked off processes can pass their UID/GID
+ * information back to PID 1. The forked off processes send their resolved UID/GID to PID 1 in a simple
+ * datagram, along with their unit name, so that we can share one communication socket pair among all units for
+ * this purpose.
+ *
+ * You might wonder why we need a communication channel for this that is independent of the usual notification
+ * socket scheme (i.e. $NOTIFY_SOCKET). The primary difference is about trust: data sent via the $NOTIFY_SOCKET
+ * channel is only accepted if it originates from the right unit and if reception was enabled for it. The user
+ * lookup socket OTOH is only accessible by PID 1 and its children until they exec(), and always available.
+ *
+ * Note that this function is called under two circumstances: when we first initialize (in which case we
+ * allocate both the socket pair and the event source to listen on it), and when we deserialize after a reload
+ * (in which case the socket pair already exists but we still need to allocate the event source for it). */
+
+ if (m->user_lookup_fds[0] < 0) {
+
+ /* Free all secondary fields */
+ safe_close_pair(m->user_lookup_fds);
+ m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source);
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, m->user_lookup_fds) < 0)
+ return log_error_errno(errno, "Failed to allocate user lookup socket: %m");
+
+ (void) fd_inc_rcvbuf(m->user_lookup_fds[0], NOTIFY_RCVBUF_SIZE);
+ }
+
+ if (!m->user_lookup_event_source) {
+ r = sd_event_add_io(m->event, &m->user_lookup_event_source, m->user_lookup_fds[0], EPOLLIN, manager_dispatch_user_lookup_fd, m);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to allocate user lookup event source: %m");
+
+ /* Process even earlier than the notify event source, so that we always know first about valid UID/GID
+ * resolutions */
+ r = sd_event_source_set_priority(m->user_lookup_event_source, SD_EVENT_PRIORITY_NORMAL-8);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to set priority ot user lookup event source: %m");
+
+ (void) sd_event_source_set_description(m->user_lookup_event_source, "user-lookup");
+ }
return 0;
}
@@ -761,9 +885,8 @@ static int manager_connect_bus(Manager *m, bool reexecuting) {
return 0;
try_bus_connect =
- m->kdbus_fd >= 0 ||
reexecuting ||
- (m->running_as == MANAGER_USER && getenv("DBUS_SESSION_BUS_ADDRESS"));
+ (MANAGER_IS_USER(m) && getenv("DBUS_SESSION_BUS_ADDRESS"));
/* Try to connect to the buses, if possible. */
return bus_init(m, try_bus_connect);
@@ -793,6 +916,18 @@ enum {
_GC_OFFSET_MAX
};
+static void unit_gc_mark_good(Unit *u, unsigned gc_marker) {
+ Iterator i;
+ Unit *other;
+
+ u->gc_marker = gc_marker + GC_OFFSET_GOOD;
+
+ /* Recursively mark referenced units as GOOD as well */
+ SET_FOREACH(other, u->dependencies[UNIT_REFERENCES], i)
+ if (other->gc_marker == gc_marker + GC_OFFSET_UNSURE)
+ unit_gc_mark_good(other, gc_marker);
+}
+
static void unit_gc_sweep(Unit *u, unsigned gc_marker) {
Iterator i;
Unit *other;
@@ -802,6 +937,7 @@ static void unit_gc_sweep(Unit *u, unsigned gc_marker) {
if (u->gc_marker == gc_marker + GC_OFFSET_GOOD ||
u->gc_marker == gc_marker + GC_OFFSET_BAD ||
+ u->gc_marker == gc_marker + GC_OFFSET_UNSURE ||
u->gc_marker == gc_marker + GC_OFFSET_IN_PATH)
return;
@@ -842,7 +978,7 @@ bad:
return;
good:
- u->gc_marker = gc_marker + GC_OFFSET_GOOD;
+ unit_gc_mark_good(u, gc_marker);
}
static unsigned manager_dispatch_gc_queue(Manager *m) {
@@ -925,11 +1061,15 @@ Manager* manager_free(Manager *m) {
* around */
manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE);
- manager_undo_generators(m);
+ lookup_paths_flush_generator(&m->lookup_paths);
bus_done(m);
+ dynamic_user_vacuum(m, false);
+ hashmap_free(m->dynamic_users);
+
hashmap_free(m->units);
+ hashmap_free(m->units_by_invocation_id);
hashmap_free(m->jobs);
hashmap_free(m->watch_pids1);
hashmap_free(m->watch_pids2);
@@ -940,15 +1080,17 @@ Manager* manager_free(Manager *m) {
sd_event_source_unref(m->signal_event_source);
sd_event_source_unref(m->notify_event_source);
+ sd_event_source_unref(m->cgroups_agent_event_source);
sd_event_source_unref(m->time_change_event_source);
sd_event_source_unref(m->jobs_in_progress_event_source);
- sd_event_source_unref(m->idle_pipe_event_source);
sd_event_source_unref(m->run_queue_event_source);
+ sd_event_source_unref(m->user_lookup_event_source);
safe_close(m->signal_fd);
safe_close(m->notify_fd);
+ safe_close(m->cgroups_agent_fd);
safe_close(m->time_change_fd);
- safe_close(m->kdbus_fd);
+ safe_close_pair(m->user_lookup_fds);
manager_close_ask_password(m);
@@ -969,17 +1111,19 @@ Manager* manager_free(Manager *m) {
free(m->switch_root_init);
for (i = 0; i < _RLIMIT_MAX; i++)
- free(m->rlimit[i]);
+ m->rlimit[i] = mfree(m->rlimit[i]);
assert(hashmap_isempty(m->units_requiring_mounts_for));
hashmap_free(m->units_requiring_mounts_for);
+ hashmap_free(m->uid_refs);
+ hashmap_free(m->gid_refs);
+
free(m);
return NULL;
}
-int manager_enumerate(Manager *m) {
- int r = 0;
+void manager_enumerate(Manager *m) {
UnitType c;
assert(m);
@@ -987,8 +1131,6 @@ int manager_enumerate(Manager *m) {
/* Let's ask every type to load all units from disk/kernel
* that it might know */
for (c = 0; c < _UNIT_TYPE_MAX; c++) {
- int q;
-
if (!unit_type_supported(c)) {
log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c));
continue;
@@ -997,13 +1139,10 @@ int manager_enumerate(Manager *m) {
if (!unit_vtable[c]->enumerate)
continue;
- q = unit_vtable[c]->enumerate(m);
- if (q < 0)
- r = q;
+ unit_vtable[c]->enumerate(m);
}
manager_dispatch_load_queue(m);
- return r;
}
static void manager_coldplug(Manager *m) {
@@ -1029,7 +1168,6 @@ static void manager_coldplug(Manager *m) {
static void manager_build_unit_path_cache(Manager *m) {
char **i;
- _cleanup_closedir_ DIR *d = NULL;
int r;
assert(m);
@@ -1038,29 +1176,27 @@ static void manager_build_unit_path_cache(Manager *m) {
m->unit_path_cache = set_new(&string_hash_ops);
if (!m->unit_path_cache) {
- log_error("Failed to allocate unit path cache.");
- return;
+ r = -ENOMEM;
+ goto fail;
}
/* This simply builds a list of files we know exist, so that
* we don't always have to go to disk */
- STRV_FOREACH(i, m->lookup_paths.unit_path) {
+ STRV_FOREACH(i, m->lookup_paths.search_path) {
+ _cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
d = opendir(*i);
if (!d) {
if (errno != ENOENT)
- log_error_errno(errno, "Failed to open directory %s: %m", *i);
+ log_warning_errno(errno, "Failed to open directory %s, ignoring: %m", *i);
continue;
}
- while ((de = readdir(d))) {
+ FOREACH_DIRENT(de, d, r = -errno; goto fail) {
char *p;
- if (hidden_file(de->d_name))
- continue;
-
p = strjoin(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL);
if (!p) {
r = -ENOMEM;
@@ -1071,25 +1207,18 @@ static void manager_build_unit_path_cache(Manager *m) {
if (r < 0)
goto fail;
}
-
- closedir(d);
- d = NULL;
}
return;
fail:
- log_error_errno(r, "Failed to build unit path cache: %m");
-
- set_free_free(m->unit_path_cache);
- m->unit_path_cache = NULL;
+ log_warning_errno(r, "Failed to build unit path cache, proceeding without: %m");
+ m->unit_path_cache = set_free_free(m->unit_path_cache);
}
-
-static int manager_distribute_fds(Manager *m, FDSet *fds) {
- Unit *u;
+static void manager_distribute_fds(Manager *m, FDSet *fds) {
Iterator i;
- int r;
+ Unit *u;
assert(m);
@@ -1098,14 +1227,11 @@ static int manager_distribute_fds(Manager *m, FDSet *fds) {
if (fdset_size(fds) <= 0)
break;
- if (UNIT_VTABLE(u)->distribute_fds) {
- r = UNIT_VTABLE(u)->distribute_fds(u, fds);
- if (r < 0)
- return r;
- }
- }
+ if (!UNIT_VTABLE(u)->distribute_fds)
+ continue;
- return 0;
+ UNIT_VTABLE(u)->distribute_fds(u, fds);
+ }
}
int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
@@ -1113,32 +1239,35 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
assert(m);
- dual_timestamp_get(&m->generators_start_timestamp);
- r = manager_run_generators(m);
- dual_timestamp_get(&m->generators_finish_timestamp);
+ r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
if (r < 0)
return r;
- r = lookup_paths_init(
- &m->lookup_paths, m->running_as, true,
- NULL,
- m->generator_unit_path,
- m->generator_unit_path_early,
- m->generator_unit_path_late);
+ /* Make sure the transient directory always exists, so that it remains in the search path */
+ if (!m->test_run) {
+ r = mkdir_p_label(m->lookup_paths.transient, 0755);
+ if (r < 0)
+ return r;
+ }
+
+ dual_timestamp_get(&m->generators_start_timestamp);
+ r = manager_run_generators(m);
+ dual_timestamp_get(&m->generators_finish_timestamp);
if (r < 0)
return r;
+ lookup_paths_reduce(&m->lookup_paths);
manager_build_unit_path_cache(m);
/* If we will deserialize make sure that during enumeration
* this is already known, so we increase the counter here
* already */
if (serialization)
- m->n_reloading ++;
+ m->n_reloading++;
/* First, enumerate what we can from all config files */
dual_timestamp_get(&m->units_load_start_timestamp);
- r = manager_enumerate(m);
+ manager_enumerate(m);
dual_timestamp_get(&m->units_load_finish_timestamp);
/* Second, deserialize if there is something to deserialize */
@@ -1149,11 +1278,7 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
* useful to allow container managers to pass some file
* descriptors to us pre-initialized. This enables
* socket-based activation of entire containers. */
- if (fdset_size(fds) > 0) {
- q = manager_distribute_fds(m, fds);
- if (q < 0 && r == 0)
- r = q;
- }
+ manager_distribute_fds(m, fds);
/* We might have deserialized the notify fd, but if we didn't
* then let's create the bus now */
@@ -1161,18 +1286,33 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
if (q < 0 && r == 0)
r = q;
- /* We might have deserialized the kdbus control fd, but if we
- * didn't, then let's create the bus now. */
- manager_setup_kdbus(m);
- manager_connect_bus(m, !!serialization);
- bus_track_coldplug(m, &m->subscribed, &m->deserialized_subscribed);
+ q = manager_setup_cgroups_agent(m);
+ if (q < 0 && r == 0)
+ r = q;
+
+ q = manager_setup_user_lookup_fd(m);
+ if (q < 0 && r == 0)
+ r = q;
+
+ /* Let's connect to the bus now. */
+ (void) manager_connect_bus(m, !!serialization);
+
+ (void) bus_track_coldplug(m, &m->subscribed, false, m->deserialized_subscribed);
+ m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
/* Third, fire things up! */
manager_coldplug(m);
+ /* Release any dynamic users no longer referenced */
+ dynamic_user_vacuum(m, true);
+
+ /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
+ manager_vacuum_uid_refs(m);
+ manager_vacuum_gid_refs(m);
+
if (serialization) {
assert(m->n_reloading > 0);
- m->n_reloading --;
+ m->n_reloading--;
/* Let's wait for the UnitNew/JobNew messages being
* sent, before we notify that the reload is
@@ -1183,7 +1323,7 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
return r;
}
-int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, sd_bus_error *e, Job **_ret) {
+int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret) {
int r;
Transaction *tr;
@@ -1206,7 +1346,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
if (!tr)
return -ENOMEM;
- r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, override, false,
+ r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, false,
mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS,
mode == JOB_IGNORE_DEPENDENCIES, e);
if (r < 0)
@@ -1238,7 +1378,7 @@ tr_abort:
return r;
}
-int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, sd_bus_error *e, Job **_ret) {
+int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **ret) {
Unit *unit;
int r;
@@ -1251,7 +1391,23 @@ int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode
if (r < 0)
return r;
- return manager_add_job(m, type, unit, mode, override, e, _ret);
+ return manager_add_job(m, type, unit, mode, e, ret);
+}
+
+int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(name);
+ assert(mode < _JOB_MODE_MAX);
+
+ r = manager_add_job_by_name(m, type, name, mode, &error, ret);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r));
+
+ return r;
}
Job *manager_get_job(Manager *m, uint32_t id) {
@@ -1318,8 +1474,12 @@ int manager_load_unit_prepare(
t = unit_name_to_type(name);
- if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE))
+ if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) {
+ if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE))
+ return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is missing the instance name.", name);
+
return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name);
+ }
ret = manager_get_unit(m, name);
if (ret) {
@@ -1412,7 +1572,7 @@ void manager_clear_jobs(Manager *m) {
while ((j = hashmap_first(m->jobs)))
/* No need to recurse. We're cancelling all jobs. */
- job_finish_and_invalidate(j, JOB_CANCELED, false);
+ job_finish_and_invalidate(j, JOB_CANCELED, false, false);
}
static int manager_dispatch_run_queue(sd_event_source *source, void *userdata) {
@@ -1478,13 +1638,41 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) {
return n;
}
-static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, char *buf, size_t n, FDSet *fds) {
+static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+ Manager *m = userdata;
+ char buf[PATH_MAX+1];
+ ssize_t n;
+
+ n = recv(fd, buf, sizeof(buf), 0);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to read cgroups agent message: %m");
+ if (n == 0) {
+ log_error("Got zero-length cgroups agent message, ignoring.");
+ return 0;
+ }
+ if ((size_t) n >= sizeof(buf)) {
+ log_error("Got overly long cgroups agent message, ignoring.");
+ return 0;
+ }
+
+ if (memchr(buf, 0, n)) {
+ log_error("Got cgroups agent message with embedded NUL byte, ignoring.");
+ return 0;
+ }
+ buf[n] = 0;
+
+ manager_notify_cgroup_empty(m, buf);
+ bus_forward_agent_released(m, buf);
+
+ return 0;
+}
+
+static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, const char *buf, FDSet *fds) {
_cleanup_strv_free_ char **tags = NULL;
assert(m);
assert(u);
assert(buf);
- assert(n > 0);
tags = strv_split(buf, "\n\r");
if (!tags) {
@@ -1494,14 +1682,43 @@ static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, char *
if (UNIT_VTABLE(u)->notify_message)
UNIT_VTABLE(u)->notify_message(u, pid, tags, fds);
- else
- log_unit_debug(u, "Got notification message for unit. Ignoring.");
+ else if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
+ _cleanup_free_ char *x = NULL, *y = NULL;
+
+ x = cescape(buf);
+ if (x)
+ y = ellipsize(x, 20, 90);
+ log_unit_debug(u, "Got notification message \"%s\", ignoring.", strnull(y));
+ }
}
static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+
+ _cleanup_fdset_free_ FDSet *fds = NULL;
Manager *m = userdata;
+ char buf[NOTIFY_BUFFER_MAX+1];
+ struct iovec iovec = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf)-1,
+ };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)];
+ } control = {};
+ struct msghdr msghdr = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+
+ struct cmsghdr *cmsg;
+ struct ucred *ucred = NULL;
+ Unit *u1, *u2, *u3;
+ int r, *fd_array = NULL;
+ unsigned n_fds = 0;
ssize_t n;
- int r;
assert(m);
assert(m->notify_fd == fd);
@@ -1511,114 +1728,108 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
return 0;
}
- for (;;) {
- _cleanup_fdset_free_ FDSet *fds = NULL;
- char buf[NOTIFY_BUFFER_MAX+1];
- struct iovec iovec = {
- .iov_base = buf,
- .iov_len = sizeof(buf)-1,
- };
- union {
- struct cmsghdr cmsghdr;
- uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
- CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)];
- } control = {};
- struct msghdr msghdr = {
- .msg_iov = &iovec,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
- struct cmsghdr *cmsg;
- struct ucred *ucred = NULL;
- bool found = false;
- Unit *u1, *u2, *u3;
- int *fd_array = NULL;
- unsigned n_fds = 0;
-
- n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
- if (n < 0) {
- if (errno == EAGAIN || errno == EINTR)
- break;
+ n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC|MSG_TRUNC);
+ if (n < 0) {
+ if (IN_SET(errno, EAGAIN, EINTR))
+ return 0; /* Spurious wakeup, try again */
- return -errno;
- }
+ /* If this is any other, real error, then let's stop processing this socket. This of course means we
+ * won't take notification messages anymore, but that's still better than busy looping around this:
+ * being woken up over and over again but being unable to actually read the message off the socket. */
+ return log_error_errno(errno, "Failed to receive notification message: %m");
+ }
- CMSG_FOREACH(cmsg, &msghdr) {
- if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ CMSG_FOREACH(cmsg, &msghdr) {
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
- fd_array = (int*) CMSG_DATA(cmsg);
- n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ fd_array = (int*) CMSG_DATA(cmsg);
+ n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
- } else if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SCM_CREDENTIALS &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+ } else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
- ucred = (struct ucred*) CMSG_DATA(cmsg);
- }
+ ucred = (struct ucred*) CMSG_DATA(cmsg);
}
+ }
- if (n_fds > 0) {
- assert(fd_array);
+ if (n_fds > 0) {
+ assert(fd_array);
- r = fdset_new_array(&fds, fd_array, n_fds);
- if (r < 0) {
- close_many(fd_array, n_fds);
- return log_oom();
- }
+ r = fdset_new_array(&fds, fd_array, n_fds);
+ if (r < 0) {
+ close_many(fd_array, n_fds);
+ log_oom();
+ return 0;
}
+ }
- if (!ucred || ucred->pid <= 0) {
- log_warning("Received notify message without valid credentials. Ignoring.");
- continue;
- }
+ if (!ucred || ucred->pid <= 0) {
+ log_warning("Received notify message without valid credentials. Ignoring.");
+ return 0;
+ }
- if ((size_t) n >= sizeof(buf)) {
- log_warning("Received notify message exceeded maximum size. Ignoring.");
- continue;
- }
+ if ((size_t) n >= sizeof(buf) || (msghdr.msg_flags & MSG_TRUNC)) {
+ log_warning("Received notify message exceeded maximum size. Ignoring.");
+ return 0;
+ }
+
+ /* As extra safety check, let's make sure the string we get doesn't contain embedded NUL bytes. We permit one
+ * trailing NUL byte in the message, but don't expect it. */
+ if (n > 1 && memchr(buf, 0, n-1)) {
+ log_warning("Received notify message with embedded NUL bytes. Ignoring.");
+ return 0;
+ }
- buf[n] = 0;
+ /* Make sure it's NUL-terminated. */
+ buf[n] = 0;
- /* Notify every unit that might be interested, but try
- * to avoid notifying the same one multiple times. */
- u1 = manager_get_unit_by_pid(m, ucred->pid);
- if (u1) {
- manager_invoke_notify_message(m, u1, ucred->pid, buf, n, fds);
- found = true;
- }
+ /* Notify every unit that might be interested, but try
+ * to avoid notifying the same one multiple times. */
+ u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid);
+ if (u1)
+ manager_invoke_notify_message(m, u1, ucred->pid, buf, fds);
- u2 = hashmap_get(m->watch_pids1, LONG_TO_PTR(ucred->pid));
- if (u2 && u2 != u1) {
- manager_invoke_notify_message(m, u2, ucred->pid, buf, n, fds);
- found = true;
- }
+ u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid));
+ if (u2 && u2 != u1)
+ manager_invoke_notify_message(m, u2, ucred->pid, buf, fds);
- u3 = hashmap_get(m->watch_pids2, LONG_TO_PTR(ucred->pid));
- if (u3 && u3 != u2 && u3 != u1) {
- manager_invoke_notify_message(m, u3, ucred->pid, buf, n, fds);
- found = true;
- }
+ u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid));
+ if (u3 && u3 != u2 && u3 != u1)
+ manager_invoke_notify_message(m, u3, ucred->pid, buf, fds);
- if (!found)
- log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid);
+ if (!u1 && !u2 && !u3)
+ log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid);
- if (fdset_size(fds) > 0)
- log_warning("Got auxiliary fds with notification message, closing all.");
- }
+ if (fdset_size(fds) > 0)
+ log_warning("Got extra auxiliary fds with notification message, closing them.");
return 0;
}
-static void invoke_sigchld_event(Manager *m, Unit *u, siginfo_t *si) {
+static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) {
+ uint64_t iteration;
+
assert(m);
assert(u);
assert(si);
+ sd_event_get_iteration(m->event, &iteration);
+
log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id);
unit_unwatch_pid(u, si->si_pid);
- UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status);
+
+ if (UNIT_VTABLE(u)->sigchld_event) {
+ if (set_size(u->pids) <= 1 ||
+ iteration != u->sigchldgen ||
+ unit_main_pid(u) == si->si_pid ||
+ unit_control_pid(u) == si->si_pid) {
+ UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status);
+ u->sigchldgen = iteration;
+ } else
+ log_debug("%s already issued a sigchld this iteration %" PRIu64 ", skipping. Pids still being watched %d", u->id, iteration, set_size(u->pids));
+ }
}
static int manager_dispatch_sigchld(Manager *m) {
@@ -1660,13 +1871,13 @@ static int manager_dispatch_sigchld(Manager *m) {
/* And now figure out the unit this belongs
* to, it might be multiple... */
- u1 = manager_get_unit_by_pid(m, si.si_pid);
+ u1 = manager_get_unit_by_pid_cgroup(m, si.si_pid);
if (u1)
invoke_sigchld_event(m, u1, &si);
- u2 = hashmap_get(m->watch_pids1, LONG_TO_PTR(si.si_pid));
+ u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(si.si_pid));
if (u2 && u2 != u1)
invoke_sigchld_event(m, u2, &si);
- u3 = hashmap_get(m->watch_pids2, LONG_TO_PTR(si.si_pid));
+ u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(si.si_pid));
if (u3 && u3 != u2 && u3 != u1)
invoke_sigchld_event(m, u3, &si);
}
@@ -1684,23 +1895,53 @@ static int manager_dispatch_sigchld(Manager *m) {
}
static int manager_start_target(Manager *m, const char *name, JobMode mode) {
- _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
log_debug("Activating special unit %s", name);
- r = manager_add_job_by_name(m, JOB_START, name, mode, true, &error, NULL);
+ r = manager_add_job_by_name(m, JOB_START, name, mode, &error, NULL);
if (r < 0)
log_error("Failed to enqueue %s job: %s", name, bus_error_message(&error, r));
return r;
}
+static void manager_handle_ctrl_alt_del(Manager *m) {
+ /* If the user presses C-A-D more than
+ * 7 times within 2s, we reboot/shutdown immediately,
+ * unless it was disabled in system.conf */
+
+ if (ratelimit_test(&m->ctrl_alt_del_ratelimit) || m->cad_burst_action == CAD_BURST_ACTION_IGNORE)
+ manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY);
+ else {
+ switch (m->cad_burst_action) {
+
+ case CAD_BURST_ACTION_REBOOT:
+ m->exit_code = MANAGER_REBOOT;
+ break;
+
+ case CAD_BURST_ACTION_POWEROFF:
+ m->exit_code = MANAGER_POWEROFF;
+ break;
+
+ default:
+ assert_not_reached("Unknown action.");
+ }
+
+ log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, performing immediate %s.",
+ cad_burst_action_to_string(m->cad_burst_action));
+ status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, performing immediate %s.",
+ cad_burst_action_to_string(m->cad_burst_action));
+ }
+}
+
static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
Manager *m = userdata;
ssize_t n;
struct signalfd_siginfo sfsi;
bool sigchld = false;
+ int r;
assert(m);
assert(m->signal_fd == fd);
@@ -1713,18 +1954,21 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
for (;;) {
n = read(m->signal_fd, &sfsi, sizeof(sfsi));
if (n != sizeof(sfsi)) {
+ if (n >= 0) {
+ log_warning("Truncated read from signal fd (%zu bytes)!", n);
+ return 0;
+ }
- if (n >= 0)
- return -EIO;
-
- if (errno == EINTR || errno == EAGAIN)
+ if (IN_SET(errno, EINTR, EAGAIN))
break;
- return -errno;
+ /* We return an error here, which will kill this handler,
+ * to avoid a busy loop on read error. */
+ return log_error_errno(errno, "Reading from signal fd failed: %m");
}
log_received_signal(sfsi.ssi_signo == SIGCHLD ||
- (sfsi.ssi_signo == SIGTERM && m->running_as == MANAGER_USER)
+ (sfsi.ssi_signo == SIGTERM && MANAGER_IS_USER(m))
? LOG_DEBUG : LOG_INFO,
&sfsi);
@@ -1735,7 +1979,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
break;
case SIGTERM:
- if (m->running_as == MANAGER_SYSTEM) {
+ if (MANAGER_IS_SYSTEM(m)) {
/* This is for compatibility with the
* original sysvinit */
m->exit_code = MANAGER_REEXECUTE;
@@ -1745,20 +1989,8 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
/* Fall through */
case SIGINT:
- if (m->running_as == MANAGER_SYSTEM) {
-
- /* If the user presses C-A-D more than
- * 7 times within 2s, we reboot
- * immediately. */
-
- if (ratelimit_test(&m->ctrl_alt_del_ratelimit))
- manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY);
- else {
- log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately.");
- status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately.");
- m->exit_code = MANAGER_REBOOT;
- }
-
+ if (MANAGER_IS_SYSTEM(m)) {
+ manager_handle_ctrl_alt_del(m);
break;
}
@@ -1771,14 +2003,14 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
break;
case SIGWINCH:
- if (m->running_as == MANAGER_SYSTEM)
+ if (MANAGER_IS_SYSTEM(m))
manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE);
/* This is a nop on non-init */
break;
case SIGPWR:
- if (m->running_as == MANAGER_SYSTEM)
+ if (MANAGER_IS_SYSTEM(m))
manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE);
/* This is a nop on non-init */
@@ -1809,20 +2041,16 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
f = open_memstream(&dump, &size);
if (!f) {
- log_warning("Failed to allocate memory stream.");
+ log_warning_errno(errno, "Failed to allocate memory stream: %m");
break;
}
manager_dump_units(m, f, "\t");
manager_dump_jobs(m, f, "\t");
- if (ferror(f)) {
- log_warning("Failed to write status stream");
- break;
- }
-
- if (fflush(f)) {
- log_warning("Failed to flush status stream");
+ r = fflush_and_check(f);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to write status stream: %m");
break;
}
@@ -1872,27 +2100,25 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
switch (sfsi.ssi_signo - SIGRTMIN) {
case 20:
- log_debug("Enabling showing of status.");
manager_set_show_status(m, SHOW_STATUS_YES);
break;
case 21:
- log_debug("Disabling showing of status.");
manager_set_show_status(m, SHOW_STATUS_NO);
break;
case 22:
log_set_max_level(LOG_DEBUG);
- log_notice("Setting log level to debug.");
+ log_info("Setting log level to debug.");
break;
case 23:
log_set_max_level(LOG_INFO);
- log_notice("Setting log level to info.");
+ log_info("Setting log level to info.");
break;
case 24:
- if (m->running_as == MANAGER_USER) {
+ if (MANAGER_IS_USER(m)) {
m->exit_code = MANAGER_EXIT;
return 0;
}
@@ -1963,7 +2189,6 @@ static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32
m->no_console_output = m->n_on_console > 0;
- m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source);
manager_close_idle_pipe(m);
return 0;
@@ -1996,8 +2221,7 @@ int manager_loop(Manager *m) {
m->exit_code = MANAGER_OK;
/* Release the path cache */
- set_free_free(m->unit_path_cache);
- m->unit_path_cache = NULL;
+ m->unit_path_cache = set_free_free(m->unit_path_cache);
manager_check_finished(m);
@@ -2010,14 +2234,13 @@ int manager_loop(Manager *m) {
while (m->exit_code == MANAGER_OK) {
usec_t wait_usec;
- if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM)
+ if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m))
watchdog_ping();
if (!ratelimit_test(&rl)) {
/* Yay, something is going seriously wrong, pause a little */
log_warning("Looping too fast. Throttling execution a little.");
sleep(1);
- continue;
}
if (manager_dispatch_load_queue(m) > 0)
@@ -2036,7 +2259,7 @@ int manager_loop(Manager *m) {
continue;
/* Sleep for half the watchdog time */
- if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) {
+ if (m->runtime_watchdog > 0 && m->runtime_watchdog != USEC_INFINITY && MANAGER_IS_SYSTEM(m)) {
wait_usec = m->runtime_watchdog / 2;
if (wait_usec <= 0)
wait_usec = 1;
@@ -2053,6 +2276,7 @@ int manager_loop(Manager *m) {
int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) {
_cleanup_free_ char *n = NULL;
+ sd_id128_t invocation_id;
Unit *u;
int r;
@@ -2064,12 +2288,25 @@ int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e,
if (r < 0)
return r;
+ /* Permit addressing units by invocation ID: if the passed bus path is suffixed by a 128bit ID then we use it
+ * as invocation ID. */
+ r = sd_id128_from_string(n, &invocation_id);
+ if (r >= 0) {
+ u = hashmap_get(m->units_by_invocation_id, &invocation_id);
+ if (u) {
+ *_u = u;
+ return 0;
+ }
+
+ return sd_bus_error_setf(e, BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID, "No unit with the specified invocation ID " SD_ID128_FORMAT_STR " known.", SD_ID128_FORMAT_VAL(invocation_id));
+ }
+
+ /* If this didn't work, we use the suffix as unit name. */
r = manager_load_unit(m, n, NULL, e, &u);
if (r < 0)
return r;
*_u = u;
-
return 0;
}
@@ -2107,16 +2344,16 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) {
const char *msg;
int audit_fd, r;
+ if (!MANAGER_IS_SYSTEM(m))
+ return;
+
audit_fd = get_audit_fd();
if (audit_fd < 0)
return;
/* Don't generate audit events if the service was already
* started and we're just deserializing */
- if (m->n_reloading > 0)
- return;
-
- if (m->running_as != MANAGER_SYSTEM)
+ if (MANAGER_IS_RELOADING(m))
return;
if (u->type != UNIT_SERVICE)
@@ -2142,21 +2379,20 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) {
}
void manager_send_unit_plymouth(Manager *m, Unit *u) {
- union sockaddr_union sa = PLYMOUTH_SOCKET;
-
- int n = 0;
+ static const union sockaddr_union sa = PLYMOUTH_SOCKET;
_cleanup_free_ char *message = NULL;
_cleanup_close_ int fd = -1;
+ int n = 0;
/* Don't generate plymouth events if the service was already
* started and we're just deserializing */
- if (m->n_reloading > 0)
+ if (MANAGER_IS_RELOADING(m))
return;
- if (m->running_as != MANAGER_SYSTEM)
+ if (!MANAGER_IS_SYSTEM(m))
return;
- if (detect_container(NULL) > 0)
+ if (detect_container() > 0)
return;
if (u->type != UNIT_SERVICE &&
@@ -2172,7 +2408,7 @@ void manager_send_unit_plymouth(Manager *m, Unit *u) {
return;
}
- if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
+ if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) {
if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED))
log_error_errno(errno, "connect() failed: %m");
@@ -2190,24 +2426,6 @@ void manager_send_unit_plymouth(Manager *m, Unit *u) {
log_error_errno(errno, "Failed to write Plymouth message: %m");
}
-void manager_dispatch_bus_name_owner_changed(
- Manager *m,
- const char *name,
- const char* old_owner,
- const char *new_owner) {
-
- Unit *u;
-
- assert(m);
- assert(name);
-
- u = hashmap_get(m->watch_bus, name);
- if (!u)
- return;
-
- UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner);
-}
-
int manager_open_serialization(Manager *m, FILE **_f) {
const char *path;
int fd = -1;
@@ -2215,8 +2433,8 @@ int manager_open_serialization(Manager *m, FILE **_f) {
assert(_f);
- path = m->running_as == MANAGER_SYSTEM ? "/run/systemd" : "/tmp";
- fd = open_tmpfile(path, O_RDWR|O_CLOEXEC);
+ path = MANAGER_IS_SYSTEM(m) ? "/run/systemd" : "/tmp";
+ fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
if (fd < 0)
return -errno;
@@ -2244,7 +2462,7 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
assert(f);
assert(fds);
- m->n_reloading ++;
+ m->n_reloading++;
fprintf(f, "current-job-id=%"PRIu32"\n", m->current_job_id);
fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr));
@@ -2290,17 +2508,38 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
fprintf(f, "notify-socket=%s\n", m->notify_socket);
}
- if (m->kdbus_fd >= 0) {
+ if (m->cgroups_agent_fd >= 0) {
int copy;
- copy = fdset_put_dup(fds, m->kdbus_fd);
+ copy = fdset_put_dup(fds, m->cgroups_agent_fd);
if (copy < 0)
return copy;
- fprintf(f, "kdbus-fd=%i\n", copy);
+ fprintf(f, "cgroups-agent-fd=%i\n", copy);
+ }
+
+ if (m->user_lookup_fds[0] >= 0) {
+ int copy0, copy1;
+
+ copy0 = fdset_put_dup(fds, m->user_lookup_fds[0]);
+ if (copy0 < 0)
+ return copy0;
+
+ copy1 = fdset_put_dup(fds, m->user_lookup_fds[1]);
+ if (copy1 < 0)
+ return copy1;
+
+ fprintf(f, "user-lookup=%i %i\n", copy0, copy1);
}
- bus_track_serialize(m->subscribed, f);
+ bus_track_serialize(m->subscribed, f, "subscribed");
+
+ r = dynamic_user_serialize(m, f, fds);
+ if (r < 0)
+ return r;
+
+ manager_serialize_uid_refs(m, f);
+ manager_serialize_gid_refs(m, f);
fputc('\n', f);
@@ -2314,13 +2553,13 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
r = unit_serialize(u, f, fds, !switching_root);
if (r < 0) {
- m->n_reloading --;
+ m->n_reloading--;
return r;
}
}
assert(m->n_reloading > 0);
- m->n_reloading --;
+ m->n_reloading--;
if (ferror(f))
return -EIO;
@@ -2340,7 +2579,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
log_debug("Deserializing state...");
- m->n_reloading ++;
+ m->n_reloading++;
for (;;) {
char line[LINE_MAX], *l;
@@ -2457,25 +2696,42 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
free(m->notify_socket);
m->notify_socket = n;
- } else if (startswith(l, "kdbus-fd=")) {
+ } else if (startswith(l, "cgroups-agent-fd=")) {
int fd;
- if (safe_atoi(l + 9, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
- log_debug("Failed to parse kdbus fd: %s", l + 9);
+ if (safe_atoi(l + 17, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse cgroups agent fd: %s", l + 10);
else {
- safe_close(m->kdbus_fd);
- m->kdbus_fd = fdset_remove(fds, fd);
+ m->cgroups_agent_event_source = sd_event_source_unref(m->cgroups_agent_event_source);
+ safe_close(m->cgroups_agent_fd);
+ m->cgroups_agent_fd = fdset_remove(fds, fd);
}
- } else {
- int k;
+ } else if (startswith(l, "user-lookup=")) {
+ int fd0, fd1;
- k = bus_track_deserialize_item(&m->deserialized_subscribed, l);
- if (k < 0)
- log_debug_errno(k, "Failed to deserialize bus tracker object: %m");
- else if (k == 0)
- log_debug("Unknown serialization item '%s'", l);
- }
+ if (sscanf(l + 12, "%i %i", &fd0, &fd1) != 2 || fd0 < 0 || fd1 < 0 || fd0 == fd1 || !fdset_contains(fds, fd0) || !fdset_contains(fds, fd1))
+ log_debug("Failed to parse user lookup fd: %s", l + 12);
+ else {
+ m->user_lookup_event_source = sd_event_source_unref(m->user_lookup_event_source);
+ safe_close_pair(m->user_lookup_fds);
+ m->user_lookup_fds[0] = fdset_remove(fds, fd0);
+ m->user_lookup_fds[1] = fdset_remove(fds, fd1);
+ }
+
+ } else if (startswith(l, "dynamic-user="))
+ dynamic_user_deserialize_one(m, l + 13, fds);
+ else if (startswith(l, "destroy-ipc-uid="))
+ manager_deserialize_uid_refs_one(m, l + 16);
+ else if (startswith(l, "destroy-ipc-gid="))
+ manager_deserialize_gid_refs_one(m, l + 16);
+ else if (startswith(l, "subscribed=")) {
+
+ if (strv_extend(&m->deserialized_subscribed, l+11) < 0)
+ log_oom();
+
+ } else if (!startswith(l, "kdbus-fd=")) /* ignore this one */
+ log_debug("Unknown serialization item '%s'", l);
}
for (;;) {
@@ -2508,7 +2764,7 @@ finish:
r = -EIO;
assert(m->n_reloading > 0);
- m->n_reloading --;
+ m->n_reloading--;
return r;
}
@@ -2524,51 +2780,48 @@ int manager_reload(Manager *m) {
if (r < 0)
return r;
- m->n_reloading ++;
+ m->n_reloading++;
bus_manager_send_reloading(m, true);
fds = fdset_new();
if (!fds) {
- m->n_reloading --;
+ m->n_reloading--;
return -ENOMEM;
}
r = manager_serialize(m, f, fds, false);
if (r < 0) {
- m->n_reloading --;
+ m->n_reloading--;
return r;
}
if (fseeko(f, 0, SEEK_SET) < 0) {
- m->n_reloading --;
+ m->n_reloading--;
return -errno;
}
/* From here on there is no way back. */
manager_clear_jobs_and_units(m);
- manager_undo_generators(m);
+ lookup_paths_flush_generator(&m->lookup_paths);
lookup_paths_free(&m->lookup_paths);
+ dynamic_user_vacuum(m, false);
+ m->uid_refs = hashmap_free(m->uid_refs);
+ m->gid_refs = hashmap_free(m->gid_refs);
- /* Find new unit paths */
- q = manager_run_generators(m);
+ q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
if (q < 0 && r >= 0)
r = q;
- q = lookup_paths_init(
- &m->lookup_paths, m->running_as, true,
- NULL,
- m->generator_unit_path,
- m->generator_unit_path_early,
- m->generator_unit_path_late);
+ /* Find new unit paths */
+ q = manager_run_generators(m);
if (q < 0 && r >= 0)
r = q;
+ lookup_paths_reduce(&m->lookup_paths);
manager_build_unit_path_cache(m);
/* First, enumerate what we can from all config files */
- q = manager_enumerate(m);
- if (q < 0 && r >= 0)
- r = q;
+ manager_enumerate(m);
/* Second, deserialize our stored data */
q = manager_deserialize(m, f, fds);
@@ -2583,9 +2836,28 @@ int manager_reload(Manager *m) {
if (q < 0 && r >= 0)
r = q;
+ q = manager_setup_cgroups_agent(m);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = manager_setup_user_lookup_fd(m);
+ if (q < 0 && r >= 0)
+ r = q;
+
/* Third, fire things up! */
manager_coldplug(m);
+ /* Release any dynamic users no longer referenced */
+ dynamic_user_vacuum(m, true);
+
+ /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
+ manager_vacuum_uid_refs(m);
+ manager_vacuum_gid_refs(m);
+
+ /* Sync current state of bus names with our set of listening units */
+ if (m->api_bus)
+ manager_sync_bus_names(m, m->api_bus);
+
assert(m->n_reloading > 0);
m->n_reloading--;
@@ -2594,12 +2866,6 @@ int manager_reload(Manager *m) {
return r;
}
-bool manager_is_reloading_or_reexecuting(Manager *m) {
- assert(m);
-
- return m->n_reloading != 0;
-}
-
void manager_reset_failed(Manager *m) {
Unit *u;
Iterator i;
@@ -2631,7 +2897,7 @@ static void manager_notify_finished(Manager *m) {
if (m->test_run)
return;
- if (m->running_as == MANAGER_SYSTEM && detect_container(NULL) <= 0) {
+ if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) {
/* Note that m->kernel_usec.monotonic is always at 0,
* and m->firmware_usec.monotonic and
@@ -2694,12 +2960,9 @@ static void manager_notify_finished(Manager *m) {
}
void manager_check_finished(Manager *m) {
- Unit *u = NULL;
- Iterator i;
-
assert(m);
- if (m->n_reloading > 0)
+ if (MANAGER_IS_RELOADING(m))
return;
/* Verify that we are actually running currently. Initially
@@ -2709,11 +2972,9 @@ void manager_check_finished(Manager *m) {
return;
if (hashmap_size(m->jobs) > 0) {
-
if (m->jobs_in_progress_event_source)
/* Ignore any failure, this is only for feedback */
- (void) sd_event_source_set_time(m->jobs_in_progress_event_source,
- now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC);
+ (void) sd_event_source_set_time(m->jobs_in_progress_event_source, now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC);
return;
}
@@ -2721,7 +2982,6 @@ void manager_check_finished(Manager *m) {
manager_flip_auto_status(m, false);
/* Notify Type=idle units that we are done now */
- m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source);
manager_close_idle_pipe(m);
/* Turn off confirm spawn now */
@@ -2740,83 +3000,7 @@ void manager_check_finished(Manager *m) {
manager_notify_finished(m);
- SET_FOREACH(u, m->startup_units, i)
- if (u->cgroup_path)
- cgroup_context_apply(unit_get_cgroup_context(u), unit_get_cgroup_mask(u), u->cgroup_path, manager_state(m));
-}
-
-static int create_generator_dir(Manager *m, char **generator, const char *name) {
- char *p;
- int r;
-
- assert(m);
- assert(generator);
- assert(name);
-
- if (*generator)
- return 0;
-
- if (m->running_as == MANAGER_SYSTEM && getpid() == 1) {
- /* systemd --system, not running --test */
-
- p = strappend("/run/systemd/", name);
- if (!p)
- return log_oom();
-
- r = mkdir_p_label(p, 0755);
- if (r < 0) {
- log_error_errno(r, "Failed to create generator directory %s: %m", p);
- free(p);
- return r;
- }
- } else if (m->running_as == MANAGER_USER) {
- const char *s = NULL;
-
- s = getenv("XDG_RUNTIME_DIR");
- if (!s)
- return -EINVAL;
- p = strjoin(s, "/systemd/", name, NULL);
- if (!p)
- return log_oom();
-
- r = mkdir_p_label(p, 0755);
- if (r < 0) {
- log_error_errno(r, "Failed to create generator directory %s: %m", p);
- free(p);
- return r;
- }
- } else {
- /* systemd --system --test */
-
- p = strjoin("/tmp/systemd-", name, ".XXXXXX", NULL);
- if (!p)
- return log_oom();
-
- if (!mkdtemp(p)) {
- log_error_errno(errno, "Failed to create generator directory %s: %m",
- p);
- free(p);
- return -errno;
- }
- }
-
- *generator = p;
- return 0;
-}
-
-static void trim_generator_dir(Manager *m, char **generator) {
- assert(m);
- assert(generator);
-
- if (!*generator)
- return;
-
- if (rmdir(*generator) >= 0) {
- free(*generator);
- *generator = NULL;
- }
-
- return;
+ manager_invalidate_startup_units(m);
}
static int manager_run_generators(Manager *m) {
@@ -2830,72 +3014,40 @@ static int manager_run_generators(Manager *m) {
if (m->test_run)
return 0;
- paths = generator_paths(m->running_as);
+ paths = generator_binary_paths(m->unit_file_scope);
if (!paths)
return log_oom();
/* Optimize by skipping the whole process by not creating output directories
* if no generators are found. */
STRV_FOREACH(path, paths) {
- r = access(*path, F_OK);
- if (r == 0)
+ if (access(*path, F_OK) >= 0)
goto found;
if (errno != ENOENT)
log_warning_errno(errno, "Failed to open generator directory %s: %m", *path);
}
+
return 0;
found:
- r = create_generator_dir(m, &m->generator_unit_path, "generator");
- if (r < 0)
- goto finish;
-
- r = create_generator_dir(m, &m->generator_unit_path_early, "generator.early");
- if (r < 0)
- goto finish;
-
- r = create_generator_dir(m, &m->generator_unit_path_late, "generator.late");
+ r = lookup_paths_mkdir_generator(&m->lookup_paths);
if (r < 0)
goto finish;
argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */
- argv[1] = m->generator_unit_path;
- argv[2] = m->generator_unit_path_early;
- argv[3] = m->generator_unit_path_late;
+ argv[1] = m->lookup_paths.generator;
+ argv[2] = m->lookup_paths.generator_early;
+ argv[3] = m->lookup_paths.generator_late;
argv[4] = NULL;
RUN_WITH_UMASK(0022)
execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv);
finish:
- trim_generator_dir(m, &m->generator_unit_path);
- trim_generator_dir(m, &m->generator_unit_path_early);
- trim_generator_dir(m, &m->generator_unit_path_late);
+ lookup_paths_trim_generator(&m->lookup_paths);
return r;
}
-static void remove_generator_dir(Manager *m, char **generator) {
- assert(m);
- assert(generator);
-
- if (!*generator)
- return;
-
- strv_remove(m->lookup_paths.unit_path, *generator);
- (void) rm_rf(*generator, REMOVE_ROOT);
-
- free(*generator);
- *generator = NULL;
-}
-
-static void manager_undo_generators(Manager *m) {
- assert(m);
-
- remove_generator_dir(m, &m->generator_unit_path);
- remove_generator_dir(m, &m->generator_unit_path_early);
- remove_generator_dir(m, &m->generator_unit_path_late);
-}
-
int manager_environment_add(Manager *m, char **minus, char **plus) {
char **a = NULL, **b = NULL, **l;
assert(m);
@@ -2940,6 +3092,8 @@ int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) {
assert(m);
for (i = 0; i < _RLIMIT_MAX; i++) {
+ m->rlimit[i] = mfree(m->rlimit[i]);
+
if (!default_rlimit[i])
continue;
@@ -2956,7 +3110,7 @@ void manager_recheck_journal(Manager *m) {
assert(m);
- if (m->running_as != MANAGER_SYSTEM)
+ if (!MANAGER_IS_SYSTEM(m))
return;
u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET);
@@ -2980,21 +3134,24 @@ void manager_set_show_status(Manager *m, ShowStatus mode) {
assert(m);
assert(IN_SET(mode, SHOW_STATUS_AUTO, SHOW_STATUS_NO, SHOW_STATUS_YES, SHOW_STATUS_TEMPORARY));
- if (m->running_as != MANAGER_SYSTEM)
+ if (!MANAGER_IS_SYSTEM(m))
return;
+ if (m->show_status != mode)
+ log_debug("%s showing of status.",
+ mode == SHOW_STATUS_NO ? "Disabling" : "Enabling");
m->show_status = mode;
if (mode > 0)
- touch("/run/systemd/show-status");
+ (void) touch("/run/systemd/show-status");
else
- unlink("/run/systemd/show-status");
+ (void) unlink("/run/systemd/show-status");
}
static bool manager_get_show_status(Manager *m, StatusType type) {
assert(m);
- if (m->running_as != MANAGER_SYSTEM)
+ if (!MANAGER_IS_SYSTEM(m))
return false;
if (m->no_console_output)
@@ -3016,15 +3173,17 @@ static bool manager_get_show_status(Manager *m, StatusType type) {
void manager_set_first_boot(Manager *m, bool b) {
assert(m);
- if (m->running_as != MANAGER_SYSTEM)
+ if (!MANAGER_IS_SYSTEM(m))
return;
- m->first_boot = b;
+ if (m->first_boot != (int) b) {
+ if (b)
+ (void) touch("/run/systemd/first-boot");
+ else
+ (void) unlink("/run/systemd/first-boot");
+ }
- if (m->first_boot)
- touch("/run/systemd/first-boot");
- else
- unlink("/run/systemd/first-boot");
+ m->first_boot = b;
}
void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) {
@@ -3045,30 +3204,6 @@ void manager_status_printf(Manager *m, StatusType type, const char *status, cons
va_end(ap);
}
-int manager_get_unit_by_path(Manager *m, const char *path, const char *suffix, Unit **_found) {
- _cleanup_free_ char *p = NULL;
- Unit *found;
- int r;
-
- assert(m);
- assert(path);
- assert(suffix);
- assert(_found);
-
- r = unit_name_from_path(path, suffix, &p);
- if (r < 0)
- return r;
-
- found = manager_get_unit(m, p);
- if (!found) {
- *_found = NULL;
- return 0;
- }
-
- *_found = found;
- return 1;
-}
-
Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) {
char p[strlen(path)+1];
@@ -3084,13 +3219,14 @@ Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) {
const char *manager_get_runtime_prefix(Manager *m) {
assert(m);
- return m->running_as == MANAGER_SYSTEM ?
+ return MANAGER_IS_SYSTEM(m) ?
"/run" :
getenv("XDG_RUNTIME_DIR");
}
-void manager_update_failed_units(Manager *m, Unit *u, bool failed) {
+int manager_update_failed_units(Manager *m, Unit *u, bool failed) {
unsigned size;
+ int r;
assert(m);
assert(u->manager == m);
@@ -3098,13 +3234,19 @@ void manager_update_failed_units(Manager *m, Unit *u, bool failed) {
size = set_size(m->failed_units);
if (failed) {
+ r = set_ensure_allocated(&m->failed_units, NULL);
+ if (r < 0)
+ return log_oom();
+
if (set_put(m->failed_units, u) < 0)
- log_oom();
+ return log_oom();
} else
- set_remove(m->failed_units, u);
+ (void) set_remove(m->failed_units, u);
if (set_size(m->failed_units) != size)
bus_manager_send_change_signal(m);
+
+ return 0;
}
ManagerState manager_state(Manager *m) {
@@ -3124,18 +3266,18 @@ ManagerState manager_state(Manager *m) {
/* Is the special shutdown target queued? If so, we are in shutdown state */
u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET);
- if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_TRY_RESTART, JOB_RELOAD_OR_START))
+ if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START))
return MANAGER_STOPPING;
/* Are the rescue or emergency targets active or queued? If so we are in maintenance state */
u = manager_get_unit(m, SPECIAL_RESCUE_TARGET);
if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) ||
- (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_TRY_RESTART, JOB_RELOAD_OR_START))))
+ (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START))))
return MANAGER_MAINTENANCE;
u = manager_get_unit(m, SPECIAL_EMERGENCY_TARGET);
if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) ||
- (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_TRY_RESTART, JOB_RELOAD_OR_START))))
+ (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START))))
return MANAGER_MAINTENANCE;
/* Are there any failed units? If so, we are in degraded mode */
@@ -3145,6 +3287,300 @@ ManagerState manager_state(Manager *m) {
return MANAGER_RUNNING;
}
+#define DESTROY_IPC_FLAG (UINT32_C(1) << 31)
+
+static void manager_unref_uid_internal(
+ Manager *m,
+ Hashmap **uid_refs,
+ uid_t uid,
+ bool destroy_now,
+ int (*_clean_ipc)(uid_t uid)) {
+
+ uint32_t c, n;
+
+ assert(m);
+ assert(uid_refs);
+ assert(uid_is_valid(uid));
+ assert(_clean_ipc);
+
+ /* A generic implementation, covering both manager_unref_uid() and manager_unref_gid(), under the assumption
+ * that uid_t and gid_t are actually defined the same way, with the same validity rules.
+ *
+ * We store a hashmap where the UID/GID is they key and the value is a 32bit reference counter, whose highest
+ * bit is used as flag for marking UIDs/GIDs whose IPC objects to remove when the last reference to the UID/GID
+ * is dropped. The flag is set to on, once at least one reference from a unit where RemoveIPC= is set is added
+ * on a UID/GID. It is reset when the UID's/GID's reference counter drops to 0 again. */
+
+ assert_cc(sizeof(uid_t) == sizeof(gid_t));
+ assert_cc(UID_INVALID == (uid_t) GID_INVALID);
+
+ if (uid == 0) /* We don't keep track of root, and will never destroy it */
+ return;
+
+ c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
+
+ n = c & ~DESTROY_IPC_FLAG;
+ assert(n > 0);
+ n--;
+
+ if (destroy_now && n == 0) {
+ hashmap_remove(*uid_refs, UID_TO_PTR(uid));
+
+ if (c & DESTROY_IPC_FLAG) {
+ log_debug("%s " UID_FMT " is no longer referenced, cleaning up its IPC.",
+ _clean_ipc == clean_ipc_by_uid ? "UID" : "GID",
+ uid);
+ (void) _clean_ipc(uid);
+ }
+ } else {
+ c = n | (c & DESTROY_IPC_FLAG);
+ assert_se(hashmap_update(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c)) >= 0);
+ }
+}
+
+void manager_unref_uid(Manager *m, uid_t uid, bool destroy_now) {
+ manager_unref_uid_internal(m, &m->uid_refs, uid, destroy_now, clean_ipc_by_uid);
+}
+
+void manager_unref_gid(Manager *m, gid_t gid, bool destroy_now) {
+ manager_unref_uid_internal(m, &m->gid_refs, (uid_t) gid, destroy_now, clean_ipc_by_gid);
+}
+
+static int manager_ref_uid_internal(
+ Manager *m,
+ Hashmap **uid_refs,
+ uid_t uid,
+ bool clean_ipc) {
+
+ uint32_t c, n;
+ int r;
+
+ assert(m);
+ assert(uid_refs);
+ assert(uid_is_valid(uid));
+
+ /* A generic implementation, covering both manager_ref_uid() and manager_ref_gid(), under the assumption
+ * that uid_t and gid_t are actually defined the same way, with the same validity rules. */
+
+ assert_cc(sizeof(uid_t) == sizeof(gid_t));
+ assert_cc(UID_INVALID == (uid_t) GID_INVALID);
+
+ if (uid == 0) /* We don't keep track of root, and will never destroy it */
+ return 0;
+
+ r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops);
+ if (r < 0)
+ return r;
+
+ c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
+
+ n = c & ~DESTROY_IPC_FLAG;
+ n++;
+
+ if (n & DESTROY_IPC_FLAG) /* check for overflow */
+ return -EOVERFLOW;
+
+ c = n | (c & DESTROY_IPC_FLAG) | (clean_ipc ? DESTROY_IPC_FLAG : 0);
+
+ return hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c));
+}
+
+int manager_ref_uid(Manager *m, uid_t uid, bool clean_ipc) {
+ return manager_ref_uid_internal(m, &m->uid_refs, uid, clean_ipc);
+}
+
+int manager_ref_gid(Manager *m, gid_t gid, bool clean_ipc) {
+ return manager_ref_uid_internal(m, &m->gid_refs, (uid_t) gid, clean_ipc);
+}
+
+static void manager_vacuum_uid_refs_internal(
+ Manager *m,
+ Hashmap **uid_refs,
+ int (*_clean_ipc)(uid_t uid)) {
+
+ Iterator i;
+ void *p, *k;
+
+ assert(m);
+ assert(uid_refs);
+ assert(_clean_ipc);
+
+ HASHMAP_FOREACH_KEY(p, k, *uid_refs, i) {
+ uint32_t c, n;
+ uid_t uid;
+
+ uid = PTR_TO_UID(k);
+ c = PTR_TO_UINT32(p);
+
+ n = c & ~DESTROY_IPC_FLAG;
+ if (n > 0)
+ continue;
+
+ if (c & DESTROY_IPC_FLAG) {
+ log_debug("Found unreferenced %s " UID_FMT " after reload/reexec. Cleaning up.",
+ _clean_ipc == clean_ipc_by_uid ? "UID" : "GID",
+ uid);
+ (void) _clean_ipc(uid);
+ }
+
+ assert_se(hashmap_remove(*uid_refs, k) == p);
+ }
+}
+
+void manager_vacuum_uid_refs(Manager *m) {
+ manager_vacuum_uid_refs_internal(m, &m->uid_refs, clean_ipc_by_uid);
+}
+
+void manager_vacuum_gid_refs(Manager *m) {
+ manager_vacuum_uid_refs_internal(m, &m->gid_refs, clean_ipc_by_gid);
+}
+
+static void manager_serialize_uid_refs_internal(
+ Manager *m,
+ FILE *f,
+ Hashmap **uid_refs,
+ const char *field_name) {
+
+ Iterator i;
+ void *p, *k;
+
+ assert(m);
+ assert(f);
+ assert(uid_refs);
+ assert(field_name);
+
+ /* Serialize the UID reference table. Or actually, just the IPC destruction flag of it, as the actual counter
+ * of it is better rebuild after a reload/reexec. */
+
+ HASHMAP_FOREACH_KEY(p, k, *uid_refs, i) {
+ uint32_t c;
+ uid_t uid;
+
+ uid = PTR_TO_UID(k);
+ c = PTR_TO_UINT32(p);
+
+ if (!(c & DESTROY_IPC_FLAG))
+ continue;
+
+ fprintf(f, "%s=" UID_FMT "\n", field_name, uid);
+ }
+}
+
+void manager_serialize_uid_refs(Manager *m, FILE *f) {
+ manager_serialize_uid_refs_internal(m, f, &m->uid_refs, "destroy-ipc-uid");
+}
+
+void manager_serialize_gid_refs(Manager *m, FILE *f) {
+ manager_serialize_uid_refs_internal(m, f, &m->gid_refs, "destroy-ipc-gid");
+}
+
+static void manager_deserialize_uid_refs_one_internal(
+ Manager *m,
+ Hashmap** uid_refs,
+ const char *value) {
+
+ uid_t uid;
+ uint32_t c;
+ int r;
+
+ assert(m);
+ assert(uid_refs);
+ assert(value);
+
+ r = parse_uid(value, &uid);
+ if (r < 0 || uid == 0) {
+ log_debug("Unable to parse UID reference serialization");
+ return;
+ }
+
+ r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
+ if (c & DESTROY_IPC_FLAG)
+ return;
+
+ c |= DESTROY_IPC_FLAG;
+
+ r = hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c));
+ if (r < 0) {
+ log_debug("Failed to add UID reference entry");
+ return;
+ }
+}
+
+void manager_deserialize_uid_refs_one(Manager *m, const char *value) {
+ manager_deserialize_uid_refs_one_internal(m, &m->uid_refs, value);
+}
+
+void manager_deserialize_gid_refs_one(Manager *m, const char *value) {
+ manager_deserialize_uid_refs_one_internal(m, &m->gid_refs, value);
+}
+
+int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+ struct buffer {
+ uid_t uid;
+ gid_t gid;
+ char unit_name[UNIT_NAME_MAX+1];
+ } _packed_ buffer;
+
+ Manager *m = userdata;
+ ssize_t l;
+ size_t n;
+ Unit *u;
+
+ assert_se(source);
+ assert_se(m);
+
+ /* Invoked whenever a child process succeeded resolving its user/group to use and sent us the resulting UID/GID
+ * in a datagram. We parse the datagram here and pass it off to the unit, so that it can add a reference to the
+ * UID/GID so that it can destroy the UID/GID's IPC objects when the reference counter drops to 0. */
+
+ l = recv(fd, &buffer, sizeof(buffer), MSG_DONTWAIT);
+ if (l < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ return 0;
+
+ return log_error_errno(errno, "Failed to read from user lookup fd: %m");
+ }
+
+ if ((size_t) l <= offsetof(struct buffer, unit_name)) {
+ log_warning("Received too short user lookup message, ignoring.");
+ return 0;
+ }
+
+ if ((size_t) l > offsetof(struct buffer, unit_name) + UNIT_NAME_MAX) {
+ log_warning("Received too long user lookup message, ignoring.");
+ return 0;
+ }
+
+ if (!uid_is_valid(buffer.uid) && !gid_is_valid(buffer.gid)) {
+ log_warning("Got user lookup message with invalid UID/GID pair, ignoring.");
+ return 0;
+ }
+
+ n = (size_t) l - offsetof(struct buffer, unit_name);
+ if (memchr(buffer.unit_name, 0, n)) {
+ log_warning("Received lookup message with embedded NUL character, ignoring.");
+ return 0;
+ }
+
+ buffer.unit_name[n] = 0;
+ u = manager_get_unit(m, buffer.unit_name);
+ if (!u) {
+ log_debug("Got user lookup message but unit doesn't exist, ignoring.");
+ return 0;
+ }
+
+ log_unit_debug(u, "User lookup succeeded: uid=" UID_FMT " gid=" GID_FMT, buffer.uid, buffer.gid);
+
+ unit_notify_user_lookup(u, buffer.uid, buffer.gid);
+ return 0;
+}
+
static const char *const manager_state_table[_MANAGER_STATE_MAX] = {
[MANAGER_INITIALIZING] = "initializing",
[MANAGER_STARTING] = "starting",
@@ -3155,3 +3591,11 @@ static const char *const manager_state_table[_MANAGER_STATE_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState);
+
+static const char *const cad_burst_action_table[_CAD_BURST_ACTION_MAX] = {
+ [CAD_BURST_ACTION_IGNORE] = "ignore",
+ [CAD_BURST_ACTION_REBOOT] = "reboot-force",
+ [CAD_BURST_ACTION_POWEROFF] = "poweroff-force",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(cad_burst_action, CADBurstAction);