summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorAnthony G. Basile <blueness@gentoo.org>2012-11-15 10:10:41 -0500
committerAnthony G. Basile <blueness@gentoo.org>2012-11-15 10:10:41 -0500
commit2944f347d087ff24ec808e4b70fe104a772a97a0 (patch)
treea5de4fbefe16ef359a526442fb41251f123399d5 /src/core
parent678b0b89572768b21d8b74360d55b75b233799c4 (diff)
parentd025f1e4dca8fc1436aff76f9e6185fe3e728daa (diff)
Fork of Original Code Base: anongit.freedesktop.org/systemd
This is the initial fork of the code base from freedsktop.org. The code is provided here as a reference of the initial starting point and for possible future checkouts after a large portion of this code is removed. Merge git://anongit.freedesktop.org/systemd/systemd
Diffstat (limited to 'src/core')
-rw-r--r--src/core/.gitignore6
l---------src/core/Makefile1
-rw-r--r--src/core/audit-fd.c73
-rw-r--r--src/core/audit-fd.h25
-rw-r--r--src/core/automount.c902
-rw-r--r--src/core/automount.h73
-rw-r--r--src/core/build.h84
-rw-r--r--src/core/bus-errors.h55
-rw-r--r--src/core/cgroup-attr.c102
-rw-r--r--src/core/cgroup-attr.h46
-rw-r--r--src/core/cgroup.c598
-rw-r--r--src/core/cgroup.h91
-rw-r--r--src/core/condition.c370
-rw-r--r--src/core/condition.h69
-rw-r--r--src/core/dbus-automount.c75
-rw-r--r--src/core/dbus-automount.h31
-rw-r--r--src/core/dbus-device.c68
-rw-r--r--src/core/dbus-device.h31
-rw-r--r--src/core/dbus-execute.c439
-rw-r--r--src/core/dbus-execute.h124
-rw-r--r--src/core/dbus-job.c378
-rw-r--r--src/core/dbus-job.h33
-rw-r--r--src/core/dbus-kill.c35
-rw-r--r--src/core/dbus-kill.h39
-rw-r--r--src/core/dbus-manager.c1685
-rw-r--r--src/core/dbus-manager.h28
-rw-r--r--src/core/dbus-mount.c168
-rw-r--r--src/core/dbus-mount.h31
-rw-r--r--src/core/dbus-path.c122
-rw-r--r--src/core/dbus-path.h32
-rw-r--r--src/core/dbus-service.c161
-rw-r--r--src/core/dbus-service.h31
-rw-r--r--src/core/dbus-snapshot.c94
-rw-r--r--src/core/dbus-snapshot.h30
-rw-r--r--src/core/dbus-socket.c151
-rw-r--r--src/core/dbus-socket.h31
-rw-r--r--src/core/dbus-swap.c115
-rw-r--r--src/core/dbus-swap.h32
-rw-r--r--src/core/dbus-target.c58
-rw-r--r--src/core/dbus-target.h30
-rw-r--r--src/core/dbus-timer.c140
-rw-r--r--src/core/dbus-timer.h31
-rw-r--r--src/core/dbus-unit.c887
-rw-r--r--src/core/dbus-unit.h147
-rw-r--r--src/core/dbus.c1479
-rw-r--r--src/core/dbus.h50
-rw-r--r--src/core/device.c644
-rw-r--r--src/core/device.h56
-rw-r--r--src/core/execute.c2132
-rw-r--r--src/core/execute.h209
-rw-r--r--src/core/fdset.c167
-rw-r--r--src/core/fdset.h37
-rw-r--r--src/core/hostname-setup.c182
-rw-r--r--src/core/hostname-setup.h24
-rw-r--r--src/core/ima-setup.c115
-rw-r--r--src/core/ima-setup.h26
-rw-r--r--src/core/initreq.h77
-rw-r--r--src/core/job.c1086
-rw-r--r--src/core/job.h230
-rw-r--r--src/core/kill.c63
-rw-r--r--src/core/kill.h60
-rw-r--r--src/core/killall.c177
-rw-r--r--src/core/killall.h27
-rw-r--r--src/core/kmod-setup.c104
-rw-r--r--src/core/kmod-setup.h24
-rw-r--r--src/core/load-dropin.c150
-rw-r--r--src/core/load-dropin.h28
-rw-r--r--src/core/load-fragment-gperf.gperf.m4255
-rw-r--r--src/core/load-fragment.c2541
-rw-r--r--src/core/load-fragment.h88
-rw-r--r--src/core/locale-setup.c241
-rw-r--r--src/core/locale-setup.h24
-rw-r--r--src/core/loopback-setup.c317
-rw-r--r--src/core/loopback-setup.h24
-rw-r--r--src/core/machine-id-setup.c246
-rw-r--r--src/core/machine-id-setup.h24
-rw-r--r--src/core/macros.systemd.in56
-rw-r--r--src/core/main.c1950
-rw-r--r--src/core/manager.c2309
-rw-r--r--src/core/manager.h293
-rw-r--r--src/core/mount-setup.c441
-rw-r--r--src/core/mount-setup.h33
-rw-r--r--src/core/mount.c1885
-rw-r--r--src/core/mount.h121
-rw-r--r--src/core/namespace.c330
-rw-r--r--src/core/namespace.h31
-rw-r--r--src/core/org.freedesktop.systemd1.conf92
-rw-r--r--src/core/org.freedesktop.systemd1.policy.in.in41
-rw-r--r--src/core/org.freedesktop.systemd1.service11
-rw-r--r--src/core/path.c773
-rw-r--r--src/core/path.h110
-rw-r--r--src/core/securebits.h45
-rw-r--r--src/core/selinux-access.c441
-rw-r--r--src/core/selinux-access.h62
-rw-r--r--src/core/selinux-setup.c123
-rw-r--r--src/core/selinux-setup.h26
-rw-r--r--src/core/service.c3941
-rw-r--r--src/core/service.h225
-rw-r--r--src/core/shutdown.c316
-rw-r--r--src/core/snapshot.c308
-rw-r--r--src/core/snapshot.h50
-rw-r--r--src/core/socket.c2289
-rw-r--r--src/core/socket.h175
-rw-r--r--src/core/special.h113
-rw-r--r--src/core/swap.c1433
-rw-r--r--src/core/swap.h121
-rw-r--r--src/core/switch-root.c142
-rw-r--r--src/core/switch-root.h27
-rw-r--r--src/core/syscall-list.c55
-rw-r--r--src/core/syscall-list.h30
-rw-r--r--src/core/sysfs-show.h24
-rw-r--r--src/core/system.conf43
-rw-r--r--src/core/systemd.pc.in23
-rw-r--r--src/core/target.c239
-rw-r--r--src/core/target.h44
-rw-r--r--src/core/tcpwrap.c68
-rw-r--r--src/core/tcpwrap.h26
-rw-r--r--src/core/timer.c519
-rw-r--r--src/core/timer.h90
-rw-r--r--src/core/transaction.c1087
-rw-r--r--src/core/transaction.h56
-rw-r--r--src/core/umount.c639
-rw-r--r--src/core/umount.h30
-rw-r--r--src/core/unit-printf.c346
-rw-r--r--src/core/unit-printf.h28
-rw-r--r--src/core/unit.c2738
-rw-r--r--src/core/unit.h551
-rw-r--r--src/core/user.conf17
128 files changed, 43625 insertions, 0 deletions
diff --git a/src/core/.gitignore b/src/core/.gitignore
new file mode 100644
index 0000000000..a763f72507
--- /dev/null
+++ b/src/core/.gitignore
@@ -0,0 +1,6 @@
+/syscall-from-name.gperf
+/syscall-from-name.h
+/syscall-list.txt
+/syscall-to-name.h
+/macros.systemd
+/systemd.pc
diff --git a/src/core/Makefile b/src/core/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/core/Makefile
@@ -0,0 +1 @@
+../Makefile \ No newline at end of file
diff --git a/src/core/audit-fd.c b/src/core/audit-fd.c
new file mode 100644
index 0000000000..5955bd846e
--- /dev/null
+++ b/src/core/audit-fd.c
@@ -0,0 +1,73 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+
+#include <errno.h>
+#include "audit-fd.h"
+
+#ifdef HAVE_AUDIT
+
+#include <stdbool.h>
+#include <libaudit.h>
+
+#include "log.h"
+#include "util.h"
+
+static bool initialized = false;
+static int audit_fd;
+
+int get_audit_fd(void) {
+
+ if (!initialized) {
+ audit_fd = audit_open();
+
+ if (audit_fd < 0) {
+ if (errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
+ log_error("Failed to connect to audit log: %m");
+
+ audit_fd = errno ? -errno : -EINVAL;
+ }
+
+ initialized = true;
+ }
+
+ return audit_fd;
+}
+
+void close_audit_fd(void) {
+
+ if (initialized && audit_fd >= 0)
+ close_nointr_nofail(audit_fd);
+
+ initialized = true;
+ audit_fd = -ECONNRESET;
+}
+
+#else
+
+int get_audit_fd(void) {
+ return -EAFNOSUPPORT;
+}
+
+void close_audit_fd(void) {
+}
+
+#endif
diff --git a/src/core/audit-fd.h b/src/core/audit-fd.h
new file mode 100644
index 0000000000..8b58289dc5
--- /dev/null
+++ b/src/core/audit-fd.h
@@ -0,0 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int get_audit_fd(void);
+void close_audit_fd(void);
diff --git a/src/core/automount.c b/src/core/automount.c
new file mode 100644
index 0000000000..b1619a64e6
--- /dev/null
+++ b/src/core/automount.c
@@ -0,0 +1,902 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <sys/mount.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <linux/auto_fs4.h>
+#include <linux/auto_dev-ioctl.h>
+
+#include "unit.h"
+#include "automount.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "unit-name.h"
+#include "dbus-automount.h"
+#include "bus-errors.h"
+#include "special.h"
+#include "label.h"
+#include "mkdir.h"
+#include "path-util.h"
+
+static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = {
+ [AUTOMOUNT_DEAD] = UNIT_INACTIVE,
+ [AUTOMOUNT_WAITING] = UNIT_ACTIVE,
+ [AUTOMOUNT_RUNNING] = UNIT_ACTIVE,
+ [AUTOMOUNT_FAILED] = UNIT_FAILED
+};
+
+static int open_dev_autofs(Manager *m);
+
+static void automount_init(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ a->pipe_watch.fd = a->pipe_fd = -1;
+ a->pipe_watch.type = WATCH_INVALID;
+
+ a->directory_mode = 0755;
+
+ UNIT(a)->ignore_on_isolate = true;
+}
+
+static void repeat_unmout(const char *path) {
+ assert(path);
+
+ for (;;) {
+ /* If there are multiple mounts on a mount point, this
+ * removes them all */
+
+ if (umount2(path, MNT_DETACH) >= 0)
+ continue;
+
+ if (errno != EINVAL)
+ log_error("Failed to unmount: %m");
+
+ break;
+ }
+}
+
+static void unmount_autofs(Automount *a) {
+ assert(a);
+
+ if (a->pipe_fd < 0)
+ return;
+
+ automount_send_ready(a, -EHOSTDOWN);
+
+ unit_unwatch_fd(UNIT(a), &a->pipe_watch);
+ close_nointr_nofail(a->pipe_fd);
+ a->pipe_fd = -1;
+
+ /* If we reload/reexecute things we keep the mount point
+ * around */
+ if (a->where &&
+ (UNIT(a)->manager->exit_code != MANAGER_RELOAD &&
+ UNIT(a)->manager->exit_code != MANAGER_REEXECUTE))
+ repeat_unmout(a->where);
+}
+
+static void automount_done(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ unmount_autofs(a);
+ unit_ref_unset(&a->mount);
+
+ free(a->where);
+ a->where = NULL;
+
+ set_free(a->tokens);
+ a->tokens = NULL;
+}
+
+int automount_add_one_mount_link(Automount *a, Mount *m) {
+ int r;
+
+ assert(a);
+ assert(m);
+
+ if (UNIT(a)->load_state != UNIT_LOADED ||
+ UNIT(m)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!path_startswith(a->where, m->where))
+ return 0;
+
+ if (path_equal(a->where, m->where))
+ return 0;
+
+ if ((r = unit_add_two_dependencies(UNIT(a), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int automount_add_mount_links(Automount *a) {
+ Unit *other;
+ int r;
+
+ assert(a);
+
+ LIST_FOREACH(units_by_type, other, UNIT(a)->manager->units_by_type[UNIT_MOUNT])
+ if ((r = automount_add_one_mount_link(a, MOUNT(other))) < 0)
+ return r;
+
+ return 0;
+}
+
+static int automount_add_default_dependencies(Automount *a) {
+ int r;
+
+ assert(a);
+
+ if (UNIT(a)->manager->running_as != SYSTEMD_SYSTEM)
+ return 0;
+
+ r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int automount_verify(Automount *a) {
+ bool b;
+ char *e;
+ assert(a);
+
+ if (UNIT(a)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (path_equal(a->where, "/")) {
+ log_error("Cannot have an automount unit for the root directory. Refusing.");
+ return -EINVAL;
+ }
+
+ if (!(e = unit_name_from_path(a->where, ".automount")))
+ return -ENOMEM;
+
+ b = unit_has_name(UNIT(a), e);
+ free(e);
+
+ if (!b) {
+ log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(a)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int automount_load(Unit *u) {
+ int r;
+ Automount *a = AUTOMOUNT(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ /* Load a .automount file */
+ if ((r = unit_load_fragment_and_dropin_optional(u)) < 0)
+ return r;
+
+ if (u->load_state == UNIT_LOADED) {
+ Unit *x;
+
+ if (!a->where)
+ if (!(a->where = unit_name_to_path(u->id)))
+ return -ENOMEM;
+
+ path_kill_slashes(a->where);
+
+ if ((r = automount_add_mount_links(a)) < 0)
+ return r;
+
+ r = unit_load_related_unit(u, ".mount", &x);
+ if (r < 0)
+ return r;
+
+ unit_ref_set(&a->mount, x);
+
+ r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(a->mount), true);
+ if (r < 0)
+ return r;
+
+ if (UNIT(a)->default_dependencies)
+ if ((r = automount_add_default_dependencies(a)) < 0)
+ return r;
+ }
+
+ return automount_verify(a);
+}
+
+static void automount_set_state(Automount *a, AutomountState state) {
+ AutomountState old_state;
+ assert(a);
+
+ old_state = a->state;
+ a->state = state;
+
+ if (state != AUTOMOUNT_WAITING &&
+ state != AUTOMOUNT_RUNNING)
+ unmount_autofs(a);
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(a)->id,
+ automount_state_to_string(old_state),
+ automount_state_to_string(state));
+
+ unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static int automount_coldplug(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+ int r;
+
+ assert(a);
+ assert(a->state == AUTOMOUNT_DEAD);
+
+ if (a->deserialized_state != a->state) {
+
+ if ((r = open_dev_autofs(u->manager)) < 0)
+ return r;
+
+ if (a->deserialized_state == AUTOMOUNT_WAITING ||
+ a->deserialized_state == AUTOMOUNT_RUNNING) {
+
+ assert(a->pipe_fd >= 0);
+
+ if ((r = unit_watch_fd(UNIT(a), a->pipe_fd, EPOLLIN, &a->pipe_watch)) < 0)
+ return r;
+ }
+
+ automount_set_state(a, a->deserialized_state);
+ }
+
+ return 0;
+}
+
+static void automount_dump(Unit *u, FILE *f, const char *prefix) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ fprintf(f,
+ "%sAutomount State: %s\n"
+ "%sResult: %s\n"
+ "%sWhere: %s\n"
+ "%sDirectoryMode: %04o\n",
+ prefix, automount_state_to_string(a->state),
+ prefix, automount_result_to_string(a->result),
+ prefix, a->where,
+ prefix, a->directory_mode);
+}
+
+static void automount_enter_dead(Automount *a, AutomountResult f) {
+ assert(a);
+
+ if (f != AUTOMOUNT_SUCCESS)
+ a->result = f;
+
+ automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD);
+}
+
+static int open_dev_autofs(Manager *m) {
+ struct autofs_dev_ioctl param;
+
+ assert(m);
+
+ if (m->dev_autofs_fd >= 0)
+ return m->dev_autofs_fd;
+
+ label_fix("/dev/autofs", false, false);
+
+ if ((m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY)) < 0) {
+ log_error("Failed to open /dev/autofs: %s", strerror(errno));
+ return -errno;
+ }
+
+ init_autofs_dev_ioctl(&param);
+ if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, &param) < 0) {
+ close_nointr_nofail(m->dev_autofs_fd);
+ m->dev_autofs_fd = -1;
+ return -errno;
+ }
+
+ log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor);
+
+ return m->dev_autofs_fd;
+}
+
+static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) {
+ struct autofs_dev_ioctl *param;
+ size_t l;
+ int r;
+
+ assert(dev_autofs_fd >= 0);
+ assert(where);
+
+ l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1;
+
+ if (!(param = malloc(l)))
+ return -ENOMEM;
+
+ init_autofs_dev_ioctl(param);
+ param->size = l;
+ param->ioctlfd = -1;
+ param->openmount.devid = devid;
+ strcpy(param->path, where);
+
+ if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (param->ioctlfd < 0) {
+ r = -EIO;
+ goto finish;
+ }
+
+ fd_cloexec(param->ioctlfd, true);
+ r = param->ioctlfd;
+
+finish:
+ free(param);
+ return r;
+}
+
+static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) {
+ uint32_t major, minor;
+ struct autofs_dev_ioctl param;
+
+ assert(dev_autofs_fd >= 0);
+ assert(ioctl_fd >= 0);
+
+ init_autofs_dev_ioctl(&param);
+ param.ioctlfd = ioctl_fd;
+
+ if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, &param) < 0)
+ return -errno;
+
+ major = param.protover.version;
+
+ init_autofs_dev_ioctl(&param);
+ param.ioctlfd = ioctl_fd;
+
+ if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, &param) < 0)
+ return -errno;
+
+ minor = param.protosubver.sub_version;
+
+ log_debug("Autofs protocol version %i.%i", major, minor);
+ return 0;
+}
+
+static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, time_t sec) {
+ struct autofs_dev_ioctl param;
+
+ assert(dev_autofs_fd >= 0);
+ assert(ioctl_fd >= 0);
+
+ init_autofs_dev_ioctl(&param);
+ param.ioctlfd = ioctl_fd;
+ param.timeout.timeout = sec;
+
+ if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, &param) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) {
+ struct autofs_dev_ioctl param;
+
+ assert(dev_autofs_fd >= 0);
+ assert(ioctl_fd >= 0);
+
+ init_autofs_dev_ioctl(&param);
+ param.ioctlfd = ioctl_fd;
+
+ if (status) {
+ param.fail.token = token;
+ param.fail.status = status;
+ } else
+ param.ready.token = token;
+
+ if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, &param) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int automount_send_ready(Automount *a, int status) {
+ int ioctl_fd, r;
+ unsigned token;
+
+ assert(a);
+ assert(status <= 0);
+
+ if (set_isempty(a->tokens))
+ return 0;
+
+ if ((ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id)) < 0) {
+ r = ioctl_fd;
+ goto fail;
+ }
+
+ if (status)
+ log_debug("Sending failure: %s", strerror(-status));
+ else
+ log_debug("Sending success.");
+
+ r = 0;
+
+ /* Autofs thankfully does not hand out 0 as a token */
+ while ((token = PTR_TO_UINT(set_steal_first(a->tokens)))) {
+ int k;
+
+ /* Autofs fun fact II:
+ *
+ * if you pass a positive status code here, the kernel will
+ * freeze! Yay! */
+
+ if ((k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd,
+ ioctl_fd,
+ token,
+ status)) < 0)
+ r = k;
+ }
+
+fail:
+ if (ioctl_fd >= 0)
+ close_nointr_nofail(ioctl_fd);
+
+ return r;
+}
+
+static void automount_enter_waiting(Automount *a) {
+ int p[2] = { -1, -1 };
+ char name[32], options[128];
+ bool mounted = false;
+ int r, ioctl_fd = -1, dev_autofs_fd;
+ struct stat st;
+
+ assert(a);
+ assert(a->pipe_fd < 0);
+ assert(a->where);
+
+ if (a->tokens)
+ set_clear(a->tokens);
+
+ if ((dev_autofs_fd = open_dev_autofs(UNIT(a)->manager)) < 0) {
+ r = dev_autofs_fd;
+ goto fail;
+ }
+
+ /* We knowingly ignore the results of this call */
+ mkdir_p_label(a->where, 0555);
+
+ if (dir_is_empty(a->where) <= 0)
+ log_notice("%s: Directory %s to mount over is not empty, mounting anyway. (To see the over-mounted files, please manually mount the underlying file system to a secondary location.)", a->meta.id, a->where);
+
+ if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ snprintf(options, sizeof(options), "fd=%i,pgrp=%u,minproto=5,maxproto=5,direct", p[1], (unsigned) getpgrp());
+ char_array_0(options);
+
+ snprintf(name, sizeof(name), "systemd-%u", (unsigned) getpid());
+ char_array_0(name);
+
+ if (mount(name, a->where, "autofs", 0, options) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ mounted = true;
+
+ close_nointr_nofail(p[1]);
+ p[1] = -1;
+
+ if (stat(a->where, &st) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if ((ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev)) < 0) {
+ r = ioctl_fd;
+ goto fail;
+ }
+
+ if ((r = autofs_protocol(dev_autofs_fd, ioctl_fd)) < 0)
+ goto fail;
+
+ if ((r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, 300)) < 0)
+ goto fail;
+
+ /* Autofs fun fact:
+ *
+ * Unless we close the ioctl fd here, for some weird reason
+ * the direct mount will not receive events from the
+ * kernel. */
+
+ close_nointr_nofail(ioctl_fd);
+ ioctl_fd = -1;
+
+ if ((r = unit_watch_fd(UNIT(a), p[0], EPOLLIN, &a->pipe_watch)) < 0)
+ goto fail;
+
+ a->pipe_fd = p[0];
+ a->dev_id = st.st_dev;
+
+ automount_set_state(a, AUTOMOUNT_WAITING);
+
+ return;
+
+fail:
+ assert_se(close_pipe(p) == 0);
+
+ if (ioctl_fd >= 0)
+ close_nointr_nofail(ioctl_fd);
+
+ if (mounted)
+ repeat_unmout(a->where);
+
+ log_error("Failed to initialize automounter: %s", strerror(-r));
+ automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES);
+}
+
+static void automount_enter_runnning(Automount *a) {
+ int r;
+ struct stat st;
+ DBusError error;
+
+ assert(a);
+ assert(UNIT_DEREF(a->mount));
+
+ dbus_error_init(&error);
+
+ /* We don't take mount requests anymore if we are supposed to
+ * shut down anyway */
+ if (unit_pending_inactive(UNIT(a))) {
+ log_debug("Suppressing automount request on %s since unit stop is scheduled.", UNIT(a)->id);
+ automount_send_ready(a, -EHOSTDOWN);
+ return;
+ }
+
+ mkdir_p_label(a->where, a->directory_mode);
+
+ /* Before we do anything, let's see if somebody is playing games with us? */
+ if (lstat(a->where, &st) < 0) {
+ log_warning("%s failed to stat automount point: %m", UNIT(a)->id);
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id)
+ log_info("%s's automount point already active?", UNIT(a)->id);
+ else if ((r = manager_add_job(UNIT(a)->manager, JOB_START, UNIT_DEREF(a->mount), JOB_REPLACE, true, &error, NULL)) < 0) {
+ log_warning("%s failed to queue mount startup job: %s", UNIT(a)->id, bus_error(&error, r));
+ goto fail;
+ }
+
+ automount_set_state(a, AUTOMOUNT_RUNNING);
+ return;
+
+fail:
+ automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES);
+ dbus_error_free(&error);
+}
+
+static int automount_start(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED);
+
+ if (path_is_mount_point(a->where, false)) {
+ log_error("Path %s is already a mount point, refusing start for %s", a->where, u->id);
+ return -EEXIST;
+ }
+
+ if (UNIT_DEREF(a->mount)->load_state != UNIT_LOADED)
+ return -ENOENT;
+
+ a->result = AUTOMOUNT_SUCCESS;
+ automount_enter_waiting(a);
+ return 0;
+}
+
+static int automount_stop(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING);
+
+ automount_enter_dead(a, AUTOMOUNT_SUCCESS);
+ return 0;
+}
+
+static int automount_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Automount *a = AUTOMOUNT(u);
+ void *p;
+ Iterator i;
+
+ assert(a);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", automount_state_to_string(a->state));
+ unit_serialize_item(u, f, "result", automount_result_to_string(a->result));
+ unit_serialize_item_format(u, f, "dev-id", "%u", (unsigned) a->dev_id);
+
+ SET_FOREACH(p, a->tokens, i)
+ unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p));
+
+ if (a->pipe_fd >= 0) {
+ int copy;
+
+ if ((copy = fdset_put_dup(fds, a->pipe_fd)) < 0)
+ return copy;
+
+ unit_serialize_item_format(u, f, "pipe-fd", "%i", copy);
+ }
+
+ return 0;
+}
+
+static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Automount *a = AUTOMOUNT(u);
+ int r;
+
+ assert(a);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ AutomountState state;
+
+ if ((state = automount_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ a->deserialized_state = state;
+ } else if (streq(key, "result")) {
+ AutomountResult f;
+
+ f = automount_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != AUTOMOUNT_SUCCESS)
+ a->result = f;
+
+ } else if (streq(key, "dev-id")) {
+ unsigned d;
+
+ if (safe_atou(value, &d) < 0)
+ log_debug("Failed to parse dev-id value %s", value);
+ else
+ a->dev_id = (unsigned) d;
+ } else if (streq(key, "token")) {
+ unsigned token;
+
+ if (safe_atou(value, &token) < 0)
+ log_debug("Failed to parse token value %s", value);
+ else {
+ if (!a->tokens)
+ if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ if ((r = set_put(a->tokens, UINT_TO_PTR(token))) < 0)
+ return r;
+ }
+ } else if (streq(key, "pipe-fd")) {
+ int fd;
+
+ if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse pipe-fd value %s", value);
+ else {
+ if (a->pipe_fd >= 0)
+ close_nointr_nofail(a->pipe_fd);
+
+ a->pipe_fd = fdset_remove(fds, fd);
+ }
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState automount_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[AUTOMOUNT(u)->state];
+}
+
+static const char *automount_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return automount_state_to_string(AUTOMOUNT(u)->state);
+}
+
+static bool automount_check_gc(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ if (!UNIT_DEREF(a->mount))
+ return false;
+
+ return UNIT_VTABLE(UNIT_DEREF(a->mount))->check_gc(UNIT_DEREF(a->mount));
+}
+
+static void automount_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
+ Automount *a = AUTOMOUNT(u);
+ union autofs_v5_packet_union packet;
+ ssize_t l;
+ int r;
+
+ assert(a);
+ assert(fd == a->pipe_fd);
+
+ if (events != EPOLLIN) {
+ log_error("Got invalid poll event on pipe.");
+ goto fail;
+ }
+
+ if ((l = loop_read(a->pipe_fd, &packet, sizeof(packet), true)) != sizeof(packet)) {
+ log_error("Invalid read from pipe: %s", l < 0 ? strerror(-l) : "short read");
+ goto fail;
+ }
+
+ switch (packet.hdr.type) {
+
+ case autofs_ptype_missing_direct:
+
+ if (packet.v5_packet.pid > 0) {
+ char *p = NULL;
+
+ get_process_comm(packet.v5_packet.pid, &p);
+ log_debug("Got direct mount request on %s, triggered by %lu (%s)",
+ a->where, (unsigned long) packet.v5_packet.pid, strna(p));
+ free(p);
+
+ } else
+ log_debug("Got direct mount request on %s", a->where);
+
+ if (!a->tokens)
+ if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) {
+ log_error("Failed to allocate token set.");
+ goto fail;
+ }
+
+ if ((r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token))) < 0) {
+ log_error("Failed to remember token: %s", strerror(-r));
+ goto fail;
+ }
+
+ automount_enter_runnning(a);
+ break;
+
+ default:
+ log_error("Received unknown automount request %i", packet.hdr.type);
+ break;
+ }
+
+ return;
+
+fail:
+ automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES);
+}
+
+static void automount_shutdown(Manager *m) {
+ assert(m);
+
+ if (m->dev_autofs_fd >= 0)
+ close_nointr_nofail(m->dev_autofs_fd);
+}
+
+static void automount_reset_failed(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ if (a->state == AUTOMOUNT_FAILED)
+ automount_set_state(a, AUTOMOUNT_DEAD);
+
+ a->result = AUTOMOUNT_SUCCESS;
+}
+
+static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
+ [AUTOMOUNT_DEAD] = "dead",
+ [AUTOMOUNT_WAITING] = "waiting",
+ [AUTOMOUNT_RUNNING] = "running",
+ [AUTOMOUNT_FAILED] = "failed"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState);
+
+static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = {
+ [AUTOMOUNT_SUCCESS] = "success",
+ [AUTOMOUNT_FAILURE_RESOURCES] = "resources"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult);
+
+const UnitVTable automount_vtable = {
+ .object_size = sizeof(Automount),
+ .sections =
+ "Unit\0"
+ "Automount\0"
+ "Install\0",
+
+ .no_alias = true,
+ .no_instances = true,
+
+ .init = automount_init,
+ .load = automount_load,
+ .done = automount_done,
+
+ .coldplug = automount_coldplug,
+
+ .dump = automount_dump,
+
+ .start = automount_start,
+ .stop = automount_stop,
+
+ .serialize = automount_serialize,
+ .deserialize_item = automount_deserialize_item,
+
+ .active_state = automount_active_state,
+ .sub_state_to_string = automount_sub_state_to_string,
+
+ .check_gc = automount_check_gc,
+
+ .fd_event = automount_fd_event,
+
+ .reset_failed = automount_reset_failed,
+
+ .bus_interface = "org.freedesktop.systemd1.Automount",
+ .bus_message_handler = bus_automount_message_handler,
+ .bus_invalidating_properties = bus_automount_invalidating_properties,
+
+ .shutdown = automount_shutdown,
+
+ .status_message_formats = {
+ .finished_start_job = {
+ [JOB_DONE] = "Set up automount %s.",
+ [JOB_FAILED] = "Failed to set up automount %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Unset automount %s.",
+ [JOB_FAILED] = "Failed to unset automount %s.",
+ },
+ },
+};
diff --git a/src/core/automount.h b/src/core/automount.h
new file mode 100644
index 0000000000..3d5736d1cb
--- /dev/null
+++ b/src/core/automount.h
@@ -0,0 +1,73 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Automount Automount;
+
+#include "unit.h"
+
+typedef enum AutomountState {
+ AUTOMOUNT_DEAD,
+ AUTOMOUNT_WAITING,
+ AUTOMOUNT_RUNNING,
+ AUTOMOUNT_FAILED,
+ _AUTOMOUNT_STATE_MAX,
+ _AUTOMOUNT_STATE_INVALID = -1
+} AutomountState;
+
+typedef enum AutomountResult {
+ AUTOMOUNT_SUCCESS,
+ AUTOMOUNT_FAILURE_RESOURCES,
+ _AUTOMOUNT_RESULT_MAX,
+ _AUTOMOUNT_RESULT_INVALID = -1
+} AutomountResult;
+
+struct Automount {
+ Unit meta;
+
+ AutomountState state, deserialized_state;
+
+ char *where;
+
+ UnitRef mount;
+
+ int pipe_fd;
+ mode_t directory_mode;
+ Watch pipe_watch;
+ dev_t dev_id;
+
+ Set *tokens;
+
+ AutomountResult result;
+};
+
+extern const UnitVTable automount_vtable;
+
+int automount_send_ready(Automount *a, int status);
+
+int automount_add_one_mount_link(Automount *a, Mount *m);
+
+const char* automount_state_to_string(AutomountState i);
+AutomountState automount_state_from_string(const char *s);
+
+const char* automount_result_to_string(AutomountResult i);
+AutomountResult automount_result_from_string(const char *s);
diff --git a/src/core/build.h b/src/core/build.h
new file mode 100644
index 0000000000..4513a0bad7
--- /dev/null
+++ b/src/core/build.h
@@ -0,0 +1,84 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_PAM
+#define _PAM_FEATURE_ "+PAM"
+#else
+#define _PAM_FEATURE_ "-PAM"
+#endif
+
+#ifdef HAVE_LIBWRAP
+#define _LIBWRAP_FEATURE_ "+LIBWRAP"
+#else
+#define _LIBWRAP_FEATURE_ "-LIBWRAP"
+#endif
+
+#ifdef HAVE_AUDIT
+#define _AUDIT_FEATURE_ "+AUDIT"
+#else
+#define _AUDIT_FEATURE_ "-AUDIT"
+#endif
+
+#ifdef HAVE_SELINUX
+#define _SELINUX_FEATURE_ "+SELINUX"
+#else
+#define _SELINUX_FEATURE_ "-SELINUX"
+#endif
+
+#ifdef HAVE_IMA
+#define _IMA_FEATURE_ "+IMA"
+#else
+#define _IMA_FEATURE_ "-IMA"
+#endif
+
+#ifdef HAVE_SYSV_COMPAT
+#define _SYSVINIT_FEATURE_ "+SYSVINIT"
+#else
+#define _SYSVINIT_FEATURE_ "-SYSVINIT"
+#endif
+
+#ifdef HAVE_LIBCRYPTSETUP
+#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP"
+#else
+#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP"
+#endif
+
+#ifdef HAVE_GCRYPT
+#define _GCRYPT_FEATURE_ "+GCRYPT"
+#else
+#define _GCRYPT_FEATURE_ "-GCRYPT"
+#endif
+
+#ifdef HAVE_ACL
+#define _ACL_FEATURE_ "+ACL"
+#else
+#define _ACL_FEATURE_ "-ACL"
+#endif
+
+#ifdef HAVE_XZ
+#define _XZ_FEATURE_ "+XZ"
+#else
+#define _XZ_FEATURE_ "-XZ"
+#endif
+
+#define SYSTEMD_FEATURES _PAM_FEATURE_ " " _LIBWRAP_FEATURE_ " " _AUDIT_FEATURE_ " " _SELINUX_FEATURE_ " " _IMA_FEATURE_ " " _SYSVINIT_FEATURE_ " " _LIBCRYPTSETUP_FEATURE_ " " _GCRYPT_FEATURE_ " " _ACL_FEATURE_ " " _XZ_FEATURE_
diff --git a/src/core/bus-errors.h b/src/core/bus-errors.h
new file mode 100644
index 0000000000..04c1b2849d
--- /dev/null
+++ b/src/core/bus-errors.h
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <dbus/dbus.h>
+
+#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit"
+#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob"
+#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed"
+#define BUS_ERROR_INVALID_PATH "org.freedesktop.systemd1.InvalidPath"
+#define BUS_ERROR_INVALID_NAME "org.freedesktop.systemd1.InvalidName"
+#define BUS_ERROR_UNIT_TYPE_MISMATCH "org.freedesktop.systemd1.UnitTypeMismatch"
+#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists"
+#define BUS_ERROR_NOT_SUPPORTED "org.freedesktop.systemd1.NotSupported"
+#define BUS_ERROR_INVALID_JOB_MODE "org.freedesktop.systemd1.InvalidJobMode"
+#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency"
+#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation"
+#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed"
+#define BUS_ERROR_MASKED "org.freedesktop.systemd1.Masked"
+#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable"
+#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive"
+#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting"
+#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic"
+#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown"
+#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess"
+
+static inline const char *bus_error(const DBusError *e, int r) {
+ if (e && e->message)
+ return e->message;
+
+ if (r >= 0)
+ return strerror(r);
+
+ return strerror(-r);
+}
diff --git a/src/core/cgroup-attr.c b/src/core/cgroup-attr.c
new file mode 100644
index 0000000000..71af09cf87
--- /dev/null
+++ b/src/core/cgroup-attr.c
@@ -0,0 +1,102 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "cgroup-attr.h"
+#include "cgroup-util.h"
+#include "list.h"
+
+int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
+ int r;
+ char *path = NULL;
+ char *v = NULL;
+
+ assert(a);
+
+ b = cgroup_bonding_find_list(b, a->controller);
+ if (!b)
+ return 0;
+
+ if (a->map_callback) {
+ r = a->map_callback(a->controller, a->name, a->value, &v);
+ if (r < 0)
+ return r;
+ }
+
+ r = cg_get_path(a->controller, b->path, a->name, &path);
+ if (r < 0) {
+ free(v);
+ return r;
+ }
+
+ r = write_one_line_file(path, v ? v : a->value);
+ if (r < 0)
+ log_warning("Failed to write '%s' to %s: %s", v ? v : a->value, path, strerror(-r));
+
+ free(path);
+ free(v);
+
+ return r;
+}
+
+int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) {
+ CGroupAttribute *a;
+ int r = 0;
+
+ LIST_FOREACH(by_unit, a, first) {
+ int k;
+
+ k = cgroup_attribute_apply(a, b);
+ if (r == 0)
+ r = k;
+ }
+
+ return r;
+}
+
+CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name) {
+ CGroupAttribute *a;
+
+ assert(controller);
+ assert(name);
+
+ LIST_FOREACH(by_unit, a, first)
+ if (streq(a->controller, controller) &&
+ streq(a->name, name))
+ return a;
+
+ return NULL;
+}
+
+static void cgroup_attribute_free(CGroupAttribute *a) {
+ assert(a);
+
+ free(a->controller);
+ free(a->name);
+ free(a->value);
+ free(a);
+}
+
+void cgroup_attribute_free_list(CGroupAttribute *first) {
+ CGroupAttribute *a, *n;
+
+ LIST_FOREACH_SAFE(by_unit, a, n, first)
+ cgroup_attribute_free(a);
+}
diff --git a/src/core/cgroup-attr.h b/src/core/cgroup-attr.h
new file mode 100644
index 0000000000..2b754eac40
--- /dev/null
+++ b/src/core/cgroup-attr.h
@@ -0,0 +1,46 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct CGroupAttribute CGroupAttribute;
+
+typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret);
+
+#include "unit.h"
+#include "cgroup.h"
+
+struct CGroupAttribute {
+ char *controller;
+ char *name;
+ char *value;
+
+ CGroupAttributeMapCallback map_callback;
+
+ LIST_FIELDS(CGroupAttribute, by_unit);
+};
+
+int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b);
+int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b);
+
+CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name);
+
+void cgroup_attribute_free_list(CGroupAttribute *first);
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
new file mode 100644
index 0000000000..8fc1731485
--- /dev/null
+++ b/src/core/cgroup.c
@@ -0,0 +1,598 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/mount.h>
+#include <fcntl.h>
+
+#include "cgroup.h"
+#include "cgroup-util.h"
+#include "log.h"
+#include "strv.h"
+#include "path-util.h"
+
+int cgroup_bonding_realize(CGroupBonding *b) {
+ int r;
+
+ assert(b);
+ assert(b->path);
+ assert(b->controller);
+
+ r = cg_create(b->controller, b->path);
+ if (r < 0) {
+ log_warning("Failed to create cgroup %s:%s: %s", b->controller, b->path, strerror(-r));
+ return r;
+ }
+
+ b->realized = true;
+
+ return 0;
+}
+
+int cgroup_bonding_realize_list(CGroupBonding *first) {
+ CGroupBonding *b;
+ int r;
+
+ LIST_FOREACH(by_unit, b, first)
+ if ((r = cgroup_bonding_realize(b)) < 0 && b->essential)
+ return r;
+
+ return 0;
+}
+
+void cgroup_bonding_free(CGroupBonding *b, bool trim) {
+ assert(b);
+
+ if (b->unit) {
+ CGroupBonding *f;
+
+ LIST_REMOVE(CGroupBonding, by_unit, b->unit->cgroup_bondings, b);
+
+ if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) {
+ assert_se(f = hashmap_get(b->unit->manager->cgroup_bondings, b->path));
+ LIST_REMOVE(CGroupBonding, by_path, f, b);
+
+ if (f)
+ hashmap_replace(b->unit->manager->cgroup_bondings, b->path, f);
+ else
+ hashmap_remove(b->unit->manager->cgroup_bondings, b->path);
+ }
+ }
+
+ if (b->realized && b->ours && trim)
+ cg_trim(b->controller, b->path, false);
+
+ free(b->controller);
+ free(b->path);
+ free(b);
+}
+
+void cgroup_bonding_free_list(CGroupBonding *first, bool remove_or_trim) {
+ CGroupBonding *b, *n;
+
+ LIST_FOREACH_SAFE(by_unit, b, n, first)
+ cgroup_bonding_free(b, remove_or_trim);
+}
+
+void cgroup_bonding_trim(CGroupBonding *b, bool delete_root) {
+ assert(b);
+
+ if (b->realized && b->ours)
+ cg_trim(b->controller, b->path, delete_root);
+}
+
+void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root) {
+ CGroupBonding *b;
+
+ LIST_FOREACH(by_unit, b, first)
+ cgroup_bonding_trim(b, delete_root);
+}
+
+
+int cgroup_bonding_install(CGroupBonding *b, pid_t pid, const char *cgroup_suffix) {
+ char *p = NULL;
+ const char *path;
+ int r;
+
+ assert(b);
+ assert(pid >= 0);
+
+ if (cgroup_suffix) {
+ p = strjoin(b->path, "/", cgroup_suffix, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ path = p;
+ } else
+ path = b->path;
+
+ r = cg_create_and_attach(b->controller, path, pid);
+ free(p);
+
+ if (r < 0)
+ return r;
+
+ b->realized = true;
+ return 0;
+}
+
+int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid, const char *cgroup_suffix) {
+ CGroupBonding *b;
+ int r;
+
+ LIST_FOREACH(by_unit, b, first) {
+ r = cgroup_bonding_install(b, pid, cgroup_suffix);
+ if (r < 0 && b->essential)
+ return r;
+ }
+
+ return 0;
+}
+
+int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid) {
+ assert(b);
+
+ if (!b->realized)
+ return -EINVAL;
+
+ return cg_set_group_access(b->controller, b->path, mode, uid, gid);
+}
+
+int cgroup_bonding_set_group_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid) {
+ CGroupBonding *b;
+ int r;
+
+ LIST_FOREACH(by_unit, b, first) {
+ r = cgroup_bonding_set_group_access(b, mode, uid, gid);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky) {
+ assert(b);
+
+ if (!b->realized)
+ return -EINVAL;
+
+ return cg_set_task_access(b->controller, b->path, mode, uid, gid, sticky);
+}
+
+int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid, int sticky) {
+ CGroupBonding *b;
+ int r;
+
+ LIST_FOREACH(by_unit, b, first) {
+ r = cgroup_bonding_set_task_access(b, mode, uid, gid, sticky);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, bool rem, Set *s, const char *cgroup_suffix) {
+ char *p = NULL;
+ const char *path;
+ int r;
+
+ assert(b);
+ assert(sig >= 0);
+
+ /* Don't kill cgroups that aren't ours */
+ if (!b->ours)
+ return 0;
+
+ if (cgroup_suffix) {
+ p = strjoin(b->path, "/", cgroup_suffix, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ path = p;
+ } else
+ path = b->path;
+
+ r = cg_kill_recursive(b->controller, path, sig, sigcont, true, rem, s);
+ free(p);
+
+ return r;
+}
+
+int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, bool rem, Set *s, const char *cgroup_suffix) {
+ CGroupBonding *b;
+ Set *allocated_set = NULL;
+ int ret = -EAGAIN, r;
+
+ if (!first)
+ return 0;
+
+ if (!s)
+ if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ LIST_FOREACH(by_unit, b, first) {
+ r = cgroup_bonding_kill(b, sig, sigcont, rem, s, cgroup_suffix);
+ if (r < 0) {
+ if (r == -EAGAIN || r == -ESRCH)
+ continue;
+
+ ret = r;
+ goto finish;
+ }
+
+ if (ret < 0 || r > 0)
+ ret = r;
+ }
+
+finish:
+ if (allocated_set)
+ set_free(allocated_set);
+
+ return ret;
+}
+
+/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we
+ * cannot know */
+int cgroup_bonding_is_empty(CGroupBonding *b) {
+ int r;
+
+ assert(b);
+
+ if ((r = cg_is_empty_recursive(b->controller, b->path, true)) < 0)
+ return r;
+
+ /* If it is empty it is empty */
+ if (r > 0)
+ return 1;
+
+ /* It's not only us using this cgroup, so we just don't know */
+ return b->ours ? 0 : -EAGAIN;
+}
+
+int cgroup_bonding_is_empty_list(CGroupBonding *first) {
+ CGroupBonding *b;
+
+ LIST_FOREACH(by_unit, b, first) {
+ int r;
+
+ if ((r = cgroup_bonding_is_empty(b)) < 0) {
+ /* If this returned -EAGAIN, then we don't know if the
+ * group is empty, so let's see if another group can
+ * tell us */
+
+ if (r != -EAGAIN)
+ return r;
+ } else
+ return r;
+ }
+
+ return -EAGAIN;
+}
+
+int manager_setup_cgroup(Manager *m) {
+ char *current = NULL, *path = NULL;
+ int r;
+ char suffix[32];
+
+ assert(m);
+
+ /* 0. Be nice to Ingo Molnar #628004 */
+ if (path_is_mount_point("/sys/fs/cgroup/systemd", false) <= 0) {
+ log_warning("No control group support available, not creating root group.");
+ return 0;
+ }
+
+ /* 1. Determine hierarchy */
+ r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 0, &current);
+ if (r < 0) {
+ log_error("Cannot determine cgroup we are running in: %s", strerror(-r));
+ goto finish;
+ }
+
+ if (m->running_as == SYSTEMD_SYSTEM)
+ strcpy(suffix, "/system");
+ else {
+ snprintf(suffix, sizeof(suffix), "/systemd-%lu", (unsigned long) getpid());
+ char_array_0(suffix);
+ }
+
+ free(m->cgroup_hierarchy);
+ if (endswith(current, suffix)) {
+ /* We probably got reexecuted and can continue to use our root cgroup */
+ m->cgroup_hierarchy = current;
+ current = NULL;
+
+ } else {
+ /* We need a new root cgroup */
+ m->cgroup_hierarchy = NULL;
+ if (asprintf(&m->cgroup_hierarchy, "%s%s", streq(current, "/") ? "" : current, suffix) < 0) {
+ r = log_oom();
+ goto finish;
+ }
+ }
+
+ /* 2. Show data */
+ r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, NULL, &path);
+ if (r < 0) {
+ log_error("Cannot find cgroup mount point: %s", strerror(-r));
+ goto finish;
+ }
+
+ log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path);
+
+ /* 3. Install agent */
+ r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH);
+ if (r < 0)
+ log_warning("Failed to install release agent, ignoring: %s", strerror(-r));
+ else if (r > 0)
+ log_debug("Installed release agent.");
+ else
+ log_debug("Release agent already installed.");
+
+ /* 4. Realize the group */
+ r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, 0);
+ if (r < 0) {
+ log_error("Failed to create root cgroup hierarchy: %s", strerror(-r));
+ goto finish;
+ }
+
+ /* 5. And pin it, so that it cannot be unmounted */
+ if (m->pin_cgroupfs_fd >= 0)
+ close_nointr_nofail(m->pin_cgroupfs_fd);
+
+ m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK);
+ if (r < 0) {
+ log_error("Failed to open pin file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ log_debug("Created root group.");
+
+ cg_shorten_controllers(m->default_controllers);
+
+finish:
+ free(current);
+ free(path);
+
+ return r;
+}
+
+void manager_shutdown_cgroup(Manager *m, bool delete) {
+ assert(m);
+
+ if (delete && m->cgroup_hierarchy)
+ cg_delete(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy);
+
+ if (m->pin_cgroupfs_fd >= 0) {
+ close_nointr_nofail(m->pin_cgroupfs_fd);
+ m->pin_cgroupfs_fd = -1;
+ }
+
+ free(m->cgroup_hierarchy);
+ m->cgroup_hierarchy = NULL;
+}
+
+int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding) {
+ CGroupBonding *b;
+ char *p;
+
+ assert(m);
+ assert(cgroup);
+ assert(bonding);
+
+ b = hashmap_get(m->cgroup_bondings, cgroup);
+ if (b) {
+ *bonding = b;
+ return 1;
+ }
+
+ p = strdup(cgroup);
+ if (!p)
+ return -ENOMEM;
+
+ for (;;) {
+ char *e;
+
+ e = strrchr(p, '/');
+ if (!e || e == p) {
+ free(p);
+ *bonding = NULL;
+ return 0;
+ }
+
+ *e = 0;
+
+ b = hashmap_get(m->cgroup_bondings, p);
+ if (b) {
+ free(p);
+ *bonding = b;
+ return 1;
+ }
+ }
+}
+
+int cgroup_notify_empty(Manager *m, const char *group) {
+ CGroupBonding *l, *b;
+ int r;
+
+ assert(m);
+ assert(group);
+
+ r = cgroup_bonding_get(m, group, &l);
+ if (r <= 0)
+ return r;
+
+ LIST_FOREACH(by_path, b, l) {
+ int t;
+
+ if (!b->unit)
+ continue;
+
+ t = cgroup_bonding_is_empty_list(b);
+ if (t < 0) {
+
+ /* If we don't know, we don't know */
+ if (t != -EAGAIN)
+ log_warning("Failed to check whether cgroup is empty: %s", strerror(errno));
+
+ continue;
+ }
+
+ if (t > 0) {
+ /* If it is empty, let's delete it */
+ cgroup_bonding_trim_list(b->unit->cgroup_bondings, true);
+
+ if (UNIT_VTABLE(b->unit)->cgroup_notify_empty)
+ UNIT_VTABLE(b->unit)->cgroup_notify_empty(b->unit);
+ }
+ }
+
+ return 0;
+}
+
+Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) {
+ CGroupBonding *l, *b;
+ char *group = NULL;
+
+ assert(m);
+
+ if (pid <= 1)
+ return NULL;
+
+ if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &group) < 0)
+ return NULL;
+
+ l = hashmap_get(m->cgroup_bondings, group);
+
+ if (!l) {
+ char *slash;
+
+ while ((slash = strrchr(group, '/'))) {
+ if (slash == group)
+ break;
+
+ *slash = 0;
+
+ if ((l = hashmap_get(m->cgroup_bondings, group)))
+ break;
+ }
+ }
+
+ free(group);
+
+ LIST_FOREACH(by_path, b, l) {
+
+ if (!b->unit)
+ continue;
+
+ if (b->ours)
+ return b->unit;
+ }
+
+ return NULL;
+}
+
+CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) {
+ CGroupBonding *b;
+
+ assert(controller);
+
+ LIST_FOREACH(by_unit, b, first)
+ if (streq(b->controller, controller))
+ return b;
+
+ return NULL;
+}
+
+char *cgroup_bonding_to_string(CGroupBonding *b) {
+ char *r;
+
+ assert(b);
+
+ if (asprintf(&r, "%s:%s", b->controller, b->path) < 0)
+ return NULL;
+
+ return r;
+}
+
+pid_t cgroup_bonding_search_main_pid(CGroupBonding *b) {
+ FILE *f;
+ pid_t pid = 0, npid, mypid;
+
+ assert(b);
+
+ if (!b->ours)
+ return 0;
+
+ if (cg_enumerate_processes(b->controller, b->path, &f) < 0)
+ return 0;
+
+ mypid = getpid();
+
+ while (cg_read_pid(f, &npid) > 0) {
+ pid_t ppid;
+
+ if (npid == pid)
+ continue;
+
+ /* Ignore processes that aren't our kids */
+ if (get_parent_of_pid(npid, &ppid) >= 0 && ppid != mypid)
+ continue;
+
+ if (pid != 0) {
+ /* Dang, there's more than one daemonized PID
+ in this group, so we don't know what process
+ is the main process. */
+ pid = 0;
+ break;
+ }
+
+ pid = npid;
+ }
+
+ fclose(f);
+
+ return pid;
+}
+
+pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *first) {
+ CGroupBonding *b;
+ pid_t pid;
+
+ /* Try to find a main pid from this cgroup, but checking if
+ * there's only one PID in the cgroup and returning it. Later
+ * on we might want to add additional, smarter heuristics
+ * here. */
+
+ LIST_FOREACH(by_unit, b, first)
+ if ((pid = cgroup_bonding_search_main_pid(b)) != 0)
+ return pid;
+
+ return 0;
+
+}
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
new file mode 100644
index 0000000000..229da52ba4
--- /dev/null
+++ b/src/core/cgroup.h
@@ -0,0 +1,91 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct CGroupBonding CGroupBonding;
+
+#include "unit.h"
+
+/* Binds a cgroup to a name */
+struct CGroupBonding {
+ char *controller;
+ char *path;
+
+ Unit *unit;
+
+ /* For the Unit::cgroup_bondings list */
+ LIST_FIELDS(CGroupBonding, by_unit);
+
+ /* For the Manager::cgroup_bondings hashmap */
+ LIST_FIELDS(CGroupBonding, by_path);
+
+ /* When shutting down, remove cgroup? Are our own tasks the
+ * only ones in this group?*/
+ bool ours:1;
+
+ /* If we cannot create this group, or add a process to it, is this fatal? */
+ bool essential:1;
+
+ /* This cgroup is realized */
+ bool realized:1;
+};
+
+int cgroup_bonding_realize(CGroupBonding *b);
+int cgroup_bonding_realize_list(CGroupBonding *first);
+
+void cgroup_bonding_free(CGroupBonding *b, bool trim);
+void cgroup_bonding_free_list(CGroupBonding *first, bool trim);
+
+int cgroup_bonding_install(CGroupBonding *b, pid_t pid, const char *suffix);
+int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid, const char *suffix);
+
+int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid);
+int cgroup_bonding_set_group_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid);
+
+int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky);
+int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky);
+
+int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, bool rem, Set *s, const char *suffix);
+int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, bool rem, Set *s, const char *suffix);
+
+void cgroup_bonding_trim(CGroupBonding *first, bool delete_root);
+void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root);
+
+int cgroup_bonding_is_empty(CGroupBonding *b);
+int cgroup_bonding_is_empty_list(CGroupBonding *first);
+
+CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller);
+
+char *cgroup_bonding_to_string(CGroupBonding *b);
+
+pid_t cgroup_bonding_search_main_pid(CGroupBonding *b);
+pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *b);
+
+#include "manager.h"
+
+int manager_setup_cgroup(Manager *m);
+void manager_shutdown_cgroup(Manager *m, bool delete);
+
+int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding);
+int cgroup_notify_empty(Manager *m, const char *group);
+
+Unit* cgroup_unit_by_pid(Manager *m, pid_t pid);
diff --git a/src/core/condition.c b/src/core/condition.c
new file mode 100644
index 0000000000..32a37ccad6
--- /dev/null
+++ b/src/core/condition.c
@@ -0,0 +1,370 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/capability.h>
+#include <sys/statvfs.h>
+#include <fnmatch.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include <systemd/sd-id128.h>
+#include "util.h"
+#include "condition.h"
+#include "virt.h"
+#include "path-util.h"
+
+Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
+ Condition *c;
+
+ assert(type < _CONDITION_TYPE_MAX);
+
+ c = new0(Condition, 1);
+ if (!c)
+ return NULL;
+
+ c->type = type;
+ c->trigger = trigger;
+ c->negate = negate;
+
+ if (parameter) {
+ c->parameter = strdup(parameter);
+ if (!c->parameter) {
+ free(c);
+ return NULL;
+ }
+ }
+
+ return c;
+}
+
+void condition_free(Condition *c) {
+ assert(c);
+
+ free(c->parameter);
+ free(c);
+}
+
+void condition_free_list(Condition *first) {
+ Condition *c, *n;
+
+ LIST_FOREACH_SAFE(conditions, c, n, first)
+ condition_free(c);
+}
+
+static bool test_kernel_command_line(const char *parameter) {
+ char *line, *w, *state, *word = NULL;
+ bool equal;
+ int r;
+ size_t l, pl;
+ bool found = false;
+
+ assert(parameter);
+
+ if (detect_container(NULL) > 0)
+ return false;
+
+ r = read_one_line_file("/proc/cmdline", &line);
+ if (r < 0) {
+ log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+ return false;
+ }
+
+ equal = !!strchr(parameter, '=');
+ pl = strlen(parameter);
+
+ FOREACH_WORD_QUOTED(w, l, line, state) {
+
+ free(word);
+ word = strndup(w, l);
+ if (!word)
+ break;
+
+ if (equal) {
+ if (streq(word, parameter)) {
+ found = true;
+ break;
+ }
+ } else {
+ if (startswith(word, parameter) && (word[pl] == '=' || word[pl] == 0)) {
+ found = true;
+ break;
+ }
+ }
+
+ }
+
+ free(word);
+ free(line);
+
+ return found;
+}
+
+static bool test_virtualization(const char *parameter) {
+ int b;
+ Virtualization v;
+ const char *id;
+
+ assert(parameter);
+
+ v = detect_virtualization(&id);
+ if (v < 0) {
+ log_warning("Failed to detect virtualization, ignoring: %s", strerror(-v));
+ return false;
+ }
+
+ /* First, compare with yes/no */
+ b = parse_boolean(parameter);
+
+ if (v > 0 && b > 0)
+ return true;
+
+ if (v == 0 && b == 0)
+ return true;
+
+ /* Then, compare categorization */
+ if (v == VIRTUALIZATION_VM && streq(parameter, "vm"))
+ return true;
+
+ if (v == VIRTUALIZATION_CONTAINER && streq(parameter, "container"))
+ return true;
+
+ /* Finally compare id */
+ return v > 0 && streq(parameter, id);
+}
+
+static bool test_security(const char *parameter) {
+#ifdef HAVE_SELINUX
+ if (streq(parameter, "selinux"))
+ return is_selinux_enabled() > 0;
+#endif
+ return false;
+}
+
+static bool test_capability(const char *parameter) {
+ cap_value_t value;
+ FILE *f;
+ char line[LINE_MAX];
+ unsigned long long capabilities = (unsigned long long) -1;
+
+ /* If it's an invalid capability, we don't have it */
+
+ if (cap_from_name(parameter, &value) < 0)
+ return false;
+
+ /* If it's a valid capability we default to assume
+ * that we have it */
+
+ f = fopen("/proc/self/status", "re");
+ if (!f)
+ return true;
+
+ while (fgets(line, sizeof(line), f)) {
+ truncate_nl(line);
+
+ if (startswith(line, "CapBnd:")) {
+ (void) sscanf(line+7, "%llx", &capabilities);
+ break;
+ }
+ }
+
+ fclose(f);
+
+ return !!(capabilities & (1ULL << value));
+}
+
+static bool test_host(const char *parameter) {
+ sd_id128_t x, y;
+ char *h;
+ int r;
+ bool b;
+
+ if (sd_id128_from_string(parameter, &x) >= 0) {
+
+ r = sd_id128_get_machine(&y);
+ if (r < 0)
+ return false;
+
+ return sd_id128_equal(x, y);
+ }
+
+ h = gethostname_malloc();
+ if (!h)
+ return false;
+
+ b = fnmatch(parameter, h, FNM_CASEFOLD) == 0;
+ free(h);
+
+ return b;
+}
+
+bool condition_test(Condition *c) {
+ assert(c);
+
+ switch(c->type) {
+
+ case CONDITION_PATH_EXISTS:
+ return (access(c->parameter, F_OK) >= 0) == !c->negate;
+
+ case CONDITION_PATH_EXISTS_GLOB:
+ return (glob_exists(c->parameter) > 0) == !c->negate;
+
+ case CONDITION_PATH_IS_DIRECTORY: {
+ struct stat st;
+
+ if (stat(c->parameter, &st) < 0)
+ return c->negate;
+ return S_ISDIR(st.st_mode) == !c->negate;
+ }
+
+ case CONDITION_PATH_IS_SYMBOLIC_LINK: {
+ struct stat st;
+
+ if (lstat(c->parameter, &st) < 0)
+ return c->negate;
+ return S_ISLNK(st.st_mode) == !c->negate;
+ }
+
+ case CONDITION_PATH_IS_MOUNT_POINT:
+ return (path_is_mount_point(c->parameter, true) > 0) == !c->negate;
+
+ case CONDITION_PATH_IS_READ_WRITE:
+ return (path_is_read_only_fs(c->parameter) > 0) == c->negate;
+
+ case CONDITION_DIRECTORY_NOT_EMPTY: {
+ int k;
+
+ k = dir_is_empty(c->parameter);
+ return !(k == -ENOENT || k > 0) == !c->negate;
+ }
+
+ case CONDITION_FILE_NOT_EMPTY: {
+ struct stat st;
+
+ if (stat(c->parameter, &st) < 0)
+ return c->negate;
+
+ return (S_ISREG(st.st_mode) && st.st_size > 0) == !c->negate;
+ }
+
+ case CONDITION_FILE_IS_EXECUTABLE: {
+ struct stat st;
+
+ if (stat(c->parameter, &st) < 0)
+ return c->negate;
+
+ return (S_ISREG(st.st_mode) && (st.st_mode & 0111)) == !c->negate;
+ }
+
+ case CONDITION_KERNEL_COMMAND_LINE:
+ return test_kernel_command_line(c->parameter) == !c->negate;
+
+ case CONDITION_VIRTUALIZATION:
+ return test_virtualization(c->parameter) == !c->negate;
+
+ case CONDITION_SECURITY:
+ return test_security(c->parameter) == !c->negate;
+
+ case CONDITION_CAPABILITY:
+ return test_capability(c->parameter) == !c->negate;
+
+ case CONDITION_HOST:
+ return test_host(c->parameter) == !c->negate;
+
+ case CONDITION_NULL:
+ return !c->negate;
+
+ default:
+ assert_not_reached("Invalid condition type.");
+ }
+}
+
+bool condition_test_list(Condition *first) {
+ Condition *c;
+ int triggered = -1;
+
+ /* If the condition list is empty, then it is true */
+ if (!first)
+ return true;
+
+ /* Otherwise, if all of the non-trigger conditions apply and
+ * if any of the trigger conditions apply (unless there are
+ * none) we return true */
+ LIST_FOREACH(conditions, c, first) {
+ bool b;
+
+ b = condition_test(c);
+
+ if (!c->trigger && !b)
+ return false;
+
+ if (c->trigger && triggered <= 0)
+ triggered = b;
+ }
+
+ return triggered != 0;
+}
+
+void condition_dump(Condition *c, FILE *f, const char *prefix) {
+ assert(c);
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+
+ fprintf(f,
+ "%s\t%s: %s%s%s\n",
+ prefix,
+ condition_type_to_string(c->type),
+ c->trigger ? "|" : "",
+ c->negate ? "!" : "",
+ c->parameter);
+}
+
+void condition_dump_list(Condition *first, FILE *f, const char *prefix) {
+ Condition *c;
+
+ LIST_FOREACH(conditions, c, first)
+ condition_dump(c, f, prefix);
+}
+
+static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
+ [CONDITION_PATH_EXISTS] = "ConditionPathExists",
+ [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
+ [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
+ [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
+ [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
+ [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
+ [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
+ [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
+ [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
+ [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
+ [CONDITION_SECURITY] = "ConditionSecurity",
+ [CONDITION_HOST] = "ConditionHost",
+ [CONDITION_NULL] = "ConditionNull"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
diff --git a/src/core/condition.h b/src/core/condition.h
new file mode 100644
index 0000000000..03954e40b3
--- /dev/null
+++ b/src/core/condition.h
@@ -0,0 +1,69 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "list.h"
+
+typedef enum ConditionType {
+ CONDITION_PATH_EXISTS,
+ CONDITION_PATH_EXISTS_GLOB,
+ CONDITION_PATH_IS_DIRECTORY,
+ CONDITION_PATH_IS_SYMBOLIC_LINK,
+ CONDITION_PATH_IS_MOUNT_POINT,
+ CONDITION_PATH_IS_READ_WRITE,
+ CONDITION_DIRECTORY_NOT_EMPTY,
+ CONDITION_FILE_NOT_EMPTY,
+ CONDITION_FILE_IS_EXECUTABLE,
+ CONDITION_KERNEL_COMMAND_LINE,
+ CONDITION_VIRTUALIZATION,
+ CONDITION_SECURITY,
+ CONDITION_CAPABILITY,
+ CONDITION_HOST,
+ CONDITION_NULL,
+ _CONDITION_TYPE_MAX,
+ _CONDITION_TYPE_INVALID = -1
+} ConditionType;
+
+typedef struct Condition {
+ ConditionType type;
+ char *parameter;
+
+ bool trigger:1;
+ bool negate:1;
+
+ LIST_FIELDS(struct Condition, conditions);
+} Condition;
+
+Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate);
+void condition_free(Condition *c);
+void condition_free_list(Condition *c);
+
+bool condition_test(Condition *c);
+bool condition_test_list(Condition *c);
+
+void condition_dump(Condition *c, FILE *f, const char *prefix);
+void condition_dump_list(Condition *c, FILE *f, const char *prefix);
+
+const char* condition_type_to_string(ConditionType t);
+int condition_type_from_string(const char *s);
diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c
new file mode 100644
index 0000000000..060cbf7707
--- /dev/null
+++ b/src/core/dbus-automount.c
@@ -0,0 +1,75 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-automount.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_AUTOMOUNT_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Automount\">\n" \
+ " <property name=\"Where\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_AUTOMOUNT_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Automount\0"
+
+const char bus_automount_interface[] _introspect_("Automount") = BUS_AUTOMOUNT_INTERFACE;
+
+const char bus_automount_invalidating_properties[] =
+ "Result\0";
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_automount_append_automount_result, automount_result, AutomountResult);
+
+static const BusProperty bus_automount_properties[] = {
+ { "Where", bus_property_append_string, "s", offsetof(Automount, where), true },
+ { "DirectoryMode", bus_property_append_mode, "u", offsetof(Automount, directory_mode) },
+ { "Result", bus_automount_append_automount_result, "s", offsetof(Automount, result) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Automount *am = AUTOMOUNT(u);
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Automount", bus_automount_properties, am },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-automount.h b/src/core/dbus-automount.h
new file mode 100644
index 0000000000..b338e25fc1
--- /dev/null
+++ b/src/core/dbus-automount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_automount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_automount_interface[];
+extern const char bus_automount_invalidating_properties[];
diff --git a/src/core/dbus-device.c b/src/core/dbus-device.c
new file mode 100644
index 0000000000..dbd91fe3db
--- /dev/null
+++ b/src/core/dbus-device.c
@@ -0,0 +1,68 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "dbus-unit.h"
+#include "dbus-device.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_DEVICE_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Device\">\n" \
+ " <property name=\"SysFSPath\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_DEVICE_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Device\0"
+
+const char bus_device_interface[] _introspect_("Device") = BUS_DEVICE_INTERFACE;
+
+const char bus_device_invalidating_properties[] =
+ "SysFSPath\0";
+
+static const BusProperty bus_device_properties[] = {
+ { "SysFSPath", bus_property_append_string, "s", offsetof(Device, sysfs), true },
+ { NULL, }
+};
+
+
+DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Device *d = DEVICE(u);
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Device", bus_device_properties, d },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-device.h b/src/core/dbus-device.h
new file mode 100644
index 0000000000..311e0685d1
--- /dev/null
+++ b/src/core/dbus-device.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_device_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_device_interface[];
+extern const char bus_device_invalidating_properties[];
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
new file mode 100644
index 0000000000..e815cb58e4
--- /dev/null
+++ b/src/core/dbus-execute.c
@@ -0,0 +1,439 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <dbus/dbus.h>
+#include <sys/prctl.h>
+
+#include "dbus-execute.h"
+#include "missing.h"
+#include "ioprio.h"
+#include "strv.h"
+#include "dbus-common.h"
+#include "syscall-list.h"
+
+DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_input, exec_input, ExecInput);
+DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_output, exec_output, ExecOutput);
+
+int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data) {
+ char **env_files = data, **j;
+ DBusMessageIter sub, sub2;
+
+ assert(i);
+ assert(property);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sb)", &sub))
+ return -ENOMEM;
+
+ STRV_FOREACH(j, env_files) {
+ dbus_bool_t b = false;
+ char *fn = *j;
+
+ if (fn[0] == '-') {
+ b = true;
+ fn++;
+ }
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &fn) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) ||
+ !dbus_message_iter_close_container(&sub, &sub2))
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ int32_t n;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->oom_score_adjust_set)
+ n = c->oom_score_adjust;
+ else {
+ char *t;
+
+ n = 0;
+ if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) {
+ safe_atoi(t, &n);
+ free(t);
+ }
+ }
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ int32_t n;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->nice_set)
+ n = c->nice;
+ else
+ n = getpriority(PRIO_PROCESS, 0);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ int32_t n;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->ioprio_set)
+ n = c->ioprio;
+ else
+ n = ioprio_get(IOPRIO_WHO_PROCESS, 0);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ int32_t n;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->cpu_sched_set)
+ n = c->cpu_sched_policy;
+ else
+ n = sched_getscheduler(0);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ int32_t n;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->cpu_sched_set)
+ n = c->cpu_sched_priority;
+ else {
+ struct sched_param p;
+ n = 0;
+
+ zero(p);
+ if (sched_getparam(0, &p) >= 0)
+ n = p.sched_priority;
+ }
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &n))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ dbus_bool_t b;
+ DBusMessageIter sub;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "y", &sub))
+ return -ENOMEM;
+
+ if (c->cpuset)
+ b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus));
+ else
+ b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &c->cpuset, 0);
+
+ if (!b)
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ uint64_t u;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->timer_slack_nsec != (nsec_t) -1)
+ u = (uint64_t) c->timer_slack_nsec;
+ else
+ u = (uint64_t) prctl(PR_GET_TIMERSLACK);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ uint64_t normal, inverted;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ /* We store this negated internally, to match the kernel, but
+ * we expose it normalized. */
+
+ normal = *(uint64_t*) data;
+ inverted = ~normal;
+
+ return bus_property_append_uint64(i, property, &inverted);
+}
+
+int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ char *t = NULL;
+ const char *s;
+ dbus_bool_t b;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (c->capabilities)
+ s = t = cap_to_text(c->capabilities, NULL);
+ else
+ s = "";
+
+ if (!s)
+ return -ENOMEM;
+
+ b = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &s);
+
+ if (t)
+ cap_free(t);
+
+ if (!b)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ int r;
+ uint64_t u;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ assert_se((r = rlimit_from_string(property)) >= 0);
+
+ if (c->rlimit[r])
+ u = (uint64_t) c->rlimit[r]->rlim_max;
+ else {
+ struct rlimit rl;
+
+ zero(rl);
+ getrlimit(r, &rl);
+
+ u = (uint64_t) rl.rlim_max;
+ }
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_command(DBusMessageIter *i, const char *property, void *data) {
+ ExecCommand *c = data;
+ DBusMessageIter sub, sub2, sub3;
+
+ assert(i);
+ assert(property);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sasbttttuii)", &sub))
+ return -ENOMEM;
+
+ LIST_FOREACH(command, c, c) {
+ char **l;
+ uint32_t pid;
+ int32_t code, status;
+ dbus_bool_t b;
+
+ if (!c->path)
+ continue;
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &c->path) ||
+ !dbus_message_iter_open_container(&sub2, DBUS_TYPE_ARRAY, "s", &sub3))
+ return -ENOMEM;
+
+ STRV_FOREACH(l, c->argv)
+ if (!dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, l))
+ return -ENOMEM;
+
+ pid = (uint32_t) c->exec_status.pid;
+ code = (int32_t) c->exec_status.code;
+ status = (int32_t) c->exec_status.status;
+
+ b = !!c->ignore;
+
+ if (!dbus_message_iter_close_container(&sub2, &sub3) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_BOOLEAN, &b) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.realtime) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.start_timestamp.monotonic) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.realtime) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &c->exec_status.exit_timestamp.monotonic) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &pid) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &code) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_INT32, &status))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(&sub, &sub2))
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_execute_append_syscall_filter(DBusMessageIter *i, const char *property, void *data) {
+ ExecContext *c = data;
+ dbus_bool_t b;
+ DBusMessageIter sub;
+
+ assert(i);
+ assert(property);
+ assert(c);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "u", &sub))
+ return -ENOMEM;
+
+ if (c->syscall_filter)
+ b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_UINT32, &c->syscall_filter, (syscall_max() + 31) >> 4);
+ else
+ b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_UINT32, &c->syscall_filter, 0);
+
+ if (!b)
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+const BusProperty bus_exec_context_properties[] = {
+ { "Environment", bus_property_append_strv, "as", offsetof(ExecContext, environment), true },
+ { "EnvironmentFiles", bus_execute_append_env_files, "a(sb)", offsetof(ExecContext, environment_files), true },
+ { "UMask", bus_property_append_mode, "u", offsetof(ExecContext, umask) },
+ { "LimitCPU", bus_execute_append_rlimits, "t", 0 },
+ { "LimitFSIZE", bus_execute_append_rlimits, "t", 0 },
+ { "LimitDATA", bus_execute_append_rlimits, "t", 0 },
+ { "LimitSTACK", bus_execute_append_rlimits, "t", 0 },
+ { "LimitCORE", bus_execute_append_rlimits, "t", 0 },
+ { "LimitRSS", bus_execute_append_rlimits, "t", 0 },
+ { "LimitNOFILE", bus_execute_append_rlimits, "t", 0 },
+ { "LimitAS", bus_execute_append_rlimits, "t", 0 },
+ { "LimitNPROC", bus_execute_append_rlimits, "t", 0 },
+ { "LimitMEMLOCK", bus_execute_append_rlimits, "t", 0 },
+ { "LimitLOCKS", bus_execute_append_rlimits, "t", 0 },
+ { "LimitSIGPENDING", bus_execute_append_rlimits, "t", 0 },
+ { "LimitMSGQUEUE", bus_execute_append_rlimits, "t", 0 },
+ { "LimitNICE", bus_execute_append_rlimits, "t", 0 },
+ { "LimitRTPRIO", bus_execute_append_rlimits, "t", 0 },
+ { "LimitRTTIME", bus_execute_append_rlimits, "t", 0 },
+ { "WorkingDirectory", bus_property_append_string, "s", offsetof(ExecContext, working_directory), true },
+ { "RootDirectory", bus_property_append_string, "s", offsetof(ExecContext, root_directory), true },
+ { "OOMScoreAdjust", bus_execute_append_oom_score_adjust, "i", 0 },
+ { "Nice", bus_execute_append_nice, "i", 0 },
+ { "IOScheduling", bus_execute_append_ioprio, "i", 0 },
+ { "CPUSchedulingPolicy", bus_execute_append_cpu_sched_policy, "i", 0 },
+ { "CPUSchedulingPriority", bus_execute_append_cpu_sched_priority, "i", 0 },
+ { "CPUAffinity", bus_execute_append_affinity, "ay", 0 },
+ { "TimerSlackNSec", bus_execute_append_timer_slack_nsec, "t", 0 },
+ { "CPUSchedulingResetOnFork", bus_property_append_bool, "b", offsetof(ExecContext, cpu_sched_reset_on_fork) },
+ { "NonBlocking", bus_property_append_bool, "b", offsetof(ExecContext, non_blocking) },
+ { "StandardInput", bus_execute_append_input, "s", offsetof(ExecContext, std_input) },
+ { "StandardOutput", bus_execute_append_output, "s", offsetof(ExecContext, std_output) },
+ { "StandardError", bus_execute_append_output, "s", offsetof(ExecContext, std_error) },
+ { "TTYPath", bus_property_append_string, "s", offsetof(ExecContext, tty_path), true },
+ { "TTYReset", bus_property_append_bool, "b", offsetof(ExecContext, tty_reset) },
+ { "TTYVHangup", bus_property_append_bool, "b", offsetof(ExecContext, tty_vhangup) },
+ { "TTYVTDisallocate", bus_property_append_bool, "b", offsetof(ExecContext, tty_vt_disallocate) },
+ { "SyslogPriority", bus_property_append_int, "i", offsetof(ExecContext, syslog_priority) },
+ { "SyslogIdentifier", bus_property_append_string, "s", offsetof(ExecContext, syslog_identifier), true },
+ { "SyslogLevelPrefix", bus_property_append_bool, "b", offsetof(ExecContext, syslog_level_prefix) },
+ { "Capabilities", bus_execute_append_capabilities, "s", 0 },
+ { "SecureBits", bus_property_append_int, "i", offsetof(ExecContext, secure_bits) },
+ { "CapabilityBoundingSet", bus_execute_append_capability_bs, "t", offsetof(ExecContext, capability_bounding_set_drop) },
+ { "User", bus_property_append_string, "s", offsetof(ExecContext, user), true },
+ { "Group", bus_property_append_string, "s", offsetof(ExecContext, group), true },
+ { "SupplementaryGroups", bus_property_append_strv, "as", offsetof(ExecContext, supplementary_groups), true },
+ { "TCPWrapName", bus_property_append_string, "s", offsetof(ExecContext, tcpwrap_name), true },
+ { "PAMName", bus_property_append_string, "s", offsetof(ExecContext, pam_name), true },
+ { "ReadWriteDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_write_dirs), true },
+ { "ReadOnlyDirectories", bus_property_append_strv, "as", offsetof(ExecContext, read_only_dirs), true },
+ { "InaccessibleDirectories", bus_property_append_strv, "as", offsetof(ExecContext, inaccessible_dirs), true },
+ { "MountFlags", bus_property_append_ul, "t", offsetof(ExecContext, mount_flags) },
+ { "PrivateTmp", bus_property_append_bool, "b", offsetof(ExecContext, private_tmp) },
+ { "PrivateNetwork", bus_property_append_bool, "b", offsetof(ExecContext, private_network) },
+ { "SameProcessGroup", bus_property_append_bool, "b", offsetof(ExecContext, same_pgrp) },
+ { "UtmpIdentifier", bus_property_append_string, "s", offsetof(ExecContext, utmp_id), true },
+ { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_modify) },
+ { "ControlGroupPersistent", bus_property_append_tristate_false, "b", offsetof(ExecContext, control_group_persistent) },
+ { "IgnoreSIGPIPE", bus_property_append_bool, "b", offsetof(ExecContext, ignore_sigpipe) },
+ { "NoNewPrivileges", bus_property_append_bool, "b", offsetof(ExecContext, no_new_privileges) },
+ { "SystemCallFilter", bus_execute_append_syscall_filter, "au", 0 },
+ { NULL, }
+};
diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h
new file mode 100644
index 0000000000..eaa1b73e69
--- /dev/null
+++ b/src/core/dbus-execute.h
@@ -0,0 +1,124 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "manager.h"
+#include "dbus-common.h"
+
+#define BUS_EXEC_STATUS_INTERFACE(prefix) \
+ " <property name=\"" prefix "StartTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"" prefix "StartTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"" prefix "ExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"" prefix "ExitTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"" prefix "PID\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"" prefix "Code\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"" prefix "Status\" type=\"i\" access=\"read\"/>\n"
+
+#define BUS_EXEC_CONTEXT_INTERFACE \
+ " <property name=\"Environment\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"UMask\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"LimitCPU\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitFSIZE\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitDATA\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitSTACK\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitCORE\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitRSS\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitNOFILE\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitAS\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitNPROC\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitMEMLOCK\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitLOCKS\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitSIGPENDING\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitMSGQUEUE\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitNICE\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitRTPRIO\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LimitRTTIME\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"WorkingDirectory\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"RootDirectory\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"OOMScoreAdjust\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"Nice\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"IOScheduling\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"CPUSchedulingPolicy\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"CPUSchedulingPriority\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"CPUAffinity\" type=\"ay\" access=\"read\"/>\n" \
+ " <property name=\"TimerSlackNS\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"CPUSchedulingResetOnFork\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"NonBlocking\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"StandardInput\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"StandardOutput\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"StandardError\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"TTYPath\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"TTYReset\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"TTYVHangup\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"TTYVTDisallocate\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"SyslogPriority\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"SyslogIdentifier\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SyslogLevelPrefix\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Capabilities\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SecureBits\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"CapabilityBoundingSet\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"User\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Group\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SupplementaryGroups\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"TCPWrapName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"PAMName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"ReadWriteDirectories\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"ReadOnlyDirectories\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"InaccessibleDirectories\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"MountFlags\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"PrivateTmp\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"SameProcessGroup\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"KillSignal\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"UtmpIdentifier\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"ControlGroupModify\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"ControlGroupPersistent\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"PrivateNetwork\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"IgnoreSIGPIPE\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"NoNewPrivileges\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"SystemCallFilter\" type=\"au\" access=\"read\"/>\n"
+
+#define BUS_EXEC_COMMAND_INTERFACE(name) \
+ " <property name=\"" name "\" type=\"a(sasbttuii)\" access=\"read\"/>\n"
+
+extern const BusProperty bus_exec_context_properties[];
+
+#define BUS_EXEC_COMMAND_PROPERTY(name, command, indirect) \
+ { name, bus_execute_append_command, "a(sasbttttuii)", (command), (indirect), NULL }
+
+int bus_execute_append_output(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_input(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_command(DBusMessageIter *u, const char *property, void *data);
+int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_syscall_filter(DBusMessageIter *i, const char *property, void *data);
diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c
new file mode 100644
index 0000000000..fdc1dce177
--- /dev/null
+++ b/src/core/dbus-job.c
@@ -0,0 +1,378 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-job.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_JOB_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Job\">\n" \
+ " <method name=\"Cancel\"/>\n" \
+ " <property name=\"Id\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Unit\" type=\"(so)\" access=\"read\"/>\n" \
+ " <property name=\"JobType\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"State\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_JOB_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+const char bus_job_interface[] _introspect_("Job") = BUS_JOB_INTERFACE;
+
+#define INTERFACES_LIST \
+ BUS_GENERIC_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Job\0"
+
+#define INVALIDATING_PROPERTIES \
+ "State\0"
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType);
+
+static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) {
+ Job *j = data;
+ DBusMessageIter sub;
+ char *p;
+
+ assert(i);
+ assert(property);
+ assert(j);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+ return -ENOMEM;
+
+ p = unit_dbus_path(j->unit);
+ if (!p)
+ return -ENOMEM;
+
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) ||
+ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
+ free(p);
+ return -ENOMEM;
+ }
+
+ free(p);
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static const BusProperty bus_job_properties[] = {
+ { "Id", bus_property_append_uint32, "u", offsetof(Job, id) },
+ { "State", bus_job_append_state, "s", offsetof(Job, state) },
+ { "JobType", bus_job_append_type, "s", offsetof(Job, type) },
+ { "Unit", bus_job_append_unit, "(so)", 0 },
+ { NULL, }
+};
+
+static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusConnection *connection, DBusMessage *message) {
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) {
+
+ SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "stop");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ job_finish_and_invalidate(j, JOB_CANCELED, true);
+ } else {
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Job", bus_job_properties, j },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "status");
+
+ return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
+ }
+
+ if (!dbus_connection_send(connection, reply, NULL))
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+ Job *j;
+ int r;
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) {
+ /* Be nice to gdbus and return introspection data for our mid-level paths */
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+ char *introspection = NULL;
+ FILE *f;
+ Iterator i;
+ size_t size;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ /* We roll our own introspection code here, instead of
+ * relying on bus_default_message_handler() because we
+ * need to generate our introspection string
+ * dynamically. */
+
+ f = open_memstream(&introspection, &size);
+ if (!f)
+ goto oom;
+
+ fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>\n", f);
+
+ fputs(BUS_INTROSPECTABLE_INTERFACE, f);
+ fputs(BUS_PEER_INTERFACE, f);
+
+ HASHMAP_FOREACH(j, m->jobs, i)
+ fprintf(f, "<node name=\"job/%lu\"/>", (unsigned long) j->id);
+
+ fputs("</node>\n", f);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(introspection);
+ goto oom;
+ }
+
+ fclose(f);
+
+ if (!introspection)
+ goto oom;
+
+ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
+ free(introspection);
+ goto oom;
+ }
+
+ free(introspection);
+
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r == -ENOENT) {
+ DBusError e;
+
+ dbus_error_init(&e);
+ dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job");
+ return bus_send_error_reply(connection, message, &e, r);
+ }
+ if (r < 0)
+ return bus_send_error_reply(connection, message, NULL, r);
+
+ return bus_job_message_dispatch(j, connection, message);
+
+oom:
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+const DBusObjectPathVTable bus_job_vtable = {
+ .message_function = bus_job_message_handler
+};
+
+static int job_send_message(Job *j, DBusMessage* (*new_message)(Job *j)) {
+ DBusMessage *m = NULL;
+ int r;
+
+ assert(j);
+ assert(new_message);
+
+ if (bus_has_subscriber(j->manager) || j->forgot_bus_clients) {
+ m = new_message(j);
+ if (!m)
+ goto oom;
+ r = bus_broadcast(j->manager, m);
+ dbus_message_unref(m);
+ if (r < 0)
+ return r;
+
+ } else {
+ /* If nobody is subscribed, we just send the message
+ * to the client(s) which created the job */
+ JobBusClient *cl;
+ assert(j->bus_client_list);
+ LIST_FOREACH(client, cl, j->bus_client_list) {
+ assert(cl->bus);
+
+ m = new_message(j);
+ if (!m)
+ goto oom;
+
+ if (!dbus_message_set_destination(m, cl->name))
+ goto oom;
+
+ if (!dbus_connection_send(cl->bus, m, NULL))
+ goto oom;
+
+ dbus_message_unref(m);
+ m = NULL;
+ }
+ }
+
+ return 0;
+oom:
+ if (m)
+ dbus_message_unref(m);
+ return -ENOMEM;
+}
+
+static DBusMessage* new_change_signal_message(Job *j) {
+ DBusMessage *m = NULL;
+ char *p = NULL;
+
+ p = job_dbus_path(j);
+ if (!p)
+ goto oom;
+
+ if (j->sent_dbus_new_signal) {
+ /* Send a properties changed signal */
+ m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES);
+ if (!m)
+ goto oom;
+
+ } else {
+ /* Send a new signal */
+
+ m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew");
+ if (!m)
+ goto oom;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT32, &j->id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_STRING, &j->unit->id,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ }
+
+ return m;
+
+oom:
+ if (m)
+ dbus_message_unref(m);
+ free(p);
+ return NULL;
+}
+
+static DBusMessage* new_removed_signal_message(Job *j) {
+ DBusMessage *m = NULL;
+ char *p = NULL;
+ const char *r;
+
+ p = job_dbus_path(j);
+ if (!p)
+ goto oom;
+
+ m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved");
+ if (!m)
+ goto oom;
+
+ r = job_result_to_string(j->result);
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT32, &j->id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_STRING, &j->unit->id,
+ DBUS_TYPE_STRING, &r,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ return m;
+
+oom:
+ if (m)
+ dbus_message_unref(m);
+ free(p);
+ return NULL;
+}
+
+void bus_job_send_change_signal(Job *j) {
+ assert(j);
+
+ if (j->in_dbus_queue) {
+ LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
+ j->in_dbus_queue = false;
+ }
+
+ if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients) {
+ j->sent_dbus_new_signal = true;
+ return;
+ }
+
+ if (job_send_message(j, new_change_signal_message) < 0)
+ goto oom;
+
+ j->sent_dbus_new_signal = true;
+
+ return;
+
+oom:
+ log_error("Failed to allocate job change signal.");
+}
+
+void bus_job_send_removed_signal(Job *j) {
+ assert(j);
+
+ if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients)
+ return;
+
+ if (!j->sent_dbus_new_signal)
+ bus_job_send_change_signal(j);
+
+ if (job_send_message(j, new_removed_signal_message) < 0)
+ goto oom;
+
+ return;
+
+oom:
+ log_error("Failed to allocate job remove signal.");
+}
diff --git a/src/core/dbus-job.h b/src/core/dbus-job.h
new file mode 100644
index 0000000000..a1b928fb16
--- /dev/null
+++ b/src/core/dbus-job.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "job.h"
+
+void bus_job_send_change_signal(Job *j);
+void bus_job_send_removed_signal(Job *j);
+
+extern const DBusObjectPathVTable bus_job_vtable;
+
+extern const char bus_job_interface[];
diff --git a/src/core/dbus-kill.c b/src/core/dbus-kill.c
new file mode 100644
index 0000000000..165f63074b
--- /dev/null
+++ b/src/core/dbus-kill.c
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <dbus/dbus.h>
+
+#include "dbus-kill.h"
+#include "dbus-common.h"
+
+DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_kill_append_mode, kill_mode, KillMode);
+
+const BusProperty bus_kill_context_properties[] = {
+ { "KillMode", bus_kill_append_mode, "s", offsetof(KillContext, kill_mode) },
+ { "KillSignal", bus_property_append_int, "i", offsetof(KillContext, kill_signal) },
+ { "SendSIGKILL", bus_property_append_bool, "b", offsetof(KillContext, send_sigkill) },
+ { NULL, }
+};
diff --git a/src/core/dbus-kill.h b/src/core/dbus-kill.h
new file mode 100644
index 0000000000..238fbd36d6
--- /dev/null
+++ b/src/core/dbus-kill.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "manager.h"
+#include "dbus-common.h"
+
+#define BUS_KILL_CONTEXT_INTERFACE \
+ " <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"KillSignal\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"SendSIGKILL\" type=\"b\" access=\"read\"/>\n"
+
+#define BUS_KILL_COMMAND_INTERFACE(name) \
+ " <property name=\"" name "\" type=\"a(sasbttuii)\" access=\"read\"/>\n"
+
+extern const BusProperty bus_kill_context_properties[];
+
+int bus_kill_append_mode(DBusMessageIter *i, const char *property, void *data);
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
new file mode 100644
index 0000000000..2010241e6a
--- /dev/null
+++ b/src/core/dbus-manager.c
@@ -0,0 +1,1685 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-manager.h"
+#include "strv.h"
+#include "bus-errors.h"
+#include "build.h"
+#include "dbus-common.h"
+#include "install.h"
+#include "selinux-access.h"
+#include "watchdog.h"
+#include "hwclock.h"
+#include "path-util.h"
+#include "dbus-unit.h"
+#include "virt.h"
+
+#define BUS_MANAGER_INTERFACE_BEGIN \
+ " <interface name=\"org.freedesktop.systemd1.Manager\">\n"
+
+#define BUS_MANAGER_INTERFACE_METHODS \
+ " <method name=\"GetUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetUnitByPID\">\n" \
+ " <arg name=\"pid\" type=\"u\" direction=\"in\"/>\n" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"LoadUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"StartUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"StartUnitReplace\">\n" \
+ " <arg name=\"old_unit\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"new_unit\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"StopUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ReloadUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"RestartUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"TryRestartUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ReloadOrRestartUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ReloadOrTryRestartUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"KillUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"who\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"signal\" type=\"i\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ResetFailedUnit\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetJob\">\n" \
+ " <arg name=\"id\" type=\"u\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ClearJobs\"/>\n" \
+ " <method name=\"ResetFailed\"/>\n" \
+ " <method name=\"ListUnits\">\n" \
+ " <arg name=\"units\" type=\"a(ssssssouso)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ListJobs\">\n" \
+ " <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Subscribe\"/>\n" \
+ " <method name=\"Unsubscribe\"/>\n" \
+ " <method name=\"Dump\">\n" \
+ " <arg name=\"dump\" type=\"s\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"CreateSnapshot\">\n" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"cleanup\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Reload\"/>\n" \
+ " <method name=\"Reexecute\"/>\n" \
+ " <method name=\"Exit\"/>\n" \
+ " <method name=\"Reboot\"/>\n" \
+ " <method name=\"PowerOff\"/>\n" \
+ " <method name=\"Halt\"/>\n" \
+ " <method name=\"KExec\"/>\n" \
+ " <method name=\"SwitchRoot\">\n" \
+ " <arg name=\"new_root\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"init\" type=\"s\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"SetEnvironment\">\n" \
+ " <arg name=\"names\" type=\"as\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"UnsetEnvironment\">\n" \
+ " <arg name=\"names\" type=\"as\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"UnsetAndSetEnvironment\">\n" \
+ " <arg name=\"unset\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"set\" type=\"as\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ListUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetUnitFileState\">\n" \
+ " <arg name=\"file\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"state\" type=\"s\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"EnableUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"force\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"carries_install_info\" type=\"b\" direction=\"out\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"DisableUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ReenableUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"force\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"carries_install_info\" type=\"b\" direction=\"out\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"LinkUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"force\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"PresetUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"force\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"carries_install_info\" type=\"b\" direction=\"out\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"MaskUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"force\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"UnmaskUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \
+ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \
+ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
+ " </method>\n"
+
+#define BUS_MANAGER_INTERFACE_SIGNALS \
+ " <signal name=\"UnitNew\">\n" \
+ " <arg name=\"id\" type=\"s\"/>\n" \
+ " <arg name=\"unit\" type=\"o\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"UnitRemoved\">\n" \
+ " <arg name=\"id\" type=\"s\"/>\n" \
+ " <arg name=\"unit\" type=\"o\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"JobNew\">\n" \
+ " <arg name=\"id\" type=\"u\"/>\n" \
+ " <arg name=\"job\" type=\"o\"/>\n" \
+ " <arg name=\"unit\" type=\"s\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"JobRemoved\">\n" \
+ " <arg name=\"id\" type=\"u\"/>\n" \
+ " <arg name=\"job\" type=\"o\"/>\n" \
+ " <arg name=\"unit\" type=\"s\"/>\n" \
+ " <arg name=\"result\" type=\"s\"/>\n" \
+ " </signal>" \
+ " <signal name=\"StartupFinished\">\n" \
+ " <arg name=\"firmware\" type=\"t\"/>\n" \
+ " <arg name=\"loader\" type=\"t\"/>\n" \
+ " <arg name=\"kernel\" type=\"t\"/>\n" \
+ " <arg name=\"initrd\" type=\"t\"/>\n" \
+ " <arg name=\"userspace\" type=\"t\"/>\n" \
+ " <arg name=\"total\" type=\"t\"/>\n" \
+ " </signal>" \
+ " <signal name=\"UnitFilesChanged\"/>\n"
+
+#define BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \
+ " <property name=\"Version\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Distribution\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Features\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Tainted\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"FirmwareTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"FirmwareTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LoaderTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LoaderTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"KernelTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"KernelTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"InitRDTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"InitRDTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"UserspaceTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"UserspaceTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"FinishTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"FinishTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"LogLevel\" type=\"s\" access=\"readwrite\"/>\n" \
+ " <property name=\"LogTarget\" type=\"s\" access=\"readwrite\"/>\n" \
+ " <property name=\"NNames\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"NJobs\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"NInstalledJobs\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"NFailedJobs\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Progress\" type=\"d\" access=\"read\"/>\n" \
+ " <property name=\"Environment\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"ConfirmSpawn\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"ShowStatus\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"UnitPath\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"ControlGroupHierarchy\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"DefaultControllers\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"DefaultStandardOutput\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"DefaultStandardError\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"RuntimeWatchdogUSec\" type=\"s\" access=\"readwrite\"/>\n" \
+ " <property name=\"ShutdownWatchdogUSec\" type=\"s\" access=\"readwrite\"/>\n" \
+ " <property name=\"Virtualization\" type=\"s\" access=\"read\"/>\n"
+
+#define BUS_MANAGER_INTERFACE_END \
+ " </interface>\n"
+
+#define BUS_MANAGER_INTERFACE \
+ BUS_MANAGER_INTERFACE_BEGIN \
+ BUS_MANAGER_INTERFACE_METHODS \
+ BUS_MANAGER_INTERFACE_SIGNALS \
+ BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \
+ BUS_MANAGER_INTERFACE_END
+
+#define INTROSPECTION_BEGIN \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_MANAGER_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE
+
+#define INTROSPECTION_END \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_GENERIC_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Manager\0"
+
+const char bus_manager_interface[] _introspect_("Manager") = BUS_MANAGER_INTERFACE;
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_exec_output, exec_output, ExecOutput);
+
+static int bus_manager_append_tainted(DBusMessageIter *i, const char *property, void *data) {
+ const char *t;
+ Manager *m = data;
+ char buf[LINE_MAX] = "", *e = buf, *p = NULL;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ if (m->taint_usr)
+ e = stpcpy(e, "split-usr:");
+
+ if (readlink_malloc("/etc/mtab", &p) < 0)
+ e = stpcpy(e, "mtab-not-symlink:");
+ else
+ free(p);
+
+ if (access("/proc/cgroups", F_OK) < 0)
+ e = stpcpy(e, "cgroups-missing:");
+
+ if (hwclock_is_localtime() > 0)
+ e = stpcpy(e, "local-hwclock:");
+
+ /* remove the last ':' */
+ if (e != buf)
+ e[-1] = 0;
+
+ t = buf;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_manager_append_log_target(DBusMessageIter *i, const char *property, void *data) {
+ const char *t;
+
+ assert(i);
+ assert(property);
+
+ t = log_target_to_string(log_get_target());
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_manager_set_log_target(DBusMessageIter *i, const char *property, void *data) {
+ const char *t;
+
+ assert(i);
+ assert(property);
+
+ dbus_message_iter_get_basic(i, &t);
+
+ return log_set_target_from_string(t);
+}
+
+static int bus_manager_append_log_level(DBusMessageIter *i, const char *property, void *data) {
+ char *t;
+ int r;
+
+ assert(i);
+ assert(property);
+
+ r = log_level_to_string_alloc(log_get_max_level(), &t);
+ if (r < 0)
+ return r;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+ r = -ENOMEM;
+
+ free(t);
+ return r;
+}
+
+static int bus_manager_set_log_level(DBusMessageIter *i, const char *property, void *data) {
+ const char *t;
+
+ assert(i);
+ assert(property);
+
+ dbus_message_iter_get_basic(i, &t);
+
+ return log_set_max_level_from_string(t);
+}
+
+static int bus_manager_append_n_names(DBusMessageIter *i, const char *property, void *data) {
+ Manager *m = data;
+ uint32_t u;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ u = hashmap_size(m->units);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_manager_append_n_jobs(DBusMessageIter *i, const char *property, void *data) {
+ Manager *m = data;
+ uint32_t u;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ u = hashmap_size(m->jobs);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_manager_append_progress(DBusMessageIter *i, const char *property, void *data) {
+ double d;
+ Manager *m = data;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ if (dual_timestamp_is_set(&m->finish_timestamp))
+ d = 1.0;
+ else
+ d = 1.0 - ((double) hashmap_size(m->jobs) / (double) m->n_installed_jobs);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_DOUBLE, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_manager_append_virt(DBusMessageIter *i, const char *property, void *data) {
+ Manager *m = data;
+ const char *id = "";
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ detect_virtualization(&id);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &id))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static DBusMessage *message_from_file_changes(
+ DBusMessage *m,
+ UnitFileChange *changes,
+ unsigned n_changes,
+ int carries_install_info) {
+
+ DBusMessageIter iter, sub, sub2;
+ DBusMessage *reply;
+ unsigned i;
+
+ reply = dbus_message_new_method_return(m);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (carries_install_info >= 0) {
+ dbus_bool_t b;
+
+ b = !!carries_install_info;
+ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b))
+ goto oom;
+ }
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sss)", &sub))
+ goto oom;
+
+ for (i = 0; i < n_changes; i++) {
+ const char *type, *path, *source;
+
+ type = unit_file_change_type_to_string(changes[i].type);
+ path = strempty(changes[i].path);
+ source = strempty(changes[i].source);
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &path) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &source) ||
+ !dbus_message_iter_close_container(&sub, &sub2))
+ goto oom;
+ }
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ return reply;
+
+oom:
+ dbus_message_unref(reply);
+ return NULL;
+}
+
+static int bus_manager_send_unit_files_changed(Manager *m) {
+ DBusMessage *s;
+ int r;
+
+ s = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged");
+ if (!s)
+ return -ENOMEM;
+
+ r = bus_broadcast(m, s);
+ dbus_message_unref(s);
+
+ return r;
+}
+
+static int bus_manager_set_runtime_watchdog_usec(DBusMessageIter *i, const char *property, void *data) {
+ uint64_t *t = data;
+
+ assert(i);
+ assert(property);
+
+ dbus_message_iter_get_basic(i, t);
+
+ return watchdog_set_timeout(t);
+}
+
+static const char systemd_property_string[] =
+ PACKAGE_STRING "\0"
+ DISTRIBUTION "\0"
+ SYSTEMD_FEATURES;
+
+static const BusProperty bus_systemd_properties[] = {
+ { "Version", bus_property_append_string, "s", 0 },
+ { "Distribution", bus_property_append_string, "s", sizeof(PACKAGE_STRING) },
+ { "Features", bus_property_append_string, "s", sizeof(PACKAGE_STRING) + sizeof(DISTRIBUTION) },
+ { NULL, }
+};
+
+static const BusProperty bus_manager_properties[] = {
+ { "Tainted", bus_manager_append_tainted, "s", 0 },
+ { "FirmwareTimestamp", bus_property_append_uint64, "t", offsetof(Manager, firmware_timestamp.realtime) },
+ { "FirmwareTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, firmware_timestamp.monotonic) },
+ { "LoaderTimestamp", bus_property_append_uint64, "t", offsetof(Manager, loader_timestamp.realtime) },
+ { "LoaderTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, loader_timestamp.monotonic) },
+ { "KernelTimestamp", bus_property_append_uint64, "t", offsetof(Manager, kernel_timestamp.realtime) },
+ { "KernelTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, kernel_timestamp.monotonic) },
+ { "InitRDTimestamp", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.realtime) },
+ { "InitRDTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, initrd_timestamp.monotonic) },
+ { "UserspaceTimestamp", bus_property_append_uint64, "t", offsetof(Manager, userspace_timestamp.realtime) },
+ { "UserspaceTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, userspace_timestamp.monotonic) },
+ { "FinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.realtime) },
+ { "FinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.monotonic) },
+ { "LogLevel", bus_manager_append_log_level, "s", 0, false, bus_manager_set_log_level },
+ { "LogTarget", bus_manager_append_log_target, "s", 0, false, bus_manager_set_log_target },
+ { "NNames", bus_manager_append_n_names, "u", 0 },
+ { "NJobs", bus_manager_append_n_jobs, "u", 0 },
+ { "NInstalledJobs", bus_property_append_uint32, "u", offsetof(Manager, n_installed_jobs) },
+ { "NFailedJobs", bus_property_append_uint32, "u", offsetof(Manager, n_failed_jobs) },
+ { "Progress", bus_manager_append_progress, "d", 0 },
+ { "Environment", bus_property_append_strv, "as", offsetof(Manager, environment), true },
+ { "ConfirmSpawn", bus_property_append_bool, "b", offsetof(Manager, confirm_spawn) },
+ { "ShowStatus", bus_property_append_bool, "b", offsetof(Manager, show_status) },
+ { "UnitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.unit_path), true },
+ { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_hierarchy), true },
+ { "DefaultControllers", bus_property_append_strv, "as", offsetof(Manager, default_controllers), true },
+ { "DefaultStandardOutput", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_output) },
+ { "DefaultStandardError", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_error) },
+ { "RuntimeWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, runtime_watchdog), false, bus_manager_set_runtime_watchdog_usec },
+ { "ShutdownWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, shutdown_watchdog), false, bus_property_set_usec },
+ { "Virtualization", bus_manager_append_virt, "s", 0, },
+ { NULL, }
+};
+
+static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ _cleanup_free_ char * path = NULL;
+ Manager *m = data;
+ int r;
+ DBusError error;
+ JobType job_type = _JOB_TYPE_INVALID;
+ bool reload_if_possible = false;
+ const char *member;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ member = dbus_message_get_member(message);
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnit")) {
+ const char *name;
+ Unit *u;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ u = manager_get_unit(m, name);
+ if (!u) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ path = unit_dbus_path(u);
+ if (!path)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitByPID")) {
+ Unit *u;
+ uint32_t pid;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_UINT32, &pid,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ u = cgroup_unit_by_pid(m, (pid_t) pid);
+ if (!u) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "No unit for PID %lu is loaded.", (unsigned long) pid);
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ path = unit_dbus_path(u);
+ if (!path)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LoadUnit")) {
+ const char *name;
+ Unit *u;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ r = manager_load_unit(m, name, NULL, &error, &u);
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ path = unit_dbus_path(u);
+ if (!path)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnit"))
+ job_type = JOB_START;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace"))
+ job_type = JOB_START;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StopUnit"))
+ job_type = JOB_STOP;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadUnit"))
+ job_type = JOB_RELOAD;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "RestartUnit"))
+ job_type = JOB_RESTART;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "TryRestartUnit"))
+ job_type = JOB_TRY_RESTART;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrRestartUnit")) {
+ reload_if_possible = true;
+ job_type = JOB_RESTART;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReloadOrTryRestartUnit")) {
+ reload_if_possible = true;
+ job_type = JOB_TRY_RESTART;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KillUnit")) {
+ const char *name, *swho;
+ int32_t signo;
+ Unit *u;
+ KillWho who;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &swho,
+ DBUS_TYPE_INT32, &signo,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ if (isempty(swho))
+ who = KILL_ALL;
+ else {
+ who = kill_who_from_string(swho);
+ if (who < 0)
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+ }
+
+ if (signo <= 0 || signo >= _NSIG)
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ u = manager_get_unit(m, name);
+ if (!u) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
+
+ r = unit_kill(u, who, signo, &error);
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetJob")) {
+ uint32_t id;
+ Job *j;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_UINT32, &id,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ j = manager_get_job(m, id);
+ if (!j) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id);
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+
+ SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ path = job_dbus_path(j);
+ if (!path)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ClearJobs")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ manager_clear_jobs(m);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailed")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "reload");
+
+ manager_reset_failed(m);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetFailedUnit")) {
+ const char *name;
+ Unit *u;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ u = manager_get_unit(m, name);
+ if (!u) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "reload");
+
+ unit_reset_failed(u);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnits")) {
+ DBusMessageIter iter, sub;
+ Iterator i;
+ Unit *u;
+ const char *k;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssssssouso)", &sub))
+ goto oom;
+
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+ char *u_path, *j_path;
+ const char *description, *load_state, *active_state, *sub_state, *sjob_type, *following;
+ DBusMessageIter sub2;
+ uint32_t job_id;
+ Unit *f;
+
+ if (k != u->id)
+ continue;
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+ goto oom;
+
+ description = unit_description(u);
+ load_state = unit_load_state_to_string(u->load_state);
+ active_state = unit_active_state_to_string(unit_active_state(u));
+ sub_state = unit_sub_state_to_string(u);
+
+ f = unit_following(u);
+ following = f ? f->id : "";
+
+ u_path = unit_dbus_path(u);
+ if (!u_path)
+ goto oom;
+
+ if (u->job) {
+ job_id = (uint32_t) u->job->id;
+
+ if (!(j_path = job_dbus_path(u->job))) {
+ free(u_path);
+ goto oom;
+ }
+
+ sjob_type = job_type_to_string(u->job->type);
+ } else {
+ job_id = 0;
+ j_path = u_path;
+ sjob_type = "";
+ }
+
+ if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &u->id) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &load_state) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &active_state) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sub_state) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &following) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sjob_type) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path)) {
+ free(u_path);
+ if (u->job)
+ free(j_path);
+ goto oom;
+ }
+
+ free(u_path);
+ if (u->job)
+ free(j_path);
+
+ if (!dbus_message_iter_close_container(&sub, &sub2))
+ goto oom;
+ }
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListJobs")) {
+ DBusMessageIter iter, sub;
+ Iterator i;
+ Job *j;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(usssoo)", &sub))
+ goto oom;
+
+ HASHMAP_FOREACH(j, m->jobs, i) {
+ char *u_path, *j_path;
+ const char *state, *type;
+ uint32_t id;
+ DBusMessageIter sub2;
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+ goto oom;
+
+ id = (uint32_t) j->id;
+ state = job_state_to_string(j->state);
+ type = job_type_to_string(j->type);
+
+ j_path = job_dbus_path(j);
+ if (!j_path)
+ goto oom;
+
+ u_path = unit_dbus_path(j->unit);
+ if (!u_path) {
+ free(j_path);
+ goto oom;
+ }
+
+ if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &id) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &j->unit->id) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path)) {
+ free(j_path);
+ free(u_path);
+ goto oom;
+ }
+
+ free(j_path);
+ free(u_path);
+
+ if (!dbus_message_iter_close_container(&sub, &sub2))
+ goto oom;
+ }
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Subscribe")) {
+ char *client;
+ Set *s;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ s = BUS_CONNECTION_SUBSCRIBED(m, connection);
+ if (!s) {
+ s = set_new(string_hash_func, string_compare_func);
+ if (!s)
+ goto oom;
+
+ if (!dbus_connection_set_data(connection, m->subscribed_data_slot, s, NULL)) {
+ set_free(s);
+ goto oom;
+ }
+ }
+
+ client = strdup(bus_message_get_sender_with_fallback(message));
+ if (!client)
+ goto oom;
+
+ r = set_put(s, client);
+ if (r < 0) {
+ free(client);
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Unsubscribe")) {
+ char *client;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ client = set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) bus_message_get_sender_with_fallback(message));
+ if (!client) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed.");
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+
+ free(client);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Dump")) {
+ FILE *f;
+ char *dump = NULL;
+ size_t size;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ f = open_memstream(&dump, &size);
+ if (!f)
+ goto oom;
+
+ manager_dump_units(m, f, NULL);
+ manager_dump_jobs(m, f, NULL);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(dump);
+ goto oom;
+ }
+
+ fclose(f);
+
+ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &dump, DBUS_TYPE_INVALID)) {
+ free(dump);
+ goto oom;
+ }
+
+ free(dump);
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "CreateSnapshot")) {
+ const char *name;
+ dbus_bool_t cleanup;
+ Snapshot *s;
+
+ SELINUX_ACCESS_CHECK(connection, message, "start");
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_BOOLEAN, &cleanup,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ if (isempty(name))
+ name = NULL;
+
+ r = snapshot_create(m, name, cleanup, &error, &s);
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ path = unit_dbus_path(UNIT(s));
+ if (!path)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+ char *introspection = NULL;
+ FILE *f;
+ Iterator i;
+ Unit *u;
+ Job *j;
+ const char *k;
+ size_t size;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ /* We roll our own introspection code here, instead of
+ * relying on bus_default_message_handler() because we
+ * need to generate our introspection string
+ * dynamically. */
+
+ f = open_memstream(&introspection, &size);
+ if (!f)
+ goto oom;
+
+ fputs(INTROSPECTION_BEGIN, f);
+
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+ char *p;
+
+ if (k != u->id)
+ continue;
+
+ p = bus_path_escape(k);
+ if (!p) {
+ fclose(f);
+ free(introspection);
+ goto oom;
+ }
+
+ fprintf(f, "<node name=\"unit/%s\"/>", p);
+ free(p);
+ }
+
+ HASHMAP_FOREACH(j, m->jobs, i)
+ fprintf(f, "<node name=\"job/%lu\"/>", (unsigned long) j->id);
+
+ fputs(INTROSPECTION_END, f);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(introspection);
+ goto oom;
+ }
+
+ fclose(f);
+
+ if (!introspection)
+ goto oom;
+
+ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
+ free(introspection);
+ goto oom;
+ }
+
+ free(introspection);
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reload")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "reload");
+
+ assert(!m->queued_message);
+
+ /* Instead of sending the reply back right away, we
+ * just remember that we need to and then send it
+ * after the reload is finished. That way the caller
+ * knows when the reload finished. */
+
+ m->queued_message = dbus_message_new_method_return(message);
+ if (!m->queued_message)
+ goto oom;
+
+ m->queued_message_connection = connection;
+ m->exit_code = MANAGER_RELOAD;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reexecute")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "reload");
+
+ /* We don't send a reply back here, the client should
+ * just wait for us disconnecting. */
+
+ m->exit_code = MANAGER_REEXECUTE;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Exit")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "halt");
+
+ if (m->running_as == SYSTEMD_SYSTEM) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers.");
+ return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ m->exit_code = MANAGER_EXIT;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reboot")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ if (m->running_as != SYSTEMD_SYSTEM) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers.");
+ return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ m->exit_code = MANAGER_REBOOT;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PowerOff")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "halt");
+
+ if (m->running_as != SYSTEMD_SYSTEM) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers.");
+ return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ m->exit_code = MANAGER_POWEROFF;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Halt")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "halt");
+
+ if (m->running_as != SYSTEMD_SYSTEM) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Halting is only supported for system managers.");
+ return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ m->exit_code = MANAGER_HALT;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "KExec")) {
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ if (m->running_as != SYSTEMD_SYSTEM) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "kexec is only supported for system managers.");
+ return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+ }
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ m->exit_code = MANAGER_KEXEC;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SwitchRoot")) {
+ const char *switch_root, *switch_root_init;
+ char *u, *v;
+ int k;
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &switch_root,
+ DBUS_TYPE_STRING, &switch_root_init,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ if (path_equal(switch_root, "/") || !path_is_absolute(switch_root))
+ return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+ if (!isempty(switch_root_init) && !path_is_absolute(switch_root_init))
+ return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+ if (m->running_as != SYSTEMD_SYSTEM) {
+ dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Switching root is only supported for system managers.");
+ return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+ }
+
+ /* Safety check */
+ if (isempty(switch_root_init))
+ k = access(switch_root, F_OK);
+ else {
+ char *p;
+
+ p = strjoin(switch_root, "/", switch_root_init, NULL);
+ if (!p)
+ goto oom;
+
+ k = access(p, X_OK);
+ free(p);
+ }
+ if (k < 0)
+ return bus_send_error_reply(connection, message, NULL, -errno);
+
+ u = strdup(switch_root);
+ if (!u)
+ goto oom;
+
+ if (!isempty(switch_root_init)) {
+ v = strdup(switch_root_init);
+ if (!v) {
+ free(u);
+ goto oom;
+ }
+ } else
+ v = NULL;
+
+ free(m->switch_root);
+ free(m->switch_root_init);
+ m->switch_root = u;
+ m->switch_root_init = v;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ m->exit_code = MANAGER_SWITCH_ROOT;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetEnvironment")) {
+ char **l = NULL, **e = NULL;
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ r = bus_parse_strv(message, &l);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r < 0)
+ return bus_send_error_reply(connection, message, NULL, r);
+
+ e = strv_env_merge(2, m->environment, l);
+ strv_free(l);
+ if (!e)
+ goto oom;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) {
+ strv_free(e);
+ goto oom;
+ }
+
+ strv_free(m->environment);
+ m->environment = e;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetEnvironment")) {
+ char **l = NULL, **e = NULL;
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ r = bus_parse_strv(message, &l);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r < 0)
+ return bus_send_error_reply(connection, message, NULL, r);
+
+ e = strv_env_delete(m->environment, 1, l);
+ strv_free(l);
+
+ if (!e)
+ goto oom;
+
+ if (!(reply = dbus_message_new_method_return(message))) {
+ strv_free(e);
+ goto oom;
+ }
+
+ strv_free(m->environment);
+ m->environment = e;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment")) {
+ char **l_set = NULL, **l_unset = NULL, **e = NULL, **f = NULL;
+ DBusMessageIter iter;
+
+ SELINUX_ACCESS_CHECK(connection, message, "reboot");
+
+ if (!dbus_message_iter_init(message, &iter))
+ goto oom;
+
+ r = bus_parse_strv_iter(&iter, &l_unset);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r < 0)
+ return bus_send_error_reply(connection, message, NULL, r);
+
+ if (!dbus_message_iter_next(&iter)) {
+ strv_free(l_unset);
+ return bus_send_error_reply(connection, message, NULL, -EINVAL);
+ }
+
+ r = bus_parse_strv_iter(&iter, &l_set);
+ if (r < 0) {
+ strv_free(l_unset);
+ if (r == -ENOMEM)
+ goto oom;
+
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ e = strv_env_delete(m->environment, 1, l_unset);
+ strv_free(l_unset);
+
+ if (!e) {
+ strv_free(l_set);
+ goto oom;
+ }
+
+ f = strv_env_merge(2, e, l_set);
+ strv_free(l_set);
+ strv_free(e);
+
+ if (!f)
+ goto oom;
+
+ if (!(reply = dbus_message_new_method_return(message))) {
+ strv_free(f);
+ goto oom;
+ }
+
+ strv_free(m->environment);
+ m->environment = f;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnitFiles")) {
+ DBusMessageIter iter, sub, sub2;
+ Hashmap *h;
+ Iterator i;
+ UnitFileList *item;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ h = hashmap_new(string_hash_func, string_compare_func);
+ if (!h)
+ goto oom;
+
+ r = unit_file_get_list(m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h);
+ if (r < 0) {
+ unit_file_list_free(h);
+ dbus_message_unref(reply);
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub)) {
+ unit_file_list_free(h);
+ goto oom;
+ }
+
+ HASHMAP_FOREACH(item, h, i) {
+ const char *state;
+
+ state = unit_file_state_to_string(item->state);
+ assert(state);
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &item->path) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) ||
+ !dbus_message_iter_close_container(&sub, &sub2)) {
+ unit_file_list_free(h);
+ goto oom;
+ }
+ }
+
+ unit_file_list_free(h);
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitFileState")) {
+ const char *name;
+ UnitFileState state;
+ const char *s;
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ state = unit_file_get_state(m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, name);
+ if (state < 0)
+ return bus_send_error_reply(connection, message, NULL, state);
+
+ s = unit_file_state_to_string(state);
+ assert(s);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_STRING, &s,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "EnableUnitFiles") ||
+ dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReenableUnitFiles") ||
+ dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LinkUnitFiles") ||
+ dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PresetUnitFiles") ||
+ dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "MaskUnitFiles")) {
+
+ char **l = NULL;
+ DBusMessageIter iter;
+ UnitFileScope scope = m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER;
+ UnitFileChange *changes = NULL;
+ unsigned n_changes = 0;
+ dbus_bool_t runtime, force;
+ int carries_install_info = -1;
+
+ SELINUX_ACCESS_CHECK(connection, message, streq(member, "MaskUnitFiles") ? "disable" : "enable");
+
+ if (!dbus_message_iter_init(message, &iter))
+ goto oom;
+
+ r = bus_parse_strv_iter(&iter, &l);
+ if (r < 0) {
+ if (r == -ENOMEM)
+ goto oom;
+
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ if (!dbus_message_iter_next(&iter) ||
+ bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0 ||
+ bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &force, false) < 0) {
+ strv_free(l);
+ return bus_send_error_reply(connection, message, NULL, -EIO);
+ }
+
+ if (streq(member, "EnableUnitFiles")) {
+ r = unit_file_enable(scope, runtime, NULL, l, force, &changes, &n_changes);
+ carries_install_info = r;
+ } else if (streq(member, "ReenableUnitFiles")) {
+ r = unit_file_reenable(scope, runtime, NULL, l, force, &changes, &n_changes);
+ carries_install_info = r;
+ } else if (streq(member, "LinkUnitFiles"))
+ r = unit_file_link(scope, runtime, NULL, l, force, &changes, &n_changes);
+ else if (streq(member, "PresetUnitFiles")) {
+ r = unit_file_preset(scope, runtime, NULL, l, force, &changes, &n_changes);
+ carries_install_info = r;
+ } else if (streq(member, "MaskUnitFiles"))
+ r = unit_file_mask(scope, runtime, NULL, l, force, &changes, &n_changes);
+ else
+ assert_not_reached("Uh? Wrong method");
+
+ strv_free(l);
+ bus_manager_send_unit_files_changed(m);
+
+ if (r < 0) {
+ unit_file_changes_free(changes, n_changes);
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ reply = message_from_file_changes(message, changes, n_changes, carries_install_info);
+ unit_file_changes_free(changes, n_changes);
+
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "DisableUnitFiles") ||
+ dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnmaskUnitFiles")) {
+
+ char **l = NULL;
+ DBusMessageIter iter;
+ UnitFileScope scope = m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER;
+ UnitFileChange *changes = NULL;
+ unsigned n_changes = 0;
+ dbus_bool_t runtime;
+
+ SELINUX_ACCESS_CHECK(connection, message, streq(member, "UnmaskUnitFiles") ? "enable" : "disable");
+
+ if (!dbus_message_iter_init(message, &iter))
+ goto oom;
+
+ r = bus_parse_strv_iter(&iter, &l);
+ if (r < 0) {
+ if (r == -ENOMEM)
+ goto oom;
+
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ if (!dbus_message_iter_next(&iter) ||
+ bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, false) < 0) {
+ strv_free(l);
+ return bus_send_error_reply(connection, message, NULL, -EIO);
+ }
+
+ if (streq(member, "DisableUnitFiles"))
+ r = unit_file_disable(scope, runtime, NULL, l, &changes, &n_changes);
+ else if (streq(member, "UnmaskUnitFiles"))
+ r = unit_file_unmask(scope, runtime, NULL, l, &changes, &n_changes);
+ else
+ assert_not_reached("Uh? Wrong method");
+
+ strv_free(l);
+ bus_manager_send_unit_files_changed(m);
+
+ if (r < 0) {
+ unit_file_changes_free(changes, n_changes);
+ return bus_send_error_reply(connection, message, NULL, r);
+ }
+
+ reply = message_from_file_changes(message, changes, n_changes, -1);
+ unit_file_changes_free(changes, n_changes);
+
+ if (!reply)
+ goto oom;
+
+ } else {
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string },
+ { "org.freedesktop.systemd1.Manager", bus_manager_properties, m },
+ { NULL, }
+ };
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, bps);
+ }
+
+ if (job_type != _JOB_TYPE_INVALID) {
+ const char *name, *smode, *old_name = NULL;
+ JobMode mode;
+ Unit *u;
+ dbus_bool_t b;
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartUnitReplace"))
+ b = dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &old_name,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &smode,
+ DBUS_TYPE_INVALID);
+ else
+ b = dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &smode,
+ DBUS_TYPE_INVALID);
+ if (!b)
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ if (old_name) {
+ u = manager_get_unit(m, old_name);
+ if (!u || !u->job || u->job->type != JOB_START) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name);
+ return bus_send_error_reply(connection, message, &error, -ENOENT);
+ }
+ }
+
+ mode = job_mode_from_string(smode);
+ if (mode < 0) {
+ dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode);
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+ }
+
+ r = manager_load_unit(m, name, NULL, &error, &u);
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ return bus_unit_queue_job(connection, message, u, job_type, mode, reload_if_possible);
+ }
+
+ if (reply)
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+const DBusObjectPathVTable bus_manager_vtable = {
+ .message_function = bus_manager_message_handler
+};
diff --git a/src/core/dbus-manager.h b/src/core/dbus-manager.h
new file mode 100644
index 0000000000..f0dce5a2e9
--- /dev/null
+++ b/src/core/dbus-manager.h
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+extern const DBusObjectPathVTable bus_manager_vtable;
+
+extern const char bus_manager_interface[];
diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c
new file mode 100644
index 0000000000..d81edeb807
--- /dev/null
+++ b/src/core/dbus-mount.c
@@ -0,0 +1,168 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-mount.h"
+#include "dbus-kill.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_MOUNT_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Mount\">\n" \
+ " <property name=\"Where\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"What\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Options\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Type\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+ BUS_EXEC_COMMAND_INTERFACE("ExecMount") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecUnmount") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecRemount") \
+ BUS_EXEC_CONTEXT_INTERFACE \
+ BUS_KILL_CONTEXT_INTERFACE \
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_MOUNT_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Mount\0"
+
+const char bus_mount_interface[] _introspect_("Mount") = BUS_MOUNT_INTERFACE;
+
+const char bus_mount_invalidating_properties[] =
+ "What\0"
+ "Options\0"
+ "Type\0"
+ "ExecMount\0"
+ "ExecUnmount\0"
+ "ExecRemount\0"
+ "ControlPID\0"
+ "Result\0";
+
+static int bus_mount_append_what(DBusMessageIter *i, const char *property, void *data) {
+ Mount *m = data;
+ const char *d;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what)
+ d = m->parameters_proc_self_mountinfo.what;
+ else if (m->from_fragment && m->parameters_fragment.what)
+ d = m->parameters_fragment.what;
+ else
+ d = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_mount_append_options(DBusMessageIter *i, const char *property, void *data) {
+ Mount *m = data;
+ const char *d;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.options)
+ d = m->parameters_proc_self_mountinfo.options;
+ else if (m->from_fragment && m->parameters_fragment.options)
+ d = m->parameters_fragment.options;
+ else
+ d = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_mount_append_type(DBusMessageIter *i, const char *property, void *data) {
+ Mount *m = data;
+ const char *d;
+
+ assert(i);
+ assert(property);
+ assert(m);
+
+ if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.fstype)
+ d = m->parameters_proc_self_mountinfo.fstype;
+ else if (m->from_fragment && m->parameters_fragment.fstype)
+ d = m->parameters_fragment.fstype;
+ else
+ d = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_mount_append_mount_result, mount_result, MountResult);
+
+static const BusProperty bus_mount_properties[] = {
+ { "Where", bus_property_append_string, "s", offsetof(Mount, where), true },
+ { "What", bus_mount_append_what, "s", 0 },
+ { "Options", bus_mount_append_options, "s", 0 },
+ { "Type", bus_mount_append_type, "s", 0 },
+ { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Mount, timeout_usec) },
+ BUS_EXEC_COMMAND_PROPERTY("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), false),
+ BUS_EXEC_COMMAND_PROPERTY("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), false),
+ BUS_EXEC_COMMAND_PROPERTY("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), false),
+ { "ControlPID", bus_property_append_pid, "u", offsetof(Mount, control_pid) },
+ { "DirectoryMode", bus_property_append_mode, "u", offsetof(Mount, directory_mode) },
+ { "Result", bus_mount_append_mount_result, "s", offsetof(Mount, result) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Mount *m = MOUNT(u);
+
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Mount", bus_mount_properties, m },
+ { "org.freedesktop.systemd1.Mount", bus_exec_context_properties, &m->exec_context },
+ { "org.freedesktop.systemd1.Mount", bus_kill_context_properties, &m->kill_context },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps );
+}
diff --git a/src/core/dbus-mount.h b/src/core/dbus-mount.h
new file mode 100644
index 0000000000..8597394373
--- /dev/null
+++ b/src/core/dbus-mount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_mount_interface[];
+extern const char bus_mount_invalidating_properties[];
diff --git a/src/core/dbus-path.c b/src/core/dbus-path.c
new file mode 100644
index 0000000000..f7fed1754d
--- /dev/null
+++ b/src/core/dbus-path.c
@@ -0,0 +1,122 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-path.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_PATH_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Path\">\n" \
+ " <property name=\"Unit\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Paths\" type=\"a(ss)\" access=\"read\"/>\n" \
+ " <property name=\"MakeDirectory\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_PATH_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Path\0"
+
+const char bus_path_interface[] _introspect_("Path") = BUS_PATH_INTERFACE;
+
+const char bus_path_invalidating_properties[] =
+ "Result\0";
+
+static int bus_path_append_paths(DBusMessageIter *i, const char *property, void *data) {
+ Path *p = data;
+ DBusMessageIter sub, sub2;
+ PathSpec *k;
+
+ assert(i);
+ assert(property);
+ assert(p);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(ss)", &sub))
+ return -ENOMEM;
+
+ LIST_FOREACH(spec, k, p->specs) {
+ const char *t = path_type_to_string(k->type);
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &t) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &k->path) ||
+ !dbus_message_iter_close_container(&sub, &sub2))
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_path_append_unit(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ Path *p = PATH(u);
+ const char *t;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ t = UNIT_DEREF(p->unit) ? UNIT_DEREF(p->unit)->id : "";
+
+ return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM;
+}
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_path_append_path_result, path_result, PathResult);
+
+static const BusProperty bus_path_properties[] = {
+ { "Unit", bus_path_append_unit, "s", 0 },
+ { "Paths", bus_path_append_paths, "a(ss)", 0 },
+ { "MakeDirectory", bus_property_append_bool, "b", offsetof(Path, make_directory) },
+ { "DirectoryMode", bus_property_append_mode, "u", offsetof(Path, directory_mode) },
+ { "Result", bus_path_append_path_result, "s", offsetof(Path, result) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Path *p = PATH(u);
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Path", bus_path_properties, p },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-path.h b/src/core/dbus-path.h
new file mode 100644
index 0000000000..c945f7d588
--- /dev/null
+++ b/src/core/dbus-path.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_path_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_path_interface[];
+
+extern const char bus_path_invalidating_properties[];
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
new file mode 100644
index 0000000000..d99058dd46
--- /dev/null
+++ b/src/core/dbus-service.c
@@ -0,0 +1,161 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-execute.h"
+#include "dbus-kill.h"
+#include "dbus-service.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_SERVICE_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Service\">\n" \
+ " <property name=\"Type\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Restart\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"PIDFile\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"NotifyAccess\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"RestartUSec\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"WatchdogUSec\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"WatchdogTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"WatchdogTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"StartLimitInterval\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"StartLimitBurst\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"StartLimitAction\" type=\"s\" access=\"readwrite\"/>\n" \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStart") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecReload") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStop") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \
+ BUS_EXEC_CONTEXT_INTERFACE \
+ BUS_KILL_CONTEXT_INTERFACE \
+ " <property name=\"PermissionsStartOnly\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"RootDirectoryStartOnly\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"RemainAfterExit\" type=\"b\" access=\"read\"/>\n" \
+ BUS_EXEC_STATUS_INTERFACE("ExecMain") \
+ " <property name=\"MainPID\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"BusName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"StatusText\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_SERVICE_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Service\0"
+
+const char bus_service_interface[] _introspect_("Service") = BUS_SERVICE_INTERFACE;
+
+const char bus_service_invalidating_properties[] =
+ "ExecStartPre\0"
+ "ExecStart\0"
+ "ExecStartPost\0"
+ "ExecReload\0"
+ "ExecStop\0"
+ "ExecStopPost\0"
+ "ExecMain\0"
+ "WatchdogTimestamp\0"
+ "WatchdogTimestampMonotonic\0"
+ "MainPID\0"
+ "ControlPID\0"
+ "StatusText\0"
+ "Result\0";
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_type, service_type, ServiceType);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_restart, service_restart, ServiceRestart);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_notify_access, notify_access, NotifyAccess);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_service_result, service_result, ServiceResult);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_start_limit_action, start_limit_action, StartLimitAction);
+static DEFINE_BUS_PROPERTY_SET_ENUM(bus_service_set_start_limit_action, start_limit_action, StartLimitAction);
+
+static const BusProperty bus_exec_main_status_properties[] = {
+ { "ExecMainStartTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) },
+ { "ExecMainStartTimestampMonotonic",bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) },
+ { "ExecMainExitTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) },
+ { "ExecMainExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) },
+ { "ExecMainPID", bus_property_append_pid, "u", offsetof(ExecStatus, pid) },
+ { "ExecMainCode", bus_property_append_int, "i", offsetof(ExecStatus, code) },
+ { "ExecMainStatus", bus_property_append_int, "i", offsetof(ExecStatus, status) },
+ { NULL, }
+};
+
+static const BusProperty bus_service_properties[] = {
+ { "Type", bus_service_append_type, "s", offsetof(Service, type) },
+ { "Restart", bus_service_append_restart, "s", offsetof(Service, restart) },
+ { "PIDFile", bus_property_append_string, "s", offsetof(Service, pid_file), true },
+ { "NotifyAccess", bus_service_append_notify_access, "s", offsetof(Service, notify_access) },
+ { "RestartUSec", bus_property_append_usec, "t", offsetof(Service, restart_usec) },
+ { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Service, timeout_start_usec) },
+ { "TimeoutStartUSec", bus_property_append_usec, "t", offsetof(Service, timeout_start_usec) },
+ { "TimeoutStopUSec", bus_property_append_usec, "t", offsetof(Service, timeout_stop_usec) },
+ { "WatchdogUSec", bus_property_append_usec, "t", offsetof(Service, watchdog_usec) },
+ { "WatchdogTimestamp", bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.realtime) },
+ { "WatchdogTimestampMonotonic",bus_property_append_usec, "t", offsetof(Service, watchdog_timestamp.monotonic) },
+ { "StartLimitInterval", bus_property_append_usec, "t", offsetof(Service, start_limit.interval) },
+ { "StartLimitBurst", bus_property_append_uint32, "u", offsetof(Service, start_limit.burst) },
+ { "StartLimitAction", bus_service_append_start_limit_action,"s", offsetof(Service, start_limit_action), false, bus_service_set_start_limit_action},
+ BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), true ),
+ { "PermissionsStartOnly", bus_property_append_bool, "b", offsetof(Service, permissions_start_only) },
+ { "RootDirectoryStartOnly", bus_property_append_bool, "b", offsetof(Service, root_directory_start_only) },
+ { "RemainAfterExit", bus_property_append_bool, "b", offsetof(Service, remain_after_exit) },
+ { "GuessMainPID", bus_property_append_bool, "b", offsetof(Service, guess_main_pid) },
+ { "MainPID", bus_property_append_pid, "u", offsetof(Service, main_pid) },
+ { "ControlPID", bus_property_append_pid, "u", offsetof(Service, control_pid) },
+ { "BusName", bus_property_append_string, "s", offsetof(Service, bus_name), true },
+ { "StatusText", bus_property_append_string, "s", offsetof(Service, status_text), true },
+ { "Result", bus_service_append_service_result,"s", offsetof(Service, result) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connection, DBusMessage *message) {
+ Service *s = SERVICE(u);
+
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Service", bus_service_properties, s },
+ { "org.freedesktop.systemd1.Service", bus_exec_context_properties, &s->exec_context },
+ { "org.freedesktop.systemd1.Service", bus_kill_context_properties, &s->kill_context },
+ { "org.freedesktop.systemd1.Service", bus_exec_main_status_properties, &s->main_exec_status },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
+
+ return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h
new file mode 100644
index 0000000000..143aed7ae5
--- /dev/null
+++ b/src/core/dbus-service.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_service_interface[];
+extern const char bus_service_invalidating_properties[];
diff --git a/src/core/dbus-snapshot.c b/src/core/dbus-snapshot.c
new file mode 100644
index 0000000000..435c6df39c
--- /dev/null
+++ b/src/core/dbus-snapshot.c
@@ -0,0 +1,94 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "dbus-unit.h"
+#include "dbus-snapshot.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_SNAPSHOT_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Snapshot\">\n" \
+ " <method name=\"Remove\"/>\n" \
+ " <property name=\"Cleanup\" type=\"b\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_SNAPSHOT_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Snapshot\0"
+
+const char bus_snapshot_interface[] _introspect_("Snapshot") = BUS_SNAPSHOT_INTERFACE;
+
+static const BusProperty bus_snapshot_properties[] = {
+ { "Cleanup", bus_property_append_bool, "b", offsetof(Snapshot, cleanup) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Snapshot *s = SNAPSHOT(u);
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Snapshot", "Remove")) {
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "stop");
+
+ snapshot_remove(SNAPSHOT(u));
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else {
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Snapshot", bus_snapshot_properties, s },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+ }
+
+ if (reply) {
+ if (!dbus_connection_send(c, reply, NULL))
+ goto oom;
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
diff --git a/src/core/dbus-snapshot.h b/src/core/dbus-snapshot.h
new file mode 100644
index 0000000000..1208aafff6
--- /dev/null
+++ b/src/core/dbus-snapshot.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_snapshot_interface[];
diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
new file mode 100644
index 0000000000..095a031612
--- /dev/null
+++ b/src/core/dbus-socket.c
@@ -0,0 +1,151 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-socket.h"
+#include "dbus-execute.h"
+#include "dbus-kill.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_SOCKET_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Socket\">\n" \
+ " <property name=\"BindIPv6Only\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Backlog\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStopPre") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \
+ BUS_EXEC_CONTEXT_INTERFACE \
+ BUS_KILL_CONTEXT_INTERFACE \
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"BindToDevice\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"SocketMode\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Accept\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"KeepAlive\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Priority\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"ReceiveBuffer\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"SendBuffer\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"IPTOS\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"IPTTL\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"PipeSize\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"FreeBind\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Transparent\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Broadcast\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"PassCredentials\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"PassSecurity\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Mark\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"MaxConnections\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"NAccepted\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"NConnections\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"MessageQueueMaxMessages\" type=\"x\" access=\"read\"/>\n" \
+ " <property name=\"MessageQueueMessageSize\" type=\"x\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SmackLabel\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SmackLabelIPIn\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SmackLabelIPOut\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n" \
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_SOCKET_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Socket\0"
+
+const char bus_socket_interface[] _introspect_("Socket") = BUS_SOCKET_INTERFACE;
+
+const char bus_socket_invalidating_properties[] =
+ "ExecStartPre\0"
+ "ExecStartPost\0"
+ "ExecStopPre\0"
+ "ExecStopPost\0"
+ "ControlPID\0"
+ "NAccepted\0"
+ "NConnections\0"
+ "Result\0";
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_socket_result, socket_result, SocketResult);
+
+static const BusProperty bus_socket_properties[] = {
+ { "BindIPv6Only", bus_socket_append_bind_ipv6_only, "s", offsetof(Socket, bind_ipv6_only) },
+ { "Backlog", bus_property_append_unsigned, "u", offsetof(Socket, backlog) },
+ { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Socket, timeout_usec) },
+ BUS_EXEC_COMMAND_PROPERTY("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), true ),
+ BUS_EXEC_COMMAND_PROPERTY("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), true ),
+ { "ControlPID", bus_property_append_pid, "u", offsetof(Socket, control_pid) },
+ { "BindToDevice", bus_property_append_string, "s", offsetof(Socket, bind_to_device), true },
+ { "DirectoryMode", bus_property_append_mode, "u", offsetof(Socket, directory_mode) },
+ { "SocketMode", bus_property_append_mode, "u", offsetof(Socket, socket_mode) },
+ { "Accept", bus_property_append_bool, "b", offsetof(Socket, accept) },
+ { "KeepAlive", bus_property_append_bool, "b", offsetof(Socket, keep_alive) },
+ { "Priority", bus_property_append_int, "i", offsetof(Socket, priority) },
+ { "ReceiveBuffer", bus_property_append_size, "t", offsetof(Socket, receive_buffer) },
+ { "SendBuffer", bus_property_append_size, "t", offsetof(Socket, send_buffer) },
+ { "IPTOS", bus_property_append_int, "i", offsetof(Socket, ip_tos) },
+ { "IPTTL", bus_property_append_int, "i", offsetof(Socket, ip_ttl) },
+ { "PipeSize", bus_property_append_size, "t", offsetof(Socket, pipe_size) },
+ { "FreeBind", bus_property_append_bool, "b", offsetof(Socket, free_bind) },
+ { "Transparent", bus_property_append_bool, "b", offsetof(Socket, transparent) },
+ { "Broadcast", bus_property_append_bool, "b", offsetof(Socket, broadcast) },
+ { "PassCredentials",bus_property_append_bool, "b", offsetof(Socket, pass_cred) },
+ { "PassSecurity", bus_property_append_bool, "b", offsetof(Socket, pass_sec) },
+ { "Mark", bus_property_append_int, "i", offsetof(Socket, mark) },
+ { "MaxConnections", bus_property_append_unsigned, "u", offsetof(Socket, max_connections) },
+ { "NConnections", bus_property_append_unsigned, "u", offsetof(Socket, n_connections) },
+ { "NAccepted", bus_property_append_unsigned, "u", offsetof(Socket, n_accepted) },
+ { "MessageQueueMaxMessages", bus_property_append_long, "x", offsetof(Socket, mq_maxmsg) },
+ { "MessageQueueMessageSize", bus_property_append_long, "x", offsetof(Socket, mq_msgsize) },
+ { "Result", bus_socket_append_socket_result, "s", offsetof(Socket, result) },
+ { "SmackLabel", bus_property_append_string, "s", offsetof(Socket, smack), true },
+ { "SmackLabelIPIn", bus_property_append_string, "s", offsetof(Socket, smack_ip_in), true },
+ { "SmackLabelIPOut",bus_property_append_string, "s", offsetof(Socket, smack_ip_out), true },
+ { NULL, }
+};
+
+DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Socket *s = SOCKET(u);
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Socket", bus_socket_properties, s },
+ { "org.freedesktop.systemd1.Socket", bus_exec_context_properties, &s->exec_context },
+ { "org.freedesktop.systemd1.Socket", bus_kill_context_properties, &s->kill_context },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-socket.h b/src/core/dbus-socket.h
new file mode 100644
index 0000000000..5369b22e5e
--- /dev/null
+++ b/src/core/dbus-socket.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_socket_interface[];
+extern const char bus_socket_invalidating_properties[];
diff --git a/src/core/dbus-swap.c b/src/core/dbus-swap.c
new file mode 100644
index 0000000000..67ea0f24fe
--- /dev/null
+++ b/src/core/dbus-swap.c
@@ -0,0 +1,115 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Maarten Lankhorst
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-swap.h"
+#include "dbus-execute.h"
+#include "dbus-kill.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_SWAP_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Swap\">\n" \
+ " <property name=\"What\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Priority\" type=\"i\" access=\"read\"/>\n" \
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+ BUS_EXEC_COMMAND_INTERFACE("ExecActivate") \
+ BUS_EXEC_COMMAND_INTERFACE("ExecDeactivate") \
+ BUS_EXEC_CONTEXT_INTERFACE \
+ BUS_KILL_CONTEXT_INTERFACE \
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_SWAP_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Swap\0"
+
+const char bus_swap_interface[] _introspect_("Swap") = BUS_SWAP_INTERFACE;
+
+const char bus_swap_invalidating_properties[] =
+ "What\0"
+ "Priority\0"
+ "ExecActivate\0"
+ "ExecDeactivate\0"
+ "ControlPID\0"
+ "Result\0";
+
+static int bus_swap_append_priority(DBusMessageIter *i, const char *property, void *data) {
+ Swap *s = data;
+ dbus_int32_t j;
+
+ assert(i);
+ assert(property);
+ assert(s);
+
+ if (s->from_proc_swaps)
+ j = s->parameters_proc_swaps.priority;
+ else if (s->from_fragment)
+ j = s->parameters_fragment.priority;
+ else
+ j = -1;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &j))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_swap_append_swap_result, swap_result, SwapResult);
+
+static const BusProperty bus_swap_properties[] = {
+ { "What", bus_property_append_string, "s", offsetof(Swap, what), true },
+ { "Priority", bus_swap_append_priority, "i", 0 },
+ BUS_EXEC_COMMAND_PROPERTY("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), false),
+ BUS_EXEC_COMMAND_PROPERTY("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), false),
+ { "ControlPID", bus_property_append_pid, "u", offsetof(Swap, control_pid) },
+ { "Result", bus_swap_append_swap_result,"s", offsetof(Swap, result) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Swap *s = SWAP(u);
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Swap", bus_swap_properties, s },
+ { "org.freedesktop.systemd1.Swap", bus_exec_context_properties, &s->exec_context },
+ { "org.freedesktop.systemd1.Swap", bus_kill_context_properties, &s->kill_context },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-swap.h b/src/core/dbus-swap.h
new file mode 100644
index 0000000000..41fe4447ff
--- /dev/null
+++ b/src/core/dbus-swap.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Maarten Lankhorst
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_swap_interface[];
+extern const char bus_swap_invalidating_properties[];
diff --git a/src/core/dbus-target.c b/src/core/dbus-target.c
new file mode 100644
index 0000000000..6a775506cc
--- /dev/null
+++ b/src/core/dbus-target.c
@@ -0,0 +1,58 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-target.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_TARGET_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Target\">\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_TARGET_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Target\0"
+
+const char bus_target_interface[] _introspect_("Target") = BUS_TARGET_INTERFACE;
+
+DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-target.h b/src/core/dbus-target.h
new file mode 100644
index 0000000000..a8a0304c75
--- /dev/null
+++ b/src/core/dbus-target.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_target_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_target_interface[];
diff --git a/src/core/dbus-timer.c b/src/core/dbus-timer.c
new file mode 100644
index 0000000000..84b823c9a4
--- /dev/null
+++ b/src/core/dbus-timer.c
@@ -0,0 +1,140 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-timer.h"
+#include "dbus-execute.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+#define BUS_TIMER_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Timer\">\n" \
+ " <property name=\"Unit\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Timers\" type=\"a(stt)\" access=\"read\"/>\n" \
+ " <property name=\"NextElapseUSec\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define INTROSPECTION \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>\n" \
+ BUS_UNIT_INTERFACE \
+ BUS_TIMER_INTERFACE \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_PEER_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE \
+ "</node>\n"
+
+#define INTERFACES_LIST \
+ BUS_UNIT_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Timer\0"
+
+const char bus_timer_interface[] _introspect_("Timer") = BUS_TIMER_INTERFACE;
+
+const char bus_timer_invalidating_properties[] =
+ "Timers\0"
+ "NextElapseUSec\0"
+ "Result\0";
+
+static int bus_timer_append_timers(DBusMessageIter *i, const char *property, void *data) {
+ Timer *p = data;
+ DBusMessageIter sub, sub2;
+ TimerValue *k;
+
+ assert(i);
+ assert(property);
+ assert(p);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(stt)", &sub))
+ return -ENOMEM;
+
+ LIST_FOREACH(value, k, p->values) {
+ char *buf;
+ const char *t;
+ size_t l;
+ bool b;
+
+ t = timer_base_to_string(k->base);
+ assert(endswith(t, "Sec"));
+
+ /* s/Sec/USec/ */
+ l = strlen(t);
+ if (!(buf = new(char, l+2)))
+ return -ENOMEM;
+
+ memcpy(buf, t, l-3);
+ memcpy(buf+l-3, "USec", 5);
+
+ b = dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
+ dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &buf) &&
+ dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->value) &&
+ dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &k->next_elapse) &&
+ dbus_message_iter_close_container(&sub, &sub2);
+
+ free(buf);
+ if (!b)
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_timer_append_unit(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ Timer *timer = TIMER(u);
+ const char *t;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ t = UNIT_DEREF(timer->unit) ? UNIT_DEREF(timer->unit)->id : "";
+
+ return dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t) ? 0 : -ENOMEM;
+}
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_timer_append_timer_result, timer_result, TimerResult);
+
+static const BusProperty bus_timer_properties[] = {
+ { "Unit", bus_timer_append_unit, "s", 0 },
+ { "Timers", bus_timer_append_timers, "a(stt)", 0 },
+ { "NextElapseUSec", bus_property_append_usec, "t", offsetof(Timer, next_elapse) },
+ { "Result", bus_timer_append_timer_result,"s", offsetof(Timer, result) },
+ { NULL, }
+};
+
+DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) {
+ Timer *t = TIMER(u);
+ const BusBoundProperties bps[] = {
+ { "org.freedesktop.systemd1.Unit", bus_unit_properties, u },
+ { "org.freedesktop.systemd1.Timer", bus_timer_properties, t },
+ { NULL, }
+ };
+
+ SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
+
+ return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
+}
diff --git a/src/core/dbus-timer.h b/src/core/dbus-timer.h
new file mode 100644
index 0000000000..9ac30501d1
--- /dev/null
+++ b/src/core/dbus-timer.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_timer_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
+
+extern const char bus_timer_interface[];
+extern const char bus_timer_invalidating_properties[];
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
new file mode 100644
index 0000000000..83ee018ff3
--- /dev/null
+++ b/src/core/dbus-unit.c
@@ -0,0 +1,887 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-unit.h"
+#include "bus-errors.h"
+#include "dbus-common.h"
+#include "selinux-access.h"
+
+const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE;
+
+#define INVALIDATING_PROPERTIES \
+ "LoadState\0" \
+ "ActiveState\0" \
+ "SubState\0" \
+ "InactiveExitTimestamp\0" \
+ "ActiveEnterTimestamp\0" \
+ "ActiveExitTimestamp\0" \
+ "InactiveEnterTimestamp\0" \
+ "Job\0" \
+ "NeedDaemonReload\0"
+
+static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) {
+ char *t;
+ Iterator j;
+ DBusMessageIter sub;
+ Unit *u = data;
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
+ return -ENOMEM;
+
+ SET_FOREACH(t, u->names, j)
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data, *f;
+ const char *d;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ f = unit_following(u);
+ d = f ? f->id : "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u;
+ Iterator j;
+ DBusMessageIter sub;
+ Set *s = data;
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
+ return -ENOMEM;
+
+ SET_FOREACH(u, s, j)
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *d;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ d = unit_description(u);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState);
+
+static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *state;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ state = unit_active_state_to_string(unit_active_state(u));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *state;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ state = unit_sub_state_to_string(u);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *state;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u)));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ b = unit_can_start(u) &&
+ !u->refuse_manual_start;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ /* On the lower levels we assume that every unit we can start
+ * we can also stop */
+
+ b = unit_can_start(u) &&
+ !u->refuse_manual_stop;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ b = unit_can_reload(u);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ b = unit_can_isolate(u) &&
+ !u->refuse_manual_start;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ DBusMessageIter sub;
+ char *p;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+ return -ENOMEM;
+
+ if (u->job) {
+
+ if (!(p = job_dbus_path(u->job)))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) ||
+ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
+ free(p);
+ return -ENOMEM;
+ }
+ } else {
+ uint32_t id = 0;
+
+ /* No job, so let's fill in some placeholder
+ * data. Since we need to fill in a valid path we
+ * simple point to ourselves. */
+
+ if (!(p = unit_dbus_path(u)))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) ||
+ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
+ free(p);
+ return -ENOMEM;
+ }
+ }
+
+ free(p);
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ char *t;
+ CGroupBonding *cgb;
+ bool success;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ if ((cgb = unit_get_default_cgroup(u))) {
+ if (!(t = cgroup_bonding_to_string(cgb)))
+ return -ENOMEM;
+ } else
+ t = (char*) "";
+
+ success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t);
+
+ if (cgb)
+ free(t);
+
+ return success ? 0 : -ENOMEM;
+}
+
+static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ CGroupBonding *cgb;
+ DBusMessageIter sub;
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
+ return -ENOMEM;
+
+ LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) {
+ char *t;
+ bool success;
+
+ if (!(t = cgroup_bonding_to_string(cgb)))
+ return -ENOMEM;
+
+ success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t);
+ free(t);
+
+ if (!success)
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ CGroupAttribute *a;
+ DBusMessageIter sub, sub2;
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub))
+ return -ENOMEM;
+
+ LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
+ char *v = NULL;
+ bool success;
+
+ if (a->map_callback)
+ a->map_callback(a->controller, a->name, a->value, &v);
+
+ success =
+ dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
+ dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) &&
+ dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) &&
+ dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) &&
+ dbus_message_iter_close_container(&sub, &sub2);
+
+ free(v);
+
+ if (!success)
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ b = unit_need_daemon_reload(u);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *name, *message;
+ DBusMessageIter sub;
+
+ assert(i);
+ assert(property);
+ assert(u);
+
+ if (u->load_error != 0) {
+ name = bus_errno_to_dbus(u->load_error);
+ message = strempty(strerror(-u->load_error));
+ } else
+ name = message = "";
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) ||
+ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) ||
+ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) ||
+ !dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) {
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ DBusError error;
+ JobType job_type = _JOB_TYPE_INVALID;
+ bool reload_if_possible = false;
+ int r;
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start"))
+ job_type = JOB_START;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop"))
+ job_type = JOB_STOP;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload"))
+ job_type = JOB_RELOAD;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart"))
+ job_type = JOB_RESTART;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart"))
+ job_type = JOB_TRY_RESTART;
+ else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) {
+ reload_if_possible = true;
+ job_type = JOB_RESTART;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) {
+ reload_if_possible = true;
+ job_type = JOB_TRY_RESTART;
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) {
+ const char *swho;
+ int32_t signo;
+ KillWho who;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &swho,
+ DBUS_TYPE_INT32, &signo,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ if (isempty(swho))
+ who = KILL_ALL;
+ else {
+ who = kill_who_from_string(swho);
+ if (who < 0)
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+ }
+
+ if (signo <= 0 || signo >= _NSIG)
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
+
+ r = unit_kill(u, who, signo, &error);
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) {
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "reload");
+
+ unit_reset_failed(u);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ } else if (UNIT_VTABLE(u)->bus_message_handler)
+ return UNIT_VTABLE(u)->bus_message_handler(u, connection, message);
+ else
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (job_type != _JOB_TYPE_INVALID) {
+ const char *smode;
+ JobMode mode;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &smode,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+ mode = job_mode_from_string(smode);
+ if (mode < 0) {
+ dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode);
+ return bus_send_error_reply(connection, message, &error, -EINVAL);
+ }
+
+ return bus_unit_queue_job(connection, message, u, job_type, mode, reload_if_possible);
+ }
+
+ if (reply)
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ dbus_error_free(&error);
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+ Unit *u;
+ int r;
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ DBusError error;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) {
+ /* Be nice to gdbus and return introspection data for our mid-level paths */
+
+ SELINUX_ACCESS_CHECK(connection, message, "status");
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+ char *introspection = NULL;
+ FILE *f;
+ Iterator i;
+ const char *k;
+ size_t size;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ /* We roll our own introspection code here, instead of
+ * relying on bus_default_message_handler() because we
+ * need to generate our introspection string
+ * dynamically. */
+
+ f = open_memstream(&introspection, &size);
+ if (!f)
+ goto oom;
+
+ fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>\n", f);
+
+ fputs(BUS_INTROSPECTABLE_INTERFACE, f);
+ fputs(BUS_PEER_INTERFACE, f);
+
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+ char *p;
+
+ if (k != u->id)
+ continue;
+
+ p = bus_path_escape(k);
+ if (!p) {
+ fclose(f);
+ free(introspection);
+ goto oom;
+ }
+
+ fprintf(f, "<node name=\"%s\"/>", p);
+ free(p);
+ }
+
+ fputs("</node>\n", f);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(introspection);
+ goto oom;
+ }
+
+ fclose(f);
+
+ if (!introspection)
+ goto oom;
+
+ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
+ free(introspection);
+ goto oom;
+ }
+
+ free(introspection);
+
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ r = manager_load_unit_from_dbus_path(m, dbus_message_get_path(message), &error, &u);
+ if (r == -ENOMEM)
+ goto oom;
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ return bus_unit_message_dispatch(u, connection, message);
+
+oom:
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+const DBusObjectPathVTable bus_unit_vtable = {
+ .message_function = bus_unit_message_handler
+};
+
+void bus_unit_send_change_signal(Unit *u) {
+ char *p = NULL;
+ DBusMessage *m = NULL;
+
+ assert(u);
+
+ if (u->in_dbus_queue) {
+ LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u);
+ u->in_dbus_queue = false;
+ }
+
+ if (!u->id)
+ return;
+
+ if (!bus_has_subscriber(u->manager)) {
+ u->sent_dbus_new_signal = true;
+ return;
+ }
+
+ if (!(p = unit_dbus_path(u)))
+ goto oom;
+
+ if (u->sent_dbus_new_signal) {
+ /* Send a properties changed signal. First for the
+ * specific type, then for the generic unit. The
+ * clients may rely on this order to get atomic
+ * behavior if needed. */
+
+ if (UNIT_VTABLE(u)->bus_invalidating_properties) {
+
+ if (!(m = bus_properties_changed_new(p,
+ UNIT_VTABLE(u)->bus_interface,
+ UNIT_VTABLE(u)->bus_invalidating_properties)))
+ goto oom;
+
+ if (bus_broadcast(u->manager, m) < 0)
+ goto oom;
+
+ dbus_message_unref(m);
+ }
+
+ if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES)))
+ goto oom;
+
+ } else {
+ /* Send a new signal */
+
+ if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew")))
+ goto oom;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &u->id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ }
+
+ if (bus_broadcast(u->manager, m) < 0)
+ goto oom;
+
+ free(p);
+ dbus_message_unref(m);
+
+ u->sent_dbus_new_signal = true;
+
+ return;
+
+oom:
+ free(p);
+
+ if (m)
+ dbus_message_unref(m);
+
+ log_error("Failed to allocate unit change/new signal.");
+}
+
+void bus_unit_send_removed_signal(Unit *u) {
+ char *p = NULL;
+ DBusMessage *m = NULL;
+
+ assert(u);
+
+ if (!bus_has_subscriber(u->manager))
+ return;
+
+ if (!u->sent_dbus_new_signal)
+ bus_unit_send_change_signal(u);
+
+ if (!u->id)
+ return;
+
+ if (!(p = unit_dbus_path(u)))
+ goto oom;
+
+ if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved")))
+ goto oom;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &u->id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (bus_broadcast(u->manager, m) < 0)
+ goto oom;
+
+ free(p);
+ dbus_message_unref(m);
+
+ return;
+
+oom:
+ free(p);
+
+ if (m)
+ dbus_message_unref(m);
+
+ log_error("Failed to allocate unit remove signal.");
+}
+
+DBusHandlerResult bus_unit_queue_job(
+ DBusConnection *connection,
+ DBusMessage *message,
+ Unit *u,
+ JobType type,
+ JobMode mode,
+ bool reload_if_possible) {
+
+ _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+ _cleanup_free_ char *path = NULL;
+ Job *j;
+ JobBusClient *cl;
+ DBusError error;
+ int r;
+
+ assert(connection);
+ assert(message);
+ assert(u);
+ assert(type >= 0 && type < _JOB_TYPE_MAX);
+ assert(mode >= 0 && mode < _JOB_MODE_MAX);
+
+ dbus_error_init(&error);
+
+ if (reload_if_possible && unit_can_reload(u)) {
+ if (type == JOB_RESTART)
+ type = JOB_RELOAD_OR_START;
+ else if (type == JOB_TRY_RESTART)
+ type = JOB_RELOAD;
+ }
+
+ SELINUX_UNIT_ACCESS_CHECK(u, connection, message,
+ (type == JOB_START || type == JOB_RESTART || type == JOB_TRY_RESTART) ? "start" :
+ type == JOB_STOP ? "stop" : "reload");
+
+ if (type == JOB_STOP && u->load_state == UNIT_ERROR && unit_active_state(u) == UNIT_INACTIVE) {
+ dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id);
+ return bus_send_error_reply(connection, message, &error, -EPERM);
+ }
+
+ if ((type == JOB_START && u->refuse_manual_start) ||
+ (type == JOB_STOP && u->refuse_manual_stop) ||
+ ((type == JOB_RESTART || type == JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop))) {
+ dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, unit %s may be requested by dependency only.", u->id);
+ return bus_send_error_reply(connection, message, &error, -EPERM);
+ }
+
+ r = manager_add_job(u->manager, type, u, mode, true, &error, &j);
+ if (r < 0)
+ return bus_send_error_reply(connection, message, &error, r);
+
+ cl = job_bus_client_new(connection, bus_message_get_sender_with_fallback(message));
+ if (!cl)
+ goto oom;
+
+ LIST_PREPEND(JobBusClient, client, j->bus_client_list, cl);
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ goto oom;
+
+ path = job_dbus_path(j);
+ if (!path)
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+const BusProperty bus_unit_properties[] = {
+ { "Id", bus_property_append_string, "s", offsetof(Unit, id), true },
+ { "Names", bus_unit_append_names, "as", 0 },
+ { "Following", bus_unit_append_following, "s", 0 },
+ { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true },
+ { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true },
+ { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true },
+ { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true },
+ { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true },
+ { "BindsTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BINDS_TO]), true },
+ { "PartOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PART_OF]), true },
+ { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true },
+ { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true },
+ { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true },
+ { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true },
+ { "ConsistsOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), true },
+ { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true },
+ { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true },
+ { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true },
+ { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true },
+ { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true },
+ { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true },
+ { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true },
+ { "PropagatesReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), true },
+ { "ReloadPropagatedFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), true },
+ { "RequiresMountsFor", bus_property_append_strv, "as", offsetof(Unit, requires_mounts_for), true },
+ { "Documentation", bus_property_append_strv, "as", offsetof(Unit, documentation), true },
+ { "Description", bus_unit_append_description, "s", 0 },
+ { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) },
+ { "ActiveState", bus_unit_append_active_state, "s", 0 },
+ { "SubState", bus_unit_append_sub_state, "s", 0 },
+ { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true },
+ { "SourcePath", bus_property_append_string, "s", offsetof(Unit, source_path), true },
+ { "UnitFileState", bus_unit_append_file_state, "s", 0 },
+ { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) },
+ { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) },
+ { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) },
+ { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) },
+ { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) },
+ { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) },
+ { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) },
+ { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) },
+ { "CanStart", bus_unit_append_can_start, "b", 0 },
+ { "CanStop", bus_unit_append_can_stop, "b", 0 },
+ { "CanReload", bus_unit_append_can_reload, "b", 0 },
+ { "CanIsolate", bus_unit_append_can_isolate, "b", 0 },
+ { "Job", bus_unit_append_job, "(uo)", 0 },
+ { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) },
+ { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) },
+ { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) },
+ { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) },
+ { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) },
+ { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) },
+ { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) },
+ { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) },
+ { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 },
+ { "ControlGroup", bus_unit_append_cgroups, "as", 0 },
+ { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,"a(sss)", 0 },
+ { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 },
+ { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) },
+ { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) },
+ { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) },
+ { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) },
+ { "LoadError", bus_unit_append_load_error, "(ss)", 0 },
+ { NULL, }
+};
diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h
new file mode 100644
index 0000000000..ac6785a949
--- /dev/null
+++ b/src/core/dbus-unit.h
@@ -0,0 +1,147 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "manager.h"
+#include "dbus-common.h"
+
+#define BUS_UNIT_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Unit\">\n" \
+ " <method name=\"Start\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Stop\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Reload\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Restart\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"TryRestart\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ReloadOrRestart\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ReloadOrTryRestart\">\n" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Kill\">\n" \
+ " <arg name=\"who\" type=\"s\" direction=\"in\"/>\n" \
+ " <arg name=\"signal\" type=\"i\" direction=\"in\"/>\n" \
+ " </method>\n" \
+ " <method name=\"ResetFailed\"/>\n" \
+ " <property name=\"Id\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Names\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Following\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Requires\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"RequiresOverridable\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Requisite\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"RequisiteOverridable\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Wants\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"BindsTo\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"RequiredBy\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"RequiredByOverridable\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"WantedBy\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"BoundBy\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Conflicts\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"ConflictedBy\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Before\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"After\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"OnFailure\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Triggers\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"TriggeredBy\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"PropagatesReloadTo\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"ReloadPropagatedFrom\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"RequiresMountsFor\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"Description\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SourcePath\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"Documentation\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"LoadState\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"ActiveState\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"SubState\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"FragmentPath\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"UnitFileState\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"InactiveExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"InactiveExitTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ActiveEnterTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ActiveEnterTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ActiveExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ActiveExitTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"InactiveEnterTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"InactiveEnterTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"CanStart\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"CanStop\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"CanReload\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"CanIsolate\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"Job\" type=\"(uo)\" access=\"read\"/>\n" \
+ " <property name=\"StopWhenUnneeded\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"RefuseManualStart\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"RefuseManualStop\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"AllowIsolate\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"DefaultDependencies\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"OnFailureIsolate\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"IgnoreOnIsolate\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"IgnoreOnSnapshot\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"ControlGroup\" type=\"as\" access=\"read\"/>\n" \
+ " <property name=\"ControlGroupAttributes\" type=\"a(sss)\" access=\"read\"/>\n" \
+ " <property name=\"NeedDaemonReload\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"JobTimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ConditionTimestamp\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ConditionTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
+ " <property name=\"ConditionResult\" type=\"b\" access=\"read\"/>\n" \
+ " <property name=\"LoadError\" type=\"(ss)\" access=\"read\"/>\n" \
+ " </interface>\n"
+
+#define BUS_UNIT_INTERFACES_LIST \
+ BUS_GENERIC_INTERFACES_LIST \
+ "org.freedesktop.systemd1.Unit\0"
+
+extern const BusProperty bus_unit_properties[];
+
+void bus_unit_send_change_signal(Unit *u);
+void bus_unit_send_removed_signal(Unit *u);
+
+
+DBusHandlerResult bus_unit_queue_job(
+ DBusConnection *connection,
+ DBusMessage *message,
+ Unit *u,
+ JobType type,
+ JobMode mode,
+ bool reload_if_possible);
+
+extern const DBusObjectPathVTable bus_unit_vtable;
+
+extern const char bus_unit_interface[];
diff --git a/src/core/dbus.c b/src/core/dbus.c
new file mode 100644
index 0000000000..2a1c66054a
--- /dev/null
+++ b/src/core/dbus.c
@@ -0,0 +1,1479 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dbus/dbus.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "strv.h"
+#include "cgroup.h"
+#include "mkdir.h"
+#include "missing.h"
+#include "dbus-unit.h"
+#include "dbus-job.h"
+#include "dbus-manager.h"
+#include "dbus-service.h"
+#include "dbus-socket.h"
+#include "dbus-target.h"
+#include "dbus-device.h"
+#include "dbus-mount.h"
+#include "dbus-automount.h"
+#include "dbus-snapshot.h"
+#include "dbus-swap.h"
+#include "dbus-timer.h"
+#include "dbus-path.h"
+#include "bus-errors.h"
+#include "special.h"
+#include "dbus-common.h"
+
+#define CONNECTIONS_MAX 52
+
+/* Well-known address (http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-types) */
+#define DBUS_SYSTEM_BUS_DEFAULT_ADDRESS "unix:path=/var/run/dbus/system_bus_socket"
+/* Only used as a fallback */
+#define DBUS_SESSION_BUS_DEFAULT_ADDRESS "autolaunch:"
+
+static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE;
+static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE;
+
+const char *const bus_interface_table[] = {
+ "org.freedesktop.DBus.Properties", bus_properties_interface,
+ "org.freedesktop.DBus.Introspectable", bus_introspectable_interface,
+ "org.freedesktop.systemd1.Manager", bus_manager_interface,
+ "org.freedesktop.systemd1.Job", bus_job_interface,
+ "org.freedesktop.systemd1.Unit", bus_unit_interface,
+ "org.freedesktop.systemd1.Service", bus_service_interface,
+ "org.freedesktop.systemd1.Socket", bus_socket_interface,
+ "org.freedesktop.systemd1.Target", bus_target_interface,
+ "org.freedesktop.systemd1.Device", bus_device_interface,
+ "org.freedesktop.systemd1.Mount", bus_mount_interface,
+ "org.freedesktop.systemd1.Automount", bus_automount_interface,
+ "org.freedesktop.systemd1.Snapshot", bus_snapshot_interface,
+ "org.freedesktop.systemd1.Swap", bus_swap_interface,
+ "org.freedesktop.systemd1.Timer", bus_timer_interface,
+ "org.freedesktop.systemd1.Path", bus_path_interface,
+ NULL
+};
+
+static void bus_done_api(Manager *m);
+static void bus_done_system(Manager *m);
+static void bus_done_private(Manager *m);
+static void shutdown_connection(Manager *m, DBusConnection *c);
+
+static void bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) {
+ Manager *m = data;
+
+ assert(bus);
+ assert(m);
+
+ /* We maintain two sets, one for those connections where we
+ * requested a dispatch, and another where we didn't. And then,
+ * we move the connections between the two sets. */
+
+ if (status == DBUS_DISPATCH_COMPLETE)
+ set_move_one(m->bus_connections, m->bus_connections_for_dispatch, bus);
+ else
+ set_move_one(m->bus_connections_for_dispatch, m->bus_connections, bus);
+}
+
+void bus_watch_event(Manager *m, Watch *w, int events) {
+ assert(m);
+ assert(w);
+
+ /* This is called by the event loop whenever there is
+ * something happening on D-Bus' file handles. */
+
+ if (!dbus_watch_get_enabled(w->data.bus_watch))
+ return;
+
+ dbus_watch_handle(w->data.bus_watch, bus_events_to_flags(events));
+}
+
+static dbus_bool_t bus_add_watch(DBusWatch *bus_watch, void *data) {
+ Manager *m = data;
+ Watch *w;
+ struct epoll_event ev;
+
+ assert(bus_watch);
+ assert(m);
+
+ if (!(w = new0(Watch, 1)))
+ return FALSE;
+
+ w->fd = dbus_watch_get_unix_fd(bus_watch);
+ w->type = WATCH_DBUS_WATCH;
+ w->data.bus_watch = bus_watch;
+
+ zero(ev);
+ ev.events = bus_flags_to_events(bus_watch);
+ ev.data.ptr = w;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) {
+
+ if (errno != EEXIST) {
+ free(w);
+ return FALSE;
+ }
+
+ /* Hmm, bloody D-Bus creates multiple watches on the
+ * same fd. epoll() does not like that. As a dirty
+ * hack we simply dup() the fd and hence get a second
+ * one we can safely add to the epoll(). */
+
+ if ((w->fd = dup(w->fd)) < 0) {
+ free(w);
+ return FALSE;
+ }
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) {
+ close_nointr_nofail(w->fd);
+ free(w);
+ return FALSE;
+ }
+
+ w->fd_is_dupped = true;
+ }
+
+ dbus_watch_set_data(bus_watch, w, NULL);
+
+ return TRUE;
+}
+
+static void bus_remove_watch(DBusWatch *bus_watch, void *data) {
+ Manager *m = data;
+ Watch *w;
+
+ assert(bus_watch);
+ assert(m);
+
+ w = dbus_watch_get_data(bus_watch);
+ if (!w)
+ return;
+
+ assert(w->type == WATCH_DBUS_WATCH);
+ assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0);
+
+ if (w->fd_is_dupped)
+ close_nointr_nofail(w->fd);
+
+ free(w);
+}
+
+static void bus_toggle_watch(DBusWatch *bus_watch, void *data) {
+ Manager *m = data;
+ Watch *w;
+ struct epoll_event ev;
+
+ assert(bus_watch);
+ assert(m);
+
+ w = dbus_watch_get_data(bus_watch);
+ if (!w)
+ return;
+
+ assert(w->type == WATCH_DBUS_WATCH);
+
+ zero(ev);
+ ev.events = bus_flags_to_events(bus_watch);
+ ev.data.ptr = w;
+
+ assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_MOD, w->fd, &ev) == 0);
+}
+
+static int bus_timeout_arm(Manager *m, Watch *w) {
+ struct itimerspec its;
+
+ assert(m);
+ assert(w);
+
+ zero(its);
+
+ if (dbus_timeout_get_enabled(w->data.bus_timeout)) {
+ timespec_store(&its.it_value, dbus_timeout_get_interval(w->data.bus_timeout) * USEC_PER_MSEC);
+ its.it_interval = its.it_value;
+ }
+
+ if (timerfd_settime(w->fd, 0, &its, NULL) < 0)
+ return -errno;
+
+ return 0;
+}
+
+void bus_timeout_event(Manager *m, Watch *w, int events) {
+ assert(m);
+ assert(w);
+
+ /* This is called by the event loop whenever there is
+ * something happening on D-Bus' file handles. */
+
+ if (!(dbus_timeout_get_enabled(w->data.bus_timeout)))
+ return;
+
+ dbus_timeout_handle(w->data.bus_timeout);
+}
+
+static dbus_bool_t bus_add_timeout(DBusTimeout *timeout, void *data) {
+ Manager *m = data;
+ Watch *w;
+ struct epoll_event ev;
+
+ assert(timeout);
+ assert(m);
+
+ if (!(w = new0(Watch, 1)))
+ return FALSE;
+
+ if ((w->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
+ goto fail;
+
+ w->type = WATCH_DBUS_TIMEOUT;
+ w->data.bus_timeout = timeout;
+
+ if (bus_timeout_arm(m, w) < 0)
+ goto fail;
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.ptr = w;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0)
+ goto fail;
+
+ dbus_timeout_set_data(timeout, w, NULL);
+
+ return TRUE;
+
+fail:
+ if (w->fd >= 0)
+ close_nointr_nofail(w->fd);
+
+ free(w);
+ return FALSE;
+}
+
+static void bus_remove_timeout(DBusTimeout *timeout, void *data) {
+ Manager *m = data;
+ Watch *w;
+
+ assert(timeout);
+ assert(m);
+
+ w = dbus_timeout_get_data(timeout);
+ if (!w)
+ return;
+
+ assert(w->type == WATCH_DBUS_TIMEOUT);
+
+ assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0);
+ close_nointr_nofail(w->fd);
+ free(w);
+}
+
+static void bus_toggle_timeout(DBusTimeout *timeout, void *data) {
+ Manager *m = data;
+ Watch *w;
+ int r;
+
+ assert(timeout);
+ assert(m);
+
+ w = dbus_timeout_get_data(timeout);
+ if (!w)
+ return;
+
+ assert(w->type == WATCH_DBUS_TIMEOUT);
+
+ if ((r = bus_timeout_arm(m, w)) < 0)
+ log_error("Failed to rearm timer: %s", strerror(-r));
+}
+
+static DBusHandlerResult api_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+ DBusError error;
+ DBusMessage *reply = NULL;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL ||
+ dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL)
+ log_debug("Got D-Bus request: %s.%s() on %s",
+ dbus_message_get_interface(message),
+ dbus_message_get_member(message),
+ dbus_message_get_path(message));
+
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+ log_debug("API D-Bus connection terminated.");
+ bus_done_api(m);
+
+ } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
+ const char *name, *old_owner, *new_owner;
+
+ if (!dbus_message_get_args(message, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old_owner,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_INVALID))
+ log_error("Failed to parse NameOwnerChanged message: %s", bus_error_message(&error));
+ else {
+ if (set_remove(BUS_CONNECTION_SUBSCRIBED(m, connection), (char*) name))
+ log_debug("Subscription client vanished: %s (left: %u)", name, set_size(BUS_CONNECTION_SUBSCRIBED(m, connection)));
+
+ if (old_owner[0] == 0)
+ old_owner = NULL;
+
+ if (new_owner[0] == 0)
+ new_owner = NULL;
+
+ manager_dispatch_bus_name_owner_changed(m, name, old_owner, new_owner);
+ }
+ } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Activator", "ActivationRequest")) {
+ const char *name;
+
+ if (!dbus_message_get_args(message, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ log_error("Failed to parse ActivationRequest message: %s", bus_error_message(&error));
+ else {
+ int r;
+ Unit *u;
+
+ log_debug("Got D-Bus activation request for %s", name);
+
+ if (manager_unit_pending_inactive(m, SPECIAL_DBUS_SERVICE) ||
+ manager_unit_pending_inactive(m, SPECIAL_DBUS_SOCKET)) {
+ r = -EADDRNOTAVAIL;
+ dbus_set_error(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down.");
+ } else {
+ r = manager_load_unit(m, name, NULL, &error, &u);
+
+ if (r >= 0 && u->refuse_manual_start)
+ r = -EPERM;
+
+ if (r >= 0)
+ r = manager_add_job(m, JOB_START, u, JOB_REPLACE, true, &error, NULL);
+ }
+
+ if (r < 0) {
+ const char *id, *text;
+
+ log_debug("D-Bus activation failed for %s: %s", name, strerror(-r));
+
+ if (!(reply = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure")))
+ goto oom;
+
+ id = error.name ? error.name : bus_errno_to_dbus(r);
+ text = bus_error(&error, r);
+
+ if (!dbus_message_set_destination(reply, DBUS_SERVICE_DBUS) ||
+ !dbus_message_append_args(reply,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &id,
+ DBUS_TYPE_STRING, &text,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ }
+
+ /* On success we don't do anything, the service will be spawned now */
+ }
+ }
+
+ dbus_error_free(&error);
+
+ if (reply) {
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static DBusHandlerResult system_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+ DBusError error;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (m->api_bus != m->system_bus &&
+ (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL ||
+ dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL))
+ log_debug("Got D-Bus request on system bus: %s.%s() on %s",
+ dbus_message_get_interface(message),
+ dbus_message_get_member(message),
+ dbus_message_get_path(message));
+
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+ log_debug("System D-Bus connection terminated.");
+ bus_done_system(m);
+
+ } else if (m->running_as != SYSTEMD_SYSTEM &&
+ dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) {
+
+ const char *cgroup;
+
+ if (!dbus_message_get_args(message, &error,
+ DBUS_TYPE_STRING, &cgroup,
+ DBUS_TYPE_INVALID))
+ log_error("Failed to parse Released message: %s", bus_error_message(&error));
+ else
+ cgroup_notify_empty(m, cgroup);
+ }
+
+ dbus_error_free(&error);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult private_bus_message_filter(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+ DBusError error;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL ||
+ dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL)
+ log_debug("Got D-Bus request: %s.%s() on %s",
+ dbus_message_get_interface(message),
+ dbus_message_get_member(message),
+ dbus_message_get_path(message));
+
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected"))
+ shutdown_connection(m, connection);
+ else if (m->running_as == SYSTEMD_SYSTEM &&
+ dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) {
+
+ const char *cgroup;
+
+ if (!dbus_message_get_args(message, &error,
+ DBUS_TYPE_STRING, &cgroup,
+ DBUS_TYPE_INVALID))
+ log_error("Failed to parse Released message: %s", bus_error_message(&error));
+ else
+ cgroup_notify_empty(m, cgroup);
+
+ /* Forward the message to the system bus, so that user
+ * instances are notified as well */
+
+ if (m->system_bus)
+ dbus_connection_send(m->system_bus, message, NULL);
+ }
+
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+unsigned bus_dispatch(Manager *m) {
+ DBusConnection *c;
+
+ assert(m);
+
+ if (m->queued_message) {
+ /* If we cannot get rid of this message we won't
+ * dispatch any D-Bus messages, so that we won't end
+ * up wanting to queue another message. */
+
+ if (m->queued_message_connection)
+ if (!dbus_connection_send(m->queued_message_connection, m->queued_message, NULL))
+ return 0;
+
+ dbus_message_unref(m->queued_message);
+ m->queued_message = NULL;
+ m->queued_message_connection = NULL;
+ }
+
+ if ((c = set_first(m->bus_connections_for_dispatch))) {
+ if (dbus_connection_dispatch(c) == DBUS_DISPATCH_COMPLETE)
+ set_move_one(m->bus_connections, m->bus_connections_for_dispatch, c);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static void request_name_pending_cb(DBusPendingCall *pending, void *userdata) {
+ DBusMessage *reply;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ assert_se(reply = dbus_pending_call_steal_reply(pending));
+
+ switch (dbus_message_get_type(reply)) {
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+
+ assert_se(dbus_set_error_from_message(&error, reply));
+ log_warning("RequestName() failed: %s", bus_error_message(&error));
+ break;
+
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN: {
+ uint32_t r;
+
+ if (!dbus_message_get_args(reply,
+ &error,
+ DBUS_TYPE_UINT32, &r,
+ DBUS_TYPE_INVALID)) {
+ log_error("Failed to parse RequestName() reply: %s", bus_error_message(&error));
+ break;
+ }
+
+ if (r == 1)
+ log_debug("Successfully acquired name.");
+ else
+ log_error("Name already owned.");
+
+ break;
+ }
+
+ default:
+ assert_not_reached("Invalid reply message");
+ }
+
+ dbus_message_unref(reply);
+ dbus_error_free(&error);
+}
+
+static int request_name(Manager *m) {
+ const char *name = "org.freedesktop.systemd1";
+ /* Allow replacing of our name, to ease implementation of
+ * reexecution, where we keep the old connection open until
+ * after the new connection is set up and the name installed
+ * to allow clients to synchronously wait for reexecution to
+ * finish */
+ uint32_t flags = DBUS_NAME_FLAG_ALLOW_REPLACEMENT|DBUS_NAME_FLAG_REPLACE_EXISTING;
+ DBusMessage *message = NULL;
+ DBusPendingCall *pending = NULL;
+
+ if (!(message = dbus_message_new_method_call(
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "RequestName")))
+ goto oom;
+
+ if (!dbus_message_append_args(
+ message,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_UINT32, &flags,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1))
+ goto oom;
+
+ if (!dbus_pending_call_set_notify(pending, request_name_pending_cb, m, NULL))
+ goto oom;
+
+ dbus_message_unref(message);
+ dbus_pending_call_unref(pending);
+
+ /* We simple ask for the name and don't wait for it. Sooner or
+ * later we'll have it. */
+
+ return 0;
+
+oom:
+ if (pending) {
+ dbus_pending_call_cancel(pending);
+ dbus_pending_call_unref(pending);
+ }
+
+ if (message)
+ dbus_message_unref(message);
+
+ return -ENOMEM;
+}
+
+static void query_name_list_pending_cb(DBusPendingCall *pending, void *userdata) {
+ DBusMessage *reply;
+ DBusError error;
+ Manager *m = userdata;
+
+ assert(m);
+
+ dbus_error_init(&error);
+
+ assert_se(reply = dbus_pending_call_steal_reply(pending));
+
+ switch (dbus_message_get_type(reply)) {
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+
+ assert_se(dbus_set_error_from_message(&error, reply));
+ log_warning("ListNames() failed: %s", bus_error_message(&error));
+ break;
+
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN: {
+ int r;
+ char **l;
+
+ if ((r = bus_parse_strv(reply, &l)) < 0)
+ log_warning("Failed to parse ListNames() reply: %s", strerror(-r));
+ else {
+ char **t;
+
+ STRV_FOREACH(t, l)
+ /* This is a bit hacky, we say the
+ * owner of the name is the name
+ * itself, because we don't want the
+ * extra traffic to figure out the
+ * real owner. */
+ manager_dispatch_bus_name_owner_changed(m, *t, NULL, *t);
+
+ strv_free(l);
+ }
+
+ break;
+ }
+
+ default:
+ assert_not_reached("Invalid reply message");
+ }
+
+ dbus_message_unref(reply);
+ dbus_error_free(&error);
+}
+
+static int query_name_list(Manager *m) {
+ DBusMessage *message = NULL;
+ DBusPendingCall *pending = NULL;
+
+ /* Asks for the currently installed bus names */
+
+ if (!(message = dbus_message_new_method_call(
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "ListNames")))
+ goto oom;
+
+ if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1))
+ goto oom;
+
+ if (!dbus_pending_call_set_notify(pending, query_name_list_pending_cb, m, NULL))
+ goto oom;
+
+ dbus_message_unref(message);
+ dbus_pending_call_unref(pending);
+
+ /* We simple ask for the list and don't wait for it. Sooner or
+ * later we'll get it. */
+
+ return 0;
+
+oom:
+ if (pending) {
+ dbus_pending_call_cancel(pending);
+ dbus_pending_call_unref(pending);
+ }
+
+ if (message)
+ dbus_message_unref(message);
+
+ return -ENOMEM;
+}
+
+static int bus_setup_loop(Manager *m, DBusConnection *bus) {
+ assert(m);
+ assert(bus);
+
+ dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+ if (!dbus_connection_set_watch_functions(bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) ||
+ !dbus_connection_set_timeout_functions(bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL))
+ return log_oom();
+
+ if (set_put(m->bus_connections_for_dispatch, bus) < 0)
+ return log_oom();
+
+ dbus_connection_set_dispatch_status_function(bus, bus_dispatch_status, m, NULL);
+ return 0;
+}
+
+static dbus_bool_t allow_only_same_user(DBusConnection *connection, unsigned long uid, void *data) {
+ return uid == 0 || uid == geteuid();
+}
+
+static void bus_new_connection(
+ DBusServer *server,
+ DBusConnection *new_connection,
+ void *data) {
+
+ Manager *m = data;
+
+ assert(m);
+
+ if (set_size(m->bus_connections) >= CONNECTIONS_MAX) {
+ log_error("Too many concurrent connections.");
+ return;
+ }
+
+ dbus_connection_set_unix_user_function(new_connection, allow_only_same_user, NULL, NULL);
+
+ if (bus_setup_loop(m, new_connection) < 0)
+ return;
+
+ if (!dbus_connection_register_object_path(new_connection, "/org/freedesktop/systemd1", &bus_manager_vtable, m) ||
+ !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) ||
+ !dbus_connection_register_fallback(new_connection, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) ||
+ !dbus_connection_add_filter(new_connection, private_bus_message_filter, m, NULL)) {
+ log_oom();
+ return;
+ }
+
+ log_debug("Accepted connection on private bus.");
+
+ dbus_connection_ref(new_connection);
+}
+
+static int init_registered_system_bus(Manager *m) {
+ char *id;
+
+ if (!dbus_connection_add_filter(m->system_bus, system_bus_message_filter, m, NULL))
+ return log_oom();
+
+ if (m->running_as != SYSTEMD_SYSTEM) {
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ dbus_bus_add_match(m->system_bus,
+ "type='signal',"
+ "interface='org.freedesktop.systemd1.Agent',"
+ "member='Released',"
+ "path='/org/freedesktop/systemd1/agent'",
+ &error);
+
+ if (dbus_error_is_set(&error)) {
+ log_error("Failed to register match: %s", bus_error_message(&error));
+ dbus_error_free(&error);
+ return -1;
+ }
+ }
+
+ log_debug("Successfully connected to system D-Bus bus %s as %s",
+ strnull((id = dbus_connection_get_server_id(m->system_bus))),
+ strnull(dbus_bus_get_unique_name(m->system_bus)));
+ dbus_free(id);
+
+ return 0;
+}
+
+static int init_registered_api_bus(Manager *m) {
+ int r;
+
+ if (!dbus_connection_register_object_path(m->api_bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) ||
+ !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) ||
+ !dbus_connection_register_fallback(m->api_bus, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) ||
+ !dbus_connection_add_filter(m->api_bus, api_bus_message_filter, m, NULL))
+ return log_oom();
+
+ /* Get NameOwnerChange messages */
+ dbus_bus_add_match(m->api_bus,
+ "type='signal',"
+ "sender='"DBUS_SERVICE_DBUS"',"
+ "interface='"DBUS_INTERFACE_DBUS"',"
+ "member='NameOwnerChanged',"
+ "path='"DBUS_PATH_DBUS"'",
+ NULL);
+
+ /* Get activation requests */
+ dbus_bus_add_match(m->api_bus,
+ "type='signal',"
+ "sender='"DBUS_SERVICE_DBUS"',"
+ "interface='org.freedesktop.systemd1.Activator',"
+ "member='ActivationRequest',"
+ "path='"DBUS_PATH_DBUS"'",
+ NULL);
+
+ r = request_name(m);
+ if (r < 0)
+ return r;
+
+ r = query_name_list(m);
+ if (r < 0)
+ return r;
+
+ if (m->running_as == SYSTEMD_USER) {
+ char *id;
+ log_debug("Successfully connected to API D-Bus bus %s as %s",
+ strnull((id = dbus_connection_get_server_id(m->api_bus))),
+ strnull(dbus_bus_get_unique_name(m->api_bus)));
+ dbus_free(id);
+ } else
+ log_debug("Successfully initialized API on the system bus");
+
+ return 0;
+}
+
+static void bus_register_cb(DBusPendingCall *pending, void *userdata) {
+ Manager *m = userdata;
+ DBusConnection **conn;
+ DBusMessage *reply;
+ DBusError error;
+ const char *name;
+ int r = 0;
+
+ dbus_error_init(&error);
+
+ conn = dbus_pending_call_get_data(pending, m->conn_data_slot);
+ assert(conn == &m->system_bus || conn == &m->api_bus);
+
+ reply = dbus_pending_call_steal_reply(pending);
+
+ switch (dbus_message_get_type(reply)) {
+ case DBUS_MESSAGE_TYPE_ERROR:
+ assert_se(dbus_set_error_from_message(&error, reply));
+ log_warning("Failed to register to bus: %s", bus_error_message(&error));
+ r = -1;
+ break;
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ if (!dbus_message_get_args(reply, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ log_error("Failed to parse Hello reply: %s", bus_error_message(&error));
+ r = -1;
+ break;
+ }
+
+ log_debug("Received name %s in reply to Hello", name);
+ if (!dbus_bus_set_unique_name(*conn, name)) {
+ log_error("Failed to set unique name");
+ r = -1;
+ break;
+ }
+
+ if (conn == &m->system_bus) {
+ r = init_registered_system_bus(m);
+ if (r == 0 && m->running_as == SYSTEMD_SYSTEM)
+ r = init_registered_api_bus(m);
+ } else
+ r = init_registered_api_bus(m);
+
+ break;
+ default:
+ assert_not_reached("Invalid reply message");
+ }
+
+ dbus_message_unref(reply);
+ dbus_error_free(&error);
+
+ if (r < 0) {
+ if (conn == &m->system_bus) {
+ log_debug("Failed setting up the system bus");
+ bus_done_system(m);
+ } else {
+ log_debug("Failed setting up the API bus");
+ bus_done_api(m);
+ }
+ }
+}
+
+static int manager_bus_async_register(Manager *m, DBusConnection **conn) {
+ DBusMessage *message = NULL;
+ DBusPendingCall *pending = NULL;
+
+ message = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "Hello");
+ if (!message)
+ goto oom;
+
+ if (!dbus_connection_send_with_reply(*conn, message, &pending, -1))
+ goto oom;
+
+ if (!dbus_pending_call_set_data(pending, m->conn_data_slot, conn, NULL))
+ goto oom;
+
+ if (!dbus_pending_call_set_notify(pending, bus_register_cb, m, NULL))
+ goto oom;
+
+ dbus_message_unref(message);
+ dbus_pending_call_unref(pending);
+
+ return 0;
+oom:
+ if (pending) {
+ dbus_pending_call_cancel(pending);
+ dbus_pending_call_unref(pending);
+ }
+
+ if (message)
+ dbus_message_unref(message);
+
+ return -ENOMEM;
+}
+
+static DBusConnection* manager_bus_connect_private(Manager *m, DBusBusType type) {
+ const char *address;
+ DBusConnection *connection;
+ DBusError error;
+
+ switch (type) {
+ case DBUS_BUS_SYSTEM:
+ address = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS");
+ if (!address || !address[0])
+ address = DBUS_SYSTEM_BUS_DEFAULT_ADDRESS;
+ break;
+ case DBUS_BUS_SESSION:
+ address = secure_getenv("DBUS_SESSION_BUS_ADDRESS");
+ if (!address || !address[0])
+ address = DBUS_SESSION_BUS_DEFAULT_ADDRESS;
+ break;
+ default:
+ assert_not_reached("Invalid bus type");
+ }
+
+ dbus_error_init(&error);
+
+ connection = dbus_connection_open_private(address, &error);
+ if (!connection) {
+ log_warning("Failed to open private bus connection: %s", bus_error_message(&error));
+ goto fail;
+ }
+
+ return connection;
+fail:
+ if (connection)
+ dbus_connection_close(connection);
+ dbus_error_free(&error);
+ return NULL;
+}
+
+static int bus_init_system(Manager *m) {
+ int r;
+
+ if (m->system_bus)
+ return 0;
+
+ m->system_bus = manager_bus_connect_private(m, DBUS_BUS_SYSTEM);
+ if (!m->system_bus) {
+ log_debug("Failed to connect to system D-Bus, retrying later");
+ r = 0;
+ goto fail;
+ }
+
+ r = bus_setup_loop(m, m->system_bus);
+ if (r < 0)
+ goto fail;
+
+ r = manager_bus_async_register(m, &m->system_bus);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+fail:
+ bus_done_system(m);
+
+ return r;
+}
+
+static int bus_init_api(Manager *m) {
+ int r;
+
+ if (m->api_bus)
+ return 0;
+
+ if (m->running_as == SYSTEMD_SYSTEM) {
+ m->api_bus = m->system_bus;
+ /* In this mode there is no distinct connection to the API bus,
+ * the API is published on the system bus.
+ * bus_register_cb() is aware of that and will init the API
+ * when the system bus gets registered.
+ * No need to setup anything here. */
+ return 0;
+ }
+
+ m->api_bus = manager_bus_connect_private(m, DBUS_BUS_SESSION);
+ if (!m->api_bus) {
+ log_debug("Failed to connect to API D-Bus, retrying later");
+ r = 0;
+ goto fail;
+ }
+
+ r = bus_setup_loop(m, m->api_bus);
+ if (r < 0)
+ goto fail;
+
+ r = manager_bus_async_register(m, &m->api_bus);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+fail:
+ bus_done_api(m);
+
+ return r;
+}
+
+static int bus_init_private(Manager *m) {
+ DBusError error;
+ int r;
+ const char *const external_only[] = {
+ "EXTERNAL",
+ NULL
+ };
+
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (m->private_bus)
+ return 0;
+
+ if (m->running_as == SYSTEMD_SYSTEM) {
+
+ /* We want the private bus only when running as init */
+ if (getpid() != 1)
+ return 0;
+
+ unlink("/run/systemd/private");
+ m->private_bus = dbus_server_listen("unix:path=/run/systemd/private", &error);
+ } else {
+ const char *e;
+ char *p;
+
+ e = secure_getenv("XDG_RUNTIME_DIR");
+ if (!e)
+ return 0;
+
+ if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0) {
+ r = log_oom();
+ goto fail;
+ }
+
+ mkdir_parents_label(p+10, 0755);
+ unlink(p+10);
+ m->private_bus = dbus_server_listen(p, &error);
+ free(p);
+ }
+
+ if (!m->private_bus) {
+ log_error("Failed to create private D-Bus server: %s", bus_error_message(&error));
+ r = -EIO;
+ goto fail;
+ }
+
+ if (!dbus_server_set_auth_mechanisms(m->private_bus, (const char**) external_only) ||
+ !dbus_server_set_watch_functions(m->private_bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) ||
+ !dbus_server_set_timeout_functions(m->private_bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL)) {
+ r = log_oom();
+ goto fail;
+ }
+
+ dbus_server_set_new_connection_function(m->private_bus, bus_new_connection, m, NULL);
+
+ log_debug("Successfully created private D-Bus server.");
+
+ return 0;
+
+fail:
+ bus_done_private(m);
+ dbus_error_free(&error);
+
+ return r;
+}
+
+int bus_init(Manager *m, bool try_bus_connect) {
+ int r;
+
+ if (set_ensure_allocated(&m->bus_connections, trivial_hash_func, trivial_compare_func) < 0 ||
+ set_ensure_allocated(&m->bus_connections_for_dispatch, trivial_hash_func, trivial_compare_func) < 0)
+ goto oom;
+
+ if (m->name_data_slot < 0)
+ if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot))
+ goto oom;
+
+ if (m->conn_data_slot < 0)
+ if (!dbus_pending_call_allocate_data_slot(&m->conn_data_slot))
+ goto oom;
+
+ if (m->subscribed_data_slot < 0)
+ if (!dbus_connection_allocate_data_slot(&m->subscribed_data_slot))
+ goto oom;
+
+ if (try_bus_connect) {
+ if ((r = bus_init_system(m)) < 0 ||
+ (r = bus_init_api(m)) < 0)
+ return r;
+ }
+
+ if ((r = bus_init_private(m)) < 0)
+ return r;
+
+ return 0;
+oom:
+ return log_oom();
+}
+
+static void shutdown_connection(Manager *m, DBusConnection *c) {
+ Set *s;
+ Job *j;
+ Iterator i;
+
+ HASHMAP_FOREACH(j, m->jobs, i) {
+ JobBusClient *cl, *nextcl;
+ LIST_FOREACH_SAFE(client, cl, nextcl, j->bus_client_list) {
+ if (cl->bus == c) {
+ LIST_REMOVE(JobBusClient, client, j->bus_client_list, cl);
+ free(cl);
+ }
+ }
+ }
+
+ set_remove(m->bus_connections, c);
+ set_remove(m->bus_connections_for_dispatch, c);
+
+ if ((s = BUS_CONNECTION_SUBSCRIBED(m, c))) {
+ char *t;
+
+ while ((t = set_steal_first(s)))
+ free(t);
+
+ set_free(s);
+ }
+
+ if (m->queued_message_connection == c) {
+ m->queued_message_connection = NULL;
+
+ if (m->queued_message) {
+ dbus_message_unref(m->queued_message);
+ m->queued_message = NULL;
+ }
+ }
+
+ dbus_connection_set_dispatch_status_function(c, NULL, NULL, NULL);
+ /* system manager cannot afford to block on DBus */
+ if (m->running_as != SYSTEMD_SYSTEM)
+ dbus_connection_flush(c);
+ dbus_connection_close(c);
+ dbus_connection_unref(c);
+}
+
+static void bus_done_api(Manager *m) {
+ if (!m->api_bus)
+ return;
+
+ if (m->running_as == SYSTEMD_USER)
+ shutdown_connection(m, m->api_bus);
+
+ m->api_bus = NULL;
+
+ if (m->queued_message) {
+ dbus_message_unref(m->queued_message);
+ m->queued_message = NULL;
+ }
+}
+
+static void bus_done_system(Manager *m) {
+ if (!m->system_bus)
+ return;
+
+ if (m->running_as == SYSTEMD_SYSTEM)
+ bus_done_api(m);
+
+ shutdown_connection(m, m->system_bus);
+ m->system_bus = NULL;
+}
+
+static void bus_done_private(Manager *m) {
+ if (!m->private_bus)
+ return;
+
+ dbus_server_disconnect(m->private_bus);
+ dbus_server_unref(m->private_bus);
+ m->private_bus = NULL;
+}
+
+void bus_done(Manager *m) {
+ DBusConnection *c;
+
+ bus_done_api(m);
+ bus_done_system(m);
+ bus_done_private(m);
+
+ while ((c = set_steal_first(m->bus_connections)))
+ shutdown_connection(m, c);
+
+ while ((c = set_steal_first(m->bus_connections_for_dispatch)))
+ shutdown_connection(m, c);
+
+ set_free(m->bus_connections);
+ set_free(m->bus_connections_for_dispatch);
+
+ if (m->name_data_slot >= 0)
+ dbus_pending_call_free_data_slot(&m->name_data_slot);
+
+ if (m->conn_data_slot >= 0)
+ dbus_pending_call_free_data_slot(&m->conn_data_slot);
+
+ if (m->subscribed_data_slot >= 0)
+ dbus_connection_free_data_slot(&m->subscribed_data_slot);
+}
+
+static void query_pid_pending_cb(DBusPendingCall *pending, void *userdata) {
+ Manager *m = userdata;
+ DBusMessage *reply;
+ DBusError error;
+ const char *name;
+
+ dbus_error_init(&error);
+
+ assert_se(name = BUS_PENDING_CALL_NAME(m, pending));
+ assert_se(reply = dbus_pending_call_steal_reply(pending));
+
+ switch (dbus_message_get_type(reply)) {
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+
+ assert_se(dbus_set_error_from_message(&error, reply));
+ log_warning("GetConnectionUnixProcessID() failed: %s", bus_error_message(&error));
+ break;
+
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN: {
+ uint32_t r;
+
+ if (!dbus_message_get_args(reply,
+ &error,
+ DBUS_TYPE_UINT32, &r,
+ DBUS_TYPE_INVALID)) {
+ log_error("Failed to parse GetConnectionUnixProcessID() reply: %s", bus_error_message(&error));
+ break;
+ }
+
+ manager_dispatch_bus_query_pid_done(m, name, (pid_t) r);
+ break;
+ }
+
+ default:
+ assert_not_reached("Invalid reply message");
+ }
+
+ dbus_message_unref(reply);
+ dbus_error_free(&error);
+}
+
+int bus_query_pid(Manager *m, const char *name) {
+ DBusMessage *message = NULL;
+ DBusPendingCall *pending = NULL;
+ char *n = NULL;
+
+ assert(m);
+ assert(name);
+
+ if (!(message = dbus_message_new_method_call(
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "GetConnectionUnixProcessID")))
+ goto oom;
+
+ if (!(dbus_message_append_args(
+ message,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)))
+ goto oom;
+
+ if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1))
+ goto oom;
+
+ if (!(n = strdup(name)))
+ goto oom;
+
+ if (!dbus_pending_call_set_data(pending, m->name_data_slot, n, free))
+ goto oom;
+
+ n = NULL;
+
+ if (!dbus_pending_call_set_notify(pending, query_pid_pending_cb, m, NULL))
+ goto oom;
+
+ dbus_message_unref(message);
+ dbus_pending_call_unref(pending);
+
+ return 0;
+
+oom:
+ free(n);
+
+ if (pending) {
+ dbus_pending_call_cancel(pending);
+ dbus_pending_call_unref(pending);
+ }
+
+ if (message)
+ dbus_message_unref(message);
+
+ return -ENOMEM;
+}
+
+int bus_broadcast(Manager *m, DBusMessage *message) {
+ bool oom = false;
+ Iterator i;
+ DBusConnection *c;
+
+ assert(m);
+ assert(message);
+
+ SET_FOREACH(c, m->bus_connections_for_dispatch, i)
+ if (c != m->system_bus || m->running_as == SYSTEMD_SYSTEM)
+ oom = !dbus_connection_send(c, message, NULL);
+
+ SET_FOREACH(c, m->bus_connections, i)
+ if (c != m->system_bus || m->running_as == SYSTEMD_SYSTEM)
+ oom = !dbus_connection_send(c, message, NULL);
+
+ return oom ? -ENOMEM : 0;
+}
+
+bool bus_has_subscriber(Manager *m) {
+ Iterator i;
+ DBusConnection *c;
+
+ assert(m);
+
+ SET_FOREACH(c, m->bus_connections_for_dispatch, i)
+ if (bus_connection_has_subscriber(m, c))
+ return true;
+
+ SET_FOREACH(c, m->bus_connections, i)
+ if (bus_connection_has_subscriber(m, c))
+ return true;
+
+ return false;
+}
+
+bool bus_connection_has_subscriber(Manager *m, DBusConnection *c) {
+ assert(m);
+ assert(c);
+
+ return !set_isempty(BUS_CONNECTION_SUBSCRIBED(m, c));
+}
+
+int bus_fdset_add_all(Manager *m, FDSet *fds) {
+ Iterator i;
+ DBusConnection *c;
+
+ assert(m);
+ assert(fds);
+
+ /* When we are about to reexecute we add all D-Bus fds to the
+ * set to pass over to the newly executed systemd. They won't
+ * be used there however, except that they are closed at the
+ * very end of deserialization, those making it possible for
+ * clients to synchronously wait for systemd to reexec by
+ * simply waiting for disconnection */
+
+ SET_FOREACH(c, m->bus_connections_for_dispatch, i) {
+ int fd;
+
+ if (dbus_connection_get_unix_fd(c, &fd)) {
+ fd = fdset_put_dup(fds, fd);
+
+ if (fd < 0)
+ return fd;
+ }
+ }
+
+ SET_FOREACH(c, m->bus_connections, i) {
+ int fd;
+
+ if (dbus_connection_get_unix_fd(c, &fd)) {
+ fd = fdset_put_dup(fds, fd);
+
+ if (fd < 0)
+ return fd;
+ }
+ }
+
+ return 0;
+}
+
+void bus_broadcast_finished(
+ Manager *m,
+ usec_t firmware_usec,
+ usec_t loader_usec,
+ usec_t kernel_usec,
+ usec_t initrd_usec,
+ usec_t userspace_usec,
+ usec_t total_usec) {
+
+ DBusMessage *message;
+
+ assert(m);
+
+ message = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished");
+ if (!message) {
+ log_oom();
+ return;
+ }
+
+ assert_cc(sizeof(usec_t) == sizeof(uint64_t));
+ if (!dbus_message_append_args(message,
+ DBUS_TYPE_UINT64, &firmware_usec,
+ DBUS_TYPE_UINT64, &loader_usec,
+ DBUS_TYPE_UINT64, &kernel_usec,
+ DBUS_TYPE_UINT64, &initrd_usec,
+ DBUS_TYPE_UINT64, &userspace_usec,
+ DBUS_TYPE_UINT64, &total_usec,
+ DBUS_TYPE_INVALID)) {
+ log_oom();
+ goto finish;
+ }
+
+
+ if (bus_broadcast(m, message) < 0) {
+ log_oom();
+ goto finish;
+ }
+
+finish:
+ if (message)
+ dbus_message_unref(message);
+}
diff --git a/src/core/dbus.h b/src/core/dbus.h
new file mode 100644
index 0000000000..c7a058e198
--- /dev/null
+++ b/src/core/dbus.h
@@ -0,0 +1,50 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "manager.h"
+
+int bus_init(Manager *m, bool try_bus_connect);
+void bus_done(Manager *m);
+
+unsigned bus_dispatch(Manager *m);
+
+void bus_watch_event(Manager *m, Watch *w, int events);
+void bus_timeout_event(Manager *m, Watch *w, int events);
+
+int bus_query_pid(Manager *m, const char *name);
+
+int bus_broadcast(Manager *m, DBusMessage *message);
+
+bool bus_has_subscriber(Manager *m);
+bool bus_connection_has_subscriber(Manager *m, DBusConnection *c);
+
+int bus_fdset_add_all(Manager *m, FDSet *fds);
+
+void bus_broadcast_finished(Manager *m, usec_t firmware_usec, usec_t loader_usec, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec);
+
+#define BUS_CONNECTION_SUBSCRIBED(m, c) dbus_connection_get_data((c), (m)->subscribed_data_slot)
+#define BUS_PENDING_CALL_NAME(m, p) dbus_pending_call_get_data((p), (m)->name_data_slot)
+
+extern const char * const bus_interface_table[];
diff --git a/src/core/device.c b/src/core/device.c
new file mode 100644
index 0000000000..be76cafaed
--- /dev/null
+++ b/src/core/device.c
@@ -0,0 +1,644 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/epoll.h>
+#include <libudev.h>
+
+#include "unit.h"
+#include "device.h"
+#include "strv.h"
+#include "log.h"
+#include "unit-name.h"
+#include "dbus-device.h"
+#include "def.h"
+#include "path-util.h"
+
+static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = {
+ [DEVICE_DEAD] = UNIT_INACTIVE,
+ [DEVICE_PLUGGED] = UNIT_ACTIVE
+};
+
+static void device_unset_sysfs(Device *d) {
+ Device *first;
+
+ assert(d);
+
+ if (!d->sysfs)
+ return;
+
+ /* Remove this unit from the chain of devices which share the
+ * same sysfs path. */
+ first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, d->sysfs);
+ LIST_REMOVE(Device, same_sysfs, first, d);
+
+ if (first)
+ hashmap_remove_and_replace(UNIT(d)->manager->devices_by_sysfs, d->sysfs, first->sysfs, first);
+ else
+ hashmap_remove(UNIT(d)->manager->devices_by_sysfs, d->sysfs);
+
+ free(d->sysfs);
+ d->sysfs = NULL;
+}
+
+static void device_init(Unit *u) {
+ Device *d = DEVICE(u);
+
+ assert(d);
+ assert(UNIT(d)->load_state == UNIT_STUB);
+
+ /* In contrast to all other unit types we timeout jobs waiting
+ * for devices by default. This is because they otherwise wait
+ * indefinitely for plugged in devices, something which cannot
+ * happen for the other units since their operations time out
+ * anyway. */
+ UNIT(d)->job_timeout = DEFAULT_TIMEOUT_USEC;
+
+ UNIT(d)->ignore_on_isolate = true;
+ UNIT(d)->ignore_on_snapshot = true;
+}
+
+static void device_done(Unit *u) {
+ Device *d = DEVICE(u);
+
+ assert(d);
+
+ device_unset_sysfs(d);
+}
+
+static void device_set_state(Device *d, DeviceState state) {
+ DeviceState old_state;
+ assert(d);
+
+ old_state = d->state;
+ d->state = state;
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(d)->id,
+ device_state_to_string(old_state),
+ device_state_to_string(state));
+
+ unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static int device_coldplug(Unit *u) {
+ Device *d = DEVICE(u);
+
+ assert(d);
+ assert(d->state == DEVICE_DEAD);
+
+ if (d->sysfs)
+ device_set_state(d, DEVICE_PLUGGED);
+
+ return 0;
+}
+
+static void device_dump(Unit *u, FILE *f, const char *prefix) {
+ Device *d = DEVICE(u);
+
+ assert(d);
+
+ fprintf(f,
+ "%sDevice State: %s\n"
+ "%sSysfs Path: %s\n",
+ prefix, device_state_to_string(d->state),
+ prefix, strna(d->sysfs));
+}
+
+static UnitActiveState device_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[DEVICE(u)->state];
+}
+
+static const char *device_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return device_state_to_string(DEVICE(u)->state);
+}
+
+static int device_add_escaped_name(Unit *u, const char *dn) {
+ char *e;
+ int r;
+
+ assert(u);
+ assert(dn);
+ assert(dn[0] == '/');
+
+ e = unit_name_from_path(dn, ".device");
+ if (!e)
+ return -ENOMEM;
+
+ r = unit_add_name(u, e);
+ free(e);
+
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ return 0;
+}
+
+static int device_find_escape_name(Manager *m, const char *dn, Unit **_u) {
+ char *e;
+ Unit *u;
+
+ assert(m);
+ assert(dn);
+ assert(dn[0] == '/');
+ assert(_u);
+
+ e = unit_name_from_path(dn, ".device");
+ if (!e)
+ return -ENOMEM;
+
+ u = manager_get_unit(m, e);
+ free(e);
+
+ if (u) {
+ *_u = u;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int device_update_unit(Manager *m, struct udev_device *dev, const char *path, bool main) {
+ const char *sysfs, *model;
+ Unit *u = NULL;
+ int r;
+ bool delete;
+
+ assert(m);
+
+ if (!(sysfs = udev_device_get_syspath(dev)))
+ return -ENOMEM;
+
+ if ((r = device_find_escape_name(m, path, &u)) < 0)
+ return r;
+
+ if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs))
+ return -EEXIST;
+
+ if (!u) {
+ delete = true;
+
+ u = unit_new(m, sizeof(Device));
+ if (!u)
+ return -ENOMEM;
+
+ r = device_add_escaped_name(u, path);
+ if (r < 0)
+ goto fail;
+
+ unit_add_to_load_queue(u);
+ } else
+ delete = false;
+
+ /* If this was created via some dependency and has not
+ * actually been seen yet ->sysfs will not be
+ * initialized. Hence initialize it if necessary. */
+
+ if (!DEVICE(u)->sysfs) {
+ Device *first;
+
+ if (!(DEVICE(u)->sysfs = strdup(sysfs))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (!m->devices_by_sysfs)
+ if (!(m->devices_by_sysfs = hashmap_new(string_hash_func, string_compare_func))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ first = hashmap_get(m->devices_by_sysfs, sysfs);
+ LIST_PREPEND(Device, same_sysfs, first, DEVICE(u));
+
+ if ((r = hashmap_replace(m->devices_by_sysfs, DEVICE(u)->sysfs, first)) < 0)
+ goto fail;
+ }
+
+ if ((model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE")) ||
+ (model = udev_device_get_property_value(dev, "ID_MODEL"))) {
+ if ((r = unit_set_description(u, model)) < 0)
+ goto fail;
+ } else
+ if ((r = unit_set_description(u, path)) < 0)
+ goto fail;
+
+ if (main) {
+ /* The additional systemd udev properties we only
+ * interpret for the main object */
+ const char *wants, *alias;
+
+ alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS");
+ if (alias) {
+ char *state, *w;
+ size_t l;
+
+ FOREACH_WORD_QUOTED(w, l, alias, state) {
+ char *e;
+
+ e = strndup(w, l);
+ if (!e) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (!is_path(e)) {
+ log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, e);
+ free(e);
+ } else {
+ device_update_unit(m, dev, e, false);
+ free(e);
+ }
+ }
+ }
+
+ wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS");
+ if (wants) {
+ char *state, *w;
+ size_t l;
+
+ FOREACH_WORD_QUOTED(w, l, wants, state) {
+ char *e;
+
+ e = strndup(w, l);
+ if (!e) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = unit_add_dependency_by_name(u, UNIT_WANTS, e, NULL, true);
+ free(e);
+ if (r < 0)
+ goto fail;
+ }
+ }
+ }
+
+ unit_add_to_dbus_queue(u);
+ return 0;
+
+fail:
+ log_warning("Failed to load device unit: %s", strerror(-r));
+
+ if (delete && u)
+ unit_free(u);
+
+ return r;
+}
+
+static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) {
+ const char *sysfs, *dn;
+ struct udev_list_entry *item = NULL, *first = NULL;
+
+ assert(m);
+
+ if (!(sysfs = udev_device_get_syspath(dev)))
+ return -ENOMEM;
+
+ /* Add the main unit named after the sysfs path */
+ device_update_unit(m, dev, sysfs, true);
+
+ /* Add an additional unit for the device node */
+ if ((dn = udev_device_get_devnode(dev)))
+ device_update_unit(m, dev, dn, false);
+
+ /* Add additional units for all symlinks */
+ first = udev_device_get_devlinks_list_entry(dev);
+ udev_list_entry_foreach(item, first) {
+ const char *p;
+ struct stat st;
+
+ /* Don't bother with the /dev/block links */
+ p = udev_list_entry_get_name(item);
+
+ if (path_startswith(p, "/dev/block/") ||
+ path_startswith(p, "/dev/char/"))
+ continue;
+
+ /* Verify that the symlink in the FS actually belongs
+ * to this device. This is useful to deal with
+ * conflicting devices, e.g. when two disks want the
+ * same /dev/disk/by-label/xxx link because they have
+ * the same label. We want to make sure that the same
+ * device that won the symlink wins in systemd, so we
+ * check the device node major/minor*/
+ if (stat(p, &st) >= 0)
+ if ((!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) ||
+ st.st_rdev != udev_device_get_devnum(dev))
+ continue;
+
+ device_update_unit(m, dev, p, false);
+ }
+
+ if (update_state) {
+ Device *d, *l;
+
+ manager_dispatch_load_queue(m);
+
+ l = hashmap_get(m->devices_by_sysfs, sysfs);
+ LIST_FOREACH(same_sysfs, d, l)
+ device_set_state(d, DEVICE_PLUGGED);
+ }
+
+ return 0;
+}
+
+static int device_process_path(Manager *m, const char *path, bool update_state) {
+ int r;
+ struct udev_device *dev;
+
+ assert(m);
+ assert(path);
+
+ if (!(dev = udev_device_new_from_syspath(m->udev, path))) {
+ log_warning("Failed to get udev device object from udev for path %s.", path);
+ return -ENOMEM;
+ }
+
+ r = device_process_new_device(m, dev, update_state);
+ udev_device_unref(dev);
+ return r;
+}
+
+static int device_process_removed_device(Manager *m, struct udev_device *dev) {
+ const char *sysfs;
+ Device *d;
+
+ assert(m);
+ assert(dev);
+
+ if (!(sysfs = udev_device_get_syspath(dev)))
+ return -ENOMEM;
+
+ /* Remove all units of this sysfs path */
+ while ((d = hashmap_get(m->devices_by_sysfs, sysfs))) {
+ device_unset_sysfs(d);
+ device_set_state(d, DEVICE_DEAD);
+ }
+
+ return 0;
+}
+
+static Unit *device_following(Unit *u) {
+ Device *d = DEVICE(u);
+ Device *other, *first = NULL;
+
+ assert(d);
+
+ if (startswith(u->id, "sys-"))
+ return NULL;
+
+ /* Make everybody follow the unit that's named after the sysfs path */
+ for (other = d->same_sysfs_next; other; other = other->same_sysfs_next)
+ if (startswith(UNIT(other)->id, "sys-"))
+ return UNIT(other);
+
+ for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) {
+ if (startswith(UNIT(other)->id, "sys-"))
+ return UNIT(other);
+
+ first = other;
+ }
+
+ return UNIT(first);
+}
+
+static int device_following_set(Unit *u, Set **_s) {
+ Device *d = DEVICE(u);
+ Device *other;
+ Set *s;
+ int r;
+
+ assert(d);
+ assert(_s);
+
+ if (!d->same_sysfs_prev && !d->same_sysfs_next) {
+ *_s = NULL;
+ return 0;
+ }
+
+ if (!(s = set_new(NULL, NULL)))
+ return -ENOMEM;
+
+ for (other = d->same_sysfs_next; other; other = other->same_sysfs_next)
+ if ((r = set_put(s, other)) < 0)
+ goto fail;
+
+ for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev)
+ if ((r = set_put(s, other)) < 0)
+ goto fail;
+
+ *_s = s;
+ return 1;
+
+fail:
+ set_free(s);
+ return r;
+}
+
+static void device_shutdown(Manager *m) {
+ assert(m);
+
+ if (m->udev_monitor) {
+ udev_monitor_unref(m->udev_monitor);
+ m->udev_monitor = NULL;
+ }
+
+ if (m->udev) {
+ udev_unref(m->udev);
+ m->udev = NULL;
+ }
+
+ hashmap_free(m->devices_by_sysfs);
+ m->devices_by_sysfs = NULL;
+}
+
+static int device_enumerate(Manager *m) {
+ struct epoll_event ev;
+ int r;
+ struct udev_enumerate *e = NULL;
+ struct udev_list_entry *item = NULL, *first = NULL;
+
+ assert(m);
+
+ if (!m->udev) {
+ if (!(m->udev = udev_new()))
+ return -ENOMEM;
+
+ if (!(m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ /* This will fail if we are unprivileged, but that
+ * should not matter much, as user instances won't run
+ * during boot. */
+ udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024);
+
+ if (udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd") < 0) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (udev_monitor_enable_receiving(m->udev_monitor) < 0) {
+ r = -EIO;
+ goto fail;
+ }
+
+ m->udev_watch.type = WATCH_UDEV;
+ m->udev_watch.fd = udev_monitor_get_fd(m->udev_monitor);
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.ptr = &m->udev_watch;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_watch.fd, &ev) < 0)
+ return -errno;
+ }
+
+ if (!(e = udev_enumerate_new(m->udev))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ if (udev_enumerate_add_match_tag(e, "systemd") < 0) {
+ r = -EIO;
+ goto fail;
+ }
+
+ if (udev_enumerate_scan_devices(e) < 0) {
+ r = -EIO;
+ goto fail;
+ }
+
+ first = udev_enumerate_get_list_entry(e);
+ udev_list_entry_foreach(item, first)
+ device_process_path(m, udev_list_entry_get_name(item), false);
+
+ udev_enumerate_unref(e);
+ return 0;
+
+fail:
+ if (e)
+ udev_enumerate_unref(e);
+
+ device_shutdown(m);
+ return r;
+}
+
+void device_fd_event(Manager *m, int events) {
+ struct udev_device *dev;
+ int r;
+ const char *action, *ready;
+
+ assert(m);
+
+ if (events != EPOLLIN) {
+ static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5);
+
+ if (!ratelimit_test(&limit))
+ log_error("Failed to get udev event: %m");
+ if (!(events & EPOLLIN))
+ return;
+ }
+
+ if (!(dev = udev_monitor_receive_device(m->udev_monitor))) {
+ /*
+ * libudev might filter-out devices which pass the bloom filter,
+ * so getting NULL here is not necessarily an error
+ */
+ return;
+ }
+
+ if (!(action = udev_device_get_action(dev))) {
+ log_error("Failed to get udev action string.");
+ goto fail;
+ }
+
+ ready = udev_device_get_property_value(dev, "SYSTEMD_READY");
+
+ if (streq(action, "remove") || (ready && parse_boolean(ready) == 0)) {
+ if ((r = device_process_removed_device(m, dev)) < 0) {
+ log_error("Failed to process udev device event: %s", strerror(-r));
+ goto fail;
+ }
+ } else {
+ if ((r = device_process_new_device(m, dev, true)) < 0) {
+ log_error("Failed to process udev device event: %s", strerror(-r));
+ goto fail;
+ }
+ }
+
+fail:
+ udev_device_unref(dev);
+}
+
+static const char* const device_state_table[_DEVICE_STATE_MAX] = {
+ [DEVICE_DEAD] = "dead",
+ [DEVICE_PLUGGED] = "plugged"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState);
+
+const UnitVTable device_vtable = {
+ .object_size = sizeof(Device),
+ .sections =
+ "Unit\0"
+ "Device\0"
+ "Install\0",
+
+ .no_instances = true,
+
+ .init = device_init,
+
+ .load = unit_load_fragment_and_dropin_optional,
+ .done = device_done,
+ .coldplug = device_coldplug,
+
+ .dump = device_dump,
+
+ .active_state = device_active_state,
+ .sub_state_to_string = device_sub_state_to_string,
+
+ .bus_interface = "org.freedesktop.systemd1.Device",
+ .bus_message_handler = bus_device_message_handler,
+ .bus_invalidating_properties = bus_device_invalidating_properties,
+
+ .following = device_following,
+ .following_set = device_following_set,
+
+ .enumerate = device_enumerate,
+ .shutdown = device_shutdown,
+
+ .status_message_formats = {
+ .starting_stopping = {
+ [0] = "Expecting device %s...",
+ },
+ .finished_start_job = {
+ [JOB_DONE] = "Found device %s.",
+ [JOB_TIMEOUT] = "Timed out waiting for device %s.",
+ },
+ },
+};
diff --git a/src/core/device.h b/src/core/device.h
new file mode 100644
index 0000000000..3c4604f60e
--- /dev/null
+++ b/src/core/device.h
@@ -0,0 +1,56 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Device Device;
+
+#include "unit.h"
+
+/* We simply watch devices, we cannot plug/unplug them. That
+ * simplifies the state engine greatly */
+typedef enum DeviceState {
+ DEVICE_DEAD,
+ DEVICE_PLUGGED,
+ _DEVICE_STATE_MAX,
+ _DEVICE_STATE_INVALID = -1
+} DeviceState;
+
+struct Device {
+ Unit meta;
+
+ char *sysfs;
+
+ /* In order to be able to distinguish dependencies on
+ different device nodes we might end up creating multiple
+ devices for the same sysfs path. We chain them up here. */
+
+ LIST_FIELDS(struct Device, same_sysfs);
+
+ DeviceState state;
+};
+
+extern const UnitVTable device_vtable;
+
+void device_fd_event(Manager *m, int events);
+
+const char* device_state_to_string(DeviceState i);
+DeviceState device_state_from_string(const char *s);
diff --git a/src/core/execute.c b/src/core/execute.c
new file mode 100644
index 0000000000..e236d38e0f
--- /dev/null
+++ b/src/core/execute.c
@@ -0,0 +1,2132 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/prctl.h>
+#include <linux/sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+#include <linux/oom.h>
+#include <sys/poll.h>
+#include <linux/seccomp-bpf.h>
+
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+#endif
+
+#include "execute.h"
+#include "strv.h"
+#include "macro.h"
+#include "capability.h"
+#include "util.h"
+#include "log.h"
+#include "ioprio.h"
+#include "securebits.h"
+#include "cgroup.h"
+#include "namespace.h"
+#include "tcpwrap.h"
+#include "exit-status.h"
+#include "missing.h"
+#include "utmp-wtmp.h"
+#include "def.h"
+#include "loopback-setup.h"
+#include "path-util.h"
+#include "syscall-list.h"
+#include "sd-id128.h"
+#include "sd-messages.h"
+
+#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC)
+
+/* This assumes there is a 'tty' group */
+#define TTY_MODE 0620
+
+static int shift_fds(int fds[], unsigned n_fds) {
+ int start, restart_from;
+
+ if (n_fds <= 0)
+ return 0;
+
+ /* Modifies the fds array! (sorts it) */
+
+ assert(fds);
+
+ start = 0;
+ for (;;) {
+ int i;
+
+ restart_from = -1;
+
+ for (i = start; i < (int) n_fds; i++) {
+ int nfd;
+
+ /* Already at right index? */
+ if (fds[i] == i+3)
+ continue;
+
+ if ((nfd = fcntl(fds[i], F_DUPFD, i+3)) < 0)
+ return -errno;
+
+ close_nointr_nofail(fds[i]);
+ fds[i] = nfd;
+
+ /* Hmm, the fd we wanted isn't free? Then
+ * let's remember that and try again from here*/
+ if (nfd != i+3 && restart_from < 0)
+ restart_from = i;
+ }
+
+ if (restart_from < 0)
+ break;
+
+ start = restart_from;
+ }
+
+ return 0;
+}
+
+static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) {
+ unsigned i;
+ int r;
+
+ if (n_fds <= 0)
+ return 0;
+
+ assert(fds);
+
+ /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */
+
+ for (i = 0; i < n_fds; i++) {
+
+ if ((r = fd_nonblock(fds[i], nonblock)) < 0)
+ return r;
+
+ /* We unconditionally drop FD_CLOEXEC from the fds,
+ * since after all we want to pass these fds to our
+ * children */
+
+ if ((r = fd_cloexec(fds[i], false)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static const char *tty_path(const ExecContext *context) {
+ assert(context);
+
+ if (context->tty_path)
+ return context->tty_path;
+
+ return "/dev/console";
+}
+
+void exec_context_tty_reset(const ExecContext *context) {
+ assert(context);
+
+ if (context->tty_vhangup)
+ terminal_vhangup(tty_path(context));
+
+ if (context->tty_reset)
+ reset_terminal(tty_path(context));
+
+ if (context->tty_vt_disallocate && context->tty_path)
+ vt_disallocate(context->tty_path);
+}
+
+static int open_null_as(int flags, int nfd) {
+ int fd, r;
+
+ assert(nfd >= 0);
+
+ if ((fd = open("/dev/null", flags|O_NOCTTY)) < 0)
+ return -errno;
+
+ if (fd != nfd) {
+ r = dup2(fd, nfd) < 0 ? -errno : nfd;
+ close_nointr_nofail(fd);
+ } else
+ r = nfd;
+
+ return r;
+}
+
+static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd) {
+ int fd, r;
+ union sockaddr_union sa;
+
+ assert(context);
+ assert(output < _EXEC_OUTPUT_MAX);
+ assert(ident);
+ assert(nfd >= 0);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -errno;
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path));
+
+ r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
+ if (r < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (shutdown(fd, SHUT_RD) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ dprintf(fd,
+ "%s\n"
+ "%s\n"
+ "%i\n"
+ "%i\n"
+ "%i\n"
+ "%i\n"
+ "%i\n",
+ context->syslog_identifier ? context->syslog_identifier : ident,
+ unit_id,
+ context->syslog_priority,
+ !!context->syslog_level_prefix,
+ output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE,
+ output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE,
+ output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE);
+
+ if (fd != nfd) {
+ r = dup2(fd, nfd) < 0 ? -errno : nfd;
+ close_nointr_nofail(fd);
+ } else
+ r = nfd;
+
+ return r;
+}
+static int open_terminal_as(const char *path, mode_t mode, int nfd) {
+ int fd, r;
+
+ assert(path);
+ assert(nfd >= 0);
+
+ if ((fd = open_terminal(path, mode | O_NOCTTY)) < 0)
+ return fd;
+
+ if (fd != nfd) {
+ r = dup2(fd, nfd) < 0 ? -errno : nfd;
+ close_nointr_nofail(fd);
+ } else
+ r = nfd;
+
+ return r;
+}
+
+static bool is_terminal_input(ExecInput i) {
+ return
+ i == EXEC_INPUT_TTY ||
+ i == EXEC_INPUT_TTY_FORCE ||
+ i == EXEC_INPUT_TTY_FAIL;
+}
+
+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_input;
+}
+
+static int fixup_output(ExecOutput std_output, int socket_fd) {
+
+ if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0)
+ return EXEC_OUTPUT_INHERIT;
+
+ return std_output;
+}
+
+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, apply_tty_stdin);
+
+ switch (i) {
+
+ case EXEC_INPUT_NULL:
+ return open_null_as(O_RDONLY, STDIN_FILENO);
+
+ case EXEC_INPUT_TTY:
+ case EXEC_INPUT_TTY_FORCE:
+ case EXEC_INPUT_TTY_FAIL: {
+ int fd, r;
+
+ if ((fd = acquire_terminal(
+ tty_path(context),
+ i == EXEC_INPUT_TTY_FAIL,
+ i == EXEC_INPUT_TTY_FORCE,
+ false,
+ (usec_t) -1)) < 0)
+ return fd;
+
+ if (fd != STDIN_FILENO) {
+ r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
+ close_nointr_nofail(fd);
+ } else
+ r = STDIN_FILENO;
+
+ return r;
+ }
+
+ case EXEC_INPUT_SOCKET:
+ return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
+
+ default:
+ assert_not_reached("Unknown input type");
+ }
+}
+
+static int setup_output(const ExecContext *context, int socket_fd, const char *ident, const char *unit_id, bool apply_tty_stdin) {
+ ExecOutput o;
+ ExecInput i;
+
+ assert(context);
+ assert(ident);
+
+ 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 */
+
+ switch (o) {
+
+ case EXEC_OUTPUT_INHERIT:
+
+ /* 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;
+
+ /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */
+ if (getppid() != 1)
+ 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);
+
+ case EXEC_OUTPUT_TTY:
+ if (is_terminal_input(i))
+ return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
+
+ /* We don't reset the terminal if this is just about output */
+ 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:
+ case EXEC_OUTPUT_JOURNAL:
+ case EXEC_OUTPUT_JOURNAL_AND_CONSOLE:
+ return connect_logger_as(context, o, ident, unit_id, STDOUT_FILENO);
+
+ case EXEC_OUTPUT_SOCKET:
+ assert(socket_fd >= 0);
+ return dup2(socket_fd, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
+
+ default:
+ assert_not_reached("Unknown output type");
+ }
+}
+
+static int setup_error(const ExecContext *context, int socket_fd, const char *ident, const char *unit_id, bool apply_tty_stdin) {
+ ExecOutput o, e;
+ ExecInput i;
+
+ assert(context);
+ assert(ident);
+
+ 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);
+
+ /* This expects the input and output are already set up */
+
+ /* Don't change the stderr file descriptor if we inherit all
+ * the way and are not on a tty */
+ if (e == EXEC_OUTPUT_INHERIT &&
+ o == EXEC_OUTPUT_INHERIT &&
+ i == EXEC_INPUT_NULL &&
+ !is_terminal_input(context->std_input) &&
+ getppid () != 1)
+ return STDERR_FILENO;
+
+ /* Duplicate from stdout if possible */
+ if (e == o || e == EXEC_OUTPUT_INHERIT)
+ return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
+
+ switch (e) {
+
+ case EXEC_OUTPUT_NULL:
+ return open_null_as(O_WRONLY, STDERR_FILENO);
+
+ case EXEC_OUTPUT_TTY:
+ if (is_terminal_input(i))
+ return dup2(STDIN_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
+
+ /* We don't reset the terminal if this is just about output */
+ 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:
+ case EXEC_OUTPUT_JOURNAL:
+ case EXEC_OUTPUT_JOURNAL_AND_CONSOLE:
+ return connect_logger_as(context, e, ident, unit_id, STDERR_FILENO);
+
+ case EXEC_OUTPUT_SOCKET:
+ assert(socket_fd >= 0);
+ return dup2(socket_fd, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
+
+ default:
+ assert_not_reached("Unknown error type");
+ }
+}
+
+static int chown_terminal(int fd, uid_t uid) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ /* This might fail. What matters are the results. */
+ (void) fchown(fd, uid, -1);
+ (void) fchmod(fd, TTY_MODE);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE)
+ return -EPERM;
+
+ return 0;
+}
+
+static int setup_confirm_stdio(int *_saved_stdin,
+ int *_saved_stdout) {
+ int fd = -1, saved_stdin, saved_stdout = -1, r;
+
+ assert(_saved_stdin);
+ assert(_saved_stdout);
+
+ saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3);
+ if (saved_stdin < 0)
+ return -errno;
+
+ saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3);
+ if (saved_stdout < 0) {
+ r = errno;
+ goto fail;
+ }
+
+ fd = acquire_terminal(
+ "/dev/console",
+ false,
+ false,
+ false,
+ DEFAULT_CONFIRM_USEC);
+ if (fd < 0) {
+ r = fd;
+ goto fail;
+ }
+
+ r = chown_terminal(fd, getuid());
+ if (r < 0)
+ goto fail;
+
+ if (dup2(fd, STDIN_FILENO) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (dup2(fd, STDOUT_FILENO) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (fd >= 2)
+ close_nointr_nofail(fd);
+
+ *_saved_stdin = saved_stdin;
+ *_saved_stdout = saved_stdout;
+
+ return 0;
+
+fail:
+ if (saved_stdout >= 0)
+ close_nointr_nofail(saved_stdout);
+
+ if (saved_stdin >= 0)
+ close_nointr_nofail(saved_stdin);
+
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+static int write_confirm_message(const char *format, ...) {
+ int fd;
+ va_list ap;
+
+ assert(format);
+
+ fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ va_start(ap, format);
+ vdprintf(fd, format, ap);
+ va_end(ap);
+
+ close_nointr_nofail(fd);
+
+ return 0;
+}
+
+static int restore_confirm_stdio(int *saved_stdin,
+ int *saved_stdout) {
+
+ int r = 0;
+
+ assert(saved_stdin);
+ assert(saved_stdout);
+
+ release_terminal();
+
+ if (*saved_stdin >= 0)
+ if (dup2(*saved_stdin, STDIN_FILENO) < 0)
+ r = -errno;
+
+ if (*saved_stdout >= 0)
+ if (dup2(*saved_stdout, STDOUT_FILENO) < 0)
+ r = -errno;
+
+ if (*saved_stdin >= 0)
+ close_nointr_nofail(*saved_stdin);
+
+ if (*saved_stdout >= 0)
+ close_nointr_nofail(*saved_stdout);
+
+ return r;
+}
+
+static int ask_for_confirmation(char *response, char **argv) {
+ int saved_stdout = -1, saved_stdin = -1, r;
+ char *line;
+
+ r = setup_confirm_stdio(&saved_stdin, &saved_stdout);
+ if (r < 0)
+ return r;
+
+ line = exec_command_line(argv);
+ if (!line)
+ return -ENOMEM;
+
+ r = ask(response, "yns", "Execute %s? [Yes, No, Skip] ", line);
+ free(line);
+
+ restore_confirm_stdio(&saved_stdin, &saved_stdout);
+
+ return r;
+}
+
+static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) {
+ bool keep_groups = false;
+ int r;
+
+ assert(context);
+
+ /* Lookup and set GID and supplementary group list. Here too
+ * we avoid NSS lookups for gid=0. */
+
+ if (context->group || username) {
+
+ if (context->group) {
+ const char *g = context->group;
+
+ if ((r = get_group_creds(&g, &gid)) < 0)
+ return r;
+ }
+
+ /* First step, initialize groups from /etc/groups */
+ if (username && gid != 0) {
+ if (initgroups(username, gid) < 0)
+ return -errno;
+
+ keep_groups = true;
+ }
+
+ /* Second step, set our gids */
+ if (setresgid(gid, gid, gid) < 0)
+ return -errno;
+ }
+
+ if (context->supplementary_groups) {
+ int ngroups_max, k;
+ gid_t *gids;
+ char **i;
+
+ /* Final step, initialize any manually set supplementary groups */
+ assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0);
+
+ if (!(gids = new(gid_t, ngroups_max)))
+ return -ENOMEM;
+
+ if (keep_groups) {
+ if ((k = getgroups(ngroups_max, gids)) < 0) {
+ free(gids);
+ return -errno;
+ }
+ } else
+ k = 0;
+
+ STRV_FOREACH(i, context->supplementary_groups) {
+ const char *g;
+
+ if (k >= ngroups_max) {
+ free(gids);
+ return -E2BIG;
+ }
+
+ g = *i;
+ r = get_group_creds(&g, gids+k);
+ if (r < 0) {
+ free(gids);
+ return r;
+ }
+
+ k++;
+ }
+
+ if (setgroups(k, gids) < 0) {
+ free(gids);
+ return -errno;
+ }
+
+ free(gids);
+ }
+
+ return 0;
+}
+
+static int enforce_user(const ExecContext *context, uid_t uid) {
+ int r;
+ assert(context);
+
+ /* Sets (but doesn't lookup) the uid and make sure we keep the
+ * capabilities while doing so. */
+
+ if (context->capabilities) {
+ cap_t d;
+ static const cap_value_t bits[] = {
+ CAP_SETUID, /* Necessary so that we can run setresuid() below */
+ CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */
+ };
+
+ /* First step: If we need to keep capabilities but
+ * drop privileges we need to make sure we keep our
+ * caps, whiel we drop privileges. */
+ if (uid != 0) {
+ int sb = context->secure_bits|SECURE_KEEP_CAPS;
+
+ if (prctl(PR_GET_SECUREBITS) != sb)
+ if (prctl(PR_SET_SECUREBITS, sb) < 0)
+ return -errno;
+ }
+
+ /* Second step: set the capabilities. This will reduce
+ * the capabilities to the minimum we need. */
+
+ if (!(d = cap_dup(context->capabilities)))
+ return -errno;
+
+ if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
+ cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) {
+ r = -errno;
+ cap_free(d);
+ return r;
+ }
+
+ if (cap_set_proc(d) < 0) {
+ r = -errno;
+ cap_free(d);
+ return r;
+ }
+
+ cap_free(d);
+ }
+
+ /* Third step: actually set the uids */
+ if (setresuid(uid, uid, uid) < 0)
+ return -errno;
+
+ /* At this point we should have all necessary capabilities but
+ are otherwise a normal user. However, the caps might got
+ corrupted due to the setresuid() so we need clean them up
+ later. This is done outside of this call. */
+
+ return 0;
+}
+
+#ifdef HAVE_PAM
+
+static int null_conv(
+ int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *appdata_ptr) {
+
+ /* We don't support conversations */
+
+ return PAM_CONV_ERR;
+}
+
+static int setup_pam(
+ const char *name,
+ const char *user,
+ uid_t uid,
+ const char *tty,
+ char ***pam_env,
+ int fds[], unsigned n_fds) {
+
+ static const struct pam_conv conv = {
+ .conv = null_conv,
+ .appdata_ptr = NULL
+ };
+
+ pam_handle_t *handle = NULL;
+ sigset_t ss, old_ss;
+ int pam_code = PAM_SUCCESS;
+ int err;
+ char **e = NULL;
+ bool close_session = false;
+ pid_t pam_pid = 0, parent_pid;
+
+ assert(name);
+ assert(user);
+ assert(pam_env);
+
+ /* We set up PAM in the parent process, then fork. The child
+ * 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
+ * of the daemon is the one we initially fork()ed. */
+
+ if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) {
+ handle = NULL;
+ goto fail;
+ }
+
+ if (tty)
+ if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS)
+ goto fail;
+
+ if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS)
+ goto fail;
+
+ if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS)
+ goto fail;
+
+ close_session = true;
+
+ if ((!(e = pam_getenvlist(handle)))) {
+ pam_code = PAM_BUF_ERR;
+ goto fail;
+ }
+
+ /* Block SIGTERM, so that we know that it won't get lost in
+ * the child */
+ if (sigemptyset(&ss) < 0 ||
+ sigaddset(&ss, SIGTERM) < 0 ||
+ sigprocmask(SIG_BLOCK, &ss, &old_ss) < 0)
+ goto fail;
+
+ parent_pid = getpid();
+
+ if ((pam_pid = fork()) < 0)
+ goto fail;
+
+ if (pam_pid == 0) {
+ int sig;
+ int r = EXIT_PAM;
+
+ /* The child's job is to reset the PAM session on
+ * termination */
+
+ /* This string must fit in 10 chars (i.e. the length
+ * of "/sbin/init"), to look pretty in /bin/ps */
+ rename_process("(sd-pam)");
+
+ /* Make sure we don't keep open the passed fds in this
+ child. We assume that otherwise only those fds are
+ open here that have been opened by PAM. */
+ close_many(fds, n_fds);
+
+ /* Drop privileges - we don't need any to pam_close_session
+ * and this will make PR_SET_PDEATHSIG work in most cases.
+ * If this fails, ignore the error - but expect sd-pam threads
+ * to fail to exit normally */
+ if (setresuid(uid, uid, uid) < 0)
+ log_error("Error: Failed to setresuid() in sd-pam: %s", strerror(-r));
+
+ /* Wait until our parent died. This will only work if
+ * the above setresuid() succeeds, otherwise the kernel
+ * will not allow 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)
+ goto child_finish;
+
+ /* Check if our parent process might already have
+ * died? */
+ if (getppid() == parent_pid) {
+ for (;;) {
+ if (sigwait(&ss, &sig) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ goto child_finish;
+ }
+
+ assert(sig == SIGTERM);
+ break;
+ }
+ }
+
+ /* If our parent died we'll end the session */
+ if (getppid() != parent_pid)
+ if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS)
+ goto child_finish;
+
+ r = 0;
+
+ child_finish:
+ pam_end(handle, pam_code | PAM_DATA_SILENT);
+ _exit(r);
+ }
+
+ /* If the child was forked off successfully it will do all the
+ * cleanups, so forget about the handle here. */
+ handle = NULL;
+
+ /* Unblock SIGTERM again in the parent */
+ if (sigprocmask(SIG_SETMASK, &old_ss, NULL) < 0)
+ goto fail;
+
+ /* We close the log explicitly here, since the PAM modules
+ * might have opened it, but we don't want this fd around. */
+ closelog();
+
+ *pam_env = e;
+ e = NULL;
+
+ return 0;
+
+fail:
+ if (pam_code != PAM_SUCCESS)
+ err = -EPERM; /* PAM errors do not map to errno */
+ else
+ err = -errno;
+
+ if (handle) {
+ if (close_session)
+ pam_code = pam_close_session(handle, PAM_DATA_SILENT);
+
+ pam_end(handle, pam_code | PAM_DATA_SILENT);
+ }
+
+ strv_free(e);
+
+ closelog();
+
+ if (pam_pid > 1) {
+ kill(pam_pid, SIGTERM);
+ kill(pam_pid, SIGCONT);
+ }
+
+ return err;
+}
+#endif
+
+static void rename_process_from_path(const char *path) {
+ char process_name[11];
+ const char *p;
+ size_t l;
+
+ /* This resulting string must fit in 10 chars (i.e. the length
+ * of "/sbin/init") to look pretty in /bin/ps */
+
+ p = path_get_file_name(path);
+ if (isempty(p)) {
+ rename_process("(...)");
+ return;
+ }
+
+ l = strlen(p);
+ if (l > 8) {
+ /* The end of the process name is usually more
+ * interesting, since the first bit might just be
+ * "systemd-" */
+ p = p + l - 8;
+ l = 8;
+ }
+
+ process_name[0] = '(';
+ memcpy(process_name+1, p, l);
+ process_name[1+l] = ')';
+ process_name[1+l+1] = 0;
+
+ rename_process(process_name);
+}
+
+static int apply_seccomp(uint32_t *syscall_filter) {
+ static const struct sock_filter header[] = {
+ VALIDATE_ARCHITECTURE,
+ EXAMINE_SYSCALL
+ };
+ static const struct sock_filter footer[] = {
+ _KILL_PROCESS
+ };
+
+ int i;
+ unsigned n;
+ struct sock_filter *f;
+ struct sock_fprog prog;
+
+ assert(syscall_filter);
+
+ /* First: count the syscalls to check for */
+ for (i = 0, n = 0; i < syscall_max(); i++)
+ if (syscall_filter[i >> 4] & (1 << (i & 31)))
+ n++;
+
+ /* Second: build the filter program from a header the syscall
+ * matches and the footer */
+ f = alloca(sizeof(struct sock_filter) * (ELEMENTSOF(header) + 2*n + ELEMENTSOF(footer)));
+ memcpy(f, header, sizeof(header));
+
+ for (i = 0, n = 0; i < syscall_max(); i++)
+ if (syscall_filter[i >> 4] & (1 << (i & 31))) {
+ struct sock_filter item[] = {
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, i, 0, 1),
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
+ };
+
+ assert_cc(ELEMENTSOF(item) == 2);
+
+ f[ELEMENTSOF(header) + 2*n] = item[0];
+ f[ELEMENTSOF(header) + 2*n+1] = item[1];
+
+ n++;
+ }
+
+ memcpy(f + (ELEMENTSOF(header) + 2*n), footer, sizeof(footer));
+
+ /* Third: install the filter */
+ zero(prog);
+ prog.len = ELEMENTSOF(header) + ELEMENTSOF(footer) + 2*n;
+ prog.filter = f;
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int exec_spawn(ExecCommand *command,
+ char **argv,
+ const ExecContext *context,
+ int fds[], unsigned n_fds,
+ char **environment,
+ bool apply_permissions,
+ bool apply_chroot,
+ bool apply_tty_stdin,
+ bool confirm_spawn,
+ CGroupBonding *cgroup_bondings,
+ CGroupAttribute *cgroup_attributes,
+ const char *cgroup_suffix,
+ const char *unit_id,
+ int idle_pipe[2],
+ pid_t *ret) {
+
+ pid_t pid;
+ int r;
+ char *line;
+ int socket_fd;
+ char _cleanup_strv_free_ **files_env = NULL;
+
+ assert(command);
+ assert(context);
+ assert(ret);
+ assert(fds || n_fds <= 0);
+
+ if (context->std_input == EXEC_INPUT_SOCKET ||
+ context->std_output == EXEC_OUTPUT_SOCKET ||
+ context->std_error == EXEC_OUTPUT_SOCKET) {
+
+ if (n_fds != 1)
+ return -EINVAL;
+
+ socket_fd = fds[0];
+
+ fds = NULL;
+ n_fds = 0;
+ } else
+ socket_fd = -1;
+
+ r = exec_context_load_environment(context, &files_env);
+ if (r < 0) {
+ log_struct(LOG_ERR,
+ "UNIT=%s", unit_id,
+ "MESSAGE=Failed to load environment files: %s", strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+ return r;
+ }
+
+ if (!argv)
+ argv = command->argv;
+
+ line = exec_command_line(argv);
+ if (!line)
+ return log_oom();
+
+ log_struct(LOG_DEBUG,
+ "UNIT=%s", unit_id,
+ "MESSAGE=About to execute %s", line,
+ NULL);
+ free(line);
+
+ r = cgroup_bonding_realize_list(cgroup_bondings);
+ if (r < 0)
+ return r;
+
+ cgroup_attribute_apply_list(cgroup_attributes, cgroup_bondings);
+
+ pid = fork();
+ if (pid < 0)
+ return -errno;
+
+ if (pid == 0) {
+ int i, err;
+ sigset_t ss;
+ const char *username = NULL, *home = NULL;
+ uid_t uid = (uid_t) -1;
+ gid_t gid = (gid_t) -1;
+ char _cleanup_strv_free_ **our_env = NULL, **pam_env = NULL,
+ **final_env = NULL, **final_argv = NULL;
+ unsigned n_env = 0;
+ bool set_access = false;
+
+ /* child */
+
+ rename_process_from_path(command->path);
+
+ /* We reset exactly these signals, since they are the
+ * only ones we set to SIG_IGN in the main daemon. All
+ * others we leave untouched because we set them to
+ * SIG_DFL or a valid handler initially, both of which
+ * will be demoted to SIG_DFL. */
+ default_signals(SIGNALS_CRASH_HANDLER,
+ SIGNALS_IGNORE, -1);
+
+ if (context->ignore_sigpipe)
+ ignore_signals(SIGPIPE, -1);
+
+ assert_se(sigemptyset(&ss) == 0);
+ if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) {
+ err = -errno;
+ r = EXIT_SIGNAL_MASK;
+ goto fail_child;
+ }
+
+ if (idle_pipe) {
+ if (idle_pipe[1] >= 0)
+ close_nointr_nofail(idle_pipe[1]);
+ if (idle_pipe[0] >= 0) {
+ fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC);
+ close_nointr_nofail(idle_pipe[0]);
+ }
+ }
+
+ /* Close sockets very early to make sure we don't
+ * block init reexecution because it cannot bind its
+ * sockets */
+ log_forget_fds();
+ err = close_all_fds(socket_fd >= 0 ? &socket_fd : fds,
+ socket_fd >= 0 ? 1 : n_fds);
+ if (err < 0) {
+ r = EXIT_FDS;
+ goto fail_child;
+ }
+
+ if (!context->same_pgrp)
+ if (setsid() < 0) {
+ err = -errno;
+ r = EXIT_SETSID;
+ goto fail_child;
+ }
+
+ if (context->tcpwrap_name) {
+ if (socket_fd >= 0)
+ if (!socket_tcpwrap(socket_fd, context->tcpwrap_name)) {
+ err = -EACCES;
+ r = EXIT_TCPWRAP;
+ goto fail_child;
+ }
+
+ for (i = 0; i < (int) n_fds; i++) {
+ if (!socket_tcpwrap(fds[i], context->tcpwrap_name)) {
+ err = -EACCES;
+ r = EXIT_TCPWRAP;
+ goto fail_child;
+ }
+ }
+ }
+
+ exec_context_tty_reset(context);
+
+ if (confirm_spawn) {
+ char response;
+
+ err = ask_for_confirmation(&response, argv);
+ if (err == -ETIMEDOUT)
+ write_confirm_message("Confirmation question timed out, assuming positive response.\n");
+ else if (err < 0)
+ write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-err));
+ else if (response == 's') {
+ write_confirm_message("Skipping execution.\n");
+ err = -ECANCELED;
+ r = EXIT_CONFIRM;
+ goto fail_child;
+ } else if (response == 'n') {
+ write_confirm_message("Failing execution.\n");
+ err = r = 0;
+ goto fail_child;
+ }
+ }
+
+ /* 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);
+
+ err = setup_input(context, socket_fd, apply_tty_stdin);
+ if (err < 0) {
+ r = EXIT_STDIN;
+ goto fail_child;
+ }
+
+ err = setup_output(context, socket_fd, path_get_file_name(command->path), unit_id, apply_tty_stdin);
+ if (err < 0) {
+ r = EXIT_STDOUT;
+ goto fail_child;
+ }
+
+ err = setup_error(context, socket_fd, path_get_file_name(command->path), unit_id, apply_tty_stdin);
+ if (err < 0) {
+ r = EXIT_STDERR;
+ goto fail_child;
+ }
+
+ if (cgroup_bondings) {
+ err = cgroup_bonding_install_list(cgroup_bondings, 0, cgroup_suffix);
+ if (err < 0) {
+ r = EXIT_CGROUP;
+ goto fail_child;
+ }
+ }
+
+ if (context->oom_score_adjust_set) {
+ char t[16];
+
+ snprintf(t, sizeof(t), "%i", context->oom_score_adjust);
+ char_array_0(t);
+
+ if (write_one_line_file("/proc/self/oom_score_adj", t) < 0) {
+ err = -errno;
+ r = EXIT_OOM_ADJUST;
+ goto fail_child;
+ }
+ }
+
+ if (context->nice_set)
+ if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) {
+ err = -errno;
+ r = EXIT_NICE;
+ goto fail_child;
+ }
+
+ if (context->cpu_sched_set) {
+ struct sched_param param;
+
+ zero(param);
+ param.sched_priority = context->cpu_sched_priority;
+
+ if (sched_setscheduler(0, context->cpu_sched_policy |
+ (context->cpu_sched_reset_on_fork ? SCHED_RESET_ON_FORK : 0), &param) < 0) {
+ err = -errno;
+ r = EXIT_SETSCHEDULER;
+ goto fail_child;
+ }
+ }
+
+ if (context->cpuset)
+ if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) {
+ err = -errno;
+ r = EXIT_CPUAFFINITY;
+ goto fail_child;
+ }
+
+ if (context->ioprio_set)
+ if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) {
+ err = -errno;
+ r = EXIT_IOPRIO;
+ goto fail_child;
+ }
+
+ if (context->timer_slack_nsec != (nsec_t) -1)
+ if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) {
+ err = -errno;
+ r = EXIT_TIMERSLACK;
+ goto fail_child;
+ }
+
+ if (context->utmp_id)
+ utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path);
+
+ if (context->user) {
+ username = context->user;
+ err = get_user_creds(&username, &uid, &gid, &home, NULL);
+ if (err < 0) {
+ r = EXIT_USER;
+ goto fail_child;
+ }
+
+ if (is_terminal_input(context->std_input)) {
+ err = chown_terminal(STDIN_FILENO, uid);
+ if (err < 0) {
+ r = EXIT_STDIN;
+ goto fail_child;
+ }
+ }
+
+ if (cgroup_bondings && context->control_group_modify) {
+ err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid);
+ if (err >= 0)
+ err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid, context->control_group_persistent);
+ if (err < 0) {
+ r = EXIT_CGROUP;
+ goto fail_child;
+ }
+
+ set_access = true;
+ }
+ }
+
+ if (cgroup_bondings && !set_access && context->control_group_persistent >= 0) {
+ err = cgroup_bonding_set_task_access_list(cgroup_bondings, (mode_t) -1, (uid_t) -1, (uid_t) -1, context->control_group_persistent);
+ if (err < 0) {
+ r = EXIT_CGROUP;
+ goto fail_child;
+ }
+ }
+
+ if (apply_permissions) {
+ err = enforce_groups(context, username, gid);
+ if (err < 0) {
+ r = EXIT_GROUP;
+ goto fail_child;
+ }
+ }
+
+ umask(context->umask);
+
+#ifdef HAVE_PAM
+ if (apply_permissions && context->pam_name && username) {
+ err = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds);
+ if (err < 0) {
+ r = EXIT_PAM;
+ goto fail_child;
+ }
+ }
+#endif
+ if (context->private_network) {
+ if (unshare(CLONE_NEWNET) < 0) {
+ err = -errno;
+ r = EXIT_NETWORK;
+ goto fail_child;
+ }
+
+ loopback_setup();
+ }
+
+ if (strv_length(context->read_write_dirs) > 0 ||
+ strv_length(context->read_only_dirs) > 0 ||
+ strv_length(context->inaccessible_dirs) > 0 ||
+ context->mount_flags != 0 ||
+ context->private_tmp) {
+ err = setup_namespace(context->read_write_dirs,
+ context->read_only_dirs,
+ context->inaccessible_dirs,
+ context->private_tmp,
+ context->mount_flags);
+ if (err < 0) {
+ r = EXIT_NAMESPACE;
+ goto fail_child;
+ }
+ }
+
+ if (apply_chroot) {
+ if (context->root_directory)
+ if (chroot(context->root_directory) < 0) {
+ err = -errno;
+ r = EXIT_CHROOT;
+ goto fail_child;
+ }
+
+ if (chdir(context->working_directory ? context->working_directory : "/") < 0) {
+ err = -errno;
+ r = EXIT_CHDIR;
+ goto fail_child;
+ }
+ } else {
+ char _cleanup_free_ *d = NULL;
+
+ if (asprintf(&d, "%s/%s",
+ context->root_directory ? context->root_directory : "",
+ context->working_directory ? context->working_directory : "") < 0) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ if (chdir(d) < 0) {
+ err = -errno;
+ r = EXIT_CHDIR;
+ goto fail_child;
+ }
+ }
+
+ /* We repeat the fd closing here, to make sure that
+ * nothing is leaked from the PAM modules */
+ err = close_all_fds(fds, n_fds);
+ if (err >= 0)
+ err = shift_fds(fds, n_fds);
+ if (err >= 0)
+ err = flags_fds(fds, n_fds, context->non_blocking);
+ if (err < 0) {
+ r = EXIT_FDS;
+ goto fail_child;
+ }
+
+ if (apply_permissions) {
+
+ for (i = 0; i < RLIMIT_NLIMITS; i++) {
+ if (!context->rlimit[i])
+ continue;
+
+ if (setrlimit_closest(i, context->rlimit[i]) < 0) {
+ err = -errno;
+ r = EXIT_LIMITS;
+ goto fail_child;
+ }
+ }
+
+ if (context->capability_bounding_set_drop) {
+ err = capability_bounding_set_drop(context->capability_bounding_set_drop, false);
+ if (err < 0) {
+ r = EXIT_CAPABILITIES;
+ goto fail_child;
+ }
+ }
+
+ if (context->user) {
+ err = enforce_user(context, uid);
+ if (err < 0) {
+ r = EXIT_USER;
+ goto fail_child;
+ }
+ }
+
+ /* 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 (prctl(PR_GET_SECUREBITS) != context->secure_bits)
+ if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) {
+ err = -errno;
+ r = EXIT_SECUREBITS;
+ goto fail_child;
+ }
+
+ if (context->capabilities)
+ if (cap_set_proc(context->capabilities) < 0) {
+ err = -errno;
+ r = EXIT_CAPABILITIES;
+ goto fail_child;
+ }
+
+ if (context->no_new_privileges)
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
+ err = -errno;
+ r = EXIT_NO_NEW_PRIVILEGES;
+ goto fail_child;
+ }
+
+ if (context->syscall_filter) {
+ err = apply_seccomp(context->syscall_filter);
+ if (err < 0) {
+ r = EXIT_SECCOMP;
+ goto fail_child;
+ }
+ }
+ }
+
+ if (!(our_env = new0(char*, 7))) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ if (n_fds > 0)
+ if (asprintf(our_env + n_env++, "LISTEN_PID=%lu", (unsigned long) getpid()) < 0 ||
+ asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ if (home)
+ if (asprintf(our_env + n_env++, "HOME=%s", home) < 0) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ if (username)
+ if (asprintf(our_env + n_env++, "LOGNAME=%s", username) < 0 ||
+ asprintf(our_env + n_env++, "USER=%s", username) < 0) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ 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))))) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ assert(n_env <= 7);
+
+ if (!(final_env = strv_env_merge(
+ 5,
+ environment,
+ our_env,
+ context->environment,
+ files_env,
+ pam_env,
+ NULL))) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ if (!(final_argv = replace_env_argv(argv, final_env))) {
+ err = -ENOMEM;
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
+
+ final_env = strv_env_clean(final_env);
+
+ execve(command->path, final_argv, final_env);
+ err = -errno;
+ r = EXIT_EXEC;
+
+ fail_child:
+ if (r != 0) {
+ log_open();
+ log_struct(LOG_ERR, MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED),
+ "EXECUTABLE=%s", command->path,
+ "MESSAGE=Failed at step %s spawning %s: %s",
+ exit_status_to_string(r, EXIT_STATUS_SYSTEMD),
+ command->path, strerror(-err),
+ "ERRNO=%d", -err,
+ NULL);
+ log_close();
+ }
+
+ _exit(r);
+ }
+
+ log_struct(LOG_DEBUG,
+ "UNIT=%s", unit_id,
+ "MESSAGE=Forked %s as %lu",
+ command->path, (unsigned long) pid,
+ NULL);
+
+ /* We add the new process to the cgroup both in the child (so
+ * that we can be sure that no user code is ever executed
+ * outside of the cgroup) and in the parent (so that we can be
+ * sure that when we kill the cgroup the process will be
+ * killed too). */
+ if (cgroup_bondings)
+ cgroup_bonding_install_list(cgroup_bondings, pid, cgroup_suffix);
+
+ exec_status_start(&command->exec_status, pid);
+
+ *ret = pid;
+ return 0;
+}
+
+void exec_context_init(ExecContext *c) {
+ assert(c);
+
+ c->umask = 0022;
+ 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->control_group_persistent = -1;
+ c->ignore_sigpipe = true;
+ c->timer_slack_nsec = (nsec_t) -1;
+}
+
+void exec_context_done(ExecContext *c) {
+ unsigned l;
+
+ assert(c);
+
+ strv_free(c->environment);
+ c->environment = NULL;
+
+ strv_free(c->environment_files);
+ c->environment_files = NULL;
+
+ for (l = 0; l < ELEMENTSOF(c->rlimit); l++) {
+ free(c->rlimit[l]);
+ c->rlimit[l] = NULL;
+ }
+
+ free(c->working_directory);
+ c->working_directory = NULL;
+ free(c->root_directory);
+ c->root_directory = NULL;
+
+ free(c->tty_path);
+ c->tty_path = NULL;
+
+ free(c->tcpwrap_name);
+ c->tcpwrap_name = NULL;
+
+ free(c->syslog_identifier);
+ c->syslog_identifier = NULL;
+
+ free(c->user);
+ c->user = NULL;
+
+ free(c->group);
+ c->group = NULL;
+
+ strv_free(c->supplementary_groups);
+ c->supplementary_groups = NULL;
+
+ free(c->pam_name);
+ c->pam_name = NULL;
+
+ if (c->capabilities) {
+ cap_free(c->capabilities);
+ c->capabilities = NULL;
+ }
+
+ strv_free(c->read_only_dirs);
+ c->read_only_dirs = NULL;
+
+ strv_free(c->read_write_dirs);
+ c->read_write_dirs = NULL;
+
+ strv_free(c->inaccessible_dirs);
+ c->inaccessible_dirs = NULL;
+
+ if (c->cpuset)
+ CPU_FREE(c->cpuset);
+
+ free(c->utmp_id);
+ c->utmp_id = NULL;
+
+ free(c->syscall_filter);
+ c->syscall_filter = NULL;
+}
+
+void exec_command_done(ExecCommand *c) {
+ assert(c);
+
+ free(c->path);
+ c->path = NULL;
+
+ strv_free(c->argv);
+ c->argv = NULL;
+}
+
+void exec_command_done_array(ExecCommand *c, unsigned n) {
+ unsigned i;
+
+ for (i = 0; i < n; i++)
+ exec_command_done(c+i);
+}
+
+void exec_command_free_list(ExecCommand *c) {
+ ExecCommand *i;
+
+ while ((i = c)) {
+ LIST_REMOVE(ExecCommand, command, c, i);
+ exec_command_done(i);
+ free(i);
+ }
+}
+
+void exec_command_free_array(ExecCommand **c, unsigned n) {
+ unsigned i;
+
+ for (i = 0; i < n; i++) {
+ exec_command_free_list(c[i]);
+ c[i] = NULL;
+ }
+}
+
+int exec_context_load_environment(const ExecContext *c, char ***l) {
+ char **i, **r = NULL;
+
+ assert(c);
+ assert(l);
+
+ STRV_FOREACH(i, c->environment_files) {
+ char *fn;
+ int k;
+ bool ignore = false;
+ char **p;
+
+ fn = *i;
+
+ if (fn[0] == '-') {
+ ignore = true;
+ fn ++;
+ }
+
+ if (!path_is_absolute(fn)) {
+
+ if (ignore)
+ continue;
+
+ strv_free(r);
+ return -EINVAL;
+ }
+
+ if ((k = load_env_file(fn, &p)) < 0) {
+
+ if (ignore)
+ continue;
+
+ strv_free(r);
+ return k;
+ }
+
+ if (r == NULL)
+ r = p;
+ else {
+ char **m;
+
+ m = strv_env_merge(2, r, p);
+ strv_free(r);
+ strv_free(p);
+
+ if (!m)
+ return -ENOMEM;
+
+ r = m;
+ }
+ }
+
+ *l = r;
+
+ return 0;
+}
+
+static void strv_fprintf(FILE *f, char **l) {
+ char **g;
+
+ assert(f);
+
+ STRV_FOREACH(g, l)
+ fprintf(f, " %s", *g);
+}
+
+void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
+ char ** e;
+ unsigned i;
+
+ assert(c);
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+
+ fprintf(f,
+ "%sUMask: %04o\n"
+ "%sWorkingDirectory: %s\n"
+ "%sRootDirectory: %s\n"
+ "%sNonBlocking: %s\n"
+ "%sPrivateTmp: %s\n"
+ "%sControlGroupModify: %s\n"
+ "%sControlGroupPersistent: %s\n"
+ "%sPrivateNetwork: %s\n"
+ "%sIgnoreSIGPIPE: %s\n",
+ prefix, c->umask,
+ prefix, c->working_directory ? c->working_directory : "/",
+ prefix, c->root_directory ? c->root_directory : "/",
+ prefix, yes_no(c->non_blocking),
+ prefix, yes_no(c->private_tmp),
+ prefix, yes_no(c->control_group_modify),
+ prefix, yes_no(c->control_group_persistent),
+ prefix, yes_no(c->private_network),
+ prefix, yes_no(c->ignore_sigpipe));
+
+ STRV_FOREACH(e, c->environment)
+ fprintf(f, "%sEnvironment: %s\n", prefix, *e);
+
+ STRV_FOREACH(e, c->environment_files)
+ fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e);
+
+ if (c->tcpwrap_name)
+ fprintf(f,
+ "%sTCPWrapName: %s\n",
+ prefix, c->tcpwrap_name);
+
+ if (c->nice_set)
+ fprintf(f,
+ "%sNice: %i\n",
+ prefix, c->nice);
+
+ if (c->oom_score_adjust_set)
+ fprintf(f,
+ "%sOOMScoreAdjust: %i\n",
+ prefix, c->oom_score_adjust);
+
+ for (i = 0; i < RLIM_NLIMITS; i++)
+ if (c->rlimit[i])
+ fprintf(f, "%s%s: %llu\n", prefix, rlimit_to_string(i), (unsigned long long) c->rlimit[i]->rlim_max);
+
+ if (c->ioprio_set) {
+ char *class_str;
+ int r;
+
+ r = ioprio_class_to_string_alloc(IOPRIO_PRIO_CLASS(c->ioprio), &class_str);
+ if (r < 0)
+ class_str = NULL;
+ fprintf(f,
+ "%sIOSchedulingClass: %s\n"
+ "%sIOPriority: %i\n",
+ prefix, strna(class_str),
+ prefix, (int) IOPRIO_PRIO_DATA(c->ioprio));
+ free(class_str);
+ }
+
+ if (c->cpu_sched_set) {
+ char *policy_str;
+ int r;
+
+ r = sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str);
+ if (r < 0)
+ policy_str = NULL;
+ fprintf(f,
+ "%sCPUSchedulingPolicy: %s\n"
+ "%sCPUSchedulingPriority: %i\n"
+ "%sCPUSchedulingResetOnFork: %s\n",
+ prefix, strna(policy_str),
+ prefix, c->cpu_sched_priority,
+ prefix, yes_no(c->cpu_sched_reset_on_fork));
+ free(policy_str);
+ }
+
+ if (c->cpuset) {
+ fprintf(f, "%sCPUAffinity:", prefix);
+ for (i = 0; i < c->cpuset_ncpus; i++)
+ if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset))
+ fprintf(f, " %i", i);
+ fputs("\n", f);
+ }
+
+ if (c->timer_slack_nsec != (nsec_t) -1)
+ fprintf(f, "%sTimerSlackNSec: %lu\n", prefix, (unsigned long)c->timer_slack_nsec);
+
+ fprintf(f,
+ "%sStandardInput: %s\n"
+ "%sStandardOutput: %s\n"
+ "%sStandardError: %s\n",
+ prefix, exec_input_to_string(c->std_input),
+ prefix, exec_output_to_string(c->std_output),
+ prefix, exec_output_to_string(c->std_error));
+
+ if (c->tty_path)
+ fprintf(f,
+ "%sTTYPath: %s\n"
+ "%sTTYReset: %s\n"
+ "%sTTYVHangup: %s\n"
+ "%sTTYVTDisallocate: %s\n",
+ prefix, c->tty_path,
+ prefix, yes_no(c->tty_reset),
+ prefix, yes_no(c->tty_vhangup),
+ prefix, yes_no(c->tty_vt_disallocate));
+
+ if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KMSG || c->std_output == EXEC_OUTPUT_JOURNAL ||
+ c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE ||
+ c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KMSG || c->std_error == EXEC_OUTPUT_JOURNAL ||
+ c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) {
+ char *fac_str, *lvl_str;
+ int r;
+
+ r = log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str);
+ if (r < 0)
+ fac_str = NULL;
+
+ r = log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str);
+ if (r < 0)
+ lvl_str = NULL;
+
+ fprintf(f,
+ "%sSyslogFacility: %s\n"
+ "%sSyslogLevel: %s\n",
+ prefix, strna(fac_str),
+ prefix, strna(lvl_str));
+ free(lvl_str);
+ free(fac_str);
+ }
+
+ if (c->capabilities) {
+ char *t;
+ if ((t = cap_to_text(c->capabilities, NULL))) {
+ fprintf(f, "%sCapabilities: %s\n",
+ prefix, t);
+ cap_free(t);
+ }
+ }
+
+ if (c->secure_bits)
+ fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n",
+ prefix,
+ (c->secure_bits & SECURE_KEEP_CAPS) ? " keep-caps" : "",
+ (c->secure_bits & SECURE_KEEP_CAPS_LOCKED) ? " keep-caps-locked" : "",
+ (c->secure_bits & SECURE_NO_SETUID_FIXUP) ? " no-setuid-fixup" : "",
+ (c->secure_bits & SECURE_NO_SETUID_FIXUP_LOCKED) ? " no-setuid-fixup-locked" : "",
+ (c->secure_bits & SECURE_NOROOT) ? " noroot" : "",
+ (c->secure_bits & SECURE_NOROOT_LOCKED) ? "noroot-locked" : "");
+
+ if (c->capability_bounding_set_drop) {
+ unsigned long l;
+ fprintf(f, "%sCapabilityBoundingSet:", prefix);
+
+ for (l = 0; l <= cap_last_cap(); l++)
+ if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) {
+ char *t;
+
+ if ((t = cap_to_name(l))) {
+ fprintf(f, " %s", t);
+ cap_free(t);
+ }
+ }
+
+ fputs("\n", f);
+ }
+
+ if (c->user)
+ fprintf(f, "%sUser: %s\n", prefix, c->user);
+ if (c->group)
+ fprintf(f, "%sGroup: %s\n", prefix, c->group);
+
+ if (strv_length(c->supplementary_groups) > 0) {
+ fprintf(f, "%sSupplementaryGroups:", prefix);
+ strv_fprintf(f, c->supplementary_groups);
+ fputs("\n", f);
+ }
+
+ if (c->pam_name)
+ fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name);
+
+ if (strv_length(c->read_write_dirs) > 0) {
+ fprintf(f, "%sReadWriteDirs:", prefix);
+ strv_fprintf(f, c->read_write_dirs);
+ fputs("\n", f);
+ }
+
+ if (strv_length(c->read_only_dirs) > 0) {
+ fprintf(f, "%sReadOnlyDirs:", prefix);
+ strv_fprintf(f, c->read_only_dirs);
+ fputs("\n", f);
+ }
+
+ if (strv_length(c->inaccessible_dirs) > 0) {
+ fprintf(f, "%sInaccessibleDirs:", prefix);
+ strv_fprintf(f, c->inaccessible_dirs);
+ fputs("\n", f);
+ }
+
+ if (c->utmp_id)
+ fprintf(f,
+ "%sUtmpIdentifier: %s\n",
+ prefix, c->utmp_id);
+}
+
+void exec_status_start(ExecStatus *s, pid_t pid) {
+ assert(s);
+
+ zero(*s);
+ s->pid = pid;
+ dual_timestamp_get(&s->start_timestamp);
+}
+
+void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) {
+ assert(s);
+
+ if (s->pid && s->pid != pid)
+ zero(*s);
+
+ s->pid = pid;
+ dual_timestamp_get(&s->exit_timestamp);
+
+ s->code = code;
+ s->status = status;
+
+ if (context) {
+ if (context->utmp_id)
+ utmp_put_dead_process(context->utmp_id, pid, code, status);
+
+ exec_context_tty_reset(context);
+ }
+}
+
+void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) {
+ char buf[FORMAT_TIMESTAMP_MAX];
+
+ assert(s);
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+
+ if (s->pid <= 0)
+ return;
+
+ fprintf(f,
+ "%sPID: %lu\n",
+ prefix, (unsigned long) s->pid);
+
+ if (s->start_timestamp.realtime > 0)
+ fprintf(f,
+ "%sStart Timestamp: %s\n",
+ prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime));
+
+ if (s->exit_timestamp.realtime > 0)
+ fprintf(f,
+ "%sExit Timestamp: %s\n"
+ "%sExit Code: %s\n"
+ "%sExit Status: %i\n",
+ prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime),
+ prefix, sigchld_code_to_string(s->code),
+ prefix, s->status);
+}
+
+char *exec_command_line(char **argv) {
+ size_t k;
+ char *n, *p, **a;
+ bool first = true;
+
+ assert(argv);
+
+ k = 1;
+ STRV_FOREACH(a, argv)
+ k += strlen(*a)+3;
+
+ if (!(n = new(char, k)))
+ return NULL;
+
+ p = n;
+ STRV_FOREACH(a, argv) {
+
+ if (!first)
+ *(p++) = ' ';
+ else
+ first = false;
+
+ if (strpbrk(*a, WHITESPACE)) {
+ *(p++) = '\'';
+ p = stpcpy(p, *a);
+ *(p++) = '\'';
+ } else
+ p = stpcpy(p, *a);
+
+ }
+
+ *p = 0;
+
+ /* FIXME: this doesn't really handle arguments that have
+ * spaces and ticks in them */
+
+ return n;
+}
+
+void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) {
+ char *p2;
+ const char *prefix2;
+
+ char *cmd;
+
+ assert(c);
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+ p2 = strappend(prefix, "\t");
+ prefix2 = p2 ? p2 : prefix;
+
+ cmd = exec_command_line(c->argv);
+
+ fprintf(f,
+ "%sCommand Line: %s\n",
+ prefix, cmd ? cmd : strerror(ENOMEM));
+
+ free(cmd);
+
+ exec_status_dump(&c->exec_status, f, prefix2);
+
+ free(p2);
+}
+
+void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) {
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+
+ LIST_FOREACH(command, c, c)
+ exec_command_dump(c, f, prefix);
+}
+
+void exec_command_append_list(ExecCommand **l, ExecCommand *e) {
+ ExecCommand *end;
+
+ assert(l);
+ assert(e);
+
+ if (*l) {
+ /* 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
+ *l = e;
+}
+
+int exec_command_set(ExecCommand *c, const char *path, ...) {
+ va_list ap;
+ char **l, *p;
+
+ assert(c);
+ assert(path);
+
+ va_start(ap, path);
+ l = strv_new_ap(path, ap);
+ va_end(ap);
+
+ if (!l)
+ return -ENOMEM;
+
+ if (!(p = strdup(path))) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ free(c->path);
+ c->path = p;
+
+ strv_free(c->argv);
+ c->argv = l;
+
+ return 0;
+}
+
+static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
+ [EXEC_INPUT_NULL] = "null",
+ [EXEC_INPUT_TTY] = "tty",
+ [EXEC_INPUT_TTY_FORCE] = "tty-force",
+ [EXEC_INPUT_TTY_FAIL] = "tty-fail",
+ [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_JOURNAL] = "journal",
+ [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console",
+ [EXEC_OUTPUT_SOCKET] = "socket"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
diff --git a/src/core/execute.h b/src/core/execute.h
new file mode 100644
index 0000000000..2bcd2e1e6c
--- /dev/null
+++ b/src/core/execute.h
@@ -0,0 +1,209 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct ExecStatus ExecStatus;
+typedef struct ExecCommand ExecCommand;
+typedef struct ExecContext ExecContext;
+
+#include <linux/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/capability.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sched.h>
+
+struct CGroupBonding;
+struct CGroupAttribute;
+
+#include "list.h"
+#include "util.h"
+
+typedef enum ExecInput {
+ EXEC_INPUT_NULL,
+ EXEC_INPUT_TTY,
+ EXEC_INPUT_TTY_FORCE,
+ EXEC_INPUT_TTY_FAIL,
+ EXEC_INPUT_SOCKET,
+ _EXEC_INPUT_MAX,
+ _EXEC_INPUT_INVALID = -1
+} ExecInput;
+
+typedef enum ExecOutput {
+ EXEC_OUTPUT_INHERIT,
+ EXEC_OUTPUT_NULL,
+ EXEC_OUTPUT_TTY,
+ EXEC_OUTPUT_SYSLOG,
+ EXEC_OUTPUT_SYSLOG_AND_CONSOLE,
+ EXEC_OUTPUT_KMSG,
+ EXEC_OUTPUT_KMSG_AND_CONSOLE,
+ EXEC_OUTPUT_JOURNAL,
+ EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
+ EXEC_OUTPUT_SOCKET,
+ _EXEC_OUTPUT_MAX,
+ _EXEC_OUTPUT_INVALID = -1
+} ExecOutput;
+
+struct ExecStatus {
+ dual_timestamp start_timestamp;
+ dual_timestamp exit_timestamp;
+ pid_t pid;
+ int code; /* as in siginfo_t::si_code */
+ int status; /* as in sigingo_t::si_status */
+};
+
+struct ExecCommand {
+ char *path;
+ char **argv;
+ ExecStatus exec_status;
+ LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */
+ bool ignore;
+};
+
+struct ExecContext {
+ char **environment;
+ char **environment_files;
+
+ struct rlimit *rlimit[RLIMIT_NLIMITS];
+ char *working_directory, *root_directory;
+
+ mode_t umask;
+ int oom_score_adjust;
+ int nice;
+ int ioprio;
+ int cpu_sched_policy;
+ int cpu_sched_priority;
+
+ cpu_set_t *cpuset;
+ unsigned cpuset_ncpus;
+
+ ExecInput std_input;
+ ExecOutput std_output;
+ ExecOutput std_error;
+
+ nsec_t timer_slack_nsec;
+
+ char *tcpwrap_name;
+
+ char *tty_path;
+
+ bool tty_reset;
+ bool tty_vhangup;
+ bool tty_vt_disallocate;
+
+ bool ignore_sigpipe;
+
+ /* Since resolving these names might might involve socket
+ * connections and we don't want to deadlock ourselves these
+ * names are resolved on execution only and in the child
+ * process. */
+ char *user;
+ char *group;
+ char **supplementary_groups;
+
+ char *pam_name;
+
+ char *utmp_id;
+
+ char **read_write_dirs, **read_only_dirs, **inaccessible_dirs;
+ unsigned long mount_flags;
+
+ uint64_t capability_bounding_set_drop;
+
+ cap_t capabilities;
+ int secure_bits;
+
+ int syslog_priority;
+ char *syslog_identifier;
+ bool syslog_level_prefix;
+
+ bool cpu_sched_reset_on_fork;
+ bool non_blocking;
+ bool private_tmp;
+ bool private_network;
+
+ bool no_new_privileges;
+
+ bool control_group_modify;
+ int control_group_persistent;
+
+ /* This is not exposed to the user but available
+ * internally. We need it to make sure that whenever we spawn
+ * /bin/mount it is run in the same process group as us so
+ * that the autofs logic detects that it belongs to us and we
+ * don't enter a trigger loop. */
+ bool same_pgrp;
+
+ uint32_t *syscall_filter;
+
+ bool oom_score_adjust_set:1;
+ bool nice_set:1;
+ bool ioprio_set:1;
+ bool cpu_sched_set:1;
+};
+
+int exec_spawn(ExecCommand *command,
+ char **argv,
+ const ExecContext *context,
+ int fds[], unsigned n_fds,
+ char **environment,
+ bool apply_permissions,
+ bool apply_chroot,
+ bool apply_tty_stdin,
+ bool confirm_spawn,
+ struct CGroupBonding *cgroup_bondings,
+ struct CGroupAttribute *cgroup_attributes,
+ const char *cgroup_suffix,
+ const char *unit_id,
+ int pipe_fd[2],
+ pid_t *ret);
+
+void exec_command_done(ExecCommand *c);
+void exec_command_done_array(ExecCommand *c, unsigned n);
+
+void exec_command_free_list(ExecCommand *c);
+void exec_command_free_array(ExecCommand **c, unsigned n);
+
+char *exec_command_line(char **argv);
+
+void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix);
+void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix);
+void exec_command_append_list(ExecCommand **l, ExecCommand *e);
+int exec_command_set(ExecCommand *c, const char *path, ...);
+
+void exec_context_init(ExecContext *c);
+void exec_context_done(ExecContext *c);
+void exec_context_dump(ExecContext *c, FILE* f, const char *prefix);
+void exec_context_tty_reset(const ExecContext *context);
+
+int exec_context_load_environment(const ExecContext *c, char ***l);
+
+void exec_status_start(ExecStatus *s, pid_t pid);
+void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status);
+void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix);
+
+const char* exec_output_to_string(ExecOutput i);
+ExecOutput exec_output_from_string(const char *s);
+
+const char* exec_input_to_string(ExecInput i);
+ExecInput exec_input_from_string(const char *s);
diff --git a/src/core/fdset.c b/src/core/fdset.c
new file mode 100644
index 0000000000..fe918cd8cc
--- /dev/null
+++ b/src/core/fdset.c
@@ -0,0 +1,167 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "set.h"
+#include "util.h"
+#include "macro.h"
+#include "fdset.h"
+
+#define MAKE_SET(s) ((Set*) s)
+#define MAKE_FDSET(s) ((FDSet*) s)
+
+/* Make sure we can distuingish fd 0 and NULL */
+#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1)
+#define PTR_TO_FD(p) (PTR_TO_INT(p)-1)
+
+FDSet *fdset_new(void) {
+ return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func));
+}
+
+void fdset_free(FDSet *s) {
+ void *p;
+
+ while ((p = set_steal_first(MAKE_SET(s)))) {
+ /* Valgrind's fd might have ended up in this set here,
+ * due to fdset_new_fill(). We'll ignore all failures
+ * here, so that the EBADFD that valgrind will return
+ * us on close() doesn't influence us */
+
+ /* When reloading duplicates of the private bus
+ * connection fds and suchlike are closed here, which
+ * has no effect at all, since they are only
+ * duplicates. So don't be surprised about these log
+ * messages. */
+
+ log_debug("Closing left-over fd %i", PTR_TO_FD(p));
+ close_nointr(PTR_TO_FD(p));
+ }
+
+ set_free(MAKE_SET(s));
+}
+
+int fdset_put(FDSet *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ return set_put(MAKE_SET(s), FD_TO_PTR(fd));
+}
+
+int fdset_put_dup(FDSet *s, int fd) {
+ int copy, r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ if ((copy = fcntl(fd, F_DUPFD_CLOEXEC, 3)) < 0)
+ return -errno;
+
+ if ((r = fdset_put(s, copy)) < 0) {
+ close_nointr_nofail(copy);
+ return r;
+ }
+
+ return copy;
+}
+
+bool fdset_contains(FDSet *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ return !!set_get(MAKE_SET(s), FD_TO_PTR(fd));
+}
+
+int fdset_remove(FDSet *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT;
+}
+
+int fdset_new_fill(FDSet **_s) {
+ DIR *d;
+ struct dirent *de;
+ int r = 0;
+ FDSet *s;
+
+ assert(_s);
+
+ /* Creates an fdsets and fills in all currently open file
+ * descriptors. */
+
+ if (!(d = opendir("/proc/self/fd")))
+ return -errno;
+
+ if (!(s = fdset_new())) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ while ((de = readdir(d))) {
+ int fd = -1;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if ((r = safe_atoi(de->d_name, &fd)) < 0)
+ goto finish;
+
+ if (fd < 3)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ if ((r = fdset_put(s, fd)) < 0)
+ goto finish;
+ }
+
+ r = 0;
+ *_s = s;
+ s = NULL;
+
+finish:
+ closedir(d);
+
+ /* We won't close the fds here! */
+ if (s)
+ set_free(MAKE_SET(s));
+
+ return r;
+}
+
+int fdset_cloexec(FDSet *fds, bool b) {
+ Iterator i;
+ void *p;
+ int r;
+
+ assert(fds);
+
+ SET_FOREACH(p, MAKE_SET(fds), i)
+ if ((r = fd_cloexec(PTR_TO_FD(p), b)) < 0)
+ return r;
+
+ return 0;
+}
diff --git a/src/core/fdset.h b/src/core/fdset.h
new file mode 100644
index 0000000000..c3e408c8ad
--- /dev/null
+++ b/src/core/fdset.h
@@ -0,0 +1,37 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct FDSet FDSet;
+
+FDSet* fdset_new(void);
+void fdset_free(FDSet *s);
+
+int fdset_put(FDSet *s, int fd);
+int fdset_put_dup(FDSet *s, int fd);
+
+bool fdset_contains(FDSet *s, int fd);
+int fdset_remove(FDSet *s, int fd);
+
+int fdset_new_fill(FDSet **_s);
+
+int fdset_cloexec(FDSet *fds, bool b);
diff --git a/src/core/hostname-setup.c b/src/core/hostname-setup.c
new file mode 100644
index 0000000000..dbd2227e21
--- /dev/null
+++ b/src/core/hostname-setup.c
@@ -0,0 +1,182 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "hostname-setup.h"
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+
+#if defined(TARGET_ALTLINUX) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+#define FILENAME "/etc/sysconfig/network"
+#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE)
+#define FILENAME "/etc/HOSTNAME"
+#elif defined(TARGET_GENTOO)
+#define FILENAME "/etc/conf.d/hostname"
+#endif
+
+static int read_and_strip_hostname(const char *path, char **hn) {
+ char *s;
+ int r;
+
+ assert(path);
+ assert(hn);
+
+ r = read_one_line_file(path, &s);
+ if (r < 0)
+ return r;
+
+ hostname_cleanup(s);
+
+ if (isempty(s)) {
+ free(s);
+ return -ENOENT;
+ }
+
+ *hn = s;
+
+ return 0;
+}
+
+static int read_distro_hostname(char **hn) {
+
+#if defined(TARGET_GENTOO) || defined(TARGET_ALTLINUX) || defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+ int r;
+ _cleanup_fclose_ FILE *f = NULL;
+
+ assert(hn);
+
+ f = fopen(FILENAME, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ char line[LINE_MAX];
+ char *s, *k;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ break;
+
+ r = -errno;
+ goto finish;
+ }
+
+ s = strstrip(line);
+
+ if (!startswith_no_case(s, "HOSTNAME="))
+ continue;
+
+ k = strdup(s+9);
+ if (!k) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ hostname_cleanup(k);
+
+ if (isempty(k)) {
+ free(k);
+ r = -ENOENT;
+ goto finish;
+ }
+
+ *hn = k;
+ r = 0;
+ goto finish;
+ }
+
+ r = -ENOENT;
+
+finish:
+ return r;
+
+#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE)
+ return read_and_strip_hostname(FILENAME, hn);
+#else
+ return -ENOENT;
+#endif
+}
+
+static int read_hostname(char **hn) {
+ int r;
+
+ assert(hn);
+
+ /* First, try to load the generic hostname configuration file,
+ * that we support on all distributions */
+
+ r = read_and_strip_hostname("/etc/hostname", hn);
+ if (r < 0) {
+ if (r == -ENOENT)
+ return read_distro_hostname(hn);
+
+ return r;
+ }
+
+ return 0;
+}
+
+int hostname_setup(void) {
+ int r;
+ char *b = NULL;
+ const char *hn = NULL;
+ bool enoent = false;
+
+ r = read_hostname(&b);
+ if (r < 0) {
+ hn = NULL;
+
+ if (r == -ENOENT)
+ enoent = true;
+ else
+ log_warning("Failed to read configured hostname: %s", strerror(-r));
+ } else
+ hn = b;
+
+ if (isempty(hn)) {
+ /* Don't override the hostname if it is already set
+ * and not explicitly configured */
+ if (hostname_is_set())
+ goto finish;
+
+ if (enoent)
+ log_info("No hostname configured.");
+
+ hn = "localhost";
+ }
+
+ if (sethostname(hn, strlen(hn)) < 0) {
+ log_warning("Failed to set hostname to <%s>: %m", hn);
+ r = -errno;
+ } else
+ log_info("Set hostname to <%s>.", hn);
+
+finish:
+ free(b);
+
+ return r;
+}
diff --git a/src/core/hostname-setup.h b/src/core/hostname-setup.h
new file mode 100644
index 0000000000..8dc3a9e1d8
--- /dev/null
+++ b/src/core/hostname-setup.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int hostname_setup(void);
diff --git a/src/core/ima-setup.c b/src/core/ima-setup.c
new file mode 100644
index 0000000000..e8cc1ba8b6
--- /dev/null
+++ b/src/core/ima-setup.c
@@ -0,0 +1,115 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy
+ TORSEC group -- http://security.polito.it
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "ima-setup.h"
+#include "mount-setup.h"
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+#include "label.h"
+
+#define IMA_SECFS_DIR "/sys/kernel/security/ima"
+#define IMA_SECFS_POLICY IMA_SECFS_DIR "/policy"
+#define IMA_POLICY_PATH "/etc/ima/ima-policy"
+
+int ima_setup(void) {
+
+#ifdef HAVE_IMA
+ struct stat st;
+ ssize_t policy_size = 0, written = 0;
+ char *policy;
+ int policyfd = -1, imafd = -1;
+ int result = 0;
+
+#ifndef HAVE_SELINUX
+ /* Mount the securityfs filesystem */
+ mount_setup_early();
+#endif
+
+ if (stat(IMA_POLICY_PATH, &st) < 0)
+ return 0;
+
+ policy_size = st.st_size;
+ if (stat(IMA_SECFS_DIR, &st) < 0) {
+ log_debug("IMA support is disabled in the kernel, ignoring.");
+ return 0;
+ }
+
+ if (stat(IMA_SECFS_POLICY, &st) < 0) {
+ log_error("Another IMA custom policy has already been loaded, "
+ "ignoring.");
+ return 0;
+ }
+
+ policyfd = open(IMA_POLICY_PATH, O_RDONLY|O_CLOEXEC);
+ if (policyfd < 0) {
+ log_error("Failed to open the IMA custom policy file %s (%m), "
+ "ignoring.", IMA_POLICY_PATH);
+ return 0;
+ }
+
+ imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC);
+ if (imafd < 0) {
+ log_error("Failed to open the IMA kernel interface %s (%m), "
+ "ignoring.", IMA_SECFS_POLICY);
+ goto out;
+ }
+
+ policy = mmap(NULL, policy_size, PROT_READ, MAP_PRIVATE, policyfd, 0);
+ if (policy == MAP_FAILED) {
+ log_error("mmap() failed (%m), freezing");
+ result = -errno;
+ goto out;
+ }
+
+ written = loop_write(imafd, policy, (size_t)policy_size, false);
+ if (written != policy_size) {
+ log_error("Failed to load the IMA custom policy file %s (%m), "
+ "ignoring.", IMA_POLICY_PATH);
+ goto out_mmap;
+ }
+
+ log_info("Successfully loaded the IMA custom policy %s.",
+ IMA_POLICY_PATH);
+out_mmap:
+ munmap(policy, policy_size);
+out:
+ if (policyfd >= 0)
+ close_nointr_nofail(policyfd);
+ if (imafd >= 0)
+ close_nointr_nofail(imafd);
+ if (result)
+ return result;
+#endif /* HAVE_IMA */
+
+ return 0;
+}
diff --git a/src/core/ima-setup.h b/src/core/ima-setup.h
new file mode 100644
index 0000000000..14b56d1fc2
--- /dev/null
+++ b/src/core/ima-setup.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy
+ TORSEC group -- http://security.polito.it
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int ima_setup(void);
diff --git a/src/core/initreq.h b/src/core/initreq.h
new file mode 100644
index 0000000000..859042ce42
--- /dev/null
+++ b/src/core/initreq.h
@@ -0,0 +1,77 @@
+/*
+ * initreq.h Interface to talk to init through /dev/initctl.
+ *
+ * Copyright (C) 1995-2004 Miquel van Smoorenburg
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS
+ *
+ */
+#ifndef _INITREQ_H
+#define _INITREQ_H
+
+#include <sys/param.h>
+
+#if defined(__FreeBSD_kernel__)
+# define INIT_FIFO "/etc/.initctl"
+#else
+# define INIT_FIFO "/dev/initctl"
+#endif
+
+#define INIT_MAGIC 0x03091969
+#define INIT_CMD_START 0
+#define INIT_CMD_RUNLVL 1
+#define INIT_CMD_POWERFAIL 2
+#define INIT_CMD_POWERFAILNOW 3
+#define INIT_CMD_POWEROK 4
+#define INIT_CMD_BSD 5
+#define INIT_CMD_SETENV 6
+#define INIT_CMD_UNSETENV 7
+
+#define INIT_CMD_CHANGECONS 12345
+
+#ifdef MAXHOSTNAMELEN
+# define INITRQ_HLEN MAXHOSTNAMELEN
+#else
+# define INITRQ_HLEN 64
+#endif
+
+/*
+ * This is what BSD 4.4 uses when talking to init.
+ * Linux doesn't use this right now.
+ */
+struct init_request_bsd {
+ char gen_id[8]; /* Beats me.. telnetd uses "fe" */
+ char tty_id[16]; /* Tty name minus /dev/tty */
+ char host[INITRQ_HLEN]; /* Hostname */
+ char term_type[16]; /* Terminal type */
+ int signal; /* Signal to send */
+ int pid; /* Process to send to */
+ char exec_name[128]; /* Program to execute */
+ char reserved[128]; /* For future expansion. */
+};
+
+
+/*
+ * Because of legacy interfaces, "runlevel" and "sleeptime"
+ * aren't in a separate struct in the union.
+ *
+ * The weird sizes are because init expects the whole
+ * struct to be 384 bytes.
+ */
+struct init_request {
+ int magic; /* Magic number */
+ int cmd; /* What kind of request */
+ int runlevel; /* Runlevel to change to */
+ int sleeptime; /* Time between TERM and KILL */
+ union {
+ struct init_request_bsd bsd;
+ char data[368];
+ } i;
+};
+
+#endif
diff --git a/src/core/job.c b/src/core/job.c
new file mode 100644
index 0000000000..f08b8cbc7d
--- /dev/null
+++ b/src/core/job.c
@@ -0,0 +1,1086 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <sys/timerfd.h>
+#include <sys/epoll.h>
+
+#include "systemd/sd-id128.h"
+#include "systemd/sd-messages.h"
+#include "set.h"
+#include "unit.h"
+#include "macro.h"
+#include "strv.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "dbus-job.h"
+
+JobBusClient* job_bus_client_new(DBusConnection *connection, const char *name) {
+ JobBusClient *cl;
+ size_t name_len;
+
+ name_len = strlen(name);
+ cl = malloc0(sizeof(JobBusClient) + name_len + 1);
+ if (!cl)
+ return NULL;
+
+ cl->bus = connection;
+ memcpy(cl->name, name, name_len + 1);
+ return cl;
+}
+
+Job* job_new_raw(Unit *unit) {
+ Job *j;
+
+ /* used for deserialization */
+
+ assert(unit);
+
+ j = new0(Job, 1);
+ if (!j)
+ return NULL;
+
+ j->manager = unit->manager;
+ j->unit = unit;
+ j->type = _JOB_TYPE_INVALID;
+ j->timer_watch.type = WATCH_INVALID;
+
+ return j;
+}
+
+Job* job_new(Unit *unit, JobType type) {
+ Job *j;
+
+ assert(type < _JOB_TYPE_MAX);
+
+ j = job_new_raw(unit);
+ if (!j)
+ return NULL;
+
+ j->id = j->manager->current_job_id++;
+ j->type = type;
+
+ /* We don't link it here, that's what job_dependency() is for */
+
+ return j;
+}
+
+void job_free(Job *j) {
+ JobBusClient *cl;
+
+ assert(j);
+ assert(!j->installed);
+ assert(!j->transaction_prev);
+ assert(!j->transaction_next);
+ assert(!j->subject_list);
+ assert(!j->object_list);
+
+ if (j->in_run_queue)
+ LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
+
+ if (j->in_dbus_queue)
+ LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
+
+ if (j->timer_watch.type != WATCH_INVALID) {
+ assert(j->timer_watch.type == WATCH_JOB_TIMER);
+ assert(j->timer_watch.data.job == j);
+ assert(j->timer_watch.fd >= 0);
+
+ assert_se(epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_DEL, j->timer_watch.fd, NULL) >= 0);
+ close_nointr_nofail(j->timer_watch.fd);
+ }
+
+ while ((cl = j->bus_client_list)) {
+ LIST_REMOVE(JobBusClient, client, j->bus_client_list, cl);
+ free(cl);
+ }
+ free(j);
+}
+
+void job_uninstall(Job *j) {
+ Job **pj;
+
+ assert(j->installed);
+
+ pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job;
+ assert(*pj == j);
+
+ /* Detach from next 'bigger' objects */
+
+ /* daemon-reload should be transparent to job observers */
+ if (j->manager->n_reloading <= 0)
+ bus_job_send_removed_signal(j);
+
+ *pj = NULL;
+
+ unit_add_to_gc_queue(j->unit);
+
+ hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
+ j->installed = false;
+}
+
+static bool job_type_allows_late_merge(JobType t) {
+ /* Tells whether it is OK to merge a job of type 't' with an already
+ * running job.
+ * Reloads cannot be merged this way. Think of the sequence:
+ * 1. Reload of a daemon is in progress; the daemon has already loaded
+ * its config file, but hasn't completed the reload operation yet.
+ * 2. Edit foo's config file.
+ * 3. Trigger another reload to have the daemon use the new config.
+ * Should the second reload job be merged into the first one, the daemon
+ * would not know about the new config.
+ * JOB_RESTART jobs on the other hand can be merged, because they get
+ * patched into JOB_START after stopping the unit. So if we see a
+ * JOB_RESTART running, it means the unit hasn't stopped yet and at
+ * this time the merge is still allowed. */
+ return t != JOB_RELOAD;
+}
+
+static void job_merge_into_installed(Job *j, Job *other) {
+ assert(j->installed);
+ assert(j->unit == other->unit);
+
+ if (j->type != JOB_NOP)
+ job_type_merge_and_collapse(&j->type, other->type, j->unit);
+ else
+ assert(other->type == JOB_NOP);
+
+ j->override = j->override || other->override;
+}
+
+Job* job_install(Job *j) {
+ Job **pj;
+ Job *uj;
+
+ assert(!j->installed);
+ assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION);
+
+ pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job;
+ uj = *pj;
+
+ if (uj) {
+ if (j->type != JOB_NOP && job_type_is_conflicting(uj->type, j->type))
+ job_finish_and_invalidate(uj, JOB_CANCELED, false);
+ else {
+ /* not conflicting, i.e. mergeable */
+
+ if (j->type == JOB_NOP || uj->state == JOB_WAITING ||
+ (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) {
+ job_merge_into_installed(uj, j);
+ log_debug("Merged into installed job %s/%s as %u",
+ uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id);
+ return uj;
+ } else {
+ /* already running and not safe to merge into */
+ /* Patch uj to become a merged job and re-run it. */
+ /* XXX It should be safer to queue j to run after uj finishes, but it is
+ * not currently possible to have more than one installed job per unit. */
+ job_merge_into_installed(uj, j);
+ log_debug("Merged into running job, re-running: %s/%s as %u",
+ uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id);
+ uj->state = JOB_WAITING;
+ return uj;
+ }
+ }
+ }
+
+ /* Install the job */
+ *pj = j;
+ j->installed = true;
+ j->manager->n_installed_jobs ++;
+ log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
+ return j;
+}
+
+int job_install_deserialized(Job *j) {
+ Job **pj;
+
+ assert(!j->installed);
+
+ if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) {
+ log_debug("Invalid job type %s in deserialization.", strna(job_type_to_string(j->type)));
+ return -EINVAL;
+ }
+
+ pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job;
+
+ if (*pj) {
+ log_debug("Unit %s already has a job installed. Not installing deserialized job.", j->unit->id);
+ return -EEXIST;
+ }
+ *pj = j;
+ j->installed = true;
+ log_debug("Reinstalled deserialized job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
+ return 0;
+}
+
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
+ JobDependency *l;
+
+ assert(object);
+
+ /* Adds a new job link, which encodes that the 'subject' job
+ * needs the 'object' job in some way. If 'subject' is NULL
+ * this means the 'anchor' job (i.e. the one the user
+ * explicitly asked for) is the requester. */
+
+ if (!(l = new0(JobDependency, 1)))
+ return NULL;
+
+ l->subject = subject;
+ l->object = object;
+ l->matters = matters;
+ l->conflicts = conflicts;
+
+ if (subject)
+ LIST_PREPEND(JobDependency, subject, subject->subject_list, l);
+
+ LIST_PREPEND(JobDependency, object, object->object_list, l);
+
+ return l;
+}
+
+void job_dependency_free(JobDependency *l) {
+ assert(l);
+
+ if (l->subject)
+ LIST_REMOVE(JobDependency, subject, l->subject->subject_list, l);
+
+ LIST_REMOVE(JobDependency, object, l->object->object_list, l);
+
+ free(l);
+}
+
+void job_dump(Job *j, FILE*f, const char *prefix) {
+ assert(j);
+ assert(f);
+
+ if (!prefix)
+ prefix = "";
+
+ fprintf(f,
+ "%s-> Job %u:\n"
+ "%s\tAction: %s -> %s\n"
+ "%s\tState: %s\n"
+ "%s\tForced: %s\n",
+ prefix, j->id,
+ prefix, j->unit->id, job_type_to_string(j->type),
+ prefix, job_state_to_string(j->state),
+ prefix, yes_no(j->override));
+}
+
+/*
+ * Merging is commutative, so imagine the matrix as symmetric. We store only
+ * its lower triangle to avoid duplication. We don't store the main diagonal,
+ * because A merged with A is simply A.
+ *
+ * If the resulting type is collapsed immediately afterwards (to get rid of
+ * the JOB_RELOAD_OR_START, which lies outside the lookup function's domain),
+ * the following properties hold:
+ *
+ * Merging is associative! A merged with B merged with C is the same as
+ * A merged with C merged with B.
+ *
+ * Mergeability is transitive! If A can be merged with B and B with C then
+ * A also with C.
+ *
+ * Also, if A merged with B cannot be merged with C, then either A or B cannot
+ * be merged with C either.
+ */
+static const JobType job_merging_table[] = {
+/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */
+/*********************************************************************************/
+/*JOB_START */
+/*JOB_VERIFY_ACTIVE */ JOB_START,
+/*JOB_STOP */ -1, -1,
+/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1,
+/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART,
+};
+
+JobType job_type_lookup_merge(JobType a, JobType b) {
+ assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2);
+ assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING);
+ assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING);
+
+ if (a == b)
+ return a;
+
+ if (a < b) {
+ JobType tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ return job_merging_table[(a - 1) * a / 2 + b];
+}
+
+bool job_type_is_redundant(JobType a, UnitActiveState b) {
+ switch (a) {
+
+ case JOB_START:
+ return
+ b == UNIT_ACTIVE ||
+ b == UNIT_RELOADING;
+
+ case JOB_STOP:
+ return
+ b == UNIT_INACTIVE ||
+ b == UNIT_FAILED;
+
+ case JOB_VERIFY_ACTIVE:
+ return
+ b == UNIT_ACTIVE ||
+ b == UNIT_RELOADING;
+
+ case JOB_RELOAD:
+ return
+ b == UNIT_RELOADING;
+
+ case JOB_RESTART:
+ return
+ b == UNIT_ACTIVATING;
+
+ default:
+ assert_not_reached("Invalid job type");
+ }
+}
+
+void job_type_collapse(JobType *t, Unit *u) {
+ UnitActiveState s;
+
+ switch (*t) {
+
+ case JOB_TRY_RESTART:
+ s = unit_active_state(u);
+ if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s))
+ *t = JOB_NOP;
+ else
+ *t = JOB_RESTART;
+ break;
+
+ case JOB_RELOAD_OR_START:
+ s = unit_active_state(u);
+ if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s))
+ *t = JOB_START;
+ else
+ *t = JOB_RELOAD;
+ break;
+
+ default:
+ ;
+ }
+}
+
+int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) {
+ JobType t = job_type_lookup_merge(*a, b);
+ if (t < 0)
+ return -EEXIST;
+ *a = t;
+ job_type_collapse(a, u);
+ return 0;
+}
+
+bool job_is_runnable(Job *j) {
+ Iterator i;
+ Unit *other;
+
+ assert(j);
+ assert(j->installed);
+
+ /* Checks whether there is any job running for the units this
+ * job needs to be running after (in the case of a 'positive'
+ * job type) or before (in the case of a 'negative' job
+ * type. */
+
+ /* First check if there is an override */
+ if (j->ignore_order)
+ return true;
+
+ if (j->type == JOB_NOP)
+ return true;
+
+ if (j->type == JOB_START ||
+ j->type == JOB_VERIFY_ACTIVE ||
+ j->type == JOB_RELOAD) {
+
+ /* Immediate result is that the job is or might be
+ * started. In this case lets wait for the
+ * dependencies, regardless whether they are
+ * starting or stopping something. */
+
+ SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i)
+ if (other->job)
+ return false;
+ }
+
+ /* Also, if something else is being stopped and we should
+ * change state after it, then lets wait. */
+
+ SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i)
+ if (other->job &&
+ (other->job->type == JOB_STOP ||
+ other->job->type == JOB_RESTART))
+ return false;
+
+ /* This means that for a service a and a service b where b
+ * shall be started after a:
+ *
+ * start a + start b → 1st step start a, 2nd step start b
+ * start a + stop b → 1st step stop b, 2nd step start a
+ * stop a + start b → 1st step stop a, 2nd step start b
+ * stop a + stop b → 1st step stop b, 2nd step stop a
+ *
+ * This has the side effect that restarts are properly
+ * synchronized too. */
+
+ return true;
+}
+
+static void job_change_type(Job *j, JobType newtype) {
+ log_debug("Converting job %s/%s -> %s/%s",
+ j->unit->id, job_type_to_string(j->type),
+ j->unit->id, job_type_to_string(newtype));
+
+ j->type = newtype;
+}
+
+int job_run_and_invalidate(Job *j) {
+ int r;
+ uint32_t id;
+ Manager *m;
+
+ assert(j);
+ assert(j->installed);
+ assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION);
+ assert(j->in_run_queue);
+
+ LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
+ j->in_run_queue = false;
+
+ if (j->state != JOB_WAITING)
+ return 0;
+
+ if (!job_is_runnable(j))
+ return -EAGAIN;
+
+ j->state = JOB_RUNNING;
+ job_add_to_dbus_queue(j);
+
+ /* While we execute this operation the job might go away (for
+ * example: because it is replaced by a new, conflicting
+ * job.) To make sure we don't access a freed job later on we
+ * store the id here, so that we can verify the job is still
+ * valid. */
+ id = j->id;
+ m = j->manager;
+
+ switch (j->type) {
+
+ case JOB_START:
+ r = unit_start(j->unit);
+
+ /* If this unit cannot be started, then simply wait */
+ if (r == -EBADR)
+ r = 0;
+ break;
+
+ case JOB_VERIFY_ACTIVE: {
+ UnitActiveState t = unit_active_state(j->unit);
+ if (UNIT_IS_ACTIVE_OR_RELOADING(t))
+ r = -EALREADY;
+ else if (t == UNIT_ACTIVATING)
+ r = -EAGAIN;
+ else
+ r = -ENOEXEC;
+ break;
+ }
+
+ case JOB_STOP:
+ case JOB_RESTART:
+ r = unit_stop(j->unit);
+
+ /* If this unit cannot stopped, then simply wait. */
+ if (r == -EBADR)
+ r = 0;
+ break;
+
+ case JOB_RELOAD:
+ r = unit_reload(j->unit);
+ break;
+
+ case JOB_NOP:
+ r = -EALREADY;
+ break;
+
+ default:
+ assert_not_reached("Unknown job type");
+ }
+
+ j = manager_get_job(m, id);
+ if (j) {
+ if (r == -EALREADY)
+ r = job_finish_and_invalidate(j, JOB_DONE, true);
+ else if (r == -ENOEXEC)
+ r = job_finish_and_invalidate(j, JOB_SKIPPED, true);
+ else if (r == -EAGAIN)
+ j->state = JOB_WAITING;
+ else if (r < 0)
+ r = job_finish_and_invalidate(j, JOB_FAILED, true);
+ }
+
+ return r;
+}
+
+static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) {
+ const UnitStatusMessageFormats *format_table;
+
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+
+ format_table = &UNIT_VTABLE(u)->status_message_formats;
+ if (!format_table)
+ return NULL;
+
+ if (t == JOB_START)
+ return format_table->finished_start_job[result];
+ else if (t == JOB_STOP || t == JOB_RESTART)
+ return format_table->finished_stop_job[result];
+
+ return NULL;
+}
+
+static const char *job_get_status_message_format_try_harder(Unit *u, JobType t, JobResult result) {
+ const char *format;
+
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+
+ format = job_get_status_message_format(u, t, result);
+ if (format)
+ return format;
+
+ /* Return generic strings */
+ if (t == JOB_START) {
+ if (result == JOB_DONE)
+ return "Started %s.";
+ else if (result == JOB_FAILED)
+ return "Failed to start %s.";
+ else if (result == JOB_DEPENDENCY)
+ return "Dependency failed for %s.";
+ else if (result == JOB_TIMEOUT)
+ return "Timed out starting %s.";
+ } else if (t == JOB_STOP || t == JOB_RESTART) {
+ if (result == JOB_DONE)
+ return "Stopped %s.";
+ else if (result == JOB_FAILED)
+ return "Stopped (with error) %s.";
+ else if (result == JOB_TIMEOUT)
+ return "Timed out stoppping %s.";
+ } else if (t == JOB_RELOAD) {
+ if (result == JOB_DONE)
+ return "Reloaded %s.";
+ else if (result == JOB_FAILED)
+ return "Reload failed for %s.";
+ else if (result == JOB_TIMEOUT)
+ return "Timed out reloading %s.";
+ }
+
+ return NULL;
+}
+
+static void job_print_status_message(Unit *u, JobType t, JobResult result) {
+ const char *format;
+
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+
+ if (t == JOB_START) {
+ format = job_get_status_message_format(u, t, result);
+ if (!format)
+ return;
+
+ switch (result) {
+
+ case JOB_DONE:
+ if (u->condition_result)
+ unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+ break;
+
+ case JOB_FAILED:
+ unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+ unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id);
+ break;
+
+ case JOB_DEPENDENCY:
+ unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+ break;
+
+ case JOB_TIMEOUT:
+ unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+ break;
+
+ default:
+ ;
+ }
+
+ } else if (t == JOB_STOP || t == JOB_RESTART) {
+
+ format = job_get_status_message_format(u, t, result);
+ if (!format)
+ return;
+
+ switch (result) {
+
+ case JOB_TIMEOUT:
+ unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+ break;
+
+ case JOB_DONE:
+ case JOB_FAILED:
+ unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format, unit_description(u));
+ break;
+
+ default:
+ ;
+ }
+
+ } else if (t == JOB_VERIFY_ACTIVE) {
+
+ /* When verify-active detects the unit is inactive, report it.
+ * Most likely a DEPEND warning from a requisiting unit will
+ * occur next and it's nice to see what was requisited. */
+ if (result == JOB_SKIPPED)
+ unit_status_printf(u, ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, "%s is not active.", unit_description(u));
+ }
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static void job_log_status_message(Unit *u, JobType t, JobResult result) {
+ const char *format;
+ char buf[LINE_MAX];
+
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+
+ /* Skip this if it goes to the console. since we already print
+ * to the console anyway... */
+
+ if (log_on_console())
+ return;
+
+ format = job_get_status_message_format_try_harder(u, t, result);
+ if (!format)
+ return;
+
+ snprintf(buf, sizeof(buf), format, unit_description(u));
+ char_array_0(buf);
+
+ if (t == JOB_START) {
+ sd_id128_t mid;
+
+ mid = result == JOB_DONE ? SD_MESSAGE_UNIT_STARTED : SD_MESSAGE_UNIT_FAILED;
+ log_struct(result == JOB_DONE ? LOG_INFO : LOG_ERR,
+ MESSAGE_ID(mid),
+ "UNIT=%s", u->id,
+ "RESULT=%s", job_result_to_string(result),
+ "MESSAGE=%s", buf,
+ NULL);
+
+ } else if (t == JOB_STOP)
+ log_struct(result == JOB_DONE ? LOG_INFO : LOG_ERR,
+ MESSAGE_ID(SD_MESSAGE_UNIT_STOPPED),
+ "UNIT=%s", u->id,
+ "RESULT=%s", job_result_to_string(result),
+ "MESSAGE=%s", buf,
+ NULL);
+
+ else if (t == JOB_RELOAD)
+ log_struct(result == JOB_DONE ? LOG_INFO : LOG_ERR,
+ MESSAGE_ID(SD_MESSAGE_UNIT_RELOADED),
+ "UNIT=%s", u->id,
+ "RESULT=%s", job_result_to_string(result),
+ "MESSAGE=%s", buf,
+ NULL);
+}
+#pragma GCC diagnostic pop
+
+int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) {
+ Unit *u;
+ Unit *other;
+ JobType t;
+ Iterator i;
+
+ assert(j);
+ assert(j->installed);
+ assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION);
+
+ u = j->unit;
+ t = j->type;
+
+ j->result = result;
+
+ log_debug("Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result));
+
+ job_print_status_message(u, t, result);
+ job_log_status_message(u, t, result);
+
+ job_add_to_dbus_queue(j);
+
+ /* Patch restart jobs so that they become normal start jobs */
+ if (result == JOB_DONE && t == JOB_RESTART) {
+
+ job_change_type(j, JOB_START);
+ j->state = JOB_WAITING;
+
+ job_add_to_run_queue(j);
+
+ goto finish;
+ }
+
+ if (result == JOB_FAILED)
+ j->manager->n_failed_jobs ++;
+
+ job_uninstall(j);
+ job_free(j);
+
+ /* Fail depending jobs on failure */
+ if (result != JOB_DONE && recursive) {
+
+ if (t == JOB_START ||
+ t == JOB_VERIFY_ACTIVE) {
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i)
+ if (other->job &&
+ (other->job->type == JOB_START ||
+ other->job->type == JOB_VERIFY_ACTIVE))
+ job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
+
+ SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i)
+ if (other->job &&
+ (other->job->type == JOB_START ||
+ other->job->type == JOB_VERIFY_ACTIVE))
+ job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i)
+ if (other->job &&
+ !other->job->override &&
+ (other->job->type == JOB_START ||
+ other->job->type == JOB_VERIFY_ACTIVE))
+ job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
+
+ } else if (t == JOB_STOP) {
+
+ SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i)
+ if (other->job &&
+ (other->job->type == JOB_START ||
+ other->job->type == JOB_VERIFY_ACTIVE))
+ job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
+ }
+ }
+
+ /* Trigger OnFailure dependencies that are not generated by
+ * the unit itself. We don't tread JOB_CANCELED as failure in
+ * this context. And JOB_FAILURE is already handled by the
+ * unit itself. */
+ if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) {
+ log_notice("Job %s/%s failed with result '%s'.",
+ u->id,
+ job_type_to_string(t),
+ job_result_to_string(result));
+
+ unit_trigger_on_failure(u);
+ }
+
+finish:
+ /* Try to start the next jobs that can be started */
+ SET_FOREACH(other, u->dependencies[UNIT_AFTER], i)
+ if (other->job)
+ job_add_to_run_queue(other->job);
+ SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i)
+ if (other->job)
+ job_add_to_run_queue(other->job);
+
+ manager_check_finished(u->manager);
+
+ return 0;
+}
+
+int job_start_timer(Job *j) {
+ struct itimerspec its;
+ struct epoll_event ev;
+ int fd, r;
+ assert(j);
+
+ if (j->unit->job_timeout <= 0 ||
+ j->timer_watch.type == WATCH_JOB_TIMER)
+ return 0;
+
+ assert(j->timer_watch.type == WATCH_INVALID);
+
+ if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ zero(its);
+ timespec_store(&its.it_value, j->unit->job_timeout);
+
+ if (timerfd_settime(fd, 0, &its, NULL) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ zero(ev);
+ ev.data.ptr = &j->timer_watch;
+ ev.events = EPOLLIN;
+
+ if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ j->timer_watch.type = WATCH_JOB_TIMER;
+ j->timer_watch.fd = fd;
+ j->timer_watch.data.job = j;
+
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+void job_add_to_run_queue(Job *j) {
+ assert(j);
+ assert(j->installed);
+
+ if (j->in_run_queue)
+ return;
+
+ LIST_PREPEND(Job, run_queue, j->manager->run_queue, j);
+ j->in_run_queue = true;
+}
+
+void job_add_to_dbus_queue(Job *j) {
+ assert(j);
+ assert(j->installed);
+
+ if (j->in_dbus_queue)
+ return;
+
+ /* We don't check if anybody is subscribed here, since this
+ * job might just have been created and not yet assigned to a
+ * connection/client. */
+
+ LIST_PREPEND(Job, dbus_queue, j->manager->dbus_job_queue, j);
+ j->in_dbus_queue = true;
+}
+
+char *job_dbus_path(Job *j) {
+ char *p;
+
+ assert(j);
+
+ if (asprintf(&p, "/org/freedesktop/systemd1/job/%lu", (unsigned long) j->id) < 0)
+ return NULL;
+
+ return p;
+}
+
+void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w) {
+ assert(j);
+ assert(w == &j->timer_watch);
+
+ log_warning("Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type));
+ job_finish_and_invalidate(j, JOB_TIMEOUT, true);
+}
+
+int job_serialize(Job *j, FILE *f, FDSet *fds) {
+ fprintf(f, "job-id=%u\n", j->id);
+ fprintf(f, "job-type=%s\n", job_type_to_string(j->type));
+ fprintf(f, "job-state=%s\n", job_state_to_string(j->state));
+ fprintf(f, "job-override=%s\n", yes_no(j->override));
+ fprintf(f, "job-sent-dbus-new-signal=%s\n", yes_no(j->sent_dbus_new_signal));
+ fprintf(f, "job-ignore-order=%s\n", yes_no(j->ignore_order));
+ /* Cannot save bus clients. Just note the fact that we're losing
+ * them. job_send_message() will fallback to broadcasting. */
+ fprintf(f, "job-forgot-bus-clients=%s\n",
+ yes_no(j->forgot_bus_clients || j->bus_client_list));
+ if (j->timer_watch.type == WATCH_JOB_TIMER) {
+ int copy = fdset_put_dup(fds, j->timer_watch.fd);
+ if (copy < 0)
+ return copy;
+ fprintf(f, "job-timer-watch-fd=%d\n", copy);
+ }
+
+ /* End marker */
+ fputc('\n', f);
+ return 0;
+}
+
+int job_deserialize(Job *j, FILE *f, FDSet *fds) {
+ for (;;) {
+ char line[LINE_MAX], *l, *v;
+ size_t k;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ return 0;
+ return -errno;
+ }
+
+ char_array_0(line);
+ l = strstrip(line);
+
+ /* End marker */
+ if (l[0] == 0)
+ return 0;
+
+ k = strcspn(l, "=");
+
+ if (l[k] == '=') {
+ l[k] = 0;
+ v = l+k+1;
+ } else
+ v = l+k;
+
+ if (streq(l, "job-id")) {
+ if (safe_atou32(v, &j->id) < 0)
+ log_debug("Failed to parse job id value %s", v);
+ } else if (streq(l, "job-type")) {
+ JobType t = job_type_from_string(v);
+ if (t < 0)
+ log_debug("Failed to parse job type %s", v);
+ else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION)
+ log_debug("Cannot deserialize job of type %s", v);
+ else
+ j->type = t;
+ } else if (streq(l, "job-state")) {
+ JobState s = job_state_from_string(v);
+ if (s < 0)
+ log_debug("Failed to parse job state %s", v);
+ else
+ j->state = s;
+ } else if (streq(l, "job-override")) {
+ int b = parse_boolean(v);
+ if (b < 0)
+ log_debug("Failed to parse job override flag %s", v);
+ else
+ j->override = j->override || b;
+ } else if (streq(l, "job-sent-dbus-new-signal")) {
+ int b = parse_boolean(v);
+ if (b < 0)
+ log_debug("Failed to parse job sent_dbus_new_signal flag %s", v);
+ else
+ j->sent_dbus_new_signal = j->sent_dbus_new_signal || b;
+ } else if (streq(l, "job-ignore-order")) {
+ int b = parse_boolean(v);
+ if (b < 0)
+ log_debug("Failed to parse job ignore_order flag %s", v);
+ else
+ j->ignore_order = j->ignore_order || b;
+ } else if (streq(l, "job-forgot-bus-clients")) {
+ int b = parse_boolean(v);
+ if (b < 0)
+ log_debug("Failed to parse job forgot_bus_clients flag %s", v);
+ else
+ j->forgot_bus_clients = j->forgot_bus_clients || b;
+ } else if (streq(l, "job-timer-watch-fd")) {
+ int fd;
+ if (safe_atoi(v, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse job-timer-watch-fd value %s", v);
+ else {
+ if (j->timer_watch.type == WATCH_JOB_TIMER)
+ close_nointr_nofail(j->timer_watch.fd);
+
+ j->timer_watch.type = WATCH_JOB_TIMER;
+ j->timer_watch.fd = fdset_remove(fds, fd);
+ j->timer_watch.data.job = j;
+ }
+ }
+ }
+}
+
+int job_coldplug(Job *j) {
+ struct epoll_event ev;
+
+ if (j->timer_watch.type != WATCH_JOB_TIMER)
+ return 0;
+
+ zero(ev);
+ ev.data.ptr = &j->timer_watch;
+ ev.events = EPOLLIN;
+
+ if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, j->timer_watch.fd, &ev) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static const char* const job_state_table[_JOB_STATE_MAX] = {
+ [JOB_WAITING] = "waiting",
+ [JOB_RUNNING] = "running"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(job_state, JobState);
+
+static const char* const job_type_table[_JOB_TYPE_MAX] = {
+ [JOB_START] = "start",
+ [JOB_VERIFY_ACTIVE] = "verify-active",
+ [JOB_STOP] = "stop",
+ [JOB_RELOAD] = "reload",
+ [JOB_RELOAD_OR_START] = "reload-or-start",
+ [JOB_RESTART] = "restart",
+ [JOB_TRY_RESTART] = "try-restart",
+ [JOB_NOP] = "nop",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(job_type, JobType);
+
+static const char* const job_mode_table[_JOB_MODE_MAX] = {
+ [JOB_FAIL] = "fail",
+ [JOB_REPLACE] = "replace",
+ [JOB_ISOLATE] = "isolate",
+ [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies",
+ [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode);
+
+static const char* const job_result_table[_JOB_RESULT_MAX] = {
+ [JOB_DONE] = "done",
+ [JOB_CANCELED] = "canceled",
+ [JOB_TIMEOUT] = "timeout",
+ [JOB_FAILED] = "failed",
+ [JOB_DEPENDENCY] = "dependency",
+ [JOB_SKIPPED] = "skipped"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult);
diff --git a/src/core/job.h b/src/core/job.h
new file mode 100644
index 0000000000..3aa49d4b46
--- /dev/null
+++ b/src/core/job.h
@@ -0,0 +1,230 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <errno.h>
+
+typedef struct Job Job;
+typedef struct JobDependency JobDependency;
+typedef struct JobBusClient JobBusClient;
+typedef enum JobType JobType;
+typedef enum JobState JobState;
+typedef enum JobMode JobMode;
+typedef enum JobResult JobResult;
+
+/* Be careful when changing the job types! Adjust job_merging_table[] accordingly! */
+enum JobType {
+ JOB_START, /* if a unit does not support being started, we'll just wait until it becomes active */
+ JOB_VERIFY_ACTIVE,
+
+ JOB_STOP,
+
+ JOB_RELOAD, /* if running, reload */
+
+ /* Note that restarts are first treated like JOB_STOP, but
+ * then instead of finishing are patched to become
+ * JOB_START. */
+ JOB_RESTART, /* If running, stop. Then start unconditionally. */
+
+ _JOB_TYPE_MAX_MERGING,
+
+ /* JOB_NOP can enter into a transaction, but as it won't pull in
+ * any dependencies, it won't have to merge with anything.
+ * job_install() avoids the problem of merging JOB_NOP too (it's
+ * special-cased, only merges with other JOB_NOPs). */
+ JOB_NOP = _JOB_TYPE_MAX_MERGING, /* do nothing */
+
+ _JOB_TYPE_MAX_IN_TRANSACTION,
+
+ /* JOB_TRY_RESTART can never appear in a transaction, because
+ * it always collapses into JOB_RESTART or JOB_NOP before entering.
+ * Thus we never need to merge it with anything. */
+ JOB_TRY_RESTART = _JOB_TYPE_MAX_IN_TRANSACTION, /* if running, stop and then start */
+
+ /* JOB_RELOAD_OR_START won't enter into a transaction and cannot result
+ * from transaction merging (there's no way for JOB_RELOAD and
+ * JOB_START to meet in one transaction). It can result from a merge
+ * during job installation, but then it will immediately collapse into
+ * one of the two simpler types. */
+ JOB_RELOAD_OR_START, /* if running, reload, otherwise start */
+
+ _JOB_TYPE_MAX,
+ _JOB_TYPE_INVALID = -1
+};
+
+enum JobState {
+ JOB_WAITING,
+ JOB_RUNNING,
+ _JOB_STATE_MAX,
+ _JOB_STATE_INVALID = -1
+};
+
+enum JobMode {
+ JOB_FAIL, /* Fail if a conflicting job is already queued */
+ JOB_REPLACE, /* Replace an existing conflicting job */
+ JOB_ISOLATE, /* Start a unit, and stop all others */
+ JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */
+ JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */
+ _JOB_MODE_MAX,
+ _JOB_MODE_INVALID = -1
+};
+
+enum JobResult {
+ JOB_DONE, /* Job completed successfully */
+ JOB_CANCELED, /* Job canceled by a conflicting job installation or by explicit cancel request */
+ JOB_TIMEOUT, /* JobTimeout elapsed */
+ JOB_FAILED, /* Job failed */
+ JOB_DEPENDENCY, /* A required dependency job did not result in JOB_DONE */
+ JOB_SKIPPED, /* JOB_RELOAD of inactive unit; negative result of JOB_VERIFY_ACTIVE */
+ _JOB_RESULT_MAX,
+ _JOB_RESULT_INVALID = -1
+};
+
+#include "manager.h"
+#include "unit.h"
+#include "hashmap.h"
+#include "list.h"
+
+struct JobDependency {
+ /* Encodes that the 'subject' job needs the 'object' job in
+ * some way. This structure is used only while building a transaction. */
+ Job *subject;
+ Job *object;
+
+ LIST_FIELDS(JobDependency, subject);
+ LIST_FIELDS(JobDependency, object);
+
+ bool matters;
+ bool conflicts;
+};
+
+struct JobBusClient {
+ LIST_FIELDS(JobBusClient, client);
+ /* Note that this bus object is not ref counted here. */
+ DBusConnection *bus;
+ char name[0];
+};
+
+struct Job {
+ Manager *manager;
+ Unit *unit;
+
+ LIST_FIELDS(Job, transaction);
+ LIST_FIELDS(Job, run_queue);
+ LIST_FIELDS(Job, dbus_queue);
+
+ LIST_HEAD(JobDependency, subject_list);
+ LIST_HEAD(JobDependency, object_list);
+
+ /* Used for graph algs as a "I have been here" marker */
+ Job* marker;
+ unsigned generation;
+
+ uint32_t id;
+
+ JobType type;
+ JobState state;
+
+ Watch timer_watch;
+
+ /* There can be more than one client, because of job merging. */
+ LIST_HEAD(JobBusClient, bus_client_list);
+
+ JobResult result;
+
+ bool installed:1;
+ bool in_run_queue:1;
+ bool matters_to_anchor:1;
+ bool override:1;
+ bool in_dbus_queue:1;
+ bool sent_dbus_new_signal:1;
+ bool ignore_order:1;
+ bool forgot_bus_clients:1;
+};
+
+JobBusClient* job_bus_client_new(DBusConnection *connection, const char *name);
+
+Job* job_new(Unit *unit, JobType type);
+Job* job_new_raw(Unit *unit);
+void job_free(Job *job);
+Job* job_install(Job *j);
+int job_install_deserialized(Job *j);
+void job_uninstall(Job *j);
+void job_dump(Job *j, FILE*f, const char *prefix);
+int job_serialize(Job *j, FILE *f, FDSet *fds);
+int job_deserialize(Job *j, FILE *f, FDSet *fds);
+int job_coldplug(Job *j);
+
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
+void job_dependency_free(JobDependency *l);
+
+int job_merge(Job *j, Job *other);
+
+JobType job_type_lookup_merge(JobType a, JobType b);
+
+static inline bool job_type_is_mergeable(JobType a, JobType b) {
+ return job_type_lookup_merge(a, b) >= 0;
+}
+
+static inline bool job_type_is_conflicting(JobType a, JobType b) {
+ return !job_type_is_mergeable(a, b);
+}
+
+static inline bool job_type_is_superset(JobType a, JobType b) {
+ /* Checks whether operation a is a "superset" of b in its actions */
+ return a == job_type_lookup_merge(a, b);
+}
+
+bool job_type_is_redundant(JobType a, UnitActiveState b);
+
+/* Collapses a state-dependent job type into a simpler type by observing
+ * the state of the unit which it is going to be applied to. */
+void job_type_collapse(JobType *t, Unit *u);
+
+int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u);
+
+bool job_is_runnable(Job *j);
+
+void job_add_to_run_queue(Job *j);
+void job_add_to_dbus_queue(Job *j);
+
+int job_start_timer(Job *j);
+void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w);
+
+int job_run_and_invalidate(Job *j);
+int job_finish_and_invalidate(Job *j, JobResult result, bool recursive);
+
+char *job_dbus_path(Job *j);
+
+const char* job_type_to_string(JobType t);
+JobType job_type_from_string(const char *s);
+
+const char* job_state_to_string(JobState t);
+JobState job_state_from_string(const char *s);
+
+const char* job_mode_to_string(JobMode t);
+JobMode job_mode_from_string(const char *s);
+
+const char* job_result_to_string(JobResult t);
+JobResult job_result_from_string(const char *s);
diff --git a/src/core/kill.c b/src/core/kill.c
new file mode 100644
index 0000000000..0775653f73
--- /dev/null
+++ b/src/core/kill.c
@@ -0,0 +1,63 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+
+#include "kill.h"
+#include "util.h"
+
+void kill_context_init(KillContext *c) {
+ assert(c);
+
+ c->kill_signal = SIGTERM;
+ c->send_sigkill = true;
+}
+
+void kill_context_dump(KillContext *c, FILE *f, const char *prefix) {
+ assert(c);
+
+ if (!prefix)
+ prefix = "";
+
+ 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));
+}
+
+static const char* const kill_mode_table[_KILL_MODE_MAX] = {
+ [KILL_CONTROL_GROUP] = "control-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);
diff --git a/src/core/kill.h b/src/core/kill.h
new file mode 100644
index 0000000000..3c9b0ab8db
--- /dev/null
+++ b/src/core/kill.h
@@ -0,0 +1,60 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct KillContext KillContext;
+
+#include <stdbool.h>
+#include <stdio.h>
+
+typedef enum KillMode {
+ /* The kill mode is a property of a unit. */
+ KILL_CONTROL_GROUP = 0,
+ KILL_PROCESS,
+ KILL_NONE,
+ _KILL_MODE_MAX,
+ _KILL_MODE_INVALID = -1
+} KillMode;
+
+struct KillContext {
+ KillMode kill_mode;
+ int kill_signal;
+ bool send_sigkill;
+};
+
+typedef enum KillWho {
+ /* Kill who is a property of an operation */
+ KILL_MAIN,
+ KILL_CONTROL,
+ KILL_ALL,
+ _KILL_WHO_MAX,
+ _KILL_WHO_INVALID = -1
+} KillWho;
+
+void kill_context_init(KillContext *c);
+void kill_context_dump(KillContext *c, FILE *f, const char *prefix);
+
+const char *kill_mode_to_string(KillMode k);
+KillMode kill_mode_from_string(const char *s);
+
+const char *kill_who_to_string(KillWho k);
+KillWho kill_who_from_string(const char *s);
diff --git a/src/core/killall.c b/src/core/killall.c
new file mode 100644
index 0000000000..55200ffa48
--- /dev/null
+++ b/src/core/killall.c
@@ -0,0 +1,177 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "util.h"
+#include "def.h"
+#include "killall.h"
+
+#define TIMEOUT_USEC (5 * USEC_PER_SEC)
+
+static bool ignore_proc(pid_t pid) {
+ char buf[PATH_MAX];
+ FILE *f;
+ char c;
+ size_t count;
+ uid_t uid;
+ int r;
+
+ /* We are PID 1, let's not commit suicide */
+ if (pid == 1)
+ return true;
+
+ r = get_process_uid(pid, &uid);
+ if (r < 0)
+ return true; /* not really, but better safe than sorry */
+
+ /* Non-root processes otherwise are always subject to be killed */
+ if (uid != 0)
+ return false;
+
+ snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long) pid);
+ char_array_0(buf);
+
+ f = fopen(buf, "re");
+ if (!f)
+ return true; /* not really, but has the desired effect */
+
+ count = fread(&c, 1, 1, f);
+ fclose(f);
+
+ /* Kernel threads have an empty cmdline */
+ if (count <= 0)
+ return true;
+
+ /* Processes with argv[0][0] = '@' we ignore from the killing
+ * spree.
+ *
+ * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */
+ if (count == 1 && c == '@')
+ return true;
+
+ return false;
+}
+
+static void wait_for_children(int n_processes, sigset_t *mask) {
+ usec_t until;
+
+ assert(mask);
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ struct timespec ts;
+ int k;
+ usec_t n;
+
+ for (;;) {
+ pid_t pid = waitpid(-1, NULL, WNOHANG);
+
+ if (pid == 0)
+ break;
+
+ if (pid < 0 && errno == ECHILD)
+ return;
+
+ if (n_processes > 0)
+ if (--n_processes == 0)
+ return;
+ }
+
+ n = now(CLOCK_MONOTONIC);
+ if (n >= until)
+ return;
+
+ timespec_store(&ts, until - n);
+
+ if ((k = sigtimedwait(mask, NULL, &ts)) != SIGCHLD) {
+
+ if (k < 0 && errno != EAGAIN) {
+ log_error("sigtimedwait() failed: %m");
+ return;
+ }
+
+ if (k >= 0)
+ log_warning("sigtimedwait() returned unexpected signal.");
+ }
+ }
+}
+
+static int killall(int sig) {
+ DIR *dir;
+ struct dirent *d;
+ unsigned int n_processes = 0;
+
+ dir = opendir("/proc");
+ if (!dir)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ pid_t pid;
+
+ if (d->d_type != DT_DIR &&
+ d->d_type != DT_UNKNOWN)
+ continue;
+
+ if (parse_pid(d->d_name, &pid) < 0)
+ continue;
+
+ if (ignore_proc(pid))
+ continue;
+
+ if (kill(pid, sig) >= 0)
+ n_processes++;
+ else if (errno != ENOENT)
+ log_warning("Could not kill %d: %m", pid);
+ }
+
+ closedir(dir);
+
+ return n_processes;
+}
+
+void broadcast_signal(int sig, bool wait_for_exit) {
+ sigset_t mask, oldmask;
+ int n_processes;
+
+ assert_se(sigemptyset(&mask) == 0);
+ assert_se(sigaddset(&mask, SIGCHLD) == 0);
+ assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
+
+ if (kill(-1, SIGSTOP) < 0 && errno != ESRCH)
+ log_warning("kill(-1, SIGSTOP) failed: %m");
+
+ n_processes = killall(sig);
+
+ if (kill(-1, SIGCONT) < 0 && errno != ESRCH)
+ log_warning("kill(-1, SIGCONT) failed: %m");
+
+ if (n_processes <= 0)
+ goto finish;
+
+ if (wait_for_exit)
+ wait_for_children(n_processes, &mask);
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+}
diff --git a/src/core/killall.h b/src/core/killall.h
new file mode 100644
index 0000000000..d08ac142f0
--- /dev/null
+++ b/src/core/killall.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fookillallhfoo
+#define fookillallhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+void broadcast_signal(int sig, bool wait);
+
+#endif
diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c
new file mode 100644
index 0000000000..20ab232374
--- /dev/null
+++ b/src/core/kmod-setup.c
@@ -0,0 +1,104 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/wait.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <libkmod.h>
+
+#include "macro.h"
+#include "execute.h"
+
+#include "kmod-setup.h"
+
+typedef struct Kmodule {
+ const char *name;
+ const char *directory;
+ bool (*condition_fn)(void);
+} KModule;
+
+static const KModule kmod_table[] = {
+ { "autofs4", "/sys/class/misc/autofs", NULL } ,
+ { "ipv6", "/sys/module/ipv6", NULL },
+ { "unix", "/proc/net/unix", NULL } ,
+};
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static void systemd_kmod_log(void *data, int priority, const char *file, int line,
+ const char *fn, const char *format, va_list args)
+{
+ /* library logging is enabled at debug only */
+ log_metav(LOG_DEBUG, file, line, fn, format, args);
+}
+#pragma GCC diagnostic pop
+
+int kmod_setup(void) {
+ unsigned i;
+ struct kmod_ctx *ctx = NULL;
+ struct kmod_module *mod;
+ int err;
+
+ for (i = 0; i < ELEMENTSOF(kmod_table); i += 2) {
+ if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn())
+ continue;
+
+ if (access(kmod_table[i].directory, F_OK) >= 0)
+ continue;
+
+ log_debug("Your kernel apparently lacks built-in %s support. Might be a good idea to compile it in. "
+ "We'll now try to work around this by loading the module...",
+ kmod_table[i].name);
+
+ if (!ctx) {
+ ctx = kmod_new(NULL, NULL);
+ if (!ctx) {
+ log_error("Failed to allocate memory for kmod");
+ return -ENOMEM;
+ }
+
+ kmod_set_log_fn(ctx, systemd_kmod_log, NULL);
+ kmod_load_resources(ctx);
+ }
+
+ err = kmod_module_new_from_name(ctx, kmod_table[i].name, &mod);
+ if (err < 0) {
+ log_error("Failed to lookup module '%s'", kmod_table[i].name);
+ continue;
+ }
+
+ err = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL);
+ if (err == 0)
+ log_info("Inserted module '%s'", kmod_module_get_name(mod));
+ else if (err == KMOD_PROBE_APPLY_BLACKLIST)
+ log_info("Module '%s' is blacklisted", kmod_module_get_name(mod));
+ else
+ log_error("Failed to insert module '%s'", kmod_module_get_name(mod));
+
+ kmod_module_unref(mod);
+ }
+
+ if (ctx)
+ kmod_unref(ctx);
+
+ return 0;
+}
diff --git a/src/core/kmod-setup.h b/src/core/kmod-setup.h
new file mode 100644
index 0000000000..24dcdddfa4
--- /dev/null
+++ b/src/core/kmod-setup.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int kmod_setup(void);
diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c
new file mode 100644
index 0000000000..86f81c7484
--- /dev/null
+++ b/src/core/load-dropin.c
@@ -0,0 +1,150 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+
+#include "unit.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "strv.h"
+#include "unit-name.h"
+
+static int iterate_dir(Unit *u, const char *path, UnitDependency dependency) {
+ DIR *d;
+ struct dirent *de;
+ int r;
+
+ assert(u);
+ assert(path);
+
+ d = opendir(path);
+ if (!d) {
+
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ while ((de = readdir(d))) {
+ char *f;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ f = strjoin(path, "/", de->d_name, NULL);
+ if (!f) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = unit_add_dependency_by_name(u, dependency, de->d_name, f, true);
+ free(f);
+
+ if (r < 0)
+ log_error("Cannot add dependency %s to %s, ignoring: %s", de->d_name, u->id, strerror(-r));
+ }
+
+ r = 0;
+
+finish:
+ closedir(d);
+ return r;
+}
+
+static int process_dir(Unit *u, const char *unit_path, const char *name, const char *suffix, UnitDependency dependency) {
+ int r;
+ char *path;
+
+ assert(u);
+ assert(unit_path);
+ assert(name);
+ assert(suffix);
+
+ path = strjoin(unit_path, "/", name, suffix, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ if (u->manager->unit_path_cache &&
+ !set_get(u->manager->unit_path_cache, path))
+ r = 0;
+ else
+ r = iterate_dir(u, path, dependency);
+ free(path);
+
+ if (r < 0)
+ return r;
+
+ if (u->instance) {
+ char *template;
+ /* Also try the template dir */
+
+ template = unit_name_template(name);
+ if (!template)
+ return -ENOMEM;
+
+ path = strjoin(unit_path, "/", template, suffix, NULL);
+ free(template);
+
+ if (!path)
+ return -ENOMEM;
+
+ if (u->manager->unit_path_cache &&
+ !set_get(u->manager->unit_path_cache, path))
+ r = 0;
+ else
+ r = iterate_dir(u, path, dependency);
+ free(path);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int unit_load_dropin(Unit *u) {
+ Iterator i;
+ char *t;
+
+ assert(u);
+
+ /* Load dependencies from supplementary drop-in directories */
+
+ SET_FOREACH(t, u->names, i) {
+ char **p;
+
+ STRV_FOREACH(p, u->manager->lookup_paths.unit_path) {
+ int r;
+
+ r = process_dir(u, *p, t, ".wants", UNIT_WANTS);
+ if (r < 0)
+ return r;
+
+ r = process_dir(u, *p, t, ".requires", UNIT_REQUIRES);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/core/load-dropin.h b/src/core/load-dropin.h
new file mode 100644
index 0000000000..1d2fafeee6
--- /dev/null
+++ b/src/core/load-dropin.h
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "unit.h"
+
+/* Read service data supplementary drop-in directories */
+
+int unit_load_dropin(Unit *u);
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
new file mode 100644
index 0000000000..0c5ccebd73
--- /dev/null
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -0,0 +1,255 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "load-fragment.h"
+#include "missing.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name load_fragment_gperf_hash
+%define lookup-function-name load_fragment_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+m4_dnl Define the context options only once
+m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
+`$1.WorkingDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.working_directory)
+$1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory)
+$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user)
+$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group)
+$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups)
+$1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context)
+$1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context)
+$1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context)
+$1.IOSchedulingPriority, config_parse_exec_io_priority, 0, offsetof($1, exec_context)
+$1.CPUSchedulingPolicy, config_parse_exec_cpu_sched_policy, 0, offsetof($1, exec_context)
+$1.CPUSchedulingPriority, config_parse_exec_cpu_sched_prio, 0, offsetof($1, exec_context)
+$1.CPUSchedulingResetOnFork, config_parse_bool, 0, offsetof($1, exec_context.cpu_sched_reset_on_fork)
+$1.CPUAffinity, config_parse_exec_cpu_affinity, 0, offsetof($1, exec_context)
+$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask)
+$1.Environment, config_parse_unit_strv_printf, 0, offsetof($1, exec_context.environment)
+$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files)
+$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input)
+$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output)
+$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error)
+$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path)
+$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset)
+$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup)
+$1.TTYVTDisallocate, config_parse_bool, 0, offsetof($1, exec_context.tty_vt_disallocate)
+$1.SyslogIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.syslog_identifier)
+$1.SyslogFacility, config_parse_facility, 0, offsetof($1, exec_context.syslog_priority)
+$1.SyslogLevel, config_parse_level, 0, offsetof($1, exec_context.syslog_priority)
+$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix)
+$1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context)
+$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context)
+$1.CapabilityBoundingSet, config_parse_bounding_set, 0, offsetof($1, exec_context.capability_bounding_set_drop)
+$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec)
+$1.NoNewPrivileges config_parse_bool, 0, offsetof($1, exec_context.no_new_privileges)
+$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context)
+$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit)
+$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit)
+$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit)
+$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit)
+$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit)
+$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit)
+$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit)
+$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit)
+$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit)
+$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit)
+$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit)
+$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit)
+$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit)
+$1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit)
+$1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit)
+$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit)
+$1.ControlGroup, config_parse_unit_cgroup, 0, 0
+$1.ControlGroupAttribute, config_parse_unit_cgroup_attr, 0, 0
+$1.CPUShares, config_parse_unit_cpu_shares, 0, 0
+$1.MemoryLimit, config_parse_unit_memory_limit, 0, 0
+$1.MemorySoftLimit, config_parse_unit_memory_limit, 0, 0
+$1.DeviceAllow, config_parse_unit_device_allow, 0, 0
+$1.DeviceDeny, config_parse_unit_device_allow, 0, 0
+$1.BlockIOWeight, config_parse_unit_blkio_weight, 0, 0
+$1.BlockIOReadBandwidth, config_parse_unit_blkio_bandwidth, 0, 0
+$1.BlockIOWriteBandwidth, config_parse_unit_blkio_bandwidth, 0, 0
+$1.ReadWriteDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.read_write_dirs)
+$1.ReadOnlyDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.read_only_dirs)
+$1.InaccessibleDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs)
+$1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp)
+$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network)
+$1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context)
+$1.TCPWrapName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.tcpwrap_name)
+$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name)
+$1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe)
+$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id)
+$1.ControlGroupModify, config_parse_bool, 0, offsetof($1, exec_context.control_group_modify)
+$1.ControlGroupPersistent, config_parse_tristate, 0, offsetof($1, exec_context.control_group_persistent)'
+)m4_dnl
+m4_define(`KILL_CONTEXT_CONFIG_ITEMS',
+`$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill)
+$1.KillMode, config_parse_kill_mode, 0, offsetof($1, kill_context.kill_mode)
+$1.KillSignal, config_parse_kill_signal, 0, offsetof($1, kill_context.kill_signal)'
+)m4_dnl
+Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description)
+Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation)
+Unit.SourcePath, config_parse_path, 0, offsetof(Unit, source_path)
+Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0
+Unit.RequiresOverridable, config_parse_unit_deps, UNIT_REQUIRES_OVERRIDABLE, 0
+Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0
+Unit.RequisiteOverridable, config_parse_unit_deps, UNIT_REQUISITE_OVERRIDABLE, 0
+Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0
+Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0
+Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0
+Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0
+Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0
+Unit.After, config_parse_unit_deps, UNIT_AFTER, 0
+Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0
+Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0
+Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0
+Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0
+Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0
+Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0
+Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, offsetof(Unit, requires_mounts_for)
+Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded)
+Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start)
+Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop)
+Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate)
+Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies)
+Unit.OnFailureIsolate, config_parse_bool, 0, offsetof(Unit, on_failure_isolate)
+Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate)
+Unit.IgnoreOnSnapshot, config_parse_bool, 0, offsetof(Unit, ignore_on_snapshot)
+Unit.JobTimeoutSec, config_parse_usec, 0, offsetof(Unit, job_timeout)
+Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, 0
+Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, 0
+Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, 0
+Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,0
+Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, 0
+Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, 0
+Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, 0
+Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, 0
+Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, 0
+Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, 0
+Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, 0
+Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, 0
+Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, 0
+Unit.ConditionNull, config_parse_unit_condition_null, 0, 0
+m4_dnl
+Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file)
+Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command)
+Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command)
+Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command)
+Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command)
+Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command)
+Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command)
+Service.RestartSec, config_parse_usec, 0, offsetof(Service, restart_usec)
+Service.TimeoutSec, config_parse_service_timeout, 0, offsetof(Service, timeout_start_usec)
+Service.TimeoutStartSec, config_parse_service_timeout, 0, offsetof(Service, timeout_start_usec)
+Service.TimeoutStopSec, config_parse_service_timeout, 0, offsetof(Service, timeout_stop_usec)
+Service.WatchdogSec, config_parse_usec, 0, offsetof(Service, watchdog_usec)
+Service.StartLimitInterval, config_parse_usec, 0, offsetof(Service, start_limit.interval)
+Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Service, start_limit.burst)
+Service.StartLimitAction, config_parse_start_limit_action, 0, offsetof(Service, start_limit_action)
+Service.Type, config_parse_service_type, 0, offsetof(Service, type)
+Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart)
+Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only)
+Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only)
+Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit)
+Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid)
+Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_ignore_status)
+Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status)
+m4_ifdef(`HAVE_SYSV_COMPAT',
+`Service.SysVStartPriority, config_parse_sysv_priority, 0, offsetof(Service, sysv_start_priority)',
+`Service.SysVStartPriority, config_parse_warn_compat, 0, 0')
+Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking)
+Service.BusName, config_parse_unit_string_printf, 0, offsetof(Service, bus_name)
+Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access)
+Service.Sockets, config_parse_service_sockets, 0, 0
+Service.FsckPassNo, config_parse_fsck_passno, 0, offsetof(Service, fsck_passno)
+EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl
+KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl
+m4_dnl
+Socket.ListenStream, config_parse_socket_listen, 0, 0
+Socket.ListenDatagram, config_parse_socket_listen, 0, 0
+Socket.ListenSequentialPacket, config_parse_socket_listen, 0, 0
+Socket.ListenFIFO, config_parse_socket_listen, 0, 0
+Socket.ListenNetlink, config_parse_socket_listen, 0, 0
+Socket.ListenSpecial, config_parse_socket_listen, 0, 0
+Socket.ListenMessageQueue, config_parse_socket_listen, 0, 0
+Socket.BindIPv6Only, config_parse_socket_bind, 0, 0,
+Socket.Backlog, config_parse_unsigned, 0, offsetof(Socket, backlog)
+Socket.BindToDevice, config_parse_socket_bindtodevice, 0, 0
+Socket.ExecStartPre, config_parse_exec, SOCKET_EXEC_START_PRE, offsetof(Socket, exec_command)
+Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC_START_POST, offsetof(Socket, exec_command)
+Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command)
+Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command)
+Socket.TimeoutSec, config_parse_usec, 0, offsetof(Socket, timeout_usec)
+Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode)
+Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode)
+Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept)
+Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections)
+Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive)
+Socket.Priority, config_parse_int, 0, offsetof(Socket, priority)
+Socket.ReceiveBuffer, config_parse_bytes_size, 0, offsetof(Socket, receive_buffer)
+Socket.SendBuffer, config_parse_bytes_size, 0, offsetof(Socket, send_buffer)
+Socket.IPTOS, config_parse_ip_tos, 0, offsetof(Socket, ip_tos)
+Socket.IPTTL, config_parse_int, 0, offsetof(Socket, ip_ttl)
+Socket.Mark, config_parse_int, 0, offsetof(Socket, mark)
+Socket.PipeSize, config_parse_bytes_size, 0, offsetof(Socket, pipe_size)
+Socket.FreeBind, config_parse_bool, 0, offsetof(Socket, free_bind)
+Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent)
+Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast)
+Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred)
+Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec)
+Socket.TCPCongestion, config_parse_string, 0, offsetof(Socket, tcp_congestion)
+Socket.MessageQueueMaxMessages, config_parse_long, 0, offsetof(Socket, mq_maxmsg)
+Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize)
+Socket.Service, config_parse_socket_service, 0, 0
+Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack)
+Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in)
+Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out)
+EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl
+KILL_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl
+m4_dnl
+Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what)
+Mount.Where, config_parse_path, 0, offsetof(Mount, where)
+Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options)
+Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype)
+Mount.FsckPassNo, config_parse_fsck_passno, 0, offsetof(Mount, parameters_fragment.passno)
+Mount.TimeoutSec, config_parse_usec, 0, offsetof(Mount, timeout_usec)
+Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode)
+EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl
+KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl
+m4_dnl
+Automount.Where, config_parse_path, 0, offsetof(Automount, where)
+Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode)
+m4_dnl
+Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what)
+Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority)
+Swap.TimeoutSec, config_parse_usec, 0, offsetof(Swap, timeout_usec)
+EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl
+KILL_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl
+m4_dnl
+Timer.OnActiveSec, config_parse_timer, 0, 0
+Timer.OnBootSec, config_parse_timer, 0, 0
+Timer.OnStartupSec, config_parse_timer, 0, 0
+Timer.OnUnitActiveSec, config_parse_timer, 0, 0
+Timer.OnUnitInactiveSec, config_parse_timer, 0, 0
+Timer.Unit, config_parse_timer_unit, 0, 0
+m4_dnl
+Path.PathExists, config_parse_path_spec, 0, 0
+Path.PathExistsGlob, config_parse_path_spec, 0, 0
+Path.PathChanged, config_parse_path_spec, 0, 0
+Path.PathModified, config_parse_path_spec, 0, 0
+Path.DirectoryNotEmpty, config_parse_path_spec, 0, 0
+Path.Unit, config_parse_path_unit, 0, 0
+Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory)
+Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode)
+m4_dnl The [Install] section is ignored here.
+Install.Alias, NULL, 0, 0
+Install.WantedBy, NULL, 0, 0
+Install.RequiredBy, NULL, 0, 0
+Install.Also, NULL, 0, 0
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
new file mode 100644
index 0000000000..5803044178
--- /dev/null
+++ b/src/core/load-fragment.c
@@ -0,0 +1,2541 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <linux/oom.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <sys/prctl.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "unit.h"
+#include "strv.h"
+#include "conf-parser.h"
+#include "load-fragment.h"
+#include "log.h"
+#include "ioprio.h"
+#include "securebits.h"
+#include "missing.h"
+#include "unit-name.h"
+#include "unit-printf.h"
+#include "bus-errors.h"
+#include "utf8.h"
+#include "path-util.h"
+#include "syscall-list.h"
+
+#ifndef HAVE_SYSV_COMPAT
+int config_parse_warn_compat(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ log_debug("[%s:%u] Support for option %s= has been disabled at compile time and is ignored", filename, line, lvalue);
+ return 0;
+}
+#endif
+
+int config_parse_unit_deps(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ UnitDependency d = ltype;
+ Unit *u = userdata;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ char *t, *k;
+ int r;
+
+ t = strndup(w, l);
+ if (!t)
+ return -ENOMEM;
+
+ k = unit_name_printf(u, t);
+ free(t);
+ if (!k)
+ return -ENOMEM;
+
+ r = unit_add_dependency_by_name(u, d, k, NULL, true);
+ if (r < 0)
+ log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r));
+
+ free(k);
+ }
+
+ return 0;
+}
+
+int config_parse_unit_string_printf(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ k = unit_full_printf(u, rvalue);
+ if (!k)
+ return -ENOMEM;
+
+ r = config_parse_string(filename, line, section, lvalue, ltype, k, data, userdata);
+ free (k);
+
+ return r;
+}
+
+int config_parse_unit_strv_printf(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ k = unit_full_printf(u, rvalue);
+ if (!k)
+ return -ENOMEM;
+
+ r = config_parse_strv(filename, line, section, lvalue, ltype, k, data, userdata);
+ free(k);
+
+ return r;
+}
+
+int config_parse_unit_path_printf(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ k = unit_full_printf(u, rvalue);
+ if (!k)
+ return log_oom();
+
+ r = config_parse_path(filename, line, section, lvalue, ltype, k, data, userdata);
+ free(k);
+
+ return r;
+}
+
+int config_parse_socket_listen(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ SocketPort *p, *tail;
+ Socket *s;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ s = SOCKET(data);
+
+ p = new0(SocketPort, 1);
+ if (!p)
+ return -ENOMEM;
+
+ if (streq(lvalue, "ListenFIFO")) {
+ p->type = SOCKET_FIFO;
+
+ if (!(p->path = unit_full_printf(UNIT(s), rvalue))) {
+ free(p);
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(p->path);
+
+ } else if (streq(lvalue, "ListenSpecial")) {
+ p->type = SOCKET_SPECIAL;
+
+ if (!(p->path = unit_full_printf(UNIT(s), rvalue))) {
+ free(p);
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(p->path);
+
+ } else if (streq(lvalue, "ListenMessageQueue")) {
+
+ p->type = SOCKET_MQUEUE;
+
+ if (!(p->path = unit_full_printf(UNIT(s), rvalue))) {
+ free(p);
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(p->path);
+
+ } else if (streq(lvalue, "ListenNetlink")) {
+ char *k;
+ int r;
+
+ p->type = SOCKET_SOCKET;
+ k = unit_full_printf(UNIT(s), rvalue);
+ r = socket_address_parse_netlink(&p->address, k);
+ free(k);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue);
+ free(p);
+ return 0;
+ }
+
+ } else {
+ char *k;
+ int r;
+
+ p->type = SOCKET_SOCKET;
+ k = unit_full_printf(UNIT(s), rvalue);
+ r = socket_address_parse(&p->address, k);
+ free(k);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse address value, ignoring: %s", filename, line, rvalue);
+ free(p);
+ return 0;
+ }
+
+ if (streq(lvalue, "ListenStream"))
+ p->address.type = SOCK_STREAM;
+ else if (streq(lvalue, "ListenDatagram"))
+ p->address.type = SOCK_DGRAM;
+ else {
+ assert(streq(lvalue, "ListenSequentialPacket"));
+ p->address.type = SOCK_SEQPACKET;
+ }
+
+ if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) {
+ log_error("[%s:%u] Address family not supported, ignoring: %s", filename, line, rvalue);
+ free(p);
+ return 0;
+ }
+ }
+
+ p->fd = -1;
+
+ if (s->ports) {
+ LIST_FIND_TAIL(SocketPort, port, s->ports, tail);
+ LIST_INSERT_AFTER(SocketPort, port, s->ports, tail, p);
+ } else
+ LIST_PREPEND(SocketPort, port, s->ports, p);
+
+ return 0;
+}
+
+int config_parse_socket_bind(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Socket *s;
+ SocketAddressBindIPv6Only b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ s = SOCKET(data);
+
+ b = socket_address_bind_ipv6_only_from_string(rvalue);
+ if (b < 0) {
+ int r;
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse bind IPv6 only value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH;
+ } else
+ s->bind_ipv6_only = b;
+
+ return 0;
+}
+
+int config_parse_exec_nice(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int priority;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atoi(rvalue, &priority) < 0) {
+ log_error("[%s:%u] Failed to parse nice priority, ignoring: %s. ", filename, line, rvalue);
+ return 0;
+ }
+
+ if (priority < PRIO_MIN || priority >= PRIO_MAX) {
+ log_error("[%s:%u] Nice priority out of range, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ c->nice = priority;
+ c->nice_set = true;
+
+ return 0;
+}
+
+int config_parse_exec_oom_score_adjust(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int oa;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atoi(rvalue, &oa) < 0) {
+ log_error("[%s:%u] Failed to parse the OOM score adjust value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) {
+ log_error("[%s:%u] OOM score adjust value out of range, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ c->oom_score_adjust = oa;
+ c->oom_score_adjust_set = true;
+
+ return 0;
+}
+
+int config_parse_exec(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecCommand **e = data, *nce;
+ char *path, **n;
+ unsigned k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(e);
+
+ /* We accept an absolute path as first argument, or
+ * alternatively an absolute prefixed with @ to allow
+ * overriding of argv[0]. */
+
+ e += ltype;
+
+ for (;;) {
+ char *w;
+ size_t l;
+ char *state;
+ bool honour_argv0 = false, ignore = false;
+
+ path = NULL;
+ nce = NULL;
+ n = NULL;
+
+ rvalue += strspn(rvalue, WHITESPACE);
+
+ if (rvalue[0] == 0)
+ break;
+
+ if (rvalue[0] == '-') {
+ ignore = true;
+ rvalue ++;
+ }
+
+ if (rvalue[0] == '@') {
+ honour_argv0 = true;
+ rvalue ++;
+ }
+
+ if (*rvalue != '/') {
+ log_error("[%s:%u] Invalid executable path in command line, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ k = 0;
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ if (strncmp(w, ";", MAX(l, 1U)) == 0)
+ break;
+
+ k++;
+ }
+
+ n = new(char*, k + !honour_argv0);
+ if (!n)
+ return -ENOMEM;
+
+ k = 0;
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ if (strncmp(w, ";", MAX(l, 1U)) == 0)
+ break;
+
+ if (honour_argv0 && w == rvalue) {
+ assert(!path);
+
+ path = strndup(w, l);
+ if (!path) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (!utf8_is_valid(path)) {
+ log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue);
+ r = 0;
+ goto fail;
+ }
+
+ } else {
+ char *c;
+
+ c = n[k++] = cunescape_length(w, l);
+ if (!c) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (!utf8_is_valid(c)) {
+ log_error("[%s:%u] Path is not UTF-8 clean, ignoring assignment: %s", filename, line, rvalue);
+ r = 0;
+ goto fail;
+ }
+ }
+ }
+
+ n[k] = NULL;
+
+ if (!n[0]) {
+ log_error("[%s:%u] Invalid command line, ignoring: %s", filename, line, rvalue);
+ r = 0;
+ goto fail;
+ }
+
+ if (!path) {
+ path = strdup(n[0]);
+ if (!path) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ assert(path_is_absolute(path));
+
+ nce = new0(ExecCommand, 1);
+ if (!nce) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ nce->argv = n;
+ nce->path = path;
+ nce->ignore = ignore;
+
+ path_kill_slashes(nce->path);
+
+ exec_command_append_list(e, nce);
+
+ rvalue = state;
+ }
+
+ return 0;
+
+fail:
+ n[k] = NULL;
+ strv_free(n);
+ free(path);
+ free(nce);
+
+ return r;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier");
+
+int config_parse_socket_bindtodevice(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Socket *s = data;
+ char *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (rvalue[0] && !streq(rvalue, "*")) {
+ if (!(n = strdup(rvalue)))
+ return -ENOMEM;
+ } else
+ n = NULL;
+
+ free(s->bind_to_device);
+ s->bind_to_device = n;
+
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier");
+
+int config_parse_exec_io_class(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = ioprio_class_from_string(rvalue);
+ if (x < 0) {
+ log_error("[%s:%u] Failed to parse IO scheduling class, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio));
+ c->ioprio_set = true;
+
+ return 0;
+}
+
+int config_parse_exec_io_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atoi(rvalue, &i) < 0 || i < 0 || i >= IOPRIO_BE_NR) {
+ log_error("[%s:%u] Failed to parse io priority, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i);
+ c->ioprio_set = true;
+
+ return 0;
+}
+
+int config_parse_exec_cpu_sched_policy(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ ExecContext *c = data;
+ int x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = sched_policy_from_string(rvalue);
+ if (x < 0) {
+ log_error("[%s:%u] Failed to parse CPU scheduling policy, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ c->cpu_sched_policy = x;
+ c->cpu_sched_set = true;
+
+ return 0;
+}
+
+int config_parse_exec_cpu_sched_prio(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* On Linux RR/FIFO have the same range */
+ if (safe_atoi(rvalue, &i) < 0 || i < sched_get_priority_min(SCHED_RR) || i > sched_get_priority_max(SCHED_RR)) {
+ log_error("[%s:%u] Failed to parse CPU scheduling priority, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ c->cpu_sched_priority = i;
+ c->cpu_sched_set = true;
+
+ return 0;
+}
+
+int config_parse_exec_cpu_affinity(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ char *t;
+ int r;
+ unsigned cpu;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ r = safe_atou(t, &cpu);
+ free(t);
+
+ if (!(c->cpuset))
+ if (!(c->cpuset = cpu_set_malloc(&c->cpuset_ncpus)))
+ return -ENOMEM;
+
+ if (r < 0 || cpu >= c->cpuset_ncpus) {
+ log_error("[%s:%u] Failed to parse CPU affinity, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ CPU_SET_S(cpu, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset);
+ }
+
+ return 0;
+}
+
+int config_parse_exec_capabilities(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ cap_t cap;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!(cap = cap_from_text(rvalue))) {
+ if (errno == ENOMEM)
+ return -ENOMEM;
+
+ log_error("[%s:%u] Failed to parse capabilities, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (c->capabilities)
+ cap_free(c->capabilities);
+ c->capabilities = cap;
+
+ return 0;
+}
+
+int config_parse_exec_secure_bits(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ if (first_word(w, "keep-caps"))
+ c->secure_bits |= SECURE_KEEP_CAPS;
+ else if (first_word(w, "keep-caps-locked"))
+ c->secure_bits |= SECURE_KEEP_CAPS_LOCKED;
+ else if (first_word(w, "no-setuid-fixup"))
+ c->secure_bits |= SECURE_NO_SETUID_FIXUP;
+ else if (first_word(w, "no-setuid-fixup-locked"))
+ c->secure_bits |= SECURE_NO_SETUID_FIXUP_LOCKED;
+ else if (first_word(w, "noroot"))
+ c->secure_bits |= SECURE_NOROOT;
+ else if (first_word(w, "noroot-locked"))
+ c->secure_bits |= SECURE_NOROOT_LOCKED;
+ else {
+ log_error("[%s:%u] Failed to parse secure bits, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+int config_parse_bounding_set(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint64_t *capability_bounding_set_drop = data;
+ char *w;
+ size_t l;
+ char *state;
+ bool invert = false;
+ uint64_t sum = 0;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (rvalue[0] == '~') {
+ invert = true;
+ rvalue++;
+ }
+
+ /* Note that we store this inverted internally, since the
+ * kernel wants it like this. But we actually expose it
+ * non-inverted everywhere to have a fully normalized
+ * interface. */
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ char *t;
+ int r;
+ cap_value_t cap;
+
+ t = strndup(w, l);
+ if (!t)
+ return -ENOMEM;
+
+ r = cap_from_name(t, &cap);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse capability bounding set, ignoring: %s", filename, line, rvalue);
+ continue;
+ }
+
+ sum |= ((uint64_t) 1ULL) << (uint64_t) cap;
+ }
+
+ if (invert)
+ *capability_bounding_set_drop |= sum;
+ else
+ *capability_bounding_set_drop |= ~sum;
+
+ return 0;
+}
+
+int config_parse_limit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ struct rlimit **rl = data;
+ unsigned long long u;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ rl += ltype;
+
+ if (streq(rvalue, "infinity"))
+ u = (unsigned long long) RLIM_INFINITY;
+ else if (safe_atollu(rvalue, &u) < 0) {
+ log_error("[%s:%u] Failed to parse resource value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (!*rl)
+ if (!(*rl = new(struct rlimit, 1)))
+ return -ENOMEM;
+
+ (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u;
+ return 0;
+}
+
+int config_parse_unit_cgroup(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *w;
+ size_t l;
+ char *state;
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ char *t, *k;
+ int r;
+
+ t = strndup(w, l);
+ if (!t)
+ return -ENOMEM;
+
+ k = unit_full_printf(u, t);
+ free(t);
+
+ if (!k)
+ return -ENOMEM;
+
+ t = cunescape(k);
+ free(k);
+
+ if (!t)
+ return -ENOMEM;
+
+ r = unit_add_cgroup_from_text(u, t);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse cgroup value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_SYSV_COMPAT
+int config_parse_sysv_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *priority = data;
+ int i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atoi(rvalue, &i) < 0 || i < 0) {
+ log_error("[%s:%u] Failed to parse SysV start priority, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ *priority = (int) i;
+ return 0;
+}
+#endif
+
+int config_parse_fsck_passno(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *passno = data;
+ int i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atoi(rvalue, &i) || i < 0) {
+ log_error("[%s:%u] Failed to parse fsck pass number, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ *passno = (int) i;
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode");
+
+int config_parse_kill_signal(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *sig = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(sig);
+
+ if ((r = signal_from_string_try_harder(rvalue)) <= 0) {
+ log_error("[%s:%u] Failed to parse kill signal, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ *sig = r;
+ return 0;
+}
+
+int config_parse_exec_mount_flags(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ char *w;
+ size_t l;
+ char *state;
+ unsigned long flags = 0;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ if (strncmp(w, "shared", MAX(l, 6U)) == 0)
+ flags |= MS_SHARED;
+ else if (strncmp(w, "slave", MAX(l, 5U)) == 0)
+ flags |= MS_SLAVE;
+ else if (strncmp(w, "private", MAX(l, 7U)) == 0)
+ flags |= MS_PRIVATE;
+ else {
+ log_error("[%s:%u] Failed to parse mount flags, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+ }
+
+ c->mount_flags = flags;
+ return 0;
+}
+
+int config_parse_timer(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Timer *t = data;
+ usec_t u;
+ TimerValue *v;
+ TimerBase b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((b = timer_base_from_string(lvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse timer base, ignoring: %s", filename, line, lvalue);
+ return 0;
+ }
+
+ if (parse_usec(rvalue, &u) < 0) {
+ log_error("[%s:%u] Failed to parse timer value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (!(v = new0(TimerValue, 1)))
+ return -ENOMEM;
+
+ v->base = b;
+ v->value = u;
+
+ LIST_PREPEND(TimerValue, value, t->values, v);
+
+ return 0;
+}
+
+int config_parse_timer_unit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Timer *t = data;
+ int r;
+ DBusError error;
+ Unit *u;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ dbus_error_init(&error);
+
+ if (endswith(rvalue, ".timer")) {
+ log_error("[%s:%u] Unit cannot be of type timer, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, NULL, &u);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r));
+ dbus_error_free(&error);
+ return 0;
+ }
+
+ unit_ref_set(&t->unit, u);
+
+ return 0;
+}
+
+int config_parse_path_spec(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Path *p = data;
+ PathSpec *s;
+ PathType b;
+ char *k;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ b = path_type_from_string(lvalue);
+ if (b < 0) {
+ log_error("[%s:%u] Failed to parse path type, ignoring: %s", filename, line, lvalue);
+ return 0;
+ }
+
+ k = unit_full_printf(UNIT(p), rvalue);
+ if (!k)
+ return log_oom();
+
+ if (!path_is_absolute(k)) {
+ log_error("[%s:%u] Path is not absolute, ignoring: %s", filename, line, k);
+ free(k);
+ return 0;
+ }
+
+ s = new0(PathSpec, 1);
+ if (!s) {
+ free(k);
+ return log_oom();
+ }
+
+ s->path = path_kill_slashes(k);
+ s->type = b;
+ s->inotify_fd = -1;
+
+ LIST_PREPEND(PathSpec, spec, p->specs, s);
+
+ return 0;
+}
+
+int config_parse_path_unit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Path *t = data;
+ int r;
+ DBusError error;
+ Unit *u;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ dbus_error_init(&error);
+
+ if (endswith(rvalue, ".path")) {
+ log_error("[%s:%u] Unit cannot be of type path, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if ((r = manager_load_unit(UNIT(t)->manager, rvalue, NULL, &error, &u)) < 0) {
+ log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r));
+ dbus_error_free(&error);
+ return 0;
+ }
+
+ unit_ref_set(&t->unit, u);
+
+ return 0;
+}
+
+int config_parse_socket_service(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Socket *s = data;
+ int r;
+ DBusError error;
+ Unit *x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ dbus_error_init(&error);
+
+ if (!endswith(rvalue, ".service")) {
+ log_error("[%s:%u] Unit must be of type service, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ r = manager_load_unit(UNIT(s)->manager, rvalue, NULL, &error, &x);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to load unit %s, ignoring: %s", filename, line, rvalue, bus_error(&error, r));
+ dbus_error_free(&error);
+ return 0;
+ }
+
+ unit_ref_set(&s->service, x);
+
+ return 0;
+}
+
+int config_parse_service_sockets(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Service *s = data;
+ int r;
+ char *state, *w;
+ size_t l;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ char *t, *k;
+
+ t = strndup(w, l);
+ if (!t)
+ return -ENOMEM;
+
+ k = unit_name_printf(UNIT(s), t);
+ free(t);
+
+ if (!k)
+ return -ENOMEM;
+
+ if (!endswith(k, ".socket")) {
+ log_error("[%s:%u] Unit must be of type socket, ignoring: %s", filename, line, rvalue);
+ free(k);
+ continue;
+ }
+
+ r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true);
+ if (r < 0)
+ log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", filename, line, k, strerror(-r));
+
+ r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true);
+ if (r < 0)
+ return r;
+
+ free(k);
+ }
+
+ return 0;
+}
+
+int config_parse_service_timeout(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Service *s = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(s);
+
+ r = config_parse_usec(filename, line, section, lvalue, ltype, rvalue, data, userdata);
+
+ if (r)
+ return r;
+
+ if (streq(lvalue, "TimeoutSec")) {
+ s->start_timeout_defined = true;
+ s->timeout_stop_usec = s->timeout_start_usec;
+ } else if (streq(lvalue, "TimeoutStartSec"))
+ s->start_timeout_defined = true;
+
+ return 0;
+}
+
+int config_parse_unit_env_file(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char ***env = data, **k;
+ Unit *u = userdata;
+ char *s;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ s = unit_full_printf(u, rvalue);
+ if (!s)
+ return -ENOMEM;
+
+ if (!path_is_absolute(s[0] == '-' ? s + 1 : s)) {
+ log_error("[%s:%u] Path '%s' is not absolute, ignoring.", filename, line, s);
+ free(s);
+ return 0;
+ }
+
+ k = strv_append(*env, s);
+ free(s);
+ if (!k)
+ return -ENOMEM;
+
+ strv_free(*env);
+ *env = k;
+
+ return 0;
+}
+
+int config_parse_ip_tos(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *ip_tos = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ x = ip_tos_from_string(rvalue);
+ if (x < 0) {
+ log_error("[%s:%u] Failed to parse IP TOS value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ *ip_tos = x;
+ return 0;
+}
+
+int config_parse_unit_condition_path(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ConditionType cond = ltype;
+ Unit *u = data;
+ bool trigger, negate;
+ Condition *c;
+ _cleanup_free_ char *p = NULL;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ trigger = rvalue[0] == '|';
+ if (trigger)
+ rvalue++;
+
+ negate = rvalue[0] == '!';
+ if (negate)
+ rvalue++;
+
+ p = unit_full_printf(u, rvalue);
+ if (!p)
+ return -ENOMEM;
+
+ if (!path_is_absolute(p)) {
+ log_error("[%s:%u] Path in condition not absolute, ignoring: %s", filename, line, p);
+ return 0;
+ }
+
+ c = condition_new(cond, p, trigger, negate);
+ if (!c)
+ return -ENOMEM;
+
+ LIST_PREPEND(Condition, conditions, u->conditions, c);
+ return 0;
+}
+
+int config_parse_unit_condition_string(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ConditionType cond = ltype;
+ Unit *u = data;
+ bool trigger, negate;
+ Condition *c;
+ _cleanup_free_ char *s = NULL;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ trigger = rvalue[0] == '|';
+ if (trigger)
+ rvalue++;
+
+ negate = rvalue[0] == '!';
+ if (negate)
+ rvalue++;
+
+ s = unit_full_printf(u, rvalue);
+ if (!s)
+ return -ENOMEM;
+
+ c = condition_new(cond, s, trigger, negate);
+ if (!c)
+ return log_oom();
+
+ LIST_PREPEND(Condition, conditions, u->conditions, c);
+ return 0;
+}
+
+int config_parse_unit_condition_null(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = data;
+ Condition *c;
+ bool trigger, negate;
+ int b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((trigger = rvalue[0] == '|'))
+ rvalue++;
+
+ if ((negate = rvalue[0] == '!'))
+ rvalue++;
+
+ if ((b = parse_boolean(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse boolean value in condition, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (!b)
+ negate = !negate;
+
+ if (!(c = condition_new(CONDITION_NULL, NULL, trigger, negate)))
+ return -ENOMEM;
+
+ LIST_PREPEND(Condition, conditions, u->conditions, c);
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_start_limit_action, start_limit_action, StartLimitAction, "Failed to parse start limit action specifier");
+
+int config_parse_unit_cgroup_attr(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = data;
+ char **l;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ l = strv_split_quoted(rvalue);
+ if (!l)
+ return -ENOMEM;
+
+ if (strv_length(l) != 2) {
+ log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL);
+ strv_free(l);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
+ Unit *u = data;
+ int r;
+ unsigned long ul;
+ char *t;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (safe_atolu(rvalue, &ul) < 0 || ul < 1) {
+ log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (asprintf(&t, "%lu", ul) < 0)
+ return -ENOMEM;
+
+ r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
+ Unit *u = data;
+ int r;
+ off_t sz;
+ char *t;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) {
+ log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ if (asprintf(&t, "%llu", (unsigned long long) sz) < 0)
+ return -ENOMEM;
+
+ r = unit_add_cgroup_attribute(u,
+ "memory",
+ streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes",
+ t, NULL);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int device_map(const char *controller, const char *name, const char *value, char **ret) {
+ char **l;
+
+ assert(controller);
+ assert(name);
+ assert(value);
+ assert(ret);
+
+ l = strv_split_quoted(value);
+ if (!l)
+ return -ENOMEM;
+
+ assert(strv_length(l) >= 1);
+
+ if (streq(l[0], "*")) {
+
+ if (asprintf(ret, "a *:*%s%s",
+ isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ } else {
+ struct stat st;
+
+ if (stat(l[0], &st) < 0) {
+ log_warning("Couldn't stat device %s", l[0]);
+ strv_free(l);
+ return -errno;
+ }
+
+ if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+ log_warning("%s is not a device.", l[0]);
+ strv_free(l);
+ return -ENODEV;
+ }
+
+ if (asprintf(ret, "%c %u:%u%s%s",
+ S_ISCHR(st.st_mode) ? 'c' : 'b',
+ major(st.st_rdev), minor(st.st_rdev),
+ isempty(l[1]) ? "" : " ", strempty(l[1])) < 0) {
+
+ strv_free(l);
+ return -ENOMEM;
+ }
+ }
+
+ strv_free(l);
+ return 0;
+}
+
+int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
+ Unit *u = data;
+ char **l;
+ int r;
+ unsigned k;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ l = strv_split_quoted(rvalue);
+ if (!l)
+ return -ENOMEM;
+
+ k = strv_length(l);
+ if (k < 1 || k > 2) {
+ log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) {
+ log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (!isempty(l[1]) && !in_charset(l[1], "rwm")) {
+ log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+ strv_free(l);
+
+ r = unit_add_cgroup_attribute(u, "devices",
+ streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny",
+ rvalue, device_map);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int blkio_map(const char *controller, const char *name, const char *value, char **ret) {
+ struct stat st;
+ char **l;
+ dev_t d;
+
+ assert(controller);
+ assert(name);
+ assert(value);
+ assert(ret);
+
+ l = strv_split_quoted(value);
+ if (!l)
+ return -ENOMEM;
+
+ assert(strv_length(l) == 2);
+
+ if (stat(l[0], &st) < 0) {
+ log_warning("Couldn't stat device %s", l[0]);
+ strv_free(l);
+ return -errno;
+ }
+
+ if (S_ISBLK(st.st_mode))
+ d = st.st_rdev;
+ else if (major(st.st_dev) != 0) {
+ /* If this is not a device node then find the block
+ * device this file is stored on */
+ d = st.st_dev;
+
+ /* If this is a partition, try to get the originating
+ * block device */
+ block_get_whole_disk(d, &d);
+ } else {
+ log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]);
+ strv_free(l);
+ return -ENODEV;
+ }
+
+ if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ strv_free(l);
+ return 0;
+}
+
+int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
+ Unit *u = data;
+ int r;
+ unsigned long ul;
+ const char *device = NULL, *weight;
+ unsigned k;
+ char *t, **l;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ l = strv_split_quoted(rvalue);
+ if (!l)
+ return -ENOMEM;
+
+ k = strv_length(l);
+ if (k < 1 || k > 2) {
+ log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (k == 1)
+ weight = l[0];
+ else {
+ device = l[0];
+ weight = l[1];
+ }
+
+ if (device && !path_is_absolute(device)) {
+ log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) {
+ log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (device)
+ r = asprintf(&t, "%s %lu", device, ul);
+ else
+ r = asprintf(&t, "%lu", ul);
+ strv_free(l);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ if (device)
+ r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map);
+ else
+ r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
+ Unit *u = data;
+ int r;
+ off_t bytes;
+ unsigned k;
+ char *t, **l;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ l = strv_split_quoted(rvalue);
+ if (!l)
+ return -ENOMEM;
+
+ k = strv_length(l);
+ if (k != 2) {
+ log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (!path_is_absolute(l[0])) {
+ log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) {
+ log_error("[%s:%u] Failed to parse block IO bandwidth value, ignoring: %s", filename, line, rvalue);
+ strv_free(l);
+ return 0;
+ }
+
+ r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes);
+ strv_free(l);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ r = unit_add_cgroup_attribute(u, "blkio",
+ streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device",
+ t, blkio_map);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int config_parse_unit_requires_mounts_for(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ int r;
+ bool empty_before;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ empty_before = !u->requires_mounts_for;
+
+ r = config_parse_path_strv(filename, line, section, lvalue, ltype, rvalue, data, userdata);
+
+ /* Make it easy to find units with requires_mounts set */
+ if (empty_before && u->requires_mounts_for)
+ LIST_PREPEND(Unit, has_requires_mounts_for, u->manager->has_requires_mounts_for, u);
+
+ return r;
+}
+
+int config_parse_documentation(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ int r;
+ char **a, **b;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ r = config_parse_unit_strv_printf(filename, line, section, lvalue, ltype, rvalue, data, userdata);
+ if (r < 0)
+ return r;
+
+ for (a = b = u->documentation; a && *a; a++) {
+
+ if (is_valid_documentation_url(*a))
+ *(b++) = *a;
+ else {
+ log_error("[%s:%u] Invalid URL, ignoring: %s", filename, line, *a);
+ free(*a);
+ }
+ }
+ *b = NULL;
+
+ return r;
+}
+
+static void syscall_set(uint32_t *p, int nr) {
+ p[nr >> 4] |= 1 << (nr & 31);
+}
+
+static void syscall_unset(uint32_t *p, int nr) {
+ p[nr >> 4] &= ~(1 << (nr & 31));
+}
+
+int config_parse_syscall_filter(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ Unit *u = userdata;
+ bool invert = false;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ if (rvalue[0] == '~') {
+ invert = true;
+ rvalue++;
+ }
+
+ if (!c->syscall_filter) {
+ size_t n;
+
+ n = (syscall_max() + 31) >> 4;
+ c->syscall_filter = new(uint32_t, n);
+ if (!c->syscall_filter)
+ return -ENOMEM;
+
+ memset(c->syscall_filter, invert ? 0xFF : 0, n * sizeof(uint32_t));
+
+ /* Add these by default */
+ syscall_set(c->syscall_filter, __NR_execve);
+ syscall_set(c->syscall_filter, __NR_rt_sigreturn);
+#ifdef __NR_sigreturn
+ syscall_set(c->syscall_filter, __NR_sigreturn);
+#endif
+ syscall_set(c->syscall_filter, __NR_exit_group);
+ syscall_set(c->syscall_filter, __NR_exit);
+ }
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ int id;
+ char *t;
+
+ t = strndup(w, l);
+ if (!t)
+ return -ENOMEM;
+
+ id = syscall_from_name(t);
+ free(t);
+
+ if (id < 0) {
+ log_error("[%s:%u] Failed to parse syscall, ignoring: %s", filename, line, rvalue);
+ continue;
+ }
+
+ if (invert)
+ syscall_unset(c->syscall_filter, id);
+ else
+ syscall_set(c->syscall_filter, id);
+ }
+
+ c->no_new_privileges = true;
+
+ return 0;
+}
+
+#define FOLLOW_MAX 8
+
+static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
+ unsigned c = 0;
+ int fd, r;
+ FILE *f;
+ char *id = NULL;
+
+ assert(filename);
+ assert(*filename);
+ assert(_f);
+ assert(names);
+
+ /* This will update the filename pointer if the loaded file is
+ * reached by a symlink. The old string will be freed. */
+
+ for (;;) {
+ char *target, *name;
+
+ if (c++ >= FOLLOW_MAX)
+ return -ELOOP;
+
+ path_kill_slashes(*filename);
+
+ /* Add the file name we are currently looking at to
+ * the names of this unit, but only if it is a valid
+ * unit name. */
+ name = path_get_file_name(*filename);
+
+ if (unit_name_is_valid(name, true)) {
+
+ id = set_get(names, name);
+ if (!id) {
+ id = strdup(name);
+ if (!id)
+ return -ENOMEM;
+
+ r = set_put(names, id);
+ if (r < 0) {
+ free(id);
+ return r;
+ }
+ }
+ }
+
+ /* Try to open the file name, but don't if its a symlink */
+ fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd >= 0)
+ break;
+
+ if (errno != ELOOP)
+ return -errno;
+
+ /* Hmm, so this is a symlink. Let's read the name, and follow it manually */
+ r = readlink_and_make_absolute(*filename, &target);
+ if (r < 0)
+ return r;
+
+ free(*filename);
+ *filename = target;
+ }
+
+ f = fdopen(fd, "re");
+ if (!f) {
+ r = -errno;
+ close_nointr_nofail(fd);
+ return r;
+ }
+
+ *_f = f;
+ *_final = id;
+ return 0;
+}
+
+static int merge_by_names(Unit **u, Set *names, const char *id) {
+ char *k;
+ int r;
+
+ assert(u);
+ assert(*u);
+ assert(names);
+
+ /* Let's try to add in all symlink names we found */
+ while ((k = set_steal_first(names))) {
+
+ /* First try to merge in the other name into our
+ * unit */
+ r = unit_merge_by_name(*u, k);
+ if (r < 0) {
+ Unit *other;
+
+ /* Hmm, we couldn't merge the other unit into
+ * ours? Then let's try it the other way
+ * round */
+
+ other = manager_get_unit((*u)->manager, k);
+ free(k);
+
+ if (other) {
+ r = unit_merge(other, *u);
+ if (r >= 0) {
+ *u = other;
+ return merge_by_names(u, names, NULL);
+ }
+ }
+
+ return r;
+ }
+
+ if (id == k)
+ unit_choose_id(*u, id);
+
+ free(k);
+ }
+
+ return 0;
+}
+
+static int load_from_path(Unit *u, const char *path) {
+ int r;
+ Set *symlink_names;
+ FILE *f = NULL;
+ char *filename = NULL, *id = NULL;
+ Unit *merged;
+ struct stat st;
+
+ assert(u);
+ assert(path);
+
+ symlink_names = set_new(string_hash_func, string_compare_func);
+ if (!symlink_names)
+ return -ENOMEM;
+
+ if (path_is_absolute(path)) {
+
+ filename = strdup(path);
+ if (!filename) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = open_follow(&filename, &f, symlink_names, &id);
+ if (r < 0) {
+ free(filename);
+ filename = NULL;
+
+ if (r != -ENOENT)
+ goto finish;
+ }
+
+ } else {
+ char **p;
+
+ STRV_FOREACH(p, u->manager->lookup_paths.unit_path) {
+
+ /* Instead of opening the path right away, we manually
+ * follow all symlinks and add their name to our unit
+ * name set while doing so */
+ filename = path_make_absolute(path, *p);
+ if (!filename) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (u->manager->unit_path_cache &&
+ !set_get(u->manager->unit_path_cache, filename))
+ r = -ENOENT;
+ else
+ r = open_follow(&filename, &f, symlink_names, &id);
+
+ if (r < 0) {
+ free(filename);
+ filename = NULL;
+
+ if (r != -ENOENT)
+ goto finish;
+
+ /* Empty the symlink names for the next run */
+ set_clear_free(symlink_names);
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (!filename) {
+ /* Hmm, no suitable file found? */
+ r = 0;
+ goto finish;
+ }
+
+ merged = u;
+ r = merge_by_names(&merged, symlink_names, id);
+ if (r < 0)
+ goto finish;
+
+ if (merged != u) {
+ u->load_state = UNIT_MERGED;
+ r = 0;
+ goto finish;
+ }
+
+ if (fstat(fileno(f), &st) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (null_or_empty(&st))
+ u->load_state = UNIT_MASKED;
+ else {
+ /* Now, parse the file contents */
+ r = config_parse(filename, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, (void*) load_fragment_gperf_lookup, false, u);
+ if (r < 0)
+ goto finish;
+
+ u->load_state = UNIT_LOADED;
+ }
+
+ free(u->fragment_path);
+ u->fragment_path = filename;
+ filename = NULL;
+
+ u->fragment_mtime = timespec_load(&st.st_mtim);
+
+ if (u->source_path) {
+ if (stat(u->source_path, &st) >= 0)
+ u->source_mtime = timespec_load(&st.st_mtim);
+ else
+ u->source_mtime = 0;
+ }
+
+ r = 0;
+
+finish:
+ set_free_free(symlink_names);
+ free(filename);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int unit_load_fragment(Unit *u) {
+ int r;
+ Iterator i;
+ const char *t;
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+ assert(u->id);
+
+ /* First, try to find the unit under its id. We always look
+ * for unit files in the default directories, to make it easy
+ * to override things by placing things in /etc/systemd/system */
+ r = load_from_path(u, u->id);
+ if (r < 0)
+ return r;
+
+ /* Try to find an alias we can load this with */
+ if (u->load_state == UNIT_STUB)
+ SET_FOREACH(t, u->names, i) {
+
+ if (t == u->id)
+ continue;
+
+ r = load_from_path(u, t);
+ if (r < 0)
+ return r;
+
+ if (u->load_state != UNIT_STUB)
+ break;
+ }
+
+ /* And now, try looking for it under the suggested (originally linked) path */
+ if (u->load_state == UNIT_STUB && u->fragment_path) {
+
+ r = load_from_path(u, u->fragment_path);
+ if (r < 0)
+ return r;
+
+ if (u->load_state == UNIT_STUB) {
+ /* Hmm, this didn't work? Then let's get rid
+ * of the fragment path stored for us, so that
+ * we don't point to an invalid location. */
+ free(u->fragment_path);
+ u->fragment_path = NULL;
+ }
+ }
+
+ /* Look for a template */
+ if (u->load_state == UNIT_STUB && u->instance) {
+ char *k;
+
+ k = unit_name_template(u->id);
+ if (!k)
+ return -ENOMEM;
+
+ r = load_from_path(u, k);
+ free(k);
+
+ if (r < 0)
+ return r;
+
+ if (u->load_state == UNIT_STUB)
+ SET_FOREACH(t, u->names, i) {
+
+ if (t == u->id)
+ continue;
+
+ k = unit_name_template(t);
+ if (!k)
+ return -ENOMEM;
+
+ r = load_from_path(u, k);
+ free(k);
+
+ if (r < 0)
+ return r;
+
+ if (u->load_state != UNIT_STUB)
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void unit_dump_config_items(FILE *f) {
+ static const struct {
+ const ConfigParserCallback callback;
+ const char *rvalue;
+ } table[] = {
+ { config_parse_int, "INTEGER" },
+ { config_parse_unsigned, "UNSIGNED" },
+ { config_parse_bytes_size, "SIZE" },
+ { config_parse_bool, "BOOLEAN" },
+ { config_parse_string, "STRING" },
+ { config_parse_path, "PATH" },
+ { config_parse_unit_path_printf, "PATH" },
+ { config_parse_strv, "STRING [...]" },
+ { config_parse_exec_nice, "NICE" },
+ { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" },
+ { config_parse_exec_io_class, "IOCLASS" },
+ { config_parse_exec_io_priority, "IOPRIORITY" },
+ { config_parse_exec_cpu_sched_policy, "CPUSCHEDPOLICY" },
+ { config_parse_exec_cpu_sched_prio, "CPUSCHEDPRIO" },
+ { config_parse_exec_cpu_affinity, "CPUAFFINITY" },
+ { config_parse_mode, "MODE" },
+ { config_parse_unit_env_file, "FILE" },
+ { config_parse_output, "OUTPUT" },
+ { config_parse_input, "INPUT" },
+ { config_parse_facility, "FACILITY" },
+ { config_parse_level, "LEVEL" },
+ { config_parse_exec_capabilities, "CAPABILITIES" },
+ { config_parse_exec_secure_bits, "SECUREBITS" },
+ { config_parse_bounding_set, "BOUNDINGSET" },
+ { config_parse_limit, "LIMIT" },
+ { config_parse_unit_cgroup, "CGROUP [...]" },
+ { config_parse_unit_deps, "UNIT [...]" },
+ { config_parse_exec, "PATH [ARGUMENT [...]]" },
+ { config_parse_service_type, "SERVICETYPE" },
+ { config_parse_service_restart, "SERVICERESTART" },
+#ifdef HAVE_SYSV_COMPAT
+ { config_parse_sysv_priority, "SYSVPRIORITY" },
+#else
+ { config_parse_warn_compat, "NOTSUPPORTED" },
+#endif
+ { config_parse_kill_mode, "KILLMODE" },
+ { config_parse_kill_signal, "SIGNAL" },
+ { config_parse_socket_listen, "SOCKET [...]" },
+ { config_parse_socket_bind, "SOCKETBIND" },
+ { config_parse_socket_bindtodevice, "NETWORKINTERFACE" },
+ { config_parse_usec, "SECONDS" },
+ { config_parse_nsec, "NANOSECONDS" },
+ { config_parse_path_strv, "PATH [...]" },
+ { config_parse_unit_requires_mounts_for, "PATH [...]" },
+ { config_parse_exec_mount_flags, "MOUNTFLAG [...]" },
+ { config_parse_unit_string_printf, "STRING" },
+ { config_parse_timer, "TIMER" },
+ { config_parse_timer_unit, "NAME" },
+ { config_parse_path_spec, "PATH" },
+ { config_parse_path_unit, "UNIT" },
+ { config_parse_notify_access, "ACCESS" },
+ { config_parse_ip_tos, "TOS" },
+ { config_parse_unit_condition_path, "CONDITION" },
+ { config_parse_unit_condition_string, "CONDITION" },
+ { config_parse_unit_condition_null, "CONDITION" },
+ };
+
+ const char *prev = NULL;
+ const char *i;
+
+ assert(f);
+
+ NULSTR_FOREACH(i, load_fragment_gperf_nulstr) {
+ const char *rvalue = "OTHER", *lvalue;
+ unsigned j;
+ size_t prefix_len;
+ const char *dot;
+ const ConfigPerfItem *p;
+
+ assert_se(p = load_fragment_gperf_lookup(i, strlen(i)));
+
+ dot = strchr(i, '.');
+ lvalue = dot ? dot + 1 : i;
+ prefix_len = dot-i;
+
+ if (dot)
+ if (!prev || strncmp(prev, i, prefix_len+1) != 0) {
+ if (prev)
+ fputc('\n', f);
+
+ fprintf(f, "[%.*s]\n", (int) prefix_len, i);
+ }
+
+ for (j = 0; j < ELEMENTSOF(table); j++)
+ if (p->parse == table[j].callback) {
+ rvalue = table[j].rvalue;
+ break;
+ }
+
+ fprintf(f, "%s=%s\n", lvalue, rvalue);
+ prev = i;
+ }
+}
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
new file mode 100644
index 0000000000..24f738464c
--- /dev/null
+++ b/src/core/load-fragment.h
@@ -0,0 +1,88 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "unit.h"
+
+/* Read service data from .desktop file style configuration fragments */
+
+int unit_load_fragment(Unit *u);
+
+void unit_dump_config_items(FILE *f);
+
+int config_parse_warn_compat(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_deps(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_string_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_strv_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_path_printf(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_documentation(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_socket_listen(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_socket_bind(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_nice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_oom_score_adjust(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_service_timeout(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_service_type(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_service_restart(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_socket_bindtodevice(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_output(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_input(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_io_class(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_io_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_cpu_sched_policy(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_cpu_sched_prio(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_cpu_affinity(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_capabilities(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_secure_bits(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_bounding_set(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_cgroup(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_sysv_priority(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_fsck_passno(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_kill_signal(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_mount_flags(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_timer(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_timer_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path_spec(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_path_unit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_socket_service(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_service_sockets(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_env_file(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_ip_tos(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_condition_path(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_condition_string(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_condition_null(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_kill_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_requires_mounts_for(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_syscall_filter(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+/* gperf prototypes */
+const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);
+extern const char load_fragment_gperf_nulstr[];
diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c
new file mode 100644
index 0000000000..b9bd3a0748
--- /dev/null
+++ b/src/core/locale-setup.c
@@ -0,0 +1,241 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "locale-setup.h"
+#include "util.h"
+#include "macro.h"
+#include "virt.h"
+
+enum {
+ /* We don't list LC_ALL here on purpose. People should be
+ * using LANG instead. */
+
+ VARIABLE_LANG,
+ VARIABLE_LANGUAGE,
+ VARIABLE_LC_CTYPE,
+ VARIABLE_LC_NUMERIC,
+ VARIABLE_LC_TIME,
+ VARIABLE_LC_COLLATE,
+ VARIABLE_LC_MONETARY,
+ VARIABLE_LC_MESSAGES,
+ VARIABLE_LC_PAPER,
+ VARIABLE_LC_NAME,
+ VARIABLE_LC_ADDRESS,
+ VARIABLE_LC_TELEPHONE,
+ VARIABLE_LC_MEASUREMENT,
+ VARIABLE_LC_IDENTIFICATION,
+ _VARIABLE_MAX
+};
+
+static const char * const variable_names[_VARIABLE_MAX] = {
+ [VARIABLE_LANG] = "LANG",
+ [VARIABLE_LANGUAGE] = "LANGUAGE",
+ [VARIABLE_LC_CTYPE] = "LC_CTYPE",
+ [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
+ [VARIABLE_LC_TIME] = "LC_TIME",
+ [VARIABLE_LC_COLLATE] = "LC_COLLATE",
+ [VARIABLE_LC_MONETARY] = "LC_MONETARY",
+ [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
+ [VARIABLE_LC_PAPER] = "LC_PAPER",
+ [VARIABLE_LC_NAME] = "LC_NAME",
+ [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
+ [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
+ [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
+ [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
+};
+
+int locale_setup(void) {
+ char *variables[_VARIABLE_MAX];
+ int r = 0, i;
+
+ zero(variables);
+
+ if (detect_container(NULL) <= 0)
+ if ((r = parse_env_file("/proc/cmdline", WHITESPACE,
+#if defined(TARGET_FEDORA)
+ "LANG", &variables[VARIABLE_LANG],
+#endif
+ "locale.LANG", &variables[VARIABLE_LANG],
+ "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
+ "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
+ "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
+ "locale.LC_TIME", &variables[VARIABLE_LC_TIME],
+ "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
+ "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
+ "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
+ "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER],
+ "locale.LC_NAME", &variables[VARIABLE_LC_NAME],
+ "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
+ "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
+ "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
+ "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /proc/cmdline: %s", strerror(-r));
+ }
+
+ /* Hmm, nothing set on the kernel cmd line? Then let's
+ * try /etc/locale.conf */
+ if (r <= 0 &&
+ (r = parse_env_file("/etc/locale.conf", NEWLINE,
+ "LANG", &variables[VARIABLE_LANG],
+ "LANGUAGE", &variables[VARIABLE_LANGUAGE],
+ "LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
+ "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
+ "LC_TIME", &variables[VARIABLE_LC_TIME],
+ "LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
+ "LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
+ "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
+ "LC_PAPER", &variables[VARIABLE_LC_PAPER],
+ "LC_NAME", &variables[VARIABLE_LC_NAME],
+ "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
+ "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
+ "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
+ "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /etc/locale.conf: %s", strerror(-r));
+ }
+
+#if defined(TARGET_ALTLINUX)
+ if (r <= 0 &&
+ (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
+ "LANG", &variables[VARIABLE_LANG],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
+ }
+
+#elif defined(TARGET_SUSE)
+ if (r <= 0 &&
+ (r = parse_env_file("/etc/sysconfig/language", NEWLINE,
+ "RC_LANG", &variables[VARIABLE_LANG],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /etc/sysconfig/language: %s", strerror(-r));
+ }
+
+#elif defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM)
+ if (r <= 0 &&
+ (r = parse_env_file("/etc/default/locale", NEWLINE,
+ "LANG", &variables[VARIABLE_LANG],
+ "LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
+ "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
+ "LC_TIME", &variables[VARIABLE_LC_TIME],
+ "LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
+ "LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
+ "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
+ "LC_PAPER", &variables[VARIABLE_LC_PAPER],
+ "LC_NAME", &variables[VARIABLE_LC_NAME],
+ "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
+ "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
+ "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
+ "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /etc/default/locale: %s", strerror(-r));
+ }
+
+#elif defined(TARGET_GENTOO)
+ /* Gentoo's openrc expects locale variables in /etc/env.d/
+ * These files are later compiled by env-update into shell
+ * export commands at /etc/profile.env, with variables being
+ * exported by openrc's runscript (so /etc/init.d/)
+ */
+ if (r <= 0 &&
+ (r = parse_env_file("/etc/profile.env", NEWLINE,
+ "export LANG", &variables[VARIABLE_LANG],
+ "export LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
+ "export LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
+ "export LC_TIME", &variables[VARIABLE_LC_TIME],
+ "export LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
+ "export LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
+ "export LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
+ "export LC_PAPER", &variables[VARIABLE_LC_PAPER],
+ "export LC_NAME", &variables[VARIABLE_LC_NAME],
+ "export LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
+ "export LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
+ "export LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
+ "export LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /etc/profile.env: %s", strerror(-r));
+ }
+#elif defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA )
+ if (r <= 0 &&
+ (r = parse_env_file("/etc/sysconfig/i18n", NEWLINE,
+ "LANG", &variables[VARIABLE_LANG],
+ "LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
+ "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
+ "LC_TIME", &variables[VARIABLE_LC_TIME],
+ "LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
+ "LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
+ "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
+ "LC_PAPER", &variables[VARIABLE_LC_PAPER],
+ "LC_NAME", &variables[VARIABLE_LC_NAME],
+ "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
+ "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
+ "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
+ "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
+ NULL)) < 0) {
+
+ if (r != -ENOENT)
+ log_warning("Failed to read /etc/sysconfig/i18n: %s", strerror(-r));
+ }
+
+#endif
+
+ if (!variables[VARIABLE_LANG]) {
+ if (!(variables[VARIABLE_LANG] = strdup("C"))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ }
+
+ for (i = 0; i < _VARIABLE_MAX; i++) {
+
+ if (variables[i]) {
+ if (setenv(variable_names[i], variables[i], 1) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ } else
+ unsetenv(variable_names[i]);
+ }
+
+ r = 0;
+
+finish:
+ for (i = 0; i < _VARIABLE_MAX; i++)
+ free(variables[i]);
+
+ return r;
+}
diff --git a/src/core/locale-setup.h b/src/core/locale-setup.h
new file mode 100644
index 0000000000..5a0f2f7888
--- /dev/null
+++ b/src/core/locale-setup.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int locale_setup(void);
diff --git a/src/core/loopback-setup.c b/src/core/loopback-setup.c
new file mode 100644
index 0000000000..065b75a6e3
--- /dev/null
+++ b/src/core/loopback-setup.c
@@ -0,0 +1,317 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <asm/types.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "util.h"
+#include "macro.h"
+#include "loopback-setup.h"
+#include "socket-util.h"
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((uint8_t*) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+static int add_rtattr(struct nlmsghdr *n, size_t max_length, int type, const void *data, size_t data_length) {
+ size_t length;
+ struct rtattr *rta;
+
+ length = RTA_LENGTH(data_length);
+
+ if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length) > max_length)
+ return -E2BIG;
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = length;
+ memcpy(RTA_DATA(rta), data, data_length);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length);
+
+ return 0;
+}
+
+static ssize_t sendto_loop(int fd, const void *buf, size_t buf_len, int flags, const struct sockaddr *sa, socklen_t sa_len) {
+
+ for (;;) {
+ ssize_t l;
+
+ l = sendto(fd, buf, buf_len, flags, sa, sa_len);
+ if (l >= 0)
+ return l;
+
+ if (errno != EINTR)
+ return -errno;
+ }
+}
+
+static ssize_t recvfrom_loop(int fd, void *buf, size_t buf_len, int flags, struct sockaddr *sa, socklen_t *sa_len) {
+
+ for (;;) {
+ ssize_t l;
+
+ l = recvfrom(fd, buf, buf_len, flags, sa, sa_len);
+ if (l >= 0)
+ return l;
+
+ if (errno != EINTR)
+ return -errno;
+ }
+}
+
+static int add_adresses(int fd, int if_loopback, unsigned *requests) {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_nl nl;
+ } sa;
+ union {
+ struct nlmsghdr header;
+ uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+ NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
+ RTA_LENGTH(sizeof(struct in6_addr))];
+ } request;
+
+ struct ifaddrmsg *ifaddrmsg;
+ uint32_t ipv4_address = htonl(INADDR_LOOPBACK);
+ int r;
+
+ zero(request);
+
+ request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+ request.header.nlmsg_type = RTM_NEWADDR;
+ request.header.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_ACK;
+ request.header.nlmsg_seq = *requests + 1;
+
+ ifaddrmsg = NLMSG_DATA(&request.header);
+ ifaddrmsg->ifa_family = AF_INET;
+ ifaddrmsg->ifa_prefixlen = 8;
+ ifaddrmsg->ifa_flags = IFA_F_PERMANENT;
+ ifaddrmsg->ifa_scope = RT_SCOPE_HOST;
+ ifaddrmsg->ifa_index = if_loopback;
+
+ r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &ipv4_address, sizeof(ipv4_address));
+ if (r < 0)
+ return r;
+
+ zero(sa);
+ sa.nl.nl_family = AF_NETLINK;
+
+ if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+ return -errno;
+ (*requests)++;
+
+ if (!socket_ipv6_is_supported())
+ return 0;
+
+ request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+ request.header.nlmsg_seq = *requests + 1;
+
+ ifaddrmsg->ifa_family = AF_INET6;
+ ifaddrmsg->ifa_prefixlen = 128;
+
+ r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &in6addr_loopback, sizeof(in6addr_loopback));
+ if (r < 0)
+ return r;
+
+ if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+ return -errno;
+ (*requests)++;
+
+ return 0;
+}
+
+static int start_interface(int fd, int if_loopback, unsigned *requests) {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_nl nl;
+ } sa;
+ union {
+ struct nlmsghdr header;
+ uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+ NLMSG_ALIGN(sizeof(struct ifinfomsg))];
+ } request;
+
+ struct ifinfomsg *ifinfomsg;
+
+ zero(request);
+
+ request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+ request.header.nlmsg_type = RTM_NEWLINK;
+ request.header.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK;
+ request.header.nlmsg_seq = *requests + 1;
+
+ ifinfomsg = NLMSG_DATA(&request.header);
+ ifinfomsg->ifi_family = AF_UNSPEC;
+ ifinfomsg->ifi_index = if_loopback;
+ ifinfomsg->ifi_flags = IFF_UP;
+ ifinfomsg->ifi_change = IFF_UP;
+
+ zero(sa);
+ sa.nl.nl_family = AF_NETLINK;
+
+ if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+ return -errno;
+
+ (*requests)++;
+
+ return 0;
+}
+
+static int read_response(int fd, unsigned requests_max) {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_nl nl;
+ } sa;
+ union {
+ struct nlmsghdr header;
+ uint8_t buf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) +
+ NLMSG_ALIGN(sizeof(struct nlmsgerr))];
+ } response;
+
+ ssize_t l;
+ socklen_t sa_len = sizeof(sa);
+ struct nlmsgerr *nlmsgerr;
+
+ l = recvfrom_loop(fd, &response, sizeof(response), 0, &sa.sa, &sa_len);
+ if (l < 0)
+ return -errno;
+
+ if (sa_len != sizeof(sa.nl) ||
+ sa.nl.nl_family != AF_NETLINK)
+ return -EIO;
+
+ if (sa.nl.nl_pid != 0)
+ return 0;
+
+ if ((size_t) l < sizeof(struct nlmsghdr))
+ return -EIO;
+
+ if (response.header.nlmsg_type != NLMSG_ERROR ||
+ (pid_t) response.header.nlmsg_pid != getpid() ||
+ response.header.nlmsg_seq >= requests_max)
+ return 0;
+
+ if ((size_t) l < NLMSG_LENGTH(sizeof(struct nlmsgerr)) ||
+ response.header.nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
+ return -EIO;
+
+ nlmsgerr = NLMSG_DATA(&response.header);
+
+ if (nlmsgerr->error < 0 && nlmsgerr->error != -EEXIST)
+ return nlmsgerr->error;
+
+ return response.header.nlmsg_seq;
+}
+
+static int check_loopback(void) {
+ int r, fd;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ } sa;
+
+ /* If we failed to set up the loop back device, check whether
+ * it might already be set up */
+
+ fd = socket(AF_INET, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -errno;
+
+ zero(sa);
+ sa.in.sin_family = AF_INET;
+ sa.in.sin_addr.s_addr = INADDR_LOOPBACK;
+
+ if (bind(fd, &sa.sa, sizeof(sa.in)) >= 0)
+ r = 1;
+ else
+ r = errno == EADDRNOTAVAIL ? 0 : -errno;
+
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+int loopback_setup(void) {
+ int r, if_loopback;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_nl nl;
+ } sa;
+ unsigned requests = 0, i;
+ int fd;
+ bool eperm = false;
+
+ errno = 0;
+ if_loopback = (int) if_nametoindex("lo");
+ if (if_loopback <= 0)
+ return errno ? -errno : -ENODEV;
+
+ fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd < 0)
+ return -errno;
+
+ zero(sa);
+ sa.nl.nl_family = AF_NETLINK;
+ if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = add_adresses(fd, if_loopback, &requests);
+ if (r < 0)
+ goto finish;
+
+ r = start_interface(fd, if_loopback, &requests);
+ if (r < 0)
+ goto finish;
+
+ for (i = 0; i < requests; i++) {
+ r = read_response(fd, requests);
+
+ if (r == -EPERM)
+ eperm = true;
+ else if (r < 0)
+ goto finish;
+ }
+
+ if (eperm && check_loopback() < 0) {
+ r = -EPERM;
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (r < 0)
+ log_warning("Failed to configure loopback device: %s", strerror(-r));
+
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
diff --git a/src/core/loopback-setup.h b/src/core/loopback-setup.h
new file mode 100644
index 0000000000..dd83cf13a7
--- /dev/null
+++ b/src/core/loopback-setup.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int loopback_setup(void);
diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c
new file mode 100644
index 0000000000..7f4c23b130
--- /dev/null
+++ b/src/core/machine-id-setup.c
@@ -0,0 +1,246 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+
+#include <systemd/sd-id128.h>
+
+#include "machine-id-setup.h"
+#include "macro.h"
+#include "util.h"
+#include "mkdir.h"
+#include "log.h"
+#include "virt.h"
+
+static int shorten_uuid(char destination[36], const char *source) {
+ unsigned i, j;
+
+ for (i = 0, j = 0; i < 36 && j < 32; i++) {
+ int t;
+
+ t = unhexchar(source[i]);
+ if (t < 0)
+ continue;
+
+ destination[j++] = hexchar(t);
+ }
+
+ if (i == 36 && j == 32) {
+ destination[32] = '\n';
+ destination[33] = 0;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int generate(char id[34]) {
+ int fd, r;
+ unsigned char *p;
+ sd_id128_t buf;
+ char *q;
+ ssize_t k;
+ const char *vm_id;
+
+ assert(id);
+
+ /* First, try reading the D-Bus machine id, unless it is a symlink */
+ fd = open("/var/lib/dbus/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd >= 0) {
+
+ k = loop_read(fd, id, 32, false);
+ close_nointr_nofail(fd);
+
+ if (k >= 32) {
+ id[32] = '\n';
+ id[33] = 0;
+
+ log_info("Initializing machine ID from D-Bus machine ID.");
+ return 0;
+ }
+ }
+
+ /* If that didn't work, see if we are running in qemu/kvm and a
+ * machine ID was passed in via -uuid on the qemu/kvm command
+ * line */
+
+ r = detect_vm(&vm_id);
+ if (r > 0 && streq(vm_id, "kvm")) {
+ char uuid[37];
+
+ fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ if (fd >= 0) {
+ k = loop_read(fd, uuid, 36, false);
+ close_nointr_nofail(fd);
+
+ if (k >= 36) {
+ r = shorten_uuid(id, uuid);
+ if (r >= 0) {
+ log_info("Initializing machine ID from KVM UUID.");
+ return 0;
+ }
+ }
+ }
+ }
+
+ /* If that didn't work either, see if we are running in a
+ * container, and a machine ID was passed in via
+ * $container_uuid the way libvirt/LXC does it */
+ r = detect_container(NULL);
+ if (r > 0) {
+ char *e;
+
+ r = getenv_for_pid(1, "container_uuid", &e);
+ if (r > 0) {
+ if (strlen(e) >= 36) {
+ r = shorten_uuid(id, e);
+ if (r >= 0) {
+ log_info("Initializing machine ID from container UUID.");
+ free(e);
+ return 0;
+ }
+ }
+
+ free(e);
+ }
+ }
+
+ /* If that didn't work, generate a random machine id */
+ r = sd_id128_randomize(&buf);
+ if (r < 0) {
+ log_error("Failed to open /dev/urandom: %s", strerror(-r));
+ return r;
+ }
+
+ for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) {
+ q[0] = hexchar(*p >> 4);
+ q[1] = hexchar(*p & 15);
+ }
+
+ id[32] = '\n';
+ id[33] = 0;
+
+ log_info("Initializing machine ID from random generator.");
+
+ return 0;
+}
+
+int machine_id_setup(void) {
+ int fd, r;
+ bool writable;
+ struct stat st;
+ char id[34]; /* 32 + \n + \0 */
+ mode_t m;
+
+ m = umask(0000);
+
+ /* We create this 0444, to indicate that this isn't really
+ * something you should ever modify. Of course, since the file
+ * will be owned by root it doesn't matter much, but maybe
+ * people look. */
+
+ fd = open("/etc/machine-id", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
+ if (fd >= 0)
+ writable = true;
+ else {
+ fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ umask(m);
+ log_error("Cannot open /etc/machine-id: %m");
+ return -errno;
+ }
+
+ writable = false;
+ }
+
+ umask(m);
+
+ if (fstat(fd, &st) < 0) {
+ log_error("fstat() failed: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (S_ISREG(st.st_mode)) {
+ if (loop_read(fd, id, 32, false) >= 32) {
+ r = 0;
+ goto finish;
+ }
+ }
+
+ /* Hmm, so, the id currently stored is not useful, then let's
+ * generate one */
+
+ r = generate(id);
+ if (r < 0)
+ goto finish;
+
+ if (S_ISREG(st.st_mode) && writable) {
+ lseek(fd, 0, SEEK_SET);
+
+ if (loop_write(fd, id, 33, false) == 33) {
+ r = 0;
+ goto finish;
+ }
+ }
+
+ close_nointr_nofail(fd);
+ fd = -1;
+
+ /* Hmm, we couldn't write it? So let's write it to
+ * /run/machine-id as a replacement */
+
+ m = umask(0022);
+ r = write_one_line_file("/run/machine-id", id);
+ umask(m);
+
+ if (r < 0) {
+ log_error("Cannot write /run/machine-id: %s", strerror(-r));
+
+ unlink("/run/machine-id");
+ goto finish;
+ }
+
+ /* And now, let's mount it over */
+ r = mount("/run/machine-id", "/etc/machine-id", NULL, MS_BIND, NULL) < 0 ? -errno : 0;
+ if (r < 0) {
+ unlink("/run/machine-id");
+ log_error("Failed to mount /etc/machine-id: %s", strerror(-r));
+ } else {
+ log_info("Installed transient /etc/machine-id file.");
+
+ /* Mark the mount read-only */
+ mount(NULL, "/etc/machine-id", NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL);
+ }
+
+finish:
+
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h
new file mode 100644
index 0000000000..b9e6b4d674
--- /dev/null
+++ b/src/core/machine-id-setup.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int machine_id_setup(void);
diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in
new file mode 100644
index 0000000000..e521df337d
--- /dev/null
+++ b/src/core/macros.systemd.in
@@ -0,0 +1,56 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*- */
+#
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+# RPM macros for packages installing systemd unit files
+
+%_unitdir @systemunitdir@
+%_presetdir @systempresetdir@
+
+%systemd_requires \
+Requires(post): systemd \
+Requires(preun): systemd \
+Requires(postun): systemd \
+%{nil}
+
+%systemd_post() \
+if [ $1 -eq 1 ] ; then \
+ # Initial installation \
+ @rootbindir@/systemctl preset %{?*} >/dev/null 2>&1 || : \
+fi \
+%{nil}
+
+%systemd_preun() \
+if [ $1 -eq 0 ] ; then \
+ # Package removal, not upgrade \
+ @rootbindir@/systemctl --no-reload disable %{?*} > /dev/null 2>&1 || : \
+ @rootbindir@/systemctl stop %{?*} > /dev/null 2>&1 || : \
+fi \
+%{nil}
+
+%systemd_postun() \
+@rootbindir@/systemctl daemon-reload >/dev/null 2>&1 || : \
+%{nil}
+
+%systemd_postun_with_restart() \
+@rootbindir@/systemctl daemon-reload >/dev/null 2>&1 || : \
+if [ $1 -ge 1 ] ; then \
+ # Package upgrade, not uninstall \
+ @rootbindir@/systemctl try-restart %{?*} >/dev/null 2>&1 || : \
+fi \
+%{nil}
diff --git a/src/core/main.c b/src/core/main.c
new file mode 100644
index 0000000000..4da8ecb864
--- /dev/null
+++ b/src/core/main.c
@@ -0,0 +1,1950 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <sys/mount.h>
+
+#include "manager.h"
+#include "log.h"
+#include "load-fragment.h"
+#include "fdset.h"
+#include "special.h"
+#include "conf-parser.h"
+#include "bus-errors.h"
+#include "missing.h"
+#include "label.h"
+#include "build.h"
+#include "strv.h"
+#include "def.h"
+#include "virt.h"
+#include "watchdog.h"
+#include "path-util.h"
+#include "switch-root.h"
+#include "capability.h"
+#include "killall.h"
+
+#include "mount-setup.h"
+#include "loopback-setup.h"
+#include "kmod-setup.h"
+#include "hostname-setup.h"
+#include "machine-id-setup.h"
+#include "locale-setup.h"
+#include "hwclock.h"
+#include "selinux-setup.h"
+#include "ima-setup.h"
+#include "sd-daemon.h"
+
+static enum {
+ ACTION_RUN,
+ ACTION_HELP,
+ ACTION_VERSION,
+ ACTION_TEST,
+ ACTION_DUMP_CONFIGURATION_ITEMS,
+ ACTION_DONE
+} arg_action = ACTION_RUN;
+
+static char *arg_default_unit = NULL;
+static SystemdRunningAs arg_running_as = _SYSTEMD_RUNNING_AS_INVALID;
+
+static bool arg_dump_core = true;
+static bool arg_crash_shell = false;
+static int arg_crash_chvt = -1;
+static bool arg_confirm_spawn = false;
+static bool arg_show_status = true;
+static bool arg_switched_root = false;
+static char **arg_default_controllers = NULL;
+static char ***arg_join_controllers = NULL;
+static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL;
+static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT;
+static usec_t arg_runtime_watchdog = 0;
+static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE;
+static struct rlimit *arg_default_rlimit[RLIMIT_NLIMITS] = {};
+static uint64_t arg_capability_bounding_set_drop = 0;
+static nsec_t arg_timer_slack_nsec = (nsec_t) -1;
+
+static FILE* serialization = NULL;
+
+static void nop_handler(int sig) {
+}
+
+_noreturn_ static void crash(int sig) {
+
+ if (!arg_dump_core)
+ log_error("Caught <%s>, not dumping core.", signal_to_string(sig));
+ else {
+ struct sigaction sa;
+ pid_t pid;
+
+ /* We want to wait for the core process, hence let's enable SIGCHLD */
+ zero(sa);
+ sa.sa_handler = nop_handler;
+ sa.sa_flags = SA_NOCLDSTOP|SA_RESTART;
+ assert_se(sigaction(SIGCHLD, &sa, NULL) == 0);
+
+ if ((pid = fork()) < 0)
+ log_error("Caught <%s>, cannot fork for core dump: %s", signal_to_string(sig), strerror(errno));
+
+ else if (pid == 0) {
+ struct rlimit rl;
+
+ /* Enable default signal handler for core dump */
+ zero(sa);
+ sa.sa_handler = SIG_DFL;
+ assert_se(sigaction(sig, &sa, NULL) == 0);
+
+ /* Don't limit the core dump size */
+ zero(rl);
+ rl.rlim_cur = RLIM_INFINITY;
+ rl.rlim_max = RLIM_INFINITY;
+ setrlimit(RLIMIT_CORE, &rl);
+
+ /* Just to be sure... */
+ assert_se(chdir("/") == 0);
+
+ /* Raise the signal again */
+ raise(sig);
+
+ assert_not_reached("We shouldn't be here...");
+ _exit(1);
+
+ } else {
+ siginfo_t status;
+ int r;
+
+ /* Order things nicely. */
+ if ((r = wait_for_terminate(pid, &status)) < 0)
+ log_error("Caught <%s>, waitpid() failed: %s", signal_to_string(sig), strerror(-r));
+ else if (status.si_code != CLD_DUMPED)
+ log_error("Caught <%s>, core dump failed.", signal_to_string(sig));
+ else
+ log_error("Caught <%s>, dumped core as pid %lu.", signal_to_string(sig), (unsigned long) pid);
+ }
+ }
+
+ if (arg_crash_chvt)
+ chvt(arg_crash_chvt);
+
+ if (arg_crash_shell) {
+ struct sigaction sa;
+ pid_t pid;
+
+ log_info("Executing crash shell in 10s...");
+ sleep(10);
+
+ /* Let the kernel reap children for us */
+ zero(sa);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART;
+ assert_se(sigaction(SIGCHLD, &sa, NULL) == 0);
+
+ pid = fork();
+ if (pid < 0)
+ log_error("Failed to fork off crash shell: %m");
+ else if (pid == 0) {
+ make_console_stdio();
+ execl("/bin/sh", "/bin/sh", NULL);
+
+ log_error("execl() failed: %m");
+ _exit(1);
+ }
+
+ log_info("Successfully spawned crash shell as pid %lu.", (unsigned long) pid);
+ }
+
+ log_info("Freezing execution.");
+ freeze();
+}
+
+static void install_crash_handler(void) {
+ struct sigaction sa;
+
+ zero(sa);
+
+ sa.sa_handler = crash;
+ sa.sa_flags = SA_NODEFER;
+
+ sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1);
+}
+
+static int console_setup(bool do_reset) {
+ int tty_fd, r;
+
+ /* If we are init, we connect stdin/stdout/stderr to /dev/null
+ * and make sure we don't have a controlling tty. */
+
+ release_terminal();
+
+ if (!do_reset)
+ return 0;
+
+ tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (tty_fd < 0) {
+ log_error("Failed to open /dev/console: %s", strerror(-tty_fd));
+ return -tty_fd;
+ }
+
+ /* We don't want to force text mode.
+ * plymouth may be showing pictures already from initrd. */
+ r = reset_terminal_fd(tty_fd, false);
+ if (r < 0)
+ log_error("Failed to reset /dev/console: %s", strerror(-r));
+
+ close_nointr_nofail(tty_fd);
+ return r;
+}
+
+static int set_default_unit(const char *u) {
+ char *c;
+
+ assert(u);
+
+ c = strdup(u);
+ if (!c)
+ return -ENOMEM;
+
+ free(arg_default_unit);
+ arg_default_unit = c;
+
+ return 0;
+}
+
+static int parse_proc_cmdline_word(const char *word) {
+
+ static const char * const rlmap[] = {
+ "emergency", SPECIAL_EMERGENCY_TARGET,
+ "-b", SPECIAL_EMERGENCY_TARGET,
+ "single", SPECIAL_RESCUE_TARGET,
+ "-s", SPECIAL_RESCUE_TARGET,
+ "s", SPECIAL_RESCUE_TARGET,
+ "S", SPECIAL_RESCUE_TARGET,
+ "1", SPECIAL_RESCUE_TARGET,
+ "2", SPECIAL_RUNLEVEL2_TARGET,
+ "3", SPECIAL_RUNLEVEL3_TARGET,
+ "4", SPECIAL_RUNLEVEL4_TARGET,
+ "5", SPECIAL_RUNLEVEL5_TARGET,
+ };
+
+ assert(word);
+
+ if (startswith(word, "systemd.unit=")) {
+
+ if (!in_initrd())
+ return set_default_unit(word + 13);
+
+ } else if (startswith(word, "rd.systemd.unit=")) {
+
+ if (in_initrd())
+ return set_default_unit(word + 16);
+
+ } else if (startswith(word, "systemd.log_target=")) {
+
+ if (log_set_target_from_string(word + 19) < 0)
+ log_warning("Failed to parse log target %s. Ignoring.", word + 19);
+
+ } else if (startswith(word, "systemd.log_level=")) {
+
+ if (log_set_max_level_from_string(word + 18) < 0)
+ log_warning("Failed to parse log level %s. Ignoring.", word + 18);
+
+ } else if (startswith(word, "systemd.log_color=")) {
+
+ if (log_show_color_from_string(word + 18) < 0)
+ log_warning("Failed to parse log color setting %s. Ignoring.", word + 18);
+
+ } else if (startswith(word, "systemd.log_location=")) {
+
+ if (log_show_location_from_string(word + 21) < 0)
+ log_warning("Failed to parse log location setting %s. Ignoring.", word + 21);
+
+ } else if (startswith(word, "systemd.dump_core=")) {
+ int r;
+
+ if ((r = parse_boolean(word + 18)) < 0)
+ log_warning("Failed to parse dump core switch %s. Ignoring.", word + 18);
+ else
+ arg_dump_core = r;
+
+ } else if (startswith(word, "systemd.crash_shell=")) {
+ int r;
+
+ if ((r = parse_boolean(word + 20)) < 0)
+ log_warning("Failed to parse crash shell switch %s. Ignoring.", word + 20);
+ else
+ arg_crash_shell = r;
+
+ } else if (startswith(word, "systemd.confirm_spawn=")) {
+ int r;
+
+ if ((r = parse_boolean(word + 22)) < 0)
+ log_warning("Failed to parse confirm spawn switch %s. Ignoring.", word + 22);
+ else
+ arg_confirm_spawn = r;
+
+ } else if (startswith(word, "systemd.crash_chvt=")) {
+ int k;
+
+ if (safe_atoi(word + 19, &k) < 0)
+ log_warning("Failed to parse crash chvt switch %s. Ignoring.", word + 19);
+ else
+ arg_crash_chvt = k;
+
+ } else if (startswith(word, "systemd.show_status=")) {
+ int r;
+
+ if ((r = parse_boolean(word + 20)) < 0)
+ log_warning("Failed to parse show status switch %s. Ignoring.", word + 20);
+ else
+ arg_show_status = r;
+ } else if (startswith(word, "systemd.default_standard_output=")) {
+ int r;
+
+ if ((r = exec_output_from_string(word + 32)) < 0)
+ log_warning("Failed to parse default standard output switch %s. Ignoring.", word + 32);
+ else
+ arg_default_std_output = r;
+ } else if (startswith(word, "systemd.default_standard_error=")) {
+ int r;
+
+ if ((r = exec_output_from_string(word + 31)) < 0)
+ log_warning("Failed to parse default standard error switch %s. Ignoring.", word + 31);
+ else
+ arg_default_std_error = r;
+ } else if (startswith(word, "systemd.setenv=")) {
+ char *cenv, *eq;
+ int r;
+
+ cenv = strdup(word + 15);
+ if (!cenv)
+ return -ENOMEM;
+
+ eq = strchr(cenv, '=');
+ if (!eq) {
+ r = unsetenv(cenv);
+ if (r < 0)
+ log_warning("unsetenv failed %m. Ignoring.");
+ } else {
+ *eq = 0;
+ r = setenv(cenv, eq + 1, 1);
+ if (r < 0)
+ log_warning("setenv failed %m. Ignoring.");
+ }
+ free(cenv);
+
+ } else if (startswith(word, "systemd.") ||
+ (in_initrd() && startswith(word, "rd.systemd."))) {
+
+ log_warning("Unknown kernel switch %s. Ignoring.", word);
+
+ log_info("Supported kernel switches:\n"
+ "systemd.unit=UNIT Default unit to start\n"
+ "rd.systemd.unit=UNIT Default unit to start when run in initrd\n"
+ "systemd.dump_core=0|1 Dump core on crash\n"
+ "systemd.crash_shell=0|1 Run shell on crash\n"
+ "systemd.crash_chvt=N Change to VT #N on crash\n"
+ "systemd.confirm_spawn=0|1 Confirm every process spawn\n"
+ "systemd.show_status=0|1 Show status updates on the console during bootup\n"
+ "systemd.log_target=console|kmsg|journal|journal-or-kmsg|syslog|syslog-or-kmsg|null\n"
+ " Log target\n"
+ "systemd.log_level=LEVEL Log level\n"
+ "systemd.log_color=0|1 Highlight important log messages\n"
+ "systemd.log_location=0|1 Include code location in log messages\n"
+ "systemd.default_standard_output=null|tty|syslog|syslog+console|kmsg|kmsg+console|journal|journal+console\n"
+ " Set default log output for services\n"
+ "systemd.default_standard_error=null|tty|syslog|syslog+console|kmsg|kmsg+console|journal|journal+console\n"
+ " Set default log error output for services\n"
+ "systemd.setenv=ASSIGNMENT Set an environment variable for all spawned processes\n");
+
+ } else if (streq(word, "quiet"))
+ arg_show_status = false;
+ else if (!in_initrd()) {
+ unsigned i;
+
+ /* SysV compatibility */
+ for (i = 0; i < ELEMENTSOF(rlmap); i += 2)
+ if (streq(word, rlmap[i]))
+ return set_default_unit(rlmap[i+1]);
+ }
+
+ return 0;
+}
+
+static int config_parse_level2(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ log_set_max_level_from_string(rvalue);
+ return 0;
+}
+
+static int config_parse_target(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ log_set_target_from_string(rvalue);
+ return 0;
+}
+
+static int config_parse_color(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ log_show_color_from_string(rvalue);
+ return 0;
+}
+
+static int config_parse_location(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ log_show_location_from_string(rvalue);
+ return 0;
+}
+
+static int config_parse_cpu_affinity2(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char *w;
+ size_t l;
+ char *state;
+ cpu_set_t *c = NULL;
+ unsigned ncpus = 0;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ char *t;
+ int r;
+ unsigned cpu;
+
+ if (!(t = strndup(w, l)))
+ return log_oom();
+
+ r = safe_atou(t, &cpu);
+ free(t);
+
+ if (!c)
+ if (!(c = cpu_set_malloc(&ncpus)))
+ return log_oom();
+
+ if (r < 0 || cpu >= ncpus) {
+ log_error("[%s:%u] Failed to parse CPU affinity: %s", filename, line, rvalue);
+ CPU_FREE(c);
+ return -EBADMSG;
+ }
+
+ CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c);
+ }
+
+ if (c) {
+ if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0)
+ log_warning("Failed to set CPU affinity: %m");
+
+ CPU_FREE(c);
+ }
+
+ return 0;
+}
+
+static void strv_free_free(char ***l) {
+ char ***i;
+
+ if (!l)
+ return;
+
+ for (i = l; *i; i++)
+ strv_free(*i);
+
+ free(l);
+}
+
+static void free_join_controllers(void) {
+ if (!arg_join_controllers)
+ return;
+
+ strv_free_free(arg_join_controllers);
+ arg_join_controllers = NULL;
+}
+
+static int config_parse_join_controllers(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ unsigned n = 0;
+ char *state, *w;
+ size_t length;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ free_join_controllers();
+
+ FOREACH_WORD_QUOTED(w, length, rvalue, state) {
+ char *s, **l;
+
+ s = strndup(w, length);
+ if (!s)
+ return log_oom();
+
+ l = strv_split(s, ",");
+ free(s);
+
+ strv_uniq(l);
+
+ if (strv_length(l) <= 1) {
+ strv_free(l);
+ continue;
+ }
+
+ if (!arg_join_controllers) {
+ arg_join_controllers = new(char**, 2);
+ if (!arg_join_controllers) {
+ strv_free(l);
+ return log_oom();
+ }
+
+ arg_join_controllers[0] = l;
+ arg_join_controllers[1] = NULL;
+
+ n = 1;
+ } else {
+ char ***a;
+ char ***t;
+
+ t = new0(char**, n+2);
+ if (!t) {
+ strv_free(l);
+ return log_oom();
+ }
+
+ n = 0;
+
+ for (a = arg_join_controllers; *a; a++) {
+
+ if (strv_overlap(*a, l)) {
+ char **c;
+
+ c = strv_merge(*a, l);
+ if (!c) {
+ strv_free(l);
+ strv_free_free(t);
+ return log_oom();
+ }
+
+ strv_free(l);
+ l = c;
+ } else {
+ char **c;
+
+ c = strv_copy(*a);
+ if (!c) {
+ strv_free(l);
+ strv_free_free(t);
+ return log_oom();
+ }
+
+ t[n++] = c;
+ }
+ }
+
+ t[n++] = strv_uniq(l);
+
+ strv_free_free(arg_join_controllers);
+ arg_join_controllers = t;
+ }
+ }
+
+ return 0;
+}
+
+static int parse_config_file(void) {
+
+ const ConfigTableItem items[] = {
+ { "Manager", "LogLevel", config_parse_level2, 0, NULL },
+ { "Manager", "LogTarget", config_parse_target, 0, NULL },
+ { "Manager", "LogColor", config_parse_color, 0, NULL },
+ { "Manager", "LogLocation", config_parse_location, 0, NULL },
+ { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core },
+ { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell },
+ { "Manager", "ShowStatus", config_parse_bool, 0, &arg_show_status },
+ { "Manager", "CrashChVT", config_parse_int, 0, &arg_crash_chvt },
+ { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL },
+ { "Manager", "DefaultControllers", config_parse_strv, 0, &arg_default_controllers },
+ { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output },
+ { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error },
+ { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers },
+ { "Manager", "RuntimeWatchdogSec", config_parse_usec, 0, &arg_runtime_watchdog },
+ { "Manager", "ShutdownWatchdogSec", config_parse_usec, 0, &arg_shutdown_watchdog },
+ { "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop },
+ { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec },
+ { "Manager", "DefaultLimitCPU", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_CPU]},
+ { "Manager", "DefaultLimitFSIZE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_FSIZE]},
+ { "Manager", "DefaultLimitDATA", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_DATA]},
+ { "Manager", "DefaultLimitSTACK", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_STACK]},
+ { "Manager", "DefaultLimitCORE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_CORE]},
+ { "Manager", "DefaultLimitRSS", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_RSS]},
+ { "Manager", "DefaultLimitNOFILE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_NOFILE]},
+ { "Manager", "DefaultLimitAS", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_AS]},
+ { "Manager", "DefaultLimitNPROC", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_NPROC]},
+ { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_MEMLOCK]},
+ { "Manager", "DefaultLimitLOCKS", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_LOCKS]},
+ { "Manager", "DefaultLimitSIGPENDING",config_parse_limit, 0, &arg_default_rlimit[RLIMIT_SIGPENDING]},
+ { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_MSGQUEUE]},
+ { "Manager", "DefaultLimitNICE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_NICE]},
+ { "Manager", "DefaultLimitRTPRIO", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_RTPRIO]},
+ { "Manager", "DefaultLimitRTTIME", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_RTTIME]},
+ { NULL, NULL, NULL, 0, NULL }
+ };
+
+ FILE *f;
+ const char *fn;
+ int r;
+
+ fn = arg_running_as == SYSTEMD_SYSTEM ? SYSTEM_CONFIG_FILE : USER_CONFIG_FILE;
+ f = fopen(fn, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+
+ log_warning("Failed to open configuration file '%s': %m", fn);
+ return 0;
+ }
+
+ r = config_parse(fn, f, "Manager\0", config_item_table_lookup, (void*) items, false, NULL);
+ if (r < 0)
+ log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+ fclose(f);
+
+ return 0;
+}
+
+static int parse_proc_cmdline(void) {
+ char *line, *w, *state;
+ int r;
+ size_t l;
+
+ /* Don't read /proc/cmdline if we are in a container, since
+ * that is only relevant for the host system */
+ if (detect_container(NULL) > 0)
+ return 0;
+
+ if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
+ log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+ return 0;
+ }
+
+ FOREACH_WORD_QUOTED(w, l, line, state) {
+ char *word;
+
+ if (!(word = strndup(w, l))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = parse_proc_cmdline_word(word);
+ if (r < 0) {
+ log_error("Failed on cmdline argument %s: %s", word, strerror(-r));
+ free(word);
+ goto finish;
+ }
+
+ free(word);
+ }
+
+ r = 0;
+
+finish:
+ free(line);
+ return r;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_LOG_LEVEL = 0x100,
+ ARG_LOG_TARGET,
+ ARG_LOG_COLOR,
+ ARG_LOG_LOCATION,
+ ARG_UNIT,
+ ARG_SYSTEM,
+ ARG_USER,
+ ARG_TEST,
+ ARG_VERSION,
+ ARG_DUMP_CONFIGURATION_ITEMS,
+ ARG_DUMP_CORE,
+ ARG_CRASH_SHELL,
+ ARG_CONFIRM_SPAWN,
+ ARG_SHOW_STATUS,
+ ARG_DESERIALIZE,
+ ARG_SWITCHED_ROOT,
+ ARG_INTROSPECT,
+ ARG_DEFAULT_STD_OUTPUT,
+ ARG_DEFAULT_STD_ERROR
+ };
+
+ static const struct option options[] = {
+ { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
+ { "log-target", required_argument, NULL, ARG_LOG_TARGET },
+ { "log-color", optional_argument, NULL, ARG_LOG_COLOR },
+ { "log-location", optional_argument, NULL, ARG_LOG_LOCATION },
+ { "unit", required_argument, NULL, ARG_UNIT },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "user", no_argument, NULL, ARG_USER },
+ { "test", no_argument, NULL, ARG_TEST },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS },
+ { "dump-core", optional_argument, NULL, ARG_DUMP_CORE },
+ { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL },
+ { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN },
+ { "show-status", optional_argument, NULL, ARG_SHOW_STATUS },
+ { "deserialize", required_argument, NULL, ARG_DESERIALIZE },
+ { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT },
+ { "introspect", optional_argument, NULL, ARG_INTROSPECT },
+ { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, },
+ { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int c, r;
+
+ assert(argc >= 1);
+ assert(argv);
+
+ if (getpid() == 1)
+ opterr = 0;
+
+ while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case ARG_LOG_LEVEL:
+ if ((r = log_set_max_level_from_string(optarg)) < 0) {
+ log_error("Failed to parse log level %s.", optarg);
+ return r;
+ }
+
+ break;
+
+ case ARG_LOG_TARGET:
+
+ if ((r = log_set_target_from_string(optarg)) < 0) {
+ log_error("Failed to parse log target %s.", optarg);
+ return r;
+ }
+
+ break;
+
+ case ARG_LOG_COLOR:
+
+ if (optarg) {
+ if ((r = log_show_color_from_string(optarg)) < 0) {
+ log_error("Failed to parse log color setting %s.", optarg);
+ return r;
+ }
+ } else
+ log_show_color(true);
+
+ break;
+
+ case ARG_LOG_LOCATION:
+
+ if (optarg) {
+ if ((r = log_show_location_from_string(optarg)) < 0) {
+ log_error("Failed to parse log location setting %s.", optarg);
+ return r;
+ }
+ } else
+ log_show_location(true);
+
+ break;
+
+ case ARG_DEFAULT_STD_OUTPUT:
+
+ if ((r = exec_output_from_string(optarg)) < 0) {
+ log_error("Failed to parse default standard output setting %s.", optarg);
+ return r;
+ } else
+ arg_default_std_output = r;
+ break;
+
+ case ARG_DEFAULT_STD_ERROR:
+
+ if ((r = exec_output_from_string(optarg)) < 0) {
+ log_error("Failed to parse default standard error output setting %s.", optarg);
+ return r;
+ } else
+ arg_default_std_error = r;
+ break;
+
+ case ARG_UNIT:
+
+ if ((r = set_default_unit(optarg)) < 0) {
+ log_error("Failed to set default unit %s: %s", optarg, strerror(-r));
+ return r;
+ }
+
+ break;
+
+ case ARG_SYSTEM:
+ arg_running_as = SYSTEMD_SYSTEM;
+ break;
+
+ case ARG_USER:
+ arg_running_as = SYSTEMD_USER;
+ break;
+
+ case ARG_TEST:
+ arg_action = ACTION_TEST;
+ break;
+
+ case ARG_VERSION:
+ arg_action = ACTION_VERSION;
+ break;
+
+ case ARG_DUMP_CONFIGURATION_ITEMS:
+ arg_action = ACTION_DUMP_CONFIGURATION_ITEMS;
+ break;
+
+ case ARG_DUMP_CORE:
+ r = optarg ? parse_boolean(optarg) : 1;
+ if (r < 0) {
+ log_error("Failed to parse dump core boolean %s.", optarg);
+ return r;
+ }
+ arg_dump_core = r;
+ break;
+
+ case ARG_CRASH_SHELL:
+ r = optarg ? parse_boolean(optarg) : 1;
+ if (r < 0) {
+ log_error("Failed to parse crash shell boolean %s.", optarg);
+ return r;
+ }
+ arg_crash_shell = r;
+ break;
+
+ case ARG_CONFIRM_SPAWN:
+ r = optarg ? parse_boolean(optarg) : 1;
+ if (r < 0) {
+ log_error("Failed to parse confirm spawn boolean %s.", optarg);
+ return r;
+ }
+ arg_confirm_spawn = r;
+ break;
+
+ case ARG_SHOW_STATUS:
+ r = optarg ? parse_boolean(optarg) : 1;
+ if (r < 0) {
+ log_error("Failed to parse show status boolean %s.", optarg);
+ return r;
+ }
+ arg_show_status = r;
+ break;
+
+ case ARG_DESERIALIZE: {
+ int fd;
+ FILE *f;
+
+ if ((r = safe_atoi(optarg, &fd)) < 0 || fd < 0) {
+ log_error("Failed to parse deserialize option %s.", optarg);
+ return r;
+ }
+
+ if (!(f = fdopen(fd, "r"))) {
+ log_error("Failed to open serialization fd: %m");
+ return r;
+ }
+
+ if (serialization)
+ fclose(serialization);
+
+ serialization = f;
+
+ break;
+ }
+
+ case ARG_SWITCHED_ROOT:
+ arg_switched_root = true;
+ break;
+
+ case ARG_INTROSPECT: {
+ const char * const * i = NULL;
+
+ for (i = bus_interface_table; *i; i += 2)
+ if (!optarg || streq(i[0], optarg)) {
+ fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>\n", stdout);
+ fputs(i[1], stdout);
+ fputs("</node>\n", stdout);
+
+ if (optarg)
+ break;
+ }
+
+ if (!i[0] && optarg)
+ log_error("Unknown interface %s.", optarg);
+
+ arg_action = ACTION_DONE;
+ break;
+ }
+
+ case 'h':
+ arg_action = ACTION_HELP;
+ break;
+
+ case 'D':
+ log_set_max_level(LOG_DEBUG);
+ break;
+
+ case 'b':
+ case 's':
+ case 'z':
+ /* Just to eat away the sysvinit kernel
+ * cmdline args without getopt() error
+ * messages that we'll parse in
+ * parse_proc_cmdline_word() or ignore. */
+
+ case '?':
+ default:
+ if (getpid() != 1) {
+ log_error("Unknown option code %c", c);
+ return -EINVAL;
+ }
+
+ break;
+ }
+
+ if (optind < argc && getpid() != 1) {
+ /* Hmm, when we aren't run as init system
+ * let's complain about excess arguments */
+
+ log_error("Excess arguments.");
+ return -EINVAL;
+ }
+
+ if (detect_container(NULL) > 0) {
+ char **a;
+
+ /* All /proc/cmdline arguments the kernel didn't
+ * understand it passed to us. We're not really
+ * interested in that usually since /proc/cmdline is
+ * more interesting and complete. With one exception:
+ * if we are run in a container /proc/cmdline is not
+ * relevant for the container, hence we rely on argv[]
+ * instead. */
+
+ for (a = argv; a < argv + argc; a++)
+ if ((r = parse_proc_cmdline_word(*a)) < 0) {
+ log_error("Failed on cmdline argument %s: %s", *a, strerror(-r));
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int help(void) {
+
+ printf("%s [OPTIONS...]\n\n"
+ "Starts up and maintains the system or user services.\n\n"
+ " -h --help Show this help\n"
+ " --test Determine startup sequence, dump it and exit\n"
+ " --dump-configuration-items Dump understood unit configuration items\n"
+ " --introspect[=INTERFACE] Extract D-Bus interface data\n"
+ " --unit=UNIT Set default unit\n"
+ " --system Run a system instance, even if PID != 1\n"
+ " --user Run a user instance\n"
+ " --dump-core[=0|1] Dump core on crash\n"
+ " --crash-shell[=0|1] Run shell on crash\n"
+ " --confirm-spawn[=0|1] Ask for confirmation when spawning processes\n"
+ " --show-status[=0|1] Show status updates on the console during bootup\n"
+ " --log-target=TARGET Set log target (console, journal, syslog, kmsg, journal-or-kmsg, syslog-or-kmsg, null)\n"
+ " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n"
+ " --log-color[=0|1] Highlight important log messages\n"
+ " --log-location[=0|1] Include code location in log messages\n"
+ " --default-standard-output= Set default standard output for services\n"
+ " --default-standard-error= Set default standard error output for services\n",
+ program_invocation_short_name);
+
+ return 0;
+}
+
+static int version(void) {
+ puts(PACKAGE_STRING);
+ puts(DISTRIBUTION);
+ puts(SYSTEMD_FEATURES);
+
+ return 0;
+}
+
+static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool serialize_jobs) {
+ FILE *f = NULL;
+ FDSet *fds = NULL;
+ int r;
+
+ assert(m);
+ assert(_f);
+ assert(_fds);
+
+ /* Make sure nothing is really destructed when we shut down */
+ m->n_reloading ++;
+
+ r = manager_open_serialization(m, &f);
+ if (r < 0) {
+ log_error("Failed to create serialization file: %s", strerror(-r));
+ goto fail;
+ }
+
+ fds = fdset_new();
+ if (!fds) {
+ r = -ENOMEM;
+ log_error("Failed to allocate fd set: %s", strerror(-r));
+ goto fail;
+ }
+
+ r = manager_serialize(m, f, fds, serialize_jobs);
+ if (r < 0) {
+ log_error("Failed to serialize state: %s", strerror(-r));
+ goto fail;
+ }
+
+ if (fseeko(f, 0, SEEK_SET) < 0) {
+ log_error("Failed to rewind serialization fd: %m");
+ goto fail;
+ }
+
+ r = fd_cloexec(fileno(f), false);
+ if (r < 0) {
+ log_error("Failed to disable O_CLOEXEC for serialization: %s", strerror(-r));
+ goto fail;
+ }
+
+ r = fdset_cloexec(fds, false);
+ if (r < 0) {
+ log_error("Failed to disable O_CLOEXEC for serialization fds: %s", strerror(-r));
+ goto fail;
+ }
+
+ *_f = f;
+ *_fds = fds;
+
+ return 0;
+
+fail:
+ fdset_free(fds);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+static int bump_rlimit_nofile(struct rlimit *saved_rlimit) {
+ struct rlimit nl;
+ int r;
+
+ assert(saved_rlimit);
+
+ /* Save the original RLIMIT_NOFILE so that we can reset it
+ * later when transitioning from the initrd to the main
+ * systemd or suchlike. */
+ if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) {
+ log_error("Reading RLIMIT_NOFILE failed: %m");
+ return -errno;
+ }
+
+ /* Make sure forked processes get the default kernel setting */
+ if (!arg_default_rlimit[RLIMIT_NOFILE]) {
+ struct rlimit *rl;
+
+ rl = newdup(struct rlimit, saved_rlimit, 1);
+ if (!rl)
+ return log_oom();
+
+ arg_default_rlimit[RLIMIT_NOFILE] = rl;
+ }
+
+ /* Bump up the resource limit for ourselves substantially */
+ nl.rlim_cur = nl.rlim_max = 64*1024;
+ r = setrlimit_closest(RLIMIT_NOFILE, &nl);
+ if (r < 0) {
+ log_error("Setting RLIMIT_NOFILE failed: %s", strerror(-r));
+ return r;
+ }
+
+ return 0;
+}
+
+static struct dual_timestamp* parse_initrd_timestamp(struct dual_timestamp *t) {
+ const char *e;
+ unsigned long long a, b;
+
+ assert(t);
+
+ e = getenv("RD_TIMESTAMP");
+ if (!e)
+ return NULL;
+
+ if (sscanf(e, "%llu %llu", &a, &b) != 2)
+ return NULL;
+
+ t->realtime = (usec_t) a;
+ t->monotonic = (usec_t) b;
+
+ return t;
+}
+
+static void test_mtab(void) {
+ char *p;
+
+ /* Check that /etc/mtab is a symlink */
+
+ if (readlink_malloc("/etc/mtab", &p) >= 0) {
+ bool b;
+
+ b = streq(p, "/proc/self/mounts") || streq(p, "/proc/mounts");
+ free(p);
+
+ if (b)
+ return;
+ }
+
+ log_warning("/etc/mtab is not a symlink or not pointing to /proc/self/mounts. "
+ "This is not supported anymore. "
+ "Please make sure to replace this file by a symlink to avoid incorrect or misleading mount(8) output.");
+}
+
+static void test_usr(void) {
+
+ /* Check that /usr is not a separate fs */
+
+ if (dir_is_empty("/usr") <= 0)
+ return;
+
+ log_warning("/usr appears to be on its own filesytem and is not already mounted. This is not a supported setup. "
+ "Some things will probably break (sometimes even silently) in mysterious ways. "
+ "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information.");
+}
+
+static void test_cgroups(void) {
+
+ if (access("/proc/cgroups", F_OK) >= 0)
+ return;
+
+ log_warning("CONFIG_CGROUPS was not set when your kernel was compiled. "
+ "Systems without control groups are not supported. "
+ "We will now sleep for 10s, and then continue boot-up. "
+ "Expect breakage and please do not file bugs. "
+ "Instead fix your kernel and enable CONFIG_CGROUPS. "
+ "Consult http://0pointer.de/blog/projects/cgroups-vs-cgroups.html for more information.");
+
+ sleep(10);
+}
+
+static int initialize_join_controllers(void) {
+ /* By default, mount "cpu" + "cpuacct" together, and "net_cls"
+ * + "net_prio". We'd like to add "cpuset" to the mix, but
+ * "cpuset" does't really work for groups with no initialized
+ * attributes. */
+
+ arg_join_controllers = new(char**, 3);
+ if (!arg_join_controllers)
+ return -ENOMEM;
+
+ arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL);
+ if (!arg_join_controllers[0])
+ return -ENOMEM;
+
+ arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL);
+ if (!arg_join_controllers[1])
+ return -ENOMEM;
+
+ arg_join_controllers[2] = NULL;
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ Manager *m = NULL;
+ int r, retval = EXIT_FAILURE;
+ usec_t before_startup, after_startup;
+ char timespan[FORMAT_TIMESPAN_MAX];
+ FDSet *fds = NULL;
+ bool reexecute = false;
+ const char *shutdown_verb = NULL;
+ dual_timestamp initrd_timestamp = { 0ULL, 0ULL };
+ static char systemd[] = "systemd";
+ bool skip_setup = false;
+ int j;
+ bool loaded_policy = false;
+ bool arm_reboot_watchdog = false;
+ bool queue_default_job = false;
+ char *switch_root_dir = NULL, *switch_root_init = NULL;
+ static struct rlimit saved_rlimit_nofile = { 0, 0 };
+
+#ifdef HAVE_SYSV_COMPAT
+ if (getpid() != 1 && strstr(program_invocation_short_name, "init")) {
+ /* This is compatibility support for SysV, where
+ * calling init as a user is identical to telinit. */
+
+ errno = -ENOENT;
+ execv(SYSTEMCTL_BINARY_PATH, argv);
+ log_error("Failed to exec " SYSTEMCTL_BINARY_PATH ": %m");
+ return 1;
+ }
+#endif
+
+ /* Determine if this is a reexecution or normal bootup. We do
+ * the full command line parsing much later, so let's just
+ * have a quick peek here. */
+ for (j = 1; j < argc; j++)
+ if (streq(argv[j], "--deserialize")) {
+ skip_setup = true;
+ break;
+ }
+
+ /* If we have switched root, do all the special setup
+ * things */
+ for (j = 1; j < argc; j++)
+ if (streq(argv[j], "--switched-root")) {
+ skip_setup = false;
+ break;
+ }
+
+ /* If we get started via the /sbin/init symlink then we are
+ called 'init'. After a subsequent reexecution we are then
+ called 'systemd'. That is confusing, hence let's call us
+ systemd right-away. */
+ program_invocation_short_name = systemd;
+ prctl(PR_SET_NAME, systemd);
+
+ saved_argv = argv;
+ saved_argc = argc;
+
+ log_show_color(isatty(STDERR_FILENO) > 0);
+
+ if (getpid() == 1 && detect_container(NULL) <= 0) {
+
+ /* Running outside of a container as PID 1 */
+ arg_running_as = SYSTEMD_SYSTEM;
+ make_null_stdio();
+ log_set_target(LOG_TARGET_KMSG);
+ log_open();
+
+ if (in_initrd()) {
+ char *rd_timestamp = NULL;
+
+ dual_timestamp_get(&initrd_timestamp);
+ asprintf(&rd_timestamp, "%llu %llu",
+ (unsigned long long) initrd_timestamp.realtime,
+ (unsigned long long) initrd_timestamp.monotonic);
+ if (rd_timestamp) {
+ setenv("RD_TIMESTAMP", rd_timestamp, 1);
+ free(rd_timestamp);
+ }
+ }
+
+ if (!skip_setup) {
+ if (selinux_setup(&loaded_policy) < 0)
+ goto finish;
+ if (ima_setup() < 0)
+ goto finish;
+ }
+
+ if (label_init(NULL) < 0)
+ goto finish;
+
+ if (!skip_setup) {
+ if (hwclock_is_localtime() > 0) {
+ int min;
+
+ /* The first-time call to settimeofday() does a time warp in the kernel */
+ r = hwclock_set_timezone(&min);
+ if (r < 0)
+ log_error("Failed to apply local time delta, ignoring: %s", strerror(-r));
+ else
+ log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min);
+ } else if (!in_initrd()) {
+ /*
+ * Do dummy first-time call to seal the kernel's time warp magic
+ *
+ * Do not call this this from inside the initrd. The initrd might not
+ * carry /etc/adjtime with LOCAL, but the real system could be set up
+ * that way. In such case, we need to delay the time-warp or the sealing
+ * until we reach the real system.
+ */
+ hwclock_reset_timezone();
+
+ /* Tell the kernel our time zone */
+ r = hwclock_set_timezone(NULL);
+ if (r < 0)
+ log_error("Failed to set the kernel's time zone, ignoring: %s", strerror(-r));
+ }
+ }
+
+ /* Set the default for later on, but don't actually
+ * open the logs like this for now. Note that if we
+ * are transitioning from the initrd there might still
+ * be journal fd open, and we shouldn't attempt
+ * opening that before we parsed /proc/cmdline which
+ * might redirect output elsewhere. */
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+
+ } else if (getpid() == 1) {
+
+ /* Running inside a container, as PID 1 */
+ arg_running_as = SYSTEMD_SYSTEM;
+ log_set_target(LOG_TARGET_CONSOLE);
+ log_open();
+
+ /* For the later on, see above... */
+ log_set_target(LOG_TARGET_JOURNAL);
+
+ } else {
+
+ /* Running as user instance */
+ arg_running_as = SYSTEMD_USER;
+ log_set_target(LOG_TARGET_AUTO);
+ log_open();
+ }
+
+ /* Initialize default unit */
+ r = set_default_unit(SPECIAL_DEFAULT_TARGET);
+ if (r < 0) {
+ log_error("Failed to set default unit %s: %s", SPECIAL_DEFAULT_TARGET, strerror(-r));
+ goto finish;
+ }
+
+ r = initialize_join_controllers();
+ if (r < 0)
+ goto finish;
+
+ /* Mount /proc, /sys and friends, so that /proc/cmdline and
+ * /proc/$PID/fd is available. */
+ if (geteuid() == 0 && !getenv("SYSTEMD_SKIP_API_MOUNTS")) {
+ r = mount_setup(loaded_policy);
+ if (r < 0)
+ goto finish;
+ }
+
+ /* Reset all signal handlers. */
+ assert_se(reset_all_signal_handlers() == 0);
+
+ /* If we are init, we can block sigkill. Yay. */
+ ignore_signals(SIGNALS_IGNORE, -1);
+
+ if (parse_config_file() < 0)
+ goto finish;
+
+ if (arg_running_as == SYSTEMD_SYSTEM)
+ if (parse_proc_cmdline() < 0)
+ goto finish;
+
+ log_parse_environment();
+
+ if (parse_argv(argc, argv) < 0)
+ goto finish;
+
+ if (arg_action == ACTION_TEST &&
+ geteuid() == 0) {
+ log_error("Don't run test mode as root.");
+ goto finish;
+ }
+
+ if (arg_running_as == SYSTEMD_USER &&
+ arg_action == ACTION_RUN &&
+ sd_booted() <= 0) {
+ log_error("Trying to run as user instance, but the system has not been booted with systemd.");
+ goto finish;
+ }
+
+ if (arg_running_as == SYSTEMD_SYSTEM &&
+ arg_action == ACTION_RUN &&
+ running_in_chroot() > 0) {
+ log_error("Cannot be run in a chroot() environment.");
+ goto finish;
+ }
+
+ if (arg_action == ACTION_HELP) {
+ retval = help();
+ goto finish;
+ } else if (arg_action == ACTION_VERSION) {
+ retval = version();
+ goto finish;
+ } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) {
+ unit_dump_config_items(stdout);
+ retval = EXIT_SUCCESS;
+ goto finish;
+ } else if (arg_action == ACTION_DONE) {
+ retval = EXIT_SUCCESS;
+ goto finish;
+ }
+
+ assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST);
+
+ /* Close logging fds, in order not to confuse fdset below */
+ log_close();
+
+ /* Remember open file descriptors for later deserialization */
+ if (serialization) {
+ r = fdset_new_fill(&fds);
+ if (r < 0) {
+ log_error("Failed to allocate fd set: %s", strerror(-r));
+ goto finish;
+ }
+
+ assert_se(fdset_remove(fds, fileno(serialization)) >= 0);
+ } else
+ close_all_fds(NULL, 0);
+
+ /* Set up PATH unless it is already set */
+ setenv("PATH",
+#ifdef HAVE_SPLIT_USR
+ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+#else
+ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin",
+#endif
+ arg_running_as == SYSTEMD_SYSTEM);
+
+ if (arg_running_as == SYSTEMD_SYSTEM) {
+ /* Parse the data passed to us. We leave this
+ * variables set, but the manager later on will not
+ * pass them on to our children. */
+ if (!in_initrd())
+ parse_initrd_timestamp(&initrd_timestamp);
+
+ /* Unset some environment variables passed in from the
+ * kernel that don't really make sense for us. */
+ unsetenv("HOME");
+ unsetenv("TERM");
+
+ /* When we are invoked by a shell, these might be set,
+ * but make little sense to pass on */
+ unsetenv("PWD");
+ unsetenv("SHLVL");
+ unsetenv("_");
+
+ /* When we are invoked by a chroot-like tool such as
+ * nspawn, these might be set, but make little sense
+ * to pass on */
+ unsetenv("USER");
+ unsetenv("LOGNAME");
+
+ /* All other variables are left as is, so that clients
+ * can still read them via /proc/1/environ */
+ }
+
+ /* Move out of the way, so that we won't block unmounts */
+ assert_se(chdir("/") == 0);
+
+ if (arg_running_as == SYSTEMD_SYSTEM) {
+ /* Become a session leader if we aren't one yet. */
+ setsid();
+
+ /* Disable the umask logic */
+ umask(0);
+ }
+
+ /* Make sure D-Bus doesn't fiddle with the SIGPIPE handlers */
+ dbus_connection_set_change_sigpipe(FALSE);
+
+ /* Reset the console, but only if this is really init and we
+ * are freshly booted */
+ if (arg_running_as == SYSTEMD_SYSTEM && arg_action == ACTION_RUN)
+ console_setup(getpid() == 1 && !skip_setup);
+
+ /* Open the logging devices, if possible and necessary */
+ log_open();
+
+ /* Make sure we leave a core dump without panicing the
+ * kernel. */
+ if (getpid() == 1)
+ install_crash_handler();
+
+ if (geteuid() == 0 && !getenv("SYSTEMD_SKIP_API_MOUNTS")) {
+ r = mount_cgroup_controllers(arg_join_controllers);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (arg_running_as == SYSTEMD_SYSTEM) {
+ const char *virtualization = NULL;
+
+ log_info(PACKAGE_STRING " running in system mode. (" SYSTEMD_FEATURES "; " DISTRIBUTION ")");
+
+ detect_virtualization(&virtualization);
+ if (virtualization)
+ log_info("Detected virtualization '%s'.", virtualization);
+
+ if (in_initrd())
+ log_info("Running in initial RAM disk.");
+
+ } else
+ log_debug(PACKAGE_STRING " running in user mode. (" SYSTEMD_FEATURES "; " DISTRIBUTION ")");
+
+ if (arg_running_as == SYSTEMD_SYSTEM && !skip_setup) {
+ locale_setup();
+
+ if (arg_show_status || plymouth_running())
+ status_welcome();
+
+ kmod_setup();
+ hostname_setup();
+ machine_id_setup();
+ loopback_setup();
+
+ test_mtab();
+ test_usr();
+ test_cgroups();
+ }
+
+ if (arg_running_as == SYSTEMD_SYSTEM && arg_runtime_watchdog > 0)
+ watchdog_set_timeout(&arg_runtime_watchdog);
+
+ if (arg_timer_slack_nsec != (nsec_t) -1)
+ if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0)
+ log_error("Failed to adjust timer slack: %m");
+
+ if (arg_capability_bounding_set_drop) {
+ r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true);
+ if (r < 0) {
+ log_error("Failed to drop capability bounding set: %s", strerror(-r));
+ goto finish;
+ }
+ r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop);
+ if (r < 0) {
+ log_error("Failed to drop capability bounding set of usermode helpers: %s", strerror(-r));
+ goto finish;
+ }
+ }
+
+ if (arg_running_as == SYSTEMD_USER) {
+ /* Become reaper of our children */
+ if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) {
+ log_warning("Failed to make us a subreaper: %m");
+ if (errno == EINVAL)
+ log_info("Perhaps the kernel version is too old (< 3.4?)");
+ }
+ }
+
+ if (arg_running_as == SYSTEMD_SYSTEM)
+ bump_rlimit_nofile(&saved_rlimit_nofile);
+
+ r = manager_new(arg_running_as, &m);
+ if (r < 0) {
+ log_error("Failed to allocate manager object: %s", strerror(-r));
+ goto finish;
+ }
+
+ m->confirm_spawn = arg_confirm_spawn;
+ m->default_std_output = arg_default_std_output;
+ m->default_std_error = arg_default_std_error;
+ m->runtime_watchdog = arg_runtime_watchdog;
+ m->shutdown_watchdog = arg_shutdown_watchdog;
+
+ manager_set_default_rlimits(m, arg_default_rlimit);
+
+ if (dual_timestamp_is_set(&initrd_timestamp))
+ m->initrd_timestamp = initrd_timestamp;
+
+ if (arg_default_controllers)
+ manager_set_default_controllers(m, arg_default_controllers);
+
+ manager_set_show_status(m, arg_show_status);
+
+ /* Remember whether we should queue the default job */
+ queue_default_job = !serialization || arg_switched_root;
+
+ before_startup = now(CLOCK_MONOTONIC);
+
+ r = manager_startup(m, serialization, fds);
+ if (r < 0)
+ log_error("Failed to fully start up daemon: %s", strerror(-r));
+
+ /* This will close all file descriptors that were opened, but
+ * not claimed by any unit. */
+ if (fds) {
+ fdset_free(fds);
+ fds = NULL;
+ }
+
+ if (serialization) {
+ fclose(serialization);
+ serialization = NULL;
+ }
+
+ if (queue_default_job) {
+ DBusError error;
+ Unit *target = NULL;
+ Job *default_unit_job;
+
+ dbus_error_init(&error);
+
+ log_debug("Activating default unit: %s", arg_default_unit);
+
+ r = manager_load_unit(m, arg_default_unit, NULL, &error, &target);
+ if (r < 0) {
+ log_error("Failed to load default target: %s", bus_error(&error, r));
+ dbus_error_free(&error);
+ } else if (target->load_state == UNIT_ERROR)
+ log_error("Failed to load default target: %s", strerror(-target->load_error));
+ else if (target->load_state == UNIT_MASKED)
+ log_error("Default target masked.");
+
+ if (!target || target->load_state != UNIT_LOADED) {
+ log_info("Trying to load rescue target...");
+
+ r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target);
+ if (r < 0) {
+ log_error("Failed to load rescue target: %s", bus_error(&error, r));
+ dbus_error_free(&error);
+ goto finish;
+ } else if (target->load_state == UNIT_ERROR) {
+ log_error("Failed to load rescue target: %s", strerror(-target->load_error));
+ goto finish;
+ } else if (target->load_state == UNIT_MASKED) {
+ log_error("Rescue target masked.");
+ goto finish;
+ }
+ }
+
+ assert(target->load_state == UNIT_LOADED);
+
+ if (arg_action == ACTION_TEST) {
+ printf("-> By units:\n");
+ manager_dump_units(m, stdout, "\t");
+ }
+
+ r = manager_add_job(m, JOB_START, target, JOB_REPLACE, false, &error, &default_unit_job);
+ if (r < 0) {
+ log_error("Failed to start default target: %s", bus_error(&error, r));
+ dbus_error_free(&error);
+ goto finish;
+ }
+ m->default_unit_job_id = default_unit_job->id;
+
+ after_startup = now(CLOCK_MONOTONIC);
+ log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG,
+ "Loaded units and determined initial transaction in %s.",
+ format_timespan(timespan, sizeof(timespan), after_startup - before_startup));
+
+ if (arg_action == ACTION_TEST) {
+ printf("-> By jobs:\n");
+ manager_dump_jobs(m, stdout, "\t");
+ retval = EXIT_SUCCESS;
+ goto finish;
+ }
+ }
+
+ for (;;) {
+ r = manager_loop(m);
+ if (r < 0) {
+ log_error("Failed to run mainloop: %s", strerror(-r));
+ goto finish;
+ }
+
+ switch (m->exit_code) {
+
+ case MANAGER_EXIT:
+ retval = EXIT_SUCCESS;
+ log_debug("Exit.");
+ goto finish;
+
+ case MANAGER_RELOAD:
+ log_info("Reloading.");
+ r = manager_reload(m);
+ if (r < 0)
+ log_error("Failed to reload: %s", strerror(-r));
+ break;
+
+ case MANAGER_REEXECUTE:
+
+ if (prepare_reexecute(m, &serialization, &fds, true) < 0)
+ goto finish;
+
+ reexecute = true;
+ log_notice("Reexecuting.");
+ goto finish;
+
+ case MANAGER_SWITCH_ROOT:
+ /* Steal the switch root parameters */
+ switch_root_dir = m->switch_root;
+ switch_root_init = m->switch_root_init;
+ m->switch_root = m->switch_root_init = NULL;
+
+ if (!switch_root_init)
+ if (prepare_reexecute(m, &serialization, &fds, false) < 0)
+ goto finish;
+
+ reexecute = true;
+ log_notice("Switching root.");
+ goto finish;
+
+ case MANAGER_REBOOT:
+ case MANAGER_POWEROFF:
+ case MANAGER_HALT:
+ case MANAGER_KEXEC: {
+ static const char * const table[_MANAGER_EXIT_CODE_MAX] = {
+ [MANAGER_REBOOT] = "reboot",
+ [MANAGER_POWEROFF] = "poweroff",
+ [MANAGER_HALT] = "halt",
+ [MANAGER_KEXEC] = "kexec"
+ };
+
+ assert_se(shutdown_verb = table[m->exit_code]);
+ arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT;
+
+ log_notice("Shutting down.");
+ goto finish;
+ }
+
+ default:
+ assert_not_reached("Unknown exit code.");
+ }
+ }
+
+finish:
+ if (m)
+ manager_free(m);
+
+ for (j = 0; j < RLIMIT_NLIMITS; j++)
+ free(arg_default_rlimit[j]);
+
+ free(arg_default_unit);
+ strv_free(arg_default_controllers);
+ free_join_controllers();
+
+ dbus_shutdown();
+ label_finish();
+
+ if (reexecute) {
+ const char **args;
+ unsigned i, args_size;
+
+ /* Close and disarm the watchdog, so that the new
+ * instance can reinitialize it, but doesn't get
+ * rebooted while we do that */
+ watchdog_close(true);
+
+ /* Reset the RLIMIT_NOFILE to the kernel default, so
+ * that the new systemd can pass the kernel default to
+ * its child processes */
+ if (saved_rlimit_nofile.rlim_cur > 0)
+ setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile);
+
+ if (switch_root_dir) {
+ /* Kill all remaining processes from the
+ * initrd, but don't wait for them, so that we
+ * can handle the SIGCHLD for them after
+ * deserializing. */
+ broadcast_signal(SIGTERM, false);
+
+ /* And switch root */
+ r = switch_root(switch_root_dir);
+ if (r < 0)
+ log_error("Failed to switch root, ignoring: %s", strerror(-r));
+ }
+
+ args_size = MAX(6, argc+1);
+ args = newa(const char*, args_size);
+
+ if (!switch_root_init) {
+ char sfd[16];
+
+ /* First try to spawn ourselves with the right
+ * path, and with full serialization. We do
+ * this only if the user didn't specify an
+ * explicit init to spawn. */
+
+ assert(serialization);
+ assert(fds);
+
+ snprintf(sfd, sizeof(sfd), "%i", fileno(serialization));
+ char_array_0(sfd);
+
+ i = 0;
+ args[i++] = SYSTEMD_BINARY_PATH;
+ if (switch_root_dir)
+ args[i++] = "--switched-root";
+ args[i++] = arg_running_as == SYSTEMD_SYSTEM ? "--system" : "--user";
+ args[i++] = "--deserialize";
+ args[i++] = sfd;
+ args[i++] = NULL;
+
+ assert(i <= args_size);
+ execv(args[0], (char* const*) args);
+ }
+
+ /* Try the fallback, if there is any, without any
+ * serialization. We pass the original argv[] and
+ * envp[]. (Well, modulo the ordering changes due to
+ * getopt() in argv[], and some cleanups in envp[],
+ * but let's hope that doesn't matter.) */
+
+ if (serialization) {
+ fclose(serialization);
+ serialization = NULL;
+ }
+
+ if (fds) {
+ fdset_free(fds);
+ fds = NULL;
+ }
+
+ /* Reopen the console */
+ make_console_stdio();
+
+ for (j = 1, i = 1; j < argc; j++)
+ args[i++] = argv[j];
+ args[i++] = NULL;
+ assert(i <= args_size);
+
+ if (switch_root_init) {
+ args[0] = switch_root_init;
+ execv(args[0], (char* const*) args);
+ log_warning("Failed to execute configured init, trying fallback: %m");
+ }
+
+ args[0] = "/sbin/init";
+ execv(args[0], (char* const*) args);
+
+ if (errno == ENOENT) {
+ log_warning("No /sbin/init, trying fallback");
+
+ args[0] = "/bin/sh";
+ args[1] = NULL;
+ execv(args[0], (char* const*) args);
+ log_error("Failed to execute /bin/sh, giving up: %m");
+ } else
+ log_warning("Failed to execute /sbin/init, giving up: %m");
+ }
+
+ if (serialization)
+ fclose(serialization);
+
+ if (fds)
+ fdset_free(fds);
+
+ if (shutdown_verb) {
+ const char * command_line[] = {
+ SYSTEMD_SHUTDOWN_BINARY_PATH,
+ shutdown_verb,
+ NULL
+ };
+ char **env_block;
+
+ if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) {
+ char e[32];
+
+ /* If we reboot let's set the shutdown
+ * watchdog and tell the shutdown binary to
+ * repeatedly ping it */
+ watchdog_set_timeout(&arg_shutdown_watchdog);
+ watchdog_close(false);
+
+ /* Tell the binary how often to ping */
+ snprintf(e, sizeof(e), "WATCHDOG_USEC=%llu", (unsigned long long) arg_shutdown_watchdog);
+ char_array_0(e);
+
+ env_block = strv_append(environ, e);
+ } else {
+ env_block = strv_copy(environ);
+ watchdog_close(true);
+ }
+
+ execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block);
+ free(env_block);
+ log_error("Failed to execute shutdown binary, freezing: %m");
+ }
+
+ if (getpid() == 1)
+ freeze();
+
+ return retval;
+}
diff --git a/src/core/manager.c b/src/core/manager.c
new file mode 100644
index 0000000000..f932c79a1b
--- /dev/null
+++ b/src/core/manager.c
@@ -0,0 +1,2309 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/reboot.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+
+#include "systemd/sd-daemon.h"
+#include "systemd/sd-id128.h"
+#include "systemd/sd-messages.h"
+
+#include "manager.h"
+#include "transaction.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "strv.h"
+#include "log.h"
+#include "util.h"
+#include "mkdir.h"
+#include "ratelimit.h"
+#include "cgroup.h"
+#include "mount-setup.h"
+#include "unit-name.h"
+#include "dbus-unit.h"
+#include "dbus-job.h"
+#include "missing.h"
+#include "path-lookup.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "exit-status.h"
+#include "virt.h"
+#include "watchdog.h"
+#include "cgroup-util.h"
+#include "path-util.h"
+#include "audit-fd.h"
+
+/* As soon as 16 units are in our GC queue, make sure to run a gc sweep */
+#define GC_QUEUE_ENTRIES_MAX 16
+
+/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
+#define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC)
+
+/* Where clients shall send notification messages to */
+#define NOTIFY_SOCKET "@/org/freedesktop/systemd1/notify"
+
+static int manager_setup_notify(Manager *m) {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ } sa;
+ struct epoll_event ev;
+ int one = 1;
+
+ assert(m);
+
+ m->notify_watch.type = WATCH_NOTIFY;
+ m->notify_watch.fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->notify_watch.fd < 0) {
+ log_error("Failed to allocate notification socket: %m");
+ return -errno;
+ }
+
+ zero(sa);
+ sa.sa.sa_family = AF_UNIX;
+
+ if (getpid() != 1 || detect_container(NULL) > 0)
+ snprintf(sa.un.sun_path, sizeof(sa.un.sun_path), NOTIFY_SOCKET "/%llu", random_ull());
+ else
+ strncpy(sa.un.sun_path, NOTIFY_SOCKET, sizeof(sa.un.sun_path));
+
+ sa.un.sun_path[0] = 0;
+
+ if (bind(m->notify_watch.fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
+ log_error("bind() failed: %m");
+ return -errno;
+ }
+
+ if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
+ log_error("SO_PASSCRED failed: %m");
+ return -errno;
+ }
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.ptr = &m->notify_watch;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0)
+ return -errno;
+
+ sa.un.sun_path[0] = '@';
+ m->notify_socket = strdup(sa.un.sun_path);
+ if (!m->notify_socket)
+ return -ENOMEM;
+
+ log_debug("Using notification socket %s", m->notify_socket);
+
+ return 0;
+}
+
+static int enable_special_signals(Manager *m) {
+ int fd;
+
+ assert(m);
+
+ /* Enable that we get SIGINT on control-alt-del. In containers
+ * this will fail with EPERM (older) or EINVAL (newer), so
+ * ignore that. */
+ if (reboot(RB_DISABLE_CAD) < 0 && errno != EPERM && errno != EINVAL)
+ log_warning("Failed to enable ctrl-alt-del handling: %m");
+
+ fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0) {
+ /* Support systems without virtual console */
+ if (fd != -ENOENT)
+ log_warning("Failed to open /dev/tty0: %m");
+ } else {
+ /* Enable that we get SIGWINCH on kbrequest */
+ if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0)
+ log_warning("Failed to enable kbrequest handling: %s", strerror(errno));
+
+ close_nointr_nofail(fd);
+ }
+
+ return 0;
+}
+
+static int manager_setup_signals(Manager *m) {
+ sigset_t mask;
+ struct epoll_event ev;
+ struct sigaction sa;
+
+ assert(m);
+
+ /* We are not interested in SIGSTOP and friends. */
+ zero(sa);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = SA_NOCLDSTOP|SA_RESTART;
+ assert_se(sigaction(SIGCHLD, &sa, NULL) == 0);
+
+ assert_se(sigemptyset(&mask) == 0);
+
+ sigset_add_many(&mask,
+ SIGCHLD, /* Child died */
+ SIGTERM, /* Reexecute daemon */
+ SIGHUP, /* Reload configuration */
+ SIGUSR1, /* systemd/upstart: reconnect to D-Bus */
+ SIGUSR2, /* systemd: dump status */
+ SIGINT, /* Kernel sends us this on control-alt-del */
+ SIGWINCH, /* Kernel sends us this on kbrequest (alt-arrowup) */
+ SIGPWR, /* Some kernel drivers and upsd send us this on power failure */
+ SIGRTMIN+0, /* systemd: start default.target */
+ SIGRTMIN+1, /* systemd: isolate rescue.target */
+ SIGRTMIN+2, /* systemd: isolate emergency.target */
+ SIGRTMIN+3, /* systemd: start halt.target */
+ SIGRTMIN+4, /* systemd: start poweroff.target */
+ SIGRTMIN+5, /* systemd: start reboot.target */
+ SIGRTMIN+6, /* systemd: start kexec.target */
+ SIGRTMIN+13, /* systemd: Immediate halt */
+ SIGRTMIN+14, /* systemd: Immediate poweroff */
+ SIGRTMIN+15, /* systemd: Immediate reboot */
+ SIGRTMIN+16, /* systemd: Immediate kexec */
+ SIGRTMIN+20, /* systemd: enable status messages */
+ SIGRTMIN+21, /* systemd: disable status messages */
+ SIGRTMIN+22, /* systemd: set log level to LOG_DEBUG */
+ SIGRTMIN+23, /* systemd: set log level to LOG_INFO */
+ SIGRTMIN+24, /* systemd: Immediate exit (--user only) */
+ SIGRTMIN+26, /* systemd: set log target to journal-or-kmsg */
+ SIGRTMIN+27, /* systemd: set log target to console */
+ SIGRTMIN+28, /* systemd: set log target to kmsg */
+ SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg */
+ -1);
+ assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+ m->signal_watch.type = WATCH_SIGNAL;
+ if ((m->signal_watch.fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0)
+ return -errno;
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.ptr = &m->signal_watch;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->signal_watch.fd, &ev) < 0)
+ return -errno;
+
+ if (m->running_as == SYSTEMD_SYSTEM)
+ return enable_special_signals(m);
+
+ return 0;
+}
+
+static void manager_strip_environment(Manager *m) {
+ assert(m);
+
+ /* Remove variables from the inherited set that are part of
+ * the container interface:
+ * http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface */
+ strv_remove_prefix(m->environment, "container=");
+ strv_remove_prefix(m->environment, "container_");
+
+ /* Remove variables from the inherited set that are part of
+ * the initrd interface:
+ * http://www.freedesktop.org/wiki/Software/systemd/InitrdInterface */
+ strv_remove_prefix(m->environment, "RD_");
+}
+
+int manager_new(SystemdRunningAs running_as, Manager **_m) {
+ Manager *m;
+ int r = -ENOMEM;
+
+ assert(_m);
+ assert(running_as >= 0);
+ assert(running_as < _SYSTEMD_RUNNING_AS_MAX);
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ dual_timestamp_get(&m->userspace_timestamp);
+
+ m->running_as = running_as;
+ m->name_data_slot = m->conn_data_slot = m->subscribed_data_slot = -1;
+ m->exit_code = _MANAGER_EXIT_CODE_INVALID;
+ m->pin_cgroupfs_fd = -1;
+ m->idle_pipe[0] = m->idle_pipe[1] = -1;
+
+ m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = m->swap_watch.fd = -1;
+ m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
+
+ m->environment = strv_copy(environ);
+ if (!m->environment)
+ goto fail;
+
+ manager_strip_environment(m);
+
+ if (running_as == SYSTEMD_SYSTEM) {
+ m->default_controllers = strv_new("cpu", NULL);
+ if (!m->default_controllers)
+ goto fail;
+ }
+
+ if (!(m->units = hashmap_new(string_hash_func, string_compare_func)))
+ goto fail;
+
+ if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func)))
+ goto fail;
+
+ if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func)))
+ goto fail;
+
+ if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func)))
+ goto fail;
+
+ if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func)))
+ goto fail;
+
+ if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
+ goto fail;
+
+ if ((r = manager_setup_signals(m)) < 0)
+ goto fail;
+
+ if ((r = manager_setup_cgroup(m)) < 0)
+ goto fail;
+
+ if ((r = manager_setup_notify(m)) < 0)
+ goto fail;
+
+ /* Try to connect to the busses, if possible. */
+ if ((r = bus_init(m, running_as != SYSTEMD_SYSTEM)) < 0)
+ goto fail;
+
+ m->taint_usr = dir_is_empty("/usr") > 0;
+
+ *_m = m;
+ return 0;
+
+fail:
+ manager_free(m);
+ return r;
+}
+
+static unsigned manager_dispatch_cleanup_queue(Manager *m) {
+ Unit *u;
+ unsigned n = 0;
+
+ assert(m);
+
+ while ((u = m->cleanup_queue)) {
+ assert(u->in_cleanup_queue);
+
+ unit_free(u);
+ n++;
+ }
+
+ return n;
+}
+
+enum {
+ GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */
+ GC_OFFSET_UNSURE, /* No clue */
+ GC_OFFSET_GOOD, /* We still need this unit */
+ GC_OFFSET_BAD, /* We don't need this unit anymore */
+ _GC_OFFSET_MAX
+};
+
+static void unit_gc_sweep(Unit *u, unsigned gc_marker) {
+ Iterator i;
+ Unit *other;
+ bool is_bad;
+
+ assert(u);
+
+ if (u->gc_marker == gc_marker + GC_OFFSET_GOOD ||
+ u->gc_marker == gc_marker + GC_OFFSET_BAD ||
+ u->gc_marker == gc_marker + GC_OFFSET_IN_PATH)
+ return;
+
+ if (u->in_cleanup_queue)
+ goto bad;
+
+ if (unit_check_gc(u))
+ goto good;
+
+ u->gc_marker = gc_marker + GC_OFFSET_IN_PATH;
+
+ is_bad = true;
+
+ SET_FOREACH(other, u->dependencies[UNIT_REFERENCED_BY], i) {
+ unit_gc_sweep(other, gc_marker);
+
+ if (other->gc_marker == gc_marker + GC_OFFSET_GOOD)
+ goto good;
+
+ if (other->gc_marker != gc_marker + GC_OFFSET_BAD)
+ is_bad = false;
+ }
+
+ if (is_bad)
+ goto bad;
+
+ /* We were unable to find anything out about this entry, so
+ * let's investigate it later */
+ u->gc_marker = gc_marker + GC_OFFSET_UNSURE;
+ unit_add_to_gc_queue(u);
+ return;
+
+bad:
+ /* We definitely know that this one is not useful anymore, so
+ * let's mark it for deletion */
+ u->gc_marker = gc_marker + GC_OFFSET_BAD;
+ unit_add_to_cleanup_queue(u);
+ return;
+
+good:
+ u->gc_marker = gc_marker + GC_OFFSET_GOOD;
+}
+
+static unsigned manager_dispatch_gc_queue(Manager *m) {
+ Unit *u;
+ unsigned n = 0;
+ unsigned gc_marker;
+
+ assert(m);
+
+ if ((m->n_in_gc_queue < GC_QUEUE_ENTRIES_MAX) &&
+ (m->gc_queue_timestamp <= 0 ||
+ (m->gc_queue_timestamp + GC_QUEUE_USEC_MAX) > now(CLOCK_MONOTONIC)))
+ return 0;
+
+ log_debug("Running GC...");
+
+ m->gc_marker += _GC_OFFSET_MAX;
+ if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX)
+ m->gc_marker = 1;
+
+ gc_marker = m->gc_marker;
+
+ while ((u = m->gc_queue)) {
+ assert(u->in_gc_queue);
+
+ unit_gc_sweep(u, gc_marker);
+
+ LIST_REMOVE(Unit, gc_queue, m->gc_queue, u);
+ u->in_gc_queue = false;
+
+ n++;
+
+ if (u->gc_marker == gc_marker + GC_OFFSET_BAD ||
+ u->gc_marker == gc_marker + GC_OFFSET_UNSURE) {
+ log_debug("Collecting %s", u->id);
+ u->gc_marker = gc_marker + GC_OFFSET_BAD;
+ unit_add_to_cleanup_queue(u);
+ }
+ }
+
+ m->n_in_gc_queue = 0;
+ m->gc_queue_timestamp = 0;
+
+ return n;
+}
+
+static void manager_clear_jobs_and_units(Manager *m) {
+ Unit *u;
+
+ assert(m);
+
+ while ((u = hashmap_first(m->units)))
+ unit_free(u);
+
+ manager_dispatch_cleanup_queue(m);
+
+ assert(!m->load_queue);
+ assert(!m->run_queue);
+ assert(!m->dbus_unit_queue);
+ assert(!m->dbus_job_queue);
+ assert(!m->cleanup_queue);
+ assert(!m->gc_queue);
+
+ assert(hashmap_isempty(m->jobs));
+ assert(hashmap_isempty(m->units));
+}
+
+void manager_free(Manager *m) {
+ UnitType c;
+ int i;
+
+ assert(m);
+
+ manager_clear_jobs_and_units(m);
+
+ for (c = 0; c < _UNIT_TYPE_MAX; c++)
+ if (unit_vtable[c]->shutdown)
+ unit_vtable[c]->shutdown(m);
+
+ /* If we reexecute ourselves, we keep the root cgroup
+ * around */
+ manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE);
+
+ manager_undo_generators(m);
+
+ bus_done(m);
+
+ hashmap_free(m->units);
+ hashmap_free(m->jobs);
+ hashmap_free(m->watch_pids);
+ hashmap_free(m->watch_bus);
+
+ if (m->epoll_fd >= 0)
+ close_nointr_nofail(m->epoll_fd);
+ if (m->signal_watch.fd >= 0)
+ close_nointr_nofail(m->signal_watch.fd);
+ if (m->notify_watch.fd >= 0)
+ close_nointr_nofail(m->notify_watch.fd);
+
+ free(m->notify_socket);
+
+ lookup_paths_free(&m->lookup_paths);
+ strv_free(m->environment);
+
+ strv_free(m->default_controllers);
+
+ hashmap_free(m->cgroup_bondings);
+ set_free_free(m->unit_path_cache);
+
+ close_pipe(m->idle_pipe);
+
+ free(m->switch_root);
+ free(m->switch_root_init);
+
+ for (i = 0; i < RLIMIT_NLIMITS; i++)
+ free(m->rlimit[i]);
+
+ free(m);
+}
+
+int manager_enumerate(Manager *m) {
+ int r = 0, q;
+ UnitType c;
+
+ assert(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++)
+ if (unit_vtable[c]->enumerate)
+ if ((q = unit_vtable[c]->enumerate(m)) < 0)
+ r = q;
+
+ manager_dispatch_load_queue(m);
+ return r;
+}
+
+int manager_coldplug(Manager *m) {
+ int r = 0, q;
+ Iterator i;
+ Unit *u;
+ char *k;
+
+ assert(m);
+
+ /* Then, let's set up their initial state. */
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+
+ /* ignore aliases */
+ if (u->id != k)
+ continue;
+
+ if ((q = unit_coldplug(u)) < 0)
+ r = q;
+ }
+
+ return r;
+}
+
+static void manager_build_unit_path_cache(Manager *m) {
+ char **i;
+ DIR *d = NULL;
+ int r;
+
+ assert(m);
+
+ set_free_free(m->unit_path_cache);
+
+ if (!(m->unit_path_cache = set_new(string_hash_func, string_compare_func))) {
+ log_error("Failed to allocate unit path cache.");
+ return;
+ }
+
+ /* 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) {
+ struct dirent *de;
+
+ if (!(d = opendir(*i))) {
+ log_error("Failed to open directory: %m");
+ continue;
+ }
+
+ while ((de = readdir(d))) {
+ char *p;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ p = strjoin(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL);
+ if (!p) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if ((r = set_put(m->unit_path_cache, p)) < 0) {
+ free(p);
+ goto fail;
+ }
+ }
+
+ closedir(d);
+ d = NULL;
+ }
+
+ return;
+
+fail:
+ log_error("Failed to build unit path cache: %s", strerror(-r));
+
+ set_free_free(m->unit_path_cache);
+ m->unit_path_cache = NULL;
+
+ if (d)
+ closedir(d);
+}
+
+int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
+ int r, q;
+
+ assert(m);
+
+ manager_run_generators(m);
+
+ r = lookup_paths_init(
+ &m->lookup_paths, m->running_as, true,
+ m->generator_unit_path,
+ m->generator_unit_path_early,
+ m->generator_unit_path_late);
+ if (r < 0)
+ return r;
+
+ 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 ++;
+
+ /* First, enumerate what we can from all config files */
+ r = manager_enumerate(m);
+
+ /* Second, deserialize if there is something to deserialize */
+ if (serialization) {
+ q = manager_deserialize(m, serialization, fds);
+ if (q < 0)
+ r = q;
+ }
+
+ /* Third, fire things up! */
+ q = manager_coldplug(m);
+ if (q < 0)
+ r = q;
+
+ if (serialization) {
+ assert(m->n_reloading > 0);
+ m->n_reloading --;
+ }
+
+ return r;
+}
+
+int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) {
+ int r;
+ Transaction *tr;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(unit);
+ assert(mode < _JOB_MODE_MAX);
+
+ if (mode == JOB_ISOLATE && type != JOB_START) {
+ dbus_set_error(e, BUS_ERROR_INVALID_JOB_MODE, "Isolate is only valid for start.");
+ return -EINVAL;
+ }
+
+ if (mode == JOB_ISOLATE && !unit->allow_isolate) {
+ dbus_set_error(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated.");
+ return -EPERM;
+ }
+
+ log_debug("Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode));
+
+ job_type_collapse(&type, unit);
+
+ tr = transaction_new();
+ if (!tr)
+ return -ENOMEM;
+
+ r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, override, false,
+ mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS,
+ mode == JOB_IGNORE_DEPENDENCIES, e);
+ if (r < 0)
+ goto tr_abort;
+
+ if (mode == JOB_ISOLATE) {
+ r = transaction_add_isolate_jobs(tr, m);
+ if (r < 0)
+ goto tr_abort;
+ }
+
+ r = transaction_activate(tr, m, mode, e);
+ if (r < 0)
+ goto tr_abort;
+
+ log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) tr->anchor_job->id);
+
+ if (_ret)
+ *_ret = tr->anchor_job;
+
+ transaction_free(tr);
+ return 0;
+
+tr_abort:
+ transaction_abort(tr);
+ transaction_free(tr);
+ return r;
+}
+
+int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, DBusError *e, Job **_ret) {
+ Unit *unit;
+ int r;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(name);
+ assert(mode < _JOB_MODE_MAX);
+
+ r = manager_load_unit(m, name, NULL, NULL, &unit);
+ if (r < 0)
+ return r;
+
+ return manager_add_job(m, type, unit, mode, override, e, _ret);
+}
+
+Job *manager_get_job(Manager *m, uint32_t id) {
+ assert(m);
+
+ return hashmap_get(m->jobs, UINT32_TO_PTR(id));
+}
+
+Unit *manager_get_unit(Manager *m, const char *name) {
+ assert(m);
+ assert(name);
+
+ return hashmap_get(m->units, name);
+}
+
+unsigned manager_dispatch_load_queue(Manager *m) {
+ Unit *u;
+ unsigned n = 0;
+
+ assert(m);
+
+ /* Make sure we are not run recursively */
+ if (m->dispatching_load_queue)
+ return 0;
+
+ m->dispatching_load_queue = true;
+
+ /* Dispatches the load queue. Takes a unit from the queue and
+ * tries to load its data until the queue is empty */
+
+ while ((u = m->load_queue)) {
+ assert(u->in_load_queue);
+
+ unit_load(u);
+ n++;
+ }
+
+ m->dispatching_load_queue = false;
+ return n;
+}
+
+int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) {
+ Unit *ret;
+ UnitType t;
+ int r;
+
+ assert(m);
+ assert(name || path);
+
+ /* This will prepare the unit for loading, but not actually
+ * load anything from disk. */
+
+ if (path && !is_path(path)) {
+ dbus_set_error(e, BUS_ERROR_INVALID_PATH, "Path %s is not absolute.", path);
+ return -EINVAL;
+ }
+
+ if (!name)
+ name = path_get_file_name(path);
+
+ t = unit_name_to_type(name);
+
+ if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, false)) {
+ dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name);
+ return -EINVAL;
+ }
+
+ ret = manager_get_unit(m, name);
+ if (ret) {
+ *_ret = ret;
+ return 1;
+ }
+
+ ret = unit_new(m, unit_vtable[t]->object_size);
+ if (!ret)
+ return -ENOMEM;
+
+ if (path) {
+ ret->fragment_path = strdup(path);
+ if (!ret->fragment_path) {
+ unit_free(ret);
+ return -ENOMEM;
+ }
+ }
+
+ if ((r = unit_add_name(ret, name)) < 0) {
+ unit_free(ret);
+ return r;
+ }
+
+ unit_add_to_load_queue(ret);
+ unit_add_to_dbus_queue(ret);
+ unit_add_to_gc_queue(ret);
+
+ if (_ret)
+ *_ret = ret;
+
+ return 0;
+}
+
+int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) {
+ int r;
+
+ assert(m);
+
+ /* This will load the service information files, but not actually
+ * start any services or anything. */
+
+ r = manager_load_unit_prepare(m, name, path, e, _ret);
+ if (r != 0)
+ return r;
+
+ manager_dispatch_load_queue(m);
+
+ if (_ret)
+ *_ret = unit_follow_merge(*_ret);
+
+ return 0;
+}
+
+void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) {
+ Iterator i;
+ Job *j;
+
+ assert(s);
+ assert(f);
+
+ HASHMAP_FOREACH(j, s->jobs, i)
+ job_dump(j, f, prefix);
+}
+
+void manager_dump_units(Manager *s, FILE *f, const char *prefix) {
+ Iterator i;
+ Unit *u;
+ const char *t;
+
+ assert(s);
+ assert(f);
+
+ HASHMAP_FOREACH_KEY(u, t, s->units, i)
+ if (u->id == t)
+ unit_dump(u, f, prefix);
+}
+
+void manager_clear_jobs(Manager *m) {
+ Job *j;
+
+ assert(m);
+
+ while ((j = hashmap_first(m->jobs)))
+ /* No need to recurse. We're cancelling all jobs. */
+ job_finish_and_invalidate(j, JOB_CANCELED, false);
+}
+
+unsigned manager_dispatch_run_queue(Manager *m) {
+ Job *j;
+ unsigned n = 0;
+
+ if (m->dispatching_run_queue)
+ return 0;
+
+ m->dispatching_run_queue = true;
+
+ while ((j = m->run_queue)) {
+ assert(j->installed);
+ assert(j->in_run_queue);
+
+ job_run_and_invalidate(j);
+ n++;
+ }
+
+ m->dispatching_run_queue = false;
+ return n;
+}
+
+unsigned manager_dispatch_dbus_queue(Manager *m) {
+ Job *j;
+ Unit *u;
+ unsigned n = 0;
+
+ assert(m);
+
+ if (m->dispatching_dbus_queue)
+ return 0;
+
+ m->dispatching_dbus_queue = true;
+
+ while ((u = m->dbus_unit_queue)) {
+ assert(u->in_dbus_queue);
+
+ bus_unit_send_change_signal(u);
+ n++;
+ }
+
+ while ((j = m->dbus_job_queue)) {
+ assert(j->in_dbus_queue);
+
+ bus_job_send_change_signal(j);
+ n++;
+ }
+
+ m->dispatching_dbus_queue = false;
+ return n;
+}
+
+static int manager_process_notify_fd(Manager *m) {
+ ssize_t n;
+
+ assert(m);
+
+ for (;;) {
+ char buf[4096];
+ struct msghdr msghdr;
+ struct iovec iovec;
+ struct ucred *ucred;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+ } control;
+ Unit *u;
+ char **tags;
+
+ zero(iovec);
+ iovec.iov_base = buf;
+ iovec.iov_len = sizeof(buf)-1;
+
+ zero(control);
+ zero(msghdr);
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ if ((n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT)) <= 0) {
+ if (n >= 0)
+ return -EIO;
+
+ if (errno == EAGAIN || errno == EINTR)
+ break;
+
+ return -errno;
+ }
+
+ if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
+ control.cmsghdr.cmsg_level != SOL_SOCKET ||
+ control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
+ control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
+ log_warning("Received notify message without credentials. Ignoring.");
+ continue;
+ }
+
+ ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+
+ if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(ucred->pid))))
+ if (!(u = cgroup_unit_by_pid(m, ucred->pid))) {
+ log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid);
+ continue;
+ }
+
+ assert((size_t) n < sizeof(buf));
+ buf[n] = 0;
+ if (!(tags = strv_split(buf, "\n\r")))
+ return -ENOMEM;
+
+ log_debug("Got notification message for unit %s", u->id);
+
+ if (UNIT_VTABLE(u)->notify_message)
+ UNIT_VTABLE(u)->notify_message(u, ucred->pid, tags);
+
+ strv_free(tags);
+ }
+
+ return 0;
+}
+
+static int manager_dispatch_sigchld(Manager *m) {
+ assert(m);
+
+ for (;;) {
+ siginfo_t si;
+ Unit *u;
+ int r;
+
+ zero(si);
+
+ /* First we call waitd() for a PID and do not reap the
+ * zombie. That way we can still access /proc/$PID for
+ * it while it is a zombie. */
+ if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) {
+
+ if (errno == ECHILD)
+ break;
+
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ if (si.si_pid <= 0)
+ break;
+
+ if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) {
+ char *name = NULL;
+
+ get_process_comm(si.si_pid, &name);
+ log_debug("Got SIGCHLD for process %lu (%s)", (unsigned long) si.si_pid, strna(name));
+ free(name);
+ }
+
+ /* Let's flush any message the dying child might still
+ * have queued for us. This ensures that the process
+ * still exists in /proc so that we can figure out
+ * which cgroup and hence unit it belongs to. */
+ if ((r = manager_process_notify_fd(m)) < 0)
+ return r;
+
+ /* And now figure out the unit this belongs to */
+ if (!(u = hashmap_get(m->watch_pids, LONG_TO_PTR(si.si_pid))))
+ u = cgroup_unit_by_pid(m, si.si_pid);
+
+ /* And now, we actually reap the zombie. */
+ if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED)
+ continue;
+
+ log_debug("Child %lu died (code=%s, status=%i/%s)",
+ (long unsigned) si.si_pid,
+ sigchld_code_to_string(si.si_code),
+ si.si_status,
+ strna(si.si_code == CLD_EXITED
+ ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL)
+ : signal_to_string(si.si_status)));
+
+ if (!u)
+ continue;
+
+ log_debug("Child %lu belongs to %s", (long unsigned) si.si_pid, u->id);
+
+ hashmap_remove(m->watch_pids, LONG_TO_PTR(si.si_pid));
+ UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status);
+ }
+
+ return 0;
+}
+
+static int manager_start_target(Manager *m, const char *name, JobMode mode) {
+ int r;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ log_debug("Activating special unit %s", name);
+
+ if ((r = manager_add_job_by_name(m, JOB_START, name, mode, true, &error, NULL)) < 0)
+ log_error("Failed to enqueue %s job: %s", name, bus_error(&error, r));
+
+ dbus_error_free(&error);
+
+ return r;
+}
+
+static int manager_process_signal_fd(Manager *m) {
+ ssize_t n;
+ struct signalfd_siginfo sfsi;
+ bool sigchld = false;
+
+ assert(m);
+
+ for (;;) {
+ if ((n = read(m->signal_watch.fd, &sfsi, sizeof(sfsi))) != sizeof(sfsi)) {
+
+ if (n >= 0)
+ return -EIO;
+
+ if (errno == EINTR || errno == EAGAIN)
+ break;
+
+ return -errno;
+ }
+
+ if (sfsi.ssi_pid > 0) {
+ char *p = NULL;
+
+ get_process_comm(sfsi.ssi_pid, &p);
+
+ log_debug("Received SIG%s from PID %lu (%s).",
+ signal_to_string(sfsi.ssi_signo),
+ (unsigned long) sfsi.ssi_pid, strna(p));
+ free(p);
+ } else
+ log_debug("Received SIG%s.", signal_to_string(sfsi.ssi_signo));
+
+ switch (sfsi.ssi_signo) {
+
+ case SIGCHLD:
+ sigchld = true;
+ break;
+
+ case SIGTERM:
+ if (m->running_as == SYSTEMD_SYSTEM) {
+ /* This is for compatibility with the
+ * original sysvinit */
+ m->exit_code = MANAGER_REEXECUTE;
+ break;
+ }
+
+ /* Fall through */
+
+ case SIGINT:
+ if (m->running_as == SYSTEMD_SYSTEM) {
+ manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE);
+ break;
+ }
+
+ /* Run the exit target if there is one, if not, just exit. */
+ if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) {
+ m->exit_code = MANAGER_EXIT;
+ return 0;
+ }
+
+ break;
+
+ case SIGWINCH:
+ if (m->running_as == SYSTEMD_SYSTEM)
+ manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE);
+
+ /* This is a nop on non-init */
+ break;
+
+ case SIGPWR:
+ if (m->running_as == SYSTEMD_SYSTEM)
+ manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE);
+
+ /* This is a nop on non-init */
+ break;
+
+ case SIGUSR1: {
+ Unit *u;
+
+ u = manager_get_unit(m, SPECIAL_DBUS_SERVICE);
+
+ if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) {
+ log_info("Trying to reconnect to bus...");
+ bus_init(m, true);
+ }
+
+ if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) {
+ log_info("Loading D-Bus service...");
+ manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE);
+ }
+
+ break;
+ }
+
+ case SIGUSR2: {
+ FILE *f;
+ char *dump = NULL;
+ size_t size;
+
+ if (!(f = open_memstream(&dump, &size))) {
+ log_warning("Failed to allocate memory stream.");
+ break;
+ }
+
+ manager_dump_units(m, f, "\t");
+ manager_dump_jobs(m, f, "\t");
+
+ if (ferror(f)) {
+ fclose(f);
+ free(dump);
+ log_warning("Failed to write status stream");
+ break;
+ }
+
+ fclose(f);
+ log_dump(LOG_INFO, dump);
+ free(dump);
+
+ break;
+ }
+
+ case SIGHUP:
+ m->exit_code = MANAGER_RELOAD;
+ break;
+
+ default: {
+
+ /* Starting SIGRTMIN+0 */
+ static const char * const target_table[] = {
+ [0] = SPECIAL_DEFAULT_TARGET,
+ [1] = SPECIAL_RESCUE_TARGET,
+ [2] = SPECIAL_EMERGENCY_TARGET,
+ [3] = SPECIAL_HALT_TARGET,
+ [4] = SPECIAL_POWEROFF_TARGET,
+ [5] = SPECIAL_REBOOT_TARGET,
+ [6] = SPECIAL_KEXEC_TARGET
+ };
+
+ /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */
+ static const ManagerExitCode code_table[] = {
+ [0] = MANAGER_HALT,
+ [1] = MANAGER_POWEROFF,
+ [2] = MANAGER_REBOOT,
+ [3] = MANAGER_KEXEC
+ };
+
+ if ((int) sfsi.ssi_signo >= SIGRTMIN+0 &&
+ (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) {
+ int idx = (int) sfsi.ssi_signo - SIGRTMIN;
+ manager_start_target(m, target_table[idx],
+ (idx == 1 || idx == 2) ? JOB_ISOLATE : JOB_REPLACE);
+ break;
+ }
+
+ if ((int) sfsi.ssi_signo >= SIGRTMIN+13 &&
+ (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) {
+ m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13];
+ break;
+ }
+
+ switch (sfsi.ssi_signo - SIGRTMIN) {
+
+ case 20:
+ log_debug("Enabling showing of status.");
+ manager_set_show_status(m, true);
+ break;
+
+ case 21:
+ log_debug("Disabling showing of status.");
+ manager_set_show_status(m, false);
+ break;
+
+ case 22:
+ log_set_max_level(LOG_DEBUG);
+ log_notice("Setting log level to debug.");
+ break;
+
+ case 23:
+ log_set_max_level(LOG_INFO);
+ log_notice("Setting log level to info.");
+ break;
+
+ case 24:
+ if (m->running_as == SYSTEMD_USER) {
+ m->exit_code = MANAGER_EXIT;
+ return 0;
+ }
+
+ /* This is a nop on init */
+ break;
+
+ case 26:
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+ log_notice("Setting log target to journal-or-kmsg.");
+ break;
+
+ case 27:
+ log_set_target(LOG_TARGET_CONSOLE);
+ log_notice("Setting log target to console.");
+ break;
+
+ case 28:
+ log_set_target(LOG_TARGET_KMSG);
+ log_notice("Setting log target to kmsg.");
+ break;
+
+ case 29:
+ log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
+ log_notice("Setting log target to syslog-or-kmsg.");
+ break;
+
+ default:
+ log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo));
+ }
+ }
+ }
+ }
+
+ if (sigchld)
+ return manager_dispatch_sigchld(m);
+
+ return 0;
+}
+
+static int process_event(Manager *m, struct epoll_event *ev) {
+ int r;
+ Watch *w;
+
+ assert(m);
+ assert(ev);
+
+ assert_se(w = ev->data.ptr);
+
+ if (w->type == WATCH_INVALID)
+ return 0;
+
+ switch (w->type) {
+
+ case WATCH_SIGNAL:
+
+ /* An incoming signal? */
+ if (ev->events != EPOLLIN)
+ return -EINVAL;
+
+ if ((r = manager_process_signal_fd(m)) < 0)
+ return r;
+
+ break;
+
+ case WATCH_NOTIFY:
+
+ /* An incoming daemon notification event? */
+ if (ev->events != EPOLLIN)
+ return -EINVAL;
+
+ if ((r = manager_process_notify_fd(m)) < 0)
+ return r;
+
+ break;
+
+ case WATCH_FD:
+
+ /* Some fd event, to be dispatched to the units */
+ UNIT_VTABLE(w->data.unit)->fd_event(w->data.unit, w->fd, ev->events, w);
+ break;
+
+ case WATCH_UNIT_TIMER:
+ case WATCH_JOB_TIMER: {
+ uint64_t v;
+ ssize_t k;
+
+ /* Some timer event, to be dispatched to the units */
+ if ((k = read(w->fd, &v, sizeof(v))) != sizeof(v)) {
+
+ if (k < 0 && (errno == EINTR || errno == EAGAIN))
+ break;
+
+ return k < 0 ? -errno : -EIO;
+ }
+
+ if (w->type == WATCH_UNIT_TIMER)
+ UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w);
+ else
+ job_timer_event(w->data.job, v, w);
+ break;
+ }
+
+ case WATCH_MOUNT:
+ /* Some mount table change, intended for the mount subsystem */
+ mount_fd_event(m, ev->events);
+ break;
+
+ case WATCH_SWAP:
+ /* Some swap table change, intended for the swap subsystem */
+ swap_fd_event(m, ev->events);
+ break;
+
+ case WATCH_UDEV:
+ /* Some notification from udev, intended for the device subsystem */
+ device_fd_event(m, ev->events);
+ break;
+
+ case WATCH_DBUS_WATCH:
+ bus_watch_event(m, w, ev->events);
+ break;
+
+ case WATCH_DBUS_TIMEOUT:
+ bus_timeout_event(m, w, ev->events);
+ break;
+
+ default:
+ log_error("event type=%i", w->type);
+ assert_not_reached("Unknown epoll event type.");
+ }
+
+ return 0;
+}
+
+int manager_loop(Manager *m) {
+ int r;
+
+ RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000);
+
+ assert(m);
+ m->exit_code = MANAGER_RUNNING;
+
+ /* Release the path cache */
+ set_free_free(m->unit_path_cache);
+ m->unit_path_cache = NULL;
+
+ manager_check_finished(m);
+
+ /* There might still be some zombies hanging around from
+ * before we were exec()'ed. Leat's reap them */
+ r = manager_dispatch_sigchld(m);
+ if (r < 0)
+ return r;
+
+ while (m->exit_code == MANAGER_RUNNING) {
+ struct epoll_event event;
+ int n;
+ int wait_msec = -1;
+
+ if (m->runtime_watchdog > 0 && m->running_as == SYSTEMD_SYSTEM)
+ 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)
+ continue;
+
+ if (manager_dispatch_run_queue(m) > 0)
+ continue;
+
+ if (bus_dispatch(m) > 0)
+ continue;
+
+ if (manager_dispatch_cleanup_queue(m) > 0)
+ continue;
+
+ if (manager_dispatch_gc_queue(m) > 0)
+ continue;
+
+ if (manager_dispatch_dbus_queue(m) > 0)
+ continue;
+
+ if (swap_dispatch_reload(m) > 0)
+ continue;
+
+ /* Sleep for half the watchdog time */
+ if (m->runtime_watchdog > 0 && m->running_as == SYSTEMD_SYSTEM) {
+ wait_msec = (int) (m->runtime_watchdog / 2 / USEC_PER_MSEC);
+ if (wait_msec <= 0)
+ wait_msec = 1;
+ } else
+ wait_msec = -1;
+
+ n = epoll_wait(m->epoll_fd, &event, 1, wait_msec);
+ if (n < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ } else if (n == 0)
+ continue;
+
+ assert(n == 1);
+
+ r = process_event(m, &event);
+ if (r < 0)
+ return r;
+ }
+
+ return m->exit_code;
+}
+
+int manager_load_unit_from_dbus_path(Manager *m, const char *s, DBusError *e, Unit **_u) {
+ char *n;
+ Unit *u;
+ int r;
+
+ assert(m);
+ assert(s);
+ assert(_u);
+
+ if (!startswith(s, "/org/freedesktop/systemd1/unit/"))
+ return -EINVAL;
+
+ n = bus_path_unescape(s+31);
+ if (!n)
+ return -ENOMEM;
+
+ r = manager_load_unit(m, n, NULL, e, &u);
+ free(n);
+
+ if (r < 0)
+ return r;
+
+ *_u = u;
+
+ return 0;
+}
+
+int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) {
+ Job *j;
+ unsigned id;
+ int r;
+
+ assert(m);
+ assert(s);
+ assert(_j);
+
+ if (!startswith(s, "/org/freedesktop/systemd1/job/"))
+ return -EINVAL;
+
+ if ((r = safe_atou(s + 30, &id)) < 0)
+ return r;
+
+ if (!(j = manager_get_job(m, id)))
+ return -ENOENT;
+
+ *_j = j;
+
+ return 0;
+}
+
+void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) {
+
+#ifdef HAVE_AUDIT
+ char *p;
+ int audit_fd;
+
+ 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 != SYSTEMD_SYSTEM)
+ return;
+
+ if (u->type != UNIT_SERVICE)
+ return;
+
+ if (!(p = unit_name_to_prefix_and_instance(u->id))) {
+ log_error("Failed to allocate unit name for audit message: %s", strerror(ENOMEM));
+ return;
+ }
+
+ if (audit_log_user_comm_message(audit_fd, type, "", p, NULL, NULL, NULL, success) < 0) {
+ if (errno == EPERM) {
+ /* We aren't allowed to send audit messages?
+ * Then let's not retry again. */
+ close_audit_fd();
+ } else
+ log_warning("Failed to send audit message: %m");
+ }
+
+ free(p);
+#endif
+
+}
+
+void manager_send_unit_plymouth(Manager *m, Unit *u) {
+ int fd = -1;
+ union sockaddr_union sa;
+ int n = 0;
+ char *message = NULL;
+
+ /* Don't generate plymouth events if the service was already
+ * started and we're just deserializing */
+ if (m->n_reloading > 0)
+ return;
+
+ if (m->running_as != SYSTEMD_SYSTEM)
+ return;
+
+ if (u->type != UNIT_SERVICE &&
+ u->type != UNIT_MOUNT &&
+ u->type != UNIT_SWAP)
+ return;
+
+ /* We set SOCK_NONBLOCK here so that we rather drop the
+ * message then wait for plymouth */
+ if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+ log_error("socket() failed: %m");
+ return;
+ }
+
+ zero(sa);
+ sa.sa.sa_family = AF_UNIX;
+ strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
+ if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
+
+ if (errno != EPIPE &&
+ errno != EAGAIN &&
+ errno != ENOENT &&
+ errno != ECONNREFUSED &&
+ errno != ECONNRESET &&
+ errno != ECONNABORTED)
+ log_error("connect() failed: %m");
+
+ goto finish;
+ }
+
+ if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) {
+ log_oom();
+ goto finish;
+ }
+
+ errno = 0;
+ if (write(fd, message, n + 1) != n + 1) {
+
+ if (errno != EPIPE &&
+ errno != EAGAIN &&
+ errno != ENOENT &&
+ errno != ECONNREFUSED &&
+ errno != ECONNRESET &&
+ errno != ECONNABORTED)
+ log_error("Failed to write Plymouth message: %m");
+
+ goto finish;
+ }
+
+finish:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ free(message);
+}
+
+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);
+
+ if (!(u = hashmap_get(m->watch_bus, name)))
+ return;
+
+ UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner);
+}
+
+void manager_dispatch_bus_query_pid_done(
+ Manager *m,
+ const char *name,
+ pid_t pid) {
+
+ Unit *u;
+
+ assert(m);
+ assert(name);
+ assert(pid >= 1);
+
+ if (!(u = hashmap_get(m->watch_bus, name)))
+ return;
+
+ UNIT_VTABLE(u)->bus_query_pid_done(u, name, pid);
+}
+
+int manager_open_serialization(Manager *m, FILE **_f) {
+ char *path = NULL;
+ mode_t saved_umask;
+ int fd;
+ FILE *f;
+
+ assert(_f);
+
+ if (m->running_as == SYSTEMD_SYSTEM)
+ asprintf(&path, "/run/systemd/dump-%lu-XXXXXX", (unsigned long) getpid());
+ else
+ asprintf(&path, "/tmp/systemd-dump-%lu-XXXXXX", (unsigned long) getpid());
+
+ if (!path)
+ return -ENOMEM;
+
+ saved_umask = umask(0077);
+ fd = mkostemp(path, O_RDWR|O_CLOEXEC);
+ umask(saved_umask);
+
+ if (fd < 0) {
+ free(path);
+ return -errno;
+ }
+
+ unlink(path);
+
+ log_debug("Serializing state to %s", path);
+ free(path);
+
+ if (!(f = fdopen(fd, "w+")))
+ return -errno;
+
+ *_f = f;
+
+ return 0;
+}
+
+int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool serialize_jobs) {
+ Iterator i;
+ Unit *u;
+ const char *t;
+ int r;
+
+ assert(m);
+ assert(f);
+ assert(fds);
+
+ m->n_reloading ++;
+
+ fprintf(f, "current-job-id=%i\n", m->current_job_id);
+ fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr));
+ fprintf(f, "n-installed-jobs=%u\n", m->n_installed_jobs);
+ fprintf(f, "n-failed-jobs=%u\n", m->n_failed_jobs);
+
+ dual_timestamp_serialize(f, "firmware-timestamp", &m->firmware_timestamp);
+ dual_timestamp_serialize(f, "kernel-timestamp", &m->kernel_timestamp);
+ dual_timestamp_serialize(f, "loader-timestamp", &m->loader_timestamp);
+ dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp);
+
+ if (!in_initrd()) {
+ dual_timestamp_serialize(f, "userspace-timestamp", &m->userspace_timestamp);
+ dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp);
+ }
+
+ fputc('\n', f);
+
+ HASHMAP_FOREACH_KEY(u, t, m->units, i) {
+ if (u->id != t)
+ continue;
+
+ if (!unit_can_serialize(u))
+ continue;
+
+ /* Start marker */
+ fputs(u->id, f);
+ fputc('\n', f);
+
+ if ((r = unit_serialize(u, f, fds, serialize_jobs)) < 0) {
+ m->n_reloading --;
+ return r;
+ }
+ }
+
+ assert(m->n_reloading > 0);
+ m->n_reloading --;
+
+ if (ferror(f))
+ return -EIO;
+
+ r = bus_fdset_add_all(m, fds);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
+ int r = 0;
+
+ assert(m);
+ assert(f);
+
+ log_debug("Deserializing state...");
+
+ m->n_reloading ++;
+
+ for (;;) {
+ char line[LINE_MAX], *l;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ r = 0;
+ else
+ r = -errno;
+
+ goto finish;
+ }
+
+ char_array_0(line);
+ l = strstrip(line);
+
+ if (l[0] == 0)
+ break;
+
+ if (startswith(l, "current-job-id=")) {
+ uint32_t id;
+
+ if (safe_atou32(l+15, &id) < 0)
+ log_debug("Failed to parse current job id value %s", l+15);
+ else
+ m->current_job_id = MAX(m->current_job_id, id);
+ } else if (startswith(l, "n-installed-jobs=")) {
+ uint32_t n;
+
+ if (safe_atou32(l+17, &n) < 0)
+ log_debug("Failed to parse installed jobs counter %s", l+17);
+ else
+ m->n_installed_jobs += n;
+ } else if (startswith(l, "n-failed-jobs=")) {
+ uint32_t n;
+
+ if (safe_atou32(l+14, &n) < 0)
+ log_debug("Failed to parse failed jobs counter %s", l+14);
+ else
+ m->n_failed_jobs += n;
+ } else if (startswith(l, "taint-usr=")) {
+ int b;
+
+ if ((b = parse_boolean(l+10)) < 0)
+ log_debug("Failed to parse taint /usr flag %s", l+10);
+ else
+ m->taint_usr = m->taint_usr || b;
+ } else if (startswith(l, "firmware-timestamp="))
+ dual_timestamp_deserialize(l+19, &m->firmware_timestamp);
+ else if (startswith(l, "loader-timestamp="))
+ dual_timestamp_deserialize(l+17, &m->loader_timestamp);
+ else if (startswith(l, "kernel-timestamp="))
+ dual_timestamp_deserialize(l+17, &m->kernel_timestamp);
+ else if (startswith(l, "initrd-timestamp="))
+ dual_timestamp_deserialize(l+17, &m->initrd_timestamp);
+ else if (startswith(l, "userspace-timestamp="))
+ dual_timestamp_deserialize(l+20, &m->userspace_timestamp);
+ else if (startswith(l, "finish-timestamp="))
+ dual_timestamp_deserialize(l+17, &m->finish_timestamp);
+ else
+ log_debug("Unknown serialization item '%s'", l);
+ }
+
+ for (;;) {
+ Unit *u;
+ char name[UNIT_NAME_MAX+2];
+
+ /* Start marker */
+ if (!fgets(name, sizeof(name), f)) {
+ if (feof(f))
+ r = 0;
+ else
+ r = -errno;
+
+ goto finish;
+ }
+
+ char_array_0(name);
+
+ if ((r = manager_load_unit(m, strstrip(name), NULL, NULL, &u)) < 0)
+ goto finish;
+
+ if ((r = unit_deserialize(u, f, fds)) < 0)
+ goto finish;
+ }
+
+finish:
+ if (ferror(f)) {
+ r = -EIO;
+ goto finish;
+ }
+
+ assert(m->n_reloading > 0);
+ m->n_reloading --;
+
+ return r;
+}
+
+int manager_reload(Manager *m) {
+ int r, q;
+ FILE *f;
+ FDSet *fds;
+
+ assert(m);
+
+ r = manager_open_serialization(m, &f);
+ if (r < 0)
+ return r;
+
+ m->n_reloading ++;
+
+ fds = fdset_new();
+ if (!fds) {
+ m->n_reloading --;
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = manager_serialize(m, f, fds, true);
+ if (r < 0) {
+ m->n_reloading --;
+ goto finish;
+ }
+
+ if (fseeko(f, 0, SEEK_SET) < 0) {
+ m->n_reloading --;
+ r = -errno;
+ goto finish;
+ }
+
+ /* From here on there is no way back. */
+ manager_clear_jobs_and_units(m);
+ manager_undo_generators(m);
+ lookup_paths_free(&m->lookup_paths);
+
+ /* Find new unit paths */
+ manager_run_generators(m);
+
+ q = lookup_paths_init(
+ &m->lookup_paths, m->running_as, true,
+ m->generator_unit_path,
+ m->generator_unit_path_early,
+ m->generator_unit_path_late);
+ if (q < 0)
+ r = q;
+
+ manager_build_unit_path_cache(m);
+
+ /* First, enumerate what we can from all config files */
+ q = manager_enumerate(m);
+ if (q < 0)
+ r = q;
+
+ /* Second, deserialize our stored data */
+ q = manager_deserialize(m, f, fds);
+ if (q < 0)
+ r = q;
+
+ fclose(f);
+ f = NULL;
+
+ /* Third, fire things up! */
+ q = manager_coldplug(m);
+ if (q < 0)
+ r = q;
+
+ assert(m->n_reloading > 0);
+ m->n_reloading--;
+
+finish:
+ if (f)
+ fclose(f);
+
+ if (fds)
+ fdset_free(fds);
+
+ return r;
+}
+
+bool manager_is_booting_or_shutting_down(Manager *m) {
+ Unit *u;
+
+ assert(m);
+
+ /* Is the initial job still around? */
+ if (manager_get_job(m, m->default_unit_job_id))
+ return true;
+
+ /* Is there a job for the shutdown target? */
+ u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET);
+ if (u)
+ return !!u->job;
+
+ return false;
+}
+
+void manager_reset_failed(Manager *m) {
+ Unit *u;
+ Iterator i;
+
+ assert(m);
+
+ HASHMAP_FOREACH(u, m->units, i)
+ unit_reset_failed(u);
+}
+
+bool manager_unit_pending_inactive(Manager *m, const char *name) {
+ Unit *u;
+
+ assert(m);
+ assert(name);
+
+ /* Returns true if the unit is inactive or going down */
+ if (!(u = manager_get_unit(m, name)))
+ return true;
+
+ return unit_pending_inactive(u);
+}
+
+void manager_check_finished(Manager *m) {
+ char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX];
+ usec_t firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec;
+
+ assert(m);
+
+ if (hashmap_size(m->jobs) > 0)
+ return;
+
+ /* Notify Type=idle units that we are done now */
+ close_pipe(m->idle_pipe);
+
+ /* Turn off confirm spawn now */
+ m->confirm_spawn = false;
+
+ if (dual_timestamp_is_set(&m->finish_timestamp))
+ return;
+
+ dual_timestamp_get(&m->finish_timestamp);
+
+ if (m->running_as == SYSTEMD_SYSTEM && detect_container(NULL) <= 0) {
+
+ /* Note that m->kernel_usec.monotonic is always at 0,
+ * and m->firmware_usec.monotonic and
+ * m->loader_usec.monotonic should be considered
+ * negative values. */
+
+ firmware_usec = m->firmware_timestamp.monotonic - m->loader_timestamp.monotonic;
+ loader_usec = m->loader_timestamp.monotonic - m->kernel_timestamp.monotonic;
+ userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic;
+ total_usec = m->firmware_timestamp.monotonic + m->finish_timestamp.monotonic;
+
+ if (dual_timestamp_is_set(&m->initrd_timestamp)) {
+
+ kernel_usec = m->initrd_timestamp.monotonic - m->kernel_timestamp.monotonic;
+ initrd_usec = m->userspace_timestamp.monotonic - m->initrd_timestamp.monotonic;
+
+ if (!log_on_console())
+ log_struct(LOG_INFO,
+ MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED),
+ "KERNEL_USEC=%llu", (unsigned long long) kernel_usec,
+ "INITRD_USEC=%llu", (unsigned long long) initrd_usec,
+ "USERSPACE_USEC=%llu", (unsigned long long) userspace_usec,
+ "MESSAGE=Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.",
+ format_timespan(kernel, sizeof(kernel), kernel_usec),
+ format_timespan(initrd, sizeof(initrd), initrd_usec),
+ format_timespan(userspace, sizeof(userspace), userspace_usec),
+ format_timespan(sum, sizeof(sum), total_usec),
+ NULL);
+ } else {
+ kernel_usec = m->userspace_timestamp.monotonic - m->kernel_timestamp.monotonic;
+ initrd_usec = 0;
+
+ if (!log_on_console())
+ log_struct(LOG_INFO,
+ MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED),
+ "KERNEL_USEC=%llu", (unsigned long long) kernel_usec,
+ "USERSPACE_USEC=%llu", (unsigned long long) userspace_usec,
+ "MESSAGE=Startup finished in %s (kernel) + %s (userspace) = %s.",
+ format_timespan(kernel, sizeof(kernel), kernel_usec),
+ format_timespan(userspace, sizeof(userspace), userspace_usec),
+ format_timespan(sum, sizeof(sum), total_usec),
+ NULL);
+ }
+ } else {
+ firmware_usec = loader_usec = initrd_usec = kernel_usec = 0;
+ total_usec = userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic;
+
+ if (!log_on_console())
+ log_struct(LOG_INFO,
+ MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED),
+ "USERSPACE_USEC=%llu", (unsigned long long) userspace_usec,
+ "MESSAGE=Startup finished in %s.",
+ format_timespan(sum, sizeof(sum), total_usec),
+ NULL);
+ }
+
+ bus_broadcast_finished(m, firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec);
+
+ sd_notifyf(false,
+ "READY=1\nSTATUS=Startup finished in %s.",
+ format_timespan(sum, sizeof(sum), total_usec));
+}
+
+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 == SYSTEMD_SYSTEM && getpid() == 1) {
+
+ p = strappend("/run/systemd/", name);
+ if (!p)
+ return log_oom();
+
+ r = mkdir_p_label(p, 0755);
+ if (r < 0) {
+ log_error("Failed to create generator directory: %s", strerror(-r));
+ free(p);
+ return r;
+ }
+ } else {
+ p = strjoin("/tmp/systemd-", name, ".XXXXXX", NULL);
+ if (!p)
+ return log_oom();
+
+ if (!mkdtemp(p)) {
+ free(p);
+ log_error("Failed to create generator directory: %m");
+ 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;
+}
+
+void manager_run_generators(Manager *m) {
+ DIR *d = NULL;
+ const char *generator_path;
+ const char *argv[5];
+ mode_t u;
+ int r;
+
+ assert(m);
+
+ generator_path = m->running_as == SYSTEMD_SYSTEM ? SYSTEM_GENERATOR_PATH : USER_GENERATOR_PATH;
+ d = opendir(generator_path);
+ if (!d) {
+ if (errno == ENOENT)
+ return;
+
+ log_error("Failed to enumerate generator directory: %m");
+ return;
+ }
+
+ 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");
+ 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[4] = NULL;
+
+ u = umask(0022);
+ execute_directory(generator_path, d, (char**) argv);
+ umask(u);
+
+ 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);
+
+finish:
+ if (d)
+ closedir(d);
+}
+
+static void remove_generator_dir(Manager *m, char **generator) {
+ assert(m);
+ assert(generator);
+
+ if (!*generator)
+ return;
+
+ strv_remove(m->lookup_paths.unit_path, *generator);
+ rm_rf(*generator, false, true, false);
+
+ free(*generator);
+ *generator = NULL;
+}
+
+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_set_default_controllers(Manager *m, char **controllers) {
+ char **l;
+
+ assert(m);
+
+ l = strv_copy(controllers);
+ if (!l)
+ return -ENOMEM;
+
+ strv_free(m->default_controllers);
+ m->default_controllers = l;
+
+ cg_shorten_controllers(m->default_controllers);
+
+ return 0;
+}
+
+int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) {
+ int i;
+
+ assert(m);
+
+ for (i = 0; i < RLIMIT_NLIMITS; i++) {
+ if (!default_rlimit[i])
+ continue;
+
+ m->rlimit[i] = newdup(struct rlimit, default_rlimit[i], 1);
+ if (!m->rlimit[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+void manager_recheck_journal(Manager *m) {
+ Unit *u;
+
+ assert(m);
+
+ if (m->running_as != SYSTEMD_SYSTEM)
+ return;
+
+ u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET);
+ if (u && SOCKET(u)->state != SOCKET_RUNNING) {
+ log_close_journal();
+ return;
+ }
+
+ u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE);
+ if (u && SERVICE(u)->state != SERVICE_RUNNING) {
+ log_close_journal();
+ return;
+ }
+
+ /* Hmm, OK, so the socket is fully up and the service is up
+ * too, then let's make use of the thing. */
+ log_open();
+}
+
+void manager_set_show_status(Manager *m, bool b) {
+ assert(m);
+
+ if (m->running_as != SYSTEMD_SYSTEM)
+ return;
+
+ m->show_status = b;
+
+ if (b)
+ touch("/run/systemd/show-status");
+ else
+ unlink("/run/systemd/show-status");
+}
+
+bool manager_get_show_status(Manager *m) {
+ assert(m);
+
+ if (m->running_as != SYSTEMD_SYSTEM)
+ return false;
+
+ if (m->show_status)
+ return true;
+
+ /* If Plymouth is running make sure we show the status, so
+ * that there's something nice to see when people press Esc */
+
+ return plymouth_running();
+}
diff --git a/src/core/manager.h b/src/core/manager.h
new file mode 100644
index 0000000000..22145024f1
--- /dev/null
+++ b/src/core/manager.h
@@ -0,0 +1,293 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <dbus/dbus.h>
+
+#include "fdset.h"
+
+/* Enforce upper limit how many names we allow */
+#define MANAGER_MAX_NAMES 131072 /* 128K */
+
+typedef struct Manager Manager;
+typedef enum WatchType WatchType;
+typedef struct Watch Watch;
+
+typedef enum ManagerExitCode {
+ MANAGER_RUNNING,
+ MANAGER_EXIT,
+ MANAGER_RELOAD,
+ MANAGER_REEXECUTE,
+ MANAGER_REBOOT,
+ MANAGER_POWEROFF,
+ MANAGER_HALT,
+ MANAGER_KEXEC,
+ MANAGER_SWITCH_ROOT,
+ _MANAGER_EXIT_CODE_MAX,
+ _MANAGER_EXIT_CODE_INVALID = -1
+} ManagerExitCode;
+
+enum WatchType {
+ WATCH_INVALID,
+ WATCH_SIGNAL,
+ WATCH_NOTIFY,
+ WATCH_FD,
+ WATCH_UNIT_TIMER,
+ WATCH_JOB_TIMER,
+ WATCH_MOUNT,
+ WATCH_SWAP,
+ WATCH_UDEV,
+ WATCH_DBUS_WATCH,
+ WATCH_DBUS_TIMEOUT
+};
+
+struct Watch {
+ int fd;
+ WatchType type;
+ union {
+ struct Unit *unit;
+ struct Job *job;
+ DBusWatch *bus_watch;
+ DBusTimeout *bus_timeout;
+ } data;
+ bool fd_is_dupped:1;
+ bool socket_accept:1;
+};
+
+#include "unit.h"
+#include "job.h"
+#include "hashmap.h"
+#include "list.h"
+#include "set.h"
+#include "dbus.h"
+#include "path-lookup.h"
+
+struct Manager {
+ /* Note that the set of units we know of is allowed to be
+ * inconsistent. However the subset of it that is loaded may
+ * not, and the list of jobs may neither. */
+
+ /* Active jobs and units */
+ Hashmap *units; /* name string => Unit object n:1 */
+ Hashmap *jobs; /* job id => Job object 1:1 */
+
+ /* To make it easy to iterate through the units of a specific
+ * type we maintain a per type linked list */
+ LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]);
+
+ /* To optimize iteration of units that have requires_mounts_for set */
+ LIST_HEAD(Unit, has_requires_mounts_for);
+
+ /* Units that need to be loaded */
+ LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */
+
+ /* Jobs that need to be run */
+ LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */
+
+ /* Units and jobs that have not yet been announced via
+ * D-Bus. When something about a job changes it is added here
+ * if it is not in there yet. This allows easy coalescing of
+ * D-Bus change signals. */
+ LIST_HEAD(Unit, dbus_unit_queue);
+ LIST_HEAD(Job, dbus_job_queue);
+
+ /* Units to remove */
+ LIST_HEAD(Unit, cleanup_queue);
+
+ /* Units to check when doing GC */
+ LIST_HEAD(Unit, gc_queue);
+
+ Hashmap *watch_pids; /* pid => Unit object n:1 */
+
+ char *notify_socket;
+
+ Watch notify_watch;
+ Watch signal_watch;
+
+ int epoll_fd;
+
+ unsigned n_snapshots;
+
+ LookupPaths lookup_paths;
+ Set *unit_path_cache;
+
+ char **environment;
+ char **default_controllers;
+
+ usec_t runtime_watchdog;
+ usec_t shutdown_watchdog;
+
+ dual_timestamp firmware_timestamp;
+ dual_timestamp loader_timestamp;
+ dual_timestamp kernel_timestamp;
+ dual_timestamp initrd_timestamp;
+ dual_timestamp userspace_timestamp;
+ dual_timestamp finish_timestamp;
+
+ char *generator_unit_path;
+ char *generator_unit_path_early;
+ char *generator_unit_path_late;
+
+ /* Data specific to the device subsystem */
+ struct udev* udev;
+ struct udev_monitor* udev_monitor;
+ Watch udev_watch;
+ Hashmap *devices_by_sysfs;
+
+ /* Data specific to the mount subsystem */
+ FILE *proc_self_mountinfo;
+ Watch mount_watch;
+
+ /* Data specific to the swap filesystem */
+ FILE *proc_swaps;
+ Hashmap *swaps_by_proc_swaps;
+ bool request_reload;
+ Watch swap_watch;
+
+ /* Data specific to the D-Bus subsystem */
+ DBusConnection *api_bus, *system_bus;
+ DBusServer *private_bus;
+ Set *bus_connections, *bus_connections_for_dispatch;
+
+ DBusMessage *queued_message; /* This is used during reloading:
+ * before the reload we queue the
+ * reply message here, and
+ * afterwards we send it */
+ DBusConnection *queued_message_connection; /* The connection to send the queued message on */
+
+ Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */
+ int32_t name_data_slot;
+ int32_t conn_data_slot;
+ int32_t subscribed_data_slot;
+
+ uint32_t current_job_id;
+ uint32_t default_unit_job_id;
+
+ /* Data specific to the Automount subsystem */
+ int dev_autofs_fd;
+
+ /* Data specific to the cgroup subsystem */
+ Hashmap *cgroup_bondings; /* path string => CGroupBonding object 1:n */
+ char *cgroup_hierarchy;
+
+ usec_t gc_queue_timestamp;
+ int gc_marker;
+ unsigned n_in_gc_queue;
+
+ /* Make sure the user cannot accidentally unmount our cgroup
+ * file system */
+ int pin_cgroupfs_fd;
+
+ /* Flags */
+ SystemdRunningAs running_as;
+ ManagerExitCode exit_code:5;
+
+ bool dispatching_load_queue:1;
+ bool dispatching_run_queue:1;
+ bool dispatching_dbus_queue:1;
+
+ bool taint_usr:1;
+
+ bool show_status;
+ bool confirm_spawn;
+
+ ExecOutput default_std_output, default_std_error;
+
+ struct rlimit *rlimit[RLIMIT_NLIMITS];
+
+ /* non-zero if we are reloading or reexecuting, */
+ int n_reloading;
+
+ unsigned n_installed_jobs;
+ unsigned n_failed_jobs;
+
+ /* Type=idle pipes */
+ int idle_pipe[2];
+
+ char *switch_root;
+ char *switch_root_init;
+};
+
+int manager_new(SystemdRunningAs running_as, Manager **m);
+void manager_free(Manager *m);
+
+int manager_enumerate(Manager *m);
+int manager_coldplug(Manager *m);
+int manager_startup(Manager *m, FILE *serialization, FDSet *fds);
+
+Job *manager_get_job(Manager *m, uint32_t id);
+Unit *manager_get_unit(Manager *m, const char *name);
+
+int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j);
+
+int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret);
+int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret);
+int manager_load_unit_from_dbus_path(Manager *m, const char *s, DBusError *e, Unit **_u);
+
+int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool force, DBusError *e, Job **_ret);
+int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool force, DBusError *e, Job **_ret);
+
+void manager_dump_units(Manager *s, FILE *f, const char *prefix);
+void manager_dump_jobs(Manager *s, FILE *f, const char *prefix);
+
+void manager_clear_jobs(Manager *m);
+
+unsigned manager_dispatch_load_queue(Manager *m);
+unsigned manager_dispatch_run_queue(Manager *m);
+unsigned manager_dispatch_dbus_queue(Manager *m);
+
+int manager_set_default_controllers(Manager *m, char **controllers);
+int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit);
+
+int manager_loop(Manager *m);
+
+void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner);
+void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid);
+
+int manager_open_serialization(Manager *m, FILE **_f);
+
+int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool serialize_jobs);
+int manager_deserialize(Manager *m, FILE *f, FDSet *fds);
+
+int manager_reload(Manager *m);
+
+bool manager_is_booting_or_shutting_down(Manager *m);
+
+void manager_reset_failed(Manager *m);
+
+void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success);
+void manager_send_unit_plymouth(Manager *m, Unit *u);
+
+bool manager_unit_pending_inactive(Manager *m, const char *name);
+
+void manager_check_finished(Manager *m);
+
+void manager_run_generators(Manager *m);
+void manager_undo_generators(Manager *m);
+
+void manager_recheck_journal(Manager *m);
+
+void manager_set_show_status(Manager *m, bool b);
+bool manager_get_show_status(Manager *m);
diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
new file mode 100644
index 0000000000..98614d0c3e
--- /dev/null
+++ b/src/core/mount-setup.c
@@ -0,0 +1,441 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mount.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <assert.h>
+#include <unistd.h>
+#include <ftw.h>
+
+#include "mount-setup.h"
+#include "dev-setup.h"
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "label.h"
+#include "set.h"
+#include "strv.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "missing.h"
+#include "virt.h"
+
+#ifndef TTY_GID
+#define TTY_GID 5
+#endif
+
+typedef enum MountMode {
+ MNT_NONE = 0,
+ MNT_FATAL = 1 << 0,
+ MNT_IN_CONTAINER = 1 << 1,
+} MountMode;
+
+typedef struct MountPoint {
+ const char *what;
+ const char *where;
+ const char *type;
+ const char *options;
+ unsigned long flags;
+ bool (*condition_fn)(void);
+ MountMode mode;
+} MountPoint;
+
+/* The first three entries we might need before SELinux is up. The
+ * fourth (securityfs) is needed by IMA to load a custom policy. The
+ * other ones we can delay until SELinux and IMA are loaded. */
+#define N_EARLY_MOUNT 4
+
+static const MountPoint mount_table[] = {
+ { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ NULL, MNT_FATAL|MNT_IN_CONTAINER },
+ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ NULL, MNT_FATAL|MNT_IN_CONTAINER },
+ { "devtmpfs", "/dev", "devtmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME,
+ NULL, MNT_FATAL|MNT_IN_CONTAINER },
+ { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ NULL, MNT_NONE },
+ { "efivarfs", "/sys/firmware/efi/efivars", "efivarfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ is_efiboot, MNT_NONE },
+ { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
+ NULL, MNT_FATAL|MNT_IN_CONTAINER },
+ { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC,
+ NULL, MNT_IN_CONTAINER },
+ { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
+ NULL, MNT_FATAL|MNT_IN_CONTAINER },
+ { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
+ NULL, MNT_IN_CONTAINER },
+ { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ NULL, MNT_IN_CONTAINER },
+};
+
+/* These are API file systems that might be mounted by other software,
+ * we just list them here so that we know that we should ignore them */
+
+static const char ignore_paths[] =
+ /* SELinux file systems */
+ "/sys/fs/selinux\0"
+ "/selinux\0"
+ /* Legacy cgroup mount points */
+ "/dev/cgroup\0"
+ "/cgroup\0"
+ /* Legacy kernel file system */
+ "/proc/bus/usb\0"
+ /* Container bind mounts */
+ "/proc/sys\0"
+ "/dev/console\0"
+ "/proc/kmsg\0";
+
+bool mount_point_is_api(const char *path) {
+ unsigned i;
+
+ /* Checks if this mount point is considered "API", and hence
+ * should be ignored */
+
+ for (i = 0; i < ELEMENTSOF(mount_table); i ++)
+ if (path_equal(path, mount_table[i].where))
+ return true;
+
+ return path_startswith(path, "/sys/fs/cgroup/");
+}
+
+bool mount_point_ignore(const char *path) {
+ const char *i;
+
+ NULSTR_FOREACH(i, ignore_paths)
+ if (path_equal(path, i))
+ return true;
+
+ return false;
+}
+
+static int mount_one(const MountPoint *p, bool relabel) {
+ int r;
+
+ assert(p);
+
+ if (p->condition_fn && !p->condition_fn())
+ return 0;
+
+ /* Relabel first, just in case */
+ if (relabel)
+ label_fix(p->where, true, true);
+
+ r = path_is_mount_point(p->where, true);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ return 0;
+
+ /* Skip securityfs in a container */
+ if (!(p->mode & MNT_IN_CONTAINER) && detect_container(NULL) > 0)
+ return 0;
+
+ /* The access mode here doesn't really matter too much, since
+ * the mounted file system will take precedence anyway. */
+ mkdir_p_label(p->where, 0755);
+
+ log_debug("Mounting %s to %s of type %s with options %s.",
+ p->what,
+ p->where,
+ p->type,
+ strna(p->options));
+
+ if (mount(p->what,
+ p->where,
+ p->type,
+ p->flags,
+ p->options) < 0) {
+ log_full((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, "Failed to mount %s: %s", p->where, strerror(errno));
+ return (p->mode & MNT_FATAL) ? -errno : 0;
+ }
+
+ /* Relabel again, since we now mounted something fresh here */
+ if (relabel)
+ label_fix(p->where, false, false);
+
+ return 1;
+}
+
+int mount_setup_early(void) {
+ unsigned i;
+ int r = 0;
+
+ assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table));
+
+ /* Do a minimal mount of /proc and friends to enable the most
+ * basic stuff, such as SELinux */
+ for (i = 0; i < N_EARLY_MOUNT; i ++) {
+ int j;
+
+ j = mount_one(mount_table + i, false);
+ if (r == 0)
+ r = j;
+ }
+
+ return r;
+}
+
+int mount_cgroup_controllers(char ***join_controllers) {
+ int r;
+ FILE *f;
+ char buf[LINE_MAX];
+ Set *controllers;
+
+ /* Mount all available cgroup controllers that are built into the kernel. */
+
+ f = fopen("/proc/cgroups", "re");
+ if (!f) {
+ log_error("Failed to enumerate cgroup controllers: %m");
+ return 0;
+ }
+
+ controllers = set_new(string_hash_func, string_compare_func);
+ if (!controllers) {
+ r = log_oom();
+ goto finish;
+ }
+
+ /* Ignore the header line */
+ (void) fgets(buf, sizeof(buf), f);
+
+ for (;;) {
+ char *controller;
+ int enabled = 0;
+
+ if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) {
+
+ if (feof(f))
+ break;
+
+ log_error("Failed to parse /proc/cgroups.");
+ r = -EIO;
+ goto finish;
+ }
+
+ if (!enabled) {
+ free(controller);
+ continue;
+ }
+
+ r = set_put(controllers, controller);
+ if (r < 0) {
+ log_error("Failed to add controller to set.");
+ free(controller);
+ goto finish;
+ }
+ }
+
+ for (;;) {
+ MountPoint p;
+ char *controller, *where, *options;
+ char ***k = NULL;
+
+ controller = set_steal_first(controllers);
+ if (!controller)
+ break;
+
+ if (join_controllers)
+ for (k = join_controllers; *k; k++)
+ if (strv_find(*k, controller))
+ break;
+
+ if (k && *k) {
+ char **i, **j;
+
+ for (i = *k, j = *k; *i; i++) {
+
+ if (!streq(*i, controller)) {
+ char *t;
+
+ t = set_remove(controllers, *i);
+ if (!t) {
+ free(*i);
+ continue;
+ }
+ free(t);
+ }
+
+ *(j++) = *i;
+ }
+
+ *j = NULL;
+
+ options = strv_join(*k, ",");
+ if (!options) {
+ free(controller);
+ r = log_oom();
+ goto finish;
+ }
+
+ } else {
+ options = controller;
+ controller = NULL;
+ }
+
+ where = strappend("/sys/fs/cgroup/", options);
+ if (!where) {
+ free(options);
+ r = log_oom();
+ goto finish;
+ }
+
+ zero(p);
+ p.what = "cgroup";
+ p.where = where;
+ p.type = "cgroup";
+ p.options = options;
+ p.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV;
+
+ r = mount_one(&p, true);
+ free(controller);
+ free(where);
+
+ if (r < 0) {
+ free(options);
+ goto finish;
+ }
+
+ if (r > 0 && k && *k) {
+ char **i;
+
+ for (i = *k; *i; i++) {
+ char *t;
+
+ t = strappend("/sys/fs/cgroup/", *i);
+ if (!t) {
+ r = log_oom();
+ free(options);
+ goto finish;
+ }
+
+ r = symlink(options, t);
+ free(t);
+
+ if (r < 0 && errno != EEXIST) {
+ log_error("Failed to create symlink: %m");
+ r = -errno;
+ free(options);
+ goto finish;
+ }
+ }
+ }
+
+ free(options);
+ }
+
+ r = 0;
+
+finish:
+ set_free_free(controllers);
+
+ fclose(f);
+
+ return r;
+}
+
+static int nftw_cb(
+ const char *fpath,
+ const struct stat *sb,
+ int tflag,
+ struct FTW *ftwbuf) {
+
+ /* No need to label /dev twice in a row... */
+ if (_unlikely_(ftwbuf->level == 0))
+ return FTW_CONTINUE;
+
+ label_fix(fpath, false, false);
+
+ /* /run/initramfs is static data and big, no need to
+ * dynamically relabel its contents at boot... */
+ if (_unlikely_(ftwbuf->level == 1 &&
+ tflag == FTW_D &&
+ streq(fpath, "/run/initramfs")))
+ return FTW_SKIP_SUBTREE;
+
+ return FTW_CONTINUE;
+};
+
+int mount_setup(bool loaded_policy) {
+
+ static const char relabel[] =
+ "/run/initramfs/root-fsck\0"
+ "/run/initramfs/shutdown\0";
+
+ int r;
+ unsigned i;
+ const char *j;
+
+ for (i = 0; i < ELEMENTSOF(mount_table); i ++) {
+ r = mount_one(mount_table + i, true);
+
+ if (r < 0)
+ return r;
+ }
+
+ /* Nodes in devtmpfs and /run need to be manually updated for
+ * the appropriate labels, after mounting. The other virtual
+ * API file systems like /sys and /proc do not need that, they
+ * use the same label for all their files. */
+ if (loaded_policy) {
+ usec_t before_relabel, after_relabel;
+ char timespan[FORMAT_TIMESPAN_MAX];
+
+ before_relabel = now(CLOCK_MONOTONIC);
+
+ nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
+ nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
+
+ /* Explicitly relabel these */
+ NULSTR_FOREACH(j, relabel)
+ label_fix(j, true, false);
+
+ after_relabel = now(CLOCK_MONOTONIC);
+
+ log_info("Relabelled /dev and /run in %s.",
+ format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel));
+ }
+
+ /* Create a few default symlinks, which are normally created
+ * by udevd, but some scripts might need them before we start
+ * udevd. */
+ dev_setup(NULL);
+
+ /* Mark the root directory as shared in regards to mount
+ * propagation. The kernel defaults to "private", but we think
+ * it makes more sense to have a default of "shared" so that
+ * nspawn and the container tools work out of the box. If
+ * specific setups need other settings they can reset the
+ * propagation mode to private if needed. */
+ if (detect_container(NULL) <= 0)
+ if (mount(NULL, "/", NULL, MS_REC|MS_SHARED, NULL) < 0)
+ log_warning("Failed to set up the root directory for shared mount propagation: %m");
+
+ /* Create a few directories we always want around */
+ mkdir_label("/run/systemd", 0755);
+ mkdir_label("/run/systemd/system", 0755);
+
+ return 0;
+}
diff --git a/src/core/mount-setup.h b/src/core/mount-setup.h
new file mode 100644
index 0000000000..4b521ad0e1
--- /dev/null
+++ b/src/core/mount-setup.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int mount_setup_early(void);
+
+int mount_setup(bool loaded_policy);
+
+int mount_cgroup_controllers(char ***join_controllers);
+
+bool mount_point_is_api(const char *path);
+bool mount_point_ignore(const char *path);
diff --git a/src/core/mount.c b/src/core/mount.c
new file mode 100644
index 0000000000..14f4863dc6
--- /dev/null
+++ b/src/core/mount.c
@@ -0,0 +1,1885 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdio.h>
+#include <mntent.h>
+#include <sys/epoll.h>
+#include <signal.h>
+
+#include "unit.h"
+#include "mount.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "strv.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "mount-setup.h"
+#include "unit-name.h"
+#include "dbus-mount.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "exit-status.h"
+#include "def.h"
+
+static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = {
+ [MOUNT_DEAD] = UNIT_INACTIVE,
+ [MOUNT_MOUNTING] = UNIT_ACTIVATING,
+ [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE,
+ [MOUNT_MOUNTED] = UNIT_ACTIVE,
+ [MOUNT_REMOUNTING] = UNIT_RELOADING,
+ [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING,
+ [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING,
+ [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING,
+ [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING,
+ [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING,
+ [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING,
+ [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING,
+ [MOUNT_FAILED] = UNIT_FAILED
+};
+
+static void mount_init(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ m->timeout_usec = DEFAULT_TIMEOUT_USEC;
+ m->directory_mode = 0755;
+
+ exec_context_init(&m->exec_context);
+
+ if (unit_has_name(u, "-.mount")) {
+ /* Don't allow start/stop for root directory */
+ UNIT(m)->refuse_manual_start = true;
+ UNIT(m)->refuse_manual_stop = true;
+ } else {
+ /* The stdio/kmsg bridge socket is on /, in order to avoid a
+ * dep loop, don't use kmsg logging for -.mount */
+ m->exec_context.std_output = u->manager->default_std_output;
+ m->exec_context.std_error = u->manager->default_std_error;
+ }
+
+ kill_context_init(&m->kill_context);
+
+ /* We need to make sure that /bin/mount is always called in
+ * the same process group as us, so that the autofs kernel
+ * side doesn't send us another mount request while we are
+ * already trying to comply its last one. */
+ m->exec_context.same_pgrp = true;
+
+ m->timer_watch.type = WATCH_INVALID;
+
+ m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID;
+
+ UNIT(m)->ignore_on_isolate = true;
+}
+
+static void mount_unwatch_control_pid(Mount *m) {
+ assert(m);
+
+ if (m->control_pid <= 0)
+ return;
+
+ unit_unwatch_pid(UNIT(m), m->control_pid);
+ m->control_pid = 0;
+}
+
+static void mount_parameters_done(MountParameters *p) {
+ assert(p);
+
+ free(p->what);
+ free(p->options);
+ free(p->fstype);
+
+ p->what = p->options = p->fstype = NULL;
+}
+
+static void mount_done(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ free(m->where);
+ m->where = NULL;
+
+ mount_parameters_done(&m->parameters_proc_self_mountinfo);
+ mount_parameters_done(&m->parameters_fragment);
+
+ exec_context_done(&m->exec_context);
+ exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX);
+ m->control_command = NULL;
+
+ mount_unwatch_control_pid(m);
+
+ unit_unwatch_timer(u, &m->timer_watch);
+}
+
+static MountParameters* get_mount_parameters_fragment(Mount *m) {
+ assert(m);
+
+ if (m->from_fragment)
+ return &m->parameters_fragment;
+
+ return NULL;
+}
+
+static MountParameters* get_mount_parameters(Mount *m) {
+ assert(m);
+
+ if (m->from_proc_self_mountinfo)
+ return &m->parameters_proc_self_mountinfo;
+
+ return get_mount_parameters_fragment(m);
+}
+
+static int mount_add_mount_links(Mount *m) {
+ Unit *other;
+ int r;
+ MountParameters *pm;
+
+ assert(m);
+
+ pm = get_mount_parameters_fragment(m);
+
+ /* Adds in links to other mount points that might lie below or
+ * above us in the hierarchy */
+
+ LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_MOUNT]) {
+ Mount *n = MOUNT(other);
+ MountParameters *pn;
+
+ if (n == m)
+ continue;
+
+ if (UNIT(n)->load_state != UNIT_LOADED)
+ continue;
+
+ pn = get_mount_parameters_fragment(n);
+
+ if (path_startswith(m->where, n->where)) {
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0)
+ return r;
+
+ if (pn)
+ if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0)
+ return r;
+
+ } else if (path_startswith(n->where, m->where)) {
+
+ if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0)
+ return r;
+
+ if (pm)
+ if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+
+ } else if (pm && pm->what && path_startswith(pm->what, n->where)) {
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0)
+ return r;
+
+ } else if (pn && pn->what && path_startswith(pn->what, m->where)) {
+
+ if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int mount_add_swap_links(Mount *m) {
+ Unit *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SWAP])
+ if ((r = swap_add_one_mount_link(SWAP(other), m)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_add_path_links(Mount *m) {
+ Unit *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_PATH])
+ if ((r = path_add_one_mount_link(PATH(other), m)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_add_automount_links(Mount *m) {
+ Unit *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_AUTOMOUNT])
+ if ((r = automount_add_one_mount_link(AUTOMOUNT(other), m)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_add_socket_links(Mount *m) {
+ Unit *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SOCKET])
+ if ((r = socket_add_one_mount_link(SOCKET(other), m)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_add_requires_mounts_links(Mount *m) {
+ Unit *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(has_requires_mounts_for, other, UNIT(m)->manager->has_requires_mounts_for) {
+ r = unit_add_one_mount_link(other, m);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static char* mount_test_option(const char *haystack, const char *needle) {
+ struct mntent me;
+
+ assert(needle);
+
+ /* Like glibc's hasmntopt(), but works on a string, not a
+ * struct mntent */
+
+ if (!haystack)
+ return NULL;
+
+ zero(me);
+ me.mnt_opts = (char*) haystack;
+
+ return hasmntopt(&me, needle);
+}
+
+static bool mount_is_network(MountParameters *p) {
+ assert(p);
+
+ if (mount_test_option(p->options, "_netdev"))
+ return true;
+
+ if (p->fstype && fstype_is_network(p->fstype))
+ return true;
+
+ return false;
+}
+
+static bool mount_is_bind(MountParameters *p) {
+ assert(p);
+
+ if (mount_test_option(p->options, "bind"))
+ return true;
+
+ if (p->fstype && streq(p->fstype, "bind"))
+ return true;
+
+ return false;
+}
+
+static bool needs_quota(MountParameters *p) {
+ assert(p);
+
+ if (mount_is_network(p))
+ return false;
+
+ if (mount_is_bind(p))
+ return false;
+
+ return mount_test_option(p->options, "usrquota") ||
+ mount_test_option(p->options, "grpquota") ||
+ mount_test_option(p->options, "quota") ||
+ mount_test_option(p->options, "usrjquota") ||
+ mount_test_option(p->options, "grpjquota");
+}
+
+static int mount_add_device_links(Mount *m) {
+ MountParameters *p;
+ int r;
+
+ assert(m);
+
+ p = get_mount_parameters_fragment(m);
+ if (!p)
+ return 0;
+
+ if (!p->what)
+ return 0;
+
+ if (mount_is_bind(p))
+ return 0;
+
+ if (!is_device_path(p->what))
+ return 0;
+
+ if (path_equal(m->where, "/"))
+ return 0;
+
+ r = unit_add_node_link(UNIT(m), p->what, false);
+ if (r < 0)
+ return r;
+
+ if (p->passno > 0 &&
+ UNIT(m)->manager->running_as == SYSTEMD_SYSTEM) {
+ char *name;
+ Unit *fsck;
+ /* Let's add in the fsck service */
+
+ /* aka SPECIAL_FSCK_SERVICE */
+ name = unit_name_from_path_instance("systemd-fsck", p->what, ".service");
+ if (!name)
+ return -ENOMEM;
+
+ r = manager_load_unit_prepare(UNIT(m)->manager, name, NULL, NULL, &fsck);
+ if (r < 0) {
+ log_warning("Failed to prepare unit %s: %s", name, strerror(-r));
+ free(name);
+ return r;
+ }
+ free(name);
+
+ SERVICE(fsck)->fsck_passno = p->passno;
+
+ r = unit_add_two_dependencies(UNIT(m), UNIT_AFTER, UNIT_REQUIRES, fsck, true);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int mount_add_quota_links(Mount *m) {
+ int r;
+ MountParameters *p;
+
+ assert(m);
+
+ if (UNIT(m)->manager->running_as != SYSTEMD_SYSTEM)
+ return 0;
+
+ p = get_mount_parameters_fragment(m);
+ if (!p)
+ return 0;
+
+ if (!needs_quota(p))
+ return 0;
+
+ r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true);
+ if (r < 0)
+ return r;
+
+ r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_add_default_dependencies(Mount *m) {
+ int r;
+ MountParameters *p;
+ const char *after;
+
+ assert(m);
+
+ if (UNIT(m)->manager->running_as != SYSTEMD_SYSTEM)
+ return 0;
+
+ p = get_mount_parameters_fragment(m);
+ if (!p)
+ return 0;
+
+ if (path_equal(m->where, "/"))
+ return 0;
+
+ if (mount_is_network(p))
+ after = SPECIAL_REMOTE_FS_PRE_TARGET;
+ else
+ after = SPECIAL_LOCAL_FS_PRE_TARGET;
+
+ r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, after, NULL, true);
+ if (r < 0)
+ return r;
+
+ r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_fix_timeouts(Mount *m) {
+ MountParameters *p;
+ const char *timeout = NULL;
+ Unit *other;
+ Iterator i;
+ usec_t u;
+ char *t;
+ int r;
+
+ assert(m);
+
+ p = get_mount_parameters_fragment(m);
+ if (!p)
+ return 0;
+
+ /* Allow configuration how long we wait for a device that
+ * backs a mount point to show up. This is useful to support
+ * endless device timeouts for devices that show up only after
+ * user input, like crypto devices. */
+
+ if ((timeout = mount_test_option(p->options, "comment=systemd.device-timeout")))
+ timeout += 31;
+ else if ((timeout = mount_test_option(p->options, "x-systemd.device-timeout")))
+ timeout += 25;
+ else
+ return 0;
+
+ t = strndup(timeout, strcspn(timeout, ",;" WHITESPACE));
+ if (!t)
+ return -ENOMEM;
+
+ r = parse_usec(t, &u);
+ free(t);
+
+ if (r < 0) {
+ log_warning("Failed to parse timeout for %s, ignoring: %s", m->where, timeout);
+ return r;
+ }
+
+ SET_FOREACH(other, UNIT(m)->dependencies[UNIT_AFTER], i) {
+ if (other->type != UNIT_DEVICE)
+ continue;
+
+ other->job_timeout = u;
+ }
+
+ return 0;
+}
+
+static int mount_verify(Mount *m) {
+ bool b;
+ char *e;
+ assert(m);
+
+ if (UNIT(m)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!m->from_fragment && !m->from_proc_self_mountinfo)
+ return -ENOENT;
+
+ if (!(e = unit_name_from_path(m->where, ".mount")))
+ return -ENOMEM;
+
+ b = unit_has_name(UNIT(m), e);
+ free(e);
+
+ if (!b) {
+ log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(m)->id);
+ return -EINVAL;
+ }
+
+ if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) {
+ log_error("Cannot create mount unit for API file system %s. Refusing.", m->where);
+ return -EINVAL;
+ }
+
+ if (UNIT(m)->fragment_path && !m->parameters_fragment.what) {
+ log_error("%s's What setting is missing. Refusing.", UNIT(m)->id);
+ return -EBADMSG;
+ }
+
+ if (m->exec_context.pam_name && m->kill_context.kill_mode != KILL_CONTROL_GROUP) {
+ log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(m)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mount_add_extras(Mount *m) {
+ Unit *u = UNIT(m);
+ int r;
+
+ if (UNIT(m)->fragment_path)
+ m->from_fragment = true;
+
+ if (!m->where) {
+ m->where = unit_name_to_path(u->id);
+ if (!m->where)
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(m->where);
+
+ r = unit_add_exec_dependencies(u, &m->exec_context);
+ if (r < 0)
+ return r;
+
+ if (!UNIT(m)->description) {
+ r = unit_set_description(u, m->where);
+ if (r < 0)
+ return r;
+ }
+
+ r = mount_add_device_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_mount_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_socket_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_swap_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_path_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_requires_mounts_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_automount_links(m);
+ if (r < 0)
+ return r;
+
+ r = mount_add_quota_links(m);
+ if (r < 0)
+ return r;
+
+ if (UNIT(m)->default_dependencies) {
+ r = mount_add_default_dependencies(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = unit_add_default_cgroups(u);
+ if (r < 0)
+ return r;
+
+ r = mount_fix_timeouts(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_load(Unit *u) {
+ Mount *m = MOUNT(u);
+ int r;
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ if (m->from_proc_self_mountinfo)
+ r = unit_load_fragment_and_dropin_optional(u);
+ else
+ r = unit_load_fragment_and_dropin(u);
+
+ if (r < 0)
+ return r;
+
+ /* This is a new unit? Then let's add in some extras */
+ if (u->load_state == UNIT_LOADED) {
+ r = mount_add_extras(m);
+ if (r < 0)
+ return r;
+
+ r = unit_exec_context_defaults(u, &m->exec_context);
+ if (r < 0)
+ return r;
+ }
+
+ return mount_verify(m);
+}
+
+static int mount_notify_automount(Mount *m, int status) {
+ Unit *p;
+ int r;
+ Iterator i;
+
+ assert(m);
+
+ SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i)
+ if (p->type == UNIT_AUTOMOUNT) {
+ r = automount_send_ready(AUTOMOUNT(p), status);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void mount_set_state(Mount *m, MountState state) {
+ MountState old_state;
+ assert(m);
+
+ old_state = m->state;
+ m->state = state;
+
+ if (state != MOUNT_MOUNTING &&
+ state != MOUNT_MOUNTING_DONE &&
+ state != MOUNT_REMOUNTING &&
+ state != MOUNT_UNMOUNTING &&
+ state != MOUNT_MOUNTING_SIGTERM &&
+ state != MOUNT_MOUNTING_SIGKILL &&
+ state != MOUNT_UNMOUNTING_SIGTERM &&
+ state != MOUNT_UNMOUNTING_SIGKILL &&
+ state != MOUNT_REMOUNTING_SIGTERM &&
+ state != MOUNT_REMOUNTING_SIGKILL) {
+ unit_unwatch_timer(UNIT(m), &m->timer_watch);
+ mount_unwatch_control_pid(m);
+ m->control_command = NULL;
+ m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID;
+ }
+
+ if (state == MOUNT_MOUNTED ||
+ state == MOUNT_REMOUNTING)
+ mount_notify_automount(m, 0);
+ else if (state == MOUNT_DEAD ||
+ state == MOUNT_UNMOUNTING ||
+ state == MOUNT_MOUNTING_SIGTERM ||
+ state == MOUNT_MOUNTING_SIGKILL ||
+ state == MOUNT_REMOUNTING_SIGTERM ||
+ state == MOUNT_REMOUNTING_SIGKILL ||
+ state == MOUNT_UNMOUNTING_SIGTERM ||
+ state == MOUNT_UNMOUNTING_SIGKILL ||
+ state == MOUNT_FAILED) {
+ if (state != old_state)
+ mount_notify_automount(m, -ENODEV);
+ }
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(m)->id,
+ mount_state_to_string(old_state),
+ mount_state_to_string(state));
+
+ unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS);
+ m->reload_result = MOUNT_SUCCESS;
+}
+
+static int mount_coldplug(Unit *u) {
+ Mount *m = MOUNT(u);
+ MountState new_state = MOUNT_DEAD;
+ int r;
+
+ assert(m);
+ assert(m->state == MOUNT_DEAD);
+
+ if (m->deserialized_state != m->state)
+ new_state = m->deserialized_state;
+ else if (m->from_proc_self_mountinfo)
+ new_state = MOUNT_MOUNTED;
+
+ if (new_state != m->state) {
+
+ if (new_state == MOUNT_MOUNTING ||
+ new_state == MOUNT_MOUNTING_DONE ||
+ new_state == MOUNT_REMOUNTING ||
+ new_state == MOUNT_UNMOUNTING ||
+ new_state == MOUNT_MOUNTING_SIGTERM ||
+ new_state == MOUNT_MOUNTING_SIGKILL ||
+ new_state == MOUNT_UNMOUNTING_SIGTERM ||
+ new_state == MOUNT_UNMOUNTING_SIGKILL ||
+ new_state == MOUNT_REMOUNTING_SIGTERM ||
+ new_state == MOUNT_REMOUNTING_SIGKILL) {
+
+ if (m->control_pid <= 0)
+ return -EBADMSG;
+
+ if ((r = unit_watch_pid(UNIT(m), m->control_pid)) < 0)
+ return r;
+
+ if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0)
+ return r;
+ }
+
+ mount_set_state(m, new_state);
+ }
+
+ return 0;
+}
+
+static void mount_dump(Unit *u, FILE *f, const char *prefix) {
+ Mount *m = MOUNT(u);
+ MountParameters *p;
+
+ assert(m);
+ assert(f);
+
+ p = get_mount_parameters(m);
+
+ fprintf(f,
+ "%sMount State: %s\n"
+ "%sResult: %s\n"
+ "%sWhere: %s\n"
+ "%sWhat: %s\n"
+ "%sFile System Type: %s\n"
+ "%sOptions: %s\n"
+ "%sFrom /proc/self/mountinfo: %s\n"
+ "%sFrom fragment: %s\n"
+ "%sDirectoryMode: %04o\n",
+ prefix, mount_state_to_string(m->state),
+ prefix, mount_result_to_string(m->result),
+ prefix, m->where,
+ prefix, strna(p->what),
+ prefix, strna(p->fstype),
+ prefix, strna(p->options),
+ prefix, yes_no(m->from_proc_self_mountinfo),
+ prefix, yes_no(m->from_fragment),
+ prefix, m->directory_mode);
+
+ if (m->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %lu\n",
+ prefix, (unsigned long) m->control_pid);
+
+ exec_context_dump(&m->exec_context, f, prefix);
+ kill_context_dump(&m->kill_context, f, prefix);
+}
+
+static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
+ pid_t pid;
+ int r;
+
+ assert(m);
+ assert(c);
+ assert(_pid);
+
+ if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0)
+ goto fail;
+
+ if ((r = exec_spawn(c,
+ NULL,
+ &m->exec_context,
+ NULL, 0,
+ UNIT(m)->manager->environment,
+ true,
+ true,
+ true,
+ UNIT(m)->manager->confirm_spawn,
+ UNIT(m)->cgroup_bondings,
+ UNIT(m)->cgroup_attributes,
+ NULL,
+ UNIT(m)->id,
+ NULL,
+ &pid)) < 0)
+ goto fail;
+
+ if ((r = unit_watch_pid(UNIT(m), pid)) < 0)
+ /* FIXME: we need to do something here */
+ goto fail;
+
+ *_pid = pid;
+
+ return 0;
+
+fail:
+ unit_unwatch_timer(UNIT(m), &m->timer_watch);
+
+ return r;
+}
+
+static void mount_enter_dead(Mount *m, MountResult f) {
+ assert(m);
+
+ if (f != MOUNT_SUCCESS)
+ m->result = f;
+
+ mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
+}
+
+static void mount_enter_mounted(Mount *m, MountResult f) {
+ assert(m);
+
+ if (f != MOUNT_SUCCESS)
+ m->result = f;
+
+ mount_set_state(m, MOUNT_MOUNTED);
+}
+
+static void mount_enter_signal(Mount *m, MountState state, MountResult f) {
+ int r;
+ Set *pid_set = NULL;
+ bool wait_for_exit = false;
+
+ assert(m);
+
+ if (f != MOUNT_SUCCESS)
+ m->result = f;
+
+ if (m->kill_context.kill_mode != KILL_NONE) {
+ int sig = (state == MOUNT_MOUNTING_SIGTERM ||
+ state == MOUNT_UNMOUNTING_SIGTERM ||
+ state == MOUNT_REMOUNTING_SIGTERM) ? m->kill_context.kill_signal : SIGKILL;
+
+ if (m->control_pid > 0) {
+ if (kill_and_sigcont(m->control_pid, sig) < 0 && errno != ESRCH)
+
+ log_warning("Failed to kill control process %li: %m", (long) m->control_pid);
+ else
+ wait_for_exit = true;
+ }
+
+ if (m->kill_context.kill_mode == KILL_CONTROL_GROUP) {
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (m->control_pid > 0)
+ if ((r = set_put(pid_set, LONG_TO_PTR(m->control_pid))) < 0)
+ goto fail;
+
+ r = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, sig, true, false, pid_set, NULL);
+ if (r < 0) {
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ log_warning("Failed to kill control group: %s", strerror(-r));
+ } else if (r > 0)
+ wait_for_exit = true;
+
+ set_free(pid_set);
+ pid_set = NULL;
+ }
+ }
+
+ if (wait_for_exit) {
+ if ((r = unit_watch_timer(UNIT(m), m->timeout_usec, &m->timer_watch)) < 0)
+ goto fail;
+
+ mount_set_state(m, state);
+ } else if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL)
+ mount_enter_mounted(m, MOUNT_SUCCESS);
+ else
+ mount_enter_dead(m, MOUNT_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", UNIT(m)->id, strerror(-r));
+
+ if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL)
+ mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES);
+ else
+ mount_enter_dead(m, MOUNT_FAILURE_RESOURCES);
+
+ if (pid_set)
+ set_free(pid_set);
+}
+
+static void mount_enter_unmounting(Mount *m) {
+ int r;
+
+ assert(m);
+
+ m->control_command_id = MOUNT_EXEC_UNMOUNT;
+ m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT;
+
+ if ((r = exec_command_set(
+ m->control_command,
+ "/bin/umount",
+ m->where,
+ NULL)) < 0)
+ goto fail;
+
+ mount_unwatch_control_pid(m);
+
+ if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0)
+ goto fail;
+
+ mount_set_state(m, MOUNT_UNMOUNTING);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'umount' task: %s", UNIT(m)->id, strerror(-r));
+ mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES);
+}
+
+static void mount_enter_mounting(Mount *m) {
+ int r;
+ MountParameters *p;
+
+ assert(m);
+
+ m->control_command_id = MOUNT_EXEC_MOUNT;
+ m->control_command = m->exec_command + MOUNT_EXEC_MOUNT;
+
+ mkdir_p_label(m->where, m->directory_mode);
+
+ if (dir_is_empty(m->where) <= 0)
+ log_notice("%s: Directory %s to mount over is not empty, mounting anyway. (To see the over-mounted files, please manually mount the underlying file system to a secondary location.)", m->meta.id, m->where);
+
+ /* Create the source directory for bind-mounts if needed */
+ p = get_mount_parameters_fragment(m);
+ if (p && mount_is_bind(p))
+ mkdir_p_label(p->what, m->directory_mode);
+
+ if (m->from_fragment)
+ r = exec_command_set(
+ m->control_command,
+ "/bin/mount",
+ m->parameters_fragment.what,
+ m->where,
+ "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto",
+ m->parameters_fragment.options ? "-o" : NULL, m->parameters_fragment.options,
+ NULL);
+ else
+ r = -ENOENT;
+
+ if (r < 0)
+ goto fail;
+
+ mount_unwatch_control_pid(m);
+
+ r = mount_spawn(m, m->control_command, &m->control_pid);
+ if (r < 0)
+ goto fail;
+
+ mount_set_state(m, MOUNT_MOUNTING);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'mount' task: %s", UNIT(m)->id, strerror(-r));
+ mount_enter_dead(m, MOUNT_FAILURE_RESOURCES);
+}
+
+static void mount_enter_mounting_done(Mount *m) {
+ assert(m);
+
+ mount_set_state(m, MOUNT_MOUNTING_DONE);
+}
+
+static void mount_enter_remounting(Mount *m) {
+ int r;
+
+ assert(m);
+
+ m->control_command_id = MOUNT_EXEC_REMOUNT;
+ m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT;
+
+ if (m->from_fragment) {
+ char *buf = NULL;
+ const char *o;
+
+ if (m->parameters_fragment.options) {
+ if (!(buf = strappend("remount,", m->parameters_fragment.options))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ o = buf;
+ } else
+ o = "remount";
+
+ r = exec_command_set(
+ m->control_command,
+ "/bin/mount",
+ m->parameters_fragment.what,
+ m->where,
+ "-t", m->parameters_fragment.fstype ? m->parameters_fragment.fstype : "auto",
+ "-o", o,
+ NULL);
+
+ free(buf);
+ } else
+ r = -ENOENT;
+
+ if (r < 0)
+ goto fail;
+
+ mount_unwatch_control_pid(m);
+
+ if ((r = mount_spawn(m, m->control_command, &m->control_pid)) < 0)
+ goto fail;
+
+ mount_set_state(m, MOUNT_REMOUNTING);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'remount' task: %s", UNIT(m)->id, strerror(-r));
+ m->reload_result = MOUNT_FAILURE_RESOURCES;
+ mount_enter_mounted(m, MOUNT_SUCCESS);
+}
+
+static int mount_start(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ /* We cannot fulfill this request right now, try again later
+ * please! */
+ if (m->state == MOUNT_UNMOUNTING ||
+ m->state == MOUNT_UNMOUNTING_SIGTERM ||
+ m->state == MOUNT_UNMOUNTING_SIGKILL ||
+ m->state == MOUNT_MOUNTING_SIGTERM ||
+ m->state == MOUNT_MOUNTING_SIGKILL)
+ return -EAGAIN;
+
+ /* Already on it! */
+ if (m->state == MOUNT_MOUNTING)
+ return 0;
+
+ assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED);
+
+ m->result = MOUNT_SUCCESS;
+ m->reload_result = MOUNT_SUCCESS;
+
+ mount_enter_mounting(m);
+ return 0;
+}
+
+static int mount_stop(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ /* Already on it */
+ if (m->state == MOUNT_UNMOUNTING ||
+ m->state == MOUNT_UNMOUNTING_SIGKILL ||
+ m->state == MOUNT_UNMOUNTING_SIGTERM ||
+ m->state == MOUNT_MOUNTING_SIGTERM ||
+ m->state == MOUNT_MOUNTING_SIGKILL)
+ return 0;
+
+ assert(m->state == MOUNT_MOUNTING ||
+ m->state == MOUNT_MOUNTING_DONE ||
+ m->state == MOUNT_MOUNTED ||
+ m->state == MOUNT_REMOUNTING ||
+ m->state == MOUNT_REMOUNTING_SIGTERM ||
+ m->state == MOUNT_REMOUNTING_SIGKILL);
+
+ mount_enter_unmounting(m);
+ return 0;
+}
+
+static int mount_reload(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ if (m->state == MOUNT_MOUNTING_DONE)
+ return -EAGAIN;
+
+ assert(m->state == MOUNT_MOUNTED);
+
+ mount_enter_remounting(m);
+ return 0;
+}
+
+static int mount_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", mount_state_to_string(m->state));
+ unit_serialize_item(u, f, "result", mount_result_to_string(m->result));
+ unit_serialize_item(u, f, "reload-result", mount_result_to_string(m->reload_result));
+
+ if (m->control_pid > 0)
+ unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) m->control_pid);
+
+ if (m->control_command_id >= 0)
+ unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(m->control_command_id));
+
+ return 0;
+}
+
+static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Mount *m = MOUNT(u);
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ MountState state;
+
+ if ((state = mount_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ m->deserialized_state = state;
+ } else if (streq(key, "result")) {
+ MountResult f;
+
+ f = mount_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != MOUNT_SUCCESS)
+ m->result = f;
+
+ } else if (streq(key, "reload-result")) {
+ MountResult f;
+
+ f = mount_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse reload result value %s", value);
+ else if (f != MOUNT_SUCCESS)
+ m->reload_result = f;
+
+ } else if (streq(key, "control-pid")) {
+ pid_t pid;
+
+ if (parse_pid(value, &pid) < 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ m->control_pid = pid;
+ } else if (streq(key, "control-command")) {
+ MountExecCommand id;
+
+ if ((id = mount_exec_command_from_string(value)) < 0)
+ log_debug("Failed to parse exec-command value %s", value);
+ else {
+ m->control_command_id = id;
+ m->control_command = m->exec_command + id;
+ }
+
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState mount_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[MOUNT(u)->state];
+}
+
+static const char *mount_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return mount_state_to_string(MOUNT(u)->state);
+}
+
+static bool mount_check_gc(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ return m->from_proc_self_mountinfo;
+}
+
+static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Mount *m = MOUNT(u);
+ MountResult f;
+
+ assert(m);
+ assert(pid >= 0);
+
+ if (pid != m->control_pid)
+ return;
+
+ m->control_pid = 0;
+
+ if (is_clean_exit(code, status, NULL))
+ f = MOUNT_SUCCESS;
+ else if (code == CLD_EXITED)
+ f = MOUNT_FAILURE_EXIT_CODE;
+ else if (code == CLD_KILLED)
+ f = MOUNT_FAILURE_SIGNAL;
+ else if (code == CLD_DUMPED)
+ f = MOUNT_FAILURE_CORE_DUMP;
+ else
+ assert_not_reached("Unknown code");
+
+ if (f != MOUNT_SUCCESS)
+ m->result = f;
+
+ if (m->control_command) {
+ exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status);
+
+ m->control_command = NULL;
+ m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID;
+ }
+
+ log_full(f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE,
+ "%s mount process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status);
+
+ /* Note that mount(8) returning and the kernel sending us a
+ * mount table change event might happen out-of-order. If an
+ * operation succeed we assume the kernel will follow soon too
+ * and already change into the resulting state. If it fails
+ * we check if the kernel still knows about the mount. and
+ * change state accordingly. */
+
+ switch (m->state) {
+
+ case MOUNT_MOUNTING:
+ case MOUNT_MOUNTING_DONE:
+ case MOUNT_MOUNTING_SIGKILL:
+ case MOUNT_MOUNTING_SIGTERM:
+
+ if (f == MOUNT_SUCCESS)
+ mount_enter_mounted(m, f);
+ else if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, f);
+ else
+ mount_enter_dead(m, f);
+ break;
+
+ case MOUNT_REMOUNTING:
+ case MOUNT_REMOUNTING_SIGKILL:
+ case MOUNT_REMOUNTING_SIGTERM:
+
+ m->reload_result = f;
+ if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, MOUNT_SUCCESS);
+ else
+ mount_enter_dead(m, MOUNT_SUCCESS);
+
+ break;
+
+ case MOUNT_UNMOUNTING:
+ case MOUNT_UNMOUNTING_SIGKILL:
+ case MOUNT_UNMOUNTING_SIGTERM:
+
+ if (f == MOUNT_SUCCESS)
+ mount_enter_dead(m, f);
+ else if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, f);
+ else
+ mount_enter_dead(m, f);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+
+ /* Notify clients about changed exit status */
+ unit_add_to_dbus_queue(u);
+}
+
+static void mount_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+ assert(elapsed == 1);
+ assert(w == &m->timer_watch);
+
+ switch (m->state) {
+
+ case MOUNT_MOUNTING:
+ case MOUNT_MOUNTING_DONE:
+ log_warning("%s mounting timed out. Stopping.", u->id);
+ mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT);
+ break;
+
+ case MOUNT_REMOUNTING:
+ log_warning("%s remounting timed out. Stopping.", u->id);
+ m->reload_result = MOUNT_FAILURE_TIMEOUT;
+ mount_enter_mounted(m, MOUNT_SUCCESS);
+ break;
+
+ case MOUNT_UNMOUNTING:
+ log_warning("%s unmounting timed out. Stopping.", u->id);
+ mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT);
+ break;
+
+ case MOUNT_MOUNTING_SIGTERM:
+ if (m->kill_context.send_sigkill) {
+ log_warning("%s mounting timed out. Killing.", u->id);
+ mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s mounting timed out. Skipping SIGKILL. Ignoring.", u->id);
+
+ if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT);
+ else
+ mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case MOUNT_REMOUNTING_SIGTERM:
+ if (m->kill_context.send_sigkill) {
+ log_warning("%s remounting timed out. Killing.", u->id);
+ mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s remounting timed out. Skipping SIGKILL. Ignoring.", u->id);
+
+ if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT);
+ else
+ mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case MOUNT_UNMOUNTING_SIGTERM:
+ if (m->kill_context.send_sigkill) {
+ log_warning("%s unmounting timed out. Killing.", u->id);
+ mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s unmounting timed out. Skipping SIGKILL. Ignoring.", u->id);
+
+ if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT);
+ else
+ mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case MOUNT_MOUNTING_SIGKILL:
+ case MOUNT_REMOUNTING_SIGKILL:
+ case MOUNT_UNMOUNTING_SIGKILL:
+ log_warning("%s mount process still around after SIGKILL. Ignoring.", u->id);
+
+ if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT);
+ else
+ mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT);
+ break;
+
+ default:
+ assert_not_reached("Timeout at wrong time.");
+ }
+}
+
+static int mount_add_one(
+ Manager *m,
+ const char *what,
+ const char *where,
+ const char *options,
+ const char *fstype,
+ int passno,
+ bool set_flags) {
+ int r;
+ Unit *u;
+ bool delete;
+ char *e, *w = NULL, *o = NULL, *f = NULL;
+ MountParameters *p;
+ bool load_extras = false;
+
+ assert(m);
+ assert(what);
+ assert(where);
+ assert(options);
+ assert(fstype);
+
+ /* Ignore API mount points. They should never be referenced in
+ * dependencies ever. */
+ if (mount_point_is_api(where) || mount_point_ignore(where))
+ return 0;
+
+ if (streq(fstype, "autofs"))
+ return 0;
+
+ /* probably some kind of swap, ignore */
+ if (!is_path(where))
+ return 0;
+
+ e = unit_name_from_path(where, ".mount");
+ if (!e)
+ return -ENOMEM;
+
+ u = manager_get_unit(m, e);
+ if (!u) {
+ delete = true;
+
+ u = unit_new(m, sizeof(Mount));
+ if (!u) {
+ free(e);
+ return -ENOMEM;
+ }
+
+ r = unit_add_name(u, e);
+ free(e);
+
+ if (r < 0)
+ goto fail;
+
+ MOUNT(u)->where = strdup(where);
+ if (!MOUNT(u)->where) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ unit_add_to_load_queue(u);
+ } else {
+ delete = false;
+ free(e);
+
+ if (!MOUNT(u)->where) {
+ MOUNT(u)->where = strdup(where);
+ if (!MOUNT(u)->where) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (u->load_state == UNIT_ERROR) {
+ u->load_state = UNIT_LOADED;
+ u->load_error = 0;
+
+ /* Load in the extras later on, after we
+ * finished initialization of the unit */
+ load_extras = true;
+ }
+ }
+
+ if (!(w = strdup(what)) ||
+ !(o = strdup(options)) ||
+ !(f = strdup(fstype))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ p = &MOUNT(u)->parameters_proc_self_mountinfo;
+ if (set_flags) {
+ MOUNT(u)->is_mounted = true;
+ MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo;
+ MOUNT(u)->just_changed = !streq_ptr(p->options, o);
+ }
+
+ MOUNT(u)->from_proc_self_mountinfo = true;
+
+ free(p->what);
+ p->what = w;
+
+ free(p->options);
+ p->options = o;
+
+ free(p->fstype);
+ p->fstype = f;
+
+ p->passno = passno;
+
+ if (load_extras) {
+ r = mount_add_extras(MOUNT(u));
+ if (r < 0)
+ goto fail;
+ }
+
+ unit_add_to_dbus_queue(u);
+
+ return 0;
+
+fail:
+ free(w);
+ free(o);
+ free(f);
+
+ if (delete && u)
+ unit_free(u);
+
+ return r;
+}
+
+static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) {
+ int r = 0;
+ unsigned i;
+ char *device, *path, *options, *options2, *fstype, *d, *p, *o;
+
+ assert(m);
+
+ rewind(m->proc_self_mountinfo);
+
+ for (i = 1;; i++) {
+ int k;
+
+ device = path = options = options2 = fstype = d = p = o = NULL;
+
+ if ((k = fscanf(m->proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%ms" /* (6) mount options */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%ms " /* (9) file system type */
+ "%ms" /* (10) mount source */
+ "%ms" /* (11) mount options 2 */
+ "%*[^\n]", /* some rubbish at the end */
+ &path,
+ &options,
+ &fstype,
+ &device,
+ &options2)) != 5) {
+
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
+ goto clean_up;
+ }
+
+ o = strjoin(options, ",", options2, NULL);
+ if (!o) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(d = cunescape(device)) ||
+ !(p = cunescape(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((k = mount_add_one(m, d, p, o, fstype, 0, set_flags)) < 0)
+ r = k;
+
+clean_up:
+ free(device);
+ free(path);
+ free(options);
+ free(options2);
+ free(fstype);
+ free(d);
+ free(p);
+ free(o);
+ }
+
+finish:
+ free(device);
+ free(path);
+ free(options);
+ free(options2);
+ free(fstype);
+ free(d);
+ free(p);
+ free(o);
+
+ return r;
+}
+
+static void mount_shutdown(Manager *m) {
+ assert(m);
+
+ if (m->proc_self_mountinfo) {
+ fclose(m->proc_self_mountinfo);
+ m->proc_self_mountinfo = NULL;
+ }
+}
+
+static int mount_enumerate(Manager *m) {
+ int r;
+ struct epoll_event ev;
+ assert(m);
+
+ if (!m->proc_self_mountinfo) {
+ if (!(m->proc_self_mountinfo = fopen("/proc/self/mountinfo", "re")))
+ return -errno;
+
+ m->mount_watch.type = WATCH_MOUNT;
+ m->mount_watch.fd = fileno(m->proc_self_mountinfo);
+
+ zero(ev);
+ ev.events = EPOLLPRI;
+ ev.data.ptr = &m->mount_watch;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->mount_watch.fd, &ev) < 0)
+ return -errno;
+ }
+
+ if ((r = mount_load_proc_self_mountinfo(m, false)) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ mount_shutdown(m);
+ return r;
+}
+
+void mount_fd_event(Manager *m, int events) {
+ Unit *u;
+ int r;
+
+ assert(m);
+ assert(events & EPOLLPRI);
+
+ /* The manager calls this for every fd event happening on the
+ * /proc/self/mountinfo file, which informs us about mounting
+ * table changes */
+
+ if ((r = mount_load_proc_self_mountinfo(m, true)) < 0) {
+ log_error("Failed to reread /proc/self/mountinfo: %s", strerror(-r));
+
+ /* Reset flags, just in case, for later calls */
+ LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) {
+ Mount *mount = MOUNT(u);
+
+ mount->is_mounted = mount->just_mounted = mount->just_changed = false;
+ }
+
+ return;
+ }
+
+ manager_dispatch_load_queue(m);
+
+ LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) {
+ Mount *mount = MOUNT(u);
+
+ if (!mount->is_mounted) {
+ /* This has just been unmounted. */
+
+ mount->from_proc_self_mountinfo = false;
+
+ switch (mount->state) {
+
+ case MOUNT_MOUNTED:
+ mount_enter_dead(mount, MOUNT_SUCCESS);
+ break;
+
+ default:
+ mount_set_state(mount, mount->state);
+ break;
+
+ }
+
+ } else if (mount->just_mounted || mount->just_changed) {
+
+ /* New or changed mount entry */
+
+ switch (mount->state) {
+
+ case MOUNT_DEAD:
+ case MOUNT_FAILED:
+ mount_enter_mounted(mount, MOUNT_SUCCESS);
+ break;
+
+ case MOUNT_MOUNTING:
+ mount_enter_mounting_done(mount);
+ break;
+
+ default:
+ /* Nothing really changed, but let's
+ * issue an notification call
+ * nonetheless, in case somebody is
+ * waiting for this. (e.g. file system
+ * ro/rw remounts.) */
+ mount_set_state(mount, mount->state);
+ break;
+ }
+ }
+
+ /* Reset the flags for later calls */
+ mount->is_mounted = mount->just_mounted = mount->just_changed = false;
+ }
+}
+
+static void mount_reset_failed(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ if (m->state == MOUNT_FAILED)
+ mount_set_state(m, MOUNT_DEAD);
+
+ m->result = MOUNT_SUCCESS;
+ m->reload_result = MOUNT_SUCCESS;
+}
+
+static int mount_kill(Unit *u, KillWho who, int signo, DBusError *error) {
+ Mount *m = MOUNT(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(m);
+
+ if (who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Mount units have no main processes");
+ return -ESRCH;
+ }
+
+ if (m->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ESRCH;
+ }
+
+ if (who == KILL_CONTROL || who == KILL_ALL)
+ if (m->control_pid > 0)
+ if (kill(m->control_pid, signo) < 0)
+ r = -errno;
+
+ if (who == KILL_ALL) {
+ int q;
+
+ pid_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!pid_set)
+ return -ENOMEM;
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (m->control_pid > 0) {
+ q = set_put(pid_set, LONG_TO_PTR(m->control_pid));
+ if (q < 0) {
+ r = q;
+ goto finish;
+ }
+ }
+
+ q = cgroup_bonding_kill_list(UNIT(m)->cgroup_bondings, signo, false, false, pid_set, NULL);
+ if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
+static const char* const mount_state_table[_MOUNT_STATE_MAX] = {
+ [MOUNT_DEAD] = "dead",
+ [MOUNT_MOUNTING] = "mounting",
+ [MOUNT_MOUNTING_DONE] = "mounting-done",
+ [MOUNT_MOUNTED] = "mounted",
+ [MOUNT_REMOUNTING] = "remounting",
+ [MOUNT_UNMOUNTING] = "unmounting",
+ [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm",
+ [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill",
+ [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm",
+ [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill",
+ [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm",
+ [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill",
+ [MOUNT_FAILED] = "failed"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState);
+
+static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = {
+ [MOUNT_EXEC_MOUNT] = "ExecMount",
+ [MOUNT_EXEC_UNMOUNT] = "ExecUnmount",
+ [MOUNT_EXEC_REMOUNT] = "ExecRemount",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand);
+
+static const char* const mount_result_table[_MOUNT_RESULT_MAX] = {
+ [MOUNT_SUCCESS] = "success",
+ [MOUNT_FAILURE_RESOURCES] = "resources",
+ [MOUNT_FAILURE_TIMEOUT] = "timeout",
+ [MOUNT_FAILURE_EXIT_CODE] = "exit-code",
+ [MOUNT_FAILURE_SIGNAL] = "signal",
+ [MOUNT_FAILURE_CORE_DUMP] = "core-dump"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult);
+
+const UnitVTable mount_vtable = {
+ .object_size = sizeof(Mount),
+ .exec_context_offset = offsetof(Mount, exec_context),
+
+ .sections =
+ "Unit\0"
+ "Mount\0"
+ "Install\0",
+
+ .no_alias = true,
+ .no_instances = true,
+
+ .init = mount_init,
+ .load = mount_load,
+ .done = mount_done,
+
+ .coldplug = mount_coldplug,
+
+ .dump = mount_dump,
+
+ .start = mount_start,
+ .stop = mount_stop,
+ .reload = mount_reload,
+
+ .kill = mount_kill,
+
+ .serialize = mount_serialize,
+ .deserialize_item = mount_deserialize_item,
+
+ .active_state = mount_active_state,
+ .sub_state_to_string = mount_sub_state_to_string,
+
+ .check_gc = mount_check_gc,
+
+ .sigchld_event = mount_sigchld_event,
+ .timer_event = mount_timer_event,
+
+ .reset_failed = mount_reset_failed,
+
+ .bus_interface = "org.freedesktop.systemd1.Mount",
+ .bus_message_handler = bus_mount_message_handler,
+ .bus_invalidating_properties = bus_mount_invalidating_properties,
+
+ .enumerate = mount_enumerate,
+ .shutdown = mount_shutdown,
+
+ .status_message_formats = {
+ .starting_stopping = {
+ [0] = "Mounting %s...",
+ [1] = "Unmounting %s...",
+ },
+ .finished_start_job = {
+ [JOB_DONE] = "Mounted %s.",
+ [JOB_FAILED] = "Failed to mount %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ [JOB_TIMEOUT] = "Timed out mounting %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Unmounted %s.",
+ [JOB_FAILED] = "Failed unmounting %s.",
+ [JOB_TIMEOUT] = "Timed out unmounting %s.",
+ },
+ },
+};
diff --git a/src/core/mount.h b/src/core/mount.h
new file mode 100644
index 0000000000..67d6132a5d
--- /dev/null
+++ b/src/core/mount.h
@@ -0,0 +1,121 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Mount Mount;
+
+#include "unit.h"
+#include "kill.h"
+
+typedef enum MountState {
+ MOUNT_DEAD,
+ MOUNT_MOUNTING, /* /bin/mount is running, but the mount is not done yet. */
+ MOUNT_MOUNTING_DONE, /* /bin/mount is running, and the mount is done. */
+ MOUNT_MOUNTED,
+ MOUNT_REMOUNTING,
+ MOUNT_UNMOUNTING,
+ MOUNT_MOUNTING_SIGTERM,
+ MOUNT_MOUNTING_SIGKILL,
+ MOUNT_REMOUNTING_SIGTERM,
+ MOUNT_REMOUNTING_SIGKILL,
+ MOUNT_UNMOUNTING_SIGTERM,
+ MOUNT_UNMOUNTING_SIGKILL,
+ MOUNT_FAILED,
+ _MOUNT_STATE_MAX,
+ _MOUNT_STATE_INVALID = -1
+} MountState;
+
+typedef enum MountExecCommand {
+ MOUNT_EXEC_MOUNT,
+ MOUNT_EXEC_UNMOUNT,
+ MOUNT_EXEC_REMOUNT,
+ _MOUNT_EXEC_COMMAND_MAX,
+ _MOUNT_EXEC_COMMAND_INVALID = -1
+} MountExecCommand;
+
+typedef struct MountParameters {
+ char *what;
+ char *options;
+ char *fstype;
+ int passno;
+} MountParameters;
+
+typedef enum MountResult {
+ MOUNT_SUCCESS,
+ MOUNT_FAILURE_RESOURCES,
+ MOUNT_FAILURE_TIMEOUT,
+ MOUNT_FAILURE_EXIT_CODE,
+ MOUNT_FAILURE_SIGNAL,
+ MOUNT_FAILURE_CORE_DUMP,
+ _MOUNT_RESULT_MAX,
+ _MOUNT_RESULT_INVALID = -1
+} MountResult;
+
+struct Mount {
+ Unit meta;
+
+ char *where;
+
+ MountParameters parameters_proc_self_mountinfo;
+ MountParameters parameters_fragment;
+
+ bool from_proc_self_mountinfo:1;
+ bool from_fragment:1;
+
+ /* Used while looking for mount points that vanished or got
+ * added from/to /proc/self/mountinfo */
+ bool is_mounted:1;
+ bool just_mounted:1;
+ bool just_changed:1;
+
+ MountResult result;
+ MountResult reload_result;
+
+ mode_t directory_mode;
+
+ usec_t timeout_usec;
+
+ ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX];
+ ExecContext exec_context;
+ KillContext kill_context;
+
+ MountState state, deserialized_state;
+
+ ExecCommand* control_command;
+ MountExecCommand control_command_id;
+ pid_t control_pid;
+
+ Watch timer_watch;
+};
+
+extern const UnitVTable mount_vtable;
+
+void mount_fd_event(Manager *m, int events);
+
+const char* mount_state_to_string(MountState i);
+MountState mount_state_from_string(const char *s);
+
+const char* mount_exec_command_to_string(MountExecCommand i);
+MountExecCommand mount_exec_command_from_string(const char *s);
+
+const char* mount_result_to_string(MountResult i);
+MountResult mount_result_from_string(const char *s);
diff --git a/src/core/namespace.c b/src/core/namespace.c
new file mode 100644
index 0000000000..ba18ddc5b0
--- /dev/null
+++ b/src/core/namespace.c
@@ -0,0 +1,330 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/mount.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <limits.h>
+#include <linux/fs.h>
+
+#include "strv.h"
+#include "util.h"
+#include "path-util.h"
+#include "namespace.h"
+#include "missing.h"
+
+typedef enum PathMode {
+ /* This is ordered by priority! */
+ INACCESSIBLE,
+ READONLY,
+ PRIVATE_TMP,
+ PRIVATE_VAR_TMP,
+ READWRITE
+} PathMode;
+
+typedef struct Path {
+ const char *path;
+ PathMode mode;
+ bool done;
+} Path;
+
+static int append_paths(Path **p, char **strv, PathMode mode) {
+ char **i;
+
+ STRV_FOREACH(i, strv) {
+
+ if (!path_is_absolute(*i))
+ return -EINVAL;
+
+ (*p)->path = *i;
+ (*p)->mode = mode;
+ (*p)++;
+ }
+
+ return 0;
+}
+
+static int path_compare(const void *a, const void *b) {
+ const Path *p = a, *q = b;
+
+ if (path_equal(p->path, q->path)) {
+
+ /* If the paths are equal, check the mode */
+ if (p->mode < q->mode)
+ return -1;
+
+ if (p->mode > q->mode)
+ return 1;
+
+ return 0;
+ }
+
+ /* If the paths are not equal, then order prefixes first */
+ if (path_startswith(p->path, q->path))
+ return 1;
+
+ if (path_startswith(q->path, p->path))
+ return -1;
+
+ return 0;
+}
+
+static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible) {
+ Path *f, *t, *previous;
+
+ assert(p);
+ assert(n);
+ assert(need_inaccessible);
+
+ for (f = p, t = p, previous = NULL; f < p+*n; f++) {
+
+ /* The first one wins */
+ if (previous && path_equal(f->path, previous->path))
+ continue;
+
+ t->path = f->path;
+ t->mode = f->mode;
+
+ if (t->mode == INACCESSIBLE)
+ *need_inaccessible = true;
+
+ previous = t;
+
+ t++;
+ }
+
+ *n = t - p;
+}
+
+static int apply_mount(
+ Path *p,
+ const char *tmp_dir,
+ const char *var_tmp_dir,
+ const char *inaccessible_dir) {
+
+ const char *what;
+ int r;
+
+ assert(p);
+
+ switch (p->mode) {
+
+ case INACCESSIBLE:
+ what = inaccessible_dir;
+ break;
+
+ case READONLY:
+ case READWRITE:
+ what = p->path;
+ break;
+
+ case PRIVATE_TMP:
+ what = tmp_dir;
+ break;
+
+ case PRIVATE_VAR_TMP:
+ what = var_tmp_dir;
+ break;
+
+ default:
+ assert_not_reached("Unknown mode");
+ }
+
+ assert(what);
+
+ r = mount(what, p->path, NULL, MS_BIND|MS_REC, NULL);
+ if (r >= 0)
+ log_debug("Successfully mounted %s to %s", what, p->path);
+
+ return r;
+}
+
+static int make_read_only(Path *p) {
+ int r;
+
+ assert(p);
+
+ if (p->mode != INACCESSIBLE && p->mode != READONLY)
+ return 0;
+
+ r = mount(NULL, p->path, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+int setup_namespace(
+ char **writable,
+ char **readable,
+ char **inaccessible,
+ bool private_tmp,
+ unsigned long flags) {
+
+ char
+ tmp_dir[] = "/tmp/systemd-private-XXXXXX",
+ var_tmp_dir[] = "/var/tmp/systemd-private-XXXXXX",
+ inaccessible_dir[] = "/tmp/systemd-inaccessible-XXXXXX";
+
+ Path *paths, *p;
+ unsigned n;
+ bool need_inaccessible = false;
+ bool remove_tmp = false, remove_var_tmp = false, remove_inaccessible = false;
+ int r;
+
+ if (!flags)
+ flags = MS_SHARED;
+
+ n =
+ strv_length(writable) +
+ strv_length(readable) +
+ strv_length(inaccessible) +
+ (private_tmp ? 2 : 0);
+
+ p = paths = alloca(sizeof(Path) * n);
+ if ((r = append_paths(&p, writable, READWRITE)) < 0 ||
+ (r = append_paths(&p, readable, READONLY)) < 0 ||
+ (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0)
+ goto fail;
+
+ if (private_tmp) {
+ p->path = "/tmp";
+ p->mode = PRIVATE_TMP;
+ p++;
+
+ p->path = "/var/tmp";
+ p->mode = PRIVATE_VAR_TMP;
+ p++;
+ }
+
+ assert(paths + n == p);
+
+ qsort(paths, n, sizeof(Path), path_compare);
+ drop_duplicates(paths, &n, &need_inaccessible);
+
+ if (need_inaccessible) {
+ mode_t u;
+ char *d;
+
+ u = umask(0777);
+ d = mkdtemp(inaccessible_dir);
+ umask(u);
+
+ if (!d) {
+ r = -errno;
+ goto fail;
+ }
+
+ remove_inaccessible = true;
+ }
+
+ if (private_tmp) {
+ mode_t u;
+ char *d;
+
+ u = umask(0000);
+ d = mkdtemp(tmp_dir);
+ umask(u);
+
+ if (!d) {
+ r = -errno;
+ goto fail;
+ }
+
+ remove_tmp = true;
+
+ u = umask(0000);
+ d = mkdtemp(var_tmp_dir);
+ umask(u);
+
+ if (!d) {
+ r = -errno;
+ goto fail;
+ }
+
+ remove_var_tmp = true;
+
+ if (chmod(tmp_dir, 0777 + S_ISVTX) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (chmod(var_tmp_dir, 0777 + S_ISVTX) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ if (unshare(CLONE_NEWNS) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Remount / as SLAVE so that nothing now mounted in the namespace
+ shows up in the parent */
+ if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ for (p = paths; p < paths + n; p++) {
+ r = apply_mount(p, tmp_dir, var_tmp_dir, inaccessible_dir);
+ if (r < 0)
+ goto undo_mounts;
+ }
+
+ for (p = paths; p < paths + n; p++) {
+ r = make_read_only(p);
+ if (r < 0)
+ goto undo_mounts;
+ }
+
+ /* Remount / as the desired mode */
+ if (mount(NULL, "/", NULL, flags|MS_REC, NULL) < 0) {
+ r = -errno;
+ goto undo_mounts;
+ }
+
+ return 0;
+
+undo_mounts:
+ for (p = paths; p < paths + n; p++)
+ if (p->done)
+ umount2(p->path, MNT_DETACH);
+
+fail:
+ if (remove_inaccessible)
+ rmdir(inaccessible_dir);
+
+ if (remove_tmp)
+ rmdir(tmp_dir);
+
+ if (remove_var_tmp)
+ rmdir(var_tmp_dir);
+
+ return r;
+}
diff --git a/src/core/namespace.h b/src/core/namespace.h
new file mode 100644
index 0000000000..5d72ed91fb
--- /dev/null
+++ b/src/core/namespace.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int setup_namespace(
+ char **writable,
+ char **readable,
+ char **inaccessible,
+ bool private_tmp,
+ unsigned long flags);
diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf
new file mode 100644
index 0000000000..a07a8e1ce3
--- /dev/null
+++ b/src/core/org.freedesktop.systemd1.conf
@@ -0,0 +1,92 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<busconfig>
+
+ <policy user="root">
+ <allow own="org.freedesktop.systemd1"/>
+
+ <!-- Root clients can do everything -->
+ <allow send_destination="org.freedesktop.systemd1"/>
+ <allow receive_sender="org.freedesktop.systemd1"/>
+
+ <!-- systemd may receive activator requests -->
+ <allow receive_interface="org.freedesktop.systemd1.Activator"
+ receive_member="ActivationRequest"/>
+ </policy>
+
+ <policy context="default">
+ <deny send_destination="org.freedesktop.systemd1"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.DBus.Introspectable"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.DBus.Peer"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="Get"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="GetAll"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="GetUnit"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="GetUnitByPID"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="LoadUnit"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="GetJob"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="ListUnits"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="ListUnitFiles"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="GetUnitFileState"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="ListJobs"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="Subscribe"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="Unsubscribe"/>
+
+ <allow send_destination="org.freedesktop.systemd1"
+ send_interface="org.freedesktop.systemd1.Manager"
+ send_member="Dump"/>
+
+ <allow receive_sender="org.freedesktop.systemd1"/>
+ </policy>
+
+</busconfig>
diff --git a/src/core/org.freedesktop.systemd1.policy.in.in b/src/core/org.freedesktop.systemd1.policy.in.in
new file mode 100644
index 0000000000..51bdafac45
--- /dev/null
+++ b/src/core/org.freedesktop.systemd1.policy.in.in
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<policyconfig>
+
+ <vendor>The systemd Project</vendor>
+ <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+ <action id="org.freedesktop.systemd1.reply-password">
+ <_description>Send passphrase back to system</_description>
+ <_message>Authentication is required to send the entered passphrase back to the system.</_message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@rootlibexecdir@/systemd-reply-password</annotate>
+ </action>
+
+ <action id="org.freedesktop.systemd1.bus-access">
+ <_description>Privileged system and service manager access</_description>
+ <_message>Authentication is required to access the system and service manager.</_message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@bindir@/systemd-stdio-bridge</annotate>
+ </action>
+
+</policyconfig>
diff --git a/src/core/org.freedesktop.systemd1.service b/src/core/org.freedesktop.systemd1.service
new file mode 100644
index 0000000000..d4df3e93a2
--- /dev/null
+++ b/src/core/org.freedesktop.systemd1.service
@@ -0,0 +1,11 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.systemd1
+Exec=/bin/false
+User=root
diff --git a/src/core/path.c b/src/core/path.c
new file mode 100644
index 0000000000..3936971b41
--- /dev/null
+++ b/src/core/path.c
@@ -0,0 +1,773 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "unit.h"
+#include "unit-name.h"
+#include "path.h"
+#include "mkdir.h"
+#include "dbus-path.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "path-util.h"
+
+static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
+ [PATH_DEAD] = UNIT_INACTIVE,
+ [PATH_WAITING] = UNIT_ACTIVE,
+ [PATH_RUNNING] = UNIT_ACTIVE,
+ [PATH_FAILED] = UNIT_FAILED
+};
+
+int path_spec_watch(PathSpec *s, Unit *u) {
+
+ static const int flags_table[_PATH_TYPE_MAX] = {
+ [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
+ [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB,
+ [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
+ [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY,
+ [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO
+ };
+
+ bool exists = false;
+ char *k, *slash;
+ int r;
+
+ assert(u);
+ assert(s);
+
+ path_spec_unwatch(s, u);
+
+ if (!(k = strdup(s->path)))
+ return -ENOMEM;
+
+ if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (unit_watch_fd(u, s->inotify_fd, EPOLLIN, &s->watch) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type]);
+ if (s->primary_wd >= 0)
+ exists = true;
+
+ do {
+ int flags;
+
+ /* This assumes the path was passed through path_kill_slashes()! */
+ slash = strrchr(k, '/');
+ if (!slash)
+ break;
+
+ /* Trim the path at the last slash. Keep the slash if it's the root dir. */
+ slash[slash == k] = 0;
+
+ flags = IN_MOVE_SELF;
+ if (!exists)
+ flags |= IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO;
+
+ if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
+ exists = true;
+ } while (slash != k);
+
+ return 0;
+
+fail:
+ free(k);
+
+ path_spec_unwatch(s, u);
+ return r;
+}
+
+void path_spec_unwatch(PathSpec *s, Unit *u) {
+
+ if (s->inotify_fd < 0)
+ return;
+
+ unit_unwatch_fd(u, &s->watch);
+
+ close_nointr_nofail(s->inotify_fd);
+ s->inotify_fd = -1;
+}
+
+int path_spec_fd_event(PathSpec *s, uint32_t events) {
+ uint8_t *buf = NULL;
+ struct inotify_event *e;
+ ssize_t k;
+ int l;
+ int r = 0;
+
+ if (events != EPOLLIN) {
+ log_error("Got Invalid poll event on inotify.");
+ r = -EINVAL;
+ goto out;
+ }
+
+ if (ioctl(s->inotify_fd, FIONREAD, &l) < 0) {
+ log_error("FIONREAD failed: %m");
+ r = -errno;
+ goto out;
+ }
+
+ assert(l > 0);
+
+ if (!(buf = malloc(l))) {
+ log_error("Failed to allocate buffer: %m");
+ r = -errno;
+ goto out;
+ }
+
+ if ((k = read(s->inotify_fd, buf, l)) < 0) {
+ log_error("Failed to read inotify event: %m");
+ r = -errno;
+ goto out;
+ }
+
+ e = (struct inotify_event*) buf;
+
+ while (k > 0) {
+ size_t step;
+
+ if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) &&
+ s->primary_wd == e->wd)
+ r = 1;
+
+ step = sizeof(struct inotify_event) + e->len;
+ assert(step <= (size_t) k);
+
+ e = (struct inotify_event*) ((uint8_t*) e + step);
+ k -= step;
+ }
+out:
+ free(buf);
+ return r;
+}
+
+static bool path_spec_check_good(PathSpec *s, bool initial) {
+ bool good = false;
+
+ switch (s->type) {
+
+ case PATH_EXISTS:
+ good = access(s->path, F_OK) >= 0;
+ break;
+
+ case PATH_EXISTS_GLOB:
+ good = glob_exists(s->path) > 0;
+ break;
+
+ case PATH_DIRECTORY_NOT_EMPTY: {
+ int k;
+
+ k = dir_is_empty(s->path);
+ good = !(k == -ENOENT || k > 0);
+ break;
+ }
+
+ case PATH_CHANGED:
+ case PATH_MODIFIED: {
+ bool b;
+
+ b = access(s->path, F_OK) >= 0;
+ good = !initial && b != s->previous_exists;
+ s->previous_exists = b;
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ return good;
+}
+
+static bool path_spec_startswith(PathSpec *s, const char *what) {
+ return path_startswith(s->path, what);
+}
+
+static void path_spec_mkdir(PathSpec *s, mode_t mode) {
+ int r;
+
+ if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB)
+ return;
+
+ if ((r = mkdir_p_label(s->path, mode)) < 0)
+ log_warning("mkdir(%s) failed: %s", s->path, strerror(-r));
+}
+
+static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) {
+ fprintf(f,
+ "%s%s: %s\n",
+ prefix,
+ path_type_to_string(s->type),
+ s->path);
+}
+
+void path_spec_done(PathSpec *s) {
+ assert(s);
+ assert(s->inotify_fd == -1);
+
+ free(s->path);
+}
+
+static void path_init(Unit *u) {
+ Path *p = PATH(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ p->directory_mode = 0755;
+}
+
+static void path_done(Unit *u) {
+ Path *p = PATH(u);
+ PathSpec *s;
+
+ assert(p);
+
+ unit_ref_unset(&p->unit);
+
+ while ((s = p->specs)) {
+ path_spec_unwatch(s, u);
+ LIST_REMOVE(PathSpec, spec, p->specs, s);
+ path_spec_done(s);
+ free(s);
+ }
+}
+
+int path_add_one_mount_link(Path *p, Mount *m) {
+ PathSpec *s;
+ int r;
+
+ assert(p);
+ assert(m);
+
+ if (UNIT(p)->load_state != UNIT_LOADED ||
+ UNIT(m)->load_state != UNIT_LOADED)
+ return 0;
+
+ LIST_FOREACH(spec, s, p->specs) {
+
+ if (!path_spec_startswith(s, m->where))
+ continue;
+
+ if ((r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int path_add_mount_links(Path *p) {
+ Unit *other;
+ int r;
+
+ assert(p);
+
+ LIST_FOREACH(units_by_type, other, UNIT(p)->manager->units_by_type[UNIT_MOUNT])
+ if ((r = path_add_one_mount_link(p, MOUNT(other))) < 0)
+ return r;
+
+ return 0;
+}
+
+static int path_verify(Path *p) {
+ assert(p);
+
+ if (UNIT(p)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!p->specs) {
+ log_error("%s lacks path setting. Refusing.", UNIT(p)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int path_add_default_dependencies(Path *p) {
+ int r;
+
+ assert(p);
+
+ if (UNIT(p)->manager->running_as == SYSTEMD_SYSTEM) {
+ if ((r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
+ return r;
+
+ if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
+ return r;
+ }
+
+ return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+}
+
+static int path_load(Unit *u) {
+ Path *p = PATH(u);
+ int r;
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ if ((r = unit_load_fragment_and_dropin(u)) < 0)
+ return r;
+
+ if (u->load_state == UNIT_LOADED) {
+
+ if (!UNIT_DEREF(p->unit)) {
+ Unit *x;
+
+ r = unit_load_related_unit(u, ".service", &x);
+ if (r < 0)
+ return r;
+
+ unit_ref_set(&p->unit, x);
+ }
+
+ r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(p->unit), true);
+ if (r < 0)
+ return r;
+
+ if ((r = path_add_mount_links(p)) < 0)
+ return r;
+
+ if (UNIT(p)->default_dependencies)
+ if ((r = path_add_default_dependencies(p)) < 0)
+ return r;
+ }
+
+ return path_verify(p);
+}
+
+static void path_dump(Unit *u, FILE *f, const char *prefix) {
+ Path *p = PATH(u);
+ PathSpec *s;
+
+ assert(p);
+ assert(f);
+
+ fprintf(f,
+ "%sPath State: %s\n"
+ "%sResult: %s\n"
+ "%sUnit: %s\n"
+ "%sMakeDirectory: %s\n"
+ "%sDirectoryMode: %04o\n",
+ prefix, path_state_to_string(p->state),
+ prefix, path_result_to_string(p->result),
+ prefix, UNIT_DEREF(p->unit)->id,
+ prefix, yes_no(p->make_directory),
+ prefix, p->directory_mode);
+
+ LIST_FOREACH(spec, s, p->specs)
+ path_spec_dump(s, f, prefix);
+}
+
+static void path_unwatch(Path *p) {
+ PathSpec *s;
+
+ assert(p);
+
+ LIST_FOREACH(spec, s, p->specs)
+ path_spec_unwatch(s, UNIT(p));
+}
+
+static int path_watch(Path *p) {
+ int r;
+ PathSpec *s;
+
+ assert(p);
+
+ LIST_FOREACH(spec, s, p->specs)
+ if ((r = path_spec_watch(s, UNIT(p))) < 0)
+ return r;
+
+ return 0;
+}
+
+static void path_set_state(Path *p, PathState state) {
+ PathState old_state;
+ assert(p);
+
+ old_state = p->state;
+ p->state = state;
+
+ if (state != PATH_WAITING &&
+ (state != PATH_RUNNING || p->inotify_triggered))
+ path_unwatch(p);
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(p)->id,
+ path_state_to_string(old_state),
+ path_state_to_string(state));
+
+ unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static void path_enter_waiting(Path *p, bool initial, bool recheck);
+
+static int path_coldplug(Unit *u) {
+ Path *p = PATH(u);
+
+ assert(p);
+ assert(p->state == PATH_DEAD);
+
+ if (p->deserialized_state != p->state) {
+
+ if (p->deserialized_state == PATH_WAITING ||
+ p->deserialized_state == PATH_RUNNING)
+ path_enter_waiting(p, true, true);
+ else
+ path_set_state(p, p->deserialized_state);
+ }
+
+ return 0;
+}
+
+static void path_enter_dead(Path *p, PathResult f) {
+ assert(p);
+
+ if (f != PATH_SUCCESS)
+ p->result = f;
+
+ path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD);
+}
+
+static void path_enter_running(Path *p) {
+ int r;
+ DBusError error;
+
+ assert(p);
+ dbus_error_init(&error);
+
+ /* Don't start job if we are supposed to go down */
+ if (UNIT(p)->job && UNIT(p)->job->type == JOB_STOP)
+ return;
+
+ if ((r = manager_add_job(UNIT(p)->manager, JOB_START, UNIT_DEREF(p->unit), JOB_REPLACE, true, &error, NULL)) < 0)
+ goto fail;
+
+ p->inotify_triggered = false;
+
+ if ((r = path_watch(p)) < 0)
+ goto fail;
+
+ path_set_state(p, PATH_RUNNING);
+ return;
+
+fail:
+ log_warning("%s failed to queue unit startup job: %s", UNIT(p)->id, bus_error(&error, r));
+ path_enter_dead(p, PATH_FAILURE_RESOURCES);
+
+ dbus_error_free(&error);
+}
+
+static bool path_check_good(Path *p, bool initial) {
+ PathSpec *s;
+ bool good = false;
+
+ assert(p);
+
+ LIST_FOREACH(spec, s, p->specs) {
+ good = path_spec_check_good(s, initial);
+
+ if (good)
+ break;
+ }
+
+ return good;
+}
+
+static void path_enter_waiting(Path *p, bool initial, bool recheck) {
+ int r;
+
+ if (recheck)
+ if (path_check_good(p, initial)) {
+ log_debug("%s got triggered.", UNIT(p)->id);
+ path_enter_running(p);
+ return;
+ }
+
+ if ((r = path_watch(p)) < 0)
+ goto fail;
+
+ /* Hmm, so now we have created inotify watches, but the file
+ * might have appeared/been removed by now, so we must
+ * recheck */
+
+ if (recheck)
+ if (path_check_good(p, false)) {
+ log_debug("%s got triggered.", UNIT(p)->id);
+ path_enter_running(p);
+ return;
+ }
+
+ path_set_state(p, PATH_WAITING);
+ return;
+
+fail:
+ log_warning("%s failed to enter waiting state: %s", UNIT(p)->id, strerror(-r));
+ path_enter_dead(p, PATH_FAILURE_RESOURCES);
+}
+
+static void path_mkdir(Path *p) {
+ PathSpec *s;
+
+ assert(p);
+
+ if (!p->make_directory)
+ return;
+
+ LIST_FOREACH(spec, s, p->specs)
+ path_spec_mkdir(s, p->directory_mode);
+}
+
+static int path_start(Unit *u) {
+ Path *p = PATH(u);
+
+ assert(p);
+ assert(p->state == PATH_DEAD || p->state == PATH_FAILED);
+
+ if (UNIT_DEREF(p->unit)->load_state != UNIT_LOADED)
+ return -ENOENT;
+
+ path_mkdir(p);
+
+ p->result = PATH_SUCCESS;
+ path_enter_waiting(p, true, true);
+
+ return 0;
+}
+
+static int path_stop(Unit *u) {
+ Path *p = PATH(u);
+
+ assert(p);
+ assert(p->state == PATH_WAITING || p->state == PATH_RUNNING);
+
+ path_enter_dead(p, PATH_SUCCESS);
+ return 0;
+}
+
+static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Path *p = PATH(u);
+
+ assert(u);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", path_state_to_string(p->state));
+ unit_serialize_item(u, f, "result", path_result_to_string(p->result));
+
+ return 0;
+}
+
+static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Path *p = PATH(u);
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ PathState state;
+
+ if ((state = path_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ p->deserialized_state = state;
+
+ } else if (streq(key, "result")) {
+ PathResult f;
+
+ f = path_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != PATH_SUCCESS)
+ p->result = f;
+
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState path_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[PATH(u)->state];
+}
+
+static const char *path_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return path_state_to_string(PATH(u)->state);
+}
+
+static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
+ Path *p = PATH(u);
+ PathSpec *s;
+ int changed;
+
+ assert(p);
+ assert(fd >= 0);
+
+ if (p->state != PATH_WAITING &&
+ p->state != PATH_RUNNING)
+ return;
+
+ /* log_debug("inotify wakeup on %s.", u->id); */
+
+ LIST_FOREACH(spec, s, p->specs)
+ if (path_spec_owns_inotify_fd(s, fd))
+ break;
+
+ if (!s) {
+ log_error("Got event on unknown fd.");
+ goto fail;
+ }
+
+ changed = path_spec_fd_event(s, events);
+ if (changed < 0)
+ goto fail;
+
+ /* If we are already running, then remember that one event was
+ * dispatched so that we restart the service only if something
+ * actually changed on disk */
+ p->inotify_triggered = true;
+
+ if (changed)
+ path_enter_running(p);
+ else
+ path_enter_waiting(p, false, true);
+
+ return;
+
+fail:
+ path_enter_dead(p, PATH_FAILURE_RESOURCES);
+}
+
+void path_unit_notify(Unit *u, UnitActiveState new_state) {
+ Iterator i;
+ Unit *k;
+
+ if (u->type == UNIT_PATH)
+ return;
+
+ SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) {
+ Path *p;
+
+ if (k->type != UNIT_PATH)
+ continue;
+
+ if (k->load_state != UNIT_LOADED)
+ continue;
+
+ p = PATH(k);
+
+ if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) {
+ log_debug("%s got notified about unit deactivation.", UNIT(p)->id);
+
+ /* Hmm, so inotify was triggered since the
+ * last activation, so I guess we need to
+ * recheck what is going on. */
+ path_enter_waiting(p, false, p->inotify_triggered);
+ }
+ }
+}
+
+static void path_reset_failed(Unit *u) {
+ Path *p = PATH(u);
+
+ assert(p);
+
+ if (p->state == PATH_FAILED)
+ path_set_state(p, PATH_DEAD);
+
+ p->result = PATH_SUCCESS;
+}
+
+static const char* const path_state_table[_PATH_STATE_MAX] = {
+ [PATH_DEAD] = "dead",
+ [PATH_WAITING] = "waiting",
+ [PATH_RUNNING] = "running",
+ [PATH_FAILED] = "failed"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
+
+static const char* const path_type_table[_PATH_TYPE_MAX] = {
+ [PATH_EXISTS] = "PathExists",
+ [PATH_EXISTS_GLOB] = "PathExistsGlob",
+ [PATH_CHANGED] = "PathChanged",
+ [PATH_MODIFIED] = "PathModified",
+ [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
+
+static const char* const path_result_table[_PATH_RESULT_MAX] = {
+ [PATH_SUCCESS] = "success",
+ [PATH_FAILURE_RESOURCES] = "resources"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
+
+const UnitVTable path_vtable = {
+ .object_size = sizeof(Path),
+ .sections =
+ "Unit\0"
+ "Path\0"
+ "Install\0",
+
+ .init = path_init,
+ .done = path_done,
+ .load = path_load,
+
+ .coldplug = path_coldplug,
+
+ .dump = path_dump,
+
+ .start = path_start,
+ .stop = path_stop,
+
+ .serialize = path_serialize,
+ .deserialize_item = path_deserialize_item,
+
+ .active_state = path_active_state,
+ .sub_state_to_string = path_sub_state_to_string,
+
+ .fd_event = path_fd_event,
+
+ .reset_failed = path_reset_failed,
+
+ .bus_interface = "org.freedesktop.systemd1.Path",
+ .bus_message_handler = bus_path_message_handler,
+ .bus_invalidating_properties = bus_path_invalidating_properties
+};
diff --git a/src/core/path.h b/src/core/path.h
new file mode 100644
index 0000000000..77926888ae
--- /dev/null
+++ b/src/core/path.h
@@ -0,0 +1,110 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Path Path;
+
+#include "unit.h"
+#include "mount.h"
+
+typedef enum PathState {
+ PATH_DEAD,
+ PATH_WAITING,
+ PATH_RUNNING,
+ PATH_FAILED,
+ _PATH_STATE_MAX,
+ _PATH_STATE_INVALID = -1
+} PathState;
+
+typedef enum PathType {
+ PATH_EXISTS,
+ PATH_EXISTS_GLOB,
+ PATH_DIRECTORY_NOT_EMPTY,
+ PATH_CHANGED,
+ PATH_MODIFIED,
+ _PATH_TYPE_MAX,
+ _PATH_TYPE_INVALID = -1
+} PathType;
+
+typedef struct PathSpec {
+ char *path;
+
+ Watch watch;
+
+ LIST_FIELDS(struct PathSpec, spec);
+
+ PathType type;
+ int inotify_fd;
+ int primary_wd;
+
+ bool previous_exists;
+} PathSpec;
+
+int path_spec_watch(PathSpec *s, Unit *u);
+void path_spec_unwatch(PathSpec *s, Unit *u);
+int path_spec_fd_event(PathSpec *s, uint32_t events);
+void path_spec_done(PathSpec *s);
+
+static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) {
+ return s->inotify_fd == fd;
+}
+
+typedef enum PathResult {
+ PATH_SUCCESS,
+ PATH_FAILURE_RESOURCES,
+ _PATH_RESULT_MAX,
+ _PATH_RESULT_INVALID = -1
+} PathResult;
+
+struct Path {
+ Unit meta;
+
+ LIST_HEAD(PathSpec, specs);
+
+ UnitRef unit;
+
+ PathState state, deserialized_state;
+
+ bool inotify_triggered;
+
+ bool make_directory;
+ mode_t directory_mode;
+
+ PathResult result;
+};
+
+void path_unit_notify(Unit *u, UnitActiveState new_state);
+
+/* Called from the mount code figure out if a mount is a dependency of
+ * any of the paths of this path object */
+int path_add_one_mount_link(Path *p, Mount *m);
+
+extern const UnitVTable path_vtable;
+
+const char* path_state_to_string(PathState i);
+PathState path_state_from_string(const char *s);
+
+const char* path_type_to_string(PathType i);
+PathType path_type_from_string(const char *s);
+
+const char* path_result_to_string(PathResult i);
+PathResult path_result_from_string(const char *s);
diff --git a/src/core/securebits.h b/src/core/securebits.h
new file mode 100644
index 0000000000..ba0bba5353
--- /dev/null
+++ b/src/core/securebits.h
@@ -0,0 +1,45 @@
+#ifndef _LINUX_SECUREBITS_H
+#define _LINUX_SECUREBITS_H 1
+
+/* This is minimal version of Linux' linux/securebits.h header file,
+ * which is licensed GPL2 */
+
+#define SECUREBITS_DEFAULT 0x00000000
+
+/* When set UID 0 has no special privileges. When unset, we support
+ inheritance of root-permissions and suid-root executable under
+ compatibility mode. We raise the effective and inheritable bitmasks
+ *of the executable file* if the effective uid of the new process is
+ 0. If the real uid is 0, we raise the effective (legacy) bit of the
+ executable file. */
+#define SECURE_NOROOT 0
+#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */
+
+/* When set, setuid to/from uid 0 does not trigger capability-"fixup".
+ When unset, to provide compatibility with old programs relying on
+ set*uid to gain/lose privilege, transitions to/from uid 0 cause
+ capabilities to be gained/lost. */
+#define SECURE_NO_SETUID_FIXUP 2
+#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */
+
+/* When set, a process can retain its capabilities even after
+ transitioning to a non-root user (the set-uid fixup suppressed by
+ bit 2). Bit-4 is cleared when a process calls exec(); setting both
+ bit 4 and 5 will create a barrier through exec that no exec()'d
+ child can use this feature again. */
+#define SECURE_KEEP_CAPS 4
+#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */
+
+/* Each securesetting is implemented using two bits. One bit specifies
+ whether the setting is on or off. The other bit specify whether the
+ setting is locked or not. A setting which is locked cannot be
+ changed from user-level. */
+#define issecure_mask(X) (1 << (X))
+#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits))
+
+#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \
+ issecure_mask(SECURE_NO_SETUID_FIXUP) | \
+ issecure_mask(SECURE_KEEP_CAPS))
+#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1)
+
+#endif /* !_LINUX_SECUREBITS_H */
diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c
new file mode 100644
index 0000000000..6dfe8b45f3
--- /dev/null
+++ b/src/core/selinux-access.c
@@ -0,0 +1,441 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Dan Walsh
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "selinux-access.h"
+
+#ifdef HAVE_SELINUX
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <selinux/selinux.h>
+#include <selinux/avc.h>
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+#include <dbus.h>
+
+#include "util.h"
+#include "log.h"
+#include "bus-errors.h"
+#include "dbus-common.h"
+#include "audit.h"
+#include "selinux-util.h"
+#include "audit-fd.h"
+
+static bool initialized = false;
+
+struct auditstruct {
+ const char *path;
+ char *cmdline;
+ uid_t loginuid;
+ uid_t uid;
+ gid_t gid;
+};
+
+static int bus_get_selinux_security_context(
+ DBusConnection *connection,
+ const char *name,
+ char **scon,
+ DBusError *error) {
+
+ _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
+ DBusMessageIter iter, sub;
+ const char *bytes;
+ char *b;
+ int nbytes;
+
+ m = dbus_message_new_method_call(
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "GetConnectionSELinuxSecurityContext");
+ if (!m) {
+ dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+ return -ENOMEM;
+ }
+
+ if (!dbus_message_append_args(
+ m,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+ return -ENOMEM;
+ }
+
+ reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error);
+ if (!reply)
+ return -EIO;
+
+ if (dbus_set_error_from_message(error, reply))
+ return -EIO;
+
+ if (!dbus_message_iter_init(reply, &iter))
+ return -EIO;
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
+ return -EIO;
+
+ dbus_message_iter_recurse(&iter, &sub);
+ dbus_message_iter_get_fixed_array(&sub, &bytes, &nbytes);
+
+ b = strndup(bytes, nbytes);
+ if (!b)
+ return -ENOMEM;
+
+ *scon = b;
+
+ log_debug("GetConnectionSELinuxSecurityContext %s (pid %ld)", *scon, (long) bus_get_unix_process_id(connection, name, error));
+
+ return 0;
+}
+
+static int bus_get_audit_data(
+ DBusConnection *connection,
+ const char *name,
+ struct auditstruct *audit,
+ DBusError *error) {
+
+ pid_t pid;
+ int r;
+
+ pid = bus_get_unix_process_id(connection, name, error);
+ if (pid <= 0)
+ return -EIO;
+
+ r = audit_loginuid_from_pid(pid, &audit->loginuid);
+ if (r < 0)
+ return r;
+
+ r = get_process_uid(pid, &audit->uid);
+ if (r < 0)
+ return r;
+
+ r = get_process_gid(pid, &audit->gid);
+ if (r < 0)
+ return r;
+
+ r = get_process_cmdline(pid, LINE_MAX, true, &audit->cmdline);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+/*
+ Any time an access gets denied this callback will be called
+ with the aduit data. We then need to just copy the audit data into the msgbuf.
+*/
+static int audit_callback(
+ void *auditdata,
+ security_class_t cls,
+ char *msgbuf,
+ size_t msgbufsize) {
+
+ struct auditstruct *audit = (struct auditstruct *) auditdata;
+
+ snprintf(msgbuf, msgbufsize,
+ "auid=%d uid=%d gid=%d%s%s%s%s%s%s",
+ audit->loginuid,
+ audit->uid,
+ audit->gid,
+ (audit->path ? " path=\"" : ""),
+ strempty(audit->path),
+ (audit->path ? "\"" : ""),
+ (audit->cmdline ? " cmdline=\"" : ""),
+ strempty(audit->cmdline),
+ (audit->cmdline ? "\"" : ""));
+
+ msgbuf[msgbufsize-1] = 0;
+
+ return 0;
+}
+
+/*
+ Any time an access gets denied this callback will be called
+ code copied from dbus. If audit is turned on the messages will go as
+ user_avc's into the /var/log/audit/audit.log, otherwise they will be
+ sent to syslog.
+*/
+static int log_callback(int type, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+
+#ifdef HAVE_AUDIT
+ if (get_audit_fd() >= 0) {
+ char buf[LINE_MAX];
+
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0);
+ va_end(ap);
+
+ return 0;
+ }
+#endif
+ log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+/*
+ Function must be called once to initialize the SELinux AVC environment.
+ Sets up callbacks.
+ If you want to cleanup memory you should need to call selinux_access_finish.
+*/
+static int access_init(void) {
+ int r;
+
+ if (avc_open(NULL, 0)) {
+ log_error("avc_open() failed: %m");
+ return -errno;
+ }
+
+ selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
+ selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
+
+ if (security_getenforce() >= 0)
+ return 0;
+
+ r = -errno;
+ avc_destroy();
+
+ return r;
+}
+
+static int selinux_access_init(DBusError *error) {
+ int r;
+
+ if (initialized)
+ return 0;
+
+ if (use_selinux()) {
+ r = access_init();
+ if (r < 0) {
+ dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
+ return r;
+ }
+ }
+
+ initialized = true;
+ return 0;
+}
+
+void selinux_access_free(void) {
+ if (!initialized)
+ return;
+
+ avc_destroy();
+ initialized = false;
+}
+
+static int get_audit_data(
+ DBusConnection *connection,
+ DBusMessage *message,
+ struct auditstruct *audit,
+ DBusError *error) {
+
+ const char *sender;
+ int r, fd;
+ struct ucred ucred;
+ socklen_t len;
+
+ sender = dbus_message_get_sender(message);
+ if (sender)
+ return bus_get_audit_data(connection, sender, audit, error);
+
+ if (!dbus_connection_get_unix_fd(connection, &fd))
+ return -EINVAL;
+
+ r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
+ if (r < 0) {
+ log_error("Failed to determine peer credentials: %m");
+ return -errno;
+ }
+
+ audit->uid = ucred.uid;
+ audit->gid = ucred.gid;
+
+ r = audit_loginuid_from_pid(ucred.pid, &audit->loginuid);
+ if (r < 0)
+ return r;
+
+ r = get_process_cmdline(ucred.pid, LINE_MAX, true, &audit->cmdline);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+/*
+ This function returns the security context of the remote end of the dbus
+ connections. Whether it is on the bus or a local connection.
+*/
+static int get_calling_context(
+ DBusConnection *connection,
+ DBusMessage *message,
+ security_context_t *scon,
+ DBusError *error) {
+
+ const char *sender;
+ int r;
+ int fd;
+
+ /*
+ If sender exists then
+ if sender is NULL this indicates a local connection. Grab the fd
+ from dbus and do an getpeercon to peers process context
+ */
+ sender = dbus_message_get_sender(message);
+ if (sender) {
+ log_error("SELinux Got Sender %s", sender);
+
+ r = bus_get_selinux_security_context(connection, sender, scon, error);
+ if (r >= 0)
+ return r;
+
+ log_error("bus_get_selinux_security_context failed: %m");
+ return r;
+ }
+
+ log_debug("SELinux No Sender");
+ if (!dbus_connection_get_unix_fd(connection, &fd)) {
+ log_error("bus_connection_get_unix_fd failed %m");
+ return -EINVAL;
+ }
+
+ r = getpeercon(fd, scon);
+ if (r < 0) {
+ log_error("getpeercon failed %m");
+ return -errno;
+ }
+
+ return 0;
+}
+
+/*
+ This function communicates with the kernel to check whether or not it should
+ allow the access.
+ If the machine is in permissive mode it will return ok. Audit messages will
+ still be generated if the access would be denied in enforcing mode.
+*/
+int selinux_access_check(
+ DBusConnection *connection,
+ DBusMessage *message,
+ const char *path,
+ const char *permission,
+ DBusError *error) {
+
+ security_context_t scon = NULL, fcon = NULL;
+ int r = 0;
+ const char *tclass = NULL;
+ struct auditstruct audit;
+
+ assert(connection);
+ assert(message);
+ assert(permission);
+ assert(error);
+
+ if (!use_selinux())
+ return 0;
+
+ r = selinux_access_init(error);
+ if (r < 0)
+ return r;
+
+ log_debug("SELinux access check for path=%s permission=%s", strna(path), permission);
+
+ audit.uid = audit.loginuid = (uid_t) -1;
+ audit.gid = (gid_t) -1;
+ audit.cmdline = NULL;
+ audit.path = path;
+
+ r = get_calling_context(connection, message, &scon, error);
+ if (r < 0) {
+ log_error("Failed to get caller's security context on: %m");
+ goto finish;
+ }
+
+ if (path) {
+ tclass = "service";
+ /* get the file context of the unit file */
+ r = getfilecon(path, &fcon);
+ if (r < 0) {
+ dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path);
+ r = -errno;
+ log_error("Failed to get security context on %s: %m",path);
+ goto finish;
+ }
+
+ } else {
+ tclass = "system";
+ r = getcon(&fcon);
+ if (r < 0) {
+ dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "Failed to get current context.");
+ r = -errno;
+ log_error("Failed to get current process context on: %m");
+ goto finish;
+ }
+ }
+
+ (void) get_audit_data(connection, message, &audit, error);
+
+ errno = 0;
+ r = selinux_check_access(scon, fcon, tclass, permission, &audit);
+ if (r < 0) {
+ dbus_set_error(error, DBUS_ERROR_ACCESS_DENIED, "SELinux policy denies access.");
+ r = -errno;
+ log_error("SELinux policy denies access.");
+ }
+
+ log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, audit.cmdline, r);
+
+finish:
+ free(audit.cmdline);
+ freecon(scon);
+ freecon(fcon);
+
+ if (r && security_getenforce() != 1) {
+ dbus_error_init(error);
+ r = 0;
+ }
+
+ return r;
+}
+
+#else
+
+int selinux_access_check(
+ DBusConnection *connection,
+ DBusMessage *message,
+ const char *path,
+ const char *permission,
+ DBusError *error) {
+
+ return 0;
+}
+
+void selinux_access_free(void) {
+}
+
+#endif
diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h
new file mode 100644
index 0000000000..9183cbc9a6
--- /dev/null
+++ b/src/core/selinux-access.h
@@ -0,0 +1,62 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Dan Walsh
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus.h>
+
+void selinux_access_free(void);
+
+int selinux_access_check(DBusConnection *connection, DBusMessage *message, const char *path, const char *permission, DBusError *error);
+
+#ifdef HAVE_SELINUX
+
+#define SELINUX_ACCESS_CHECK(connection, message, permission) \
+ do { \
+ DBusError _error; \
+ int _r; \
+ DBusConnection *_c = (connection); \
+ DBusMessage *_m = (message); \
+ dbus_error_init(&_error); \
+ _r = selinux_access_check(_c, _m, NULL, (permission), &_error); \
+ if (_r < 0) \
+ return bus_send_error_reply(_c, _m, &_error, _r); \
+ } while (false)
+
+#define SELINUX_UNIT_ACCESS_CHECK(unit, connection, message, permission) \
+ do { \
+ DBusError _error; \
+ int _r; \
+ DBusConnection *_c = (connection); \
+ DBusMessage *_m = (message); \
+ Unit *_u = (unit); \
+ dbus_error_init(&_error); \
+ _r = selinux_access_check(_c, _m, _u->source_path ?: _u->fragment_path, (permission), &_error); \
+ if (_r < 0) \
+ return bus_send_error_reply(_c, _m, &_error, _r); \
+ } while (false)
+
+#else
+
+#define SELINUX_ACCESS_CHECK(connection, message, permission) do { } while (false)
+#define SELINUX_UNIT_ACCESS_CHECK(unit, connection, message, permission) do { } while (false)
+
+#endif
diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c
new file mode 100644
index 0000000000..e9c0de92f1
--- /dev/null
+++ b/src/core/selinux-setup.c
@@ -0,0 +1,123 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include "selinux-setup.h"
+#include "selinux-util.h"
+#include "label.h"
+#include "mount-setup.h"
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+
+#ifdef HAVE_SELINUX
+static int null_log(int type, const char *fmt, ...) {
+ return 0;
+}
+#endif
+
+int selinux_setup(bool *loaded_policy) {
+
+#ifdef HAVE_SELINUX
+ int enforce = 0;
+ usec_t before_load, after_load;
+ security_context_t con;
+ int r;
+ union selinux_callback cb;
+
+ assert(loaded_policy);
+
+ /* Turn off all of SELinux' own logging, we want to do that */
+ cb.func_log = null_log;
+ selinux_set_callback(SELINUX_CB_LOG, cb);
+
+ /* Make sure getcon() works, which needs /proc and /sys */
+ mount_setup_early();
+
+ /* Already initialized by somebody else? */
+ r = getcon_raw(&con);
+ if (r == 0) {
+ bool initialized;
+
+ initialized = !streq(con, "kernel");
+ freecon(con);
+
+ if (initialized)
+ return 0;
+ }
+
+ /* Make sure we have no fds open while loading the policy and
+ * transitioning */
+ log_close();
+
+ /* Now load the policy */
+ before_load = now(CLOCK_MONOTONIC);
+ r = selinux_init_load_policy(&enforce);
+ if (r == 0) {
+ char timespan[FORMAT_TIMESPAN_MAX];
+ char *label;
+
+ retest_selinux();
+
+ /* Transition to the new context */
+ r = label_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label);
+ if (r < 0 || label == NULL) {
+ log_open();
+ log_error("Failed to compute init label, ignoring.");
+ } else {
+ r = setcon(label);
+
+ log_open();
+ if (r < 0)
+ log_error("Failed to transition into init label '%s', ignoring.", label);
+
+ label_free(label);
+ }
+
+ after_load = now(CLOCK_MONOTONIC);
+
+ log_info("Successfully loaded SELinux policy in %s.",
+ format_timespan(timespan, sizeof(timespan), after_load - before_load));
+
+ *loaded_policy = true;
+
+ } else {
+ log_open();
+
+ if (enforce > 0) {
+ log_error("Failed to load SELinux policy. Freezing.");
+ return -EIO;
+ } else
+ log_debug("Unable to load SELinux policy. Ignoring.");
+ }
+#endif
+
+ return 0;
+}
diff --git a/src/core/selinux-setup.h b/src/core/selinux-setup.h
new file mode 100644
index 0000000000..39e2bc25bb
--- /dev/null
+++ b/src/core/selinux-setup.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+int selinux_setup(bool *loaded_policy);
diff --git a/src/core/service.c b/src/core/service.c
new file mode 100644
index 0000000000..cf08485374
--- /dev/null
+++ b/src/core/service.c
@@ -0,0 +1,3941 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <signal.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/reboot.h>
+
+#include "manager.h"
+#include "unit.h"
+#include "service.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "unit-printf.h"
+#include "dbus-service.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "exit-status.h"
+#include "def.h"
+#include "path-util.h"
+#include "util.h"
+#include "utf8.h"
+
+#ifdef HAVE_SYSV_COMPAT
+
+#define DEFAULT_SYSV_TIMEOUT_USEC (5*USEC_PER_MINUTE)
+
+typedef enum RunlevelType {
+ RUNLEVEL_UP,
+ RUNLEVEL_DOWN,
+ RUNLEVEL_SYSINIT
+} RunlevelType;
+
+static const struct {
+ const char *path;
+ const char *target;
+ const RunlevelType type;
+} rcnd_table[] = {
+ /* Standard SysV runlevels for start-up */
+ { "rc1.d", SPECIAL_RESCUE_TARGET, RUNLEVEL_UP },
+ { "rc2.d", SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
+ { "rc3.d", SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
+ { "rc4.d", SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
+ { "rc5.d", SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
+
+#ifdef TARGET_SUSE
+ /* SUSE style boot.d */
+ { "boot.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT },
+#endif
+
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM)
+ /* Debian style rcS.d */
+ { "rcS.d", SPECIAL_SYSINIT_TARGET, RUNLEVEL_SYSINIT },
+#endif
+
+ /* Standard SysV runlevels for shutdown */
+ { "rc0.d", SPECIAL_POWEROFF_TARGET, RUNLEVEL_DOWN },
+ { "rc6.d", SPECIAL_REBOOT_TARGET, RUNLEVEL_DOWN }
+
+ /* Note that the order here matters, as we read the
+ directories in this order, and we want to make sure that
+ sysv_start_priority is known when we first load the
+ unit. And that value we only know from S links. Hence
+ UP/SYSINIT must be read before DOWN */
+};
+
+#define RUNLEVELS_UP "12345"
+/* #define RUNLEVELS_DOWN "06" */
+#define RUNLEVELS_BOOT "bBsS"
+#endif
+
+static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
+ [SERVICE_DEAD] = UNIT_INACTIVE,
+ [SERVICE_START_PRE] = UNIT_ACTIVATING,
+ [SERVICE_START] = UNIT_ACTIVATING,
+ [SERVICE_START_POST] = UNIT_ACTIVATING,
+ [SERVICE_RUNNING] = UNIT_ACTIVE,
+ [SERVICE_EXITED] = UNIT_ACTIVE,
+ [SERVICE_RELOAD] = UNIT_RELOADING,
+ [SERVICE_STOP] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_POST] = UNIT_DEACTIVATING,
+ [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
+ [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
+ [SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
+};
+
+/* For Type=idle we never want to delay any other jobs, hence we
+ * consider idle jobs active as soon as we start working on them */
+static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = {
+ [SERVICE_DEAD] = UNIT_INACTIVE,
+ [SERVICE_START_PRE] = UNIT_ACTIVE,
+ [SERVICE_START] = UNIT_ACTIVE,
+ [SERVICE_START_POST] = UNIT_ACTIVE,
+ [SERVICE_RUNNING] = UNIT_ACTIVE,
+ [SERVICE_EXITED] = UNIT_ACTIVE,
+ [SERVICE_RELOAD] = UNIT_RELOADING,
+ [SERVICE_STOP] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING,
+ [SERVICE_STOP_POST] = UNIT_DEACTIVATING,
+ [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
+ [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
+ [SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
+};
+
+static void service_init(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ s->timeout_start_usec = DEFAULT_TIMEOUT_USEC;
+ s->timeout_stop_usec = DEFAULT_TIMEOUT_USEC;
+ s->restart_usec = DEFAULT_RESTART_USEC;
+ s->type = _SERVICE_TYPE_INVALID;
+
+ s->watchdog_watch.type = WATCH_INVALID;
+
+ s->timer_watch.type = WATCH_INVALID;
+#ifdef HAVE_SYSV_COMPAT
+ s->sysv_start_priority = -1;
+ s->sysv_start_priority_from_rcnd = -1;
+#endif
+ s->socket_fd = -1;
+ s->guess_main_pid = true;
+
+ exec_context_init(&s->exec_context);
+ kill_context_init(&s->kill_context);
+
+ RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5);
+
+ s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+}
+
+static void service_unwatch_control_pid(Service *s) {
+ assert(s);
+
+ if (s->control_pid <= 0)
+ return;
+
+ unit_unwatch_pid(UNIT(s), s->control_pid);
+ s->control_pid = 0;
+}
+
+static void service_unwatch_main_pid(Service *s) {
+ assert(s);
+
+ if (s->main_pid <= 0)
+ return;
+
+ unit_unwatch_pid(UNIT(s), s->main_pid);
+ s->main_pid = 0;
+}
+
+static void service_unwatch_pid_file(Service *s) {
+ if (!s->pid_file_pathspec)
+ return;
+
+ log_debug("Stopping watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path);
+ path_spec_unwatch(s->pid_file_pathspec, UNIT(s));
+ path_spec_done(s->pid_file_pathspec);
+ free(s->pid_file_pathspec);
+ s->pid_file_pathspec = NULL;
+}
+
+static int service_set_main_pid(Service *s, pid_t pid) {
+ pid_t ppid;
+
+ assert(s);
+
+ if (pid <= 1)
+ return -EINVAL;
+
+ if (pid == getpid())
+ return -EINVAL;
+
+ s->main_pid = pid;
+ s->main_pid_known = true;
+
+ if (get_parent_of_pid(pid, &ppid) >= 0 && ppid != getpid()) {
+ log_warning("%s: Supervising process %lu which is not our child. We'll most likely not notice when it exits.",
+ UNIT(s)->id, (unsigned long) pid);
+
+ s->main_pid_alien = true;
+ } else
+ s->main_pid_alien = false;
+
+ exec_status_start(&s->main_exec_status, pid);
+
+ return 0;
+}
+
+static void service_close_socket_fd(Service *s) {
+ assert(s);
+
+ if (s->socket_fd < 0)
+ return;
+
+ close_nointr_nofail(s->socket_fd);
+ s->socket_fd = -1;
+}
+
+static void service_connection_unref(Service *s) {
+ assert(s);
+
+ if (!UNIT_DEREF(s->accept_socket))
+ return;
+
+ socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket)));
+ unit_ref_unset(&s->accept_socket);
+}
+
+static void service_stop_watchdog(Service *s) {
+ assert(s);
+
+ unit_unwatch_timer(UNIT(s), &s->watchdog_watch);
+ s->watchdog_timestamp.realtime = 0;
+ s->watchdog_timestamp.monotonic = 0;
+}
+
+static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart);
+
+static void service_handle_watchdog(Service *s) {
+ usec_t offset;
+ int r;
+
+ assert(s);
+
+ if (s->watchdog_usec == 0)
+ return;
+
+ offset = now(CLOCK_MONOTONIC) - s->watchdog_timestamp.monotonic;
+ if (offset >= s->watchdog_usec) {
+ log_error("%s watchdog timeout!", UNIT(s)->id);
+ service_enter_dead(s, SERVICE_FAILURE_WATCHDOG, true);
+ return;
+ }
+
+ r = unit_watch_timer(UNIT(s), s->watchdog_usec - offset, &s->watchdog_watch);
+ if (r < 0)
+ log_warning("%s failed to install watchdog timer: %s", UNIT(s)->id, strerror(-r));
+}
+
+static void service_reset_watchdog(Service *s) {
+ assert(s);
+
+ dual_timestamp_get(&s->watchdog_timestamp);
+ service_handle_watchdog(s);
+}
+
+static void service_done(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ free(s->pid_file);
+ s->pid_file = NULL;
+
+#ifdef HAVE_SYSV_COMPAT
+ free(s->sysv_runlevels);
+ s->sysv_runlevels = NULL;
+#endif
+
+ free(s->status_text);
+ s->status_text = NULL;
+
+ exec_context_done(&s->exec_context);
+ exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
+ s->control_command = NULL;
+ s->main_command = NULL;
+
+ set_free(s->restart_ignore_status.code);
+ s->restart_ignore_status.code = NULL;
+ set_free(s->restart_ignore_status.signal);
+ s->restart_ignore_status.signal = NULL;
+
+ set_free(s->success_status.code);
+ s->success_status.code = NULL;
+ set_free(s->success_status.signal);
+ s->success_status.signal = NULL;
+
+ /* This will leak a process, but at least no memory or any of
+ * our resources */
+ service_unwatch_main_pid(s);
+ service_unwatch_control_pid(s);
+ service_unwatch_pid_file(s);
+
+ if (s->bus_name) {
+ unit_unwatch_bus_name(u, s->bus_name);
+ free(s->bus_name);
+ s->bus_name = NULL;
+ }
+
+ service_close_socket_fd(s);
+ service_connection_unref(s);
+
+ unit_ref_unset(&s->accept_socket);
+
+ service_stop_watchdog(s);
+
+ unit_unwatch_timer(u, &s->timer_watch);
+}
+
+#ifdef HAVE_SYSV_COMPAT
+static char *sysv_translate_name(const char *name) {
+ char *r;
+
+ if (!(r = new(char, strlen(name) + sizeof(".service"))))
+ return NULL;
+
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM)
+ if (endswith(name, ".sh"))
+ /* Drop Debian-style .sh suffix */
+ strcpy(stpcpy(r, name) - 3, ".service");
+#endif
+#ifdef TARGET_SUSE
+ if (startswith(name, "boot."))
+ /* Drop SuSE-style boot. prefix */
+ strcpy(stpcpy(r, name + 5), ".service");
+#endif
+#ifdef TARGET_FRUGALWARE
+ if (startswith(name, "rc."))
+ /* Drop Frugalware-style rc. prefix */
+ strcpy(stpcpy(r, name + 3), ".service");
+#endif
+ else
+ /* Normal init scripts */
+ strcpy(stpcpy(r, name), ".service");
+
+ return r;
+}
+
+static int sysv_translate_facility(const char *name, const char *filename, char **_r) {
+
+ /* We silently ignore the $ prefix here. According to the LSB
+ * spec it simply indicates whether something is a
+ * standardized name or a distribution-specific one. Since we
+ * just follow what already exists and do not introduce new
+ * uses or names we don't care who introduced a new name. */
+
+ static const char * const table[] = {
+ /* LSB defined facilities */
+ "local_fs", SPECIAL_LOCAL_FS_TARGET,
+#if defined(TARGET_MANDRIVA) || defined(TARGET_MAGEIA)
+#else
+ /* Due to unfortunate name selection in Mandriva,
+ * $network is provided by network-up which is ordered
+ * after network which actually starts interfaces.
+ * To break the loop, just ignore it */
+ "network", SPECIAL_NETWORK_TARGET,
+#endif
+ "named", SPECIAL_NSS_LOOKUP_TARGET,
+ "portmap", SPECIAL_RPCBIND_TARGET,
+ "remote_fs", SPECIAL_REMOTE_FS_TARGET,
+ "syslog", SPECIAL_SYSLOG_TARGET,
+ "time", SPECIAL_TIME_SYNC_TARGET,
+
+ /* common extensions */
+ "mail-transfer-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
+ "x-display-manager", SPECIAL_DISPLAY_MANAGER_SERVICE,
+ "null", NULL,
+
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM)
+ "mail-transport-agent", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
+#endif
+
+#ifdef TARGET_SUSE
+ "smtp", SPECIAL_MAIL_TRANSFER_AGENT_TARGET,
+#endif
+ };
+
+ unsigned i;
+ char *r;
+ const char *n;
+
+ assert(name);
+ assert(_r);
+
+ n = *name == '$' ? name + 1 : name;
+
+ for (i = 0; i < ELEMENTSOF(table); i += 2) {
+
+ if (!streq(table[i], n))
+ continue;
+
+ if (!table[i+1])
+ return 0;
+
+ if (!(r = strdup(table[i+1])))
+ return -ENOMEM;
+
+ goto finish;
+ }
+
+ /* If we don't know this name, fallback heuristics to figure
+ * out whether something is a target or a service alias. */
+
+ if (*name == '$') {
+ if (!unit_prefix_is_valid(n))
+ return -EINVAL;
+
+ /* Facilities starting with $ are most likely targets */
+ r = unit_name_build(n, NULL, ".target");
+ } else if (filename && streq(name, filename))
+ /* Names equaling the file name of the services are redundant */
+ return 0;
+ else
+ /* Everything else we assume to be normal service names */
+ r = sysv_translate_name(n);
+
+ if (!r)
+ return -ENOMEM;
+
+finish:
+ *_r = r;
+
+ return 1;
+}
+
+static int sysv_fix_order(Service *s) {
+ Unit *other;
+ int r;
+
+ assert(s);
+
+ if (s->sysv_start_priority < 0)
+ return 0;
+
+ /* For each pair of services where at least one lacks a LSB
+ * header, we use the start priority value to order things. */
+
+ LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) {
+ Service *t;
+ UnitDependency d;
+ bool special_s, special_t;
+
+ t = SERVICE(other);
+
+ if (s == t)
+ continue;
+
+ if (UNIT(t)->load_state != UNIT_LOADED)
+ continue;
+
+ if (t->sysv_start_priority < 0)
+ continue;
+
+ /* If both units have modern headers we don't care
+ * about the priorities */
+ if ((UNIT(s)->fragment_path || s->sysv_has_lsb) &&
+ (UNIT(t)->fragment_path || t->sysv_has_lsb))
+ continue;
+
+ special_s = s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels);
+ special_t = t->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, t->sysv_runlevels);
+
+ if (special_t && !special_s)
+ d = UNIT_AFTER;
+ else if (special_s && !special_t)
+ d = UNIT_BEFORE;
+ else if (t->sysv_start_priority < s->sysv_start_priority)
+ d = UNIT_AFTER;
+ else if (t->sysv_start_priority > s->sysv_start_priority)
+ d = UNIT_BEFORE;
+ else
+ continue;
+
+ /* FIXME: Maybe we should compare the name here lexicographically? */
+
+ if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static ExecCommand *exec_command_new(const char *path, const char *arg1) {
+ ExecCommand *c;
+
+ if (!(c = new0(ExecCommand, 1)))
+ return NULL;
+
+ if (!(c->path = strdup(path))) {
+ free(c);
+ return NULL;
+ }
+
+ if (!(c->argv = strv_new(path, arg1, NULL))) {
+ free(c->path);
+ free(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+static int sysv_exec_commands(Service *s, const bool supports_reload) {
+ ExecCommand *c;
+
+ assert(s);
+ assert(s->is_sysv);
+ assert(UNIT(s)->source_path);
+
+ c = exec_command_new(UNIT(s)->source_path, "start");
+ if (!c)
+ return -ENOMEM;
+ exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c);
+
+ c = exec_command_new(UNIT(s)->source_path, "stop");
+ if (!c)
+ return -ENOMEM;
+ exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c);
+
+ if (supports_reload) {
+ c = exec_command_new(UNIT(s)->source_path, "reload");
+ if (!c)
+ return -ENOMEM;
+ exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c);
+ }
+
+ return 0;
+}
+
+static bool usage_contains_reload(const char *line) {
+ return (strcasestr(line, "{reload|") ||
+ strcasestr(line, "{reload}") ||
+ strcasestr(line, "{reload\"") ||
+ strcasestr(line, "|reload|") ||
+ strcasestr(line, "|reload}") ||
+ strcasestr(line, "|reload\""));
+}
+
+static int service_load_sysv_path(Service *s, const char *path) {
+ FILE *f;
+ Unit *u;
+ unsigned line = 0;
+ int r;
+ enum {
+ NORMAL,
+ DESCRIPTION,
+ LSB,
+ LSB_DESCRIPTION,
+ USAGE_CONTINUATION
+ } state = NORMAL;
+ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL, *description;
+ struct stat st;
+ bool supports_reload = false;
+
+ assert(s);
+ assert(path);
+
+ u = UNIT(s);
+
+ f = fopen(path, "re");
+ if (!f) {
+ r = errno == ENOENT ? 0 : -errno;
+ goto finish;
+ }
+
+ if (fstat(fileno(f), &st) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(u->source_path);
+ u->source_path = strdup(path);
+ if (!u->source_path) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ u->source_mtime = timespec_load(&st.st_mtim);
+
+ if (null_or_empty(&st)) {
+ u->load_state = UNIT_MASKED;
+ r = 0;
+ goto finish;
+ }
+
+ s->is_sysv = true;
+
+ while (!feof(f)) {
+ char l[LINE_MAX], *t;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ r = -errno;
+ log_error("Failed to read configuration file '%s': %s", path, strerror(-r));
+ goto finish;
+ }
+
+ line++;
+
+ t = strstrip(l);
+ if (*t != '#') {
+ /* Try to figure out whether this init script supports
+ * the reload operation. This heuristic looks for
+ * "Usage" lines which include the reload option. */
+ if ( state == USAGE_CONTINUATION ||
+ (state == NORMAL && strcasestr(t, "usage"))) {
+ if (usage_contains_reload(t)) {
+ supports_reload = true;
+ state = NORMAL;
+ } else if (t[strlen(t)-1] == '\\')
+ state = USAGE_CONTINUATION;
+ else
+ state = NORMAL;
+ }
+
+ continue;
+ }
+
+ if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
+ state = LSB;
+ s->sysv_has_lsb = true;
+ continue;
+ }
+
+ if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) {
+ state = NORMAL;
+ continue;
+ }
+
+ t++;
+ t += strspn(t, WHITESPACE);
+
+ if (state == NORMAL) {
+
+ /* Try to parse Red Hat style chkconfig headers */
+
+ if (startswith_no_case(t, "chkconfig:")) {
+ int start_priority;
+ char runlevels[16], *k;
+
+ state = NORMAL;
+
+ if (sscanf(t+10, "%15s %i %*i",
+ runlevels,
+ &start_priority) != 2) {
+
+ log_warning("[%s:%u] Failed to parse chkconfig line. Ignoring.", path, line);
+ continue;
+ }
+
+ /* A start priority gathered from the
+ * symlink farms is preferred over the
+ * data from the LSB header. */
+ if (start_priority < 0 || start_priority > 99)
+ log_warning("[%s:%u] Start priority out of range. Ignoring.", path, line);
+ else
+ s->sysv_start_priority = start_priority;
+
+ char_array_0(runlevels);
+ k = delete_chars(runlevels, WHITESPACE "-");
+
+ if (k[0]) {
+ char *d;
+
+ if (!(d = strdup(k))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(s->sysv_runlevels);
+ s->sysv_runlevels = d;
+ }
+
+ } else if (startswith_no_case(t, "description:")) {
+
+ size_t k = strlen(t);
+ char *d;
+ const char *j;
+
+ if (t[k-1] == '\\') {
+ state = DESCRIPTION;
+ t[k-1] = 0;
+ }
+
+ if ((j = strstrip(t+12)) && *j) {
+ if (!(d = strdup(j))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ } else
+ d = NULL;
+
+ free(chkconfig_description);
+ chkconfig_description = d;
+
+ } else if (startswith_no_case(t, "pidfile:")) {
+
+ char *fn;
+
+ state = NORMAL;
+
+ fn = strstrip(t+8);
+ if (!path_is_absolute(fn)) {
+ log_warning("[%s:%u] PID file not absolute. Ignoring.", path, line);
+ continue;
+ }
+
+ if (!(fn = strdup(fn))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(s->pid_file);
+ s->pid_file = fn;
+ }
+
+ } else if (state == DESCRIPTION) {
+
+ /* Try to parse Red Hat style description
+ * continuation */
+
+ size_t k = strlen(t);
+ char *j;
+
+ if (t[k-1] == '\\')
+ t[k-1] = 0;
+ else
+ state = NORMAL;
+
+ if ((j = strstrip(t)) && *j) {
+ char *d = NULL;
+
+ if (chkconfig_description)
+ d = strjoin(chkconfig_description, " ", j, NULL);
+ else
+ d = strdup(j);
+
+ if (!d) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(chkconfig_description);
+ chkconfig_description = d;
+ }
+
+ } else if (state == LSB || state == LSB_DESCRIPTION) {
+
+ if (startswith_no_case(t, "Provides:")) {
+ char *i, *w;
+ size_t z;
+
+ state = LSB;
+
+ FOREACH_WORD_QUOTED(w, z, t+9, i) {
+ char *n, *m;
+
+ if (!(n = strndup(w, z))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = sysv_translate_facility(n, path_get_file_name(path), &m);
+ free(n);
+
+ if (r < 0)
+ goto finish;
+
+ if (r == 0)
+ continue;
+
+ if (unit_name_to_type(m) == UNIT_SERVICE)
+ r = unit_add_name(u, m);
+ else
+ /* NB: SysV targets
+ * which are provided
+ * by a service are
+ * pulled in by the
+ * services, as an
+ * indication that the
+ * generic service is
+ * now available. This
+ * is strictly
+ * one-way. The
+ * targets do NOT pull
+ * in the SysV
+ * services! */
+ r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_WANTS, m, NULL, true);
+
+ if (r < 0)
+ log_error("[%s:%u] Failed to add LSB Provides name %s, ignoring: %s", path, line, m, strerror(-r));
+
+ free(m);
+ }
+
+ } else if (startswith_no_case(t, "Required-Start:") ||
+ startswith_no_case(t, "Should-Start:") ||
+ startswith_no_case(t, "X-Start-Before:") ||
+ startswith_no_case(t, "X-Start-After:")) {
+ char *i, *w;
+ size_t z;
+
+ state = LSB;
+
+ FOREACH_WORD_QUOTED(w, z, strchr(t, ':')+1, i) {
+ char *n, *m;
+
+ if (!(n = strndup(w, z))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = sysv_translate_facility(n, path_get_file_name(path), &m);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r));
+ free(n);
+ continue;
+ }
+
+ free(n);
+
+ if (r == 0)
+ continue;
+
+ r = unit_add_dependency_by_name(u, startswith_no_case(t, "X-Start-Before:") ? UNIT_BEFORE : UNIT_AFTER, m, NULL, true);
+
+ if (r < 0)
+ log_error("[%s:%u] Failed to add dependency on %s, ignoring: %s", path, line, m, strerror(-r));
+
+ free(m);
+ }
+ } else if (startswith_no_case(t, "Default-Start:")) {
+ char *k, *d;
+
+ state = LSB;
+
+ k = delete_chars(t+14, WHITESPACE "-");
+
+ if (k[0] != 0) {
+ if (!(d = strdup(k))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(s->sysv_runlevels);
+ s->sysv_runlevels = d;
+ }
+
+ } else if (startswith_no_case(t, "Description:")) {
+ char *d, *j;
+
+ state = LSB_DESCRIPTION;
+
+ if ((j = strstrip(t+12)) && *j) {
+ if (!(d = strdup(j))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ } else
+ d = NULL;
+
+ free(long_description);
+ long_description = d;
+
+ } else if (startswith_no_case(t, "Short-Description:")) {
+ char *d, *j;
+
+ state = LSB;
+
+ if ((j = strstrip(t+18)) && *j) {
+ if (!(d = strdup(j))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ } else
+ d = NULL;
+
+ free(short_description);
+ short_description = d;
+
+ } else if (state == LSB_DESCRIPTION) {
+
+ if (startswith(l, "#\t") || startswith(l, "# ")) {
+ char *j;
+
+ if ((j = strstrip(t)) && *j) {
+ char *d = NULL;
+
+ if (long_description)
+ d = strjoin(long_description, " ", t, NULL);
+ else
+ d = strdup(j);
+
+ if (!d) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(long_description);
+ long_description = d;
+ }
+
+ } else
+ state = LSB;
+ }
+ }
+ }
+
+ if ((r = sysv_exec_commands(s, supports_reload)) < 0)
+ goto finish;
+ if (s->sysv_runlevels &&
+ chars_intersect(RUNLEVELS_BOOT, s->sysv_runlevels) &&
+ chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) {
+ /* Service has both boot and "up" runlevels
+ configured. Kill the "up" ones. */
+ delete_chars(s->sysv_runlevels, RUNLEVELS_UP);
+ }
+
+ if (s->sysv_runlevels && !chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) {
+ /* If there a runlevels configured for this service
+ * but none of the standard ones, then we assume this
+ * is some special kind of service (which might be
+ * needed for early boot) and don't create any links
+ * to it. */
+
+ UNIT(s)->default_dependencies = false;
+
+ /* Don't timeout special services during boot (like fsck) */
+ s->timeout_start_usec = 0;
+ s->timeout_stop_usec = 0;
+ } else {
+ s->timeout_start_usec = DEFAULT_SYSV_TIMEOUT_USEC;
+ s->timeout_stop_usec = DEFAULT_SYSV_TIMEOUT_USEC;
+ }
+
+ /* Special setting for all SysV services */
+ s->type = SERVICE_FORKING;
+ s->remain_after_exit = !s->pid_file;
+ s->guess_main_pid = false;
+ s->restart = SERVICE_RESTART_NO;
+ s->exec_context.ignore_sigpipe = false;
+ s->kill_context.kill_mode = KILL_PROCESS;
+
+ /* We use the long description only if
+ * no short description is set. */
+
+ if (short_description)
+ description = short_description;
+ else if (chkconfig_description)
+ description = chkconfig_description;
+ else if (long_description)
+ description = long_description;
+ else
+ description = NULL;
+
+ if (description) {
+ char *d;
+
+ if (!(d = strappend(s->sysv_has_lsb ? "LSB: " : "SYSV: ", description))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ u->description = d;
+ }
+
+ /* The priority that has been set in /etc/rcN.d/ hierarchies
+ * takes precedence over what is stored as default in the LSB
+ * header */
+ if (s->sysv_start_priority_from_rcnd >= 0)
+ s->sysv_start_priority = s->sysv_start_priority_from_rcnd;
+
+ u->load_state = UNIT_LOADED;
+ r = 0;
+
+finish:
+
+ if (f)
+ fclose(f);
+
+ free(short_description);
+ free(long_description);
+ free(chkconfig_description);
+
+ return r;
+}
+
+static int service_load_sysv_name(Service *s, const char *name) {
+ char **p;
+
+ assert(s);
+ assert(name);
+
+ /* For SysV services we strip the boot.*, rc.* and *.sh
+ * prefixes/suffixes. */
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM)
+ if (endswith(name, ".sh.service"))
+ return -ENOENT;
+#endif
+
+#ifdef TARGET_SUSE
+ if (startswith(name, "boot."))
+ return -ENOENT;
+#endif
+
+#ifdef TARGET_FRUGALWARE
+ if (startswith(name, "rc."))
+ return -ENOENT;
+#endif
+
+ STRV_FOREACH(p, UNIT(s)->manager->lookup_paths.sysvinit_path) {
+ char *path;
+ int r;
+
+ path = strjoin(*p, "/", name, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ assert(endswith(path, ".service"));
+ path[strlen(path)-8] = 0;
+
+ r = service_load_sysv_path(s, path);
+
+#if defined(TARGET_DEBIAN) || defined(TARGET_UBUNTU) || defined(TARGET_ANGSTROM)
+ if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) {
+ /* Try Debian style *.sh source'able init scripts */
+ strcat(path, ".sh");
+ r = service_load_sysv_path(s, path);
+ }
+#endif
+ free(path);
+
+#ifdef TARGET_SUSE
+ if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) {
+ /* Try SUSE style boot.* init scripts */
+
+ path = strjoin(*p, "/boot.", name, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ /* Drop .service suffix */
+ path[strlen(path)-8] = 0;
+ r = service_load_sysv_path(s, path);
+ free(path);
+ }
+#endif
+
+#ifdef TARGET_FRUGALWARE
+ if (r >= 0 && UNIT(s)->load_state == UNIT_STUB) {
+ /* Try Frugalware style rc.* init scripts */
+
+ path = strjoin(*p, "/rc.", name, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ /* Drop .service suffix */
+ path[strlen(path)-8] = 0;
+ r = service_load_sysv_path(s, path);
+ free(path);
+ }
+#endif
+
+ if (r < 0)
+ return r;
+
+ if ((UNIT(s)->load_state != UNIT_STUB))
+ break;
+ }
+
+ return 0;
+}
+
+static int service_load_sysv(Service *s) {
+ const char *t;
+ Iterator i;
+ int r;
+
+ assert(s);
+
+ /* Load service data from SysV init scripts, preferably with
+ * LSB headers ... */
+
+ if (strv_isempty(UNIT(s)->manager->lookup_paths.sysvinit_path))
+ return 0;
+
+ if ((t = UNIT(s)->id))
+ if ((r = service_load_sysv_name(s, t)) < 0)
+ return r;
+
+ if (UNIT(s)->load_state == UNIT_STUB)
+ SET_FOREACH(t, UNIT(s)->names, i) {
+ if (t == UNIT(s)->id)
+ continue;
+
+ if ((r = service_load_sysv_name(s, t)) < 0)
+ return r;
+
+ if (UNIT(s)->load_state != UNIT_STUB)
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+static int fsck_fix_order(Service *s) {
+ Unit *other;
+ int r;
+
+ assert(s);
+
+ if (s->fsck_passno <= 0)
+ return 0;
+
+ /* For each pair of services where both have an fsck priority
+ * we order things based on it. */
+
+ LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_SERVICE]) {
+ Service *t;
+ UnitDependency d;
+
+ t = SERVICE(other);
+
+ if (s == t)
+ continue;
+
+ if (UNIT(t)->load_state != UNIT_LOADED)
+ continue;
+
+ if (t->fsck_passno <= 0)
+ continue;
+
+ if (t->fsck_passno < s->fsck_passno)
+ d = UNIT_AFTER;
+ else if (t->fsck_passno > s->fsck_passno)
+ d = UNIT_BEFORE;
+ else
+ continue;
+
+ if ((r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int service_verify(Service *s) {
+ assert(s);
+
+ if (UNIT(s)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!s->exec_command[SERVICE_EXEC_START]) {
+ log_error("%s lacks ExecStart setting. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->type != SERVICE_ONESHOT &&
+ s->exec_command[SERVICE_EXEC_START]->command_next) {
+ log_error("%s has more than one ExecStart setting, which is only allowed for Type=oneshot services. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->type == SERVICE_DBUS && !s->bus_name) {
+ log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->bus_name && s->type != SERVICE_DBUS)
+ log_warning("%s has a D-Bus service name specified, but is not of type dbus. Ignoring.", UNIT(s)->id);
+
+ if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) {
+ log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int service_add_default_dependencies(Service *s) {
+ int r;
+
+ assert(s);
+
+ /* Add a number of automatic dependencies useful for the
+ * majority of services. */
+
+ /* First, pull in base system */
+ if (UNIT(s)->manager->running_as == SYSTEMD_SYSTEM) {
+
+ if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
+ return r;
+
+ } else if (UNIT(s)->manager->running_as == SYSTEMD_USER) {
+
+ if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0)
+ return r;
+ }
+
+ /* Second, activate normal shutdown */
+ return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+}
+
+static void service_fix_output(Service *s) {
+ assert(s);
+
+ /* If nothing has been explicitly configured, patch default
+ * output in. If input is socket/tty we avoid this however,
+ * since in that case we want output to default to the same
+ * place as we read input from. */
+
+ if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT &&
+ s->exec_context.std_output == EXEC_OUTPUT_INHERIT &&
+ s->exec_context.std_input == EXEC_INPUT_NULL)
+ s->exec_context.std_error = UNIT(s)->manager->default_std_error;
+
+ if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT &&
+ s->exec_context.std_input == EXEC_INPUT_NULL)
+ s->exec_context.std_output = UNIT(s)->manager->default_std_output;
+}
+
+static int service_load(Unit *u) {
+ int r;
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ /* Load a .service file */
+ if ((r = unit_load_fragment(u)) < 0)
+ return r;
+
+#ifdef HAVE_SYSV_COMPAT
+ /* Load a classic init script as a fallback, if we couldn't find anything */
+ if (u->load_state == UNIT_STUB)
+ if ((r = service_load_sysv(s)) < 0)
+ return r;
+#endif
+
+ /* Still nothing found? Then let's give up */
+ if (u->load_state == UNIT_STUB)
+ return -ENOENT;
+
+ /* We were able to load something, then let's add in the
+ * dropin directories. */
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
+ return r;
+
+ /* This is a new unit? Then let's add in some extras */
+ if (u->load_state == UNIT_LOADED) {
+ if (s->type == _SERVICE_TYPE_INVALID)
+ s->type = s->bus_name ? SERVICE_DBUS : SERVICE_SIMPLE;
+
+ /* Oneshot services have disabled start timeout by default */
+ if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined)
+ s->timeout_start_usec = 0;
+
+ service_fix_output(s);
+
+ if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroups(u)) < 0)
+ return r;
+
+#ifdef HAVE_SYSV_COMPAT
+ if ((r = sysv_fix_order(s)) < 0)
+ return r;
+#endif
+
+ if ((r = fsck_fix_order(s)) < 0)
+ return r;
+
+ if (s->bus_name)
+ if ((r = unit_watch_bus_name(u, s->bus_name)) < 0)
+ return r;
+
+ if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE)
+ s->notify_access = NOTIFY_MAIN;
+
+ if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE)
+ s->notify_access = NOTIFY_MAIN;
+
+ if (s->type == SERVICE_DBUS || s->bus_name)
+ if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true)) < 0)
+ return r;
+
+ if (UNIT(s)->default_dependencies)
+ if ((r = service_add_default_dependencies(s)) < 0)
+ return r;
+
+ r = unit_exec_context_defaults(u, &s->exec_context);
+ if (r < 0)
+ return r;
+ }
+
+ return service_verify(s);
+}
+
+static void service_dump(Unit *u, FILE *f, const char *prefix) {
+
+ ServiceExecCommand c;
+ Service *s = SERVICE(u);
+ const char *prefix2;
+ char *p2;
+
+ assert(s);
+
+ p2 = strappend(prefix, "\t");
+ prefix2 = p2 ? p2 : prefix;
+
+ fprintf(f,
+ "%sService State: %s\n"
+ "%sResult: %s\n"
+ "%sReload Result: %s\n"
+ "%sPermissionsStartOnly: %s\n"
+ "%sRootDirectoryStartOnly: %s\n"
+ "%sRemainAfterExit: %s\n"
+ "%sGuessMainPID: %s\n"
+ "%sType: %s\n"
+ "%sRestart: %s\n"
+ "%sNotifyAccess: %s\n",
+ prefix, service_state_to_string(s->state),
+ prefix, service_result_to_string(s->result),
+ prefix, service_result_to_string(s->reload_result),
+ prefix, yes_no(s->permissions_start_only),
+ prefix, yes_no(s->root_directory_start_only),
+ prefix, yes_no(s->remain_after_exit),
+ prefix, yes_no(s->guess_main_pid),
+ prefix, service_type_to_string(s->type),
+ prefix, service_restart_to_string(s->restart),
+ prefix, notify_access_to_string(s->notify_access));
+
+ if (s->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %lu\n",
+ prefix, (unsigned long) s->control_pid);
+
+ if (s->main_pid > 0)
+ fprintf(f,
+ "%sMain PID: %lu\n"
+ "%sMain PID Known: %s\n"
+ "%sMain PID Alien: %s\n",
+ prefix, (unsigned long) s->main_pid,
+ prefix, yes_no(s->main_pid_known),
+ prefix, yes_no(s->main_pid_alien));
+
+ if (s->pid_file)
+ fprintf(f,
+ "%sPIDFile: %s\n",
+ prefix, s->pid_file);
+
+ if (s->bus_name)
+ fprintf(f,
+ "%sBusName: %s\n"
+ "%sBus Name Good: %s\n",
+ prefix, s->bus_name,
+ prefix, yes_no(s->bus_name_good));
+
+ kill_context_dump(&s->kill_context, f, prefix);
+ exec_context_dump(&s->exec_context, f, prefix);
+
+ for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) {
+
+ if (!s->exec_command[c])
+ continue;
+
+ fprintf(f, "%s-> %s:\n",
+ prefix, service_exec_command_to_string(c));
+
+ exec_command_dump_list(s->exec_command[c], f, prefix2);
+ }
+
+#ifdef HAVE_SYSV_COMPAT
+ if (s->is_sysv)
+ fprintf(f,
+ "%sSysV Init Script has LSB Header: %s\n"
+ "%sSysVEnabled: %s\n",
+ prefix, yes_no(s->sysv_has_lsb),
+ prefix, yes_no(s->sysv_enabled));
+
+ if (s->sysv_start_priority >= 0)
+ fprintf(f,
+ "%sSysVStartPriority: %i\n",
+ prefix, s->sysv_start_priority);
+
+ if (s->sysv_runlevels)
+ fprintf(f, "%sSysVRunLevels: %s\n",
+ prefix, s->sysv_runlevels);
+#endif
+
+ if (s->fsck_passno > 0)
+ fprintf(f,
+ "%sFsckPassNo: %i\n",
+ prefix, s->fsck_passno);
+
+ if (s->status_text)
+ fprintf(f, "%sStatus Text: %s\n",
+ prefix, s->status_text);
+
+ free(p2);
+}
+
+static int service_load_pid_file(Service *s, bool may_warn) {
+ char *k;
+ int r;
+ pid_t pid;
+
+ assert(s);
+
+ if (!s->pid_file)
+ return -ENOENT;
+
+ if ((r = read_one_line_file(s->pid_file, &k)) < 0) {
+ if (may_warn)
+ log_info("PID file %s not readable (yet?) after %s.",
+ s->pid_file, service_state_to_string(s->state));
+ return r;
+ }
+
+ r = parse_pid(k, &pid);
+ free(k);
+
+ if (r < 0)
+ return r;
+
+ if (kill(pid, 0) < 0 && errno != EPERM) {
+ if (may_warn)
+ log_info("PID %lu read from file %s does not exist.",
+ (unsigned long) pid, s->pid_file);
+ return -ESRCH;
+ }
+
+ if (s->main_pid_known) {
+ if (pid == s->main_pid)
+ return 0;
+
+ log_debug("Main PID changing: %lu -> %lu",
+ (unsigned long) s->main_pid, (unsigned long) pid);
+ service_unwatch_main_pid(s);
+ s->main_pid_known = false;
+ } else
+ log_debug("Main PID loaded: %lu", (unsigned long) pid);
+
+ if ((r = service_set_main_pid(s, pid)) < 0)
+ return r;
+
+ if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
+ /* FIXME: we need to do something here */
+ return r;
+
+ return 0;
+}
+
+static int service_search_main_pid(Service *s) {
+ pid_t pid;
+ int r;
+
+ assert(s);
+
+ /* If we know it anyway, don't ever fallback to unreliable
+ * heuristics */
+ if (s->main_pid_known)
+ return 0;
+
+ if (!s->guess_main_pid)
+ return 0;
+
+ assert(s->main_pid <= 0);
+
+ if ((pid = cgroup_bonding_search_main_pid_list(UNIT(s)->cgroup_bondings)) <= 0)
+ return -ENOENT;
+
+ log_debug("Main PID guessed: %lu", (unsigned long) pid);
+ if ((r = service_set_main_pid(s, pid)) < 0)
+ return r;
+
+ if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
+ /* FIXME: we need to do something here */
+ return r;
+
+ return 0;
+}
+
+static void service_notify_sockets_dead(Service *s, bool failed_permanent) {
+ Iterator i;
+ Unit *u;
+
+ assert(s);
+
+ /* Notifies all our sockets when we die */
+
+ if (s->socket_fd >= 0)
+ return;
+
+ SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i)
+ if (u->type == UNIT_SOCKET)
+ socket_notify_service_dead(SOCKET(u), failed_permanent);
+
+ return;
+}
+
+static void service_set_state(Service *s, ServiceState state) {
+ ServiceState old_state;
+ const UnitActiveState *table;
+ assert(s);
+
+ table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table;
+
+ old_state = s->state;
+ s->state = state;
+
+ service_unwatch_pid_file(s);
+
+ if (state != SERVICE_START_PRE &&
+ state != SERVICE_START &&
+ state != SERVICE_START_POST &&
+ state != SERVICE_RELOAD &&
+ state != SERVICE_STOP &&
+ state != SERVICE_STOP_SIGTERM &&
+ state != SERVICE_STOP_SIGKILL &&
+ state != SERVICE_STOP_POST &&
+ state != SERVICE_FINAL_SIGTERM &&
+ state != SERVICE_FINAL_SIGKILL &&
+ state != SERVICE_AUTO_RESTART)
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+
+ if (state != SERVICE_START &&
+ state != SERVICE_START_POST &&
+ state != SERVICE_RUNNING &&
+ state != SERVICE_RELOAD &&
+ state != SERVICE_STOP &&
+ state != SERVICE_STOP_SIGTERM &&
+ state != SERVICE_STOP_SIGKILL) {
+ service_unwatch_main_pid(s);
+ s->main_command = NULL;
+ }
+
+ if (state != SERVICE_START_PRE &&
+ state != SERVICE_START &&
+ state != SERVICE_START_POST &&
+ state != SERVICE_RELOAD &&
+ state != SERVICE_STOP &&
+ state != SERVICE_STOP_SIGTERM &&
+ state != SERVICE_STOP_SIGKILL &&
+ state != SERVICE_STOP_POST &&
+ state != SERVICE_FINAL_SIGTERM &&
+ state != SERVICE_FINAL_SIGKILL) {
+ service_unwatch_control_pid(s);
+ s->control_command = NULL;
+ s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+ }
+
+ if (state == SERVICE_DEAD ||
+ state == SERVICE_STOP ||
+ state == SERVICE_STOP_SIGTERM ||
+ state == SERVICE_STOP_SIGKILL ||
+ state == SERVICE_STOP_POST ||
+ state == SERVICE_FINAL_SIGTERM ||
+ state == SERVICE_FINAL_SIGKILL ||
+ state == SERVICE_FAILED ||
+ state == SERVICE_AUTO_RESTART)
+ service_notify_sockets_dead(s, false);
+
+ if (state != SERVICE_START_PRE &&
+ state != SERVICE_START &&
+ state != SERVICE_START_POST &&
+ state != SERVICE_RUNNING &&
+ state != SERVICE_RELOAD &&
+ state != SERVICE_STOP &&
+ state != SERVICE_STOP_SIGTERM &&
+ state != SERVICE_STOP_SIGKILL &&
+ state != SERVICE_STOP_POST &&
+ state != SERVICE_FINAL_SIGTERM &&
+ state != SERVICE_FINAL_SIGKILL &&
+ !(state == SERVICE_DEAD && UNIT(s)->job)) {
+ service_close_socket_fd(s);
+ service_connection_unref(s);
+ }
+
+ if (state == SERVICE_STOP)
+ service_stop_watchdog(s);
+
+ /* For the inactive states unit_notify() will trim the cgroup,
+ * but for exit we have to do that ourselves... */
+ if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0)
+ cgroup_bonding_trim_list(UNIT(s)->cgroup_bondings, true);
+
+ if (old_state != state)
+ log_struct(LOG_DEBUG,
+ "UNIT=%s", UNIT(s)->id,
+ "MESSAGE=%s changed %s -> %s", UNIT(s)->id,
+ service_state_to_string(old_state),
+ service_state_to_string(state),
+ NULL);
+
+ unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS);
+ s->reload_result = SERVICE_SUCCESS;
+}
+
+static int service_coldplug(Unit *u) {
+ Service *s = SERVICE(u);
+ int r;
+
+ assert(s);
+ assert(s->state == SERVICE_DEAD);
+
+ if (s->deserialized_state != s->state) {
+
+ if (s->deserialized_state == SERVICE_START_PRE ||
+ s->deserialized_state == SERVICE_START ||
+ s->deserialized_state == SERVICE_START_POST ||
+ s->deserialized_state == SERVICE_RELOAD ||
+ s->deserialized_state == SERVICE_STOP ||
+ s->deserialized_state == SERVICE_STOP_SIGTERM ||
+ s->deserialized_state == SERVICE_STOP_SIGKILL ||
+ s->deserialized_state == SERVICE_STOP_POST ||
+ s->deserialized_state == SERVICE_FINAL_SIGTERM ||
+ s->deserialized_state == SERVICE_FINAL_SIGKILL ||
+ s->deserialized_state == SERVICE_AUTO_RESTART) {
+ if (s->deserialized_state == SERVICE_AUTO_RESTART || s->timeout_start_usec > 0) {
+ usec_t k;
+
+ k = s->deserialized_state == SERVICE_AUTO_RESTART ? s->restart_usec : s->timeout_start_usec;
+
+ if ((r = unit_watch_timer(UNIT(s), k, &s->timer_watch)) < 0)
+ return r;
+ }
+ }
+
+ if ((s->deserialized_state == SERVICE_START &&
+ (s->type == SERVICE_FORKING ||
+ s->type == SERVICE_DBUS ||
+ s->type == SERVICE_ONESHOT ||
+ s->type == SERVICE_NOTIFY)) ||
+ s->deserialized_state == SERVICE_START_POST ||
+ s->deserialized_state == SERVICE_RUNNING ||
+ s->deserialized_state == SERVICE_RELOAD ||
+ s->deserialized_state == SERVICE_STOP ||
+ s->deserialized_state == SERVICE_STOP_SIGTERM ||
+ s->deserialized_state == SERVICE_STOP_SIGKILL)
+ if (s->main_pid > 0)
+ if ((r = unit_watch_pid(UNIT(s), s->main_pid)) < 0)
+ return r;
+
+ if (s->deserialized_state == SERVICE_START_PRE ||
+ s->deserialized_state == SERVICE_START ||
+ s->deserialized_state == SERVICE_START_POST ||
+ s->deserialized_state == SERVICE_RELOAD ||
+ s->deserialized_state == SERVICE_STOP ||
+ s->deserialized_state == SERVICE_STOP_SIGTERM ||
+ s->deserialized_state == SERVICE_STOP_SIGKILL ||
+ s->deserialized_state == SERVICE_STOP_POST ||
+ s->deserialized_state == SERVICE_FINAL_SIGTERM ||
+ s->deserialized_state == SERVICE_FINAL_SIGKILL)
+ if (s->control_pid > 0)
+ if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0)
+ return r;
+
+ if (s->deserialized_state == SERVICE_START_POST ||
+ s->deserialized_state == SERVICE_RUNNING)
+ service_handle_watchdog(s);
+
+ service_set_state(s, s->deserialized_state);
+ }
+ return 0;
+}
+
+static int service_collect_fds(Service *s, int **fds, unsigned *n_fds) {
+ Iterator i;
+ int r;
+ int *rfds = NULL;
+ unsigned rn_fds = 0;
+ Unit *u;
+
+ assert(s);
+ assert(fds);
+ assert(n_fds);
+
+ if (s->socket_fd >= 0)
+ return 0;
+
+ SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) {
+ int *cfds;
+ unsigned cn_fds;
+ Socket *sock;
+
+ if (u->type != UNIT_SOCKET)
+ continue;
+
+ sock = SOCKET(u);
+
+ if ((r = socket_collect_fds(sock, &cfds, &cn_fds)) < 0)
+ goto fail;
+
+ if (!cfds)
+ continue;
+
+ if (!rfds) {
+ rfds = cfds;
+ rn_fds = cn_fds;
+ } else {
+ int *t;
+
+ if (!(t = new(int, rn_fds+cn_fds))) {
+ free(cfds);
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ memcpy(t, rfds, rn_fds * sizeof(int));
+ memcpy(t+rn_fds, cfds, cn_fds * sizeof(int));
+ free(rfds);
+ free(cfds);
+
+ rfds = t;
+ rn_fds = rn_fds+cn_fds;
+ }
+ }
+
+ *fds = rfds;
+ *n_fds = rn_fds;
+
+ return 0;
+
+fail:
+ free(rfds);
+
+ return r;
+}
+
+static int service_spawn(
+ Service *s,
+ ExecCommand *c,
+ bool timeout,
+ bool pass_fds,
+ bool apply_permissions,
+ bool apply_chroot,
+ bool apply_tty_stdin,
+ bool set_notify_socket,
+ bool is_control,
+ pid_t *_pid) {
+
+ pid_t pid;
+ int r;
+ int *fds = NULL, *fdsbuf = NULL;
+ unsigned n_fds = 0, n_env = 0;
+ char **argv = NULL, **final_env = NULL, **our_env = NULL;
+
+ assert(s);
+ assert(c);
+ assert(_pid);
+
+ if (pass_fds ||
+ s->exec_context.std_input == EXEC_INPUT_SOCKET ||
+ s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
+ s->exec_context.std_error == EXEC_OUTPUT_SOCKET) {
+
+ if (s->socket_fd >= 0) {
+ fds = &s->socket_fd;
+ n_fds = 1;
+ } else {
+ if ((r = service_collect_fds(s, &fdsbuf, &n_fds)) < 0)
+ goto fail;
+
+ fds = fdsbuf;
+ }
+ }
+
+ if (timeout && s->timeout_start_usec) {
+ r = unit_watch_timer(UNIT(s), s->timeout_start_usec, &s->timer_watch);
+ if (r < 0)
+ goto fail;
+ } else
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+
+ if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ our_env = new0(char*, 5);
+ if (!our_env) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (set_notify_socket)
+ if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (s->main_pid > 0)
+ if (asprintf(our_env + n_env++, "MAINPID=%lu", (unsigned long) s->main_pid) < 0) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (s->watchdog_usec > 0)
+ if (asprintf(our_env + n_env++, "WATCHDOG_USEC=%llu", (unsigned long long) s->watchdog_usec) < 0) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (s->meta.manager->running_as != SYSTEMD_SYSTEM)
+ if (asprintf(our_env + n_env++, "MANAGERPID=%lu", (unsigned long) getpid()) < 0) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL);
+ if (!final_env) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = exec_spawn(c,
+ argv,
+ &s->exec_context,
+ fds, n_fds,
+ final_env,
+ apply_permissions,
+ apply_chroot,
+ apply_tty_stdin,
+ UNIT(s)->manager->confirm_spawn,
+ UNIT(s)->cgroup_bondings,
+ UNIT(s)->cgroup_attributes,
+ is_control ? "control" : NULL,
+ UNIT(s)->id,
+ s->type == SERVICE_IDLE ? UNIT(s)->manager->idle_pipe : NULL,
+ &pid);
+
+ if (r < 0)
+ goto fail;
+
+ if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
+ /* FIXME: we need to do something here */
+ goto fail;
+
+ free(fdsbuf);
+ strv_free(argv);
+ strv_free(our_env);
+ strv_free(final_env);
+
+ *_pid = pid;
+
+ return 0;
+
+fail:
+ free(fdsbuf);
+ strv_free(argv);
+ strv_free(our_env);
+ strv_free(final_env);
+
+ if (timeout)
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+
+ return r;
+}
+
+static int main_pid_good(Service *s) {
+ assert(s);
+
+ /* Returns 0 if the pid is dead, 1 if it is good, -1 if we
+ * don't know */
+
+ /* If we know the pid file, then lets just check if it is
+ * still valid */
+ if (s->main_pid_known) {
+
+ /* If it's an alien child let's check if it is still
+ * alive ... */
+ if (s->main_pid_alien)
+ return kill(s->main_pid, 0) >= 0 || errno != ESRCH;
+
+ /* .. otherwise assume we'll get a SIGCHLD for it,
+ * which we really should wait for to collect exit
+ * status and code */
+ return s->main_pid > 0;
+ }
+
+ /* We don't know the pid */
+ return -EAGAIN;
+}
+
+static int control_pid_good(Service *s) {
+ assert(s);
+
+ return s->control_pid > 0;
+}
+
+static int cgroup_good(Service *s) {
+ int r;
+
+ assert(s);
+
+ if ((r = cgroup_bonding_is_empty_list(UNIT(s)->cgroup_bondings)) < 0)
+ return r;
+
+ return !r;
+}
+
+static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
+ int r;
+ assert(s);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD);
+
+ if (allow_restart &&
+ !s->forbid_restart &&
+ (s->restart == SERVICE_RESTART_ALWAYS ||
+ (s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) ||
+ (s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) ||
+ (s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL ||
+ s->result == SERVICE_FAILURE_CORE_DUMP))) &&
+ (s->result != SERVICE_FAILURE_EXIT_CODE ||
+ !set_contains(s->restart_ignore_status.code, INT_TO_PTR(s->main_exec_status.status))) &&
+ (s->result != SERVICE_FAILURE_SIGNAL ||
+ !set_contains(s->restart_ignore_status.signal, INT_TO_PTR(s->main_exec_status.status)))
+ ) {
+
+ r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch);
+ if (r < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_AUTO_RESTART);
+ }
+
+ s->forbid_restart = false;
+
+ return;
+
+fail:
+ log_warning("%s failed to run install restart timer: %s", UNIT(s)->id, strerror(-r));
+ service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false);
+}
+
+static void service_enter_signal(Service *s, ServiceState state, ServiceResult f);
+
+static void service_enter_stop_post(Service *s, ServiceResult f) {
+ int r;
+ assert(s);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ service_unwatch_control_pid(s);
+
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) {
+ s->control_command_id = SERVICE_EXEC_STOP_POST;
+
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ true,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+
+ service_set_state(s, SERVICE_STOP_POST);
+ } else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r));
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) {
+ int r;
+ Set *pid_set = NULL;
+ bool wait_for_exit = false;
+
+ assert(s);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ if (s->kill_context.kill_mode != KILL_NONE) {
+ int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? s->kill_context.kill_signal : SIGKILL;
+
+ if (s->main_pid > 0) {
+ if (kill_and_sigcont(s->main_pid, sig) < 0 && errno != ESRCH)
+ log_warning("Failed to kill main process %li: %m", (long) s->main_pid);
+ else
+ wait_for_exit = !s->main_pid_alien;
+ }
+
+ if (s->control_pid > 0) {
+ if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH)
+ log_warning("Failed to kill control process %li: %m", (long) s->control_pid);
+ else
+ wait_for_exit = true;
+ }
+
+ if (s->kill_context.kill_mode == KILL_CONTROL_GROUP) {
+
+ pid_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!pid_set) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ /* Exclude the main/control pids from being killed via the cgroup */
+ if (s->main_pid > 0)
+ if ((r = set_put(pid_set, LONG_TO_PTR(s->main_pid))) < 0)
+ goto fail;
+
+ if (s->control_pid > 0)
+ if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0)
+ goto fail;
+
+ r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, false, pid_set, NULL);
+ if (r < 0) {
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ log_warning("Failed to kill control group: %s", strerror(-r));
+ } else if (r > 0)
+ wait_for_exit = true;
+
+ set_free(pid_set);
+ pid_set = NULL;
+ }
+ }
+
+ if (wait_for_exit) {
+ if (s->timeout_stop_usec > 0) {
+ r = unit_watch_timer(UNIT(s), s->timeout_stop_usec, &s->timer_watch);
+ if (r < 0)
+ goto fail;
+ }
+
+ service_set_state(s, state);
+ } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+ service_enter_stop_post(s, SERVICE_SUCCESS);
+ else
+ service_enter_dead(s, SERVICE_SUCCESS, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r));
+
+ if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+ service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES);
+ else
+ service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
+
+ if (pid_set)
+ set_free(pid_set);
+}
+
+static void service_enter_stop(Service *s, ServiceResult f) {
+ int r;
+
+ assert(s);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ service_unwatch_control_pid(s);
+
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) {
+ s->control_command_id = SERVICE_EXEC_STOP;
+
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ false,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_STOP);
+ } else
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'stop' task: %s", UNIT(s)->id, strerror(-r));
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_enter_running(Service *s, ServiceResult f) {
+ int main_pid_ok, cgroup_ok;
+ assert(s);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ main_pid_ok = main_pid_good(s);
+ cgroup_ok = cgroup_good(s);
+
+ if ((main_pid_ok > 0 || (main_pid_ok < 0 && cgroup_ok != 0)) &&
+ (s->bus_name_good || s->type != SERVICE_DBUS))
+ service_set_state(s, SERVICE_RUNNING);
+ else if (s->remain_after_exit)
+ service_set_state(s, SERVICE_EXITED);
+ else
+ service_enter_stop(s, SERVICE_SUCCESS);
+}
+
+static void service_enter_start_post(Service *s) {
+ int r;
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ if (s->watchdog_usec > 0)
+ service_reset_watchdog(s);
+
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) {
+ s->control_command_id = SERVICE_EXEC_START_POST;
+
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ false,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_START_POST);
+ } else
+ service_enter_running(s, SERVICE_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r));
+ service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_enter_start(Service *s) {
+ pid_t pid;
+ int r;
+ ExecCommand *c;
+
+ assert(s);
+
+ assert(s->exec_command[SERVICE_EXEC_START]);
+ assert(!s->exec_command[SERVICE_EXEC_START]->command_next || s->type == SERVICE_ONESHOT);
+
+ if (s->type == SERVICE_FORKING)
+ service_unwatch_control_pid(s);
+ else
+ service_unwatch_main_pid(s);
+
+ /* We want to ensure that nobody leaks processes from
+ * START_PRE here, so let's go on a killing spree, People
+ * should not spawn long running processes from START_PRE. */
+ cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
+
+ if (s->type == SERVICE_FORKING) {
+ s->control_command_id = SERVICE_EXEC_START;
+ c = s->control_command = s->exec_command[SERVICE_EXEC_START];
+
+ s->main_command = NULL;
+ } else {
+ s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+ s->control_command = NULL;
+
+ c = s->main_command = s->exec_command[SERVICE_EXEC_START];
+ }
+
+ r = service_spawn(s,
+ c,
+ s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY || s->type == SERVICE_ONESHOT,
+ true,
+ true,
+ true,
+ true,
+ s->notify_access != NOTIFY_NONE,
+ false,
+ &pid);
+ if (r < 0)
+ goto fail;
+
+ if (s->type == SERVICE_SIMPLE || s->type == SERVICE_IDLE) {
+ /* For simple services we immediately start
+ * the START_POST binaries. */
+
+ service_set_main_pid(s, pid);
+ service_enter_start_post(s);
+
+ } else if (s->type == SERVICE_FORKING) {
+
+ /* For forking services we wait until the start
+ * process exited. */
+
+ s->control_pid = pid;
+ service_set_state(s, SERVICE_START);
+
+ } else if (s->type == SERVICE_ONESHOT ||
+ s->type == SERVICE_DBUS ||
+ s->type == SERVICE_NOTIFY) {
+
+ /* For oneshot services we wait until the start
+ * process exited, too, but it is our main process. */
+
+ /* For D-Bus services we know the main pid right away,
+ * but wait for the bus name to appear on the
+ * bus. Notify services are similar. */
+
+ service_set_main_pid(s, pid);
+ service_set_state(s, SERVICE_START);
+ } else
+ assert_not_reached("Unknown service type");
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'start' task: %s", UNIT(s)->id, strerror(-r));
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_enter_start_pre(Service *s) {
+ int r;
+
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) {
+
+ /* Before we start anything, let's clear up what might
+ * be left from previous runs. */
+ cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
+
+ s->control_command_id = SERVICE_EXEC_START_PRE;
+
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ true,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_START_PRE);
+ } else
+ service_enter_start(s);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r));
+ service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
+}
+
+static void service_enter_restart(Service *s) {
+ int r;
+ DBusError error;
+
+ assert(s);
+ dbus_error_init(&error);
+
+ if (UNIT(s)->job && UNIT(s)->job->type == JOB_STOP) {
+ /* Don't restart things if we are going down anyway */
+ log_info("Stop job pending for unit, delaying automatic restart.");
+
+ r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch);
+ if (r < 0)
+ goto fail;
+
+ return;
+ }
+
+ /* Any units that are bound to this service must also be
+ * restarted. We use JOB_RESTART (instead of the more obvious
+ * JOB_START) here so that those dependency jobs will be added
+ * as well. */
+ r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_FAIL, false, &error, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* Note that we stay in the SERVICE_AUTO_RESTART state here,
+ * it will be canceled as part of the service_stop() call that
+ * is executed as part of JOB_RESTART. */
+
+ log_debug("%s scheduled restart job.", UNIT(s)->id);
+ return;
+
+fail:
+ log_warning("%s failed to schedule restart job: %s", UNIT(s)->id, bus_error(&error, -r));
+ service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false);
+
+ dbus_error_free(&error);
+}
+
+static void service_enter_reload(Service *s) {
+ int r;
+
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) {
+ s->control_command_id = SERVICE_EXEC_RELOAD;
+
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ false,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_RELOAD);
+ } else
+ service_enter_running(s, SERVICE_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'reload' task: %s", UNIT(s)->id, strerror(-r));
+ s->reload_result = SERVICE_FAILURE_RESOURCES;
+ service_enter_running(s, SERVICE_SUCCESS);
+}
+
+static void service_run_next_control(Service *s) {
+ int r;
+
+ assert(s);
+ assert(s->control_command);
+ assert(s->control_command->command_next);
+
+ assert(s->control_command_id != SERVICE_EXEC_START);
+
+ s->control_command = s->control_command->command_next;
+ service_unwatch_control_pid(s);
+
+ r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ s->control_command_id == SERVICE_EXEC_START_PRE ||
+ s->control_command_id == SERVICE_EXEC_STOP_POST,
+ false,
+ true,
+ &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ return;
+
+fail:
+ log_warning("%s failed to run next control task: %s", UNIT(s)->id, strerror(-r));
+
+ if (s->state == SERVICE_START_PRE)
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+ else if (s->state == SERVICE_STOP)
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
+ else if (s->state == SERVICE_STOP_POST)
+ service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
+ else if (s->state == SERVICE_RELOAD) {
+ s->reload_result = SERVICE_FAILURE_RESOURCES;
+ service_enter_running(s, SERVICE_SUCCESS);
+ } else
+ service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_run_next_main(Service *s) {
+ pid_t pid;
+ int r;
+
+ assert(s);
+ assert(s->main_command);
+ assert(s->main_command->command_next);
+ assert(s->type == SERVICE_ONESHOT);
+
+ s->main_command = s->main_command->command_next;
+ service_unwatch_main_pid(s);
+
+ r = service_spawn(s,
+ s->main_command,
+ true,
+ true,
+ true,
+ true,
+ true,
+ s->notify_access != NOTIFY_NONE,
+ false,
+ &pid);
+ if (r < 0)
+ goto fail;
+
+ service_set_main_pid(s, pid);
+
+ return;
+
+fail:
+ log_warning("%s failed to run next main task: %s", UNIT(s)->id, strerror(-r));
+ service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+}
+
+static int service_start_limit_test(Service *s) {
+ assert(s);
+
+ if (ratelimit_test(&s->start_limit))
+ return 0;
+
+ switch (s->start_limit_action) {
+
+ case SERVICE_START_LIMIT_NONE:
+ log_warning("%s start request repeated too quickly, refusing to start.", UNIT(s)->id);
+ break;
+
+ case SERVICE_START_LIMIT_REBOOT: {
+ DBusError error;
+ int r;
+
+ dbus_error_init(&error);
+
+ log_warning("%s start request repeated too quickly, rebooting.", UNIT(s)->id);
+
+ r = manager_add_job_by_name(UNIT(s)->manager, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, true, &error, NULL);
+ if (r < 0) {
+ log_error("Failed to reboot: %s.", bus_error(&error, r));
+ dbus_error_free(&error);
+ }
+
+ break;
+ }
+
+ case SERVICE_START_LIMIT_REBOOT_FORCE:
+ log_warning("%s start request repeated too quickly, forcibly rebooting.", UNIT(s)->id);
+ UNIT(s)->manager->exit_code = MANAGER_REBOOT;
+ break;
+
+ case SERVICE_START_LIMIT_REBOOT_IMMEDIATE:
+ log_warning("%s start request repeated too quickly, rebooting immediately.", UNIT(s)->id);
+ reboot(RB_AUTOBOOT);
+ break;
+
+ default:
+ log_error("start limit action=%i", s->start_limit_action);
+ assert_not_reached("Unknown StartLimitAction.");
+ }
+
+ return -ECANCELED;
+}
+
+static int service_start(Unit *u) {
+ Service *s = SERVICE(u);
+ int r;
+
+ assert(s);
+
+ /* We cannot fulfill this request right now, try again later
+ * please! */
+ if (s->state == SERVICE_STOP ||
+ s->state == SERVICE_STOP_SIGTERM ||
+ s->state == SERVICE_STOP_SIGKILL ||
+ s->state == SERVICE_STOP_POST ||
+ s->state == SERVICE_FINAL_SIGTERM ||
+ s->state == SERVICE_FINAL_SIGKILL)
+ return -EAGAIN;
+
+ /* Already on it! */
+ if (s->state == SERVICE_START_PRE ||
+ s->state == SERVICE_START ||
+ s->state == SERVICE_START_POST)
+ return 0;
+
+ /* A service that will be restarted must be stopped first to
+ * trigger BindsTo and/or OnFailure dependencies. If a user
+ * does not want to wait for the holdoff time to elapse, the
+ * service should be manually restarted, not started. We
+ * simply return EAGAIN here, so that any start jobs stay
+ * queued, and assume that the auto restart timer will
+ * eventually trigger the restart. */
+ if (s->state == SERVICE_AUTO_RESTART)
+ return -EAGAIN;
+
+ assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED);
+
+ /* Make sure we don't enter a busy loop of some kind. */
+ r = service_start_limit_test(s);
+ if (r < 0) {
+ service_enter_dead(s, SERVICE_FAILURE_START_LIMIT, false);
+ return r;
+ }
+
+ s->result = SERVICE_SUCCESS;
+ s->reload_result = SERVICE_SUCCESS;
+ s->main_pid_known = false;
+ s->main_pid_alien = false;
+ s->forbid_restart = false;
+
+ service_enter_start_pre(s);
+ return 0;
+}
+
+static int service_stop(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ /* Don't create restart jobs from here. */
+ s->forbid_restart = true;
+
+ /* Already on it */
+ if (s->state == SERVICE_STOP ||
+ s->state == SERVICE_STOP_SIGTERM ||
+ s->state == SERVICE_STOP_SIGKILL ||
+ s->state == SERVICE_STOP_POST ||
+ s->state == SERVICE_FINAL_SIGTERM ||
+ s->state == SERVICE_FINAL_SIGKILL)
+ return 0;
+
+ /* A restart will be scheduled or is in progress. */
+ if (s->state == SERVICE_AUTO_RESTART) {
+ service_set_state(s, SERVICE_DEAD);
+ return 0;
+ }
+
+ /* If there's already something running we go directly into
+ * kill mode. */
+ if (s->state == SERVICE_START_PRE ||
+ s->state == SERVICE_START ||
+ s->state == SERVICE_START_POST ||
+ s->state == SERVICE_RELOAD) {
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
+ return 0;
+ }
+
+ assert(s->state == SERVICE_RUNNING ||
+ s->state == SERVICE_EXITED);
+
+ service_enter_stop(s, SERVICE_SUCCESS);
+ return 0;
+}
+
+static int service_reload(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED);
+
+ service_enter_reload(s);
+ return 0;
+}
+
+static bool service_can_reload(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ return !!s->exec_command[SERVICE_EXEC_RELOAD];
+}
+
+static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Service *s = SERVICE(u);
+
+ assert(u);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", service_state_to_string(s->state));
+ unit_serialize_item(u, f, "result", service_result_to_string(s->result));
+ unit_serialize_item(u, f, "reload-result", service_result_to_string(s->reload_result));
+
+ if (s->control_pid > 0)
+ unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid);
+
+ if (s->main_pid_known && s->main_pid > 0)
+ unit_serialize_item_format(u, f, "main-pid", "%lu", (unsigned long) s->main_pid);
+
+ unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known));
+
+ if (s->status_text)
+ unit_serialize_item(u, f, "status-text", s->status_text);
+
+ /* FIXME: There's a minor uncleanliness here: if there are
+ * multiple commands attached here, we will start from the
+ * first one again */
+ if (s->control_command_id >= 0)
+ unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id));
+
+ if (s->socket_fd >= 0) {
+ int copy;
+
+ if ((copy = fdset_put_dup(fds, s->socket_fd)) < 0)
+ return copy;
+
+ unit_serialize_item_format(u, f, "socket-fd", "%i", copy);
+ }
+
+ if (s->main_exec_status.pid > 0) {
+ unit_serialize_item_format(u, f, "main-exec-status-pid", "%lu", (unsigned long) s->main_exec_status.pid);
+ dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp);
+ dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp);
+
+ if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) {
+ unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code);
+ unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status);
+ }
+ }
+ if (dual_timestamp_is_set(&s->watchdog_timestamp))
+ dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp);
+
+ return 0;
+}
+
+static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Service *s = SERVICE(u);
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ ServiceState state;
+
+ if ((state = service_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ s->deserialized_state = state;
+ } else if (streq(key, "result")) {
+ ServiceResult f;
+
+ f = service_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ } else if (streq(key, "reload-result")) {
+ ServiceResult f;
+
+ f = service_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse reload result value %s", value);
+ else if (f != SERVICE_SUCCESS)
+ s->reload_result = f;
+
+ } else if (streq(key, "control-pid")) {
+ pid_t pid;
+
+ if (parse_pid(value, &pid) < 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ s->control_pid = pid;
+ } else if (streq(key, "main-pid")) {
+ pid_t pid;
+
+ if (parse_pid(value, &pid) < 0)
+ log_debug("Failed to parse main-pid value %s", value);
+ else
+ service_set_main_pid(s, (pid_t) pid);
+ } else if (streq(key, "main-pid-known")) {
+ int b;
+
+ if ((b = parse_boolean(value)) < 0)
+ log_debug("Failed to parse main-pid-known value %s", value);
+ else
+ s->main_pid_known = b;
+ } else if (streq(key, "status-text")) {
+ char *t;
+
+ if ((t = strdup(value))) {
+ free(s->status_text);
+ s->status_text = t;
+ }
+
+ } else if (streq(key, "control-command")) {
+ ServiceExecCommand id;
+
+ if ((id = service_exec_command_from_string(value)) < 0)
+ log_debug("Failed to parse exec-command value %s", value);
+ else {
+ s->control_command_id = id;
+ s->control_command = s->exec_command[id];
+ }
+ } else if (streq(key, "socket-fd")) {
+ int fd;
+
+ if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse socket-fd value %s", value);
+ else {
+
+ if (s->socket_fd >= 0)
+ close_nointr_nofail(s->socket_fd);
+ s->socket_fd = fdset_remove(fds, fd);
+ }
+ } else if (streq(key, "main-exec-status-pid")) {
+ pid_t pid;
+
+ if (parse_pid(value, &pid) < 0)
+ log_debug("Failed to parse main-exec-status-pid value %s", value);
+ else
+ s->main_exec_status.pid = pid;
+ } else if (streq(key, "main-exec-status-code")) {
+ int i;
+
+ if (safe_atoi(value, &i) < 0)
+ log_debug("Failed to parse main-exec-status-code value %s", value);
+ else
+ s->main_exec_status.code = i;
+ } else if (streq(key, "main-exec-status-status")) {
+ int i;
+
+ if (safe_atoi(value, &i) < 0)
+ log_debug("Failed to parse main-exec-status-status value %s", value);
+ else
+ s->main_exec_status.status = i;
+ } else if (streq(key, "main-exec-status-start"))
+ dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp);
+ else if (streq(key, "main-exec-status-exit"))
+ dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp);
+ else if (streq(key, "watchdog-timestamp"))
+ dual_timestamp_deserialize(value, &s->watchdog_timestamp);
+ else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState service_active_state(Unit *u) {
+ const UnitActiveState *table;
+
+ assert(u);
+
+ table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table;
+
+ return table[SERVICE(u)->state];
+}
+
+static const char *service_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return service_state_to_string(SERVICE(u)->state);
+}
+
+static bool service_check_gc(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ /* Never clean up services that still have a process around,
+ * even if the service is formally dead. */
+ if (cgroup_good(s) > 0 ||
+ main_pid_good(s) > 0 ||
+ control_pid_good(s) > 0)
+ return true;
+
+#ifdef HAVE_SYSV_COMPAT
+ if (s->is_sysv)
+ return true;
+#endif
+
+ return false;
+}
+
+static bool service_check_snapshot(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ return !s->got_socket_fd;
+}
+
+static int service_retry_pid_file(Service *s) {
+ int r;
+
+ assert(s->pid_file);
+ assert(s->state == SERVICE_START || s->state == SERVICE_START_POST);
+
+ r = service_load_pid_file(s, false);
+ if (r < 0)
+ return r;
+
+ service_unwatch_pid_file(s);
+
+ service_enter_running(s, SERVICE_SUCCESS);
+ return 0;
+}
+
+static int service_watch_pid_file(Service *s) {
+ int r;
+
+ log_debug("Setting watch for %s's PID file %s", UNIT(s)->id, s->pid_file_pathspec->path);
+ r = path_spec_watch(s->pid_file_pathspec, UNIT(s));
+ if (r < 0)
+ goto fail;
+
+ /* the pidfile might have appeared just before we set the watch */
+ service_retry_pid_file(s);
+
+ return 0;
+fail:
+ log_error("Failed to set a watch for %s's PID file %s: %s",
+ UNIT(s)->id, s->pid_file_pathspec->path, strerror(-r));
+ service_unwatch_pid_file(s);
+ return r;
+}
+
+static int service_demand_pid_file(Service *s) {
+ PathSpec *ps;
+
+ assert(s->pid_file);
+ assert(!s->pid_file_pathspec);
+
+ ps = new0(PathSpec, 1);
+ if (!ps)
+ return -ENOMEM;
+
+ ps->path = strdup(s->pid_file);
+ if (!ps->path) {
+ free(ps);
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(ps->path);
+
+ /* PATH_CHANGED would not be enough. There are daemons (sendmail) that
+ * keep their PID file open all the time. */
+ ps->type = PATH_MODIFIED;
+ ps->inotify_fd = -1;
+
+ s->pid_file_pathspec = ps;
+
+ return service_watch_pid_file(s);
+}
+
+static void service_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+ assert(fd >= 0);
+ assert(s->state == SERVICE_START || s->state == SERVICE_START_POST);
+ assert(s->pid_file_pathspec);
+ assert(path_spec_owns_inotify_fd(s->pid_file_pathspec, fd));
+
+ log_debug("inotify event for %s", u->id);
+
+ if (path_spec_fd_event(s->pid_file_pathspec, events) < 0)
+ goto fail;
+
+ if (service_retry_pid_file(s) == 0)
+ return;
+
+ if (service_watch_pid_file(s) < 0)
+ goto fail;
+
+ return;
+fail:
+ service_unwatch_pid_file(s);
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
+}
+
+static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Service *s = SERVICE(u);
+ ServiceResult f;
+
+ assert(s);
+ assert(pid >= 0);
+
+ if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) :
+ is_clean_exit_lsb(code, status, &s->success_status))
+ f = SERVICE_SUCCESS;
+ else if (code == CLD_EXITED)
+ f = SERVICE_FAILURE_EXIT_CODE;
+ else if (code == CLD_KILLED)
+ f = SERVICE_FAILURE_SIGNAL;
+ else if (code == CLD_DUMPED)
+ f = SERVICE_FAILURE_CORE_DUMP;
+ else
+ assert_not_reached("Unknown code");
+
+ if (s->main_pid == pid) {
+ /* Forking services may occasionally move to a new PID.
+ * As long as they update the PID file before exiting the old
+ * PID, they're fine. */
+ if (service_load_pid_file(s, false) == 0)
+ return;
+
+ s->main_pid = 0;
+ exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status);
+
+ /* If this is not a forking service than the main
+ * process got started and hence we copy the exit
+ * status so that it is recorded both as main and as
+ * control process exit status */
+ if (s->main_command) {
+ s->main_command->exec_status = s->main_exec_status;
+
+ if (s->main_command->ignore)
+ f = SERVICE_SUCCESS;
+ }
+
+ log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE,
+ "MESSAGE=%s: main process exited, code=%s, status=%i/%s",
+ u->id, sigchld_code_to_string(code), status,
+ strna(code == CLD_EXITED
+ ? exit_status_to_string(status, EXIT_STATUS_FULL)
+ : signal_to_string(status)),
+ "UNIT=%s", u->id,
+ "EXIT_CODE=%s", sigchld_code_to_string(code),
+ "EXIT_STATUS=%i", status,
+ NULL);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ if (s->main_command &&
+ s->main_command->command_next &&
+ f == SERVICE_SUCCESS) {
+
+ /* There is another command to *
+ * execute, so let's do that. */
+
+ log_debug("%s running next main command for state %s", u->id, service_state_to_string(s->state));
+ service_run_next_main(s);
+
+ } else {
+
+ /* The service exited, so the service is officially
+ * gone. */
+ s->main_command = NULL;
+
+ switch (s->state) {
+
+ case SERVICE_START_POST:
+ case SERVICE_RELOAD:
+ case SERVICE_STOP:
+ /* Need to wait until the operation is
+ * done */
+ break;
+
+ case SERVICE_START:
+ if (s->type == SERVICE_ONESHOT) {
+ /* This was our main goal, so let's go on */
+ if (f == SERVICE_SUCCESS)
+ service_enter_start_post(s);
+ else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+ break;
+ }
+
+ /* Fall through */
+
+ case SERVICE_RUNNING:
+ service_enter_running(s, f);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+
+ if (!control_pid_good(s))
+ service_enter_stop_post(s, f);
+
+ /* If there is still a control process, wait for that first */
+ break;
+
+ default:
+ assert_not_reached("Uh, main process died at wrong time.");
+ }
+ }
+
+ } else if (s->control_pid == pid) {
+
+ s->control_pid = 0;
+
+ if (s->control_command) {
+ exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status);
+
+ if (s->control_command->ignore)
+ f = SERVICE_SUCCESS;
+ }
+
+ log_full(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE,
+ "%s: control process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status);
+
+ if (f != SERVICE_SUCCESS)
+ s->result = f;
+
+ /* Immediately get rid of the cgroup, so that the
+ * kernel doesn't delay the cgroup empty messages for
+ * the service cgroup any longer than necessary */
+ cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
+
+ if (s->control_command &&
+ s->control_command->command_next &&
+ f == SERVICE_SUCCESS) {
+
+ /* There is another command to *
+ * execute, so let's do that. */
+
+ log_debug("%s running next control command for state %s", u->id, service_state_to_string(s->state));
+ service_run_next_control(s);
+
+ } else {
+ /* No further commands for this step, so let's
+ * figure out what to do next */
+
+ s->control_command = NULL;
+ s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+
+ log_debug("%s got final SIGCHLD for state %s", u->id, service_state_to_string(s->state));
+
+ switch (s->state) {
+
+ case SERVICE_START_PRE:
+ if (f == SERVICE_SUCCESS)
+ service_enter_start(s);
+ else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+ break;
+
+ case SERVICE_START:
+ if (s->type != SERVICE_FORKING)
+ /* Maybe spurious event due to a reload that changed the type? */
+ break;
+
+ if (f != SERVICE_SUCCESS) {
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+ break;
+ }
+
+ if (s->pid_file) {
+ bool has_start_post;
+ int r;
+
+ /* Let's try to load the pid file here if we can.
+ * The PID file might actually be created by a START_POST
+ * script. In that case don't worry if the loading fails. */
+
+ has_start_post = !!s->exec_command[SERVICE_EXEC_START_POST];
+ r = service_load_pid_file(s, !has_start_post);
+ if (!has_start_post && r < 0) {
+ r = service_demand_pid_file(s);
+ if (r < 0 || !cgroup_good(s))
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+ break;
+ }
+ } else
+ service_search_main_pid(s);
+
+ service_enter_start_post(s);
+ break;
+
+ case SERVICE_START_POST:
+ if (f != SERVICE_SUCCESS) {
+ service_enter_stop(s, f);
+ break;
+ }
+
+ if (s->pid_file) {
+ int r;
+
+ r = service_load_pid_file(s, true);
+ if (r < 0) {
+ r = service_demand_pid_file(s);
+ if (r < 0 || !cgroup_good(s))
+ service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+ break;
+ }
+ } else
+ service_search_main_pid(s);
+
+ service_enter_running(s, SERVICE_SUCCESS);
+ break;
+
+ case SERVICE_RELOAD:
+ if (f == SERVICE_SUCCESS) {
+ service_load_pid_file(s, true);
+ service_search_main_pid(s);
+ }
+
+ s->reload_result = f;
+ service_enter_running(s, SERVICE_SUCCESS);
+ break;
+
+ case SERVICE_STOP:
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+ if (main_pid_good(s) <= 0)
+ service_enter_stop_post(s, f);
+
+ /* If there is still a service
+ * process around, wait until
+ * that one quit, too */
+ break;
+
+ case SERVICE_STOP_POST:
+ case SERVICE_FINAL_SIGTERM:
+ case SERVICE_FINAL_SIGKILL:
+ service_enter_dead(s, f, true);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+ }
+ }
+
+ /* Notify clients about changed exit status */
+ unit_add_to_dbus_queue(u);
+}
+
+static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+ assert(elapsed == 1);
+
+ if (w == &s->watchdog_watch) {
+ service_handle_watchdog(s);
+ return;
+ }
+
+ assert(w == &s->timer_watch);
+
+ switch (s->state) {
+
+ case SERVICE_START_PRE:
+ case SERVICE_START:
+ log_warning("%s operation timed out. Terminating.", u->id);
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT);
+ break;
+
+ case SERVICE_START_POST:
+ log_warning("%s operation timed out. Stopping.", u->id);
+ service_enter_stop(s, SERVICE_FAILURE_TIMEOUT);
+ break;
+
+ case SERVICE_RELOAD:
+ log_warning("%s operation timed out. Stopping.", u->id);
+ s->reload_result = SERVICE_FAILURE_TIMEOUT;
+ service_enter_running(s, SERVICE_SUCCESS);
+ break;
+
+ case SERVICE_STOP:
+ log_warning("%s stopping timed out. Terminating.", u->id);
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ if (s->kill_context.send_sigkill) {
+ log_warning("%s stopping timed out. Killing.", u->id);
+ service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s stopping timed out. Skipping SIGKILL.", u->id);
+ service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT);
+ }
+
+ break;
+
+ case SERVICE_STOP_SIGKILL:
+ /* Uh, we sent a SIGKILL and it is still not gone?
+ * Must be something we cannot kill, so let's just be
+ * weirded out and continue */
+
+ log_warning("%s still around after SIGKILL. Ignoring.", u->id);
+ service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT);
+ break;
+
+ case SERVICE_STOP_POST:
+ log_warning("%s stopping timed out (2). Terminating.", u->id);
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT);
+ break;
+
+ case SERVICE_FINAL_SIGTERM:
+ if (s->kill_context.send_sigkill) {
+ log_warning("%s stopping timed out (2). Killing.", u->id);
+ service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s stopping timed out (2). Skipping SIGKILL. Entering failed mode.", u->id);
+ service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false);
+ }
+
+ break;
+
+ case SERVICE_FINAL_SIGKILL:
+ log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id);
+ service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true);
+ break;
+
+ case SERVICE_AUTO_RESTART:
+ log_info("%s holdoff time over, scheduling restart.", u->id);
+ service_enter_restart(s);
+ break;
+
+ default:
+ assert_not_reached("Timeout at wrong time.");
+ }
+}
+
+static void service_cgroup_notify_event(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(u);
+
+ log_debug("%s: cgroup is empty", u->id);
+
+ switch (s->state) {
+
+ /* Waiting for SIGCHLD is usually more interesting,
+ * because it includes return codes/signals. Which is
+ * why we ignore the cgroup events for most cases,
+ * except when we don't know pid which to expect the
+ * SIGCHLD for. */
+
+ case SERVICE_START:
+ case SERVICE_START_POST:
+ /* If we were hoping for the daemon to write its PID file,
+ * we can give up now. */
+ if (s->pid_file_pathspec) {
+ log_warning("%s never wrote its PID file. Failing.", UNIT(s)->id);
+ service_unwatch_pid_file(s);
+ if (s->state == SERVICE_START)
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+ else
+ service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+ }
+ break;
+
+ case SERVICE_RUNNING:
+ /* service_enter_running() will figure out what to do */
+ service_enter_running(s, SERVICE_SUCCESS);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+
+ if (main_pid_good(s) <= 0 && !control_pid_good(s))
+ service_enter_stop_post(s, SERVICE_SUCCESS);
+
+ break;
+
+ case SERVICE_FINAL_SIGTERM:
+ case SERVICE_FINAL_SIGKILL:
+ if (main_pid_good(s) <= 0 && !control_pid_good(s))
+ service_enter_dead(s, SERVICE_SUCCESS, true);
+
+ break;
+
+ default:
+ ;
+ }
+}
+
+static void service_notify_message(Unit *u, pid_t pid, char **tags) {
+ Service *s = SERVICE(u);
+ const char *e;
+
+ assert(u);
+
+ if (s->notify_access == NOTIFY_NONE) {
+ log_warning("%s: Got notification message from PID %lu, but reception is disabled.",
+ u->id, (unsigned long) pid);
+ return;
+ }
+
+ if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) {
+ log_warning("%s: Got notification message from PID %lu, but reception only permitted for PID %lu",
+ u->id, (unsigned long) pid, (unsigned long) s->main_pid);
+ return;
+ }
+
+ log_debug("%s: Got message", u->id);
+
+ /* Interpret MAINPID= */
+ if ((e = strv_find_prefix(tags, "MAINPID=")) &&
+ (s->state == SERVICE_START ||
+ s->state == SERVICE_START_POST ||
+ s->state == SERVICE_RUNNING ||
+ s->state == SERVICE_RELOAD)) {
+
+ if (parse_pid(e + 8, &pid) < 0)
+ log_warning("Failed to parse notification message %s", e);
+ else {
+ log_debug("%s: got %s", u->id, e);
+ service_set_main_pid(s, pid);
+ }
+ }
+
+ /* Interpret READY= */
+ if (s->type == SERVICE_NOTIFY &&
+ s->state == SERVICE_START &&
+ strv_find(tags, "READY=1")) {
+ log_debug("%s: got READY=1", u->id);
+
+ service_enter_start_post(s);
+ }
+
+ /* Interpret STATUS= */
+ e = strv_find_prefix(tags, "STATUS=");
+ if (e) {
+ char *t;
+
+ if (e[7]) {
+
+ if (!utf8_is_valid(e+7)) {
+ log_warning("Status message in notification is not UTF-8 clean.");
+ return;
+ }
+
+ t = strdup(e+7);
+ if (!t) {
+ log_error("Failed to allocate string.");
+ return;
+ }
+
+ log_debug("%s: got %s", u->id, e);
+
+ free(s->status_text);
+ s->status_text = t;
+ } else {
+ free(s->status_text);
+ s->status_text = NULL;
+ }
+
+ }
+ if (strv_find(tags, "WATCHDOG=1")) {
+ log_debug("%s: got WATCHDOG=1", u->id);
+ service_reset_watchdog(s);
+ }
+
+ /* Notify clients about changed status or main pid */
+ unit_add_to_dbus_queue(u);
+}
+
+#ifdef HAVE_SYSV_COMPAT
+
+#ifdef TARGET_SUSE
+static void sysv_facility_in_insserv_conf(Manager *mgr) {
+ FILE *f=NULL;
+ int r;
+
+ if (!(f = fopen("/etc/insserv.conf", "re"))) {
+ r = errno == ENOENT ? 0 : -errno;
+ goto finish;
+ }
+
+ while (!feof(f)) {
+ char l[LINE_MAX], *t;
+ char **parsed = NULL;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ r = -errno;
+ log_error("Failed to read configuration file '/etc/insserv.conf': %s", strerror(-r));
+ goto finish;
+ }
+
+ t = strstrip(l);
+ if (*t != '$' && *t != '<')
+ continue;
+
+ parsed = strv_split(t,WHITESPACE);
+ /* we ignore <interactive>, not used, equivalent to X-Interactive */
+ if (parsed && !startswith_no_case (parsed[0], "<interactive>")) {
+ char *facility;
+ Unit *u;
+ if (sysv_translate_facility(parsed[0], NULL, &facility) < 0)
+ continue;
+ if ((u = manager_get_unit(mgr, facility)) && (u->type == UNIT_TARGET)) {
+ UnitDependency e;
+ char *dep = NULL, *name, **j;
+
+ STRV_FOREACH (j, parsed+1) {
+ if (*j[0]=='+') {
+ e = UNIT_WANTS;
+ name = *j+1;
+ }
+ else {
+ e = UNIT_REQUIRES;
+ name = *j;
+ }
+ if (sysv_translate_facility(name, NULL, &dep) < 0)
+ continue;
+
+ r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, e, dep, NULL, true);
+ free(dep);
+ }
+ }
+ free(facility);
+ }
+ strv_free(parsed);
+ }
+finish:
+ if (f)
+ fclose(f);
+
+}
+#endif
+
+static int service_enumerate(Manager *m) {
+ char **p;
+ unsigned i;
+ DIR *d = NULL;
+ char *path = NULL, *fpath = NULL, *name = NULL;
+ Set *runlevel_services[ELEMENTSOF(rcnd_table)], *shutdown_services = NULL;
+ Unit *service;
+ Iterator j;
+ int r;
+
+ assert(m);
+
+ if (m->running_as != SYSTEMD_SYSTEM)
+ return 0;
+
+ zero(runlevel_services);
+
+ STRV_FOREACH(p, m->lookup_paths.sysvrcnd_path)
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
+ struct dirent *de;
+
+ free(path);
+ path = strjoin(*p, "/", rcnd_table[i].path, NULL);
+ if (!path) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (d)
+ closedir(d);
+
+ if (!(d = opendir(path))) {
+ if (errno != ENOENT)
+ log_warning("opendir() failed on %s: %s", path, strerror(errno));
+
+ continue;
+ }
+
+ while ((de = readdir(d))) {
+ int a, b;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
+ continue;
+
+ if (strlen(de->d_name) < 4)
+ continue;
+
+ a = undecchar(de->d_name[1]);
+ b = undecchar(de->d_name[2]);
+
+ if (a < 0 || b < 0)
+ continue;
+
+ free(fpath);
+ fpath = strjoin(path, "/", de->d_name, NULL);
+ if (!fpath) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (access(fpath, X_OK) < 0) {
+
+ if (errno != ENOENT)
+ log_warning("access() failed on %s: %s", fpath, strerror(errno));
+
+ continue;
+ }
+
+ free(name);
+ if (!(name = sysv_translate_name(de->d_name + 3))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((r = manager_load_unit_prepare(m, name, NULL, NULL, &service)) < 0) {
+ log_warning("Failed to prepare unit %s: %s", name, strerror(-r));
+ continue;
+ }
+
+ if (de->d_name[0] == 'S') {
+
+ if (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_SYSINIT) {
+ SERVICE(service)->sysv_start_priority_from_rcnd =
+ MAX(a*10 + b, SERVICE(service)->sysv_start_priority_from_rcnd);
+
+ SERVICE(service)->sysv_enabled = true;
+ }
+
+ if ((r = set_ensure_allocated(&runlevel_services[i], trivial_hash_func, trivial_compare_func)) < 0)
+ goto finish;
+
+ if ((r = set_put(runlevel_services[i], service)) < 0)
+ goto finish;
+
+ } else if (de->d_name[0] == 'K' &&
+ (rcnd_table[i].type == RUNLEVEL_DOWN ||
+ rcnd_table[i].type == RUNLEVEL_SYSINIT)) {
+
+ if ((r = set_ensure_allocated(&shutdown_services, trivial_hash_func, trivial_compare_func)) < 0)
+ goto finish;
+
+ if ((r = set_put(shutdown_services, service)) < 0)
+ goto finish;
+ }
+ }
+ }
+
+ /* Now we loaded all stubs and are aware of the lowest
+ start-up priority for all services, not let's actually load
+ the services, this will also tell us which services are
+ actually native now */
+ manager_dispatch_load_queue(m);
+
+ /* If this is a native service, rely on native ways to pull in
+ * a service, don't pull it in via sysv rcN.d links. */
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
+ SET_FOREACH(service, runlevel_services[i], j) {
+ service = unit_follow_merge(service);
+
+ if (service->fragment_path)
+ continue;
+
+ if ((r = unit_add_two_dependencies_by_name_inverse(service, UNIT_AFTER, UNIT_WANTS, rcnd_table[i].target, NULL, true)) < 0)
+ goto finish;
+ }
+
+ /* We honour K links only for halt/reboot. For the normal
+ * runlevels we assume the stop jobs will be implicitly added
+ * by the core logic. Also, we don't really distinguish here
+ * between the runlevels 0 and 6 and just add them to the
+ * special shutdown target. On SUSE the boot.d/ runlevel is
+ * also used for shutdown, so we add links for that too to the
+ * shutdown target.*/
+ SET_FOREACH(service, shutdown_services, j) {
+ service = unit_follow_merge(service);
+
+ if (service->fragment_path)
+ continue;
+
+ if ((r = unit_add_two_dependencies_by_name(service, UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true)) < 0)
+ goto finish;
+ }
+
+ r = 0;
+
+#ifdef TARGET_SUSE
+ sysv_facility_in_insserv_conf (m);
+#endif
+
+finish:
+ free(path);
+ free(fpath);
+ free(name);
+
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
+ set_free(runlevel_services[i]);
+ set_free(shutdown_services);
+
+ if (d)
+ closedir(d);
+
+ return r;
+}
+#endif
+
+static void service_bus_name_owner_change(
+ Unit *u,
+ const char *name,
+ const char *old_owner,
+ const char *new_owner) {
+
+ Service *s = SERVICE(u);
+
+ assert(s);
+ assert(name);
+
+ assert(streq(s->bus_name, name));
+ assert(old_owner || new_owner);
+
+ if (old_owner && new_owner)
+ log_debug("%s's D-Bus name %s changed owner from %s to %s", u->id, name, old_owner, new_owner);
+ else if (old_owner)
+ log_debug("%s's D-Bus name %s no longer registered by %s", u->id, name, old_owner);
+ else
+ log_debug("%s's D-Bus name %s now registered by %s", u->id, name, new_owner);
+
+ s->bus_name_good = !!new_owner;
+
+ if (s->type == SERVICE_DBUS) {
+
+ /* service_enter_running() will figure out what to
+ * do */
+ if (s->state == SERVICE_RUNNING)
+ service_enter_running(s, SERVICE_SUCCESS);
+ else if (s->state == SERVICE_START && new_owner)
+ service_enter_start_post(s);
+
+ } else if (new_owner &&
+ s->main_pid <= 0 &&
+ (s->state == SERVICE_START ||
+ s->state == SERVICE_START_POST ||
+ s->state == SERVICE_RUNNING ||
+ s->state == SERVICE_RELOAD)) {
+
+ /* Try to acquire PID from bus service */
+ log_debug("Trying to acquire PID from D-Bus name...");
+
+ bus_query_pid(u->manager, name);
+ }
+}
+
+static void service_bus_query_pid_done(
+ Unit *u,
+ const char *name,
+ pid_t pid) {
+
+ Service *s = SERVICE(u);
+
+ assert(s);
+ assert(name);
+
+ log_debug("%s's D-Bus name %s is now owned by process %u", u->id, name, (unsigned) pid);
+
+ if (s->main_pid <= 0 &&
+ (s->state == SERVICE_START ||
+ s->state == SERVICE_START_POST ||
+ s->state == SERVICE_RUNNING ||
+ s->state == SERVICE_RELOAD))
+ service_set_main_pid(s, pid);
+}
+
+int service_set_socket_fd(Service *s, int fd, Socket *sock) {
+
+ assert(s);
+ assert(fd >= 0);
+
+ /* This is called by the socket code when instantiating a new
+ * service for a stream socket and the socket needs to be
+ * configured. */
+
+ if (UNIT(s)->load_state != UNIT_LOADED)
+ return -EINVAL;
+
+ if (s->socket_fd >= 0)
+ return -EBUSY;
+
+ if (s->state != SERVICE_DEAD)
+ return -EAGAIN;
+
+ s->socket_fd = fd;
+ s->got_socket_fd = true;
+
+ unit_ref_set(&s->accept_socket, UNIT(sock));
+
+ return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false);
+}
+
+static void service_reset_failed(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ if (s->state == SERVICE_FAILED)
+ service_set_state(s, SERVICE_DEAD);
+
+ s->result = SERVICE_SUCCESS;
+ s->reload_result = SERVICE_SUCCESS;
+
+ RATELIMIT_RESET(s->start_limit);
+}
+
+static int service_kill(Unit *u, KillWho who, int signo, DBusError *error) {
+ Service *s = SERVICE(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (s->main_pid <= 0 && who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill");
+ return -ESRCH;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ESRCH;
+ }
+
+ if (who == KILL_CONTROL || who == KILL_ALL)
+ if (s->control_pid > 0)
+ if (kill(s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (who == KILL_MAIN || who == KILL_ALL)
+ if (s->main_pid > 0)
+ if (kill(s->main_pid, signo) < 0)
+ r = -errno;
+
+ if (who == KILL_ALL) {
+ int q;
+
+ pid_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!pid_set)
+ return -ENOMEM;
+
+ /* Exclude the control/main pid from being killed via the cgroup */
+ if (s->control_pid > 0) {
+ q = set_put(pid_set, LONG_TO_PTR(s->control_pid));
+ if (q < 0) {
+ r = q;
+ goto finish;
+ }
+ }
+
+ if (s->main_pid > 0) {
+ q = set_put(pid_set, LONG_TO_PTR(s->main_pid));
+ if (q < 0) {
+ r = q;
+ goto finish;
+ }
+ }
+
+ q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, false, pid_set, NULL);
+ if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
+static const char* const service_state_table[_SERVICE_STATE_MAX] = {
+ [SERVICE_DEAD] = "dead",
+ [SERVICE_START_PRE] = "start-pre",
+ [SERVICE_START] = "start",
+ [SERVICE_START_POST] = "start-post",
+ [SERVICE_RUNNING] = "running",
+ [SERVICE_EXITED] = "exited",
+ [SERVICE_RELOAD] = "reload",
+ [SERVICE_STOP] = "stop",
+ [SERVICE_STOP_SIGTERM] = "stop-sigterm",
+ [SERVICE_STOP_SIGKILL] = "stop-sigkill",
+ [SERVICE_STOP_POST] = "stop-post",
+ [SERVICE_FINAL_SIGTERM] = "final-sigterm",
+ [SERVICE_FINAL_SIGKILL] = "final-sigkill",
+ [SERVICE_FAILED] = "failed",
+ [SERVICE_AUTO_RESTART] = "auto-restart",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
+
+static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
+ [SERVICE_RESTART_NO] = "no",
+ [SERVICE_RESTART_ON_SUCCESS] = "on-success",
+ [SERVICE_RESTART_ON_FAILURE] = "on-failure",
+ [SERVICE_RESTART_ON_ABORT] = "on-abort",
+ [SERVICE_RESTART_ALWAYS] = "always"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart);
+
+static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
+ [SERVICE_SIMPLE] = "simple",
+ [SERVICE_FORKING] = "forking",
+ [SERVICE_ONESHOT] = "oneshot",
+ [SERVICE_DBUS] = "dbus",
+ [SERVICE_NOTIFY] = "notify",
+ [SERVICE_IDLE] = "idle"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
+
+static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = {
+ [SERVICE_EXEC_START_PRE] = "ExecStartPre",
+ [SERVICE_EXEC_START] = "ExecStart",
+ [SERVICE_EXEC_START_POST] = "ExecStartPost",
+ [SERVICE_EXEC_RELOAD] = "ExecReload",
+ [SERVICE_EXEC_STOP] = "ExecStop",
+ [SERVICE_EXEC_STOP_POST] = "ExecStopPost",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand);
+
+static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = {
+ [NOTIFY_NONE] = "none",
+ [NOTIFY_MAIN] = "main",
+ [NOTIFY_ALL] = "all"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess);
+
+static const char* const service_result_table[_SERVICE_RESULT_MAX] = {
+ [SERVICE_SUCCESS] = "success",
+ [SERVICE_FAILURE_RESOURCES] = "resources",
+ [SERVICE_FAILURE_TIMEOUT] = "timeout",
+ [SERVICE_FAILURE_EXIT_CODE] = "exit-code",
+ [SERVICE_FAILURE_SIGNAL] = "signal",
+ [SERVICE_FAILURE_CORE_DUMP] = "core-dump",
+ [SERVICE_FAILURE_WATCHDOG] = "watchdog",
+ [SERVICE_FAILURE_START_LIMIT] = "start-limit"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
+
+static const char* const start_limit_action_table[_SERVICE_START_LIMIT_MAX] = {
+ [SERVICE_START_LIMIT_NONE] = "none",
+ [SERVICE_START_LIMIT_REBOOT] = "reboot",
+ [SERVICE_START_LIMIT_REBOOT_FORCE] = "reboot-force",
+ [SERVICE_START_LIMIT_REBOOT_IMMEDIATE] = "reboot-immediate"
+};
+DEFINE_STRING_TABLE_LOOKUP(start_limit_action, StartLimitAction);
+
+const UnitVTable service_vtable = {
+ .object_size = sizeof(Service),
+ .exec_context_offset = offsetof(Service, exec_context),
+
+ .sections =
+ "Unit\0"
+ "Service\0"
+ "Install\0",
+
+ .init = service_init,
+ .done = service_done,
+ .load = service_load,
+
+ .coldplug = service_coldplug,
+
+ .dump = service_dump,
+
+ .start = service_start,
+ .stop = service_stop,
+ .reload = service_reload,
+
+ .can_reload = service_can_reload,
+
+ .kill = service_kill,
+
+ .serialize = service_serialize,
+ .deserialize_item = service_deserialize_item,
+
+ .active_state = service_active_state,
+ .sub_state_to_string = service_sub_state_to_string,
+
+ .check_gc = service_check_gc,
+ .check_snapshot = service_check_snapshot,
+
+ .sigchld_event = service_sigchld_event,
+ .timer_event = service_timer_event,
+ .fd_event = service_fd_event,
+
+ .reset_failed = service_reset_failed,
+
+ .cgroup_notify_empty = service_cgroup_notify_event,
+ .notify_message = service_notify_message,
+
+ .bus_name_owner_change = service_bus_name_owner_change,
+ .bus_query_pid_done = service_bus_query_pid_done,
+
+ .bus_interface = "org.freedesktop.systemd1.Service",
+ .bus_message_handler = bus_service_message_handler,
+ .bus_invalidating_properties = bus_service_invalidating_properties,
+
+#ifdef HAVE_SYSV_COMPAT
+ .enumerate = service_enumerate,
+#endif
+ .status_message_formats = {
+ .starting_stopping = {
+ [0] = "Starting %s...",
+ [1] = "Stopping %s...",
+ },
+ .finished_start_job = {
+ [JOB_DONE] = "Started %s.",
+ [JOB_FAILED] = "Failed to start %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ [JOB_TIMEOUT] = "Timed out starting %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Stopped %s.",
+ [JOB_FAILED] = "Stopped (with error) %s.",
+ [JOB_TIMEOUT] = "Timed out stopping %s.",
+ },
+ },
+};
diff --git a/src/core/service.h b/src/core/service.h
new file mode 100644
index 0000000000..d1e53bf727
--- /dev/null
+++ b/src/core/service.h
@@ -0,0 +1,225 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Service Service;
+
+#include "unit.h"
+#include "path.h"
+#include "ratelimit.h"
+#include "service.h"
+#include "kill.h"
+#include "exit-status.h"
+
+typedef enum ServiceState {
+ SERVICE_DEAD,
+ SERVICE_START_PRE,
+ SERVICE_START,
+ SERVICE_START_POST,
+ SERVICE_RUNNING,
+ SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
+ SERVICE_RELOAD,
+ SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */
+ SERVICE_STOP_SIGTERM,
+ SERVICE_STOP_SIGKILL,
+ SERVICE_STOP_POST,
+ SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
+ SERVICE_FINAL_SIGKILL,
+ SERVICE_FAILED,
+ SERVICE_AUTO_RESTART,
+ _SERVICE_STATE_MAX,
+ _SERVICE_STATE_INVALID = -1
+} ServiceState;
+
+typedef enum ServiceRestart {
+ SERVICE_RESTART_NO,
+ SERVICE_RESTART_ON_SUCCESS,
+ SERVICE_RESTART_ON_FAILURE,
+ SERVICE_RESTART_ON_ABORT,
+ SERVICE_RESTART_ALWAYS,
+ _SERVICE_RESTART_MAX,
+ _SERVICE_RESTART_INVALID = -1
+} ServiceRestart;
+
+typedef enum ServiceType {
+ SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */
+ SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */
+ SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */
+ SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */
+ SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */
+ SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */
+ _SERVICE_TYPE_MAX,
+ _SERVICE_TYPE_INVALID = -1
+} ServiceType;
+
+typedef enum ServiceExecCommand {
+ SERVICE_EXEC_START_PRE,
+ SERVICE_EXEC_START,
+ SERVICE_EXEC_START_POST,
+ SERVICE_EXEC_RELOAD,
+ SERVICE_EXEC_STOP,
+ SERVICE_EXEC_STOP_POST,
+ _SERVICE_EXEC_COMMAND_MAX,
+ _SERVICE_EXEC_COMMAND_INVALID = -1
+} ServiceExecCommand;
+
+typedef enum NotifyAccess {
+ NOTIFY_NONE,
+ NOTIFY_ALL,
+ NOTIFY_MAIN,
+ _NOTIFY_ACCESS_MAX,
+ _NOTIFY_ACCESS_INVALID = -1
+} NotifyAccess;
+
+typedef enum ServiceResult {
+ SERVICE_SUCCESS,
+ SERVICE_FAILURE_RESOURCES,
+ SERVICE_FAILURE_TIMEOUT,
+ SERVICE_FAILURE_EXIT_CODE,
+ SERVICE_FAILURE_SIGNAL,
+ SERVICE_FAILURE_CORE_DUMP,
+ SERVICE_FAILURE_WATCHDOG,
+ SERVICE_FAILURE_START_LIMIT,
+ _SERVICE_RESULT_MAX,
+ _SERVICE_RESULT_INVALID = -1
+} ServiceResult;
+
+typedef enum StartLimitAction {
+ SERVICE_START_LIMIT_NONE,
+ SERVICE_START_LIMIT_REBOOT,
+ SERVICE_START_LIMIT_REBOOT_FORCE,
+ SERVICE_START_LIMIT_REBOOT_IMMEDIATE,
+ _SERVICE_START_LIMIT_MAX,
+ _SERVICE_START_LIMIT_INVALID = -1
+} StartLimitAction;
+
+struct Service {
+ Unit meta;
+
+ ServiceType type;
+ ServiceRestart restart;
+ ExitStatusSet restart_ignore_status;
+ ExitStatusSet success_status;
+
+ /* If set we'll read the main daemon PID from this file */
+ char *pid_file;
+
+ usec_t restart_usec;
+ usec_t timeout_start_usec;
+ usec_t timeout_stop_usec;
+
+ dual_timestamp watchdog_timestamp;
+ usec_t watchdog_usec;
+ Watch watchdog_watch;
+
+ ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX];
+
+ ExecContext exec_context;
+ KillContext kill_context;
+
+ ServiceState state, deserialized_state;
+
+ /* The exit status of the real main process */
+ ExecStatus main_exec_status;
+
+ /* The currently executed control process */
+ ExecCommand *control_command;
+
+ /* The currently executed main process, which may be NULL if
+ * the main process got started via forking mode and not by
+ * us */
+ ExecCommand *main_command;
+
+ /* The ID of the control command currently being executed */
+ ServiceExecCommand control_command_id;
+
+ pid_t main_pid, control_pid;
+ int socket_fd;
+
+ int fsck_passno;
+
+ bool permissions_start_only;
+ bool root_directory_start_only;
+ bool remain_after_exit;
+ bool guess_main_pid;
+
+ /* If we shut down, remember why */
+ ServiceResult result;
+ ServiceResult reload_result;
+
+ bool main_pid_known:1;
+ bool main_pid_alien:1;
+ bool bus_name_good:1;
+ bool forbid_restart:1;
+ bool got_socket_fd:1;
+ bool start_timeout_defined:1;
+#ifdef HAVE_SYSV_COMPAT
+ bool is_sysv:1;
+ bool sysv_has_lsb:1;
+ bool sysv_enabled:1;
+ int sysv_start_priority_from_rcnd;
+ int sysv_start_priority;
+
+ char *sysv_runlevels;
+#endif
+
+ char *bus_name;
+
+ char *status_text;
+
+ RateLimit start_limit;
+ StartLimitAction start_limit_action;
+
+ UnitRef accept_socket;
+
+ Watch timer_watch;
+ PathSpec *pid_file_pathspec;
+
+ NotifyAccess notify_access;
+};
+
+extern const UnitVTable service_vtable;
+
+struct Socket;
+
+int service_set_socket_fd(Service *s, int fd, struct Socket *socket);
+
+const char* service_state_to_string(ServiceState i);
+ServiceState service_state_from_string(const char *s);
+
+const char* service_restart_to_string(ServiceRestart i);
+ServiceRestart service_restart_from_string(const char *s);
+
+const char* service_type_to_string(ServiceType i);
+ServiceType service_type_from_string(const char *s);
+
+const char* service_exec_command_to_string(ServiceExecCommand i);
+ServiceExecCommand service_exec_command_from_string(const char *s);
+
+const char* notify_access_to_string(NotifyAccess i);
+NotifyAccess notify_access_from_string(const char *s);
+
+const char* service_result_to_string(ServiceResult i);
+ServiceResult service_result_from_string(const char *s);
+
+const char* start_limit_action_to_string(StartLimitAction i);
+StartLimitAction start_limit_action_from_string(const char *s);
diff --git a/src/core/shutdown.c b/src/core/shutdown.c
new file mode 100644
index 0000000000..cc8c57bd2d
--- /dev/null
+++ b/src/core/shutdown.c
@@ -0,0 +1,316 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <linux/reboot.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "missing.h"
+#include "log.h"
+#include "umount.h"
+#include "util.h"
+#include "mkdir.h"
+#include "virt.h"
+#include "watchdog.h"
+#include "killall.h"
+
+#define FINALIZE_ATTEMPTS 50
+
+static int prepare_new_root(void) {
+ static const char dirs[] =
+ "/run/initramfs/oldroot\0"
+ "/run/initramfs/proc\0"
+ "/run/initramfs/sys\0"
+ "/run/initramfs/dev\0"
+ "/run/initramfs/run\0";
+
+ const char *dir;
+
+ if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) {
+ log_error("Failed to mount bind /run/initramfs on /run/initramfs: %m");
+ return -errno;
+ }
+
+ if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) {
+ log_error("Failed to make /run/initramfs private mount: %m");
+ return -errno;
+ }
+
+ NULSTR_FOREACH(dir, dirs)
+ if (mkdir_p_label(dir, 0755) < 0 && errno != EEXIST) {
+ log_error("Failed to mkdir %s: %m", dir);
+ return -errno;
+ }
+
+ if (mount("/sys", "/run/initramfs/sys", NULL, MS_BIND, NULL) < 0) {
+ log_error("Failed to mount bind /sys on /run/initramfs/sys: %m");
+ return -errno;
+ }
+
+ if (mount("/proc", "/run/initramfs/proc", NULL, MS_BIND, NULL) < 0) {
+ log_error("Failed to mount bind /proc on /run/initramfs/proc: %m");
+ return -errno;
+ }
+
+ if (mount("/dev", "/run/initramfs/dev", NULL, MS_BIND, NULL) < 0) {
+ log_error("Failed to mount bind /dev on /run/initramfs/dev: %m");
+ return -errno;
+ }
+
+ if (mount("/run", "/run/initramfs/run", NULL, MS_BIND, NULL) < 0) {
+ log_error("Failed to mount bind /run on /run/initramfs/run: %m");
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int pivot_to_new_root(void) {
+
+ if (chdir("/run/initramfs") < 0) {
+ log_error("Failed to change directory to /run/initramfs: %m");
+ return -errno;
+ }
+
+ /* Work-around for a kernel bug: for some reason the kernel
+ * refuses switching root if any file systems are mounted
+ * MS_SHARED. Hence remount them MS_PRIVATE here as a
+ * work-around.
+ *
+ * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
+ if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+ log_warning("Failed to make \"/\" private mount: %m");
+
+ if (pivot_root(".", "oldroot") < 0) {
+ log_error("pivot failed: %m");
+ /* only chroot if pivot root succeeded */
+ return -errno;
+ }
+
+ chroot(".");
+
+ setsid();
+ make_console_stdio();
+
+ log_info("Successfully changed into root pivot.");
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ int cmd, r;
+ unsigned retries;
+ bool need_umount = true, need_swapoff = true, need_loop_detach = true, need_dm_detach = true;
+ bool in_container, use_watchdog = false;
+ char *arguments[3];
+
+ log_parse_environment();
+ log_set_target(LOG_TARGET_CONSOLE); /* syslog will die if not gone yet */
+ log_open();
+
+ umask(0022);
+
+ if (getpid() != 1) {
+ log_error("Not executed by init (pid 1).");
+ r = -EPERM;
+ goto error;
+ }
+
+ if (argc != 2) {
+ log_error("Invalid number of arguments.");
+ r = -EINVAL;
+ goto error;
+ }
+
+ in_container = detect_container(NULL) > 0;
+
+ if (streq(argv[1], "reboot"))
+ cmd = RB_AUTOBOOT;
+ else if (streq(argv[1], "poweroff"))
+ cmd = RB_POWER_OFF;
+ else if (streq(argv[1], "halt"))
+ cmd = RB_HALT_SYSTEM;
+ else if (streq(argv[1], "kexec"))
+ cmd = LINUX_REBOOT_CMD_KEXEC;
+ else {
+ log_error("Unknown action '%s'.", argv[1]);
+ r = -EINVAL;
+ goto error;
+ }
+
+ use_watchdog = !!getenv("WATCHDOG_USEC");
+
+ /* lock us into memory */
+ mlockall(MCL_CURRENT|MCL_FUTURE);
+
+ log_info("Sending SIGTERM to remaining processes...");
+ broadcast_signal(SIGTERM, true);
+
+ log_info("Sending SIGKILL to remaining processes...");
+ broadcast_signal(SIGKILL, true);
+
+ if (in_container) {
+ need_swapoff = false;
+ need_dm_detach = false;
+ need_loop_detach = false;
+ }
+
+ /* Unmount all mountpoints, swaps, and loopback devices */
+ for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) {
+ bool changed = false;
+
+ if (use_watchdog)
+ watchdog_ping();
+
+ if (need_umount) {
+ log_info("Unmounting file systems.");
+ r = umount_all(&changed);
+ if (r == 0)
+ need_umount = false;
+ else if (r > 0)
+ log_info("Not all file systems unmounted, %d left.", r);
+ else
+ log_error("Failed to unmount file systems: %s", strerror(-r));
+ }
+
+ if (need_swapoff) {
+ log_info("Disabling swaps.");
+ r = swapoff_all(&changed);
+ if (r == 0)
+ need_swapoff = false;
+ else if (r > 0)
+ log_info("Not all swaps are turned off, %d left.", r);
+ else
+ log_error("Failed to turn off swaps: %s", strerror(-r));
+ }
+
+ if (need_loop_detach) {
+ log_info("Detaching loop devices.");
+ r = loopback_detach_all(&changed);
+ if (r == 0)
+ need_loop_detach = false;
+ else if (r > 0)
+ log_info("Not all loop devices detached, %d left.", r);
+ else
+ log_error("Failed to detach loop devices: %s", strerror(-r));
+ }
+
+ if (need_dm_detach) {
+ log_info("Detaching DM devices.");
+ r = dm_detach_all(&changed);
+ if (r == 0)
+ need_dm_detach = false;
+ else if (r > 0)
+ log_warning("Not all DM devices detached, %d left.", r);
+ else
+ log_error("Failed to detach DM devices: %s", strerror(-r));
+ }
+
+ if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) {
+ if (retries > 0)
+ log_info("All filesystems, swaps, loop devices, DM devices detached.");
+ /* Yay, done */
+ break;
+ }
+
+ /* If in this iteration we didn't manage to
+ * unmount/deactivate anything, we simply give up */
+ if (!changed) {
+ log_error("Cannot finalize remaining file systems and devices, giving up.");
+ break;
+ }
+
+ log_debug("Couldn't finalize remaining file systems and devices after %u retries, trying again.", retries+1);
+ }
+
+ if (retries >= FINALIZE_ATTEMPTS)
+ log_error("Too many iterations, giving up.");
+
+ arguments[0] = NULL;
+ arguments[1] = argv[1];
+ arguments[2] = NULL;
+ execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, arguments);
+
+ if (!in_container &&
+ access("/run/initramfs/shutdown", X_OK) == 0) {
+
+ if (prepare_new_root() >= 0 &&
+ pivot_to_new_root() >= 0) {
+ execv("/shutdown", argv);
+ log_error("Failed to execute shutdown binary: %m");
+ }
+ }
+
+ if (cmd == LINUX_REBOOT_CMD_KEXEC) {
+
+ if (!in_container) {
+ /* We cheat and exec kexec to avoid doing all its work */
+ pid_t pid = fork();
+
+ if (pid < 0)
+ log_error("Could not fork: %m. Falling back to normal reboot.");
+ else if (pid > 0) {
+ wait_for_terminate_and_warn("kexec", pid);
+ log_warning("kexec failed. Falling back to normal reboot.");
+ } else {
+ /* Child */
+ const char *args[3] = { "/sbin/kexec", "-e", NULL };
+ execv(args[0], (char * const *) args);
+ return EXIT_FAILURE;
+ }
+ }
+
+ cmd = RB_AUTOBOOT;
+ }
+
+ reboot(cmd);
+
+ if (errno == EPERM && in_container) {
+ /* If we are in a container, and we lacked
+ * CAP_SYS_BOOT just exit, this will kill our
+ * container for good. */
+ log_error("Exiting container.");
+ exit(0);
+ }
+
+ log_error("Failed to invoke reboot(): %m");
+ r = -errno;
+
+ error:
+ log_error("Critical error while doing system shutdown: %s", strerror(-r));
+
+ freeze();
+ return EXIT_FAILURE;
+}
diff --git a/src/core/snapshot.c b/src/core/snapshot.c
new file mode 100644
index 0000000000..5c2a319cb6
--- /dev/null
+++ b/src/core/snapshot.c
@@ -0,0 +1,308 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "unit.h"
+#include "snapshot.h"
+#include "unit-name.h"
+#include "dbus-snapshot.h"
+#include "bus-errors.h"
+
+static const UnitActiveState state_translation_table[_SNAPSHOT_STATE_MAX] = {
+ [SNAPSHOT_DEAD] = UNIT_INACTIVE,
+ [SNAPSHOT_ACTIVE] = UNIT_ACTIVE
+};
+
+static void snapshot_init(Unit *u) {
+ Snapshot *s = SNAPSHOT(u);
+
+ assert(s);
+ assert(UNIT(s)->load_state == UNIT_STUB);
+
+ UNIT(s)->ignore_on_isolate = true;
+ UNIT(s)->ignore_on_snapshot = true;
+}
+
+static void snapshot_set_state(Snapshot *s, SnapshotState state) {
+ SnapshotState old_state;
+ assert(s);
+
+ old_state = s->state;
+ s->state = state;
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(s)->id,
+ snapshot_state_to_string(old_state),
+ snapshot_state_to_string(state));
+
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static int snapshot_load(Unit *u) {
+ Snapshot *s = SNAPSHOT(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ /* Make sure that only snapshots created via snapshot_create()
+ * can be loaded */
+ if (!s->by_snapshot_create && UNIT(s)->manager->n_reloading <= 0)
+ return -ENOENT;
+
+ u->load_state = UNIT_LOADED;
+ return 0;
+}
+
+static int snapshot_coldplug(Unit *u) {
+ Snapshot *s = SNAPSHOT(u);
+
+ assert(s);
+ assert(s->state == SNAPSHOT_DEAD);
+
+ if (s->deserialized_state != s->state)
+ snapshot_set_state(s, s->deserialized_state);
+
+ return 0;
+}
+
+static void snapshot_dump(Unit *u, FILE *f, const char *prefix) {
+ Snapshot *s = SNAPSHOT(u);
+
+ assert(s);
+ assert(f);
+
+ fprintf(f,
+ "%sSnapshot State: %s\n"
+ "%sClean Up: %s\n",
+ prefix, snapshot_state_to_string(s->state),
+ prefix, yes_no(s->cleanup));
+}
+
+static int snapshot_start(Unit *u) {
+ Snapshot *s = SNAPSHOT(u);
+
+ assert(s);
+ assert(s->state == SNAPSHOT_DEAD);
+
+ snapshot_set_state(s, SNAPSHOT_ACTIVE);
+
+ if (s->cleanup)
+ unit_add_to_cleanup_queue(u);
+
+ return 0;
+}
+
+static int snapshot_stop(Unit *u) {
+ Snapshot *s = SNAPSHOT(u);
+
+ assert(s);
+ assert(s->state == SNAPSHOT_ACTIVE);
+
+ snapshot_set_state(s, SNAPSHOT_DEAD);
+ return 0;
+}
+
+static int snapshot_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Snapshot *s = SNAPSHOT(u);
+ Unit *other;
+ Iterator i;
+
+ assert(s);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", snapshot_state_to_string(s->state));
+ unit_serialize_item(u, f, "cleanup", yes_no(s->cleanup));
+ SET_FOREACH(other, u->dependencies[UNIT_WANTS], i)
+ unit_serialize_item(u, f, "wants", other->id);
+
+ return 0;
+}
+
+static int snapshot_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Snapshot *s = SNAPSHOT(u);
+ int r;
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ SnapshotState state;
+
+ if ((state = snapshot_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ s->deserialized_state = state;
+
+ } else if (streq(key, "cleanup")) {
+
+ if ((r = parse_boolean(value)) < 0)
+ log_debug("Failed to parse cleanup value %s", value);
+ else
+ s->cleanup = r;
+
+ } else if (streq(key, "wants")) {
+
+ if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, value, NULL, true)) < 0)
+ return r;
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState snapshot_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[SNAPSHOT(u)->state];
+}
+
+static const char *snapshot_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return snapshot_state_to_string(SNAPSHOT(u)->state);
+}
+
+int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **_s) {
+ Iterator i;
+ Unit *other, *u = NULL;
+ char *n = NULL;
+ int r;
+ const char *k;
+
+ assert(m);
+ assert(_s);
+
+ if (name) {
+ if (!unit_name_is_valid(name, false)) {
+ dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name);
+ return -EINVAL;
+ }
+
+ if (unit_name_to_type(name) != UNIT_SNAPSHOT) {
+ dbus_set_error(e, BUS_ERROR_UNIT_TYPE_MISMATCH, "Unit name %s lacks snapshot suffix.", name);
+ return -EINVAL;
+ }
+
+ if (manager_get_unit(m, name)) {
+ dbus_set_error(e, BUS_ERROR_UNIT_EXISTS, "Snapshot %s exists already.", name);
+ return -EEXIST;
+ }
+
+ } else {
+
+ for (;;) {
+ if (asprintf(&n, "snapshot-%u.snapshot", ++ m->n_snapshots) < 0)
+ return -ENOMEM;
+
+ if (!manager_get_unit(m, n))
+ break;
+
+ free(n);
+ }
+
+ name = n;
+ }
+
+ r = manager_load_unit_prepare(m, name, NULL, e, &u);
+ free(n);
+
+ if (r < 0)
+ goto fail;
+
+ SNAPSHOT(u)->by_snapshot_create = true;
+ manager_dispatch_load_queue(m);
+ assert(u->load_state == UNIT_LOADED);
+
+ HASHMAP_FOREACH_KEY(other, k, m->units, i) {
+
+ if (other->ignore_on_snapshot)
+ continue;
+
+ if (k != other->id)
+ continue;
+
+ if (UNIT_VTABLE(other)->check_snapshot)
+ if (!UNIT_VTABLE(other)->check_snapshot(other))
+ continue;
+
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ continue;
+
+ if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, other, true)) < 0)
+ goto fail;
+ }
+
+ SNAPSHOT(u)->cleanup = cleanup;
+ *_s = SNAPSHOT(u);
+
+ return 0;
+
+fail:
+ if (u)
+ unit_add_to_cleanup_queue(u);
+
+ return r;
+}
+
+void snapshot_remove(Snapshot *s) {
+ assert(s);
+
+ unit_add_to_cleanup_queue(UNIT(s));
+}
+
+static const char* const snapshot_state_table[_SNAPSHOT_STATE_MAX] = {
+ [SNAPSHOT_DEAD] = "dead",
+ [SNAPSHOT_ACTIVE] = "active"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(snapshot_state, SnapshotState);
+
+const UnitVTable snapshot_vtable = {
+ .object_size = sizeof(Snapshot),
+
+ .no_alias = true,
+ .no_instances = true,
+ .no_gc = true,
+
+ .init = snapshot_init,
+
+ .load = snapshot_load,
+ .coldplug = snapshot_coldplug,
+
+ .dump = snapshot_dump,
+
+ .start = snapshot_start,
+ .stop = snapshot_stop,
+
+ .serialize = snapshot_serialize,
+ .deserialize_item = snapshot_deserialize_item,
+
+ .active_state = snapshot_active_state,
+ .sub_state_to_string = snapshot_sub_state_to_string,
+
+ .bus_interface = "org.freedesktop.systemd1.Snapshot",
+ .bus_message_handler = bus_snapshot_message_handler
+};
diff --git a/src/core/snapshot.h b/src/core/snapshot.h
new file mode 100644
index 0000000000..9662d93164
--- /dev/null
+++ b/src/core/snapshot.h
@@ -0,0 +1,50 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Snapshot Snapshot;
+
+#include "unit.h"
+
+typedef enum SnapshotState {
+ SNAPSHOT_DEAD,
+ SNAPSHOT_ACTIVE,
+ _SNAPSHOT_STATE_MAX,
+ _SNAPSHOT_STATE_INVALID = -1
+} SnapshotState;
+
+struct Snapshot {
+ Unit meta;
+
+ SnapshotState state, deserialized_state;
+
+ bool cleanup;
+ bool by_snapshot_create:1;
+};
+
+extern const UnitVTable snapshot_vtable;
+
+int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **s);
+void snapshot_remove(Snapshot *s);
+
+const char* snapshot_state_to_string(SnapshotState i);
+SnapshotState snapshot_state_from_string(const char *s);
diff --git a/src/core/socket.c b/src/core/socket.c
new file mode 100644
index 0000000000..c0959815c1
--- /dev/null
+++ b/src/core/socket.c
@@ -0,0 +1,2289 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <signal.h>
+#include <arpa/inet.h>
+#include <mqueue.h>
+#include <attr/xattr.h>
+
+#include "unit.h"
+#include "socket.h"
+#include "netinet/tcp.h"
+#include "log.h"
+#include "load-dropin.h"
+#include "load-fragment.h"
+#include "strv.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "unit-name.h"
+#include "unit-printf.h"
+#include "dbus-socket.h"
+#include "missing.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "label.h"
+#include "exit-status.h"
+#include "def.h"
+
+static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
+ [SOCKET_DEAD] = UNIT_INACTIVE,
+ [SOCKET_START_PRE] = UNIT_ACTIVATING,
+ [SOCKET_START_POST] = UNIT_ACTIVATING,
+ [SOCKET_LISTENING] = UNIT_ACTIVE,
+ [SOCKET_RUNNING] = UNIT_ACTIVE,
+ [SOCKET_STOP_PRE] = UNIT_DEACTIVATING,
+ [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING,
+ [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING,
+ [SOCKET_STOP_POST] = UNIT_DEACTIVATING,
+ [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING,
+ [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING,
+ [SOCKET_FAILED] = UNIT_FAILED
+};
+
+static void socket_init(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ s->backlog = SOMAXCONN;
+ s->timeout_usec = DEFAULT_TIMEOUT_USEC;
+ s->directory_mode = 0755;
+ s->socket_mode = 0666;
+
+ s->max_connections = 64;
+
+ s->priority = -1;
+ s->ip_tos = -1;
+ s->ip_ttl = -1;
+ s->mark = -1;
+
+ exec_context_init(&s->exec_context);
+ s->exec_context.std_output = u->manager->default_std_output;
+ s->exec_context.std_error = u->manager->default_std_error;
+ kill_context_init(&s->kill_context);
+
+ s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID;
+}
+
+static void socket_unwatch_control_pid(Socket *s) {
+ assert(s);
+
+ if (s->control_pid <= 0)
+ return;
+
+ unit_unwatch_pid(UNIT(s), s->control_pid);
+ s->control_pid = 0;
+}
+
+static void socket_done(Unit *u) {
+ Socket *s = SOCKET(u);
+ SocketPort *p;
+
+ assert(s);
+
+ while ((p = s->ports)) {
+ LIST_REMOVE(SocketPort, port, s->ports, p);
+
+ if (p->fd >= 0) {
+ unit_unwatch_fd(UNIT(s), &p->fd_watch);
+ close_nointr_nofail(p->fd);
+ }
+
+ free(p->path);
+ free(p);
+ }
+
+ exec_context_done(&s->exec_context);
+ exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX);
+ s->control_command = NULL;
+
+ socket_unwatch_control_pid(s);
+
+ unit_ref_unset(&s->service);
+
+ free(s->tcp_congestion);
+ s->tcp_congestion = NULL;
+
+ free(s->bind_to_device);
+ s->bind_to_device = NULL;
+
+ free(s->smack);
+ free(s->smack_ip_in);
+ free(s->smack_ip_out);
+
+ unit_unwatch_timer(u, &s->timer_watch);
+}
+
+static int socket_instantiate_service(Socket *s) {
+ char *prefix, *name;
+ int r;
+ Unit *u;
+
+ assert(s);
+
+ /* This fills in s->service if it isn't filled in yet. For
+ * Accept=yes sockets we create the next connection service
+ * here. For Accept=no this is mostly a NOP since the service
+ * is figured out at load time anyway. */
+
+ if (UNIT_DEREF(s->service))
+ return 0;
+
+ assert(s->accept);
+
+ if (!(prefix = unit_name_to_prefix(UNIT(s)->id)))
+ return -ENOMEM;
+
+ r = asprintf(&name, "%s@%u.service", prefix, s->n_accepted);
+ free(prefix);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u);
+ free(name);
+
+ if (r < 0)
+ return r;
+
+#ifdef HAVE_SYSV_COMPAT
+ if (SERVICE(u)->is_sysv) {
+ log_error("Using SysV services for socket activation is not supported. Refusing.");
+ return -ENOENT;
+ }
+#endif
+
+ u->no_gc = true;
+ unit_ref_set(&s->service, u);
+
+ return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false);
+}
+
+static bool have_non_accept_socket(Socket *s) {
+ SocketPort *p;
+
+ assert(s);
+
+ if (!s->accept)
+ return true;
+
+ LIST_FOREACH(port, p, s->ports) {
+
+ if (p->type != SOCKET_SOCKET)
+ return true;
+
+ if (!socket_address_can_accept(&p->address))
+ return true;
+ }
+
+ return false;
+}
+
+static int socket_verify(Socket *s) {
+ assert(s);
+
+ if (UNIT(s)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!s->ports) {
+ log_error("%s lacks Listen setting. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->accept && have_non_accept_socket(s)) {
+ log_error("%s configured for accepting sockets, but sockets are non-accepting. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->accept && s->max_connections <= 0) {
+ log_error("%s's MaxConnection setting too small. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->accept && UNIT_DEREF(s->service)) {
+ log_error("Explicit service configuration for accepting sockets not supported on %s. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) {
+ log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static bool socket_needs_mount(Socket *s, const char *prefix) {
+ SocketPort *p;
+
+ assert(s);
+
+ LIST_FOREACH(port, p, s->ports) {
+
+ if (p->type == SOCKET_SOCKET) {
+ if (socket_address_needs_mount(&p->address, prefix))
+ return true;
+ } else if (p->type == SOCKET_FIFO || p->type == SOCKET_SPECIAL) {
+ if (path_startswith(p->path, prefix))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int socket_add_one_mount_link(Socket *s, Mount *m) {
+ int r;
+
+ assert(s);
+ assert(m);
+
+ if (UNIT(s)->load_state != UNIT_LOADED ||
+ UNIT(m)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!socket_needs_mount(s, m->where))
+ return 0;
+
+ r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int socket_add_mount_links(Socket *s) {
+ Unit *other;
+ int r;
+
+ assert(s);
+
+ LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) {
+ r = socket_add_one_mount_link(s, MOUNT(other));
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int socket_add_device_link(Socket *s) {
+ char *t;
+ int r;
+
+ assert(s);
+
+ if (!s->bind_to_device)
+ return 0;
+
+ if (asprintf(&t, "/sys/subsystem/net/devices/%s", s->bind_to_device) < 0)
+ return -ENOMEM;
+
+ r = unit_add_node_link(UNIT(s), t, false);
+ free(t);
+
+ return r;
+}
+
+static int socket_add_default_dependencies(Socket *s) {
+ int r;
+ assert(s);
+
+ if (UNIT(s)->manager->running_as == SYSTEMD_SYSTEM) {
+ if ((r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true)) < 0)
+ return r;
+
+ if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
+ return r;
+ }
+
+ return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+}
+
+static bool socket_has_exec(Socket *s) {
+ unsigned i;
+ assert(s);
+
+ for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++)
+ if (s->exec_command[i])
+ return true;
+
+ return false;
+}
+
+static int socket_load(Unit *u) {
+ Socket *s = SOCKET(u);
+ int r;
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ if ((r = unit_load_fragment_and_dropin(u)) < 0)
+ return r;
+
+ /* This is a new unit? Then let's add in some extras */
+ if (u->load_state == UNIT_LOADED) {
+
+ if (have_non_accept_socket(s)) {
+
+ if (!UNIT_DEREF(s->service)) {
+ Unit *x;
+
+ r = unit_load_related_unit(u, ".service", &x);
+ if (r < 0)
+ return r;
+
+ unit_ref_set(&s->service, x);
+ }
+
+ r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true);
+ if (r < 0)
+ return r;
+ }
+
+ if ((r = socket_add_mount_links(s)) < 0)
+ return r;
+
+ if ((r = socket_add_device_link(s)) < 0)
+ return r;
+
+ if (socket_has_exec(s))
+ if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroups(u)) < 0)
+ return r;
+
+ if (UNIT(s)->default_dependencies)
+ if ((r = socket_add_default_dependencies(s)) < 0)
+ return r;
+
+ r = unit_exec_context_defaults(u, &s->exec_context);
+ if (r < 0)
+ return r;
+ }
+
+ return socket_verify(s);
+}
+
+static const char* listen_lookup(int family, int type) {
+
+ if (family == AF_NETLINK)
+ return "ListenNetlink";
+
+ if (type == SOCK_STREAM)
+ return "ListenStream";
+ else if (type == SOCK_DGRAM)
+ return "ListenDatagram";
+ else if (type == SOCK_SEQPACKET)
+ return "ListenSequentialPacket";
+
+ assert_not_reached("Unknown socket type");
+ return NULL;
+}
+
+static void socket_dump(Unit *u, FILE *f, const char *prefix) {
+
+ SocketExecCommand c;
+ Socket *s = SOCKET(u);
+ SocketPort *p;
+ const char *prefix2;
+ char *p2;
+
+ assert(s);
+ assert(f);
+
+ p2 = strappend(prefix, "\t");
+ prefix2 = p2 ? p2 : prefix;
+
+ fprintf(f,
+ "%sSocket State: %s\n"
+ "%sResult: %s\n"
+ "%sBindIPv6Only: %s\n"
+ "%sBacklog: %u\n"
+ "%sSocketMode: %04o\n"
+ "%sDirectoryMode: %04o\n"
+ "%sKeepAlive: %s\n"
+ "%sFreeBind: %s\n"
+ "%sTransparent: %s\n"
+ "%sBroadcast: %s\n"
+ "%sPassCredentials: %s\n"
+ "%sPassSecurity: %s\n"
+ "%sTCPCongestion: %s\n",
+ prefix, socket_state_to_string(s->state),
+ prefix, socket_result_to_string(s->result),
+ prefix, socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only),
+ prefix, s->backlog,
+ prefix, s->socket_mode,
+ prefix, s->directory_mode,
+ prefix, yes_no(s->keep_alive),
+ prefix, yes_no(s->free_bind),
+ prefix, yes_no(s->transparent),
+ prefix, yes_no(s->broadcast),
+ prefix, yes_no(s->pass_cred),
+ prefix, yes_no(s->pass_sec),
+ prefix, strna(s->tcp_congestion));
+
+ if (s->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %lu\n",
+ prefix, (unsigned long) s->control_pid);
+
+ if (s->bind_to_device)
+ fprintf(f,
+ "%sBindToDevice: %s\n",
+ prefix, s->bind_to_device);
+
+ if (s->accept)
+ fprintf(f,
+ "%sAccepted: %u\n"
+ "%sNConnections: %u\n"
+ "%sMaxConnections: %u\n",
+ prefix, s->n_accepted,
+ prefix, s->n_connections,
+ prefix, s->max_connections);
+
+ if (s->priority >= 0)
+ fprintf(f,
+ "%sPriority: %i\n",
+ prefix, s->priority);
+
+ if (s->receive_buffer > 0)
+ fprintf(f,
+ "%sReceiveBuffer: %zu\n",
+ prefix, s->receive_buffer);
+
+ if (s->send_buffer > 0)
+ fprintf(f,
+ "%sSendBuffer: %zu\n",
+ prefix, s->send_buffer);
+
+ if (s->ip_tos >= 0)
+ fprintf(f,
+ "%sIPTOS: %i\n",
+ prefix, s->ip_tos);
+
+ if (s->ip_ttl >= 0)
+ fprintf(f,
+ "%sIPTTL: %i\n",
+ prefix, s->ip_ttl);
+
+ if (s->pipe_size > 0)
+ fprintf(f,
+ "%sPipeSize: %zu\n",
+ prefix, s->pipe_size);
+
+ if (s->mark >= 0)
+ fprintf(f,
+ "%sMark: %i\n",
+ prefix, s->mark);
+
+ if (s->mq_maxmsg > 0)
+ fprintf(f,
+ "%sMessageQueueMaxMessages: %li\n",
+ prefix, s->mq_maxmsg);
+
+ if (s->mq_msgsize > 0)
+ fprintf(f,
+ "%sMessageQueueMessageSize: %li\n",
+ prefix, s->mq_msgsize);
+
+ if (s->smack)
+ fprintf(f,
+ "%sSmackLabel: %s\n",
+ prefix, s->smack);
+
+ if (s->smack_ip_in)
+ fprintf(f,
+ "%sSmackLabelIPIn: %s\n",
+ prefix, s->smack_ip_in);
+
+ if (s->smack_ip_out)
+ fprintf(f,
+ "%sSmackLabelIPOut: %s\n",
+ prefix, s->smack_ip_out);
+
+ LIST_FOREACH(port, p, s->ports) {
+
+ if (p->type == SOCKET_SOCKET) {
+ const char *t;
+ int r;
+ char *k = NULL;
+
+ if ((r = socket_address_print(&p->address, &k)) < 0)
+ t = strerror(-r);
+ else
+ t = k;
+
+ fprintf(f, "%s%s: %s\n", prefix, listen_lookup(socket_address_family(&p->address), p->address.type), t);
+ free(k);
+ } else if (p->type == SOCKET_SPECIAL)
+ fprintf(f, "%sListenSpecial: %s\n", prefix, p->path);
+ else if (p->type == SOCKET_MQUEUE)
+ fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path);
+ else
+ fprintf(f, "%sListenFIFO: %s\n", prefix, p->path);
+ }
+
+ exec_context_dump(&s->exec_context, f, prefix);
+ kill_context_dump(&s->kill_context, f, prefix);
+
+ for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) {
+ if (!s->exec_command[c])
+ continue;
+
+ fprintf(f, "%s-> %s:\n",
+ prefix, socket_exec_command_to_string(c));
+
+ exec_command_dump_list(s->exec_command[c], f, prefix2);
+ }
+
+ free(p2);
+}
+
+static int instance_from_socket(int fd, unsigned nr, char **instance) {
+ socklen_t l;
+ char *r;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ struct sockaddr_storage storage;
+ } local, remote;
+
+ assert(fd >= 0);
+ assert(instance);
+
+ l = sizeof(local);
+ if (getsockname(fd, &local.sa, &l) < 0)
+ return -errno;
+
+ l = sizeof(remote);
+ if (getpeername(fd, &remote.sa, &l) < 0)
+ return -errno;
+
+ switch (local.sa.sa_family) {
+
+ case AF_INET: {
+ uint32_t
+ a = ntohl(local.in.sin_addr.s_addr),
+ b = ntohl(remote.in.sin_addr.s_addr);
+
+ if (asprintf(&r,
+ "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u",
+ nr,
+ a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
+ ntohs(local.in.sin_port),
+ b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF,
+ ntohs(remote.in.sin_port)) < 0)
+ return -ENOMEM;
+
+ break;
+ }
+
+ case AF_INET6: {
+ static const unsigned char ipv4_prefix[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF
+ };
+
+ if (memcmp(&local.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0 &&
+ memcmp(&remote.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) {
+ const uint8_t
+ *a = local.in6.sin6_addr.s6_addr+12,
+ *b = remote.in6.sin6_addr.s6_addr+12;
+
+ if (asprintf(&r,
+ "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u",
+ nr,
+ a[0], a[1], a[2], a[3],
+ ntohs(local.in6.sin6_port),
+ b[0], b[1], b[2], b[3],
+ ntohs(remote.in6.sin6_port)) < 0)
+ return -ENOMEM;
+ } else {
+ char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN];
+
+ if (asprintf(&r,
+ "%u-%s:%u-%s:%u",
+ nr,
+ inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)),
+ ntohs(local.in6.sin6_port),
+ inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)),
+ ntohs(remote.in6.sin6_port)) < 0)
+ return -ENOMEM;
+ }
+
+ break;
+ }
+
+ case AF_UNIX: {
+ struct ucred ucred;
+
+ l = sizeof(ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0)
+ return -errno;
+
+ if (asprintf(&r,
+ "%u-%lu-%lu",
+ nr,
+ (unsigned long) ucred.pid,
+ (unsigned long) ucred.uid) < 0)
+ return -ENOMEM;
+
+ break;
+ }
+
+ default:
+ assert_not_reached("Unhandled socket type.");
+ }
+
+ *instance = r;
+ return 0;
+}
+
+static void socket_close_fds(Socket *s) {
+ SocketPort *p;
+
+ assert(s);
+
+ LIST_FOREACH(port, p, s->ports) {
+ if (p->fd < 0)
+ continue;
+
+ unit_unwatch_fd(UNIT(s), &p->fd_watch);
+ close_nointr_nofail(p->fd);
+
+ /* One little note: we should never delete any sockets
+ * in the file system here! After all some other
+ * process we spawned might still have a reference of
+ * this fd and wants to continue to use it. Therefore
+ * we delete sockets in the file system before we
+ * create a new one, not after we stopped using
+ * one! */
+
+ p->fd = -1;
+ }
+}
+
+static void socket_apply_socket_options(Socket *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ if (s->keep_alive) {
+ int b = s->keep_alive;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &b, sizeof(b)) < 0)
+ log_warning("SO_KEEPALIVE failed: %m");
+ }
+
+ if (s->broadcast) {
+ int one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0)
+ log_warning("SO_BROADCAST failed: %m");
+ }
+
+ if (s->pass_cred) {
+ int one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0)
+ log_warning("SO_PASSCRED failed: %m");
+ }
+
+ if (s->pass_sec) {
+ int one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0)
+ log_warning("SO_PASSSEC failed: %m");
+ }
+
+ if (s->priority >= 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0)
+ log_warning("SO_PRIORITY failed: %m");
+
+ if (s->receive_buffer > 0) {
+ int value = (int) s->receive_buffer;
+
+ /* We first try with SO_RCVBUFFORCE, in case we have the perms for that */
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0)
+ log_warning("SO_RCVBUF failed: %m");
+ }
+
+ if (s->send_buffer > 0) {
+ int value = (int) s->send_buffer;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0)
+ log_warning("SO_SNDBUF failed: %m");
+ }
+
+ if (s->mark >= 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0)
+ log_warning("SO_MARK failed: %m");
+
+ if (s->ip_tos >= 0)
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, &s->ip_tos, sizeof(s->ip_tos)) < 0)
+ log_warning("IP_TOS failed: %m");
+
+ if (s->ip_ttl >= 0) {
+ int r, x;
+
+ r = setsockopt(fd, IPPROTO_IP, IP_TTL, &s->ip_ttl, sizeof(s->ip_ttl));
+
+ if (socket_ipv6_is_supported())
+ x = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->ip_ttl, sizeof(s->ip_ttl));
+ else {
+ x = -1;
+ errno = EAFNOSUPPORT;
+ }
+
+ if (r < 0 && x < 0)
+ log_warning("IP_TTL/IPV6_UNICAST_HOPS failed: %m");
+ }
+
+ if (s->tcp_congestion)
+ if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0)
+ log_warning("TCP_CONGESTION failed: %m");
+
+ if (s->smack_ip_in)
+ if (fsetxattr(fd, "security.SMACK64IPIN", s->smack_ip_in, strlen(s->smack_ip_in), 0) < 0)
+ log_error("fsetxattr(\"security.SMACK64IPIN\"): %m");
+
+ if (s->smack_ip_out)
+ if (fsetxattr(fd, "security.SMACK64IPOUT", s->smack_ip_out, strlen(s->smack_ip_out), 0) < 0)
+ log_error("fsetxattr(\"security.SMACK64IPOUT\"): %m");
+}
+
+static void socket_apply_fifo_options(Socket *s, int fd) {
+ assert(s);
+ assert(fd >= 0);
+
+ if (s->pipe_size > 0)
+ if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0)
+ log_warning("F_SETPIPE_SZ: %m");
+
+ if (s->smack)
+ if (fsetxattr(fd, "security.SMACK64", s->smack, strlen(s->smack), 0) < 0)
+ log_error("fsetxattr(\"security.SMACK64\"): %m");
+}
+
+static int fifo_address_create(
+ const char *path,
+ mode_t directory_mode,
+ mode_t socket_mode,
+ int *_fd) {
+
+ int fd = -1, r = 0;
+ struct stat st;
+ mode_t old_mask;
+
+ assert(path);
+ assert(_fd);
+
+ mkdir_parents_label(path, directory_mode);
+
+ r = label_context_set(path, S_IFIFO);
+ if (r < 0)
+ goto fail;
+
+ /* Enforce the right access mode for the fifo */
+ old_mask = umask(~ socket_mode);
+
+ /* Include the original umask in our mask */
+ umask(~socket_mode | old_mask);
+
+ r = mkfifo(path, socket_mode);
+ umask(old_mask);
+
+ if (r < 0 && errno != EEXIST) {
+ r = -errno;
+ goto fail;
+ }
+
+ if ((fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ label_context_clear();
+
+ if (fstat(fd, &st) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode) ||
+ (st.st_mode & 0777) != (socket_mode & ~old_mask) ||
+ st.st_uid != getuid() ||
+ st.st_gid != getgid()) {
+
+ r = -EEXIST;
+ goto fail;
+ }
+
+ *_fd = fd;
+ return 0;
+
+fail:
+ label_context_clear();
+
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+static int special_address_create(
+ const char *path,
+ int *_fd) {
+
+ int fd = -1, r = 0;
+ struct stat st;
+
+ assert(path);
+ assert(_fd);
+
+ if ((fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Check whether this is a /proc, /sys or /dev file or char device */
+ if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) {
+ r = -EEXIST;
+ goto fail;
+ }
+
+ *_fd = fd;
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+static int mq_address_create(
+ const char *path,
+ mode_t mq_mode,
+ long maxmsg,
+ long msgsize,
+ int *_fd) {
+
+ int fd = -1, r = 0;
+ struct stat st;
+ mode_t old_mask;
+ struct mq_attr _attr, *attr = NULL;
+
+ assert(path);
+ assert(_fd);
+
+ if (maxmsg > 0 && msgsize > 0) {
+ zero(_attr);
+ _attr.mq_flags = O_NONBLOCK;
+ _attr.mq_maxmsg = maxmsg;
+ _attr.mq_msgsize = msgsize;
+ attr = &_attr;
+ }
+
+ /* Enforce the right access mode for the mq */
+ old_mask = umask(~ mq_mode);
+
+ /* Include the original umask in our mask */
+ umask(~mq_mode | old_mask);
+
+ fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr);
+ umask(old_mask);
+
+ if (fd < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if ((st.st_mode & 0777) != (mq_mode & ~old_mask) ||
+ st.st_uid != getuid() ||
+ st.st_gid != getgid()) {
+
+ r = -EEXIST;
+ goto fail;
+ }
+
+ *_fd = fd;
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
+
+static int socket_open_fds(Socket *s) {
+ SocketPort *p;
+ int r;
+ char *label = NULL;
+ bool know_label = false;
+
+ assert(s);
+
+ LIST_FOREACH(port, p, s->ports) {
+
+ if (p->fd >= 0)
+ continue;
+
+ if (p->type == SOCKET_SOCKET) {
+
+ if (!know_label) {
+
+ if ((r = socket_instantiate_service(s)) < 0)
+ return r;
+
+ if (UNIT_DEREF(s->service) &&
+ SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) {
+ r = label_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label);
+
+ if (r < 0) {
+ if (r != -EPERM)
+ return r;
+ }
+ }
+
+ know_label = true;
+ }
+
+ if ((r = socket_address_listen(
+ &p->address,
+ s->backlog,
+ s->bind_ipv6_only,
+ s->bind_to_device,
+ s->free_bind,
+ s->transparent,
+ s->directory_mode,
+ s->socket_mode,
+ label,
+ &p->fd)) < 0)
+ goto rollback;
+
+ socket_apply_socket_options(s, p->fd);
+
+ } else if (p->type == SOCKET_SPECIAL) {
+
+ if ((r = special_address_create(
+ p->path,
+ &p->fd)) < 0)
+ goto rollback;
+
+ } else if (p->type == SOCKET_FIFO) {
+
+ if ((r = fifo_address_create(
+ p->path,
+ s->directory_mode,
+ s->socket_mode,
+ &p->fd)) < 0)
+ goto rollback;
+
+ socket_apply_fifo_options(s, p->fd);
+ } else if (p->type == SOCKET_MQUEUE) {
+
+ if ((r = mq_address_create(
+ p->path,
+ s->socket_mode,
+ s->mq_maxmsg,
+ s->mq_msgsize,
+ &p->fd)) < 0)
+ goto rollback;
+ } else
+ assert_not_reached("Unknown port type");
+ }
+
+ label_free(label);
+ return 0;
+
+rollback:
+ socket_close_fds(s);
+ label_free(label);
+ return r;
+}
+
+static void socket_unwatch_fds(Socket *s) {
+ SocketPort *p;
+
+ assert(s);
+
+ LIST_FOREACH(port, p, s->ports) {
+ if (p->fd < 0)
+ continue;
+
+ unit_unwatch_fd(UNIT(s), &p->fd_watch);
+ }
+}
+
+static int socket_watch_fds(Socket *s) {
+ SocketPort *p;
+ int r;
+
+ assert(s);
+
+ LIST_FOREACH(port, p, s->ports) {
+ if (p->fd < 0)
+ continue;
+
+ p->fd_watch.socket_accept =
+ s->accept &&
+ p->type == SOCKET_SOCKET &&
+ socket_address_can_accept(&p->address);
+
+ if ((r = unit_watch_fd(UNIT(s), p->fd, EPOLLIN, &p->fd_watch)) < 0)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ socket_unwatch_fds(s);
+ return r;
+}
+
+static void socket_set_state(Socket *s, SocketState state) {
+ SocketState old_state;
+ assert(s);
+
+ old_state = s->state;
+ s->state = state;
+
+ if (state != SOCKET_START_PRE &&
+ state != SOCKET_START_POST &&
+ state != SOCKET_STOP_PRE &&
+ state != SOCKET_STOP_PRE_SIGTERM &&
+ state != SOCKET_STOP_PRE_SIGKILL &&
+ state != SOCKET_STOP_POST &&
+ state != SOCKET_FINAL_SIGTERM &&
+ state != SOCKET_FINAL_SIGKILL) {
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+ socket_unwatch_control_pid(s);
+ s->control_command = NULL;
+ s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID;
+ }
+
+ if (state != SOCKET_LISTENING)
+ socket_unwatch_fds(s);
+
+ if (state != SOCKET_START_POST &&
+ state != SOCKET_LISTENING &&
+ state != SOCKET_RUNNING &&
+ state != SOCKET_STOP_PRE &&
+ state != SOCKET_STOP_PRE_SIGTERM &&
+ state != SOCKET_STOP_PRE_SIGKILL)
+ socket_close_fds(s);
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(s)->id,
+ socket_state_to_string(old_state),
+ socket_state_to_string(state));
+
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static int socket_coldplug(Unit *u) {
+ Socket *s = SOCKET(u);
+ int r;
+
+ assert(s);
+ assert(s->state == SOCKET_DEAD);
+
+ if (s->deserialized_state != s->state) {
+
+ if (s->deserialized_state == SOCKET_START_PRE ||
+ s->deserialized_state == SOCKET_START_POST ||
+ s->deserialized_state == SOCKET_STOP_PRE ||
+ s->deserialized_state == SOCKET_STOP_PRE_SIGTERM ||
+ s->deserialized_state == SOCKET_STOP_PRE_SIGKILL ||
+ s->deserialized_state == SOCKET_STOP_POST ||
+ s->deserialized_state == SOCKET_FINAL_SIGTERM ||
+ s->deserialized_state == SOCKET_FINAL_SIGKILL) {
+
+ if (s->control_pid <= 0)
+ return -EBADMSG;
+
+ if ((r = unit_watch_pid(UNIT(s), s->control_pid)) < 0)
+ return r;
+
+ if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
+ return r;
+ }
+
+ if (s->deserialized_state == SOCKET_START_POST ||
+ s->deserialized_state == SOCKET_LISTENING ||
+ s->deserialized_state == SOCKET_RUNNING ||
+ s->deserialized_state == SOCKET_STOP_PRE ||
+ s->deserialized_state == SOCKET_STOP_PRE_SIGTERM ||
+ s->deserialized_state == SOCKET_STOP_PRE_SIGKILL)
+ if ((r = socket_open_fds(s)) < 0)
+ return r;
+
+ if (s->deserialized_state == SOCKET_LISTENING)
+ if ((r = socket_watch_fds(s)) < 0)
+ return r;
+
+ socket_set_state(s, s->deserialized_state);
+ }
+
+ return 0;
+}
+
+static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
+ pid_t pid;
+ int r;
+ char **argv;
+
+ assert(s);
+ assert(c);
+ assert(_pid);
+
+ if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
+ goto fail;
+
+ if (!(argv = unit_full_printf_strv(UNIT(s), c->argv))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = exec_spawn(c,
+ argv,
+ &s->exec_context,
+ NULL, 0,
+ UNIT(s)->manager->environment,
+ true,
+ true,
+ true,
+ UNIT(s)->manager->confirm_spawn,
+ UNIT(s)->cgroup_bondings,
+ UNIT(s)->cgroup_attributes,
+ NULL,
+ UNIT(s)->id,
+ NULL,
+ &pid);
+
+ strv_free(argv);
+ if (r < 0)
+ goto fail;
+
+ if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
+ /* FIXME: we need to do something here */
+ goto fail;
+
+ *_pid = pid;
+
+ return 0;
+
+fail:
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+
+ return r;
+}
+
+static void socket_enter_dead(Socket *s, SocketResult f) {
+ assert(s);
+
+ if (f != SOCKET_SUCCESS)
+ s->result = f;
+
+ socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
+}
+
+static void socket_enter_signal(Socket *s, SocketState state, SocketResult f);
+
+static void socket_enter_stop_post(Socket *s, SocketResult f) {
+ int r;
+ assert(s);
+
+ if (f != SOCKET_SUCCESS)
+ s->result = f;
+
+ socket_unwatch_control_pid(s);
+
+ s->control_command_id = SOCKET_EXEC_STOP_POST;
+
+ if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) {
+ if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
+ goto fail;
+
+ socket_set_state(s, SOCKET_STOP_POST);
+ } else
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'stop-post' task: %s", UNIT(s)->id, strerror(-r));
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES);
+}
+
+static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) {
+ int r;
+ Set *pid_set = NULL;
+ bool wait_for_exit = false;
+
+ assert(s);
+
+ if (f != SOCKET_SUCCESS)
+ s->result = f;
+
+ if (s->kill_context.kill_mode != KILL_NONE) {
+ int sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_FINAL_SIGTERM) ? s->kill_context.kill_signal : SIGKILL;
+
+ if (s->control_pid > 0) {
+ if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH)
+
+ log_warning("Failed to kill control process %li: %m", (long) s->control_pid);
+ else
+ wait_for_exit = true;
+ }
+
+ if (s->kill_context.kill_mode == KILL_CONTROL_GROUP) {
+
+ if (!(pid_set = set_new(trivial_hash_func, trivial_compare_func))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (s->control_pid > 0)
+ if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0)
+ goto fail;
+
+ r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, false, pid_set, NULL);
+ if (r < 0) {
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ log_warning("Failed to kill control group: %s", strerror(-r));
+ } else if (r > 0)
+ wait_for_exit = true;
+
+ set_free(pid_set);
+ pid_set = NULL;
+ }
+ }
+
+ if (wait_for_exit) {
+ if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
+ goto fail;
+
+ socket_set_state(s, state);
+ } else if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL)
+ socket_enter_stop_post(s, SOCKET_SUCCESS);
+ else
+ socket_enter_dead(s, SOCKET_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r));
+
+ if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL)
+ socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES);
+ else
+ socket_enter_dead(s, SOCKET_FAILURE_RESOURCES);
+
+ if (pid_set)
+ set_free(pid_set);
+}
+
+static void socket_enter_stop_pre(Socket *s, SocketResult f) {
+ int r;
+ assert(s);
+
+ if (f != SOCKET_SUCCESS)
+ s->result = f;
+
+ socket_unwatch_control_pid(s);
+
+ s->control_command_id = SOCKET_EXEC_STOP_PRE;
+
+ if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) {
+ if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
+ goto fail;
+
+ socket_set_state(s, SOCKET_STOP_PRE);
+ } else
+ socket_enter_stop_post(s, SOCKET_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'stop-pre' task: %s", UNIT(s)->id, strerror(-r));
+ socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES);
+}
+
+static void socket_enter_listening(Socket *s) {
+ int r;
+ assert(s);
+
+ r = socket_watch_fds(s);
+ if (r < 0) {
+ log_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r));
+ goto fail;
+ }
+
+ socket_set_state(s, SOCKET_LISTENING);
+ return;
+
+fail:
+ socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
+}
+
+static void socket_enter_start_post(Socket *s) {
+ int r;
+ assert(s);
+
+ r = socket_open_fds(s);
+ if (r < 0) {
+ log_warning("%s failed to listen on sockets: %s", UNIT(s)->id, strerror(-r));
+ goto fail;
+ }
+
+ socket_unwatch_control_pid(s);
+
+ s->control_command_id = SOCKET_EXEC_START_POST;
+
+ if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) {
+ r = socket_spawn(s, s->control_command, &s->control_pid);
+ if (r < 0) {
+ log_warning("%s failed to run 'start-post' task: %s", UNIT(s)->id, strerror(-r));
+ goto fail;
+ }
+
+ socket_set_state(s, SOCKET_START_POST);
+ } else
+ socket_enter_listening(s);
+
+ return;
+
+fail:
+ socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
+}
+
+static void socket_enter_start_pre(Socket *s) {
+ int r;
+ assert(s);
+
+ socket_unwatch_control_pid(s);
+
+ s->control_command_id = SOCKET_EXEC_START_PRE;
+
+ if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) {
+ if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
+ goto fail;
+
+ socket_set_state(s, SOCKET_START_PRE);
+ } else
+ socket_enter_start_post(s);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'start-pre' task: %s", UNIT(s)->id, strerror(-r));
+ socket_enter_dead(s, SOCKET_FAILURE_RESOURCES);
+}
+
+static void socket_enter_running(Socket *s, int cfd) {
+ int r;
+ DBusError error;
+
+ assert(s);
+ dbus_error_init(&error);
+
+ /* We don't take connections anymore if we are supposed to
+ * shut down anyway */
+ if (unit_pending_inactive(UNIT(s))) {
+ log_debug("Suppressing connection request on %s since unit stop is scheduled.", UNIT(s)->id);
+
+ if (cfd >= 0)
+ close_nointr_nofail(cfd);
+ else {
+ /* Flush all sockets by closing and reopening them */
+ socket_close_fds(s);
+
+ r = socket_watch_fds(s);
+ if (r < 0) {
+ log_warning("%s failed to watch sockets: %s", UNIT(s)->id, strerror(-r));
+ socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
+ }
+ }
+
+ return;
+ }
+
+ if (cfd < 0) {
+ Iterator i;
+ Unit *u;
+ bool pending = false;
+
+ /* If there's already a start pending don't bother to
+ * do anything */
+ SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERS], i)
+ if (unit_pending_active(u)) {
+ pending = true;
+ break;
+ }
+
+ if (!pending) {
+ r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, true, &error, NULL);
+ if (r < 0)
+ goto fail;
+ }
+
+ socket_set_state(s, SOCKET_RUNNING);
+ } else {
+ char *prefix, *instance = NULL, *name;
+ Service *service;
+
+ if (s->n_connections >= s->max_connections) {
+ log_warning("Too many incoming connections (%u)", s->n_connections);
+ close_nointr_nofail(cfd);
+ return;
+ }
+
+ r = socket_instantiate_service(s);
+ if (r < 0)
+ goto fail;
+
+ r = instance_from_socket(cfd, s->n_accepted, &instance);
+ if (r < 0) {
+ if (r != -ENOTCONN)
+ goto fail;
+
+ /* ENOTCONN is legitimate if TCP RST was received.
+ * This connection is over, but the socket unit lives on. */
+ close_nointr_nofail(cfd);
+ return;
+ }
+
+ prefix = unit_name_to_prefix(UNIT(s)->id);
+ if (!prefix) {
+ free(instance);
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ name = unit_name_build(prefix, instance, ".service");
+ free(prefix);
+ free(instance);
+
+ if (!name) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = unit_add_name(UNIT_DEREF(s->service), name);
+ if (r < 0) {
+ free(name);
+ goto fail;
+ }
+
+ service = SERVICE(UNIT_DEREF(s->service));
+ unit_ref_unset(&s->service);
+ s->n_accepted ++;
+
+ UNIT(service)->no_gc = false;
+
+ unit_choose_id(UNIT(service), name);
+ free(name);
+
+ r = service_set_socket_fd(service, cfd, s);
+ if (r < 0)
+ goto fail;
+
+ cfd = -1;
+ s->n_connections ++;
+
+ r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, true, &error, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* Notify clients about changed counters */
+ unit_add_to_dbus_queue(UNIT(s));
+ }
+
+ return;
+
+fail:
+ log_warning("%s failed to queue socket startup job: %s", UNIT(s)->id, bus_error(&error, r));
+ socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
+
+ if (cfd >= 0)
+ close_nointr_nofail(cfd);
+
+ dbus_error_free(&error);
+}
+
+static void socket_run_next(Socket *s) {
+ int r;
+
+ assert(s);
+ assert(s->control_command);
+ assert(s->control_command->command_next);
+
+ socket_unwatch_control_pid(s);
+
+ s->control_command = s->control_command->command_next;
+
+ if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0)
+ goto fail;
+
+ return;
+
+fail:
+ log_warning("%s failed to run next task: %s", UNIT(s)->id, strerror(-r));
+
+ if (s->state == SOCKET_START_POST)
+ socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
+ else if (s->state == SOCKET_STOP_POST)
+ socket_enter_dead(s, SOCKET_FAILURE_RESOURCES);
+ else
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES);
+}
+
+static int socket_start(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(s);
+
+ /* We cannot fulfill this request right now, try again later
+ * please! */
+ if (s->state == SOCKET_STOP_PRE ||
+ s->state == SOCKET_STOP_PRE_SIGKILL ||
+ s->state == SOCKET_STOP_PRE_SIGTERM ||
+ s->state == SOCKET_STOP_POST ||
+ s->state == SOCKET_FINAL_SIGTERM ||
+ s->state == SOCKET_FINAL_SIGKILL)
+ return -EAGAIN;
+
+ if (s->state == SOCKET_START_PRE ||
+ s->state == SOCKET_START_POST)
+ return 0;
+
+ /* Cannot run this without the service being around */
+ if (UNIT_DEREF(s->service)) {
+ Service *service;
+
+ service = SERVICE(UNIT_DEREF(s->service));
+
+ if (UNIT(service)->load_state != UNIT_LOADED) {
+ log_error("Socket service %s not loaded, refusing.", UNIT(service)->id);
+ return -ENOENT;
+ }
+
+ /* If the service is already active we cannot start the
+ * socket */
+ if (service->state != SERVICE_DEAD &&
+ service->state != SERVICE_FAILED &&
+ service->state != SERVICE_AUTO_RESTART) {
+ log_error("Socket service %s already active, refusing.", UNIT(service)->id);
+ return -EBUSY;
+ }
+
+#ifdef HAVE_SYSV_COMPAT
+ if (service->is_sysv) {
+ log_error("Using SysV services for socket activation is not supported. Refusing.");
+ return -ENOENT;
+ }
+#endif
+ }
+
+ assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED);
+
+ s->result = SOCKET_SUCCESS;
+ socket_enter_start_pre(s);
+ return 0;
+}
+
+static int socket_stop(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(s);
+
+ /* Already on it */
+ if (s->state == SOCKET_STOP_PRE ||
+ s->state == SOCKET_STOP_PRE_SIGTERM ||
+ s->state == SOCKET_STOP_PRE_SIGKILL ||
+ s->state == SOCKET_STOP_POST ||
+ s->state == SOCKET_FINAL_SIGTERM ||
+ s->state == SOCKET_FINAL_SIGKILL)
+ return 0;
+
+ /* If there's already something running we go directly into
+ * kill mode. */
+ if (s->state == SOCKET_START_PRE ||
+ s->state == SOCKET_START_POST) {
+ socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_SUCCESS);
+ return -EAGAIN;
+ }
+
+ assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
+
+ socket_enter_stop_pre(s, SOCKET_SUCCESS);
+ return 0;
+}
+
+static int socket_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Socket *s = SOCKET(u);
+ SocketPort *p;
+ int r;
+
+ assert(u);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", socket_state_to_string(s->state));
+ unit_serialize_item(u, f, "result", socket_result_to_string(s->result));
+ unit_serialize_item_format(u, f, "n-accepted", "%u", s->n_accepted);
+
+ if (s->control_pid > 0)
+ unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid);
+
+ if (s->control_command_id >= 0)
+ unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id));
+
+ LIST_FOREACH(port, p, s->ports) {
+ int copy;
+
+ if (p->fd < 0)
+ continue;
+
+ if ((copy = fdset_put_dup(fds, p->fd)) < 0)
+ return copy;
+
+ if (p->type == SOCKET_SOCKET) {
+ char *t;
+
+ if ((r = socket_address_print(&p->address, &t)) < 0)
+ return r;
+
+ if (socket_address_family(&p->address) == AF_NETLINK)
+ unit_serialize_item_format(u, f, "netlink", "%i %s", copy, t);
+ else
+ unit_serialize_item_format(u, f, "socket", "%i %i %s", copy, p->address.type, t);
+ free(t);
+ } else if (p->type == SOCKET_SPECIAL)
+ unit_serialize_item_format(u, f, "special", "%i %s", copy, p->path);
+ else {
+ assert(p->type == SOCKET_FIFO);
+ unit_serialize_item_format(u, f, "fifo", "%i %s", copy, p->path);
+ }
+ }
+
+ return 0;
+}
+
+static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Socket *s = SOCKET(u);
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ SocketState state;
+
+ if ((state = socket_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ s->deserialized_state = state;
+ } else if (streq(key, "result")) {
+ SocketResult f;
+
+ f = socket_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != SOCKET_SUCCESS)
+ s->result = f;
+
+ } else if (streq(key, "n-accepted")) {
+ unsigned k;
+
+ if (safe_atou(value, &k) < 0)
+ log_debug("Failed to parse n-accepted value %s", value);
+ else
+ s->n_accepted += k;
+ } else if (streq(key, "control-pid")) {
+ pid_t pid;
+
+ if (parse_pid(value, &pid) < 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ s->control_pid = pid;
+ } else if (streq(key, "control-command")) {
+ SocketExecCommand id;
+
+ if ((id = socket_exec_command_from_string(value)) < 0)
+ log_debug("Failed to parse exec-command value %s", value);
+ else {
+ s->control_command_id = id;
+ s->control_command = s->exec_command[id];
+ }
+ } else if (streq(key, "fifo")) {
+ int fd, skip = 0;
+ SocketPort *p;
+
+ if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse fifo value %s", value);
+ else {
+
+ LIST_FOREACH(port, p, s->ports)
+ if (p->type == SOCKET_FIFO &&
+ streq_ptr(p->path, value+skip))
+ break;
+
+ if (p) {
+ if (p->fd >= 0)
+ close_nointr_nofail(p->fd);
+ p->fd = fdset_remove(fds, fd);
+ }
+ }
+
+ } else if (streq(key, "special")) {
+ int fd, skip = 0;
+ SocketPort *p;
+
+ if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse special value %s", value);
+ else {
+
+ LIST_FOREACH(port, p, s->ports)
+ if (p->type == SOCKET_SPECIAL &&
+ streq_ptr(p->path, value+skip))
+ break;
+
+ if (p) {
+ if (p->fd >= 0)
+ close_nointr_nofail(p->fd);
+ p->fd = fdset_remove(fds, fd);
+ }
+ }
+
+ } else if (streq(key, "socket")) {
+ int fd, type, skip = 0;
+ SocketPort *p;
+
+ if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse socket value %s", value);
+ else {
+
+ LIST_FOREACH(port, p, s->ports)
+ if (socket_address_is(&p->address, value+skip, type))
+ break;
+
+ if (p) {
+ if (p->fd >= 0)
+ close_nointr_nofail(p->fd);
+ p->fd = fdset_remove(fds, fd);
+ }
+ }
+
+ } else if (streq(key, "netlink")) {
+ int fd, skip = 0;
+ SocketPort *p;
+
+ if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
+ log_debug("Failed to parse socket value %s", value);
+ else {
+
+ LIST_FOREACH(port, p, s->ports)
+ if (socket_address_is_netlink(&p->address, value+skip))
+ break;
+
+ if (p) {
+ if (p->fd >= 0)
+ close_nointr_nofail(p->fd);
+ p->fd = fdset_remove(fds, fd);
+ }
+ }
+
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState socket_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[SOCKET(u)->state];
+}
+
+static const char *socket_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return socket_state_to_string(SOCKET(u)->state);
+}
+
+static bool socket_check_gc(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(u);
+
+ return s->n_connections > 0;
+}
+
+static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
+ Socket *s = SOCKET(u);
+ int cfd = -1;
+
+ assert(s);
+ assert(fd >= 0);
+
+ if (s->state != SOCKET_LISTENING)
+ return;
+
+ log_debug("Incoming traffic on %s", u->id);
+
+ if (events != EPOLLIN) {
+
+ if (events & EPOLLHUP)
+ log_error("%s: Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that.", u->id);
+ else
+ log_error("%s: Got unexpected poll event (0x%x) on socket.", u->id, events);
+
+ goto fail;
+ }
+
+ if (w->socket_accept) {
+ for (;;) {
+
+ cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK);
+ if (cfd < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("Failed to accept socket: %m");
+ goto fail;
+ }
+
+ break;
+ }
+
+ socket_apply_socket_options(s, cfd);
+ }
+
+ socket_enter_running(s, cfd);
+ return;
+
+fail:
+ socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
+}
+
+static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Socket *s = SOCKET(u);
+ SocketResult f;
+
+ assert(s);
+ assert(pid >= 0);
+
+ if (pid != s->control_pid)
+ return;
+
+ s->control_pid = 0;
+
+ if (is_clean_exit(code, status, NULL))
+ f = SOCKET_SUCCESS;
+ else if (code == CLD_EXITED)
+ f = SOCKET_FAILURE_EXIT_CODE;
+ else if (code == CLD_KILLED)
+ f = SOCKET_FAILURE_SIGNAL;
+ else if (code == CLD_DUMPED)
+ f = SOCKET_FAILURE_CORE_DUMP;
+ else
+ assert_not_reached("Unknown code");
+
+ if (s->control_command) {
+ exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status);
+
+ if (s->control_command->ignore)
+ f = SOCKET_SUCCESS;
+ }
+
+ log_full(f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE,
+ "%s control process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status);
+
+ if (f != SOCKET_SUCCESS)
+ s->result = f;
+
+ if (s->control_command &&
+ s->control_command->command_next &&
+ f == SOCKET_SUCCESS) {
+
+ log_debug("%s running next command for state %s", u->id, socket_state_to_string(s->state));
+ socket_run_next(s);
+ } else {
+ s->control_command = NULL;
+ s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID;
+
+ /* No further commands for this step, so let's figure
+ * out what to do next */
+
+ log_debug("%s got final SIGCHLD for state %s", u->id, socket_state_to_string(s->state));
+
+ switch (s->state) {
+
+ case SOCKET_START_PRE:
+ if (f == SOCKET_SUCCESS)
+ socket_enter_start_post(s);
+ else
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, f);
+ break;
+
+ case SOCKET_START_POST:
+ if (f == SOCKET_SUCCESS)
+ socket_enter_listening(s);
+ else
+ socket_enter_stop_pre(s, f);
+ break;
+
+ case SOCKET_STOP_PRE:
+ case SOCKET_STOP_PRE_SIGTERM:
+ case SOCKET_STOP_PRE_SIGKILL:
+ socket_enter_stop_post(s, f);
+ break;
+
+ case SOCKET_STOP_POST:
+ case SOCKET_FINAL_SIGTERM:
+ case SOCKET_FINAL_SIGKILL:
+ socket_enter_dead(s, f);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+ }
+
+ /* Notify clients about changed exit status */
+ unit_add_to_dbus_queue(u);
+}
+
+static void socket_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
+ Socket *s = SOCKET(u);
+
+ assert(s);
+ assert(elapsed == 1);
+ assert(w == &s->timer_watch);
+
+ switch (s->state) {
+
+ case SOCKET_START_PRE:
+ log_warning("%s starting timed out. Terminating.", u->id);
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT);
+ break;
+
+ case SOCKET_START_POST:
+ log_warning("%s starting timed out. Stopping.", u->id);
+ socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT);
+ break;
+
+ case SOCKET_STOP_PRE:
+ log_warning("%s stopping timed out. Terminating.", u->id);
+ socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT);
+ break;
+
+ case SOCKET_STOP_PRE_SIGTERM:
+ if (s->kill_context.send_sigkill) {
+ log_warning("%s stopping timed out. Killing.", u->id);
+ socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s stopping timed out. Skipping SIGKILL. Ignoring.", u->id);
+ socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case SOCKET_STOP_PRE_SIGKILL:
+ log_warning("%s still around after SIGKILL. Ignoring.", u->id);
+ socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT);
+ break;
+
+ case SOCKET_STOP_POST:
+ log_warning("%s stopping timed out (2). Terminating.", u->id);
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT);
+ break;
+
+ case SOCKET_FINAL_SIGTERM:
+ if (s->kill_context.send_sigkill) {
+ log_warning("%s stopping timed out (2). Killing.", u->id);
+ socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s stopping timed out (2). Skipping SIGKILL. Ignoring.", u->id);
+ socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case SOCKET_FINAL_SIGKILL:
+ log_warning("%s still around after SIGKILL (2). Entering failed mode.", u->id);
+ socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT);
+ break;
+
+ default:
+ assert_not_reached("Timeout at wrong time.");
+ }
+}
+
+int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds) {
+ int *rfds;
+ unsigned rn_fds, k;
+ SocketPort *p;
+
+ assert(s);
+ assert(fds);
+ assert(n_fds);
+
+ /* Called from the service code for requesting our fds */
+
+ rn_fds = 0;
+ LIST_FOREACH(port, p, s->ports)
+ if (p->fd >= 0)
+ rn_fds++;
+
+ if (rn_fds <= 0) {
+ *fds = NULL;
+ *n_fds = 0;
+ return 0;
+ }
+
+ if (!(rfds = new(int, rn_fds)))
+ return -ENOMEM;
+
+ k = 0;
+ LIST_FOREACH(port, p, s->ports)
+ if (p->fd >= 0)
+ rfds[k++] = p->fd;
+
+ assert(k == rn_fds);
+
+ *fds = rfds;
+ *n_fds = rn_fds;
+
+ return 0;
+}
+
+void socket_notify_service_dead(Socket *s, bool failed_permanent) {
+ assert(s);
+
+ /* The service is dead. Dang!
+ *
+ * This is strictly for one-instance-for-all-connections
+ * services. */
+
+ if (s->state == SOCKET_RUNNING) {
+ log_debug("%s got notified about service death (failed permanently: %s)", UNIT(s)->id, yes_no(failed_permanent));
+ if (failed_permanent)
+ socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_FAILED_PERMANENT);
+ else
+ socket_enter_listening(s);
+ }
+}
+
+void socket_connection_unref(Socket *s) {
+ assert(s);
+
+ /* The service is dead. Yay!
+ *
+ * This is strictly for one-instance-per-connection
+ * services. */
+
+ assert(s->n_connections > 0);
+ s->n_connections--;
+
+ log_debug("%s: One connection closed, %u left.", UNIT(s)->id, s->n_connections);
+}
+
+static void socket_reset_failed(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(s);
+
+ if (s->state == SOCKET_FAILED)
+ socket_set_state(s, SOCKET_DEAD);
+
+ s->result = SOCKET_SUCCESS;
+}
+
+static int socket_kill(Unit *u, KillWho who, int signo, DBusError *error) {
+ Socket *s = SOCKET(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Socket units have no main processes");
+ return -ESRCH;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ESRCH;
+ }
+
+ if (who == KILL_CONTROL || who == KILL_ALL)
+ if (s->control_pid > 0)
+ if (kill(s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (who == KILL_ALL) {
+ int q;
+
+ pid_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!pid_set)
+ return -ENOMEM;
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (s->control_pid > 0) {
+ q = set_put(pid_set, LONG_TO_PTR(s->control_pid));
+ if (q < 0) {
+ r = q;
+ goto finish;
+ }
+ }
+
+ q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, false, pid_set, NULL);
+ if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
+static const char* const socket_state_table[_SOCKET_STATE_MAX] = {
+ [SOCKET_DEAD] = "dead",
+ [SOCKET_START_PRE] = "start-pre",
+ [SOCKET_START_POST] = "start-post",
+ [SOCKET_LISTENING] = "listening",
+ [SOCKET_RUNNING] = "running",
+ [SOCKET_STOP_PRE] = "stop-pre",
+ [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm",
+ [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill",
+ [SOCKET_STOP_POST] = "stop-post",
+ [SOCKET_FINAL_SIGTERM] = "final-sigterm",
+ [SOCKET_FINAL_SIGKILL] = "final-sigkill",
+ [SOCKET_FAILED] = "failed"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState);
+
+static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = {
+ [SOCKET_EXEC_START_PRE] = "StartPre",
+ [SOCKET_EXEC_START_POST] = "StartPost",
+ [SOCKET_EXEC_STOP_PRE] = "StopPre",
+ [SOCKET_EXEC_STOP_POST] = "StopPost"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand);
+
+static const char* const socket_result_table[_SOCKET_RESULT_MAX] = {
+ [SOCKET_SUCCESS] = "success",
+ [SOCKET_FAILURE_RESOURCES] = "resources",
+ [SOCKET_FAILURE_TIMEOUT] = "timeout",
+ [SOCKET_FAILURE_EXIT_CODE] = "exit-code",
+ [SOCKET_FAILURE_SIGNAL] = "signal",
+ [SOCKET_FAILURE_CORE_DUMP] = "core-dump",
+ [SOCKET_FAILURE_SERVICE_FAILED_PERMANENT] = "service-failed-permanent"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult);
+
+const UnitVTable socket_vtable = {
+ .object_size = sizeof(Socket),
+ .exec_context_offset = offsetof(Socket, exec_context),
+
+ .sections =
+ "Unit\0"
+ "Socket\0"
+ "Install\0",
+
+ .init = socket_init,
+ .done = socket_done,
+ .load = socket_load,
+
+ .kill = socket_kill,
+
+ .coldplug = socket_coldplug,
+
+ .dump = socket_dump,
+
+ .start = socket_start,
+ .stop = socket_stop,
+
+ .serialize = socket_serialize,
+ .deserialize_item = socket_deserialize_item,
+
+ .active_state = socket_active_state,
+ .sub_state_to_string = socket_sub_state_to_string,
+
+ .check_gc = socket_check_gc,
+
+ .fd_event = socket_fd_event,
+ .sigchld_event = socket_sigchld_event,
+ .timer_event = socket_timer_event,
+
+ .reset_failed = socket_reset_failed,
+
+ .bus_interface = "org.freedesktop.systemd1.Socket",
+ .bus_message_handler = bus_socket_message_handler,
+ .bus_invalidating_properties = bus_socket_invalidating_properties,
+
+ .status_message_formats = {
+ /*.starting_stopping = {
+ [0] = "Starting socket %s...",
+ [1] = "Stopping socket %s...",
+ },*/
+ .finished_start_job = {
+ [JOB_DONE] = "Listening on %s.",
+ [JOB_FAILED] = "Failed to listen on %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ [JOB_TIMEOUT] = "Timed out starting %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Closed %s.",
+ [JOB_FAILED] = "Failed stopping %s.",
+ [JOB_TIMEOUT] = "Timed out stopping %s.",
+ },
+ },
+};
diff --git a/src/core/socket.h b/src/core/socket.h
new file mode 100644
index 0000000000..f099520dce
--- /dev/null
+++ b/src/core/socket.h
@@ -0,0 +1,175 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Socket Socket;
+
+#include "manager.h"
+#include "unit.h"
+#include "socket-util.h"
+#include "mount.h"
+#include "service.h"
+
+typedef enum SocketState {
+ SOCKET_DEAD,
+ SOCKET_START_PRE,
+ SOCKET_START_POST,
+ SOCKET_LISTENING,
+ SOCKET_RUNNING,
+ SOCKET_STOP_PRE,
+ SOCKET_STOP_PRE_SIGTERM,
+ SOCKET_STOP_PRE_SIGKILL,
+ SOCKET_STOP_POST,
+ SOCKET_FINAL_SIGTERM,
+ SOCKET_FINAL_SIGKILL,
+ SOCKET_FAILED,
+ _SOCKET_STATE_MAX,
+ _SOCKET_STATE_INVALID = -1
+} SocketState;
+
+typedef enum SocketExecCommand {
+ SOCKET_EXEC_START_PRE,
+ SOCKET_EXEC_START_POST,
+ SOCKET_EXEC_STOP_PRE,
+ SOCKET_EXEC_STOP_POST,
+ _SOCKET_EXEC_COMMAND_MAX,
+ _SOCKET_EXEC_COMMAND_INVALID = -1
+} SocketExecCommand;
+
+typedef enum SocketType {
+ SOCKET_SOCKET,
+ SOCKET_FIFO,
+ SOCKET_SPECIAL,
+ SOCKET_MQUEUE,
+ _SOCKET_FIFO_MAX,
+ _SOCKET_FIFO_INVALID = -1
+} SocketType;
+
+typedef enum SocketResult {
+ SOCKET_SUCCESS,
+ SOCKET_FAILURE_RESOURCES,
+ SOCKET_FAILURE_TIMEOUT,
+ SOCKET_FAILURE_EXIT_CODE,
+ SOCKET_FAILURE_SIGNAL,
+ SOCKET_FAILURE_CORE_DUMP,
+ SOCKET_FAILURE_SERVICE_FAILED_PERMANENT,
+ _SOCKET_RESULT_MAX,
+ _SOCKET_RESULT_INVALID = -1
+} SocketResult;
+
+typedef struct SocketPort {
+ SocketType type;
+ int fd;
+
+ SocketAddress address;
+ char *path;
+ Watch fd_watch;
+
+ LIST_FIELDS(struct SocketPort, port);
+} SocketPort;
+
+struct Socket {
+ Unit meta;
+
+ LIST_HEAD(SocketPort, ports);
+
+ unsigned n_accepted;
+ unsigned n_connections;
+ unsigned max_connections;
+
+ unsigned backlog;
+ usec_t timeout_usec;
+
+ ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX];
+ ExecContext exec_context;
+ KillContext kill_context;
+
+ /* For Accept=no sockets refers to the one service we'll
+ activate. For Accept=yes sockets is either NULL, or filled
+ when the next service we spawn. */
+ UnitRef service;
+
+ SocketState state, deserialized_state;
+
+ Watch timer_watch;
+
+ ExecCommand* control_command;
+ SocketExecCommand control_command_id;
+ pid_t control_pid;
+
+ mode_t directory_mode;
+ mode_t socket_mode;
+
+ SocketResult result;
+
+ bool accept;
+
+ /* Socket options */
+ bool keep_alive;
+ bool free_bind;
+ bool transparent;
+ bool broadcast;
+ bool pass_cred;
+ bool pass_sec;
+ int priority;
+ int mark;
+ size_t receive_buffer;
+ size_t send_buffer;
+ int ip_tos;
+ int ip_ttl;
+ size_t pipe_size;
+ char *bind_to_device;
+ char *tcp_congestion;
+ long mq_maxmsg;
+ long mq_msgsize;
+
+ /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */
+ SocketAddressBindIPv6Only bind_ipv6_only;
+
+ char *smack;
+ char *smack_ip_in;
+ char *smack_ip_out;
+};
+
+/* Called from the service code when collecting fds */
+int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds);
+
+/* Called from the service when it shut down */
+void socket_notify_service_dead(Socket *s, bool failed_permanent);
+
+/* Called from the mount code figure out if a mount is a dependency of
+ * any of the sockets of this socket */
+int socket_add_one_mount_link(Socket *s, Mount *m);
+
+/* Called from the service code when a per-connection service ended */
+void socket_connection_unref(Socket *s);
+
+extern const UnitVTable socket_vtable;
+
+const char* socket_state_to_string(SocketState i);
+SocketState socket_state_from_string(const char *s);
+
+const char* socket_exec_command_to_string(SocketExecCommand i);
+SocketExecCommand socket_exec_command_from_string(const char *s);
+
+const char* socket_result_to_string(SocketResult i);
+SocketResult socket_result_from_string(const char *s);
diff --git a/src/core/special.h b/src/core/special.h
new file mode 100644
index 0000000000..ef72260ecd
--- /dev/null
+++ b/src/core/special.h
@@ -0,0 +1,113 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#define SPECIAL_DEFAULT_TARGET "default.target"
+
+/* Shutdown targets */
+#define SPECIAL_UMOUNT_TARGET "umount.target"
+/* This is not really intended to be started by directly. This is
+ * mostly so that other targets (reboot/halt/poweroff) can depend on
+ * it to bring all services down that want to be brought down on
+ * system shutdown. */
+#define SPECIAL_SHUTDOWN_TARGET "shutdown.target"
+#define SPECIAL_HALT_TARGET "halt.target"
+#define SPECIAL_POWEROFF_TARGET "poweroff.target"
+#define SPECIAL_REBOOT_TARGET "reboot.target"
+#define SPECIAL_KEXEC_TARGET "kexec.target"
+#define SPECIAL_EXIT_TARGET "exit.target"
+#define SPECIAL_SUSPEND_TARGET "suspend.target"
+#define SPECIAL_HIBERNATE_TARGET "hibernate.target"
+#define SPECIAL_HYBRID_SLEEP_TARGET "hybrid-sleep.target"
+
+/* Special boot targets */
+#define SPECIAL_RESCUE_TARGET "rescue.target"
+#define SPECIAL_EMERGENCY_TARGET "emergency.target"
+
+/* Early boot targets */
+#define SPECIAL_SYSINIT_TARGET "sysinit.target"
+#define SPECIAL_SOCKETS_TARGET "sockets.target"
+#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" /* LSB's $local_fs */
+#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target"
+#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */
+#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target"
+#define SPECIAL_SWAP_TARGET "swap.target"
+#define SPECIAL_BASIC_TARGET "basic.target"
+
+/* LSB compatibility */
+#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */
+#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */
+#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */
+#define SPECIAL_SYSLOG_TARGET "syslog.target" /* LSB's $syslog */
+#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */
+#define SPECIAL_DISPLAY_MANAGER_SERVICE "display-manager.service" /* Common extension of LSB */
+#define SPECIAL_MAIL_TRANSFER_AGENT_TARGET "mail-transfer-agent.target" /* Common extension of LSB */
+
+/*
+ * Rules regarding adding further high level targets like the above:
+ *
+ * - Be conservative, only add more of these when we really need
+ * them. We need strong usecases for further additions.
+ *
+ * - When there can be multiple implementations running side-by-side,
+ * it needs to be a .target unit which can pull in all
+ * implementations.
+ *
+ * - If something can be implemented with socket activation, and
+ * without, it needs to be a .target unit, so that it can pull in
+ * the appropriate unit.
+ *
+ * - Otherwise, it should be a .service unit.
+ *
+ * - In some cases it is OK to have both a .service and a .target
+ * unit, i.e. if there can be multiple parallel implementations, but
+ * only one is the "system" one. Example: syslog.
+ *
+ * Or to put this in other words: .service symlinks can be used to
+ * arbitrate between multiple implementations if there can be only one
+ * of a kind. .target units can be used to support multiple
+ * implementations that can run side-by-side.
+ */
+
+/* Magic early boot services */
+#define SPECIAL_FSCK_SERVICE "systemd-fsck@.service"
+#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck.service"
+#define SPECIAL_QUOTAON_SERVICE "quotaon.service"
+#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service"
+
+/* Services systemd relies on */
+#define SPECIAL_DBUS_SERVICE "dbus.service"
+#define SPECIAL_DBUS_SOCKET "dbus.socket"
+#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket"
+#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service"
+
+/* Magic init signals */
+#define SPECIAL_KBREQUEST_TARGET "kbrequest.target"
+#define SPECIAL_SIGPWR_TARGET "sigpwr.target"
+#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target"
+
+/* For SysV compatibility. Usually an alias for a saner target. On
+ * SysV-free systems this doesn't exist. */
+#define SPECIAL_RUNLEVEL2_TARGET "runlevel2.target"
+#define SPECIAL_RUNLEVEL3_TARGET "runlevel3.target"
+#define SPECIAL_RUNLEVEL4_TARGET "runlevel4.target"
+#define SPECIAL_RUNLEVEL5_TARGET "runlevel5.target"
diff --git a/src/core/swap.c b/src/core/swap.c
new file mode 100644
index 0000000000..97145a9974
--- /dev/null
+++ b/src/core/swap.c
@@ -0,0 +1,1433 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/swap.h>
+#include <libudev.h>
+
+#include "unit.h"
+#include "swap.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "unit-name.h"
+#include "dbus-swap.h"
+#include "special.h"
+#include "bus-errors.h"
+#include "exit-status.h"
+#include "def.h"
+#include "path-util.h"
+#include "virt.h"
+
+static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = {
+ [SWAP_DEAD] = UNIT_INACTIVE,
+ [SWAP_ACTIVATING] = UNIT_ACTIVATING,
+ [SWAP_ACTIVE] = UNIT_ACTIVE,
+ [SWAP_DEACTIVATING] = UNIT_DEACTIVATING,
+ [SWAP_ACTIVATING_SIGTERM] = UNIT_DEACTIVATING,
+ [SWAP_ACTIVATING_SIGKILL] = UNIT_DEACTIVATING,
+ [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING,
+ [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING,
+ [SWAP_FAILED] = UNIT_FAILED
+};
+
+static void swap_unset_proc_swaps(Swap *s) {
+ Swap *first;
+ Hashmap *swaps;
+
+ assert(s);
+
+ if (!s->parameters_proc_swaps.what)
+ return;
+
+ /* Remove this unit from the chain of swaps which share the
+ * same kernel swap device. */
+ swaps = UNIT(s)->manager->swaps_by_proc_swaps;
+ first = hashmap_get(swaps, s->parameters_proc_swaps.what);
+ LIST_REMOVE(Swap, same_proc_swaps, first, s);
+
+ if (first)
+ hashmap_remove_and_replace(swaps,
+ s->parameters_proc_swaps.what,
+ first->parameters_proc_swaps.what,
+ first);
+ else
+ hashmap_remove(swaps, s->parameters_proc_swaps.what);
+
+ free(s->parameters_proc_swaps.what);
+ s->parameters_proc_swaps.what = NULL;
+}
+
+static void swap_init(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(UNIT(s)->load_state == UNIT_STUB);
+
+ s->timeout_usec = DEFAULT_TIMEOUT_USEC;
+
+ exec_context_init(&s->exec_context);
+ s->exec_context.std_output = u->manager->default_std_output;
+ s->exec_context.std_error = u->manager->default_std_error;
+ kill_context_init(&s->kill_context);
+
+ s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1;
+
+ s->timer_watch.type = WATCH_INVALID;
+
+ s->control_command_id = _SWAP_EXEC_COMMAND_INVALID;
+
+ UNIT(s)->ignore_on_isolate = true;
+}
+
+static void swap_unwatch_control_pid(Swap *s) {
+ assert(s);
+
+ if (s->control_pid <= 0)
+ return;
+
+ unit_unwatch_pid(UNIT(s), s->control_pid);
+ s->control_pid = 0;
+}
+
+static void swap_done(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+
+ swap_unset_proc_swaps(s);
+
+ free(s->what);
+ s->what = NULL;
+
+ free(s->parameters_fragment.what);
+ s->parameters_fragment.what = NULL;
+
+ exec_context_done(&s->exec_context);
+ exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX);
+ s->control_command = NULL;
+
+ swap_unwatch_control_pid(s);
+
+ unit_unwatch_timer(u, &s->timer_watch);
+}
+
+int swap_add_one_mount_link(Swap *s, Mount *m) {
+ int r;
+
+ assert(s);
+ assert(m);
+
+ if (UNIT(s)->load_state != UNIT_LOADED ||
+ UNIT(m)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (is_device_path(s->what))
+ return 0;
+
+ if (!path_startswith(s->what, m->where))
+ return 0;
+
+ r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int swap_add_mount_links(Swap *s) {
+ Unit *other;
+ int r;
+
+ assert(s);
+
+ LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT])
+ if ((r = swap_add_one_mount_link(s, MOUNT(other))) < 0)
+ return r;
+
+ return 0;
+}
+
+static int swap_add_device_links(Swap *s) {
+ SwapParameters *p;
+
+ assert(s);
+
+ if (!s->what)
+ return 0;
+
+ if (s->from_fragment)
+ p = &s->parameters_fragment;
+ else
+ return 0;
+
+ if (is_device_path(s->what))
+ return unit_add_node_link(UNIT(s), s->what,
+ !p->noauto && p->nofail &&
+ UNIT(s)->manager->running_as == SYSTEMD_SYSTEM);
+ else
+ /* File based swap devices need to be ordered after
+ * systemd-remount-fs.service, since they might need a
+ * writable file system. */
+ return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, NULL, true);
+}
+
+static int swap_add_default_dependencies(Swap *s) {
+ int r;
+
+ assert(s);
+
+ if (UNIT(s)->manager->running_as != SYSTEMD_SYSTEM)
+ return 0;
+
+ if (detect_container(NULL) > 0)
+ return 0;
+
+ r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int swap_verify(Swap *s) {
+ bool b;
+ char _cleanup_free_ *e = NULL;
+
+ if (UNIT(s)->load_state != UNIT_LOADED)
+ return 0;
+
+ e = unit_name_from_path(s->what, ".swap");
+ if (e == NULL)
+ return log_oom();
+
+ b = unit_has_name(UNIT(s), e);
+ if (!b) {
+ log_error("%s: Value of \"What\" and unit name do not match, not loading.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) {
+ log_error("%s has PAM enabled. Kill mode must be set to 'control-group'. Refusing to load.", UNIT(s)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int swap_load(Unit *u) {
+ int r;
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(u->load_state == UNIT_STUB);
+
+ /* Load a .swap file */
+ r = unit_load_fragment_and_dropin_optional(u);
+ if (r < 0)
+ return r;
+
+ if (u->load_state == UNIT_LOADED) {
+ r = unit_add_exec_dependencies(u, &s->exec_context);
+ if (r < 0)
+ return r;
+
+ if (UNIT(s)->fragment_path)
+ s->from_fragment = true;
+
+ if (!s->what) {
+ if (s->parameters_fragment.what)
+ s->what = strdup(s->parameters_fragment.what);
+ else if (s->parameters_proc_swaps.what)
+ s->what = strdup(s->parameters_proc_swaps.what);
+ else
+ s->what = unit_name_to_path(u->id);
+
+ if (!s->what)
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(s->what);
+
+ if (!UNIT(s)->description)
+ if ((r = unit_set_description(u, s->what)) < 0)
+ return r;
+
+ r = swap_add_device_links(s);
+ if (r < 0)
+ return r;
+
+ r = swap_add_mount_links(s);
+ if (r < 0)
+ return r;
+
+ r = unit_add_default_cgroups(u);
+ if (r < 0)
+ return r;
+
+ if (UNIT(s)->default_dependencies) {
+ r = swap_add_default_dependencies(s);
+ if (r < 0)
+ return r;
+ }
+
+ r = unit_exec_context_defaults(u, &s->exec_context);
+ if (r < 0)
+ return r;
+ }
+
+ return swap_verify(s);
+}
+
+static int swap_add_one(
+ Manager *m,
+ const char *what,
+ const char *what_proc_swaps,
+ int priority,
+ bool noauto,
+ bool nofail,
+ bool set_flags) {
+
+ Unit *u = NULL;
+ char _cleanup_free_ *e = NULL;
+ char *wp = NULL;
+ bool delete = false;
+ int r;
+ SwapParameters *p;
+ Swap *first;
+
+ assert(m);
+ assert(what);
+ assert(what_proc_swaps);
+
+ e = unit_name_from_path(what, ".swap");
+ if (!e)
+ return log_oom();
+
+ u = manager_get_unit(m, e);
+
+ if (u &&
+ SWAP(u)->from_proc_swaps &&
+ !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps))
+ return -EEXIST;
+
+ if (!u) {
+ delete = true;
+
+ u = unit_new(m, sizeof(Swap));
+ if (!u)
+ return log_oom();
+
+ r = unit_add_name(u, e);
+ if (r < 0)
+ goto fail;
+
+ SWAP(u)->what = strdup(what);
+ if (!SWAP(u)->what) {
+ r = log_oom();
+ goto fail;
+ }
+
+ unit_add_to_load_queue(u);
+ } else
+ delete = false;
+
+ p = &SWAP(u)->parameters_proc_swaps;
+
+ if (!p->what) {
+ wp = strdup(what_proc_swaps);
+ if (!wp) {
+ r = log_oom();
+ goto fail;
+ }
+
+ if (!m->swaps_by_proc_swaps) {
+ m->swaps_by_proc_swaps = hashmap_new(string_hash_func, string_compare_func);
+ if (!m->swaps_by_proc_swaps) {
+ r = log_oom();
+ goto fail;
+ }
+ }
+
+ free(p->what);
+ p->what = wp;
+
+ first = hashmap_get(m->swaps_by_proc_swaps, wp);
+ LIST_PREPEND(Swap, same_proc_swaps, first, SWAP(u));
+
+ r = hashmap_replace(m->swaps_by_proc_swaps, wp, first);
+ if (r < 0)
+ goto fail;
+ }
+
+ if (set_flags) {
+ SWAP(u)->is_active = true;
+ SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps;
+ }
+
+ SWAP(u)->from_proc_swaps = true;
+
+ p->priority = priority;
+ p->noauto = noauto;
+ p->nofail = nofail;
+
+ unit_add_to_dbus_queue(u);
+
+ return 0;
+
+fail:
+ log_warning("Failed to load swap unit: %s", strerror(-r));
+
+ free(wp);
+
+ if (delete && u)
+ unit_free(u);
+
+ return r;
+}
+
+static int swap_process_new_swap(Manager *m, const char *device, int prio, bool set_flags) {
+ struct stat st;
+ int r = 0, k;
+
+ assert(m);
+
+ if (stat(device, &st) >= 0 && S_ISBLK(st.st_mode)) {
+ struct udev_device *d;
+ const char *dn;
+ struct udev_list_entry *item = NULL, *first = NULL;
+
+ /* So this is a proper swap device. Create swap units
+ * for all names this swap device is known under */
+
+ d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev);
+ if (!d)
+ return log_oom();
+
+ dn = udev_device_get_devnode(d);
+ /* Skip dn==device, since that case will be handled below */
+ if (dn && !streq(dn, device))
+ r = swap_add_one(m, dn, device, prio, false, false, set_flags);
+
+ /* Add additional units for all symlinks */
+ first = udev_device_get_devlinks_list_entry(d);
+ udev_list_entry_foreach(item, first) {
+ const char *p;
+
+ /* Don't bother with the /dev/block links */
+ p = udev_list_entry_get_name(item);
+
+ if (path_startswith(p, "/dev/block/"))
+ continue;
+
+ if (stat(p, &st) >= 0)
+ if ((!S_ISBLK(st.st_mode)) ||
+ st.st_rdev != udev_device_get_devnum(d))
+ continue;
+
+ k = swap_add_one(m, p, device, prio, false, false, set_flags);
+ if (k < 0)
+ r = k;
+ }
+
+ udev_device_unref(d);
+ }
+
+ k = swap_add_one(m, device, device, prio, false, false, set_flags);
+ if (k < 0)
+ r = k;
+
+ return r;
+}
+
+static void swap_set_state(Swap *s, SwapState state) {
+ SwapState old_state;
+
+ assert(s);
+
+ old_state = s->state;
+ s->state = state;
+
+ if (state != SWAP_ACTIVATING &&
+ state != SWAP_ACTIVATING_SIGTERM &&
+ state != SWAP_ACTIVATING_SIGKILL &&
+ state != SWAP_DEACTIVATING &&
+ state != SWAP_DEACTIVATING_SIGTERM &&
+ state != SWAP_DEACTIVATING_SIGKILL) {
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+ swap_unwatch_control_pid(s);
+ s->control_command = NULL;
+ s->control_command_id = _SWAP_EXEC_COMMAND_INVALID;
+ }
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(s)->id,
+ swap_state_to_string(old_state),
+ swap_state_to_string(state));
+
+ unit_notify(UNIT(s), state_translation_table[old_state],
+ state_translation_table[state], true);
+}
+
+static int swap_coldplug(Unit *u) {
+ Swap *s = SWAP(u);
+ SwapState new_state = SWAP_DEAD;
+ int r;
+
+ assert(s);
+ assert(s->state == SWAP_DEAD);
+
+ if (s->deserialized_state != s->state)
+ new_state = s->deserialized_state;
+ else if (s->from_proc_swaps)
+ new_state = SWAP_ACTIVE;
+
+ if (new_state != s->state) {
+
+ if (new_state == SWAP_ACTIVATING ||
+ new_state == SWAP_ACTIVATING_SIGTERM ||
+ new_state == SWAP_ACTIVATING_SIGKILL ||
+ new_state == SWAP_DEACTIVATING ||
+ new_state == SWAP_DEACTIVATING_SIGTERM ||
+ new_state == SWAP_DEACTIVATING_SIGKILL) {
+
+ if (s->control_pid <= 0)
+ return -EBADMSG;
+
+ r = unit_watch_pid(UNIT(s), s->control_pid);
+ if (r < 0)
+ return r;
+
+ r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch);
+ if (r < 0)
+ return r;
+ }
+
+ swap_set_state(s, new_state);
+ }
+
+ return 0;
+}
+
+static void swap_dump(Unit *u, FILE *f, const char *prefix) {
+ Swap *s = SWAP(u);
+ SwapParameters *p;
+
+ assert(s);
+ assert(f);
+
+ if (s->from_proc_swaps)
+ p = &s->parameters_proc_swaps;
+ else if (s->from_fragment)
+ p = &s->parameters_fragment;
+ else
+ p = NULL;
+
+ fprintf(f,
+ "%sSwap State: %s\n"
+ "%sResult: %s\n"
+ "%sWhat: %s\n"
+ "%sFrom /proc/swaps: %s\n"
+ "%sFrom fragment: %s\n",
+ prefix, swap_state_to_string(s->state),
+ prefix, swap_result_to_string(s->result),
+ prefix, s->what,
+ prefix, yes_no(s->from_proc_swaps),
+ prefix, yes_no(s->from_fragment));
+
+ if (p)
+ fprintf(f,
+ "%sPriority: %i\n"
+ "%sNoAuto: %s\n"
+ "%sNoFail: %s\n",
+ prefix, p->priority,
+ prefix, yes_no(p->noauto),
+ prefix, yes_no(p->nofail));
+
+ if (s->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %lu\n",
+ prefix, (unsigned long) s->control_pid);
+
+ exec_context_dump(&s->exec_context, f, prefix);
+ kill_context_dump(&s->kill_context, f, prefix);
+}
+
+static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
+ pid_t pid;
+ int r;
+
+ assert(s);
+ assert(c);
+ assert(_pid);
+
+ r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch);
+ if (r < 0)
+ goto fail;
+
+ r = exec_spawn(c,
+ NULL,
+ &s->exec_context,
+ NULL, 0,
+ UNIT(s)->manager->environment,
+ true,
+ true,
+ true,
+ UNIT(s)->manager->confirm_spawn,
+ UNIT(s)->cgroup_bondings,
+ UNIT(s)->cgroup_attributes,
+ NULL,
+ UNIT(s)->id,
+ NULL,
+ &pid);
+ if (r < 0)
+ goto fail;
+
+ r = unit_watch_pid(UNIT(s), pid);
+ if (r < 0)
+ /* FIXME: we need to do something here */
+ goto fail;
+
+ *_pid = pid;
+
+ return 0;
+
+fail:
+ unit_unwatch_timer(UNIT(s), &s->timer_watch);
+
+ return r;
+}
+
+static void swap_enter_dead(Swap *s, SwapResult f) {
+ assert(s);
+
+ if (f != SWAP_SUCCESS)
+ s->result = f;
+
+ swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
+}
+
+static void swap_enter_active(Swap *s, SwapResult f) {
+ assert(s);
+
+ if (f != SWAP_SUCCESS)
+ s->result = f;
+
+ swap_set_state(s, SWAP_ACTIVE);
+}
+
+static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) {
+ int r;
+ Set *pid_set = NULL;
+ bool wait_for_exit = false;
+
+ assert(s);
+
+ if (f != SWAP_SUCCESS)
+ s->result = f;
+
+ if (s->kill_context.kill_mode != KILL_NONE) {
+ int sig = (state == SWAP_ACTIVATING_SIGTERM ||
+ state == SWAP_DEACTIVATING_SIGTERM) ? s->kill_context.kill_signal : SIGKILL;
+
+ if (s->control_pid > 0) {
+ if (kill_and_sigcont(s->control_pid, sig) < 0 && errno != ESRCH)
+
+ log_warning("Failed to kill control process %li: %m", (long) s->control_pid);
+ else
+ wait_for_exit = true;
+ }
+
+ if (s->kill_context.kill_mode == KILL_CONTROL_GROUP) {
+
+ pid_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!pid_set) {
+ r = log_oom();
+ goto fail;
+ }
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (s->control_pid > 0) {
+ r = set_put(pid_set, LONG_TO_PTR(s->control_pid));
+ if (r < 0)
+ goto fail;
+ }
+
+ r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, false, pid_set, NULL);
+ if (r < 0) {
+ if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
+ log_warning("Failed to kill control group: %s", strerror(-r));
+ } else if (r > 0)
+ wait_for_exit = true;
+
+ set_free(pid_set);
+ pid_set = NULL;
+ }
+ }
+
+ if (wait_for_exit) {
+ r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch);
+ if (r < 0)
+ goto fail;
+
+ swap_set_state(s, state);
+ } else
+ swap_enter_dead(s, SWAP_SUCCESS);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", UNIT(s)->id, strerror(-r));
+
+ swap_enter_dead(s, SWAP_FAILURE_RESOURCES);
+
+ if (pid_set)
+ set_free(pid_set);
+}
+
+static void swap_enter_activating(Swap *s) {
+ int r, priority;
+
+ assert(s);
+
+ s->control_command_id = SWAP_EXEC_ACTIVATE;
+ s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE;
+
+ if (s->from_fragment)
+ priority = s->parameters_fragment.priority;
+ else
+ priority = -1;
+
+ if (priority >= 0) {
+ char p[LINE_MAX];
+
+ snprintf(p, sizeof(p), "%i", priority);
+ char_array_0(p);
+
+ r = exec_command_set(
+ s->control_command,
+ "/sbin/swapon",
+ "-p",
+ p,
+ s->what,
+ NULL);
+ } else
+ r = exec_command_set(
+ s->control_command,
+ "/sbin/swapon",
+ s->what,
+ NULL);
+
+ if (r < 0)
+ goto fail;
+
+ swap_unwatch_control_pid(s);
+
+ r = swap_spawn(s, s->control_command, &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ swap_set_state(s, SWAP_ACTIVATING);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'swapon' task: %s", UNIT(s)->id, strerror(-r));
+ swap_enter_dead(s, SWAP_FAILURE_RESOURCES);
+}
+
+static void swap_enter_deactivating(Swap *s) {
+ int r;
+
+ assert(s);
+
+ s->control_command_id = SWAP_EXEC_DEACTIVATE;
+ s->control_command = s->exec_command + SWAP_EXEC_DEACTIVATE;
+
+ r = exec_command_set(s->control_command,
+ "/sbin/swapoff",
+ s->what,
+ NULL);
+ if (r < 0)
+ goto fail;
+
+ swap_unwatch_control_pid(s);
+
+ r = swap_spawn(s, s->control_command, &s->control_pid);
+ if (r < 0)
+ goto fail;
+
+ swap_set_state(s, SWAP_DEACTIVATING);
+
+ return;
+
+fail:
+ log_warning("%s failed to run 'swapoff' task: %s", UNIT(s)->id, strerror(-r));
+ swap_enter_active(s, SWAP_FAILURE_RESOURCES);
+}
+
+static int swap_start(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+
+ /* We cannot fulfill this request right now, try again later
+ * please! */
+
+ if (s->state == SWAP_DEACTIVATING ||
+ s->state == SWAP_DEACTIVATING_SIGTERM ||
+ s->state == SWAP_DEACTIVATING_SIGKILL ||
+ s->state == SWAP_ACTIVATING_SIGTERM ||
+ s->state == SWAP_ACTIVATING_SIGKILL)
+ return -EAGAIN;
+
+ if (s->state == SWAP_ACTIVATING)
+ return 0;
+
+ assert(s->state == SWAP_DEAD || s->state == SWAP_FAILED);
+
+ if (detect_container(NULL) > 0)
+ return -EPERM;
+
+ s->result = SWAP_SUCCESS;
+ swap_enter_activating(s);
+ return 0;
+}
+
+static int swap_stop(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+
+ if (s->state == SWAP_DEACTIVATING ||
+ s->state == SWAP_DEACTIVATING_SIGTERM ||
+ s->state == SWAP_DEACTIVATING_SIGKILL ||
+ s->state == SWAP_ACTIVATING_SIGTERM ||
+ s->state == SWAP_ACTIVATING_SIGKILL)
+ return 0;
+
+ assert(s->state == SWAP_ACTIVATING ||
+ s->state == SWAP_ACTIVE);
+
+ if (detect_container(NULL) > 0)
+ return -EPERM;
+
+ swap_enter_deactivating(s);
+ return 0;
+}
+
+static int swap_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", swap_state_to_string(s->state));
+ unit_serialize_item(u, f, "result", swap_result_to_string(s->result));
+
+ if (s->control_pid > 0)
+ unit_serialize_item_format(u, f, "control-pid", "%lu", (unsigned long) s->control_pid);
+
+ if (s->control_command_id >= 0)
+ unit_serialize_item(u, f, "control-command", swap_exec_command_to_string(s->control_command_id));
+
+ return 0;
+}
+
+static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ SwapState state;
+
+ state = swap_state_from_string(value);
+ if (state < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ s->deserialized_state = state;
+ } else if (streq(key, "result")) {
+ SwapResult f;
+
+ f = swap_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != SWAP_SUCCESS)
+ s->result = f;
+ } else if (streq(key, "control-pid")) {
+ pid_t pid;
+
+ if (parse_pid(value, &pid) < 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ s->control_pid = pid;
+
+ } else if (streq(key, "control-command")) {
+ SwapExecCommand id;
+
+ id = swap_exec_command_from_string(value);
+ if (id < 0)
+ log_debug("Failed to parse exec-command value %s", value);
+ else {
+ s->control_command_id = id;
+ s->control_command = s->exec_command + id;
+ }
+
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState swap_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[SWAP(u)->state];
+}
+
+static const char *swap_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return swap_state_to_string(SWAP(u)->state);
+}
+
+static bool swap_check_gc(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+
+ return s->from_proc_swaps;
+}
+
+static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Swap *s = SWAP(u);
+ SwapResult f;
+
+ assert(s);
+ assert(pid >= 0);
+
+ if (pid != s->control_pid)
+ return;
+
+ s->control_pid = 0;
+
+ if (is_clean_exit(code, status, NULL))
+ f = SWAP_SUCCESS;
+ else if (code == CLD_EXITED)
+ f = SWAP_FAILURE_EXIT_CODE;
+ else if (code == CLD_KILLED)
+ f = SWAP_FAILURE_SIGNAL;
+ else if (code == CLD_DUMPED)
+ f = SWAP_FAILURE_CORE_DUMP;
+ else
+ assert_not_reached("Unknown code");
+
+ if (f != SWAP_SUCCESS)
+ s->result = f;
+
+ if (s->control_command) {
+ exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status);
+
+ s->control_command = NULL;
+ s->control_command_id = _SWAP_EXEC_COMMAND_INVALID;
+ }
+
+ log_full(f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE,
+ "%s swap process exited, code=%s status=%i", u->id, sigchld_code_to_string(code), status);
+
+ switch (s->state) {
+
+ case SWAP_ACTIVATING:
+ case SWAP_ACTIVATING_SIGTERM:
+ case SWAP_ACTIVATING_SIGKILL:
+
+ if (f == SWAP_SUCCESS)
+ swap_enter_active(s, f);
+ else
+ swap_enter_dead(s, f);
+ break;
+
+ case SWAP_DEACTIVATING:
+ case SWAP_DEACTIVATING_SIGKILL:
+ case SWAP_DEACTIVATING_SIGTERM:
+
+ if (f == SWAP_SUCCESS)
+ swap_enter_dead(s, f);
+ else
+ swap_enter_dead(s, f);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+
+ /* Notify clients about changed exit status */
+ unit_add_to_dbus_queue(u);
+
+ /* Request a reload of /proc/swaps, so that following units
+ * can follow our state change */
+ u->manager->request_reload = true;
+}
+
+static void swap_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(elapsed == 1);
+ assert(w == &s->timer_watch);
+
+ switch (s->state) {
+
+ case SWAP_ACTIVATING:
+ log_warning("%s activation timed out. Stopping.", u->id);
+ swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT);
+ break;
+
+ case SWAP_DEACTIVATING:
+ log_warning("%s deactivation timed out. Stopping.", u->id);
+ swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT);
+ break;
+
+ case SWAP_ACTIVATING_SIGTERM:
+ if (s->kill_context.send_sigkill) {
+ log_warning("%s activation timed out. Killing.", u->id);
+ swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s activation timed out. Skipping SIGKILL. Ignoring.", u->id);
+ swap_enter_dead(s, SWAP_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case SWAP_DEACTIVATING_SIGTERM:
+ if (s->kill_context.send_sigkill) {
+ log_warning("%s deactivation timed out. Killing.", u->id);
+ swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT);
+ } else {
+ log_warning("%s deactivation timed out. Skipping SIGKILL. Ignoring.", u->id);
+ swap_enter_dead(s, SWAP_FAILURE_TIMEOUT);
+ }
+ break;
+
+ case SWAP_ACTIVATING_SIGKILL:
+ case SWAP_DEACTIVATING_SIGKILL:
+ log_warning("%s swap process still around after SIGKILL. Ignoring.", u->id);
+ swap_enter_dead(s, SWAP_FAILURE_TIMEOUT);
+ break;
+
+ default:
+ assert_not_reached("Timeout at wrong time.");
+ }
+}
+
+static int swap_load_proc_swaps(Manager *m, bool set_flags) {
+ unsigned i;
+ int r = 0;
+
+ assert(m);
+
+ rewind(m->proc_swaps);
+
+ (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 1;; i++) {
+ char *dev = NULL, *d;
+ int prio = 0, k;
+
+ k = fscanf(m->proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%i\n", /* priority */
+ &dev, &prio);
+ if (k != 2) {
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u", i);
+ free(dev);
+ continue;
+ }
+
+ d = cunescape(dev);
+ free(dev);
+
+ if (!d)
+ return -ENOMEM;
+
+ k = swap_process_new_swap(m, d, prio, set_flags);
+ free(d);
+
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+int swap_dispatch_reload(Manager *m) {
+ /* This function should go as soon as the kernel properly notifies us */
+
+ if (_likely_(!m->request_reload))
+ return 0;
+
+ m->request_reload = false;
+
+ return swap_fd_event(m, EPOLLPRI);
+}
+
+int swap_fd_event(Manager *m, int events) {
+ Unit *u;
+ int r;
+
+ assert(m);
+ assert(events & EPOLLPRI);
+
+ r = swap_load_proc_swaps(m, true);
+ if (r < 0) {
+ log_error("Failed to reread /proc/swaps: %s", strerror(-r));
+
+ /* Reset flags, just in case, for late calls */
+ LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) {
+ Swap *swap = SWAP(u);
+
+ swap->is_active = swap->just_activated = false;
+ }
+
+ return 0;
+ }
+
+ manager_dispatch_load_queue(m);
+
+ LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) {
+ Swap *swap = SWAP(u);
+
+ if (!swap->is_active) {
+ /* This has just been deactivated */
+
+ swap->from_proc_swaps = false;
+ swap_unset_proc_swaps(swap);
+
+ switch (swap->state) {
+
+ case SWAP_ACTIVE:
+ swap_enter_dead(swap, SWAP_SUCCESS);
+ break;
+
+ default:
+ swap_set_state(swap, swap->state);
+ break;
+ }
+
+ } else if (swap->just_activated) {
+
+ /* New swap entry */
+
+ switch (swap->state) {
+
+ case SWAP_DEAD:
+ case SWAP_FAILED:
+ swap_enter_active(swap, SWAP_SUCCESS);
+ break;
+
+ default:
+ /* Nothing really changed, but let's
+ * issue an notification call
+ * nonetheless, in case somebody is
+ * waiting for this. */
+ swap_set_state(swap, swap->state);
+ break;
+ }
+ }
+
+ /* Reset the flags for later calls */
+ swap->is_active = swap->just_activated = false;
+ }
+
+ return 1;
+}
+
+static Unit *swap_following(Unit *u) {
+ Swap *s = SWAP(u);
+ Swap *other, *first = NULL;
+
+ assert(s);
+
+ if (streq_ptr(s->what, s->parameters_proc_swaps.what))
+ return NULL;
+
+ /* Make everybody follow the unit that's named after the swap
+ * device in the kernel */
+
+ LIST_FOREACH_AFTER(same_proc_swaps, other, s)
+ if (streq_ptr(other->what, other->parameters_proc_swaps.what))
+ return UNIT(other);
+
+ LIST_FOREACH_BEFORE(same_proc_swaps, other, s) {
+ if (streq_ptr(other->what, other->parameters_proc_swaps.what))
+ return UNIT(other);
+
+ first = other;
+ }
+
+ return UNIT(first);
+}
+
+static int swap_following_set(Unit *u, Set **_set) {
+ Swap *s = SWAP(u);
+ Swap *other;
+ Set *set;
+ int r;
+
+ assert(s);
+ assert(_set);
+
+ if (LIST_JUST_US(same_proc_swaps, s)) {
+ *_set = NULL;
+ return 0;
+ }
+
+ if (!(set = set_new(NULL, NULL)))
+ return -ENOMEM;
+
+ LIST_FOREACH_AFTER(same_proc_swaps, other, s)
+ if ((r = set_put(set, other)) < 0)
+ goto fail;
+
+ LIST_FOREACH_BEFORE(same_proc_swaps, other, s)
+ if ((r = set_put(set, other)) < 0)
+ goto fail;
+
+ *_set = set;
+ return 1;
+
+fail:
+ set_free(set);
+ return r;
+}
+
+static void swap_shutdown(Manager *m) {
+ assert(m);
+
+ if (m->proc_swaps) {
+ fclose(m->proc_swaps);
+ m->proc_swaps = NULL;
+ }
+
+ hashmap_free(m->swaps_by_proc_swaps);
+ m->swaps_by_proc_swaps = NULL;
+}
+
+static int swap_enumerate(Manager *m) {
+ int r;
+ struct epoll_event ev;
+ assert(m);
+
+ if (!m->proc_swaps) {
+ m->proc_swaps = fopen("/proc/swaps", "re");
+ if (!m->proc_swaps)
+ return (errno == ENOENT) ? 0 : -errno;
+
+ m->swap_watch.type = WATCH_SWAP;
+ m->swap_watch.fd = fileno(m->proc_swaps);
+
+ zero(ev);
+ ev.events = EPOLLPRI;
+ ev.data.ptr = &m->swap_watch;
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->swap_watch.fd, &ev) < 0)
+ return -errno;
+ }
+
+ r = swap_load_proc_swaps(m, false);
+ if (r < 0)
+ swap_shutdown(m);
+
+ return r;
+}
+
+static void swap_reset_failed(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+
+ if (s->state == SWAP_FAILED)
+ swap_set_state(s, SWAP_DEAD);
+
+ s->result = SWAP_SUCCESS;
+}
+
+static int swap_kill(Unit *u, KillWho who, int signo, DBusError *error) {
+ Swap *s = SWAP(u);
+ int r = 0;
+ Set *pid_set = NULL;
+
+ assert(s);
+
+ if (who == KILL_MAIN) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "Swap units have no main processes");
+ return -ESRCH;
+ }
+
+ if (s->control_pid <= 0 && who == KILL_CONTROL) {
+ dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill");
+ return -ESRCH;
+ }
+
+ if (who == KILL_CONTROL || who == KILL_ALL)
+ if (s->control_pid > 0)
+ if (kill(s->control_pid, signo) < 0)
+ r = -errno;
+
+ if (who == KILL_ALL) {
+ int q;
+
+ pid_set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!pid_set)
+ return -ENOMEM;
+
+ /* Exclude the control pid from being killed via the cgroup */
+ if (s->control_pid > 0) {
+ q = set_put(pid_set, LONG_TO_PTR(s->control_pid));
+ if (q < 0) {
+ r = q;
+ goto finish;
+ }
+ }
+
+ q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, false, pid_set, NULL);
+ if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT)
+ r = q;
+ }
+
+finish:
+ if (pid_set)
+ set_free(pid_set);
+
+ return r;
+}
+
+static const char* const swap_state_table[_SWAP_STATE_MAX] = {
+ [SWAP_DEAD] = "dead",
+ [SWAP_ACTIVATING] = "activating",
+ [SWAP_ACTIVE] = "active",
+ [SWAP_DEACTIVATING] = "deactivating",
+ [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm",
+ [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill",
+ [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm",
+ [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill",
+ [SWAP_FAILED] = "failed"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState);
+
+static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = {
+ [SWAP_EXEC_ACTIVATE] = "ExecActivate",
+ [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand);
+
+static const char* const swap_result_table[_SWAP_RESULT_MAX] = {
+ [SWAP_SUCCESS] = "success",
+ [SWAP_FAILURE_RESOURCES] = "resources",
+ [SWAP_FAILURE_TIMEOUT] = "timeout",
+ [SWAP_FAILURE_EXIT_CODE] = "exit-code",
+ [SWAP_FAILURE_SIGNAL] = "signal",
+ [SWAP_FAILURE_CORE_DUMP] = "core-dump"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult);
+
+const UnitVTable swap_vtable = {
+ .object_size = sizeof(Swap),
+ .exec_context_offset = offsetof(Swap, exec_context),
+
+ .sections =
+ "Unit\0"
+ "Swap\0"
+ "Install\0",
+
+ .no_alias = true,
+ .no_instances = true,
+
+ .init = swap_init,
+ .load = swap_load,
+ .done = swap_done,
+
+ .coldplug = swap_coldplug,
+
+ .dump = swap_dump,
+
+ .start = swap_start,
+ .stop = swap_stop,
+
+ .kill = swap_kill,
+
+ .serialize = swap_serialize,
+ .deserialize_item = swap_deserialize_item,
+
+ .active_state = swap_active_state,
+ .sub_state_to_string = swap_sub_state_to_string,
+
+ .check_gc = swap_check_gc,
+
+ .sigchld_event = swap_sigchld_event,
+ .timer_event = swap_timer_event,
+
+ .reset_failed = swap_reset_failed,
+
+ .bus_interface = "org.freedesktop.systemd1.Swap",
+ .bus_message_handler = bus_swap_message_handler,
+ .bus_invalidating_properties = bus_swap_invalidating_properties,
+
+ .following = swap_following,
+ .following_set = swap_following_set,
+
+ .enumerate = swap_enumerate,
+ .shutdown = swap_shutdown,
+
+ .status_message_formats = {
+ .starting_stopping = {
+ [0] = "Activating swap %s...",
+ [1] = "Deactivating swap %s...",
+ },
+ .finished_start_job = {
+ [JOB_DONE] = "Activated swap %s.",
+ [JOB_FAILED] = "Failed to activate swap %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ [JOB_TIMEOUT] = "Timed out activating swap %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Deactivated swap %s.",
+ [JOB_FAILED] = "Failed deactivating swap %s.",
+ [JOB_TIMEOUT] = "Timed out deactivating swap %s.",
+ },
+ },
+};
diff --git a/src/core/swap.h b/src/core/swap.h
new file mode 100644
index 0000000000..35d47fd46f
--- /dev/null
+++ b/src/core/swap.h
@@ -0,0 +1,121 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2010 Maarten Lankhorst
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Swap Swap;
+
+#include "unit.h"
+
+typedef enum SwapState {
+ SWAP_DEAD,
+ SWAP_ACTIVATING,
+ SWAP_ACTIVE,
+ SWAP_DEACTIVATING,
+ SWAP_ACTIVATING_SIGTERM,
+ SWAP_ACTIVATING_SIGKILL,
+ SWAP_DEACTIVATING_SIGTERM,
+ SWAP_DEACTIVATING_SIGKILL,
+ SWAP_FAILED,
+ _SWAP_STATE_MAX,
+ _SWAP_STATE_INVALID = -1
+} SwapState;
+
+typedef enum SwapExecCommand {
+ SWAP_EXEC_ACTIVATE,
+ SWAP_EXEC_DEACTIVATE,
+ _SWAP_EXEC_COMMAND_MAX,
+ _SWAP_EXEC_COMMAND_INVALID = -1
+} SwapExecCommand;
+
+typedef struct SwapParameters {
+ char *what;
+ int priority;
+ bool noauto:1;
+ bool nofail:1;
+} SwapParameters;
+
+typedef enum SwapResult {
+ SWAP_SUCCESS,
+ SWAP_FAILURE_RESOURCES,
+ SWAP_FAILURE_TIMEOUT,
+ SWAP_FAILURE_EXIT_CODE,
+ SWAP_FAILURE_SIGNAL,
+ SWAP_FAILURE_CORE_DUMP,
+ _SWAP_RESULT_MAX,
+ _SWAP_RESULT_INVALID = -1
+} SwapResult;
+
+struct Swap {
+ Unit meta;
+
+ char *what;
+
+ SwapParameters parameters_proc_swaps;
+ SwapParameters parameters_fragment;
+
+ bool from_proc_swaps:1;
+ bool from_fragment:1;
+
+ /* Used while looking for swaps that vanished or got added
+ * from/to /proc/swaps */
+ bool is_active:1;
+ bool just_activated:1;
+
+ SwapResult result;
+
+ usec_t timeout_usec;
+
+ ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX];
+ ExecContext exec_context;
+ KillContext kill_context;
+
+ SwapState state, deserialized_state;
+
+ ExecCommand* control_command;
+ SwapExecCommand control_command_id;
+ pid_t control_pid;
+
+ Watch timer_watch;
+
+ /* In order to be able to distinguish dependencies on
+ different device nodes we might end up creating multiple
+ devices for the same swap. We chain them up here. */
+
+ LIST_FIELDS(struct Swap, same_proc_swaps);
+};
+
+extern const UnitVTable swap_vtable;
+
+int swap_add_one_mount_link(Swap *s, Mount *m);
+
+int swap_dispatch_reload(Manager *m);
+int swap_fd_event(Manager *m, int events);
+
+const char* swap_state_to_string(SwapState i);
+SwapState swap_state_from_string(const char *s);
+
+const char* swap_exec_command_to_string(SwapExecCommand i);
+SwapExecCommand swap_exec_command_from_string(const char *s);
+
+const char* swap_result_to_string(SwapResult i);
+SwapResult swap_result_from_string(const char *s);
diff --git a/src/core/switch-root.c b/src/core/switch-root.c
new file mode 100644
index 0000000000..150332a858
--- /dev/null
+++ b/src/core/switch-root.c
@@ -0,0 +1,142 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Harald Hoyer, Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "util.h"
+#include "path-util.h"
+#include "switch-root.h"
+
+int switch_root(const char *new_root) {
+
+ /* Don't try to unmount/move the old "/", there's no way to do it. */
+ static const char move_mounts[] =
+ "/dev\0"
+ "/proc\0"
+ "/sys\0"
+ "/run\0";
+
+ int r, old_root_fd = -1;
+ struct stat new_root_stat;
+ bool old_root_remove;
+ const char *i;
+
+ if (path_equal(new_root, "/"))
+ return 0;
+
+ old_root_remove = in_initrd();
+
+ if (stat(new_root, &new_root_stat) < 0) {
+ r = -errno;
+ log_error("Failed to stat directory %s: %m", new_root);
+ goto fail;
+ }
+
+ /* Work-around for a kernel bug: for some reason the kernel
+ * refuses switching root if any file systems are mounted
+ * MS_SHARED. Hence remount them MS_PRIVATE here as a
+ * work-around.
+ *
+ * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
+ if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+ log_warning("Failed to make \"/\" private mount: %m");
+
+ NULSTR_FOREACH(i, move_mounts) {
+ char new_mount[PATH_MAX];
+ struct stat sb;
+
+ snprintf(new_mount, sizeof(new_mount), "%s%s", new_root, i);
+ char_array_0(new_mount);
+
+ if ((stat(new_mount, &sb) < 0) ||
+ sb.st_dev != new_root_stat.st_dev) {
+
+ /* Mount point seems to be mounted already or
+ * stat failed. Unmount the old mount
+ * point. */
+ if (umount2(i, MNT_DETACH) < 0)
+ log_warning("Failed to unmount %s: %m", i);
+ continue;
+ }
+
+ if (mount(i, new_mount, NULL, MS_MOVE, NULL) < 0) {
+ log_error("Failed to move mount %s to %s, forcing unmount: %m", i, new_mount);
+
+ if (umount2(i, MNT_FORCE) < 0)
+ log_warning("Failed to unmount %s: %m", i);
+ }
+ }
+
+ if (chdir(new_root) < 0) {
+ r = -errno;
+ log_error("Failed to change directory to %s: %m", new_root);
+ goto fail;
+ }
+
+ if (old_root_remove) {
+ old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
+ if (old_root_fd < 0)
+ log_warning("Failed to open root directory: %m");
+ }
+
+ if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) {
+ r = -errno;
+ log_error("Failed to mount moving %s to /: %m", new_root);
+ goto fail;
+ }
+
+ if (chroot(".") < 0) {
+ r = -errno;
+ log_error("Failed to change root: %m");
+ goto fail;
+ }
+
+ if (chdir("/") < 0) {
+ r = -errno;
+ log_error("Failed to change directory: %m");
+ goto fail;
+ }
+
+ if (old_root_fd >= 0) {
+ struct stat rb;
+
+ if (fstat(old_root_fd, &rb) < 0)
+ log_warning("Failed to stat old root directory, leaving: %m");
+ else {
+ rm_rf_children(old_root_fd, false, false, &rb);
+ old_root_fd = -1;
+ }
+ }
+
+ r = 0;
+
+fail:
+ if (old_root_fd >= 0)
+ close_nointr_nofail(old_root_fd);
+
+ return r;
+}
diff --git a/src/core/switch-root.h b/src/core/switch-root.h
new file mode 100644
index 0000000000..0c4cd1e403
--- /dev/null
+++ b/src/core/switch-root.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooswitchroothfoo
+#define fooswitchroothfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Harald Hoyer, Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int switch_root(const char *switch_root);
+
+#endif
diff --git a/src/core/syscall-list.c b/src/core/syscall-list.c
new file mode 100644
index 0000000000..05fad3e158
--- /dev/null
+++ b/src/core/syscall-list.c
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/syscall.h>
+#include <string.h>
+
+#include "util.h"
+
+#include "syscall-list.h"
+
+const struct syscall_name *lookup_syscall(register const char *str, register unsigned int len);
+
+#include "syscall-to-name.h"
+#include "syscall-from-name.h"
+
+const char *syscall_to_name(int id) {
+ if (id < 0 || id >= (int) ELEMENTSOF(syscall_names))
+ return NULL;
+
+ return syscall_names[id];
+}
+
+int syscall_from_name(const char *name) {
+ const struct syscall_name *sc;
+
+ assert(name);
+
+ sc = lookup_syscall(name, strlen(name));
+ if (!sc)
+ return -1;
+
+ return sc->id;
+}
+
+int syscall_max(void) {
+ return ELEMENTSOF(syscall_names);
+}
diff --git a/src/core/syscall-list.h b/src/core/syscall-list.h
new file mode 100644
index 0000000000..0fc6859605
--- /dev/null
+++ b/src/core/syscall-list.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosyscalllisthfoo
+#define foosyscalllisthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+const char *syscall_to_name(int id);
+int syscall_from_name(const char *name);
+
+int syscall_max(void);
+
+#endif
diff --git a/src/core/sysfs-show.h b/src/core/sysfs-show.h
new file mode 100644
index 0000000000..9ffd129c48
--- /dev/null
+++ b/src/core/sysfs-show.h
@@ -0,0 +1,24 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int show_sysfs(const char *seat, const char *prefix, unsigned columns);
diff --git a/src/core/system.conf b/src/core/system.conf
new file mode 100644
index 0000000000..68076d9735
--- /dev/null
+++ b/src/core/system.conf
@@ -0,0 +1,43 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# See systemd.conf(5) for details
+
+[Manager]
+#LogLevel=info
+#LogTarget=journal-or-kmsg
+#LogColor=yes
+#LogLocation=no
+#DumpCore=yes
+#CrashShell=no
+#ShowStatus=yes
+#CrashChVT=1
+#CPUAffinity=1 2
+#DefaultControllers=cpu
+#DefaultStandardOutput=journal
+#DefaultStandardError=inherit
+#JoinControllers=cpu,cpuacct,cpuset net_cls,net_prio
+#RuntimeWatchdogSec=0
+#ShutdownWatchdogSec=10min
+#CapabilityBoundingSet=
+#TimerSlackNSec=
+#DefaultLimitCPU=
+#DefaultLimitFSIZE=
+#DefaultLimitDATA=
+#DefaultLimitSTACK=
+#DefaultLimitCORE=
+#DefaultLimitRSS=
+#DefaultLimitNOFILE=
+#DefaultLimitAS=
+#DefaultLimitNPROC=
+#DefaultLimitMEMLOCK=
+#DefaultLimitLOCKS=
+#DefaultLimitSIGPENDING=
+#DefaultLimitMSGQUEUE=
+#DefaultLimitNICE=
+#DefaultLimitRTPRIO=
+#DefaultLimitRTTIME=
diff --git a/src/core/systemd.pc.in b/src/core/systemd.pc.in
new file mode 100644
index 0000000000..2f49d5df52
--- /dev/null
+++ b/src/core/systemd.pc.in
@@ -0,0 +1,23 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+systemdutildir=@rootlibexecdir@
+systemdsystemunitdir=@systemunitdir@
+systemdsystempresetdir=@systempresetdir@
+systemduserunitdir=@userunitdir@
+systemduserpresetdir=@userpresetdir@
+systemdsystemconfdir=@pkgsysconfdir@/system
+systemduserconfdir=@pkgsysconfdir@/user
+systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system
+systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user
+
+Name: systemd
+Description: systemd System and Service Manager
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
diff --git a/src/core/target.c b/src/core/target.c
new file mode 100644
index 0000000000..981424132b
--- /dev/null
+++ b/src/core/target.c
@@ -0,0 +1,239 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "unit.h"
+#include "target.h"
+#include "load-fragment.h"
+#include "log.h"
+#include "dbus-target.h"
+#include "special.h"
+#include "unit-name.h"
+
+static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = {
+ [TARGET_DEAD] = UNIT_INACTIVE,
+ [TARGET_ACTIVE] = UNIT_ACTIVE
+};
+
+static void target_set_state(Target *t, TargetState state) {
+ TargetState old_state;
+ assert(t);
+
+ old_state = t->state;
+ t->state = state;
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(t)->id,
+ target_state_to_string(old_state),
+ target_state_to_string(state));
+
+ unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static int target_add_default_dependencies(Target *t) {
+
+ static const UnitDependency deps[] = {
+ UNIT_REQUIRES,
+ UNIT_REQUIRES_OVERRIDABLE,
+ UNIT_REQUISITE,
+ UNIT_REQUISITE_OVERRIDABLE,
+ UNIT_WANTS,
+ UNIT_BINDS_TO,
+ UNIT_PART_OF
+ };
+
+ Iterator i;
+ Unit *other;
+ int r;
+ unsigned k;
+
+ assert(t);
+
+ /* Imply ordering for requirement dependencies on target
+ * units. Note that when the user created a contradicting
+ * ordering manually we won't add anything in here to make
+ * sure we don't create a loop. */
+
+ for (k = 0; k < ELEMENTSOF(deps); k++)
+ SET_FOREACH(other, UNIT(t)->dependencies[deps[k]], i) {
+ r = unit_add_default_target_dependency(other, UNIT(t));
+ if (r < 0)
+ return r;
+ }
+
+ /* Make sure targets are unloaded on shutdown */
+ return unit_add_dependency_by_name(UNIT(t), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+}
+
+static int target_load(Unit *u) {
+ Target *t = TARGET(u);
+ int r;
+
+ assert(t);
+
+ r = unit_load_fragment_and_dropin(u);
+ if (r < 0)
+ return r;
+
+ /* This is a new unit? Then let's add in some extras */
+ if (u->load_state == UNIT_LOADED && u->default_dependencies) {
+ r = target_add_default_dependencies(t);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int target_coldplug(Unit *u) {
+ Target *t = TARGET(u);
+
+ assert(t);
+ assert(t->state == TARGET_DEAD);
+
+ if (t->deserialized_state != t->state)
+ target_set_state(t, t->deserialized_state);
+
+ return 0;
+}
+
+static void target_dump(Unit *u, FILE *f, const char *prefix) {
+ Target *t = TARGET(u);
+
+ assert(t);
+ assert(f);
+
+ fprintf(f,
+ "%sTarget State: %s\n",
+ prefix, target_state_to_string(t->state));
+}
+
+static int target_start(Unit *u) {
+ Target *t = TARGET(u);
+
+ assert(t);
+ assert(t->state == TARGET_DEAD);
+
+ target_set_state(t, TARGET_ACTIVE);
+ return 0;
+}
+
+static int target_stop(Unit *u) {
+ Target *t = TARGET(u);
+
+ assert(t);
+ assert(t->state == TARGET_ACTIVE);
+
+ target_set_state(t, TARGET_DEAD);
+ return 0;
+}
+
+static int target_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Target *s = TARGET(u);
+
+ assert(s);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", target_state_to_string(s->state));
+ return 0;
+}
+
+static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Target *s = TARGET(u);
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ TargetState state;
+
+ state = target_state_from_string(value);
+ if (state < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ s->deserialized_state = state;
+
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState target_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[TARGET(u)->state];
+}
+
+static const char *target_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return target_state_to_string(TARGET(u)->state);
+}
+
+static const char* const target_state_table[_TARGET_STATE_MAX] = {
+ [TARGET_DEAD] = "dead",
+ [TARGET_ACTIVE] = "active"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState);
+
+const UnitVTable target_vtable = {
+ .object_size = sizeof(Target),
+ .sections =
+ "Unit\0"
+ "Target\0"
+ "Install\0",
+
+ .load = target_load,
+ .coldplug = target_coldplug,
+
+ .dump = target_dump,
+
+ .start = target_start,
+ .stop = target_stop,
+
+ .serialize = target_serialize,
+ .deserialize_item = target_deserialize_item,
+
+ .active_state = target_active_state,
+ .sub_state_to_string = target_sub_state_to_string,
+
+ .bus_interface = "org.freedesktop.systemd1.Target",
+ .bus_message_handler = bus_target_message_handler,
+
+ .status_message_formats = {
+ .finished_start_job = {
+ [JOB_DONE] = "Reached target %s.",
+ [JOB_DEPENDENCY] = "Dependency failed for %s.",
+ },
+ .finished_stop_job = {
+ [JOB_DONE] = "Stopped target %s.",
+ },
+ },
+};
diff --git a/src/core/target.h b/src/core/target.h
new file mode 100644
index 0000000000..1676553add
--- /dev/null
+++ b/src/core/target.h
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Target Target;
+
+#include "unit.h"
+
+typedef enum TargetState {
+ TARGET_DEAD,
+ TARGET_ACTIVE,
+ _TARGET_STATE_MAX,
+ _TARGET_STATE_INVALID = -1
+} TargetState;
+
+struct Target {
+ Unit meta;
+
+ TargetState state, deserialized_state;
+};
+
+extern const UnitVTable target_vtable;
+
+const char* target_state_to_string(TargetState i);
+TargetState target_state_from_string(const char *s);
diff --git a/src/core/tcpwrap.c b/src/core/tcpwrap.c
new file mode 100644
index 0000000000..6c630fac60
--- /dev/null
+++ b/src/core/tcpwrap.c
@@ -0,0 +1,68 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
+#include "tcpwrap.h"
+#include "log.h"
+
+bool socket_tcpwrap(int fd, const char *name) {
+#ifdef HAVE_LIBWRAP
+ struct request_info req;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_storage storage;
+ } sa_union;
+ socklen_t l = sizeof(sa_union);
+
+ if (getsockname(fd, &sa_union.sa, &l) < 0)
+ return true;
+
+ if (sa_union.sa.sa_family != AF_INET &&
+ sa_union.sa.sa_family != AF_INET6)
+ return true;
+
+ request_init(&req,
+ RQ_DAEMON, name,
+ RQ_FILE, fd,
+ NULL);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ log_warning("Connection refused by tcpwrap.");
+ return false;
+ }
+
+ log_debug("Connection accepted by tcpwrap.");
+#endif
+ return true;
+}
diff --git a/src/core/tcpwrap.h b/src/core/tcpwrap.h
new file mode 100644
index 0000000000..3353b6596e
--- /dev/null
+++ b/src/core/tcpwrap.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+bool socket_tcpwrap(int fd, const char *name);
diff --git a/src/core/timer.c b/src/core/timer.c
new file mode 100644
index 0000000000..7080b32c6b
--- /dev/null
+++ b/src/core/timer.c
@@ -0,0 +1,519 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "unit.h"
+#include "unit-name.h"
+#include "timer.h"
+#include "dbus-timer.h"
+#include "special.h"
+#include "bus-errors.h"
+
+static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = {
+ [TIMER_DEAD] = UNIT_INACTIVE,
+ [TIMER_WAITING] = UNIT_ACTIVE,
+ [TIMER_RUNNING] = UNIT_ACTIVE,
+ [TIMER_ELAPSED] = UNIT_ACTIVE,
+ [TIMER_FAILED] = UNIT_FAILED
+};
+
+static void timer_init(Unit *u) {
+ Timer *t = TIMER(u);
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ t->next_elapse = (usec_t) -1;
+}
+
+static void timer_done(Unit *u) {
+ Timer *t = TIMER(u);
+ TimerValue *v;
+
+ assert(t);
+
+ while ((v = t->values)) {
+ LIST_REMOVE(TimerValue, value, t->values, v);
+ free(v);
+ }
+
+ unit_unwatch_timer(u, &t->timer_watch);
+
+ unit_ref_unset(&t->unit);
+}
+
+static int timer_verify(Timer *t) {
+ assert(t);
+
+ if (UNIT(t)->load_state != UNIT_LOADED)
+ return 0;
+
+ if (!t->values) {
+ log_error("%s lacks value setting. Refusing.", UNIT(t)->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int timer_add_default_dependencies(Timer *t) {
+ int r;
+
+ assert(t);
+
+ if (UNIT(t)->manager->running_as == SYSTEMD_SYSTEM) {
+ if ((r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
+ return r;
+
+ if ((r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
+ return r;
+ }
+
+ return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+}
+
+static int timer_load(Unit *u) {
+ Timer *t = TIMER(u);
+ int r;
+
+ assert(u);
+ assert(u->load_state == UNIT_STUB);
+
+ if ((r = unit_load_fragment_and_dropin(u)) < 0)
+ return r;
+
+ if (u->load_state == UNIT_LOADED) {
+
+ if (!UNIT_DEREF(t->unit)) {
+ Unit *x;
+
+ r = unit_load_related_unit(u, ".service", &x);
+ if (r < 0)
+ return r;
+
+ unit_ref_set(&t->unit, x);
+ }
+
+ r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(t->unit), true);
+ if (r < 0)
+ return r;
+
+ if (UNIT(t)->default_dependencies)
+ if ((r = timer_add_default_dependencies(t)) < 0)
+ return r;
+ }
+
+ return timer_verify(t);
+}
+
+static void timer_dump(Unit *u, FILE *f, const char *prefix) {
+ Timer *t = TIMER(u);
+ TimerValue *v;
+ char
+ timespan1[FORMAT_TIMESPAN_MAX];
+
+ fprintf(f,
+ "%sTimer State: %s\n"
+ "%sResult: %s\n"
+ "%sUnit: %s\n",
+ prefix, timer_state_to_string(t->state),
+ prefix, timer_result_to_string(t->result),
+ prefix, UNIT_DEREF(t->unit)->id);
+
+ LIST_FOREACH(value, v, t->values)
+ fprintf(f,
+ "%s%s: %s\n",
+ prefix,
+ timer_base_to_string(v->base),
+ strna(format_timespan(timespan1, sizeof(timespan1), v->value)));
+}
+
+static void timer_set_state(Timer *t, TimerState state) {
+ TimerState old_state;
+ assert(t);
+
+ old_state = t->state;
+ t->state = state;
+
+ if (state != TIMER_WAITING)
+ unit_unwatch_timer(UNIT(t), &t->timer_watch);
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(t)->id,
+ timer_state_to_string(old_state),
+ timer_state_to_string(state));
+
+ unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true);
+}
+
+static void timer_enter_waiting(Timer *t, bool initial);
+
+static int timer_coldplug(Unit *u) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+ assert(t->state == TIMER_DEAD);
+
+ if (t->deserialized_state != t->state) {
+
+ if (t->deserialized_state == TIMER_WAITING)
+ timer_enter_waiting(t, false);
+ else
+ timer_set_state(t, t->deserialized_state);
+ }
+
+ return 0;
+}
+
+static void timer_enter_dead(Timer *t, TimerResult f) {
+ assert(t);
+
+ if (f != TIMER_SUCCESS)
+ t->result = f;
+
+ timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD);
+}
+
+static void timer_enter_waiting(Timer *t, bool initial) {
+ TimerValue *v;
+ usec_t base = 0, delay, n;
+ bool found = false;
+ int r;
+
+ n = now(CLOCK_MONOTONIC);
+
+ LIST_FOREACH(value, v, t->values) {
+
+ if (v->disabled)
+ continue;
+
+ switch (v->base) {
+
+ case TIMER_ACTIVE:
+ if (state_translation_table[t->state] == UNIT_ACTIVE)
+ base = UNIT(t)->inactive_exit_timestamp.monotonic;
+ else
+ base = n;
+ break;
+
+ case TIMER_BOOT:
+ /* CLOCK_MONOTONIC equals the uptime on Linux */
+ base = 0;
+ break;
+
+ case TIMER_STARTUP:
+ base = UNIT(t)->manager->userspace_timestamp.monotonic;
+ break;
+
+ case TIMER_UNIT_ACTIVE:
+
+ if (UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic <= 0)
+ continue;
+
+ base = UNIT_DEREF(t->unit)->inactive_exit_timestamp.monotonic;
+ break;
+
+ case TIMER_UNIT_INACTIVE:
+
+ if (UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic <= 0)
+ continue;
+
+ base = UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic;
+ break;
+
+ default:
+ assert_not_reached("Unknown timer base");
+ }
+
+ v->next_elapse = base + v->value;
+
+ if (!initial && v->next_elapse < n) {
+ v->disabled = true;
+ continue;
+ }
+
+ if (!found)
+ t->next_elapse = v->next_elapse;
+ else
+ t->next_elapse = MIN(t->next_elapse, v->next_elapse);
+
+ found = true;
+ }
+
+ if (!found) {
+ timer_set_state(t, TIMER_ELAPSED);
+ return;
+ }
+
+ delay = n < t->next_elapse ? t->next_elapse - n : 0;
+
+ if ((r = unit_watch_timer(UNIT(t), delay, &t->timer_watch)) < 0)
+ goto fail;
+
+ timer_set_state(t, TIMER_WAITING);
+ return;
+
+fail:
+ log_warning("%s failed to enter waiting state: %s", UNIT(t)->id, strerror(-r));
+ timer_enter_dead(t, TIMER_FAILURE_RESOURCES);
+}
+
+static void timer_enter_running(Timer *t) {
+ DBusError error;
+ int r;
+
+ assert(t);
+ dbus_error_init(&error);
+
+ /* Don't start job if we are supposed to go down */
+ if (UNIT(t)->job && UNIT(t)->job->type == JOB_STOP)
+ return;
+
+ if ((r = manager_add_job(UNIT(t)->manager, JOB_START, UNIT_DEREF(t->unit), JOB_REPLACE, true, &error, NULL)) < 0)
+ goto fail;
+
+ timer_set_state(t, TIMER_RUNNING);
+ return;
+
+fail:
+ log_warning("%s failed to queue unit startup job: %s", UNIT(t)->id, bus_error(&error, r));
+ timer_enter_dead(t, TIMER_FAILURE_RESOURCES);
+
+ dbus_error_free(&error);
+}
+
+static int timer_start(Unit *u) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+ assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED);
+
+ if (UNIT_DEREF(t->unit)->load_state != UNIT_LOADED)
+ return -ENOENT;
+
+ t->result = TIMER_SUCCESS;
+ timer_enter_waiting(t, true);
+ return 0;
+}
+
+static int timer_stop(Unit *u) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+ assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED);
+
+ timer_enter_dead(t, TIMER_SUCCESS);
+ return 0;
+}
+
+static int timer_serialize(Unit *u, FILE *f, FDSet *fds) {
+ Timer *t = TIMER(u);
+
+ assert(u);
+ assert(f);
+ assert(fds);
+
+ unit_serialize_item(u, f, "state", timer_state_to_string(t->state));
+ unit_serialize_item(u, f, "result", timer_result_to_string(t->result));
+
+ return 0;
+}
+
+static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Timer *t = TIMER(u);
+
+ assert(u);
+ assert(key);
+ assert(value);
+ assert(fds);
+
+ if (streq(key, "state")) {
+ TimerState state;
+
+ if ((state = timer_state_from_string(value)) < 0)
+ log_debug("Failed to parse state value %s", value);
+ else
+ t->deserialized_state = state;
+ } else if (streq(key, "result")) {
+ TimerResult f;
+
+ f = timer_result_from_string(value);
+ if (f < 0)
+ log_debug("Failed to parse result value %s", value);
+ else if (f != TIMER_SUCCESS)
+ t->result = f;
+
+ } else
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState timer_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_table[TIMER(u)->state];
+}
+
+static const char *timer_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return timer_state_to_string(TIMER(u)->state);
+}
+
+static void timer_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+ assert(elapsed == 1);
+
+ if (t->state != TIMER_WAITING)
+ return;
+
+ log_debug("Timer elapsed on %s", u->id);
+ timer_enter_running(t);
+}
+
+void timer_unit_notify(Unit *u, UnitActiveState new_state) {
+ Iterator i;
+ Unit *k;
+
+ if (u->type == UNIT_TIMER)
+ return;
+
+ SET_FOREACH(k, u->dependencies[UNIT_TRIGGERED_BY], i) {
+ Timer *t;
+ TimerValue *v;
+
+ if (k->type != UNIT_TIMER)
+ continue;
+
+ if (k->load_state != UNIT_LOADED)
+ continue;
+
+ t = TIMER(k);
+
+ /* Reenable all timers that depend on unit state */
+ LIST_FOREACH(value, v, t->values)
+ if (v->base == TIMER_UNIT_ACTIVE ||
+ v->base == TIMER_UNIT_INACTIVE)
+ v->disabled = false;
+
+ switch (t->state) {
+
+ case TIMER_WAITING:
+ case TIMER_ELAPSED:
+
+ /* Recalculate sleep time */
+ timer_enter_waiting(t, false);
+ break;
+
+ case TIMER_RUNNING:
+
+ if (UNIT_IS_INACTIVE_OR_FAILED(new_state)) {
+ log_debug("%s got notified about unit deactivation.", UNIT(t)->id);
+ timer_enter_waiting(t, false);
+ }
+
+ break;
+
+ case TIMER_DEAD:
+ case TIMER_FAILED:
+ break;
+
+ default:
+ assert_not_reached("Unknown timer state");
+ }
+ }
+}
+
+static void timer_reset_failed(Unit *u) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+
+ if (t->state == TIMER_FAILED)
+ timer_set_state(t, TIMER_DEAD);
+
+ t->result = TIMER_SUCCESS;
+}
+
+static const char* const timer_state_table[_TIMER_STATE_MAX] = {
+ [TIMER_DEAD] = "dead",
+ [TIMER_WAITING] = "waiting",
+ [TIMER_RUNNING] = "running",
+ [TIMER_ELAPSED] = "elapsed",
+ [TIMER_FAILED] = "failed"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState);
+
+static const char* const timer_base_table[_TIMER_BASE_MAX] = {
+ [TIMER_ACTIVE] = "OnActiveSec",
+ [TIMER_BOOT] = "OnBootSec",
+ [TIMER_STARTUP] = "OnStartupSec",
+ [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec",
+ [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase);
+
+static const char* const timer_result_table[_TIMER_RESULT_MAX] = {
+ [TIMER_SUCCESS] = "success",
+ [TIMER_FAILURE_RESOURCES] = "resources"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult);
+
+const UnitVTable timer_vtable = {
+ .object_size = sizeof(Timer),
+ .sections =
+ "Unit\0"
+ "Timer\0"
+ "Install\0",
+
+ .init = timer_init,
+ .done = timer_done,
+ .load = timer_load,
+
+ .coldplug = timer_coldplug,
+
+ .dump = timer_dump,
+
+ .start = timer_start,
+ .stop = timer_stop,
+
+ .serialize = timer_serialize,
+ .deserialize_item = timer_deserialize_item,
+
+ .active_state = timer_active_state,
+ .sub_state_to_string = timer_sub_state_to_string,
+
+ .timer_event = timer_timer_event,
+
+ .reset_failed = timer_reset_failed,
+
+ .bus_interface = "org.freedesktop.systemd1.Timer",
+ .bus_message_handler = bus_timer_message_handler,
+ .bus_invalidating_properties = bus_timer_invalidating_properties
+};
diff --git a/src/core/timer.h b/src/core/timer.h
new file mode 100644
index 0000000000..c6d1d42e44
--- /dev/null
+++ b/src/core/timer.h
@@ -0,0 +1,90 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Timer Timer;
+
+#include "unit.h"
+
+typedef enum TimerState {
+ TIMER_DEAD,
+ TIMER_WAITING,
+ TIMER_RUNNING,
+ TIMER_ELAPSED,
+ TIMER_FAILED,
+ _TIMER_STATE_MAX,
+ _TIMER_STATE_INVALID = -1
+} TimerState;
+
+typedef enum TimerBase {
+ TIMER_ACTIVE,
+ TIMER_BOOT,
+ TIMER_STARTUP,
+ TIMER_UNIT_ACTIVE,
+ TIMER_UNIT_INACTIVE,
+ _TIMER_BASE_MAX,
+ _TIMER_BASE_INVALID = -1
+} TimerBase;
+
+typedef struct TimerValue {
+ usec_t value;
+ usec_t next_elapse;
+
+ LIST_FIELDS(struct TimerValue, value);
+
+ TimerBase base;
+ bool disabled;
+} TimerValue;
+
+typedef enum TimerResult {
+ TIMER_SUCCESS,
+ TIMER_FAILURE_RESOURCES,
+ _TIMER_RESULT_MAX,
+ _TIMER_RESULT_INVALID = -1
+} TimerResult;
+
+struct Timer {
+ Unit meta;
+
+ LIST_HEAD(TimerValue, values);
+ usec_t next_elapse;
+
+ TimerState state, deserialized_state;
+ UnitRef unit;
+
+ Watch timer_watch;
+
+ TimerResult result;
+};
+
+void timer_unit_notify(Unit *u, UnitActiveState new_state);
+
+extern const UnitVTable timer_vtable;
+
+const char *timer_state_to_string(TimerState i);
+TimerState timer_state_from_string(const char *s);
+
+const char *timer_base_to_string(TimerBase i);
+TimerBase timer_base_from_string(const char *s);
+
+const char* timer_result_to_string(TimerResult i);
+TimerResult timer_result_from_string(const char *s);
diff --git a/src/core/transaction.c b/src/core/transaction.c
new file mode 100644
index 0000000000..4bce942012
--- /dev/null
+++ b/src/core/transaction.c
@@ -0,0 +1,1087 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "transaction.h"
+#include "bus-errors.h"
+
+static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies);
+
+static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) {
+ assert(tr);
+ assert(j);
+
+ /* Deletes one job from the transaction */
+
+ transaction_unlink_job(tr, j, delete_dependencies);
+
+ job_free(j);
+}
+
+static void transaction_delete_unit(Transaction *tr, Unit *u) {
+ Job *j;
+
+ /* Deletes all jobs associated with a certain unit from the
+ * transaction */
+
+ while ((j = hashmap_get(tr->jobs, u)))
+ transaction_delete_job(tr, j, true);
+}
+
+void transaction_abort(Transaction *tr) {
+ Job *j;
+
+ assert(tr);
+
+ while ((j = hashmap_first(tr->jobs)))
+ transaction_delete_job(tr, j, false);
+
+ assert(hashmap_isempty(tr->jobs));
+}
+
+static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) {
+ JobDependency *l;
+
+ /* A recursive sweep through the graph that marks all units
+ * that matter to the anchor job, i.e. are directly or
+ * indirectly a dependency of the anchor job via paths that
+ * are fully marked as mattering. */
+
+ j->matters_to_anchor = true;
+ j->generation = generation;
+
+ LIST_FOREACH(subject, l, j->subject_list) {
+
+ /* This link does not matter */
+ if (!l->matters)
+ continue;
+
+ /* This unit has already been marked */
+ if (l->object->generation == generation)
+ continue;
+
+ transaction_find_jobs_that_matter_to_anchor(l->object, generation);
+ }
+}
+
+static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) {
+ JobDependency *l, *last;
+
+ assert(j);
+ assert(other);
+ assert(j->unit == other->unit);
+ assert(!j->installed);
+
+ /* Merges 'other' into 'j' and then deletes 'other'. */
+
+ j->type = t;
+ j->state = JOB_WAITING;
+ j->override = j->override || other->override;
+
+ j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor;
+
+ /* Patch us in as new owner of the JobDependency objects */
+ last = NULL;
+ LIST_FOREACH(subject, l, other->subject_list) {
+ assert(l->subject == other);
+ l->subject = j;
+ last = l;
+ }
+
+ /* Merge both lists */
+ if (last) {
+ last->subject_next = j->subject_list;
+ if (j->subject_list)
+ j->subject_list->subject_prev = last;
+ j->subject_list = other->subject_list;
+ }
+
+ /* Patch us in as new owner of the JobDependency objects */
+ last = NULL;
+ LIST_FOREACH(object, l, other->object_list) {
+ assert(l->object == other);
+ l->object = j;
+ last = l;
+ }
+
+ /* Merge both lists */
+ if (last) {
+ last->object_next = j->object_list;
+ if (j->object_list)
+ j->object_list->object_prev = last;
+ j->object_list = other->object_list;
+ }
+
+ /* Kill the other job */
+ other->subject_list = NULL;
+ other->object_list = NULL;
+ transaction_delete_job(tr, other, true);
+}
+
+static bool job_is_conflicted_by(Job *j) {
+ JobDependency *l;
+
+ assert(j);
+
+ /* Returns true if this job is pulled in by a least one
+ * ConflictedBy dependency. */
+
+ LIST_FOREACH(object, l, j->object_list)
+ if (l->conflicts)
+ return true;
+
+ return false;
+}
+
+static int delete_one_unmergeable_job(Transaction *tr, Job *j) {
+ Job *k;
+
+ assert(j);
+
+ /* Tries to delete one item in the linked list
+ * j->transaction_next->transaction_next->... that conflicts
+ * with another one, in an attempt to make an inconsistent
+ * transaction work. */
+
+ /* We rely here on the fact that if a merged with b does not
+ * merge with c, either a or b merge with c neither */
+ LIST_FOREACH(transaction, j, j)
+ LIST_FOREACH(transaction, k, j->transaction_next) {
+ Job *d;
+
+ /* Is this one mergeable? Then skip it */
+ if (job_type_is_mergeable(j->type, k->type))
+ continue;
+
+ /* Ok, we found two that conflict, let's see if we can
+ * drop one of them */
+ if (!j->matters_to_anchor && !k->matters_to_anchor) {
+
+ /* Both jobs don't matter, so let's
+ * find the one that is smarter to
+ * remove. Let's think positive and
+ * rather remove stops then starts --
+ * except if something is being
+ * stopped because it is conflicted by
+ * another unit in which case we
+ * rather remove the start. */
+
+ log_debug("Looking at job %s/%s conflicted_by=%s", j->unit->id, job_type_to_string(j->type), yes_no(j->type == JOB_STOP && job_is_conflicted_by(j)));
+ log_debug("Looking at job %s/%s conflicted_by=%s", k->unit->id, job_type_to_string(k->type), yes_no(k->type == JOB_STOP && job_is_conflicted_by(k)));
+
+ if (j->type == JOB_STOP) {
+
+ if (job_is_conflicted_by(j))
+ d = k;
+ else
+ d = j;
+
+ } else if (k->type == JOB_STOP) {
+
+ if (job_is_conflicted_by(k))
+ d = j;
+ else
+ d = k;
+ } else
+ d = j;
+
+ } else if (!j->matters_to_anchor)
+ d = j;
+ else if (!k->matters_to_anchor)
+ d = k;
+ else
+ return -ENOEXEC;
+
+ /* Ok, we can drop one, so let's do so. */
+ log_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type));
+ transaction_delete_job(tr, d, true);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int transaction_merge_jobs(Transaction *tr, DBusError *e) {
+ Job *j;
+ Iterator i;
+ int r;
+
+ assert(tr);
+
+ /* First step, check whether any of the jobs for one specific
+ * task conflict. If so, try to drop one of them. */
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+ JobType t;
+ Job *k;
+
+ t = j->type;
+ LIST_FOREACH(transaction, k, j->transaction_next) {
+ if (job_type_merge_and_collapse(&t, k->type, j->unit) >= 0)
+ continue;
+
+ /* OK, we could not merge all jobs for this
+ * action. Let's see if we can get rid of one
+ * of them */
+
+ r = delete_one_unmergeable_job(tr, j);
+ if (r >= 0)
+ /* Ok, we managed to drop one, now
+ * let's ask our callers to call us
+ * again after garbage collecting */
+ return -EAGAIN;
+
+ /* We couldn't merge anything. Failure */
+ dbus_set_error(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, "Transaction contains conflicting jobs '%s' and '%s' for %s. Probably contradicting requirement dependencies configured.",
+ job_type_to_string(t), job_type_to_string(k->type), k->unit->id);
+ return r;
+ }
+ }
+
+ /* Second step, merge the jobs. */
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+ JobType t = j->type;
+ Job *k;
+
+ /* Merge all transaction jobs for j->unit */
+ LIST_FOREACH(transaction, k, j->transaction_next)
+ assert_se(job_type_merge_and_collapse(&t, k->type, j->unit) == 0);
+
+ while ((k = j->transaction_next)) {
+ if (tr->anchor_job == k) {
+ transaction_merge_and_delete_job(tr, k, j, t);
+ j = k;
+ } else
+ transaction_merge_and_delete_job(tr, j, k, t);
+ }
+
+ assert(!j->transaction_next);
+ assert(!j->transaction_prev);
+ }
+
+ return 0;
+}
+
+static void transaction_drop_redundant(Transaction *tr) {
+ Job *j;
+ Iterator i;
+
+ /* Goes through the transaction and removes all jobs of the units
+ * whose jobs are all noops. If not all of a unit's jobs are
+ * redundant, they are kept. */
+
+ assert(tr);
+
+rescan:
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+ Job *k;
+
+ LIST_FOREACH(transaction, k, j) {
+
+ if (tr->anchor_job == k ||
+ !job_type_is_redundant(k->type, unit_active_state(k->unit)) ||
+ (k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type)))
+ goto next_unit;
+ }
+
+ /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */
+ transaction_delete_job(tr, j, false);
+ goto rescan;
+ next_unit:;
+ }
+}
+
+static bool unit_matters_to_anchor(Unit *u, Job *j) {
+ assert(u);
+ assert(!j->transaction_prev);
+
+ /* Checks whether at least one of the jobs for this unit
+ * matters to the anchor. */
+
+ LIST_FOREACH(transaction, j, j)
+ if (j->matters_to_anchor)
+ return true;
+
+ return false;
+}
+
+static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, DBusError *e) {
+ Iterator i;
+ Unit *u;
+ int r;
+
+ assert(tr);
+ assert(j);
+ assert(!j->transaction_prev);
+
+ /* Does a recursive sweep through the ordering graph, looking
+ * for a cycle. If we find cycle we try to break it. */
+
+ /* Have we seen this before? */
+ if (j->generation == generation) {
+ Job *k, *delete;
+
+ /* If the marker is NULL we have been here already and
+ * decided the job was loop-free from here. Hence
+ * shortcut things and return right-away. */
+ if (!j->marker)
+ return 0;
+
+ /* So, the marker is not NULL and we already have been
+ * here. We have a cycle. Let's try to break it. We go
+ * backwards in our path and try to find a suitable
+ * job to remove. We use the marker to find our way
+ * back, since smart how we are we stored our way back
+ * in there. */
+ log_warning("Found ordering cycle on %s/%s", j->unit->id, job_type_to_string(j->type));
+
+ delete = NULL;
+ for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) {
+
+ log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type));
+
+ if (!delete &&
+ !unit_matters_to_anchor(k->unit, k)) {
+ /* Ok, we can drop this one, so let's
+ * do so. */
+ delete = k;
+ }
+
+ /* Check if this in fact was the beginning of
+ * the cycle */
+ if (k == j)
+ break;
+ }
+
+
+ if (delete) {
+ log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type));
+ transaction_delete_unit(tr, delete->unit);
+ return -EAGAIN;
+ }
+
+ log_error("Unable to break cycle");
+
+ dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details.");
+ return -ENOEXEC;
+ }
+
+ /* Make the marker point to where we come from, so that we can
+ * find our way backwards if we want to break a cycle. We use
+ * a special marker for the beginning: we point to
+ * ourselves. */
+ j->marker = from ? from : j;
+ j->generation = generation;
+
+ /* We assume that the dependencies are bidirectional, and
+ * hence can ignore UNIT_AFTER */
+ SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) {
+ Job *o;
+
+ /* Is there a job for this unit? */
+ o = hashmap_get(tr->jobs, u);
+ if (!o) {
+ /* Ok, there is no job for this in the
+ * transaction, but maybe there is already one
+ * running? */
+ o = u->job;
+ if (!o)
+ continue;
+ }
+
+ r = transaction_verify_order_one(tr, o, j, generation, e);
+ if (r < 0)
+ return r;
+ }
+
+ /* Ok, let's backtrack, and remember that this entry is not on
+ * our path anymore. */
+ j->marker = NULL;
+
+ return 0;
+}
+
+static int transaction_verify_order(Transaction *tr, unsigned *generation, DBusError *e) {
+ Job *j;
+ int r;
+ Iterator i;
+ unsigned g;
+
+ assert(tr);
+ assert(generation);
+
+ /* Check if the ordering graph is cyclic. If it is, try to fix
+ * that up by dropping one of the jobs. */
+
+ g = (*generation)++;
+
+ HASHMAP_FOREACH(j, tr->jobs, i)
+ if ((r = transaction_verify_order_one(tr, j, NULL, g, e)) < 0)
+ return r;
+
+ return 0;
+}
+
+static void transaction_collect_garbage(Transaction *tr) {
+ Iterator i;
+ Job *j;
+
+ assert(tr);
+
+ /* Drop jobs that are not required by any other job */
+
+rescan:
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+ if (tr->anchor_job == j || j->object_list) {
+ /* log_debug("Keeping job %s/%s because of %s/%s", */
+ /* j->unit->id, job_type_to_string(j->type), */
+ /* j->object_list->subject ? j->object_list->subject->unit->id : "root", */
+ /* j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */
+ continue;
+ }
+
+ /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */
+ transaction_delete_job(tr, j, true);
+ goto rescan;
+ }
+}
+
+static int transaction_is_destructive(Transaction *tr, DBusError *e) {
+ Iterator i;
+ Job *j;
+
+ assert(tr);
+
+ /* Checks whether applying this transaction means that
+ * existing jobs would be replaced */
+
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+
+ /* Assume merged */
+ assert(!j->transaction_prev);
+ assert(!j->transaction_next);
+
+ if (j->unit->job &&
+ !job_type_is_superset(j->type, j->unit->job->type)) {
+
+ dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive.");
+ return -EEXIST;
+ }
+ }
+
+ return 0;
+}
+
+static void transaction_minimize_impact(Transaction *tr) {
+ Job *j;
+ Iterator i;
+
+ assert(tr);
+
+ /* Drops all unnecessary jobs that reverse already active jobs
+ * or that stop a running service. */
+
+rescan:
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+ LIST_FOREACH(transaction, j, j) {
+ bool stops_running_service, changes_existing_job;
+
+ /* If it matters, we shouldn't drop it */
+ if (j->matters_to_anchor)
+ continue;
+
+ /* Would this stop a running service?
+ * Would this change an existing job?
+ * If so, let's drop this entry */
+
+ stops_running_service =
+ j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit));
+
+ changes_existing_job =
+ j->unit->job &&
+ job_type_is_conflicting(j->type, j->unit->job->type);
+
+ if (!stops_running_service && !changes_existing_job)
+ continue;
+
+ if (stops_running_service)
+ log_debug("%s/%s would stop a running service.", j->unit->id, job_type_to_string(j->type));
+
+ if (changes_existing_job)
+ log_debug("%s/%s would change existing job.", j->unit->id, job_type_to_string(j->type));
+
+ /* Ok, let's get rid of this */
+ log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type));
+
+ transaction_delete_job(tr, j, true);
+ goto rescan;
+ }
+ }
+}
+
+static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
+ Iterator i;
+ Job *j;
+ int r;
+
+ /* Moves the transaction jobs to the set of active jobs */
+
+ if (mode == JOB_ISOLATE) {
+
+ /* When isolating first kill all installed jobs which
+ * aren't part of the new transaction */
+ HASHMAP_FOREACH(j, m->jobs, i) {
+ assert(j->installed);
+
+ if (hashmap_get(tr->jobs, j->unit))
+ continue;
+
+ /* Not invalidating recursively. Avoids triggering
+ * OnFailure= actions of dependent jobs. Also avoids
+ * invalidating our iterator. */
+ job_finish_and_invalidate(j, JOB_CANCELED, false);
+ }
+ }
+
+ HASHMAP_FOREACH(j, tr->jobs, i) {
+ /* Assume merged */
+ assert(!j->transaction_prev);
+ assert(!j->transaction_next);
+
+ r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j);
+ if (r < 0)
+ goto rollback;
+ }
+
+ while ((j = hashmap_steal_first(tr->jobs))) {
+ Job *installed_job;
+
+ /* Clean the job dependencies */
+ transaction_unlink_job(tr, j, false);
+
+ installed_job = job_install(j);
+ if (installed_job != j) {
+ /* j has been merged into a previously installed job */
+ if (tr->anchor_job == j)
+ tr->anchor_job = installed_job;
+ hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
+ job_free(j);
+ j = installed_job;
+ }
+
+ job_add_to_run_queue(j);
+ job_add_to_dbus_queue(j);
+ job_start_timer(j);
+ }
+
+ return 0;
+
+rollback:
+
+ HASHMAP_FOREACH(j, tr->jobs, i)
+ hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
+
+ return r;
+}
+
+int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e) {
+ Iterator i;
+ Job *j;
+ int r;
+ unsigned generation = 1;
+
+ assert(tr);
+
+ /* This applies the changes recorded in tr->jobs to
+ * the actual list of jobs, if possible. */
+
+ /* Reset the generation counter of all installed jobs. The detection of cycles
+ * looks at installed jobs. If they had a non-zero generation from some previous
+ * walk of the graph, the algorithm would break. */
+ HASHMAP_FOREACH(j, m->jobs, i)
+ j->generation = 0;
+
+ /* First step: figure out which jobs matter */
+ transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++);
+
+ /* Second step: Try not to stop any running services if
+ * we don't have to. Don't try to reverse running
+ * jobs if we don't have to. */
+ if (mode == JOB_FAIL)
+ transaction_minimize_impact(tr);
+
+ /* Third step: Drop redundant jobs */
+ transaction_drop_redundant(tr);
+
+ for (;;) {
+ /* Fourth step: Let's remove unneeded jobs that might
+ * be lurking. */
+ if (mode != JOB_ISOLATE)
+ transaction_collect_garbage(tr);
+
+ /* Fifth step: verify order makes sense and correct
+ * cycles if necessary and possible */
+ r = transaction_verify_order(tr, &generation, e);
+ if (r >= 0)
+ break;
+
+ if (r != -EAGAIN) {
+ log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r));
+ return r;
+ }
+
+ /* Let's see if the resulting transaction ordering
+ * graph is still cyclic... */
+ }
+
+ for (;;) {
+ /* Sixth step: let's drop unmergeable entries if
+ * necessary and possible, merge entries we can
+ * merge */
+ r = transaction_merge_jobs(tr, e);
+ if (r >= 0)
+ break;
+
+ if (r != -EAGAIN) {
+ log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r));
+ return r;
+ }
+
+ /* Seventh step: an entry got dropped, let's garbage
+ * collect its dependencies. */
+ if (mode != JOB_ISOLATE)
+ transaction_collect_garbage(tr);
+
+ /* Let's see if the resulting transaction still has
+ * unmergeable entries ... */
+ }
+
+ /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
+ transaction_drop_redundant(tr);
+
+ /* Ninth step: check whether we can actually apply this */
+ if (mode == JOB_FAIL) {
+ r = transaction_is_destructive(tr, e);
+ if (r < 0) {
+ log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r));
+ return r;
+ }
+ }
+
+ /* Tenth step: apply changes */
+ r = transaction_apply(tr, m, mode);
+ if (r < 0) {
+ log_warning("Failed to apply transaction: %s", strerror(-r));
+ return r;
+ }
+
+ assert(hashmap_isempty(tr->jobs));
+
+ if (!hashmap_isempty(m->jobs)) {
+ /* Are there any jobs now? Then make sure we have the
+ * idle pipe around. We don't really care too much
+ * whether this works or not, as the idle pipe is a
+ * feature for cosmetics, not actually useful for
+ * anything beyond that. */
+
+ if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0)
+ pipe2(m->idle_pipe, O_NONBLOCK|O_CLOEXEC);
+ }
+
+ return 0;
+}
+
+static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool override, bool *is_new) {
+ Job *j, *f;
+
+ assert(tr);
+ assert(unit);
+
+ /* Looks for an existing prospective job and returns that. If
+ * it doesn't exist it is created and added to the prospective
+ * jobs list. */
+
+ f = hashmap_get(tr->jobs, unit);
+
+ LIST_FOREACH(transaction, j, f) {
+ assert(j->unit == unit);
+
+ if (j->type == type) {
+ if (is_new)
+ *is_new = false;
+ return j;
+ }
+ }
+
+ j = job_new(unit, type);
+ if (!j)
+ return NULL;
+
+ j->generation = 0;
+ j->marker = NULL;
+ j->matters_to_anchor = false;
+ j->override = override;
+
+ LIST_PREPEND(Job, transaction, f, j);
+
+ if (hashmap_replace(tr->jobs, unit, f) < 0) {
+ LIST_REMOVE(Job, transaction, f, j);
+ job_free(j);
+ return NULL;
+ }
+
+ if (is_new)
+ *is_new = true;
+
+ /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */
+
+ return j;
+}
+
+static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) {
+ assert(tr);
+ assert(j);
+
+ if (j->transaction_prev)
+ j->transaction_prev->transaction_next = j->transaction_next;
+ else if (j->transaction_next)
+ hashmap_replace(tr->jobs, j->unit, j->transaction_next);
+ else
+ hashmap_remove_value(tr->jobs, j->unit, j);
+
+ if (j->transaction_next)
+ j->transaction_next->transaction_prev = j->transaction_prev;
+
+ j->transaction_prev = j->transaction_next = NULL;
+
+ while (j->subject_list)
+ job_dependency_free(j->subject_list);
+
+ while (j->object_list) {
+ Job *other = j->object_list->matters ? j->object_list->subject : NULL;
+
+ job_dependency_free(j->object_list);
+
+ if (other && delete_dependencies) {
+ log_debug("Deleting job %s/%s as dependency of job %s/%s",
+ other->unit->id, job_type_to_string(other->type),
+ j->unit->id, job_type_to_string(j->type));
+ transaction_delete_job(tr, other, delete_dependencies);
+ }
+ }
+}
+
+int transaction_add_job_and_dependencies(
+ Transaction *tr,
+ JobType type,
+ Unit *unit,
+ Job *by,
+ bool matters,
+ bool override,
+ bool conflicts,
+ bool ignore_requirements,
+ bool ignore_order,
+ DBusError *e) {
+ Job *ret;
+ Iterator i;
+ Unit *dep;
+ int r;
+ bool is_new;
+
+ assert(tr);
+ assert(type < _JOB_TYPE_MAX);
+ assert(type < _JOB_TYPE_MAX_IN_TRANSACTION);
+ assert(unit);
+
+ /* log_debug("Pulling in %s/%s from %s/%s", */
+ /* unit->id, job_type_to_string(type), */
+ /* by ? by->unit->id : "NA", */
+ /* by ? job_type_to_string(by->type) : "NA"); */
+
+ if (unit->load_state != UNIT_LOADED &&
+ unit->load_state != UNIT_ERROR &&
+ unit->load_state != UNIT_MASKED) {
+ dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id);
+ return -EINVAL;
+ }
+
+ if (type != JOB_STOP && unit->load_state == UNIT_ERROR) {
+ dbus_set_error(e, BUS_ERROR_LOAD_FAILED,
+ "Unit %s failed to load: %s. "
+ "See system logs and 'systemctl status %s' for details.",
+ unit->id,
+ strerror(-unit->load_error),
+ unit->id);
+ return -EINVAL;
+ }
+
+ if (type != JOB_STOP && unit->load_state == UNIT_MASKED) {
+ dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id);
+ return -EADDRNOTAVAIL;
+ }
+
+ if (!unit_job_is_applicable(unit, type)) {
+ dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->id);
+ return -EBADR;
+ }
+
+ /* First add the job. */
+ ret = transaction_add_one_job(tr, type, unit, override, &is_new);
+ if (!ret)
+ return -ENOMEM;
+
+ ret->ignore_order = ret->ignore_order || ignore_order;
+
+ /* Then, add a link to the job. */
+ if (by) {
+ if (!job_dependency_new(by, ret, matters, conflicts))
+ return -ENOMEM;
+ } else {
+ /* If the job has no parent job, it is the anchor job. */
+ assert(!tr->anchor_job);
+ tr->anchor_job = ret;
+ }
+
+ if (is_new && !ignore_requirements && type != JOB_NOP) {
+ Set *following;
+
+ /* If we are following some other unit, make sure we
+ * add all dependencies of everybody following. */
+ if (unit_following_set(ret->unit, &following) > 0) {
+ SET_FOREACH(dep, following, i) {
+ r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, override, false, false, ignore_order, e);
+ if (r < 0) {
+ log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ set_free(following);
+ }
+
+ /* Finally, recursively add in all dependencies. */
+ if (type == JOB_START || type == JOB_RESTART) {
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_BINDS_TO], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, !override, override, false, false, ignore_order, e);
+ if (r < 0) {
+ log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_WARNING,
+ "Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, false, ignore_order, e);
+ if (r < 0) {
+ log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_WARNING,
+ "Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e);
+ if (r < 0) {
+ log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_WARNING,
+ "Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e);
+ if (r < 0) {
+ log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ }
+
+ if (type == JOB_STOP || type == JOB_RESTART) {
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) {
+ r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) {
+ r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONSISTS_OF], i) {
+ r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e);
+ if (r < 0) {
+ if (r != -EBADR)
+ goto fail;
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+
+ }
+
+ if (type == JOB_RELOAD) {
+
+ SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATES_RELOAD_TO], i) {
+ r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e);
+ if (r < 0) {
+ log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+ if (e)
+ dbus_error_free(e);
+ }
+ }
+ }
+
+ /* JOB_VERIFY_STARTED require no dependency handling */
+ }
+
+ return 0;
+
+fail:
+ return r;
+}
+
+int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
+ Iterator i;
+ Unit *u;
+ char *k;
+ int r;
+
+ assert(tr);
+ assert(m);
+
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+
+ /* ignore aliases */
+ if (u->id != k)
+ continue;
+
+ if (u->ignore_on_isolate)
+ continue;
+
+ /* No need to stop inactive jobs */
+ if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job)
+ continue;
+
+ /* Is there already something listed for this? */
+ if (hashmap_get(tr->jobs, u))
+ continue;
+
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, false, NULL);
+ if (r < 0)
+ log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r));
+ }
+
+ return 0;
+}
+
+Transaction *transaction_new(void) {
+ Transaction *tr;
+
+ tr = new0(Transaction, 1);
+ if (!tr)
+ return NULL;
+
+ tr->jobs = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!tr->jobs) {
+ free(tr);
+ return NULL;
+ }
+
+ return tr;
+}
+
+void transaction_free(Transaction *tr) {
+ assert(hashmap_isempty(tr->jobs));
+ hashmap_free(tr->jobs);
+ free(tr);
+}
diff --git a/src/core/transaction.h b/src/core/transaction.h
new file mode 100644
index 0000000000..67ace4da0b
--- /dev/null
+++ b/src/core/transaction.h
@@ -0,0 +1,56 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef footransactionhfoo
+#define footransactionhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Transaction Transaction;
+
+#include "unit.h"
+#include "manager.h"
+#include "job.h"
+#include "hashmap.h"
+
+struct Transaction {
+ /* Jobs to be added */
+ Hashmap *jobs; /* Unit object => Job object list 1:1 */
+ Job *anchor_job; /* the job the user asked for */
+};
+
+Transaction *transaction_new(void);
+void transaction_free(Transaction *tr);
+
+int transaction_add_job_and_dependencies(
+ Transaction *tr,
+ JobType type,
+ Unit *unit,
+ Job *by,
+ bool matters,
+ bool override,
+ bool conflicts,
+ bool ignore_requirements,
+ bool ignore_order,
+ DBusError *e);
+int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e);
+int transaction_add_isolate_jobs(Transaction *tr, Manager *m);
+void transaction_abort(Transaction *tr);
+
+#endif
diff --git a/src/core/umount.c b/src/core/umount.c
new file mode 100644
index 0000000000..83c9de3e82
--- /dev/null
+++ b/src/core/umount.c
@@ -0,0 +1,639 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/swap.h>
+#include <unistd.h>
+#include <linux/loop.h>
+#include <linux/dm-ioctl.h>
+#include <libudev.h>
+
+#include "list.h"
+#include "mount-setup.h"
+#include "umount.h"
+#include "path-util.h"
+#include "util.h"
+#include "virt.h"
+
+typedef struct MountPoint {
+ char *path;
+ dev_t devnum;
+ LIST_FIELDS (struct MountPoint, mount_point);
+} MountPoint;
+
+static void mount_point_free(MountPoint **head, MountPoint *m) {
+ assert(head);
+ assert(m);
+
+ LIST_REMOVE(MountPoint, mount_point, *head, m);
+
+ free(m->path);
+ free(m);
+}
+
+static void mount_points_list_free(MountPoint **head) {
+ assert(head);
+
+ while (*head)
+ mount_point_free(head, *head);
+}
+
+static int mount_points_list_get(MountPoint **head) {
+ FILE *proc_self_mountinfo;
+ char *path, *p;
+ unsigned int i;
+ int r;
+
+ assert(head);
+
+ if (!(proc_self_mountinfo = fopen("/proc/self/mountinfo", "re")))
+ return -errno;
+
+ for (i = 1;; i++) {
+ int k;
+ MountPoint *m;
+
+ path = p = NULL;
+
+ if ((k = fscanf(proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%*s" /* (6) mount options */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%*s " /* (9) file system type */
+ "%*s" /* (10) mount source */
+ "%*s" /* (11) mount options 2 */
+ "%*[^\n]", /* some rubbish at the end */
+ &path)) != 1) {
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
+
+ free(path);
+ continue;
+ }
+
+ p = cunescape(path);
+ free(path);
+
+ if (!p) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ /* Ignore mount points we can't unmount because they
+ * are API or because we are keeping them open (like
+ * /dev/console) */
+ if (mount_point_is_api(p) ||
+ mount_point_ignore(p) ||
+ path_equal(p, "/dev/console")) {
+ free(p);
+ continue;
+ }
+
+ if (!(m = new0(MountPoint, 1))) {
+ free(p);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ m->path = p;
+ LIST_PREPEND(MountPoint, mount_point, *head, m);
+ }
+
+ r = 0;
+
+finish:
+ fclose(proc_self_mountinfo);
+
+ return r;
+}
+
+static int swap_list_get(MountPoint **head) {
+ FILE *proc_swaps;
+ unsigned int i;
+ int r;
+
+ assert(head);
+
+ if (!(proc_swaps = fopen("/proc/swaps", "re")))
+ return (errno == ENOENT) ? 0 : -errno;
+
+ (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 2;; i++) {
+ MountPoint *swap;
+ char *dev = NULL, *d;
+ int k;
+
+ if ((k = fscanf(proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%*s\n", /* priority */
+ &dev)) != 1) {
+
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u.", i);
+
+ free(dev);
+ continue;
+ }
+
+ if (endswith(dev, "(deleted)")) {
+ free(dev);
+ continue;
+ }
+
+ d = cunescape(dev);
+ free(dev);
+
+ if (!d) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(swap = new0(MountPoint, 1))) {
+ free(d);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ swap->path = d;
+ LIST_PREPEND(MountPoint, mount_point, *head, swap);
+ }
+
+ r = 0;
+
+finish:
+ fclose(proc_swaps);
+
+ return r;
+}
+
+static int loopback_list_get(MountPoint **head) {
+ int r;
+ struct udev *udev;
+ struct udev_enumerate *e = NULL;
+ struct udev_list_entry *item = NULL, *first = NULL;
+
+ assert(head);
+
+ if (!(udev = udev_new())) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(e = udev_enumerate_new(udev))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (udev_enumerate_add_match_subsystem(e, "block") < 0 ||
+ udev_enumerate_add_match_sysname(e, "loop*") < 0) {
+ r = -EIO;
+ goto finish;
+ }
+
+ if (udev_enumerate_scan_devices(e) < 0) {
+ r = -EIO;
+ goto finish;
+ }
+
+ first = udev_enumerate_get_list_entry(e);
+ udev_list_entry_foreach(item, first) {
+ MountPoint *lb;
+ struct udev_device *d;
+ char *loop;
+ const char *dn;
+
+ if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(dn = udev_device_get_devnode(d))) {
+ udev_device_unref(d);
+ continue;
+ }
+
+ loop = strdup(dn);
+ udev_device_unref(d);
+
+ if (!loop) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(lb = new0(MountPoint, 1))) {
+ free(loop);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ lb->path = loop;
+ LIST_PREPEND(MountPoint, mount_point, *head, lb);
+ }
+
+ r = 0;
+
+finish:
+ if (e)
+ udev_enumerate_unref(e);
+
+ if (udev)
+ udev_unref(udev);
+
+ return r;
+}
+
+static int dm_list_get(MountPoint **head) {
+ int r;
+ struct udev *udev;
+ struct udev_enumerate *e = NULL;
+ struct udev_list_entry *item = NULL, *first = NULL;
+
+ assert(head);
+
+ if (!(udev = udev_new())) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(e = udev_enumerate_new(udev))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (udev_enumerate_add_match_subsystem(e, "block") < 0 ||
+ udev_enumerate_add_match_sysname(e, "dm-*") < 0) {
+ r = -EIO;
+ goto finish;
+ }
+
+ if (udev_enumerate_scan_devices(e) < 0) {
+ r = -EIO;
+ goto finish;
+ }
+
+ first = udev_enumerate_get_list_entry(e);
+
+ udev_list_entry_foreach(item, first) {
+ MountPoint *m;
+ struct udev_device *d;
+ dev_t devnum;
+ char *node;
+ const char *dn;
+
+ if (!(d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ devnum = udev_device_get_devnum(d);
+ dn = udev_device_get_devnode(d);
+
+ if (major(devnum) == 0 || !dn) {
+ udev_device_unref(d);
+ continue;
+ }
+
+ node = strdup(dn);
+ udev_device_unref(d);
+
+ if (!node) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(m = new(MountPoint, 1))) {
+ free(node);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ m->path = node;
+ m->devnum = devnum;
+ LIST_PREPEND(MountPoint, mount_point, *head, m);
+ }
+
+ r = 0;
+
+finish:
+ if (e)
+ udev_enumerate_unref(e);
+
+ if (udev)
+ udev_unref(udev);
+
+ return r;
+}
+
+static int delete_loopback(const char *device) {
+ int fd, r;
+
+ if ((fd = open(device, O_RDONLY|O_CLOEXEC)) < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ r = ioctl(fd, LOOP_CLR_FD, 0);
+ close_nointr_nofail(fd);
+
+ if (r >= 0)
+ return 1;
+
+ /* ENXIO: not bound, so no error */
+ if (errno == ENXIO)
+ return 0;
+
+ return -errno;
+}
+
+static int delete_dm(dev_t devnum) {
+ int fd, r;
+ struct dm_ioctl dm;
+
+ assert(major(devnum) != 0);
+
+ if ((fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC)) < 0)
+ return -errno;
+
+ zero(dm);
+ dm.version[0] = DM_VERSION_MAJOR;
+ dm.version[1] = DM_VERSION_MINOR;
+ dm.version[2] = DM_VERSION_PATCHLEVEL;
+
+ dm.data_size = sizeof(dm);
+ dm.dev = devnum;
+
+ r = ioctl(fd, DM_DEV_REMOVE, &dm);
+ close_nointr_nofail(fd);
+
+ return r >= 0 ? 0 : -errno;
+}
+
+static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) {
+ MountPoint *m, *n;
+ int n_failed = 0;
+
+ assert(head);
+
+ LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+ if (path_equal(m->path, "/")
+#ifndef HAVE_SPLIT_USR
+ || path_equal(m->path, "/usr")
+#endif
+ ) {
+ n_failed++;
+ continue;
+ }
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(m->path, MNT_FORCE) == 0) {
+ log_info("Unmounted %s.", m->path);
+ if (changed)
+ *changed = true;
+
+ mount_point_free(head, m);
+ } else if (log_error) {
+ log_warning("Could not unmount %s: %m", m->path);
+ n_failed++;
+ }
+ }
+
+ return n_failed;
+}
+
+static int mount_points_list_remount_read_only(MountPoint **head, bool *changed) {
+ MountPoint *m, *n;
+ int n_failed = 0;
+
+ assert(head);
+
+ LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+
+ /* Trying to remount read-only */
+ if (mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, NULL) == 0) {
+ if (changed)
+ *changed = true;
+
+ mount_point_free(head, m);
+ } else {
+ log_warning("Could not remount as read-only %s: %m", m->path);
+ n_failed++;
+ }
+ }
+
+ return n_failed;
+}
+
+static int swap_points_list_off(MountPoint **head, bool *changed) {
+ MountPoint *m, *n;
+ int n_failed = 0;
+
+ assert(head);
+
+ LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+ if (swapoff(m->path) == 0) {
+ if (changed)
+ *changed = true;
+
+ mount_point_free(head, m);
+ } else {
+ log_warning("Could not deactivate swap %s: %m", m->path);
+ n_failed++;
+ }
+ }
+
+ return n_failed;
+}
+
+static int loopback_points_list_detach(MountPoint **head, bool *changed) {
+ MountPoint *m, *n;
+ int n_failed = 0, k;
+ struct stat root_st;
+
+ assert(head);
+
+ k = lstat("/", &root_st);
+
+ LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+ int r;
+ struct stat loopback_st;
+
+ if (k >= 0 &&
+ major(root_st.st_dev) != 0 &&
+ lstat(m->path, &loopback_st) >= 0 &&
+ root_st.st_dev == loopback_st.st_rdev) {
+ n_failed ++;
+ continue;
+ }
+
+ if ((r = delete_loopback(m->path)) >= 0) {
+
+ if (r > 0 && changed)
+ *changed = true;
+
+ mount_point_free(head, m);
+ } else {
+ log_warning("Could not delete loopback %s: %m", m->path);
+ n_failed++;
+ }
+ }
+
+ return n_failed;
+}
+
+static int dm_points_list_detach(MountPoint **head, bool *changed) {
+ MountPoint *m, *n;
+ int n_failed = 0, k;
+ struct stat root_st;
+
+ assert(head);
+
+ k = lstat("/", &root_st);
+
+ LIST_FOREACH_SAFE(mount_point, m, n, *head) {
+ int r;
+
+ if (k >= 0 &&
+ major(root_st.st_dev) != 0 &&
+ root_st.st_dev == m->devnum) {
+ n_failed ++;
+ continue;
+ }
+
+ if ((r = delete_dm(m->devnum)) >= 0) {
+
+ if (r > 0 && changed)
+ *changed = true;
+
+ mount_point_free(head, m);
+ } else {
+ log_warning("Could not delete dm %s: %m", m->path);
+ n_failed++;
+ }
+ }
+
+ return n_failed;
+}
+
+int umount_all(bool *changed) {
+ int r;
+ bool umount_changed;
+ LIST_HEAD(MountPoint, mp_list_head);
+
+ LIST_HEAD_INIT(MountPoint, mp_list_head);
+ r = mount_points_list_get(&mp_list_head);
+ if (r < 0)
+ goto end;
+
+ /* retry umount, until nothing can be umounted anymore */
+ do {
+ umount_changed = false;
+
+ mount_points_list_umount(&mp_list_head, &umount_changed, false);
+ if (umount_changed)
+ *changed = true;
+
+ } while (umount_changed);
+
+ /* umount one more time with logging enabled */
+ r = mount_points_list_umount(&mp_list_head, &umount_changed, true);
+ if (r <= 0)
+ goto end;
+
+ /* If we are in a container, don't attempt to read-only mount
+ anything as that brings no real benefits, but might confuse
+ the host, as we remount the superblock here, not the bind
+ mound. */
+ if (detect_container(NULL) <= 0)
+ r = mount_points_list_remount_read_only(&mp_list_head, changed);
+
+ end:
+ mount_points_list_free(&mp_list_head);
+
+ return r;
+}
+
+int swapoff_all(bool *changed) {
+ int r;
+ LIST_HEAD(MountPoint, swap_list_head);
+
+ LIST_HEAD_INIT(MountPoint, swap_list_head);
+
+ r = swap_list_get(&swap_list_head);
+ if (r < 0)
+ goto end;
+
+ r = swap_points_list_off(&swap_list_head, changed);
+
+ end:
+ mount_points_list_free(&swap_list_head);
+
+ return r;
+}
+
+int loopback_detach_all(bool *changed) {
+ int r;
+ LIST_HEAD(MountPoint, loopback_list_head);
+
+ LIST_HEAD_INIT(MountPoint, loopback_list_head);
+
+ r = loopback_list_get(&loopback_list_head);
+ if (r < 0)
+ goto end;
+
+ r = loopback_points_list_detach(&loopback_list_head, changed);
+
+ end:
+ mount_points_list_free(&loopback_list_head);
+
+ return r;
+}
+
+int dm_detach_all(bool *changed) {
+ int r;
+ LIST_HEAD(MountPoint, dm_list_head);
+
+ LIST_HEAD_INIT(MountPoint, dm_list_head);
+
+ r = dm_list_get(&dm_list_head);
+ if (r < 0)
+ goto end;
+
+ r = dm_points_list_detach(&dm_list_head, changed);
+
+ end:
+ mount_points_list_free(&dm_list_head);
+
+ return r;
+}
diff --git a/src/core/umount.h b/src/core/umount.h
new file mode 100644
index 0000000000..8439ffe58f
--- /dev/null
+++ b/src/core/umount.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int umount_all(bool *changed);
+
+int swapoff_all(bool *changed);
+
+int loopback_detach_all(bool *changed);
+
+int dm_detach_all(bool *changed);
diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c
new file mode 100644
index 0000000000..cbae45d9f7
--- /dev/null
+++ b/src/core/unit-printf.c
@@ -0,0 +1,346 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "systemd/sd-id128.h"
+#include "unit.h"
+#include "specifier.h"
+#include "path-util.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "unit-printf.h"
+
+static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ return unit_name_to_prefix_and_instance(u->id);
+}
+
+static char *specifier_prefix(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ return unit_name_to_prefix(u->id);
+}
+
+static char *specifier_prefix_unescaped(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ char *p, *r;
+
+ assert(u);
+
+ p = unit_name_to_prefix(u->id);
+ if (!p)
+ return NULL;
+
+ r = unit_name_unescape(p);
+ free(p);
+
+ return r;
+}
+
+static char *specifier_instance_unescaped(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ if (u->instance)
+ return unit_name_unescape(u->instance);
+
+ return strdup("");
+}
+
+static char *specifier_filename(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ if (u->instance)
+ return unit_name_path_unescape(u->instance);
+
+ return unit_name_to_path(u->id);
+}
+
+static char *specifier_cgroup(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ return unit_default_cgroup_path(u);
+}
+
+static char *specifier_cgroup_root(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ char *p;
+ assert(u);
+
+ if (specifier == 'r')
+ return strdup(u->manager->cgroup_hierarchy);
+
+ if (path_get_parent(u->manager->cgroup_hierarchy, &p) < 0)
+ return strdup("");
+
+ if (streq(p, "/")) {
+ free(p);
+ return strdup("");
+ }
+
+ return p;
+}
+
+static char *specifier_runtime(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ if (u->manager->running_as == SYSTEMD_USER) {
+ const char *e;
+
+ e = getenv("XDG_RUNTIME_DIR");
+ if (e)
+ return strdup(e);
+ }
+
+ return strdup("/run");
+}
+
+static char *specifier_user_name(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ ExecContext *c;
+ int r;
+ const char *username;
+
+ assert(u);
+
+ c = unit_get_exec_context(u);
+ if (!c)
+ return NULL;
+
+ /* get USER env from our own env if set */
+ if (!c->user)
+ return getusername_malloc();
+
+ /* fish username from passwd */
+ username = c->user;
+ r = get_user_creds(&username, NULL, NULL, NULL, NULL);
+ if (r < 0)
+ return NULL;
+
+ return strdup(username);
+}
+
+static char *specifier_user_home(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ ExecContext *c;
+ int r;
+ const char *username, *home;
+
+ assert(u);
+
+ c = unit_get_exec_context(u);
+ if (!c)
+ return NULL;
+
+ /* return HOME if set, otherwise from passwd */
+ if (!c->user) {
+ char *h;
+
+ r = get_home_dir(&h);
+ if (r < 0)
+ return NULL;
+
+ return h;
+ }
+
+ username = c->user;
+ r = get_user_creds(&username, NULL, NULL, &home, NULL);
+ if (r < 0)
+ return NULL;
+
+ return strdup(home);
+}
+
+static char *specifier_user_shell(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ ExecContext *c;
+ int r;
+ const char *username, *shell;
+
+ assert(u);
+
+ c = unit_get_exec_context(u);
+ if (!c)
+ return NULL;
+
+ /* return HOME if set, otherwise from passwd */
+ if (!c->user) {
+ char *sh;
+
+ r = get_shell(&sh);
+ if (r < 0)
+ return strdup("/bin/sh");
+
+ return sh;
+ }
+
+ username = c->user;
+ r = get_user_creds(&username, NULL, NULL, NULL, &shell);
+ if (r < 0)
+ return strdup("/bin/sh");
+
+ return strdup(shell);
+}
+
+static char *specifier_machine_id(char specifier, void *data, void *userdata) {
+ sd_id128_t id;
+ char *buf;
+ int r;
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return NULL;
+
+ buf = new(char, 33);
+ if (!buf)
+ return NULL;
+
+ return sd_id128_to_string(id, buf);
+}
+
+static char *specifier_boot_id(char specifier, void *data, void *userdata) {
+ sd_id128_t id;
+ char *buf;
+ int r;
+
+ r = sd_id128_get_boot(&id);
+ if (r < 0)
+ return NULL;
+
+ buf = new(char, 33);
+ if (!buf)
+ return NULL;
+
+ return sd_id128_to_string(id, buf);
+}
+
+static char *specifier_host_name(char specifier, void *data, void *userdata) {
+ return gethostname_malloc();
+}
+
+char *unit_name_printf(Unit *u, const char* format) {
+
+ /*
+ * This will use the passed string as format string and
+ * replace the following specifiers:
+ *
+ * %n: the full id of the unit (foo@bar.waldo)
+ * %N: the id of the unit without the suffix (foo@bar)
+ * %p: the prefix (foo)
+ * %i: the instance (bar)
+ */
+
+ const Specifier table[] = {
+ { 'n', specifier_string, u->id },
+ { 'N', specifier_prefix_and_instance, NULL },
+ { 'p', specifier_prefix, NULL },
+ { 'i', specifier_string, u->instance },
+ { 0, NULL, NULL }
+ };
+
+ assert(u);
+ assert(format);
+
+ return specifier_printf(format, table, u);
+}
+
+char *unit_full_printf(Unit *u, const char *format) {
+
+ /* This is similar to unit_name_printf() but also supports
+ * unescaping. Also, adds a couple of additional codes:
+ *
+ * %f the the instance if set, otherwise the id
+ * %c cgroup path of unit
+ * %r root cgroup path of this systemd instance (e.g. "/user/lennart/shared/systemd-4711")
+ * %R parent of root cgroup path (e.g. "/usr/lennart/shared")
+ * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR)
+ * %u the username of the configured user or running user
+ * %h the homedir of the configured user or running user
+ * %s the shell of the configured user or running user
+ * %m the machine ID of the running system
+ * %b the boot ID of the running system
+ * %H the host name of the running system
+ */
+
+ const Specifier table[] = {
+ { 'n', specifier_string, u->id },
+ { 'N', specifier_prefix_and_instance, NULL },
+ { 'p', specifier_prefix, NULL },
+ { 'P', specifier_prefix_unescaped, NULL },
+ { 'i', specifier_string, u->instance },
+ { 'I', specifier_instance_unescaped, NULL },
+
+ { 'f', specifier_filename, NULL },
+ { 'c', specifier_cgroup, NULL },
+ { 'r', specifier_cgroup_root, NULL },
+ { 'R', specifier_cgroup_root, NULL },
+ { 't', specifier_runtime, NULL },
+ { 'u', specifier_user_name, NULL },
+ { 'h', specifier_user_home, NULL },
+ { 's', specifier_user_shell, NULL },
+
+ { 'm', specifier_machine_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 0, NULL, NULL }
+ };
+
+ assert(u);
+ assert(format);
+
+ return specifier_printf(format, table, u);
+}
+
+char **unit_full_printf_strv(Unit *u, char **l) {
+ size_t n;
+ char **r, **i, **j;
+
+ /* Applies unit_full_printf to every entry in l */
+
+ assert(u);
+
+ n = strv_length(l);
+ r = new(char*, n+1);
+ if (!r)
+ return NULL;
+
+ for (i = l, j = r; *i; i++, j++) {
+ *j = unit_full_printf(u, *i);
+ if (!*j)
+ goto fail;
+ }
+
+ *j = NULL;
+ return r;
+
+fail:
+ for (j--; j >= r; j--)
+ free(*j);
+
+ free(r);
+
+ return NULL;
+}
diff --git a/src/core/unit-printf.h b/src/core/unit-printf.h
new file mode 100644
index 0000000000..d2f4ccd178
--- /dev/null
+++ b/src/core/unit-printf.h
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "unit.h"
+
+char *unit_name_printf(Unit *u, const char* text);
+char *unit_full_printf(Unit *u, const char *text);
+char **unit_full_printf_strv(Unit *u, char **l);
diff --git a/src/core/unit.c b/src/core/unit.c
new file mode 100644
index 0000000000..99e1c27948
--- /dev/null
+++ b/src/core/unit.c
@@ -0,0 +1,2738 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/poll.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "systemd/sd-id128.h"
+#include "systemd/sd-messages.h"
+#include "set.h"
+#include "unit.h"
+#include "macro.h"
+#include "strv.h"
+#include "path-util.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "unit-name.h"
+#include "dbus-unit.h"
+#include "special.h"
+#include "cgroup-util.h"
+#include "missing.h"
+#include "cgroup-attr.h"
+
+const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = {
+ [UNIT_SERVICE] = &service_vtable,
+ [UNIT_TIMER] = &timer_vtable,
+ [UNIT_SOCKET] = &socket_vtable,
+ [UNIT_TARGET] = &target_vtable,
+ [UNIT_DEVICE] = &device_vtable,
+ [UNIT_MOUNT] = &mount_vtable,
+ [UNIT_AUTOMOUNT] = &automount_vtable,
+ [UNIT_SNAPSHOT] = &snapshot_vtable,
+ [UNIT_SWAP] = &swap_vtable,
+ [UNIT_PATH] = &path_vtable
+};
+
+Unit *unit_new(Manager *m, size_t size) {
+ Unit *u;
+
+ assert(m);
+ assert(size >= sizeof(Unit));
+
+ u = malloc0(size);
+ if (!u)
+ return NULL;
+
+ u->names = set_new(string_hash_func, string_compare_func);
+ if (!u->names) {
+ free(u);
+ return NULL;
+ }
+
+ u->manager = m;
+ u->type = _UNIT_TYPE_INVALID;
+ u->deserialized_job = _JOB_TYPE_INVALID;
+ u->default_dependencies = true;
+ u->unit_file_state = _UNIT_FILE_STATE_INVALID;
+
+ return u;
+}
+
+bool unit_has_name(Unit *u, const char *name) {
+ assert(u);
+ assert(name);
+
+ return !!set_get(u->names, (char*) name);
+}
+
+int unit_add_name(Unit *u, const char *text) {
+ UnitType t;
+ char *s, *i = NULL;
+ int r;
+
+ assert(u);
+ assert(text);
+
+ if (unit_name_is_template(text)) {
+ if (!u->instance)
+ return -EINVAL;
+
+ s = unit_name_replace_instance(text, u->instance);
+ } else
+ s = strdup(text);
+
+ if (!s)
+ return -ENOMEM;
+
+ if (!unit_name_is_valid(s, false)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ assert_se((t = unit_name_to_type(s)) >= 0);
+
+ if (u->type != _UNIT_TYPE_INVALID && t != u->type) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if ((r = unit_name_to_instance(s, &i)) < 0)
+ goto fail;
+
+ if (i && unit_vtable[t]->no_instances) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ /* Ensure that this unit is either instanced or not instanced,
+ * but not both. */
+ if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (unit_vtable[t]->no_alias &&
+ !set_isempty(u->names) &&
+ !set_get(u->names, s)) {
+ r = -EEXIST;
+ goto fail;
+ }
+
+ if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) {
+ r = -E2BIG;
+ goto fail;
+ }
+
+ if ((r = set_put(u->names, s)) < 0) {
+ if (r == -EEXIST)
+ r = 0;
+ goto fail;
+ }
+
+ if ((r = hashmap_put(u->manager->units, s, u)) < 0) {
+ set_remove(u->names, s);
+ goto fail;
+ }
+
+ if (u->type == _UNIT_TYPE_INVALID) {
+
+ u->type = t;
+ u->id = s;
+ u->instance = i;
+
+ LIST_PREPEND(Unit, units_by_type, u->manager->units_by_type[t], u);
+
+ if (UNIT_VTABLE(u)->init)
+ UNIT_VTABLE(u)->init(u);
+ } else
+ free(i);
+
+ unit_add_to_dbus_queue(u);
+ return 0;
+
+fail:
+ free(s);
+ free(i);
+
+ return r;
+}
+
+int unit_choose_id(Unit *u, const char *name) {
+ char *s, *t = NULL, *i;
+ int r;
+
+ assert(u);
+ assert(name);
+
+ if (unit_name_is_template(name)) {
+
+ if (!u->instance)
+ return -EINVAL;
+
+ if (!(t = unit_name_replace_instance(name, u->instance)))
+ return -ENOMEM;
+
+ name = t;
+ }
+
+ /* Selects one of the names of this unit as the id */
+ s = set_get(u->names, (char*) name);
+ free(t);
+
+ if (!s)
+ return -ENOENT;
+
+ if ((r = unit_name_to_instance(s, &i)) < 0)
+ return r;
+
+ u->id = s;
+
+ free(u->instance);
+ u->instance = i;
+
+ unit_add_to_dbus_queue(u);
+
+ return 0;
+}
+
+int unit_set_description(Unit *u, const char *description) {
+ char *s;
+
+ assert(u);
+
+ if (!(s = strdup(description)))
+ return -ENOMEM;
+
+ free(u->description);
+ u->description = s;
+
+ unit_add_to_dbus_queue(u);
+ return 0;
+}
+
+bool unit_check_gc(Unit *u) {
+ assert(u);
+
+ if (u->load_state == UNIT_STUB)
+ return true;
+
+ if (UNIT_VTABLE(u)->no_gc)
+ return true;
+
+ if (u->no_gc)
+ return true;
+
+ if (u->job)
+ return true;
+
+ if (u->nop_job)
+ return true;
+
+ if (unit_active_state(u) != UNIT_INACTIVE)
+ return true;
+
+ if (u->refs)
+ return true;
+
+ if (UNIT_VTABLE(u)->check_gc)
+ if (UNIT_VTABLE(u)->check_gc(u))
+ return true;
+
+ return false;
+}
+
+void unit_add_to_load_queue(Unit *u) {
+ assert(u);
+ assert(u->type != _UNIT_TYPE_INVALID);
+
+ if (u->load_state != UNIT_STUB || u->in_load_queue)
+ return;
+
+ LIST_PREPEND(Unit, load_queue, u->manager->load_queue, u);
+ u->in_load_queue = true;
+}
+
+void unit_add_to_cleanup_queue(Unit *u) {
+ assert(u);
+
+ if (u->in_cleanup_queue)
+ return;
+
+ LIST_PREPEND(Unit, cleanup_queue, u->manager->cleanup_queue, u);
+ u->in_cleanup_queue = true;
+}
+
+void unit_add_to_gc_queue(Unit *u) {
+ assert(u);
+
+ if (u->in_gc_queue || u->in_cleanup_queue)
+ return;
+
+ if (unit_check_gc(u))
+ return;
+
+ LIST_PREPEND(Unit, gc_queue, u->manager->gc_queue, u);
+ u->in_gc_queue = true;
+
+ u->manager->n_in_gc_queue ++;
+
+ if (u->manager->gc_queue_timestamp <= 0)
+ u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC);
+}
+
+void unit_add_to_dbus_queue(Unit *u) {
+ assert(u);
+ assert(u->type != _UNIT_TYPE_INVALID);
+
+ if (u->load_state == UNIT_STUB || u->in_dbus_queue)
+ return;
+
+ /* Shortcut things if nobody cares */
+ if (!bus_has_subscriber(u->manager)) {
+ u->sent_dbus_new_signal = true;
+ return;
+ }
+
+ LIST_PREPEND(Unit, dbus_queue, u->manager->dbus_unit_queue, u);
+ u->in_dbus_queue = true;
+}
+
+static void bidi_set_free(Unit *u, Set *s) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+
+ /* Frees the set and makes sure we are dropped from the
+ * inverse pointers */
+
+ SET_FOREACH(other, s, i) {
+ UnitDependency d;
+
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
+ set_remove(other->dependencies[d], u);
+
+ unit_add_to_gc_queue(other);
+ }
+
+ set_free(s);
+}
+
+void unit_free(Unit *u) {
+ UnitDependency d;
+ Iterator i;
+ char *t;
+
+ assert(u);
+
+ bus_unit_send_removed_signal(u);
+
+ if (u->load_state != UNIT_STUB)
+ if (UNIT_VTABLE(u)->done)
+ UNIT_VTABLE(u)->done(u);
+
+ SET_FOREACH(t, u->names, i)
+ hashmap_remove_value(u->manager->units, t, u);
+
+ if (u->job) {
+ Job *j = u->job;
+ job_uninstall(j);
+ job_free(j);
+ }
+
+ if (u->nop_job) {
+ Job *j = u->nop_job;
+ job_uninstall(j);
+ job_free(j);
+ }
+
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
+ bidi_set_free(u, u->dependencies[d]);
+
+ if (u->requires_mounts_for) {
+ LIST_REMOVE(Unit, has_requires_mounts_for, u->manager->has_requires_mounts_for, u);
+ strv_free(u->requires_mounts_for);
+ }
+
+ if (u->type != _UNIT_TYPE_INVALID)
+ LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u);
+
+ if (u->in_load_queue)
+ LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u);
+
+ if (u->in_dbus_queue)
+ LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u);
+
+ if (u->in_cleanup_queue)
+ LIST_REMOVE(Unit, cleanup_queue, u->manager->cleanup_queue, u);
+
+ if (u->in_gc_queue) {
+ LIST_REMOVE(Unit, gc_queue, u->manager->gc_queue, u);
+ u->manager->n_in_gc_queue--;
+ }
+
+ cgroup_bonding_free_list(u->cgroup_bondings, u->manager->n_reloading <= 0);
+ cgroup_attribute_free_list(u->cgroup_attributes);
+
+ free(u->description);
+ strv_free(u->documentation);
+ free(u->fragment_path);
+ free(u->source_path);
+ free(u->instance);
+
+ set_free_free(u->names);
+
+ condition_free_list(u->conditions);
+
+ while (u->refs)
+ unit_ref_unset(u->refs);
+
+ free(u);
+}
+
+UnitActiveState unit_active_state(Unit *u) {
+ assert(u);
+
+ if (u->load_state == UNIT_MERGED)
+ return unit_active_state(unit_follow_merge(u));
+
+ /* After a reload it might happen that a unit is not correctly
+ * loaded but still has a process around. That's why we won't
+ * shortcut failed loading to UNIT_INACTIVE_FAILED. */
+
+ return UNIT_VTABLE(u)->active_state(u);
+}
+
+const char* unit_sub_state_to_string(Unit *u) {
+ assert(u);
+
+ return UNIT_VTABLE(u)->sub_state_to_string(u);
+}
+
+static void complete_move(Set **s, Set **other) {
+ assert(s);
+ assert(other);
+
+ if (!*other)
+ return;
+
+ if (*s)
+ set_move(*s, *other);
+ else {
+ *s = *other;
+ *other = NULL;
+ }
+}
+
+static void merge_names(Unit *u, Unit *other) {
+ char *t;
+ Iterator i;
+
+ assert(u);
+ assert(other);
+
+ complete_move(&u->names, &other->names);
+
+ set_free_free(other->names);
+ other->names = NULL;
+ other->id = NULL;
+
+ SET_FOREACH(t, u->names, i)
+ assert_se(hashmap_replace(u->manager->units, t, u) == 0);
+}
+
+static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) {
+ Iterator i;
+ Unit *back;
+ int r;
+
+ assert(u);
+ assert(other);
+ assert(d < _UNIT_DEPENDENCY_MAX);
+
+ /* Fix backwards pointers */
+ SET_FOREACH(back, other->dependencies[d], i) {
+ UnitDependency k;
+
+ for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++)
+ if ((r = set_remove_and_put(back->dependencies[k], other, u)) < 0) {
+
+ if (r == -EEXIST)
+ set_remove(back->dependencies[k], other);
+ else
+ assert(r == -ENOENT);
+ }
+ }
+
+ complete_move(&u->dependencies[d], &other->dependencies[d]);
+
+ set_free(other->dependencies[d]);
+ other->dependencies[d] = NULL;
+}
+
+int unit_merge(Unit *u, Unit *other) {
+ UnitDependency d;
+
+ assert(u);
+ assert(other);
+ assert(u->manager == other->manager);
+ assert(u->type != _UNIT_TYPE_INVALID);
+
+ other = unit_follow_merge(other);
+
+ if (other == u)
+ return 0;
+
+ if (u->type != other->type)
+ return -EINVAL;
+
+ if (!u->instance != !other->instance)
+ return -EINVAL;
+
+ if (other->load_state != UNIT_STUB &&
+ other->load_state != UNIT_ERROR)
+ return -EEXIST;
+
+ if (other->job)
+ return -EEXIST;
+
+ if (other->nop_job)
+ return -EEXIST;
+
+ if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other)))
+ return -EEXIST;
+
+ /* Merge names */
+ merge_names(u, other);
+
+ /* Redirect all references */
+ while (other->refs)
+ unit_ref_set(other->refs, u);
+
+ /* Merge dependencies */
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
+ merge_dependencies(u, other, d);
+
+ other->load_state = UNIT_MERGED;
+ other->merged_into = u;
+
+ /* If there is still some data attached to the other node, we
+ * don't need it anymore, and can free it. */
+ if (other->load_state != UNIT_STUB)
+ if (UNIT_VTABLE(other)->done)
+ UNIT_VTABLE(other)->done(other);
+
+ unit_add_to_dbus_queue(u);
+ unit_add_to_cleanup_queue(other);
+
+ return 0;
+}
+
+int unit_merge_by_name(Unit *u, const char *name) {
+ Unit *other;
+ int r;
+ char *s = NULL;
+
+ assert(u);
+ assert(name);
+
+ if (unit_name_is_template(name)) {
+ if (!u->instance)
+ return -EINVAL;
+
+ if (!(s = unit_name_replace_instance(name, u->instance)))
+ return -ENOMEM;
+
+ name = s;
+ }
+
+ if (!(other = manager_get_unit(u->manager, name)))
+ r = unit_add_name(u, name);
+ else
+ r = unit_merge(u, other);
+
+ free(s);
+ return r;
+}
+
+Unit* unit_follow_merge(Unit *u) {
+ assert(u);
+
+ while (u->load_state == UNIT_MERGED)
+ assert_se(u = u->merged_into);
+
+ return u;
+}
+
+int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
+ int r;
+
+ assert(u);
+ assert(c);
+
+ if (c->std_output != EXEC_OUTPUT_KMSG &&
+ c->std_output != EXEC_OUTPUT_SYSLOG &&
+ c->std_output != EXEC_OUTPUT_JOURNAL &&
+ c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE &&
+ c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE &&
+ c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE &&
+ c->std_error != EXEC_OUTPUT_KMSG &&
+ c->std_error != EXEC_OUTPUT_SYSLOG &&
+ c->std_error != EXEC_OUTPUT_JOURNAL &&
+ c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE &&
+ c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE &&
+ c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE)
+ return 0;
+
+ /* If syslog or kernel logging is requested, make sure our own
+ * logging daemon is run first. */
+
+ if (u->manager->running_as == SYSTEMD_SYSTEM)
+ if ((r = unit_add_two_dependencies_by_name(u, UNIT_REQUIRES, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true)) < 0)
+ return r;
+
+ return 0;
+}
+
+const char *unit_description(Unit *u) {
+ assert(u);
+
+ if (u->description)
+ return u->description;
+
+ return strna(u->id);
+}
+
+void unit_dump(Unit *u, FILE *f, const char *prefix) {
+ char *t, **j;
+ UnitDependency d;
+ Iterator i;
+ char *p2;
+ const char *prefix2;
+ char
+ timestamp1[FORMAT_TIMESTAMP_MAX],
+ timestamp2[FORMAT_TIMESTAMP_MAX],
+ timestamp3[FORMAT_TIMESTAMP_MAX],
+ timestamp4[FORMAT_TIMESTAMP_MAX],
+ timespan[FORMAT_TIMESPAN_MAX];
+ Unit *following;
+
+ assert(u);
+ assert(u->type >= 0);
+
+ if (!prefix)
+ prefix = "";
+ p2 = strappend(prefix, "\t");
+ prefix2 = p2 ? p2 : prefix;
+
+ fprintf(f,
+ "%s-> Unit %s:\n"
+ "%s\tDescription: %s\n"
+ "%s\tInstance: %s\n"
+ "%s\tUnit Load State: %s\n"
+ "%s\tUnit Active State: %s\n"
+ "%s\tInactive Exit Timestamp: %s\n"
+ "%s\tActive Enter Timestamp: %s\n"
+ "%s\tActive Exit Timestamp: %s\n"
+ "%s\tInactive Enter Timestamp: %s\n"
+ "%s\tGC Check Good: %s\n"
+ "%s\tNeed Daemon Reload: %s\n",
+ prefix, u->id,
+ prefix, unit_description(u),
+ prefix, strna(u->instance),
+ prefix, unit_load_state_to_string(u->load_state),
+ prefix, unit_active_state_to_string(unit_active_state(u)),
+ prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)),
+ prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)),
+ prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)),
+ prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)),
+ prefix, yes_no(unit_check_gc(u)),
+ prefix, yes_no(unit_need_daemon_reload(u)));
+
+ SET_FOREACH(t, u->names, i)
+ fprintf(f, "%s\tName: %s\n", prefix, t);
+
+ STRV_FOREACH(j, u->documentation)
+ fprintf(f, "%s\tDocumentation: %s\n", prefix, *j);
+
+ if ((following = unit_following(u)))
+ fprintf(f, "%s\tFollowing: %s\n", prefix, following->id);
+
+ if (u->fragment_path)
+ fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path);
+
+ if (u->source_path)
+ fprintf(f, "%s\tSource Path: %s\n", prefix, u->source_path);
+
+ if (u->job_timeout > 0)
+ fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout));
+
+ condition_dump_list(u->conditions, f, prefix);
+
+ if (dual_timestamp_is_set(&u->condition_timestamp))
+ fprintf(f,
+ "%s\tCondition Timestamp: %s\n"
+ "%s\tCondition Result: %s\n",
+ prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)),
+ prefix, yes_no(u->condition_result));
+
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) {
+ Unit *other;
+
+ SET_FOREACH(other, u->dependencies[d], i)
+ fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id);
+ }
+
+ if (!strv_isempty(u->requires_mounts_for)) {
+ fprintf(f,
+ "%s\tRequiresMountsFor:", prefix);
+
+ STRV_FOREACH(j, u->requires_mounts_for)
+ fprintf(f, " %s", *j);
+
+ fputs("\n", f);
+ }
+
+ if (u->load_state == UNIT_LOADED) {
+ CGroupBonding *b;
+ CGroupAttribute *a;
+
+ fprintf(f,
+ "%s\tStopWhenUnneeded: %s\n"
+ "%s\tRefuseManualStart: %s\n"
+ "%s\tRefuseManualStop: %s\n"
+ "%s\tDefaultDependencies: %s\n"
+ "%s\tOnFailureIsolate: %s\n"
+ "%s\tIgnoreOnIsolate: %s\n"
+ "%s\tIgnoreOnSnapshot: %s\n",
+ prefix, yes_no(u->stop_when_unneeded),
+ prefix, yes_no(u->refuse_manual_start),
+ prefix, yes_no(u->refuse_manual_stop),
+ prefix, yes_no(u->default_dependencies),
+ prefix, yes_no(u->on_failure_isolate),
+ prefix, yes_no(u->ignore_on_isolate),
+ prefix, yes_no(u->ignore_on_snapshot));
+
+ LIST_FOREACH(by_unit, b, u->cgroup_bondings)
+ fprintf(f, "%s\tControlGroup: %s:%s\n",
+ prefix, b->controller, b->path);
+
+ LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
+ char *v = NULL;
+
+ if (a->map_callback)
+ a->map_callback(a->controller, a->name, a->value, &v);
+
+ fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n",
+ prefix, a->controller, a->name, v ? v : a->value);
+
+ free(v);
+ }
+
+ if (UNIT_VTABLE(u)->dump)
+ UNIT_VTABLE(u)->dump(u, f, prefix2);
+
+ } else if (u->load_state == UNIT_MERGED)
+ fprintf(f,
+ "%s\tMerged into: %s\n",
+ prefix, u->merged_into->id);
+ else if (u->load_state == UNIT_ERROR)
+ fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error));
+
+
+ if (u->job)
+ job_dump(u->job, f, prefix2);
+
+ if (u->nop_job)
+ job_dump(u->nop_job, f, prefix2);
+
+ free(p2);
+}
+
+/* Common implementation for multiple backends */
+int unit_load_fragment_and_dropin(Unit *u) {
+ int r;
+
+ assert(u);
+
+ /* Load a .service file */
+ if ((r = unit_load_fragment(u)) < 0)
+ return r;
+
+ if (u->load_state == UNIT_STUB)
+ return -ENOENT;
+
+ /* Load drop-in directory data */
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
+ return r;
+
+ return 0;
+}
+
+/* Common implementation for multiple backends */
+int unit_load_fragment_and_dropin_optional(Unit *u) {
+ int r;
+
+ assert(u);
+
+ /* Same as unit_load_fragment_and_dropin(), but whether
+ * something can be loaded or not doesn't matter. */
+
+ /* Load a .service file */
+ if ((r = unit_load_fragment(u)) < 0)
+ return r;
+
+ if (u->load_state == UNIT_STUB)
+ u->load_state = UNIT_LOADED;
+
+ /* Load drop-in directory data */
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
+ return r;
+
+ return 0;
+}
+
+int unit_add_default_target_dependency(Unit *u, Unit *target) {
+ assert(u);
+ assert(target);
+
+ if (target->type != UNIT_TARGET)
+ return 0;
+
+ /* Only add the dependency if both units are loaded, so that
+ * that loop check below is reliable */
+ if (u->load_state != UNIT_LOADED ||
+ target->load_state != UNIT_LOADED)
+ return 0;
+
+ /* If either side wants no automatic dependencies, then let's
+ * skip this */
+ if (!u->default_dependencies ||
+ !target->default_dependencies)
+ return 0;
+
+ /* Don't create loops */
+ if (set_get(target->dependencies[UNIT_BEFORE], u))
+ return 0;
+
+ return unit_add_dependency(target, UNIT_AFTER, u, true);
+}
+
+static int unit_add_default_dependencies(Unit *u) {
+ static const UnitDependency deps[] = {
+ UNIT_REQUIRED_BY,
+ UNIT_REQUIRED_BY_OVERRIDABLE,
+ UNIT_WANTED_BY,
+ UNIT_BOUND_BY
+ };
+
+ Unit *target;
+ Iterator i;
+ int r;
+ unsigned k;
+
+ assert(u);
+
+ for (k = 0; k < ELEMENTSOF(deps); k++)
+ SET_FOREACH(target, u->dependencies[deps[k]], i)
+ if ((r = unit_add_default_target_dependency(u, target)) < 0)
+ return r;
+
+ return 0;
+}
+
+int unit_load(Unit *u) {
+ int r;
+
+ assert(u);
+
+ if (u->in_load_queue) {
+ LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u);
+ u->in_load_queue = false;
+ }
+
+ if (u->type == _UNIT_TYPE_INVALID)
+ return -EINVAL;
+
+ if (u->load_state != UNIT_STUB)
+ return 0;
+
+ if (UNIT_VTABLE(u)->load)
+ if ((r = UNIT_VTABLE(u)->load(u)) < 0)
+ goto fail;
+
+ if (u->load_state == UNIT_STUB) {
+ r = -ENOENT;
+ goto fail;
+ }
+
+ if (u->load_state == UNIT_LOADED &&
+ u->default_dependencies)
+ if ((r = unit_add_default_dependencies(u)) < 0)
+ goto fail;
+
+ if (u->load_state == UNIT_LOADED) {
+ r = unit_add_mount_links(u);
+ if (r < 0)
+ return r;
+ }
+
+ if (u->on_failure_isolate &&
+ set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) {
+
+ log_error("More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.",
+ u->id);
+
+ r = -EINVAL;
+ goto fail;
+ }
+
+ assert((u->load_state != UNIT_MERGED) == !u->merged_into);
+
+ unit_add_to_dbus_queue(unit_follow_merge(u));
+ unit_add_to_gc_queue(u);
+
+ return 0;
+
+fail:
+ u->load_state = UNIT_ERROR;
+ u->load_error = r;
+ unit_add_to_dbus_queue(u);
+ unit_add_to_gc_queue(u);
+
+ log_debug("Failed to load configuration for %s: %s", u->id, strerror(-r));
+
+ return r;
+}
+
+bool unit_condition_test(Unit *u) {
+ assert(u);
+
+ dual_timestamp_get(&u->condition_timestamp);
+ u->condition_result = condition_test_list(u->conditions);
+
+ return u->condition_result;
+}
+
+static const char* unit_get_status_message_format(Unit *u, JobType t) {
+ const UnitStatusMessageFormats *format_table;
+
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+
+ if (t != JOB_START && t != JOB_STOP)
+ return NULL;
+
+ format_table = &UNIT_VTABLE(u)->status_message_formats;
+ if (!format_table)
+ return NULL;
+
+ return format_table->starting_stopping[t == JOB_STOP];
+}
+
+static const char *unit_get_status_message_format_try_harder(Unit *u, JobType t) {
+ const char *format;
+
+ assert(u);
+ assert(t >= 0);
+ assert(t < _JOB_TYPE_MAX);
+
+ format = unit_get_status_message_format(u, t);
+ if (format)
+ return format;
+
+ /* Return generic strings */
+ if (t == JOB_START)
+ return "Starting %s.";
+ else if (t == JOB_STOP)
+ return "Stopping %s.";
+ else if (t == JOB_RELOAD)
+ return "Reloading %s.";
+
+ return NULL;
+}
+
+static void unit_status_print_starting_stopping(Unit *u, JobType t) {
+ const char *format;
+
+ assert(u);
+
+ /* We only print status messages for selected units on
+ * selected operations. */
+
+ format = unit_get_status_message_format(u, t);
+ if (!format)
+ return;
+
+ unit_status_printf(u, "", format, unit_description(u));
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) {
+ const char *format;
+ char buf[LINE_MAX];
+ sd_id128_t mid;
+
+ assert(u);
+
+ if (t != JOB_START && t != JOB_STOP && t != JOB_RELOAD)
+ return;
+
+ if (log_on_console())
+ return;
+
+ /* We log status messages for all units and all operations. */
+
+ format = unit_get_status_message_format_try_harder(u, t);
+ if (!format)
+ return;
+
+ snprintf(buf, sizeof(buf), format, unit_description(u));
+ char_array_0(buf);
+
+ mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING :
+ t == JOB_STOP ? SD_MESSAGE_UNIT_STOPPING :
+ SD_MESSAGE_UNIT_RELOADING;
+
+ log_struct(LOG_INFO,
+ MESSAGE_ID(mid),
+ "UNIT=%s", u->id,
+ "MESSAGE=%s", buf,
+ NULL);
+}
+#pragma GCC diagnostic pop
+
+/* Errors:
+ * -EBADR: This unit type does not support starting.
+ * -EALREADY: Unit is already started.
+ * -EAGAIN: An operation is already in progress. Retry later.
+ * -ECANCELED: Too many requests for now.
+ */
+int unit_start(Unit *u) {
+ UnitActiveState state;
+ Unit *following;
+
+ assert(u);
+
+ if (u->load_state != UNIT_LOADED)
+ return -EINVAL;
+
+ /* If this is already started, then this will succeed. Note
+ * that this will even succeed if this unit is not startable
+ * by the user. This is relied on to detect when we need to
+ * wait for units and when waiting is finished. */
+ state = unit_active_state(u);
+ if (UNIT_IS_ACTIVE_OR_RELOADING(state))
+ return -EALREADY;
+
+ /* If the conditions failed, don't do anything at all. If we
+ * already are activating this call might still be useful to
+ * speed up activation in case there is some hold-off time,
+ * but we don't want to recheck the condition in that case. */
+ if (state != UNIT_ACTIVATING &&
+ !unit_condition_test(u)) {
+ log_debug("Starting of %s requested but condition failed. Ignoring.", u->id);
+ return -EALREADY;
+ }
+
+ /* Forward to the main object, if we aren't it. */
+ if ((following = unit_following(u))) {
+ log_debug("Redirecting start request from %s to %s.", u->id, following->id);
+ return unit_start(following);
+ }
+
+ unit_status_log_starting_stopping_reloading(u, JOB_START);
+ unit_status_print_starting_stopping(u, JOB_START);
+
+ /* If it is stopped, but we cannot start it, then fail */
+ if (!UNIT_VTABLE(u)->start)
+ return -EBADR;
+
+ /* We don't suppress calls to ->start() here when we are
+ * already starting, to allow this request to be used as a
+ * "hurry up" call, for example when the unit is in some "auto
+ * restart" state where it waits for a holdoff timer to elapse
+ * before it will start again. */
+
+ unit_add_to_dbus_queue(u);
+
+ return UNIT_VTABLE(u)->start(u);
+}
+
+bool unit_can_start(Unit *u) {
+ assert(u);
+
+ return !!UNIT_VTABLE(u)->start;
+}
+
+bool unit_can_isolate(Unit *u) {
+ assert(u);
+
+ return unit_can_start(u) &&
+ u->allow_isolate;
+}
+
+/* Errors:
+ * -EBADR: This unit type does not support stopping.
+ * -EALREADY: Unit is already stopped.
+ * -EAGAIN: An operation is already in progress. Retry later.
+ */
+int unit_stop(Unit *u) {
+ UnitActiveState state;
+ Unit *following;
+
+ assert(u);
+
+ state = unit_active_state(u);
+ if (UNIT_IS_INACTIVE_OR_FAILED(state))
+ return -EALREADY;
+
+ if ((following = unit_following(u))) {
+ log_debug("Redirecting stop request from %s to %s.", u->id, following->id);
+ return unit_stop(following);
+ }
+
+ unit_status_log_starting_stopping_reloading(u, JOB_STOP);
+ unit_status_print_starting_stopping(u, JOB_STOP);
+
+ if (!UNIT_VTABLE(u)->stop)
+ return -EBADR;
+
+ unit_add_to_dbus_queue(u);
+
+ return UNIT_VTABLE(u)->stop(u);
+}
+
+/* Errors:
+ * -EBADR: This unit type does not support reloading.
+ * -ENOEXEC: Unit is not started.
+ * -EAGAIN: An operation is already in progress. Retry later.
+ */
+int unit_reload(Unit *u) {
+ UnitActiveState state;
+ Unit *following;
+
+ assert(u);
+
+ if (u->load_state != UNIT_LOADED)
+ return -EINVAL;
+
+ if (!unit_can_reload(u))
+ return -EBADR;
+
+ state = unit_active_state(u);
+ if (state == UNIT_RELOADING)
+ return -EALREADY;
+
+ if (state != UNIT_ACTIVE)
+ return -ENOEXEC;
+
+ if ((following = unit_following(u))) {
+ log_debug("Redirecting reload request from %s to %s.", u->id, following->id);
+ return unit_reload(following);
+ }
+
+ unit_status_log_starting_stopping_reloading(u, JOB_RELOAD);
+
+ unit_add_to_dbus_queue(u);
+ return UNIT_VTABLE(u)->reload(u);
+}
+
+bool unit_can_reload(Unit *u) {
+ assert(u);
+
+ if (!UNIT_VTABLE(u)->reload)
+ return false;
+
+ if (!UNIT_VTABLE(u)->can_reload)
+ return true;
+
+ return UNIT_VTABLE(u)->can_reload(u);
+}
+
+static void unit_check_unneeded(Unit *u) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+
+ /* If this service shall be shut down when unneeded then do
+ * so. */
+
+ if (!u->stop_when_unneeded)
+ return;
+
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
+ return;
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i)
+ if (unit_pending_active(other))
+ return;
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i)
+ if (unit_pending_active(other))
+ return;
+
+ SET_FOREACH(other, u->dependencies[UNIT_WANTED_BY], i)
+ if (unit_pending_active(other))
+ return;
+
+ SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i)
+ if (unit_pending_active(other))
+ return;
+
+ log_info("Service %s is not needed anymore. Stopping.", u->id);
+
+ /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
+ manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL);
+}
+
+static void retroactively_start_dependencies(Unit *u) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+ assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)));
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i)
+ if (!set_get(u->dependencies[UNIT_AFTER], other) &&
+ !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL);
+
+ SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i)
+ if (!set_get(u->dependencies[UNIT_AFTER], other) &&
+ !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL);
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
+ if (!set_get(u->dependencies[UNIT_AFTER], other) &&
+ !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_START, other, JOB_FAIL, false, NULL, NULL);
+
+ SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i)
+ if (!set_get(u->dependencies[UNIT_AFTER], other) &&
+ !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL);
+
+ SET_FOREACH(other, u->dependencies[UNIT_WANTS], i)
+ if (!set_get(u->dependencies[UNIT_AFTER], other) &&
+ !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_START, other, JOB_FAIL, false, NULL, NULL);
+
+ SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL);
+
+ SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL);
+}
+
+static void retroactively_stop_dependencies(Unit *u) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+ assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)));
+
+ /* Pull down units which are bound to us recursively if enabled */
+ SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL);
+}
+
+static void check_unneeded_dependencies(Unit *u) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+ assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)));
+
+ /* Garbage collect services that might not be needed anymore, if enabled */
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_unneeded(other);
+ SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_unneeded(other);
+ SET_FOREACH(other, u->dependencies[UNIT_WANTS], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_unneeded(other);
+ SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_unneeded(other);
+ SET_FOREACH(other, u->dependencies[UNIT_REQUISITE_OVERRIDABLE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_unneeded(other);
+ SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_unneeded(other);
+}
+
+void unit_trigger_on_failure(Unit *u) {
+ Unit *other;
+ Iterator i;
+
+ assert(u);
+
+ if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0)
+ return;
+
+ log_info("Triggering OnFailure= dependencies of %s.", u->id);
+
+ SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) {
+ int r;
+
+ if ((r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL)) < 0)
+ log_error("Failed to enqueue OnFailure= job: %s", strerror(-r));
+ }
+}
+
+void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) {
+ bool unexpected;
+
+ assert(u);
+ assert(os < _UNIT_ACTIVE_STATE_MAX);
+ assert(ns < _UNIT_ACTIVE_STATE_MAX);
+
+ /* Note that this is called for all low-level state changes,
+ * even if they might map to the same high-level
+ * UnitActiveState! That means that ns == os is OK an expected
+ * behavior here. For example: if a mount point is remounted
+ * this function will be called too! */
+
+ if (u->manager->n_reloading <= 0) {
+ dual_timestamp ts;
+
+ dual_timestamp_get(&ts);
+
+ if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns))
+ u->inactive_exit_timestamp = ts;
+ else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns))
+ u->inactive_enter_timestamp = ts;
+
+ if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns))
+ u->active_enter_timestamp = ts;
+ else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns))
+ u->active_exit_timestamp = ts;
+
+ timer_unit_notify(u, ns);
+ path_unit_notify(u, ns);
+ }
+
+ if (UNIT_IS_INACTIVE_OR_FAILED(ns))
+ cgroup_bonding_trim_list(u->cgroup_bondings, true);
+
+ if (u->job) {
+ unexpected = false;
+
+ if (u->job->state == JOB_WAITING)
+
+ /* So we reached a different state for this
+ * job. Let's see if we can run it now if it
+ * failed previously due to EAGAIN. */
+ job_add_to_run_queue(u->job);
+
+ /* Let's check whether this state change constitutes a
+ * finished job, or maybe contradicts a running job and
+ * hence needs to invalidate jobs. */
+
+ switch (u->job->type) {
+
+ case JOB_START:
+ case JOB_VERIFY_ACTIVE:
+
+ if (UNIT_IS_ACTIVE_OR_RELOADING(ns))
+ job_finish_and_invalidate(u->job, JOB_DONE, true);
+ else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) {
+ unexpected = true;
+
+ if (UNIT_IS_INACTIVE_OR_FAILED(ns))
+ job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true);
+ }
+
+ break;
+
+ case JOB_RELOAD:
+ case JOB_RELOAD_OR_START:
+
+ if (u->job->state == JOB_RUNNING) {
+ if (ns == UNIT_ACTIVE)
+ job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true);
+ else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) {
+ unexpected = true;
+
+ if (UNIT_IS_INACTIVE_OR_FAILED(ns))
+ job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true);
+ }
+ }
+
+ break;
+
+ case JOB_STOP:
+ case JOB_RESTART:
+ case JOB_TRY_RESTART:
+
+ if (UNIT_IS_INACTIVE_OR_FAILED(ns))
+ job_finish_and_invalidate(u->job, JOB_DONE, true);
+ else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) {
+ unexpected = true;
+ job_finish_and_invalidate(u->job, JOB_FAILED, true);
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Job type unknown");
+ }
+
+ } else
+ unexpected = true;
+
+ if (u->manager->n_reloading <= 0) {
+
+ /* If this state change happened without being
+ * requested by a job, then let's retroactively start
+ * or stop dependencies. We skip that step when
+ * deserializing, since we don't want to create any
+ * additional jobs just because something is already
+ * activated. */
+
+ if (unexpected) {
+ if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns))
+ retroactively_start_dependencies(u);
+ else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns))
+ retroactively_stop_dependencies(u);
+ }
+
+ /* stop unneeded units regardless if going down was expected or not */
+ if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns))
+ check_unneeded_dependencies(u);
+
+ if (ns != os && ns == UNIT_FAILED) {
+ log_struct(LOG_NOTICE,
+ "MESSAGE=Unit %s entered failed state", u->id,
+ "UNIT=%s", u->id,
+ NULL);
+ unit_trigger_on_failure(u);
+ }
+ }
+
+ /* Some names are special */
+ if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) {
+
+ if (unit_has_name(u, SPECIAL_DBUS_SERVICE))
+ /* The bus just might have become available,
+ * hence try to connect to it, if we aren't
+ * yet connected. */
+ bus_init(u->manager, true);
+
+ if (u->type == UNIT_SERVICE &&
+ !UNIT_IS_ACTIVE_OR_RELOADING(os) &&
+ u->manager->n_reloading <= 0) {
+ /* Write audit record if we have just finished starting up */
+ manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true);
+ u->in_audit = true;
+ }
+
+ if (!UNIT_IS_ACTIVE_OR_RELOADING(os))
+ manager_send_unit_plymouth(u->manager, u);
+
+ } else {
+
+ /* We don't care about D-Bus here, since we'll get an
+ * asynchronous notification for it anyway. */
+
+ if (u->type == UNIT_SERVICE &&
+ UNIT_IS_INACTIVE_OR_FAILED(ns) &&
+ !UNIT_IS_INACTIVE_OR_FAILED(os) &&
+ u->manager->n_reloading <= 0) {
+
+ /* Hmm, if there was no start record written
+ * write it now, so that we always have a nice
+ * pair */
+ if (!u->in_audit) {
+ manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE);
+
+ if (ns == UNIT_INACTIVE)
+ manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true);
+ } else
+ /* Write audit record if we have just finished shutting down */
+ manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE);
+
+ u->in_audit = false;
+ }
+ }
+
+ manager_recheck_journal(u->manager);
+
+ /* Maybe we finished startup and are now ready for being
+ * stopped because unneeded? */
+ unit_check_unneeded(u);
+
+ unit_add_to_dbus_queue(u);
+ unit_add_to_gc_queue(u);
+}
+
+int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) {
+ struct epoll_event ev;
+
+ assert(u);
+ assert(fd >= 0);
+ assert(w);
+ assert(w->type == WATCH_INVALID || (w->type == WATCH_FD && w->fd == fd && w->data.unit == u));
+
+ zero(ev);
+ ev.data.ptr = w;
+ ev.events = events;
+
+ if (epoll_ctl(u->manager->epoll_fd,
+ w->type == WATCH_INVALID ? EPOLL_CTL_ADD : EPOLL_CTL_MOD,
+ fd,
+ &ev) < 0)
+ return -errno;
+
+ w->fd = fd;
+ w->type = WATCH_FD;
+ w->data.unit = u;
+
+ return 0;
+}
+
+void unit_unwatch_fd(Unit *u, Watch *w) {
+ assert(u);
+ assert(w);
+
+ if (w->type == WATCH_INVALID)
+ return;
+
+ assert(w->type == WATCH_FD);
+ assert(w->data.unit == u);
+ assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0);
+
+ w->fd = -1;
+ w->type = WATCH_INVALID;
+ w->data.unit = NULL;
+}
+
+int unit_watch_pid(Unit *u, pid_t pid) {
+ assert(u);
+ assert(pid >= 1);
+
+ /* Watch a specific PID. We only support one unit watching
+ * each PID for now. */
+
+ return hashmap_put(u->manager->watch_pids, LONG_TO_PTR(pid), u);
+}
+
+void unit_unwatch_pid(Unit *u, pid_t pid) {
+ assert(u);
+ assert(pid >= 1);
+
+ hashmap_remove_value(u->manager->watch_pids, LONG_TO_PTR(pid), u);
+}
+
+int unit_watch_timer(Unit *u, usec_t delay, Watch *w) {
+ struct itimerspec its;
+ int flags, fd;
+ bool ours;
+
+ assert(u);
+ assert(w);
+ assert(w->type == WATCH_INVALID || (w->type == WATCH_UNIT_TIMER && w->data.unit == u));
+
+ /* This will try to reuse the old timer if there is one */
+
+ if (w->type == WATCH_UNIT_TIMER) {
+ assert(w->data.unit == u);
+ assert(w->fd >= 0);
+
+ ours = false;
+ fd = w->fd;
+ } else if (w->type == WATCH_INVALID) {
+
+ ours = true;
+ if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
+ return -errno;
+ } else
+ assert_not_reached("Invalid watch type");
+
+ zero(its);
+
+ if (delay <= 0) {
+ /* Set absolute time in the past, but not 0, since we
+ * don't want to disarm the timer */
+ its.it_value.tv_sec = 0;
+ its.it_value.tv_nsec = 1;
+
+ flags = TFD_TIMER_ABSTIME;
+ } else {
+ timespec_store(&its.it_value, delay);
+ flags = 0;
+ }
+
+ /* This will also flush the elapse counter */
+ if (timerfd_settime(fd, flags, &its, NULL) < 0)
+ goto fail;
+
+ if (w->type == WATCH_INVALID) {
+ struct epoll_event ev;
+
+ zero(ev);
+ ev.data.ptr = w;
+ ev.events = EPOLLIN;
+
+ if (epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
+ goto fail;
+ }
+
+ w->type = WATCH_UNIT_TIMER;
+ w->fd = fd;
+ w->data.unit = u;
+
+ return 0;
+
+fail:
+ if (ours)
+ close_nointr_nofail(fd);
+
+ return -errno;
+}
+
+void unit_unwatch_timer(Unit *u, Watch *w) {
+ assert(u);
+ assert(w);
+
+ if (w->type == WATCH_INVALID)
+ return;
+
+ assert(w->type == WATCH_UNIT_TIMER);
+ assert(w->data.unit == u);
+ assert(w->fd >= 0);
+
+ assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0);
+ close_nointr_nofail(w->fd);
+
+ w->fd = -1;
+ w->type = WATCH_INVALID;
+ w->data.unit = NULL;
+}
+
+bool unit_job_is_applicable(Unit *u, JobType j) {
+ assert(u);
+ assert(j >= 0 && j < _JOB_TYPE_MAX);
+
+ switch (j) {
+
+ case JOB_VERIFY_ACTIVE:
+ case JOB_START:
+ case JOB_STOP:
+ case JOB_NOP:
+ return true;
+
+ case JOB_RESTART:
+ case JOB_TRY_RESTART:
+ return unit_can_start(u);
+
+ case JOB_RELOAD:
+ return unit_can_reload(u);
+
+ case JOB_RELOAD_OR_START:
+ return unit_can_reload(u) && unit_can_start(u);
+
+ default:
+ assert_not_reached("Invalid job type");
+ }
+}
+
+int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) {
+
+ static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = {
+ [UNIT_REQUIRES] = UNIT_REQUIRED_BY,
+ [UNIT_REQUIRES_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE,
+ [UNIT_WANTS] = UNIT_WANTED_BY,
+ [UNIT_REQUISITE] = UNIT_REQUIRED_BY,
+ [UNIT_REQUISITE_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE,
+ [UNIT_BINDS_TO] = UNIT_BOUND_BY,
+ [UNIT_PART_OF] = UNIT_CONSISTS_OF,
+ [UNIT_REQUIRED_BY] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_REQUIRED_BY_OVERRIDABLE] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_BOUND_BY] = UNIT_BINDS_TO,
+ [UNIT_CONSISTS_OF] = UNIT_PART_OF,
+ [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY,
+ [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS,
+ [UNIT_BEFORE] = UNIT_AFTER,
+ [UNIT_AFTER] = UNIT_BEFORE,
+ [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_REFERENCES] = UNIT_REFERENCED_BY,
+ [UNIT_REFERENCED_BY] = UNIT_REFERENCES,
+ [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY,
+ [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS,
+ [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM,
+ [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO,
+ };
+ int r, q = 0, v = 0, w = 0;
+
+ assert(u);
+ assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX);
+ assert(other);
+
+ u = unit_follow_merge(u);
+ other = unit_follow_merge(other);
+
+ /* We won't allow dependencies on ourselves. We will not
+ * consider them an error however. */
+ if (u == other)
+ return 0;
+
+ if ((r = set_ensure_allocated(&u->dependencies[d], trivial_hash_func, trivial_compare_func)) < 0)
+ return r;
+
+ if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID)
+ if ((r = set_ensure_allocated(&other->dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0)
+ return r;
+
+ if (add_reference)
+ if ((r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 ||
+ (r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0)
+ return r;
+
+ if ((q = set_put(u->dependencies[d], other)) < 0)
+ return q;
+
+ if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID)
+ if ((v = set_put(other->dependencies[inverse_table[d]], u)) < 0) {
+ r = v;
+ goto fail;
+ }
+
+ if (add_reference) {
+ if ((w = set_put(u->dependencies[UNIT_REFERENCES], other)) < 0) {
+ r = w;
+ goto fail;
+ }
+
+ if ((r = set_put(other->dependencies[UNIT_REFERENCED_BY], u)) < 0)
+ goto fail;
+ }
+
+ unit_add_to_dbus_queue(u);
+ return 0;
+
+fail:
+ if (q > 0)
+ set_remove(u->dependencies[d], other);
+
+ if (v > 0)
+ set_remove(other->dependencies[inverse_table[d]], u);
+
+ if (w > 0)
+ set_remove(u->dependencies[UNIT_REFERENCES], other);
+
+ return r;
+}
+
+int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) {
+ int r;
+
+ assert(u);
+
+ if ((r = unit_add_dependency(u, d, other, add_reference)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(u, e, other, add_reference)) < 0)
+ return r;
+
+ return 0;
+}
+
+static const char *resolve_template(Unit *u, const char *name, const char*path, char **p) {
+ char *s;
+
+ assert(u);
+ assert(name || path);
+
+ if (!name)
+ name = path_get_file_name(path);
+
+ if (!unit_name_is_template(name)) {
+ *p = NULL;
+ return name;
+ }
+
+ if (u->instance)
+ s = unit_name_replace_instance(name, u->instance);
+ else {
+ char *i;
+
+ if (!(i = unit_name_to_prefix(u->id)))
+ return NULL;
+
+ s = unit_name_replace_instance(name, i);
+ free(i);
+ }
+
+ if (!s)
+ return NULL;
+
+ *p = s;
+ return s;
+}
+
+int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) {
+ Unit *other;
+ int r;
+ char *s;
+
+ assert(u);
+ assert(name || path);
+
+ if (!(name = resolve_template(u, name, path, &s)))
+ return -ENOMEM;
+
+ if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0)
+ goto finish;
+
+ r = unit_add_dependency(u, d, other, add_reference);
+
+finish:
+ free(s);
+ return r;
+}
+
+int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) {
+ Unit *other;
+ int r;
+ char *s;
+
+ assert(u);
+ assert(name || path);
+
+ if (!(name = resolve_template(u, name, path, &s)))
+ return -ENOMEM;
+
+ if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0)
+ goto finish;
+
+ r = unit_add_two_dependencies(u, d, e, other, add_reference);
+
+finish:
+ free(s);
+ return r;
+}
+
+int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) {
+ Unit *other;
+ int r;
+ char *s;
+
+ assert(u);
+ assert(name || path);
+
+ if (!(name = resolve_template(u, name, path, &s)))
+ return -ENOMEM;
+
+ if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0)
+ goto finish;
+
+ r = unit_add_dependency(other, d, u, add_reference);
+
+finish:
+ free(s);
+ return r;
+}
+
+int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) {
+ Unit *other;
+ int r;
+ char *s;
+
+ assert(u);
+ assert(name || path);
+
+ if (!(name = resolve_template(u, name, path, &s)))
+ return -ENOMEM;
+
+ if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0)
+ goto finish;
+
+ if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0)
+ goto finish;
+
+finish:
+ free(s);
+ return r;
+}
+
+int set_unit_path(const char *p) {
+ char *cwd, *c;
+ int r;
+
+ /* This is mostly for debug purposes */
+
+ if (path_is_absolute(p)) {
+ if (!(c = strdup(p)))
+ return -ENOMEM;
+ } else {
+ if (!(cwd = get_current_dir_name()))
+ return -errno;
+
+ r = asprintf(&c, "%s/%s", cwd, p);
+ free(cwd);
+
+ if (r < 0)
+ return -ENOMEM;
+ }
+
+ if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) {
+ r = -errno;
+ free(c);
+ return r;
+ }
+
+ return 0;
+}
+
+char *unit_dbus_path(Unit *u) {
+ assert(u);
+
+ if (!u->id)
+ return NULL;
+
+ return unit_dbus_path_from_name(u->id);
+}
+
+int unit_add_cgroup(Unit *u, CGroupBonding *b) {
+ int r;
+
+ assert(u);
+ assert(b);
+
+ assert(b->path);
+
+ if (!b->controller) {
+ if (!(b->controller = strdup(SYSTEMD_CGROUP_CONTROLLER)))
+ return -ENOMEM;
+
+ b->ours = true;
+ }
+
+ /* Ensure this hasn't been added yet */
+ assert(!b->unit);
+
+ if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) {
+ CGroupBonding *l;
+
+ l = hashmap_get(u->manager->cgroup_bondings, b->path);
+ LIST_PREPEND(CGroupBonding, by_path, l, b);
+
+ if ((r = hashmap_replace(u->manager->cgroup_bondings, b->path, l)) < 0) {
+ LIST_REMOVE(CGroupBonding, by_path, l, b);
+ return r;
+ }
+ }
+
+ LIST_PREPEND(CGroupBonding, by_unit, u->cgroup_bondings, b);
+ b->unit = u;
+
+ return 0;
+}
+
+char *unit_default_cgroup_path(Unit *u) {
+ char *p;
+
+ assert(u);
+
+ if (u->instance) {
+ char *t;
+
+ t = unit_name_template(u->id);
+ if (!t)
+ return NULL;
+
+ p = strjoin(u->manager->cgroup_hierarchy, "/", t, "/", u->instance, NULL);
+ free(t);
+ } else
+ p = strjoin(u->manager->cgroup_hierarchy, "/", u->id, NULL);
+
+ return p;
+}
+
+int unit_add_cgroup_from_text(Unit *u, const char *name) {
+ char *controller = NULL, *path = NULL;
+ CGroupBonding *b = NULL;
+ bool ours = false;
+ int r;
+
+ assert(u);
+ assert(name);
+
+ if ((r = cg_split_spec(name, &controller, &path)) < 0)
+ return r;
+
+ if (!path) {
+ path = unit_default_cgroup_path(u);
+ ours = true;
+ }
+
+ if (!controller) {
+ controller = strdup(SYSTEMD_CGROUP_CONTROLLER);
+ ours = true;
+ }
+
+ if (!path || !controller) {
+ free(path);
+ free(controller);
+
+ return -ENOMEM;
+ }
+
+ if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) {
+ r = -EEXIST;
+ goto fail;
+ }
+
+ if (!(b = new0(CGroupBonding, 1))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ b->controller = controller;
+ b->path = path;
+ b->ours = ours;
+ b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER);
+
+ if ((r = unit_add_cgroup(u, b)) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ free(path);
+ free(controller);
+ free(b);
+
+ return r;
+}
+
+static int unit_add_one_default_cgroup(Unit *u, const char *controller) {
+ CGroupBonding *b = NULL;
+ int r = -ENOMEM;
+
+ assert(u);
+
+ if (!controller)
+ controller = SYSTEMD_CGROUP_CONTROLLER;
+
+ if (cgroup_bonding_find_list(u->cgroup_bondings, controller))
+ return 0;
+
+ if (!(b = new0(CGroupBonding, 1)))
+ return -ENOMEM;
+
+ if (!(b->controller = strdup(controller)))
+ goto fail;
+
+ b->path = unit_default_cgroup_path(u);
+ if (!b->path)
+ goto fail;
+
+ b->ours = true;
+ b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER);
+
+ if ((r = unit_add_cgroup(u, b)) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ free(b->path);
+ free(b->controller);
+ free(b);
+
+ return r;
+}
+
+int unit_add_default_cgroups(Unit *u) {
+ CGroupAttribute *a;
+ char **c;
+ int r;
+
+ assert(u);
+
+ /* Adds in the default cgroups, if they weren't specified
+ * otherwise. */
+
+ if (!u->manager->cgroup_hierarchy)
+ return 0;
+
+ if ((r = unit_add_one_default_cgroup(u, NULL)) < 0)
+ return r;
+
+ STRV_FOREACH(c, u->manager->default_controllers)
+ unit_add_one_default_cgroup(u, *c);
+
+ LIST_FOREACH(by_unit, a, u->cgroup_attributes)
+ unit_add_one_default_cgroup(u, a->controller);
+
+ return 0;
+}
+
+CGroupBonding* unit_get_default_cgroup(Unit *u) {
+ assert(u);
+
+ return cgroup_bonding_find_list(u->cgroup_bondings, SYSTEMD_CGROUP_CONTROLLER);
+}
+
+int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback) {
+ int r;
+ char *c = NULL;
+ CGroupAttribute *a;
+
+ assert(u);
+ assert(name);
+ assert(value);
+
+ if (!controller) {
+ const char *dot;
+
+ dot = strchr(name, '.');
+ if (!dot)
+ return -EINVAL;
+
+ c = strndup(name, dot - name);
+ if (!c)
+ return -ENOMEM;
+
+ controller = c;
+ }
+
+ if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ a = new0(CGroupAttribute, 1);
+ if (!a) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (c) {
+ a->controller = c;
+ c = NULL;
+ } else
+ a->controller = strdup(controller);
+
+ a->name = strdup(name);
+ a->value = strdup(value);
+
+ if (!a->controller || !a->name || !a->value) {
+ free(a->controller);
+ free(a->name);
+ free(a->value);
+ free(a);
+
+ return -ENOMEM;
+ }
+
+ a->map_callback = map_callback;
+
+ LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a);
+
+ r = 0;
+
+finish:
+ free(c);
+ return r;
+}
+
+int unit_load_related_unit(Unit *u, const char *type, Unit **_found) {
+ char *t;
+ int r;
+
+ assert(u);
+ assert(type);
+ assert(_found);
+
+ if (!(t = unit_name_change_suffix(u->id, type)))
+ return -ENOMEM;
+
+ assert(!unit_has_name(u, t));
+
+ r = manager_load_unit(u->manager, t, NULL, NULL, _found);
+ free(t);
+
+ assert(r < 0 || *_found != u);
+
+ return r;
+}
+
+int unit_get_related_unit(Unit *u, const char *type, Unit **_found) {
+ Unit *found;
+ char *t;
+
+ assert(u);
+ assert(type);
+ assert(_found);
+
+ if (!(t = unit_name_change_suffix(u->id, type)))
+ return -ENOMEM;
+
+ assert(!unit_has_name(u, t));
+
+ found = manager_get_unit(u->manager, t);
+ free(t);
+
+ if (!found)
+ return -ENOENT;
+
+ *_found = found;
+ return 0;
+}
+
+int unit_watch_bus_name(Unit *u, const char *name) {
+ assert(u);
+ assert(name);
+
+ /* Watch a specific name on the bus. We only support one unit
+ * watching each name for now. */
+
+ return hashmap_put(u->manager->watch_bus, name, u);
+}
+
+void unit_unwatch_bus_name(Unit *u, const char *name) {
+ assert(u);
+ assert(name);
+
+ hashmap_remove_value(u->manager->watch_bus, name, u);
+}
+
+bool unit_can_serialize(Unit *u) {
+ assert(u);
+
+ return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item;
+}
+
+int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
+ int r;
+
+ assert(u);
+ assert(f);
+ assert(fds);
+
+ if (!unit_can_serialize(u))
+ return 0;
+
+ if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0)
+ return r;
+
+
+ if (serialize_jobs) {
+ if (u->job) {
+ fprintf(f, "job\n");
+ job_serialize(u->job, f, fds);
+ }
+
+ if (u->nop_job) {
+ fprintf(f, "job\n");
+ job_serialize(u->nop_job, f, fds);
+ }
+ }
+
+ dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp);
+ dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp);
+ dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp);
+ dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp);
+ dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp);
+
+ if (dual_timestamp_is_set(&u->condition_timestamp))
+ unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result));
+
+ /* End marker */
+ fputc('\n', f);
+ return 0;
+}
+
+void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) {
+ va_list ap;
+
+ assert(u);
+ assert(f);
+ assert(key);
+ assert(format);
+
+ fputs(key, f);
+ fputc('=', f);
+
+ va_start(ap, format);
+ vfprintf(f, format, ap);
+ va_end(ap);
+
+ fputc('\n', f);
+}
+
+void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) {
+ assert(u);
+ assert(f);
+ assert(key);
+ assert(value);
+
+ fprintf(f, "%s=%s\n", key, value);
+}
+
+int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
+ int r;
+
+ assert(u);
+ assert(f);
+ assert(fds);
+
+ if (!unit_can_serialize(u))
+ return 0;
+
+ for (;;) {
+ char line[LINE_MAX], *l, *v;
+ size_t k;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ return 0;
+ return -errno;
+ }
+
+ char_array_0(line);
+ l = strstrip(line);
+
+ /* End marker */
+ if (l[0] == 0)
+ return 0;
+
+ k = strcspn(l, "=");
+
+ if (l[k] == '=') {
+ l[k] = 0;
+ v = l+k+1;
+ } else
+ v = l+k;
+
+ if (streq(l, "job")) {
+ if (v[0] == '\0') {
+ /* new-style serialized job */
+ Job *j = job_new_raw(u);
+ if (!j)
+ return -ENOMEM;
+
+ r = job_deserialize(j, f, fds);
+ if (r < 0) {
+ job_free(j);
+ return r;
+ }
+
+ r = hashmap_put(u->manager->jobs, UINT32_TO_PTR(j->id), j);
+ if (r < 0) {
+ job_free(j);
+ return r;
+ }
+
+ r = job_install_deserialized(j);
+ if (r < 0) {
+ hashmap_remove(u->manager->jobs, UINT32_TO_PTR(j->id));
+ job_free(j);
+ return r;
+ }
+ } else {
+ /* legacy */
+ JobType type = job_type_from_string(v);
+ if (type < 0)
+ log_debug("Failed to parse job type value %s", v);
+ else
+ u->deserialized_job = type;
+ }
+ continue;
+ } else if (streq(l, "inactive-exit-timestamp")) {
+ dual_timestamp_deserialize(v, &u->inactive_exit_timestamp);
+ continue;
+ } else if (streq(l, "active-enter-timestamp")) {
+ dual_timestamp_deserialize(v, &u->active_enter_timestamp);
+ continue;
+ } else if (streq(l, "active-exit-timestamp")) {
+ dual_timestamp_deserialize(v, &u->active_exit_timestamp);
+ continue;
+ } else if (streq(l, "inactive-enter-timestamp")) {
+ dual_timestamp_deserialize(v, &u->inactive_enter_timestamp);
+ continue;
+ } else if (streq(l, "condition-timestamp")) {
+ dual_timestamp_deserialize(v, &u->condition_timestamp);
+ continue;
+ } else if (streq(l, "condition-result")) {
+ int b;
+
+ if ((b = parse_boolean(v)) < 0)
+ log_debug("Failed to parse condition result value %s", v);
+ else
+ u->condition_result = b;
+
+ continue;
+ }
+
+ if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0)
+ return r;
+ }
+}
+
+int unit_add_node_link(Unit *u, const char *what, bool wants) {
+ Unit *device;
+ char *e;
+ int r;
+
+ assert(u);
+
+ if (!what)
+ return 0;
+
+ /* Adds in links to the device node that this unit is based on */
+
+ if (!is_device_path(what))
+ return 0;
+
+ e = unit_name_from_path(what, ".device");
+ if (!e)
+ return -ENOMEM;
+
+ r = manager_load_unit(u->manager, e, NULL, NULL, &device);
+ free(e);
+ if (r < 0)
+ return r;
+
+ r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_BINDS_TO, device, true);
+ if (r < 0)
+ return r;
+
+ if (wants) {
+ r = unit_add_dependency(device, UNIT_WANTS, u, false);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int unit_coldplug(Unit *u) {
+ int r;
+
+ assert(u);
+
+ if (UNIT_VTABLE(u)->coldplug)
+ if ((r = UNIT_VTABLE(u)->coldplug(u)) < 0)
+ return r;
+
+ if (u->job) {
+ r = job_coldplug(u->job);
+ if (r < 0)
+ return r;
+ } else if (u->deserialized_job >= 0) {
+ /* legacy */
+ r = manager_add_job(u->manager, u->deserialized_job, u, JOB_IGNORE_REQUIREMENTS, false, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ u->deserialized_job = _JOB_TYPE_INVALID;
+ }
+
+ return 0;
+}
+
+void unit_status_printf(Unit *u, const char *status, const char *format, ...) {
+ va_list ap;
+
+ assert(u);
+ assert(format);
+
+ if (!manager_get_show_status(u->manager))
+ return;
+
+ if (!manager_is_booting_or_shutting_down(u->manager))
+ return;
+
+ va_start(ap, format);
+ status_vprintf(status, true, format, ap);
+ va_end(ap);
+}
+
+bool unit_need_daemon_reload(Unit *u) {
+ struct stat st;
+
+ assert(u);
+
+ if (u->fragment_path) {
+ zero(st);
+ if (stat(u->fragment_path, &st) < 0)
+ /* What, cannot access this anymore? */
+ return true;
+
+ if (u->fragment_mtime > 0 &&
+ timespec_load(&st.st_mtim) != u->fragment_mtime)
+ return true;
+ }
+
+ if (u->source_path) {
+ zero(st);
+ if (stat(u->source_path, &st) < 0)
+ return true;
+
+ if (u->source_mtime > 0 &&
+ timespec_load(&st.st_mtim) != u->source_mtime)
+ return true;
+ }
+
+ return false;
+}
+
+void unit_reset_failed(Unit *u) {
+ assert(u);
+
+ if (UNIT_VTABLE(u)->reset_failed)
+ UNIT_VTABLE(u)->reset_failed(u);
+}
+
+Unit *unit_following(Unit *u) {
+ assert(u);
+
+ if (UNIT_VTABLE(u)->following)
+ return UNIT_VTABLE(u)->following(u);
+
+ return NULL;
+}
+
+bool unit_pending_inactive(Unit *u) {
+ assert(u);
+
+ /* Returns true if the unit is inactive or going down */
+
+ if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)))
+ return true;
+
+ if (u->job && u->job->type == JOB_STOP)
+ return true;
+
+ return false;
+}
+
+bool unit_pending_active(Unit *u) {
+ assert(u);
+
+ /* Returns true if the unit is active or going up */
+
+ if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
+ return true;
+
+ if (u->job &&
+ (u->job->type == JOB_START ||
+ u->job->type == JOB_RELOAD_OR_START ||
+ u->job->type == JOB_RESTART))
+ return true;
+
+ return false;
+}
+
+int unit_kill(Unit *u, KillWho w, int signo, DBusError *error) {
+ assert(u);
+ assert(w >= 0 && w < _KILL_WHO_MAX);
+ assert(signo > 0);
+ assert(signo < _NSIG);
+
+ if (!UNIT_VTABLE(u)->kill)
+ return -ENOTSUP;
+
+ return UNIT_VTABLE(u)->kill(u, w, signo, error);
+}
+
+int unit_following_set(Unit *u, Set **s) {
+ assert(u);
+ assert(s);
+
+ if (UNIT_VTABLE(u)->following_set)
+ return UNIT_VTABLE(u)->following_set(u, s);
+
+ *s = NULL;
+ return 0;
+}
+
+UnitFileState unit_get_unit_file_state(Unit *u) {
+ assert(u);
+
+ if (u->unit_file_state < 0 && u->fragment_path)
+ u->unit_file_state = unit_file_get_state(
+ u->manager->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER,
+ NULL, path_get_file_name(u->fragment_path));
+
+ return u->unit_file_state;
+}
+
+Unit* unit_ref_set(UnitRef *ref, Unit *u) {
+ assert(ref);
+ assert(u);
+
+ if (ref->unit)
+ unit_ref_unset(ref);
+
+ ref->unit = u;
+ LIST_PREPEND(UnitRef, refs, u->refs, ref);
+ return u;
+}
+
+void unit_ref_unset(UnitRef *ref) {
+ assert(ref);
+
+ if (!ref->unit)
+ return;
+
+ LIST_REMOVE(UnitRef, refs, ref->unit->refs, ref);
+ ref->unit = NULL;
+}
+
+int unit_add_one_mount_link(Unit *u, Mount *m) {
+ char **i;
+
+ assert(u);
+ assert(m);
+
+ if (u->load_state != UNIT_LOADED ||
+ UNIT(m)->load_state != UNIT_LOADED)
+ return 0;
+
+ STRV_FOREACH(i, u->requires_mounts_for) {
+
+ if (UNIT(m) == u)
+ continue;
+
+ if (!path_startswith(*i, m->where))
+ continue;
+
+ return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true);
+ }
+
+ return 0;
+}
+
+int unit_add_mount_links(Unit *u) {
+ Unit *other;
+ int r;
+
+ assert(u);
+
+ LIST_FOREACH(units_by_type, other, u->manager->units_by_type[UNIT_MOUNT]) {
+ r = unit_add_one_mount_link(u, MOUNT(other));
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int unit_exec_context_defaults(Unit *u, ExecContext *c) {
+ unsigned i;
+ int r;
+
+ assert(u);
+ assert(c);
+
+ /* This only copies in the ones that need memory */
+
+ for (i = 0; i < RLIMIT_NLIMITS; i++)
+ if (u->manager->rlimit[i] && !c->rlimit[i]) {
+ c->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1);
+ if (!c->rlimit[i])
+ return -ENOMEM;
+ }
+
+ if (u->manager->running_as == SYSTEMD_USER &&
+ !c->working_directory) {
+
+ r = get_home_dir(&c->working_directory);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+ExecContext *unit_get_exec_context(Unit *u) {
+ size_t offset;
+ assert(u);
+
+ offset = UNIT_VTABLE(u)->exec_context_offset;
+ if (offset <= 0)
+ return NULL;
+
+ return (ExecContext*) ((uint8_t*) u + offset);
+}
+
+static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
+ [UNIT_ACTIVE] = "active",
+ [UNIT_RELOADING] = "reloading",
+ [UNIT_INACTIVE] = "inactive",
+ [UNIT_FAILED] = "failed",
+ [UNIT_ACTIVATING] = "activating",
+ [UNIT_DEACTIVATING] = "deactivating"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
+
+static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
+ [UNIT_REQUIRES] = "Requires",
+ [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable",
+ [UNIT_REQUISITE] = "Requisite",
+ [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
+ [UNIT_WANTS] = "Wants",
+ [UNIT_BINDS_TO] = "BindsTo",
+ [UNIT_PART_OF] = "PartOf",
+ [UNIT_REQUIRED_BY] = "RequiredBy",
+ [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
+ [UNIT_WANTED_BY] = "WantedBy",
+ [UNIT_BOUND_BY] = "BoundBy",
+ [UNIT_CONSISTS_OF] = "ConsistsOf",
+ [UNIT_CONFLICTS] = "Conflicts",
+ [UNIT_CONFLICTED_BY] = "ConflictedBy",
+ [UNIT_BEFORE] = "Before",
+ [UNIT_AFTER] = "After",
+ [UNIT_ON_FAILURE] = "OnFailure",
+ [UNIT_TRIGGERS] = "Triggers",
+ [UNIT_TRIGGERED_BY] = "TriggeredBy",
+ [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo",
+ [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom",
+ [UNIT_REFERENCES] = "References",
+ [UNIT_REFERENCED_BY] = "ReferencedBy",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);
diff --git a/src/core/unit.h b/src/core/unit.h
new file mode 100644
index 0000000000..bf961c2aac
--- /dev/null
+++ b/src/core/unit.h
@@ -0,0 +1,551 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+typedef struct Unit Unit;
+typedef struct UnitVTable UnitVTable;
+typedef enum UnitActiveState UnitActiveState;
+typedef enum UnitDependency UnitDependency;
+typedef struct UnitRef UnitRef;
+typedef struct UnitStatusMessageFormats UnitStatusMessageFormats;
+
+#include "set.h"
+#include "util.h"
+#include "list.h"
+#include "socket-util.h"
+#include "execute.h"
+#include "condition.h"
+#include "install.h"
+#include "unit-name.h"
+
+enum UnitActiveState {
+ UNIT_ACTIVE,
+ UNIT_RELOADING,
+ UNIT_INACTIVE,
+ UNIT_FAILED,
+ UNIT_ACTIVATING,
+ UNIT_DEACTIVATING,
+ _UNIT_ACTIVE_STATE_MAX,
+ _UNIT_ACTIVE_STATE_INVALID = -1
+};
+
+static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) {
+ return t == UNIT_ACTIVE || t == UNIT_RELOADING;
+}
+
+static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) {
+ return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_RELOADING;
+}
+
+static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) {
+ return t == UNIT_INACTIVE || t == UNIT_FAILED || t == UNIT_DEACTIVATING;
+}
+
+static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) {
+ return t == UNIT_INACTIVE || t == UNIT_FAILED;
+}
+
+enum UnitDependency {
+ /* Positive dependencies */
+ UNIT_REQUIRES,
+ UNIT_REQUIRES_OVERRIDABLE,
+ UNIT_REQUISITE,
+ UNIT_REQUISITE_OVERRIDABLE,
+ UNIT_WANTS,
+ UNIT_BINDS_TO,
+ UNIT_PART_OF,
+
+ /* Inverse of the above */
+ UNIT_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */
+ UNIT_REQUIRED_BY_OVERRIDABLE, /* inverse of 'requires_overridable' and 'requisite_overridable' is 'soft_required_by' */
+ UNIT_WANTED_BY, /* inverse of 'wants' */
+ UNIT_BOUND_BY, /* inverse of 'binds_to' */
+ UNIT_CONSISTS_OF, /* inverse of 'part_of' */
+
+ /* Negative dependencies */
+ UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */
+ UNIT_CONFLICTED_BY,
+
+ /* Order */
+ UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */
+ UNIT_AFTER,
+
+ /* On Failure */
+ UNIT_ON_FAILURE,
+
+ /* Triggers (i.e. a socket triggers a service) */
+ UNIT_TRIGGERS,
+ UNIT_TRIGGERED_BY,
+
+ /* Propagate reloads */
+ UNIT_PROPAGATES_RELOAD_TO,
+ UNIT_RELOAD_PROPAGATED_FROM,
+
+ /* Reference information for GC logic */
+ UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */
+ UNIT_REFERENCED_BY,
+
+ _UNIT_DEPENDENCY_MAX,
+ _UNIT_DEPENDENCY_INVALID = -1
+};
+
+#include "manager.h"
+#include "job.h"
+#include "cgroup.h"
+#include "cgroup-attr.h"
+
+struct Unit {
+ Manager *manager;
+
+ UnitType type;
+ UnitLoadState load_state;
+ Unit *merged_into;
+
+ char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
+ char *instance;
+
+ Set *names;
+ Set *dependencies[_UNIT_DEPENDENCY_MAX];
+
+ char **requires_mounts_for;
+
+ char *description;
+ char **documentation;
+
+ char *fragment_path; /* if loaded from a config file this is the primary path to it */
+ char *source_path; /* if converted, the source file */
+ usec_t fragment_mtime;
+ usec_t source_mtime;
+
+ /* If there is something to do with this unit, then this is the installed job for it */
+ Job *job;
+
+ /* JOB_NOP jobs are special and can be installed without disturbing the real job. */
+ Job *nop_job;
+
+ usec_t job_timeout;
+
+ /* References to this */
+ LIST_HEAD(UnitRef, refs);
+
+ /* Conditions to check */
+ LIST_HEAD(Condition, conditions);
+
+ dual_timestamp condition_timestamp;
+
+ dual_timestamp inactive_exit_timestamp;
+ dual_timestamp active_enter_timestamp;
+ dual_timestamp active_exit_timestamp;
+ dual_timestamp inactive_enter_timestamp;
+
+ /* Counterparts in the cgroup filesystem */
+ CGroupBonding *cgroup_bondings;
+ CGroupAttribute *cgroup_attributes;
+
+ /* Per type list */
+ LIST_FIELDS(Unit, units_by_type);
+
+ /* All units which have requires_mounts_for set */
+ LIST_FIELDS(Unit, has_requires_mounts_for);
+
+ /* Load queue */
+ LIST_FIELDS(Unit, load_queue);
+
+ /* D-Bus queue */
+ LIST_FIELDS(Unit, dbus_queue);
+
+ /* Cleanup queue */
+ LIST_FIELDS(Unit, cleanup_queue);
+
+ /* GC queue */
+ LIST_FIELDS(Unit, gc_queue);
+
+ /* Used during GC sweeps */
+ unsigned gc_marker;
+
+ /* When deserializing, temporarily store the job type for this
+ * unit here, if there was a job scheduled.
+ * Only for deserializing from a legacy version. New style uses full
+ * serialized jobs. */
+ int deserialized_job; /* This is actually of type JobType */
+
+ /* Error code when we didn't manage to load the unit (negative) */
+ int load_error;
+
+ /* Cached unit file state */
+ UnitFileState unit_file_state;
+
+ /* Garbage collect us we nobody wants or requires us anymore */
+ bool stop_when_unneeded;
+
+ /* Create default dependencies */
+ bool default_dependencies;
+
+ /* Refuse manual starting, allow starting only indirectly via dependency. */
+ bool refuse_manual_start;
+
+ /* Don't allow the user to stop this unit manually, allow stopping only indirectly via dependency. */
+ bool refuse_manual_stop;
+
+ /* Allow isolation requests */
+ bool allow_isolate;
+
+ /* Isolate OnFailure unit */
+ bool on_failure_isolate;
+
+ /* Ignore this unit when isolating */
+ bool ignore_on_isolate;
+
+ /* Ignore this unit when snapshotting */
+ bool ignore_on_snapshot;
+
+ /* Did the last condition check succeed? */
+ bool condition_result;
+
+ bool in_load_queue:1;
+ bool in_dbus_queue:1;
+ bool in_cleanup_queue:1;
+ bool in_gc_queue:1;
+
+ bool sent_dbus_new_signal:1;
+
+ bool no_gc:1;
+
+ bool in_audit:1;
+};
+
+struct UnitRef {
+ /* Keeps tracks of references to a unit. This is useful so
+ * that we can merge two units if necessary and correct all
+ * references to them */
+
+ Unit* unit;
+ LIST_FIELDS(UnitRef, refs);
+};
+
+struct UnitStatusMessageFormats {
+ const char *starting_stopping[2];
+ const char *finished_start_job[_JOB_RESULT_MAX];
+ const char *finished_stop_job[_JOB_RESULT_MAX];
+};
+
+#include "service.h"
+#include "timer.h"
+#include "socket.h"
+#include "target.h"
+#include "device.h"
+#include "mount.h"
+#include "automount.h"
+#include "snapshot.h"
+#include "swap.h"
+#include "path.h"
+
+struct UnitVTable {
+ /* How much memory does an object of this unit type need */
+ size_t object_size;
+
+ /* If greater than 0, the offset into the object where
+ * ExecContext is found, if the unit type has that */
+ size_t exec_context_offset;
+
+ /* Config file sections this unit type understands, separated
+ * by NUL chars */
+ const char *sections;
+
+ /* This should reset all type-specific variables. This should
+ * not allocate memory, and is called with zero-initialized
+ * data. It should hence only initialize variables that need
+ * to be set != 0. */
+ void (*init)(Unit *u);
+
+ /* This should free all type-specific variables. It should be
+ * idempotent. */
+ void (*done)(Unit *u);
+
+ /* Actually load data from disk. This may fail, and should set
+ * load_state to UNIT_LOADED, UNIT_MERGED or leave it at
+ * UNIT_STUB if no configuration could be found. */
+ int (*load)(Unit *u);
+
+ /* If a lot of units got created via enumerate(), this is
+ * where to actually set the state and call unit_notify(). */
+ int (*coldplug)(Unit *u);
+
+ void (*dump)(Unit *u, FILE *f, const char *prefix);
+
+ int (*start)(Unit *u);
+ int (*stop)(Unit *u);
+ int (*reload)(Unit *u);
+
+ int (*kill)(Unit *u, KillWho w, int signo, DBusError *error);
+
+ bool (*can_reload)(Unit *u);
+
+ /* Write all data that cannot be restored from other sources
+ * away using unit_serialize_item() */
+ int (*serialize)(Unit *u, FILE *f, FDSet *fds);
+
+ /* Restore one item from the serialization */
+ int (*deserialize_item)(Unit *u, const char *key, const char *data, FDSet *fds);
+
+ /* Boils down the more complex internal state of this unit to
+ * a simpler one that the engine can understand */
+ UnitActiveState (*active_state)(Unit *u);
+
+ /* Returns the substate specific to this unit type as
+ * string. This is purely information so that we can give the
+ * user a more fine grained explanation in which actual state a
+ * unit is in. */
+ const char* (*sub_state_to_string)(Unit *u);
+
+ /* Return true when there is reason to keep this entry around
+ * even nothing references it and it isn't active in any
+ * way */
+ bool (*check_gc)(Unit *u);
+
+ /* Return true when this unit is suitable for snapshotting */
+ bool (*check_snapshot)(Unit *u);
+
+ void (*fd_event)(Unit *u, int fd, uint32_t events, Watch *w);
+ void (*sigchld_event)(Unit *u, pid_t pid, int code, int status);
+ void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w);
+
+ /* Reset failed state if we are in failed state */
+ void (*reset_failed)(Unit *u);
+
+ /* Called whenever any of the cgroups this unit watches for
+ * ran empty */
+ void (*cgroup_notify_empty)(Unit *u);
+
+ /* Called whenever a process of this unit sends us a message */
+ void (*notify_message)(Unit *u, pid_t pid, char **tags);
+
+ /* Called whenever a name thus Unit registered for comes or
+ * goes away. */
+ void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner);
+
+ /* Called whenever a bus PID lookup finishes */
+ void (*bus_query_pid_done)(Unit *u, const char *name, pid_t pid);
+
+ /* Called for each message received on the bus */
+ DBusHandlerResult (*bus_message_handler)(Unit *u, DBusConnection *c, DBusMessage *message);
+
+ /* Return the unit this unit is following */
+ Unit *(*following)(Unit *u);
+
+ /* Return the set of units that are following each other */
+ int (*following_set)(Unit *u, Set **s);
+
+ /* This is called for each unit type and should be used to
+ * enumerate existing devices and load them. However,
+ * everything that is loaded here should still stay in
+ * inactive state. It is the job of the coldplug() call above
+ * to put the units into the initial state. */
+ int (*enumerate)(Manager *m);
+
+ /* Type specific cleanups. */
+ void (*shutdown)(Manager *m);
+
+ /* When sending out PropertiesChanged signal, which properties
+ * shall be invalidated? This is a NUL separated list of
+ * strings, to minimize relocations a little. */
+ const char *bus_invalidating_properties;
+
+ /* The interface name */
+ const char *bus_interface;
+
+ UnitStatusMessageFormats status_message_formats;
+
+ /* Can units of this type have multiple names? */
+ bool no_alias:1;
+
+ /* Instances make no sense for this type */
+ bool no_instances:1;
+
+ /* Exclude from automatic gc */
+ bool no_gc:1;
+};
+
+extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX];
+
+#define UNIT_VTABLE(u) unit_vtable[(u)->type]
+
+/* For casting a unit into the various unit types */
+#define DEFINE_CAST(UPPERCASE, MixedCase) \
+ static inline MixedCase* UPPERCASE(Unit *u) { \
+ if (_unlikely_(!u || u->type != UNIT_##UPPERCASE)) \
+ return NULL; \
+ \
+ return (MixedCase*) u; \
+ }
+
+/* For casting the various unit types into a unit */
+#define UNIT(u) (&(u)->meta)
+
+DEFINE_CAST(SOCKET, Socket);
+DEFINE_CAST(TIMER, Timer);
+DEFINE_CAST(SERVICE, Service);
+DEFINE_CAST(TARGET, Target);
+DEFINE_CAST(DEVICE, Device);
+DEFINE_CAST(MOUNT, Mount);
+DEFINE_CAST(AUTOMOUNT, Automount);
+DEFINE_CAST(SNAPSHOT, Snapshot);
+DEFINE_CAST(SWAP, Swap);
+DEFINE_CAST(PATH, Path);
+
+Unit *unit_new(Manager *m, size_t size);
+void unit_free(Unit *u);
+
+int unit_add_name(Unit *u, const char *name);
+
+int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference);
+int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference);
+
+int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference);
+int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference);
+
+int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference);
+int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference);
+
+int unit_add_exec_dependencies(Unit *u, ExecContext *c);
+
+int unit_add_cgroup(Unit *u, CGroupBonding *b);
+int unit_add_cgroup_from_text(Unit *u, const char *name);
+int unit_add_default_cgroups(Unit *u);
+CGroupBonding* unit_get_default_cgroup(Unit *u);
+int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback);
+
+int unit_choose_id(Unit *u, const char *name);
+int unit_set_description(Unit *u, const char *description);
+
+bool unit_check_gc(Unit *u);
+
+void unit_add_to_load_queue(Unit *u);
+void unit_add_to_dbus_queue(Unit *u);
+void unit_add_to_cleanup_queue(Unit *u);
+void unit_add_to_gc_queue(Unit *u);
+
+int unit_merge(Unit *u, Unit *other);
+int unit_merge_by_name(Unit *u, const char *other);
+
+Unit *unit_follow_merge(Unit *u);
+
+int unit_load_fragment_and_dropin(Unit *u);
+int unit_load_fragment_and_dropin_optional(Unit *u);
+int unit_load(Unit *unit);
+
+const char *unit_description(Unit *u);
+
+bool unit_has_name(Unit *u, const char *name);
+
+UnitActiveState unit_active_state(Unit *u);
+
+const char* unit_sub_state_to_string(Unit *u);
+
+void unit_dump(Unit *u, FILE *f, const char *prefix);
+
+bool unit_can_reload(Unit *u);
+bool unit_can_start(Unit *u);
+bool unit_can_isolate(Unit *u);
+
+int unit_start(Unit *u);
+int unit_stop(Unit *u);
+int unit_reload(Unit *u);
+
+int unit_kill(Unit *u, KillWho w, int signo, DBusError *error);
+
+void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success);
+
+int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w);
+void unit_unwatch_fd(Unit *u, Watch *w);
+
+int unit_watch_pid(Unit *u, pid_t pid);
+void unit_unwatch_pid(Unit *u, pid_t pid);
+
+int unit_watch_timer(Unit *u, usec_t delay, Watch *w);
+void unit_unwatch_timer(Unit *u, Watch *w);
+
+int unit_watch_bus_name(Unit *u, const char *name);
+void unit_unwatch_bus_name(Unit *u, const char *name);
+
+bool unit_job_is_applicable(Unit *u, JobType j);
+
+int set_unit_path(const char *p);
+
+char *unit_dbus_path(Unit *u);
+
+int unit_load_related_unit(Unit *u, const char *type, Unit **_found);
+int unit_get_related_unit(Unit *u, const char *type, Unit **_found);
+
+bool unit_can_serialize(Unit *u);
+int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs);
+void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_attr_(4,5);
+void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value);
+int unit_deserialize(Unit *u, FILE *f, FDSet *fds);
+
+int unit_add_node_link(Unit *u, const char *what, bool wants);
+
+int unit_coldplug(Unit *u);
+
+void unit_status_printf(Unit *u, const char *status, const char *format, ...);
+
+bool unit_need_daemon_reload(Unit *u);
+
+void unit_reset_failed(Unit *u);
+
+Unit *unit_following(Unit *u);
+
+bool unit_pending_inactive(Unit *u);
+bool unit_pending_active(Unit *u);
+
+int unit_add_default_target_dependency(Unit *u, Unit *target);
+
+char *unit_default_cgroup_path(Unit *u);
+
+int unit_following_set(Unit *u, Set **s);
+
+void unit_trigger_on_failure(Unit *u);
+
+bool unit_condition_test(Unit *u);
+
+UnitFileState unit_get_unit_file_state(Unit *u);
+
+Unit* unit_ref_set(UnitRef *ref, Unit *u);
+void unit_ref_unset(UnitRef *ref);
+
+#define UNIT_DEREF(ref) ((ref).unit)
+
+int unit_add_one_mount_link(Unit *u, Mount *m);
+int unit_add_mount_links(Unit *u);
+
+int unit_exec_context_defaults(Unit *u, ExecContext *c);
+
+ExecContext *unit_get_exec_context(Unit *u);
+
+const char *unit_active_state_to_string(UnitActiveState i);
+UnitActiveState unit_active_state_from_string(const char *s);
+
+const char *unit_dependency_to_string(UnitDependency i);
+UnitDependency unit_dependency_from_string(const char *s);
diff --git a/src/core/user.conf b/src/core/user.conf
new file mode 100644
index 0000000000..e02b46bc3a
--- /dev/null
+++ b/src/core/user.conf
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# See systemd.conf(5) for details
+
+[Manager]
+#LogLevel=info
+#LogTarget=console
+#LogColor=yes
+#LogLocation=no
+#DefaultControllers=cpu
+#DefaultStandardOutput=inherit
+#DefaultStandardError=inherit