summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2010-05-16 18:45:24 +0200
committerLennart Poettering <lennart@poettering.net>2010-05-16 18:45:24 +0200
commite99e38bbdcca3fe5956823bdb3d38544ccf93221 (patch)
tree109871cf2bdcad19105d55f4eea62d5495a8f614 /src
parente9da3678fcfc774b325dc1eaa054d0e00028a1fc (diff)
build-sys: move source files to subdirectory
Diffstat (limited to 'src')
-rw-r--r--src/automount.c784
-rw-r--r--src/automount.h65
-rw-r--r--src/cgroup.c561
-rw-r--r--src/cgroup.h82
-rw-r--r--src/cgroups-agent.c72
-rw-r--r--src/conf-parser.c460
-rw-r--r--src/conf-parser.h55
-rw-r--r--src/dbus-automount.c44
-rw-r--r--src/dbus-automount.h31
-rw-r--r--src/dbus-device.c44
-rw-r--r--src/dbus-device.h31
-rw-r--r--src/dbus-execute.c28
-rw-r--r--src/dbus-execute.h79
-rw-r--r--src/dbus-job.c236
-rw-r--r--src/dbus-job.h32
-rw-r--r--src/dbus-manager.c657
-rw-r--r--src/dbus-manager.h29
-rw-r--r--src/dbus-mount.c134
-rw-r--r--src/dbus-mount.h31
-rw-r--r--src/dbus-service.c78
-rw-r--r--src/dbus-service.h31
-rw-r--r--src/dbus-snapshot.c75
-rw-r--r--src/dbus-snapshot.h31
-rw-r--r--src/dbus-socket.c64
-rw-r--r--src/dbus-socket.h31
-rw-r--r--src/dbus-swap.c73
-rw-r--r--src/dbus-swap.h32
-rw-r--r--src/dbus-target.c42
-rw-r--r--src/dbus-target.h31
-rw-r--r--src/dbus-unit.c451
-rw-r--r--src/dbus-unit.h128
-rw-r--r--src/dbus.c1136
-rw-r--r--src/dbus.h107
-rw-r--r--src/device.c471
-rw-r--r--src/device.h53
-rw-r--r--src/execute.c1619
-rw-r--r--src/execute.h221
-rw-r--r--src/fdset.c162
-rw-r--r--src/fdset.h40
-rw-r--r--src/hashmap.c543
-rw-r--r--src/hashmap.h85
-rw-r--r--src/hostname-setup.c168
-rw-r--r--src/hostname-setup.h27
-rw-r--r--src/initctl.c397
-rw-r--r--src/initreq.h77
-rw-r--r--src/ioprio.h57
-rw-r--r--src/job.c589
-rw-r--r--src/job.h150
-rw-r--r--src/linux/auto_dev-ioctl.h229
-rw-r--r--src/list.h119
-rw-r--r--src/load-dropin.c117
-rw-r--r--src/load-dropin.h31
-rw-r--r--src/load-fragment.c1483
-rw-r--r--src/load-fragment.h33
-rw-r--r--src/log.c439
-rw-r--r--src/log.h79
-rw-r--r--src/logger.c565
-rw-r--r--src/loopback-setup.c276
-rw-r--r--src/loopback-setup.h27
-rw-r--r--src/macro.h130
-rw-r--r--src/main.c787
-rw-r--r--src/manager.c2291
-rw-r--r--src/manager.h284
-rw-r--r--src/missing.h38
-rw-r--r--src/mount-setup.c168
-rw-r--r--src/mount-setup.h31
-rw-r--r--src/mount.c1539
-rw-r--r--src/mount.h110
-rw-r--r--src/namespace.c331
-rw-r--r--src/namespace.h34
-rw-r--r--src/ratelimit.c62
-rw-r--r--src/ratelimit.h55
-rw-r--r--src/sd-daemon.c96
-rw-r--r--src/sd-daemon.h61
-rw-r--r--src/securebits.h45
-rw-r--r--src/service.c2463
-rw-r--r--src/service.h147
-rw-r--r--src/set.c114
-rw-r--r--src/set.h68
-rw-r--r--src/snapshot.c276
-rw-r--r--src/snapshot.h52
-rw-r--r--src/socket-util.c468
-rw-r--r--src/socket-util.h79
-rw-r--r--src/socket.c1411
-rw-r--r--src/socket.h132
-rw-r--r--src/specifier.c110
-rw-r--r--src/specifier.h37
-rw-r--r--src/strv.c503
-rw-r--r--src/strv.h65
-rw-r--r--src/swap.c578
-rw-r--r--src/swap.h71
-rw-r--r--src/systemadm.vala956
-rw-r--r--src/systemctl.vala321
-rw-r--r--src/systemd-interfaces.vala137
-rw-r--r--src/target.c194
-rw-r--r--src/target.h49
-rw-r--r--src/test-engine.c99
-rw-r--r--src/test-job-type.c84
-rw-r--r--src/test-loopback.c35
-rw-r--r--src/test-ns.c60
-rw-r--r--src/timer.c51
-rw-r--r--src/timer.h49
-rw-r--r--src/unit-name.c424
-rw-r--r--src/unit-name.h54
-rw-r--r--src/unit.c1949
-rw-r--r--src/unit.h448
-rw-r--r--src/util.c2027
-rw-r--r--src/util.h256
-rw-r--r--src/utmp-wtmp.c214
-rw-r--r--src/utmp-wtmp.h33
110 files changed, 33898 insertions, 0 deletions
diff --git a/src/automount.c b/src/automount.c
new file mode 100644
index 0000000000..465354f555
--- /dev/null
+++ b/src/automount.c
@@ -0,0 +1,784 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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"
+
+static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = {
+ [AUTOMOUNT_DEAD] = UNIT_INACTIVE,
+ [AUTOMOUNT_WAITING] = UNIT_ACTIVE,
+ [AUTOMOUNT_RUNNING] = UNIT_ACTIVE,
+ [AUTOMOUNT_MAINTAINANCE] = UNIT_INACTIVE,
+};
+
+static int open_dev_autofs(Manager *m);
+
+static void automount_init(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(u);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ a->pipe_watch.fd = a->pipe_fd = -1;
+ a->pipe_watch.type = WATCH_INVALID;
+}
+
+static void repeat_unmout(const char *path) {
+ assert(path);
+
+ for (;;) {
+
+ 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)->meta.manager->exit_code != MANAGER_RELOAD &&
+ UNIT(a)->meta.manager->exit_code != MANAGER_REEXECUTE))
+ repeat_unmout(a->where);
+}
+
+static void automount_done(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ unmount_autofs(a);
+ a->mount = NULL;
+
+ 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 (a->meta.load_state != UNIT_LOADED ||
+ m->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ if (!path_startswith(a->where, m->where))
+ return 0;
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(a), true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(UNIT(a), UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int automount_add_mount_links(Automount *a) {
+ Meta *other;
+ int r;
+
+ assert(a);
+
+ LIST_FOREACH(units_per_type, other, a->meta.manager->units_per_type[UNIT_MOUNT])
+ if ((r = automount_add_one_mount_link(a, (Mount*) other)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int automount_verify(Automount *a) {
+ bool b;
+ char *e;
+ assert(a);
+
+ if (UNIT(a)->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ 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)->meta.id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int automount_load(Unit *u) {
+ int r;
+ Automount *a = AUTOMOUNT(u);
+
+ assert(u);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ /* Load a .automount file */
+ if ((r = unit_load_fragment_and_dropin_optional(u)) < 0)
+ return r;
+
+ if (u->meta.load_state == UNIT_LOADED) {
+
+ if (!a->where)
+ if (!(a->where = unit_name_to_path(u->meta.id)))
+ return -ENOMEM;
+
+ path_kill_slashes(a->where);
+
+ if ((r = automount_add_mount_links(a)) < 0)
+ return r;
+
+ if ((r = unit_load_related_unit(u, ".mount", (Unit**) &a->mount)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(a->mount), true)) < 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)->meta.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]);
+}
+
+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->meta.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"
+ "%sWhere: %s\n",
+ prefix, automount_state_to_string(a->state),
+ prefix, a->where);
+}
+
+static void automount_enter_dead(Automount *a, bool success) {
+ assert(a);
+
+ if (!success)
+ a->failure = true;
+
+ automount_set_state(a, a->failure ? AUTOMOUNT_MAINTAINANCE : 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;
+
+ 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)->meta.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.");
+
+ /* 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)->meta.manager->dev_autofs_fd,
+ ioctl_fd,
+ token,
+ status)) < 0)
+ r = k;
+ }
+
+ r = 0;
+
+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)->meta.manager)) < 0) {
+ r = dev_autofs_fd;
+ goto fail;
+ }
+
+ /* We knowingly ignore the results of this call */
+ mkdir_p(a->where, 0555);
+
+ 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, false);
+}
+
+static void automount_enter_runnning(Automount *a) {
+ int r;
+ struct stat st;
+
+ assert(a);
+ assert(a->mount);
+
+ /* Before we do anything, let's see if somebody is playing games with us? */
+
+ if (stat(a->where, &st) < 0) {
+ log_warning("%s failed stat automount point: %m", a->meta.id);
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id)
+ log_info("%s's automount point already active?", a->meta.id);
+ else if ((r = manager_add_job(UNIT(a)->meta.manager, JOB_START, UNIT(a->mount), JOB_REPLACE, true, NULL)) < 0) {
+ log_warning("%s failed to queue mount startup job: %s", a->meta.id, strerror(-r));
+ goto fail;
+ }
+
+ automount_set_state(a, AUTOMOUNT_RUNNING);
+ return;
+
+fail:
+ automount_enter_dead(a, false);
+}
+
+static int automount_start(Unit *u) {
+ Automount *a = AUTOMOUNT(u);
+
+ assert(a);
+
+ if (path_is_mount_point(a->where)) {
+ log_error("Path %s is already a mount point, refusing start for %s", a->where, u->meta.id);
+ return -EEXIST;
+ }
+
+ assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_MAINTAINANCE);
+
+ a->failure = false;
+ 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, true);
+ 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, "failure", yes_no(a->failure));
+ 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, "failure")) {
+ int b;
+
+ if ((b = parse_boolean(value)) < 0)
+ log_debug("Failed to parse failure value %s", value);
+ else
+ a->failure = b || a->failure;
+ } 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);
+
+ return UNIT_VTABLE(UNIT(a->mount))->check_gc(UNIT(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))) != 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:
+ log_debug("Got direct mount request for %s", packet.v5_packet.name);
+
+ 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, false);
+}
+
+static void automount_shutdown(Manager *m) {
+ assert(m);
+
+ if (m->dev_autofs_fd >= 0)
+ close_nointr_nofail(m->dev_autofs_fd);
+}
+
+static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
+ [AUTOMOUNT_DEAD] = "dead",
+ [AUTOMOUNT_WAITING] = "waiting",
+ [AUTOMOUNT_RUNNING] = "running",
+ [AUTOMOUNT_MAINTAINANCE] = "maintainance"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState);
+
+const UnitVTable automount_vtable = {
+ .suffix = ".automount",
+
+ .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,
+
+ .bus_message_handler = bus_automount_message_handler,
+
+ .shutdown = automount_shutdown
+};
diff --git a/src/automount.h b/src/automount.h
new file mode 100644
index 0000000000..014482cc5a
--- /dev/null
+++ b/src/automount.h
@@ -0,0 +1,65 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooautomounthfoo
+#define fooautomounthfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Automount Automount;
+
+#include "unit.h"
+
+typedef enum AutomountState {
+ AUTOMOUNT_DEAD,
+ AUTOMOUNT_WAITING,
+ AUTOMOUNT_RUNNING,
+ AUTOMOUNT_MAINTAINANCE,
+ _AUTOMOUNT_STATE_MAX,
+ _AUTOMOUNT_STATE_INVALID = -1
+} AutomountState;
+
+struct Automount {
+ Meta meta;
+
+ AutomountState state, deserialized_state;
+
+ char *where;
+
+ Mount *mount;
+
+ int pipe_fd;
+ Watch pipe_watch;
+ dev_t dev_id;
+
+ Set *tokens;
+
+ bool failure:1;
+};
+
+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);
+
+#endif
diff --git a/src/cgroup.c b/src/cgroup.c
new file mode 100644
index 0000000000..301fc949da
--- /dev/null
+++ b/src/cgroup.c
@@ -0,0 +1,561 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/mount.h>
+
+#include "cgroup.h"
+#include "log.h"
+
+static int translate_error(int error, int _errno) {
+
+ switch (error) {
+
+ case ECGROUPNOTCOMPILED:
+ case ECGROUPNOTMOUNTED:
+ case ECGROUPNOTEXIST:
+ case ECGROUPNOTCREATED:
+ return -ENOENT;
+
+ case ECGINVAL:
+ return -EINVAL;
+
+ case ECGROUPNOTALLOWED:
+ return -EPERM;
+
+ case ECGOTHER:
+ return -_errno;
+ }
+
+ return -EIO;
+}
+
+int cgroup_bonding_realize(CGroupBonding *b) {
+ int r;
+
+ assert(b);
+ assert(b->path);
+ assert(b->controller);
+
+ if (b->cgroup)
+ return 0;
+
+ if (!(b->cgroup = cgroup_new_cgroup(b->path)))
+ return -ENOMEM;
+
+ if (!cgroup_add_controller(b->cgroup, b->controller)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (b->inherit)
+ r = cgroup_create_cgroup_from_parent(b->cgroup, true);
+ else
+ r = cgroup_create_cgroup(b->cgroup, true);
+
+ if (r != 0) {
+ r = translate_error(r, errno);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ cgroup_free(&b->cgroup);
+ b->cgroup = NULL;
+ return r;
+}
+
+int cgroup_bonding_realize_list(CGroupBonding *first) {
+ CGroupBonding *b;
+
+ LIST_FOREACH(by_unit, b, first) {
+ int r;
+
+ if ((r = cgroup_bonding_realize(b)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+void cgroup_bonding_free(CGroupBonding *b) {
+ assert(b);
+
+ if (b->unit) {
+ CGroupBonding *f;
+
+ LIST_REMOVE(CGroupBonding, by_unit, b->unit->meta.cgroup_bondings, b);
+
+ assert_se(f = hashmap_get(b->unit->meta.manager->cgroup_bondings, b->path));
+ LIST_REMOVE(CGroupBonding, by_path, f, b);
+
+ if (f)
+ hashmap_replace(b->unit->meta.manager->cgroup_bondings, b->path, f);
+ else
+ hashmap_remove(b->unit->meta.manager->cgroup_bondings, b->path);
+ }
+
+ if (b->cgroup) {
+ if (b->only_us && b->clean_up && cgroup_bonding_is_empty(b) > 0)
+ cgroup_delete_cgroup_ext(b->cgroup, true);
+
+ cgroup_free(&b->cgroup);
+ }
+
+ free(b->controller);
+ free(b->path);
+ free(b);
+}
+
+void cgroup_bonding_free_list(CGroupBonding *first) {
+ CGroupBonding *b, *n;
+
+ LIST_FOREACH_SAFE(by_unit, b, n, first)
+ cgroup_bonding_free(b);
+}
+
+int cgroup_bonding_install(CGroupBonding *b, pid_t pid) {
+ int r;
+
+ assert(b);
+ assert(pid >= 0);
+
+ if (pid == 0)
+ pid = getpid();
+
+ if (!b->cgroup)
+ return -ENOENT;
+
+ if ((r = cgroup_attach_task_pid(b->cgroup, pid)))
+ return translate_error(r, errno);
+
+ return 0;
+}
+
+int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid) {
+ CGroupBonding *b;
+
+ LIST_FOREACH(by_unit, b, first) {
+ int r;
+
+ if ((r = cgroup_bonding_install(b, pid)) < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int cgroup_bonding_kill(CGroupBonding *b, int sig) {
+ int r;
+ Set *s;
+ bool done;
+ bool killed = false;
+
+ assert(b);
+ assert(sig > 0);
+
+ if (!b->only_us)
+ return -EAGAIN;
+
+ if (!(s = set_new(trivial_hash_func, trivial_compare_func)))
+ return -ENOMEM;
+
+ do {
+ void *iterator;
+ pid_t pid;
+
+ done = true;
+
+ if ((r = cgroup_get_task_begin(b->path, b->controller, &iterator, &pid)) != 0) {
+ if (r == ECGEOF) {
+ r = 0;
+ goto kill_done;
+ } else {
+ r = translate_error(r, errno);
+ break;
+ }
+ }
+
+ for (;;) {
+ if (set_get(s, INT_TO_PTR(pid)) != INT_TO_PTR(pid)) {
+
+ /* If we haven't killed this process
+ * yet, kill it */
+
+ if (kill(pid, sig) < 0 && errno != ESRCH) {
+ r = -errno;
+ break;
+ }
+
+ killed = true;
+ done = false;
+
+ if ((r = set_put(s, INT_TO_PTR(pid))) < 0)
+ break;
+ }
+
+ if ((r = cgroup_get_task_next(&iterator, &pid)) != 0) {
+
+ if (r == ECGEOF)
+ r = 0;
+ else
+ r = translate_error(r, errno);
+
+ break;
+ }
+ }
+
+ kill_done:
+ assert_se(cgroup_get_task_end(&iterator) == 0);
+
+ /* To avoid racing against processes which fork
+ * quicker than we can kill them we repeat this until
+ * no new pids need to be killed. */
+
+ } while (!done && r >= 0);
+
+ set_free(s);
+
+ if (r < 0)
+ return r;
+
+ return killed ? 0 : -ESRCH;
+}
+
+int cgroup_bonding_kill_list(CGroupBonding *first, int sig) {
+ CGroupBonding *b;
+ int r = -EAGAIN;
+
+ LIST_FOREACH(by_unit, b, first) {
+ if ((r = cgroup_bonding_kill(b, sig)) < 0) {
+ if (r == -EAGAIN || -ESRCH)
+ continue;
+
+ return r;
+ }
+
+ return 0;
+ }
+
+ return r;
+}
+
+/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we
+ * cannot know */
+int cgroup_bonding_is_empty(CGroupBonding *b) {
+ void *iterator;
+ pid_t pid;
+ int r;
+
+ assert(b);
+
+ r = cgroup_get_task_begin(b->path, b->controller, &iterator, &pid);
+
+ if (r == 0 || r == ECGEOF)
+ cgroup_get_task_end(&iterator);
+
+ /* Hmm, no PID in this group? Then it is definitely empty */
+ if (r == ECGEOF)
+ return 1;
+
+ /* Some error? Let's return it */
+ if (r != 0)
+ return translate_error(r, errno);
+
+ /* It's not empty, and we are the only user, then it is
+ * definitely not empty */
+ if (b->only_us)
+ return 0;
+
+ /* There are PIDs in the group but we aren't the only users,
+ * hence we cannot say */
+ return -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;
+}
+
+static int install_release_agent(Manager *m, const char *mount_point) {
+ char *p, *c, *sc;
+ int r;
+
+ assert(m);
+ assert(mount_point);
+
+ if (asprintf(&p, "%s/release_agent", mount_point) < 0)
+ return -ENOMEM;
+
+ if ((r = read_one_line_file(p, &c)) < 0) {
+ free(p);
+ return r;
+ }
+
+ sc = strstrip(c);
+
+ if (sc[0] == 0) {
+ if ((r = write_one_line_file(p, CGROUP_AGENT_PATH "\n" )) < 0) {
+ free(p);
+ free(c);
+ return r;
+ }
+ } else if (!streq(sc, CGROUP_AGENT_PATH)) {
+ free(p);
+ free(c);
+ return -EEXIST;
+ }
+
+ free(c);
+ free(p);
+
+ if (asprintf(&p, "%s/notify_on_release", mount_point) < 0)
+ return -ENOMEM;
+
+ if ((r = read_one_line_file(p, &c)) < 0) {
+ free(p);
+ return r;
+ }
+
+ sc = strstrip(c);
+
+ if (streq(sc, "0")) {
+ if ((r = write_one_line_file(p, "1\n")) < 0) {
+ free(p);
+ free(c);
+ return r;
+ }
+ } else if (!streq(sc, "1")) {
+ free(p);
+ free(c);
+ return -EIO;
+ }
+
+ free(p);
+ free(c);
+
+ return 0;
+}
+
+static int create_hierarchy_cgroup(Manager *m) {
+ struct cgroup *cg;
+ int r;
+
+ assert(m);
+
+ if (!(cg = cgroup_new_cgroup(m->cgroup_hierarchy)))
+ return -ENOMEM;
+
+ if (!(cgroup_add_controller(cg, m->cgroup_controller))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((r = cgroup_create_cgroup(cg, true)) != 0) {
+ log_error("Failed to create cgroup hierarchy group: %s", cgroup_strerror(r));
+ r = translate_error(r, errno);
+ goto finish;
+ }
+
+ if ((r = cgroup_attach_task(cg)) != 0) {
+ log_error("Failed to add ourselves to hierarchy group: %s", cgroup_strerror(r));
+ r = translate_error(r, errno);
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ cgroup_free(&cg);
+ return r;
+}
+
+int manager_setup_cgroup(Manager *m) {
+ char *mp, *cp;
+ int r;
+ pid_t pid;
+ char suffix[32];
+
+ assert(m);
+
+ if ((r = cgroup_init()) != 0) {
+ log_error("Failed to initialize libcg: %s", cgroup_strerror(r));
+ return translate_error(r, errno);
+ }
+
+ free(m->cgroup_controller);
+ if (!(m->cgroup_controller = strdup("debug")))
+ return -ENOMEM;
+
+ if ((r = cgroup_get_subsys_mount_point(m->cgroup_controller, &mp)))
+ return translate_error(r, errno);
+
+ pid = getpid();
+
+ if ((r = cgroup_get_current_controller_path(pid, m->cgroup_controller, &cp))) {
+ free(mp);
+ return translate_error(r, errno);
+ }
+
+ snprintf(suffix, sizeof(suffix), "/systemd-%u", (unsigned) pid);
+ char_array_0(suffix);
+
+ free(m->cgroup_hierarchy);
+
+ if (endswith(cp, suffix))
+ /* We probably got reexecuted and can continue to use our root cgroup */
+ m->cgroup_hierarchy = cp;
+ else {
+ /* We need a new root cgroup */
+
+ m->cgroup_hierarchy = NULL;
+ r = asprintf(&m->cgroup_hierarchy, "%s%s", streq(cp, "/") ? "" : cp, suffix);
+ free(cp);
+
+ if (r < 0) {
+ free(mp);
+ return -ENOMEM;
+ }
+ }
+
+ log_debug("Using cgroup controller <%s>, hierarchy mounted at <%s>, using root group <%s>.",
+ m->cgroup_controller,
+ mp,
+ m->cgroup_hierarchy);
+
+ if ((r = install_release_agent(m, mp)) < 0)
+ log_warning("Failed to install release agent, ignoring: %s", strerror(-r));
+ else
+ log_debug("Installed release agent, or already installed.");
+
+ free(mp);
+
+ if ((r = create_hierarchy_cgroup(m)) < 0)
+ log_error("Failed to create root cgroup hierarchy: %s", strerror(-r));
+ else
+ log_debug("Created root group.");
+
+ return r;
+}
+
+int manager_shutdown_cgroup(Manager *m, bool delete) {
+ struct cgroup *cg;
+ int r;
+
+ assert(m);
+
+ if (!m->cgroup_hierarchy)
+ return 0;
+
+ if (!(cg = cgroup_new_cgroup(m->cgroup_hierarchy)))
+ return -ENOMEM;
+
+ if (!(cgroup_add_controller(cg, m->cgroup_controller))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ /* Often enough we won't be able to delete the cgroup we
+ * ourselves are in, hence ignore all errors here */
+ if (delete)
+ cgroup_delete_cgroup_ext(cg, CGFLAG_DELETE_IGNORE_MIGRATION|CGFLAG_DELETE_RECURSIVE);
+ r = 0;
+
+finish:
+ cgroup_free(&cg);
+ return r;
+
+}
+
+int cgroup_notify_empty(Manager *m, const char *group) {
+ CGroupBonding *l, *b;
+
+ assert(m);
+ assert(group);
+
+ if (!(l = hashmap_get(m->cgroup_bondings, group)))
+ return 0;
+
+ LIST_FOREACH(by_path, b, l) {
+ int t;
+
+ if (!b->unit)
+ continue;
+
+ if ((t = cgroup_bonding_is_empty_list(b)) < 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 (UNIT_VTABLE(b->unit)->cgroup_notify_empty)
+ UNIT_VTABLE(b->unit)->cgroup_notify_empty(b->unit);
+ }
+
+ return 0;
+}
+
+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;
+}
diff --git a/src/cgroup.h b/src/cgroup.h
new file mode 100644
index 0000000000..d27c063c12
--- /dev/null
+++ b/src/cgroup.h
@@ -0,0 +1,82 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foocgrouphfoo
+#define foocgrouphfoo
+
+/***
+ 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 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 <libcgroup.h>
+
+typedef struct CGroupBonding CGroupBonding;
+
+#include "unit.h"
+
+/* Binds a cgroup to a name */
+struct CGroupBonding {
+ char *controller;
+ char *path;
+
+ Unit *unit;
+
+ struct cgroup *cgroup;
+
+ /* 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? */
+ bool clean_up:1;
+
+ /* When our tasks are the only ones in this group */
+ bool only_us:1;
+
+ /* Inherit parameters from parent group */
+ bool inherit:1;
+};
+
+int cgroup_bonding_realize(CGroupBonding *b);
+int cgroup_bonding_realize_list(CGroupBonding *first);
+
+void cgroup_bonding_free(CGroupBonding *b);
+void cgroup_bonding_free_list(CGroupBonding *first);
+
+int cgroup_bonding_install(CGroupBonding *b, pid_t pid);
+int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid);
+
+int cgroup_bonding_kill(CGroupBonding *b, int sig);
+int cgroup_bonding_kill_list(CGroupBonding *first, int sig);
+
+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);
+
+#include "manager.h"
+
+int manager_setup_cgroup(Manager *m);
+int manager_shutdown_cgroup(Manager *m, bool delete);
+
+int cgroup_notify_empty(Manager *m, const char *group);
+
+#endif
diff --git a/src/cgroups-agent.c b/src/cgroups-agent.c
new file mode 100644
index 0000000000..232b63e2da
--- /dev/null
+++ b/src/cgroups-agent.c
@@ -0,0 +1,72 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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/dbus.h>
+
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+ DBusError error;
+ DBusConnection *bus = NULL;
+ DBusMessage *m = NULL;
+ int r = 1;
+
+ dbus_error_init(&error);
+
+ if (argc != 2) {
+ log_error("Incorrect number of arguments.");
+ goto finish;
+ }
+
+ if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) {
+ log_error("Failed to get D-Bus connection: %s", error.message);
+ goto finish;
+ }
+
+ if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1/agent", "org.freedesktop.systemd1.Agent", "Released"))) {
+ log_error("Could not allocate signal message.");
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &argv[1],
+ DBUS_TYPE_INVALID)) {
+ log_error("Could not attach group information to signal message.");
+ goto finish;
+ }
+
+ if (!dbus_connection_send(bus, m, NULL)) {
+ log_error("Failed to send signal message.");
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (bus)
+ dbus_connection_unref(bus);
+
+ if (m)
+ dbus_message_unref(m);
+
+ dbus_error_free(&error);
+ return r;
+}
diff --git a/src/conf-parser.c b/src/conf-parser.c
new file mode 100644
index 0000000000..6994211b10
--- /dev/null
+++ b/src/conf-parser.c
@@ -0,0 +1,460 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "conf-parser.h"
+#include "util.h"
+#include "macro.h"
+#include "strv.h"
+#include "log.h"
+
+#define COMMENTS "#;\n"
+#define LINE_MAX 4096
+
+/* Run the user supplied parser for an assignment */
+static int next_assignment(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const ConfigItem *t,
+ const char *lvalue,
+ const char *rvalue,
+ void *userdata) {
+
+ assert(filename);
+ assert(t);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (; t->parse; t++) {
+
+ if (t->lvalue && !streq(lvalue, t->lvalue))
+ continue;
+
+ if (t->section && !section)
+ continue;
+
+ if (t->section && !streq(section, t->section))
+ continue;
+
+ return t->parse(filename, line, section, lvalue, rvalue, t->data, userdata);
+ }
+
+ /* Warn about unknown non-extension fields. */
+ if (!startswith(lvalue, "X-"))
+ log_info("[%s:%u] Unknown lvalue '%s' in section '%s'. Ignoring.", filename, line, lvalue, strna(section));
+
+ return 0;
+}
+
+/* Parse a variable assignment line */
+static int parse_line(const char *filename, unsigned line, char **section, const char* const * sections, const ConfigItem *t, char *l, void *userdata) {
+ char *e;
+
+ l = strstrip(l);
+
+ if (!*l)
+ return 0;
+
+ if (strchr(COMMENTS, *l))
+ return 0;
+
+ if (startswith(l, ".include ")) {
+ char *fn;
+ int r;
+
+ if (!(fn = file_in_same_dir(filename, strstrip(l+9))))
+ return -ENOMEM;
+
+ r = config_parse(fn, NULL, sections, t, userdata);
+ free(fn);
+
+ return r;
+ }
+
+ if (*l == '[') {
+ size_t k;
+ char *n;
+
+ k = strlen(l);
+ assert(k > 0);
+
+ if (l[k-1] != ']') {
+ log_error("[%s:%u] Invalid section header.", filename, line);
+ return -EBADMSG;
+ }
+
+ if (!(n = strndup(l+1, k-2)))
+ return -ENOMEM;
+
+ if (sections && !strv_contains((char**) sections, n)) {
+ log_error("[%s:%u] Unknown section '%s'.", filename, line, n);
+ free(n);
+ return -EBADMSG;
+ }
+
+ free(*section);
+ *section = n;
+
+ return 0;
+ }
+
+ if (!(e = strchr(l, '='))) {
+ log_error("[%s:%u] Missing '='.", filename, line);
+ return -EBADMSG;
+ }
+
+ *e = 0;
+ e++;
+
+ return next_assignment(filename, line, *section, t, strstrip(l), strstrip(e), userdata);
+}
+
+/* Go through the file and parse each line */
+int config_parse(const char *filename, FILE *f, const char* const * sections, const ConfigItem *t, void *userdata) {
+ unsigned line = 0;
+ char *section = NULL;
+ int r;
+ bool ours = false;
+
+ assert(filename);
+ assert(t);
+
+ if (!f) {
+ if (!(f = fopen(filename, "re"))) {
+ r = -errno;
+ log_error("Failed to open configuration file '%s': %s", filename, strerror(-r));
+ goto finish;
+ }
+
+ ours = true;
+ }
+
+ while (!feof(f)) {
+ char l[LINE_MAX];
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ r = -errno;
+ log_error("Failed to read configuration file '%s': %s", filename, strerror(-r));
+ goto finish;
+ }
+
+ if ((r = parse_line(filename, ++line, &section, sections, t, l, userdata)) < 0)
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ free(section);
+
+ if (f && ours)
+ fclose(f);
+
+ return r;
+}
+
+int config_parse_int(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *i = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atoi(rvalue, i)) < 0) {
+ log_error("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ return 0;
+}
+
+int config_parse_unsigned(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ unsigned *u = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atou(rvalue, u)) < 0) {
+ log_error("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ return 0;
+}
+
+int config_parse_size(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ size_t *sz = data;
+ unsigned u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atou(rvalue, &u)) < 0) {
+ log_error("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ *sz = (size_t) u;
+ return 0;
+}
+
+int config_parse_bool(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int k;
+ bool *b = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((k = parse_boolean(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse boolean value: %s", filename, line, rvalue);
+ return k;
+ }
+
+ *b = !!k;
+ return 0;
+}
+
+int config_parse_string(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data;
+ char *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (*rvalue) {
+ if (!(n = strdup(rvalue)))
+ return -ENOMEM;
+ } else
+ n = NULL;
+
+ free(*s);
+ *s = n;
+
+ return 0;
+}
+
+int config_parse_path(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **s = data;
+ char *n;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!path_is_absolute(rvalue)) {
+ log_error("[%s:%u] Not an absolute path: %s", filename, line, rvalue);
+ return -EINVAL;
+ }
+
+ if (!(n = strdup(rvalue)))
+ return -ENOMEM;
+
+ free(*s);
+ *s = n;
+
+ return 0;
+}
+
+int config_parse_strv(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char*** sv = data;
+ char **n;
+ char *w;
+ unsigned k;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ k = strv_length(*sv);
+ FOREACH_WORD_QUOTED(w, l, rvalue, state)
+ k++;
+
+ if (!(n = new(char*, k+1)))
+ return -ENOMEM;
+
+ if (*sv)
+ for (k = 0; (*sv)[k]; k++)
+ n[k] = (*sv)[k];
+ else
+ k = 0;
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state)
+ if (!(n[k++] = strndup(w, l)))
+ goto fail;
+
+ n[k] = NULL;
+ free(*sv);
+ *sv = n;
+
+ return 0;
+
+fail:
+ for (; k > 0; k--)
+ free(n[k-1]);
+ free(n);
+
+ return -ENOMEM;
+}
+
+int config_parse_path_strv(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char*** sv = data;
+ char **n;
+ char *w;
+ unsigned k;
+ size_t l;
+ char *state;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ k = strv_length(*sv);
+ FOREACH_WORD_QUOTED(w, l, rvalue, state)
+ k++;
+
+ if (!(n = new(char*, k+1)))
+ return -ENOMEM;
+
+ k = 0;
+ if (*sv)
+ for (; (*sv)[k]; k++)
+ n[k] = (*sv)[k];
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ if (!(n[k] = strndup(w, l))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (!path_is_absolute(n[k])) {
+ log_error("[%s:%u] Not an absolute path: %s", filename, line, rvalue);
+ r = -EINVAL;
+ goto fail;
+ }
+
+ k++;
+ }
+
+ n[k] = NULL;
+ free(*sv);
+ *sv = n;
+
+ return 0;
+
+fail:
+ free(n[k]);
+ for (; k > 0; k--)
+ free(n[k-1]);
+ free(n);
+
+ return r;
+}
diff --git a/src/conf-parser.h b/src/conf-parser.h
new file mode 100644
index 0000000000..bea2a8e9be
--- /dev/null
+++ b/src/conf-parser.h
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooconfparserhfoo
+#define fooconfparserhfoo
+
+/***
+ 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 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 <stdio.h>
+
+/* An abstract parser for simple, line based, shallow configuration
+ * files consisting of variable assignments only. */
+
+typedef int (*ConfigParserCallback)(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+
+/* Wraps info for parsing a specific configuration variable */
+typedef struct ConfigItem {
+ const char *lvalue; /* name of the variable */
+ ConfigParserCallback parse; /* Function that is called to parse the variable's value */
+ void *data; /* Where to store the variable's data */
+ const char *section;
+} ConfigItem;
+
+/* The configuration file parsing routine. Expects a table of
+ * config_items in *t that is terminated by an item where lvalue is
+ * NULL */
+int config_parse(const char *filename, FILE *f, const char* const * sections, const ConfigItem *t, void *userdata);
+
+/* Generic parsers */
+int config_parse_int(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_unsigned(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_size(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_path(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_strv(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_path_strv(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+
+#endif
diff --git a/src/dbus-automount.c b/src/dbus-automount.c
new file mode 100644
index 0000000000..9003b74b8a
--- /dev/null
+++ b/src/dbus-automount.c
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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-unit.h"
+#include "dbus-automount.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Automount\">"
+ " <property name=\"Where\" type=\"s\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+DBusHandlerResult bus_automount_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Automount", "Where", bus_property_append_string, "s", u->automount.where },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-automount.h b/src/dbus-automount.h
new file mode 100644
index 0000000000..947bf0f59f
--- /dev/null
+++ b/src/dbus-automount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusautomounthfoo
+#define foodbusautomounthfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_automount_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-device.c b/src/dbus-device.c
new file mode 100644
index 0000000000..83764783ad
--- /dev/null
+++ b/src/dbus-device.c
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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-unit.h"
+#include "dbus-device.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Device\">"
+ " <property name=\"SysFSPath\" type=\"s\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+DBusHandlerResult bus_device_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Device", "SysFSPath", bus_property_append_string, "s", u->device.sysfs },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-device.h b/src/dbus-device.h
new file mode 100644
index 0000000000..f2850a63f8
--- /dev/null
+++ b/src/dbus-device.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusdevicehfoo
+#define foodbusdevicehfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_device_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-execute.c b/src/dbus-execute.c
new file mode 100644
index 0000000000..8840396bc1
--- /dev/null
+++ b/src/dbus-execute.c
@@ -0,0 +1,28 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <dbus/dbus.h>
+
+#include "dbus-execute.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);
diff --git a/src/dbus-execute.h b/src/dbus-execute.h
new file mode 100644
index 0000000000..25ecd982a4
--- /dev/null
+++ b/src/dbus-execute.h
@@ -0,0 +1,79 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusexecutehfoo
+#define foodbusexecutehfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "manager.h"
+
+#define BUS_EXEC_CONTEXT_INTERFACE \
+ " <property name=\"Environment\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"UMask\" type=\"u\" access=\"read\"/>" \
+ " <property name=\"WorkingDirectory\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"RootDirectory\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"CPUSchedulingResetOnFork\" type=\"b\" access=\"read\"/>" \
+ " <property name=\"NonBlocking\" type=\"b\" access=\"read\"/>" \
+ " <property name=\"StandardInput\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"StandardOutput\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"StandardError\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"TTYPath\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"SyslogPriority\" type=\"i\" access=\"read\"/>" \
+ " <property name=\"SyslogIdentifier\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"SecureBits\" type=\"i\" access=\"read\"/>" \
+ " <property name=\"CapabilityBoundingSetDrop\" type=\"t\" access=\"read\"/>" \
+ " <property name=\"User\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"Group\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"SupplementaryGroups\" type=\"as\" access=\"read\"/>"
+
+#define BUS_EXEC_CONTEXT_PROPERTIES(interface, context) \
+ { interface, "Environment", bus_property_append_strv, "as", (context).environment }, \
+ { interface, "UMask", bus_property_append_mode, "u", &(context).umask }, \
+ /* RLimits */ \
+ { interface, "WorkingDirectory", bus_property_append_string, "s", (context).working_directory }, \
+ { interface, "RootDirectory", bus_property_append_string, "s", (context).root_directory }, \
+ /* OOM Adjust */ \
+ /* Nice */ \
+ /* IOPrio */ \
+ /* CPUSchedPolicy */ \
+ /* CPUSchedPriority */ \
+ /* CPUAffinity */ \
+ /* TimerSlackNS */ \
+ { interface, "CPUSchedulingResetOnFork", bus_property_append_bool, "b", &(context).cpu_sched_reset_on_fork }, \
+ { interface, "NonBlocking", bus_property_append_bool, "b", &(context).non_blocking }, \
+ { interface, "StandardInput", bus_execute_append_input, "s", &(context).std_input }, \
+ { interface, "StandardOutput", bus_execute_append_output, "s", &(context).std_output }, \
+ { interface, "StandardError", bus_execute_append_output, "s", &(context).std_error }, \
+ { interface, "TTYPath", bus_property_append_string, "s", (context).tty_path }, \
+ { interface, "SyslogPriority", bus_property_append_int, "i", &(context).syslog_priority }, \
+ { interface, "SyslogIdentifier", bus_property_append_string, "s", (context).syslog_identifier }, \
+ /* CAPABILITIES */ \
+ { interface, "SecureBits", bus_property_append_int, "i", &(context).secure_bits }, \
+ { interface, "CapabilityBoundingSetDrop", bus_property_append_uint64, "t", &(context).capability_bounding_set_drop }, \
+ { interface, "User", bus_property_append_string, "s", (context).user }, \
+ { interface, "Group", bus_property_append_string, "s", (context).group }, \
+ { interface, "SupplementaryGroups", bus_property_append_strv, "as", (context).supplementary_groups }
+
+int bus_execute_append_output(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_execute_append_input(Manager *m, DBusMessageIter *i, const char *property, void *data);
+
+#endif
diff --git a/src/dbus-job.c b/src/dbus-job.c
new file mode 100644
index 0000000000..3a6e7159e9
--- /dev/null
+++ b/src/dbus-job.c
@@ -0,0 +1,236 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-job.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ " <interface name=\"org.freedesktop.systemd1.Job\">"
+ " <method name=\"Cancel\"/>"
+ " <signal name=\"Changed\"/>"
+ " <property name=\"Id\" type=\"u\" access=\"read\"/>"
+ " <property name=\"Unit\" type=\"(so)\" access=\"read\"/>"
+ " <property name=\"JobType\" type=\"s\" access=\"read\"/>"
+ " <property name=\"State\" type=\"s\" access=\"read\"/>"
+ " </interface>"
+ BUS_PROPERTIES_INTERFACE
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+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(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Job *j = data;
+ DBusMessageIter sub;
+ char *p;
+
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(j);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+ return -ENOMEM;
+
+ if (!(p = unit_dbus_path(j->unit)))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->meta.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 DBusHandlerResult bus_job_message_dispatch(Job *j, DBusMessage *message) {
+ const BusProperty properties[] = {
+ { "org.freedesktop.systemd1.Job", "Id", bus_property_append_uint32, "u", &j->id },
+ { "org.freedesktop.systemd1.Job", "State", bus_job_append_state, "s", &j->state },
+ { "org.freedesktop.systemd1.Job", "JobType", bus_job_append_type, "s", &j->type },
+ { "org.freedesktop.systemd1.Job", "Unit", bus_job_append_unit, "(so)", j },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ DBusMessage *reply = NULL;
+ Manager *m = j->manager;
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) {
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ job_free(j);
+
+ } else
+ return bus_default_message_handler(j->manager, message, introspection, properties);
+
+ if (reply) {
+ if (!dbus_connection_send(m->api_bus, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+ Job *j;
+ int r;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ 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 ((r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j)) < 0) {
+
+ if (r == -ENOMEM)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ if (r == -ENOENT)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ return bus_send_error_reply(m, message, NULL, r);
+ }
+
+ return bus_job_message_dispatch(j, message);
+}
+
+const DBusObjectPathVTable bus_job_vtable = {
+ .message_function = bus_job_message_handler
+};
+
+void bus_job_send_change_signal(Job *j) {
+ char *p = NULL;
+ DBusMessage *m = NULL;
+
+ assert(j);
+ assert(j->in_dbus_queue);
+
+ LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
+ j->in_dbus_queue = false;
+
+ if (set_isempty(j->manager->subscribed)) {
+ j->sent_dbus_new_signal = true;
+ return;
+ }
+
+ if (!(p = job_dbus_path(j)))
+ goto oom;
+
+ if (j->sent_dbus_new_signal) {
+ /* Send a change signal */
+
+ if (!(m = dbus_message_new_signal(p, "org.freedesktop.systemd1.Job", "Changed")))
+ goto oom;
+ } else {
+ /* Send a new signal */
+
+ if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew")))
+ goto oom;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT32, &j->id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ }
+
+ if (!dbus_connection_send(j->manager->api_bus, m, NULL))
+ goto oom;
+
+ free(p);
+ dbus_message_unref(m);
+
+ j->sent_dbus_new_signal = true;
+
+ return;
+
+oom:
+ free(p);
+
+ if (m)
+ dbus_message_unref(m);
+
+ log_error("Failed to allocate job change signal.");
+}
+
+void bus_job_send_removed_signal(Job *j) {
+ char *p = NULL;
+ DBusMessage *m = NULL;
+
+ assert(j);
+
+ if (set_isempty(j->manager->subscribed) || !j->sent_dbus_new_signal)
+ return;
+
+ if (!(p = job_dbus_path(j)))
+ goto oom;
+
+ if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved")))
+ goto oom;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT32, &j->id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (!dbus_connection_send(j->manager->api_bus, m, NULL))
+ goto oom;
+
+ free(p);
+ dbus_message_unref(m);
+
+ return;
+
+oom:
+ free(p);
+
+ if (m)
+ dbus_message_unref(m);
+
+ log_error("Failed to allocate job remove signal.");
+}
diff --git a/src/dbus-job.h b/src/dbus-job.h
new file mode 100644
index 0000000000..cf9176052d
--- /dev/null
+++ b/src/dbus-job.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusjobhfoo
+#define foodbusjobhfoo
+
+/***
+ 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 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/dbus.h>
+
+void bus_job_send_change_signal(Job *j);
+void bus_job_send_removed_signal(Job *j);
+
+extern const DBusObjectPathVTable bus_job_vtable;
+
+#endif
diff --git a/src/dbus-manager.c b/src/dbus-manager.c
new file mode 100644
index 0000000000..90ab8d1a0c
--- /dev/null
+++ b/src/dbus-manager.c
@@ -0,0 +1,657 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-manager.h"
+#include "strv.h"
+
+#define INTROSPECTION_BEGIN \
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
+ "<node>" \
+ " <interface name=\"org.freedesktop.systemd1.Manager\">" \
+ " <method name=\"GetUnit\">" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"LoadUnit\">" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"GetJob\">" \
+ " <arg name=\"id\" type=\"u\" direction=\"in\"/>" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"ClearJobs\"/>" \
+ " <method name=\"ListUnits\">" \
+ " <arg name=\"units\" type=\"a(sssssouso)\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"ListJobs\">" \
+ " <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"Subscribe\"/>" \
+ " <method name=\"Unsubscribe\"/>" \
+ " <method name=\"Dump\"/>" \
+ " <method name=\"CreateSnapshot\">" \
+ " <arg name=\"name\" type=\"s\" direction=\"in\"/>" \
+ " <arg nane=\"cleanup\" type=\"b\" direction=\"in\"/>" \
+ " <arg name=\"unit\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"Reload\"/>" \
+ " <method name=\"Reexecute\"/>" \
+ " <method name=\"Exit\"/>" \
+ " <method name=\"SetEnvironment\">" \
+ " <arg name=\"names\" type=\"as\" direction=\"in\"/>" \
+ " </method>" \
+ " <method name=\"UnsetEnvironment\">" \
+ " <arg name=\"names\" type=\"as\" direction=\"in\"/>" \
+ " </method>" \
+ " <signal name=\"UnitNew\">" \
+ " <arg name=\"id\" type=\"s\"/>" \
+ " <arg name=\"unit\" type=\"o\"/>" \
+ " </signal>" \
+ " <signal name=\"UnitRemoved\">" \
+ " <arg name=\"id\" type=\"s\"/>" \
+ " <arg name=\"unit\" type=\"o\"/>" \
+ " </signal>" \
+ " <signal name=\"JobNew\">" \
+ " <arg name=\"id\" type=\"u\"/>" \
+ " <arg name=\"job\" type=\"o\"/>" \
+ " </signal>" \
+ " <signal name=\"JobRemoved\">" \
+ " <arg name=\"id\" type=\"u\"/>" \
+ " <arg name=\"job\" type=\"o\"/>" \
+ " </signal>" \
+ " <property name=\"Version\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"RunningAs\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"BootTimestamp\" type=\"t\" access=\"read\"/>" \
+ " <property name=\"LogLevel\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"LogTarget\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"NNames\" type=\"u\" access=\"read\"/>" \
+ " <property name=\"NJobs\" type=\"u\" access=\"read\"/>" \
+ " <property name=\"Environment\" type=\"as\" access=\"read\"/>" \
+ " </interface>" \
+ BUS_PROPERTIES_INTERFACE \
+ BUS_INTROSPECTABLE_INTERFACE
+
+#define INTROSPECTION_END \
+ "</node>"
+
+static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_running_as, manager_running_as, ManagerRunningAs);
+
+static int bus_manager_append_log_target(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ const char *t;
+
+ assert(m);
+ 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_append_log_level(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ const char *t;
+
+ assert(m);
+ assert(i);
+ assert(property);
+
+ t = log_level_to_string(log_get_max_level());
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_manager_append_n_names(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ uint32_t u;
+
+ assert(m);
+ assert(i);
+ assert(property);
+
+ 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(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ uint32_t u;
+
+ assert(m);
+ assert(i);
+ assert(property);
+
+ u = hashmap_size(m->jobs);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, &u))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
+ Manager *m = data;
+
+ const BusProperty properties[] = {
+ { "org.freedesktop.systemd1.Manager", "Version", bus_property_append_string, "s", PACKAGE_STRING },
+ { "org.freedesktop.systemd1.Manager", "RunningAs", bus_manager_append_running_as, "s", &m->running_as },
+ { "org.freedesktop.systemd1.Manager", "BootTimestamp", bus_property_append_uint64, "t", &m->boot_timestamp },
+ { "org.freedesktop.systemd1.Manager", "LogLevel", bus_manager_append_log_level, "s", NULL },
+ { "org.freedesktop.systemd1.Manager", "LogTarget", bus_manager_append_log_target, "s", NULL },
+ { "org.freedesktop.systemd1.Manager", "NNames", bus_manager_append_n_names, "u", NULL },
+ { "org.freedesktop.systemd1.Manager", "NJobs", bus_manager_append_n_jobs, "u", NULL },
+ { "org.freedesktop.systemd1.Manager", "Environment", bus_property_append_strv, "as", m->environment },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ int r;
+ DBusError error;
+ DBusMessage *reply = NULL;
+ char * path = NULL;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ 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_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(m, message, &error, -EINVAL);
+
+ if (!(u = manager_get_unit(m, name)))
+ return bus_send_error_reply(m, message, NULL, -ENOENT);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!(path = unit_dbus_path(u)))
+ 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(m, message, &error, -EINVAL);
+
+ if ((r = manager_load_unit(m, name, NULL, &u)) < 0)
+ return bus_send_error_reply(m, message, NULL, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!(path = unit_dbus_path(u)))
+ 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", "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(m, message, &error, -EINVAL);
+
+ if (!(j = manager_get_job(m, id)))
+ return bus_send_error_reply(m, message, NULL, -ENOENT);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!(path = job_dbus_path(j)))
+ 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")) {
+
+ manager_clear_jobs(m);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ 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;
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sssssouso)", &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, *job_type;
+ DBusMessageIter sub2;
+ uint32_t job_id;
+
+ if (k != u->meta.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->meta.load_state);
+ active_state = unit_active_state_to_string(unit_active_state(u));
+ sub_state = unit_sub_state_to_string(u);
+
+ if (!(u_path = unit_dbus_path(u)))
+ goto oom;
+
+ if (u->meta.job) {
+ job_id = (uint32_t) u->meta.job->id;
+
+ if (!(j_path = job_dbus_path(u->meta.job))) {
+ free(u_path);
+ goto oom;
+ }
+
+ job_type = job_type_to_string(u->meta.job->type);
+ } else {
+ job_id = 0;
+ j_path = u_path;
+ job_type = "";
+ }
+
+ if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &u->meta.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_OBJECT_PATH, &u_path) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &job_type) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path)) {
+ free(u_path);
+ if (u->meta.job)
+ free(j_path);
+ goto oom;
+ }
+
+ free(u_path);
+ if (u->meta.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;
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ 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);
+
+ if (!(j_path = job_dbus_path(j)))
+ goto oom;
+
+ if (!(u_path = unit_dbus_path(j->unit))) {
+ 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->meta.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;
+
+ if (!(client = strdup(dbus_message_get_sender(message))))
+ goto oom;
+
+ r = set_put(m->subscribed, client);
+
+ if (r < 0)
+ return bus_send_error_reply(m, message, NULL, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Unsubscribe")) {
+ char *client;
+
+ if (!(client = set_remove(m->subscribed, (char*) dbus_message_get_sender(message))))
+ return bus_send_error_reply(m, message, NULL, -ENOENT);
+
+ free(client);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Dump")) {
+ FILE *f;
+ char *dump = NULL;
+ size_t size;
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!(f = open_memstream(&dump, &size)))
+ 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;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_BOOLEAN, &cleanup,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(m, message, &error, -EINVAL);
+
+ if (name && name[0] == 0)
+ name = NULL;
+
+ if ((r = snapshot_create(m, name, cleanup, &s)) < 0)
+ return bus_send_error_reply(m, message, NULL, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!(path = unit_dbus_path(UNIT(s))))
+ 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;
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ 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. */
+
+ if (!(f = open_memstream(&introspection, &size)))
+ goto oom;
+
+ fputs(INTROSPECTION_BEGIN, f);
+
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+ char *p;
+
+ if (k != u->meta.id)
+ continue;
+
+ if (!(p = bus_path_escape(k))) {
+ 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")) {
+
+ 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. */
+
+ if (!(m->queued_message = dbus_message_new_method_return(message)))
+ goto oom;
+
+ m->exit_code = MANAGER_RELOAD;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reexecute")) {
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ m->exit_code = MANAGER_REEXECUTE;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Exit")) {
+
+ if (m->running_as == MANAGER_INIT)
+ return bus_send_error_reply(m, message, NULL, -ENOTSUP);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ m->exit_code = MANAGER_EXIT;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetEnvironment")) {
+ char **l = NULL, **e = NULL;
+
+ if ((r = bus_parse_strv(message, &l)) < 0) {
+ if (r == -ENOMEM)
+ goto oom;
+
+ return bus_send_error_reply(m, message, NULL, r);
+ }
+
+ e = strv_env_merge(m->environment, l, NULL);
+ 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", "UnsetEnvironment")) {
+ char **l = NULL, **e = NULL;
+
+ if ((r = bus_parse_strv(message, &l)) < 0) {
+ if (r == -ENOMEM)
+ goto oom;
+
+ return bus_send_error_reply(m, message, NULL, r);
+ }
+
+ e = strv_env_delete(m->environment, l, NULL);
+ strv_free(l);
+
+ if (!e)
+ goto oom;
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ strv_free(m->environment);
+ m->environment = e;
+
+ } else
+ return bus_default_message_handler(m, message, NULL, properties);
+
+ free(path);
+
+ if (reply) {
+ if (!dbus_connection_send(connection, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ free(path);
+
+ if (reply)
+ dbus_message_unref(reply);
+
+ 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/dbus-manager.h b/src/dbus-manager.h
new file mode 100644
index 0000000000..0acd2d086d
--- /dev/null
+++ b/src/dbus-manager.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusmanagerhfoo
+#define foodbusmanagerhfoo
+
+/***
+ 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 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/dbus.h>
+
+extern const DBusObjectPathVTable bus_manager_vtable;
+
+#endif
diff --git a/src/dbus-mount.c b/src/dbus-mount.c
new file mode 100644
index 0000000000..500b773bf6
--- /dev/null
+++ b/src/dbus-mount.c
@@ -0,0 +1,134 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-mount.h"
+#include "dbus-execute.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Mount\">"
+ " <property name=\"Where\" type=\"s\" access=\"read\"/>"
+ " <property name=\"What\" type=\"s\" access=\"read\"/>"
+ " <property name=\"Options\" type=\"s\" access=\"read\"/>"
+ " <property name=\"Type\" type=\"s\" access=\"read\"/>"
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>"
+ BUS_EXEC_CONTEXT_INTERFACE
+ " <property name=\"KillMode\" type=\"s\" access=\"read\"/>"
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+static int bus_mount_append_what(Manager *n, DBusMessageIter *i, const char *property, void *data) {
+ Mount *m = data;
+ const char *d;
+
+ assert(n);
+ 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 if (m->from_etc_fstab && m->parameters_etc_fstab.what)
+ d = m->parameters_etc_fstab.what;
+ else
+ d = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_mount_append_options(Manager *n, DBusMessageIter *i, const char *property, void *data) {
+ Mount *m = data;
+ const char *d;
+
+ assert(n);
+ 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 if (m->from_etc_fstab && m->parameters_etc_fstab.options)
+ d = m->parameters_etc_fstab.options;
+ else
+ d = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int bus_mount_append_type(Manager *n, DBusMessageIter *i, const char *property, void *data) {
+ Mount *m = data;
+ const char *d;
+
+ assert(n);
+ 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 if (m->from_etc_fstab && m->parameters_etc_fstab.fstype)
+ d = m->parameters_etc_fstab.fstype;
+ else
+ d = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+ return -ENOMEM;
+
+ return 0;
+}
+
+DBusHandlerResult bus_mount_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Mount", "Where", bus_property_append_string, "s", u->mount.where },
+ { "org.freedesktop.systemd1.Mount", "What", bus_mount_append_what, "s", u },
+ { "org.freedesktop.systemd1.Mount", "Options", bus_mount_append_options, "s", u },
+ { "org.freedesktop.systemd1.Mount", "Type", bus_mount_append_type, "s", u },
+ { "org.freedesktop.systemd1.Mount", "TimeoutUSec", bus_property_append_usec, "t", &u->mount.timeout_usec },
+ /* ExecCommand */
+ BUS_EXEC_CONTEXT_PROPERTIES("org.freedesktop.systemd1.Mount", u->mount.exec_context),
+ { "org.freedesktop.systemd1.Mount", "KillMode", bus_unit_append_kill_mode, "s", &u->mount.kill_mode },
+ { "org.freedesktop.systemd1.Mount", "ControlPID", bus_property_append_pid, "u", &u->mount.control_pid },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-mount.h b/src/dbus-mount.h
new file mode 100644
index 0000000000..b92867df28
--- /dev/null
+++ b/src/dbus-mount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusmounthfoo
+#define foodbusmounthfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_mount_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-service.c b/src/dbus-service.c
new file mode 100644
index 0000000000..24dd6c14f6
--- /dev/null
+++ b/src/dbus-service.c
@@ -0,0 +1,78 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-execute.h"
+#include "dbus-service.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Service\">"
+ " <property name=\"Type\" type=\"s\" access=\"read\"/>"
+ " <property name=\"Restart\" type=\"s\" access=\"read\"/>"
+ " <property name=\"PIDFile\" type=\"s\" access=\"read\"/>"
+ " <property name=\"RestartUSec\" type=\"t\" access=\"read\"/>"
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>"
+ BUS_EXEC_CONTEXT_INTERFACE
+ " <property name=\"PermissionsStartOnly\" type=\"b\" access=\"read\"/>"
+ " <property name=\"RootDirectoryStartOnly\" type=\"b\" access=\"read\"/>"
+ " <property name=\"ValidNoProcess\" type=\"b\" access=\"read\"/>"
+ " <property name=\"KillMode\" type=\"s\" access=\"read\"/>"
+ " <property name=\"MainPID\" type=\"u\" access=\"read\"/>"
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>"
+ " <property name=\"SysVPath\" type=\"s\" access=\"read\"/>"
+ " <property name=\"BusName\" type=\"s\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+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);
+
+DBusHandlerResult bus_service_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Service", "Type", bus_service_append_type, "s", &u->service.type },
+ { "org.freedesktop.systemd1.Service", "Restart", bus_service_append_restart, "s", &u->service.restart },
+ { "org.freedesktop.systemd1.Service", "PIDFile", bus_property_append_string, "s", u->service.pid_file },
+ { "org.freedesktop.systemd1.Service", "RestartUSec", bus_property_append_usec, "t", &u->service.restart_usec },
+ { "org.freedesktop.systemd1.Service", "TimeoutUSec", bus_property_append_usec, "t", &u->service.timeout_usec },
+ /* ExecCommand */
+ BUS_EXEC_CONTEXT_PROPERTIES("org.freedesktop.systemd1.Service", u->service.exec_context),
+ { "org.freedesktop.systemd1.Service", "PermissionsStartOnly", bus_property_append_bool, "b", &u->service.permissions_start_only },
+ { "org.freedesktop.systemd1.Service", "RootDirectoryStartOnly", bus_property_append_bool, "b", &u->service.root_directory_start_only },
+ { "org.freedesktop.systemd1.Service", "ValidNoProcess", bus_property_append_bool, "b", &u->service.valid_no_process },
+ { "org.freedesktop.systemd1.Service", "KillMode", bus_unit_append_kill_mode, "s", &u->service.kill_mode },
+ /* MainExecStatus */
+ { "org.freedesktop.systemd1.Service", "MainPID", bus_property_append_pid, "u", &u->service.main_pid },
+ { "org.freedesktop.systemd1.Service", "ControlPID", bus_property_append_pid, "u", &u->service.control_pid },
+ { "org.freedesktop.systemd1.Service", "SysVPath", bus_property_append_string, "s", u->service.sysv_path },
+ { "org.freedesktop.systemd1.Service", "BusName", bus_property_append_string, "s", u->service.bus_name },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-service.h b/src/dbus-service.h
new file mode 100644
index 0000000000..f0a468e90d
--- /dev/null
+++ b/src/dbus-service.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusservicehfoo
+#define foodbusservicehfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_service_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-snapshot.c b/src/dbus-snapshot.c
new file mode 100644
index 0000000000..8aeca15254
--- /dev/null
+++ b/src/dbus-snapshot.c
@@ -0,0 +1,75 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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-unit.h"
+#include "dbus-snapshot.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Snapshot\">"
+ " <method name=\"Remove\"/>"
+ " <property name=\"Cleanup\" type=\"b\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Snapshot", "Cleanup", bus_property_append_bool, "b", &u->snapshot.cleanup },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ DBusMessage *reply = NULL;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Snapshot", "Remove")) {
+
+ snapshot_remove(SNAPSHOT(u));
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ } else
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+
+ if (reply) {
+ if (!dbus_connection_send(u->meta.manager->api_bus, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
diff --git a/src/dbus-snapshot.h b/src/dbus-snapshot.h
new file mode 100644
index 0000000000..5f28550c95
--- /dev/null
+++ b/src/dbus-snapshot.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbussnapshothfoo
+#define foodbussnapshothfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-socket.c b/src/dbus-socket.c
new file mode 100644
index 0000000000..2a2349c00d
--- /dev/null
+++ b/src/dbus-socket.c
@@ -0,0 +1,64 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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-unit.h"
+#include "dbus-socket.h"
+#include "dbus-execute.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Socket\">"
+ " <property name=\"BindIPv6Only\" type=\"b\" access=\"read\"/>"
+ " <property name=\"Backlog\" type=\"u\" access=\"read\"/>"
+ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>"
+ BUS_EXEC_CONTEXT_INTERFACE
+ " <property name=\"KillMode\" type=\"s\" access=\"read\"/>"
+ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>"
+ " <property name=\"BindToDevice\" type=\"s\" access=\"read\"/>"
+ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>"
+ " <property name=\"SocketMode\" type=\"u\" access=\"read\"/>"
+ " <property name=\"Accept\" type=\"b\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+DBusHandlerResult bus_socket_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Socket", "BindIPv6Only", bus_property_append_bool, "b", &u->socket.bind_ipv6_only },
+ { "org.freedesktop.systemd1.Socket", "Backlog", bus_property_append_unsigned, "u", &u->socket.backlog },
+ { "org.freedesktop.systemd1.Socket", "TimeoutUSec", bus_property_append_usec, "t", &u->socket.timeout_usec },
+ /* ExecCommand */
+ BUS_EXEC_CONTEXT_PROPERTIES("org.freedesktop.systemd1.Socket", u->socket.exec_context),
+ { "org.freedesktop.systemd1.Socket", "KillMode", bus_unit_append_kill_mode, "s", &u->socket.kill_mode },
+ { "org.freedesktop.systemd1.Socket", "ControlPID", bus_property_append_pid, "u", &u->socket.control_pid },
+ { "org.freedesktop.systemd1.Socket", "BindToDevice", bus_property_append_string, "s", u->socket.bind_to_device },
+ { "org.freedesktop.systemd1.Socket", "DirectoryMode", bus_property_append_mode, "u", &u->socket.directory_mode },
+ { "org.freedesktop.systemd1.Socket", "SocketMode", bus_property_append_mode, "u", &u->socket.socket_mode },
+ { "org.freedesktop.systemd1.Socket", "Accept", bus_property_append_bool, "b", &u->socket.accept },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-socket.h b/src/dbus-socket.h
new file mode 100644
index 0000000000..6a8f534bf5
--- /dev/null
+++ b/src/dbus-socket.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbussockethfoo
+#define foodbussockethfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_socket_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-swap.c b/src/dbus-swap.c
new file mode 100644
index 0000000000..e935e09bf2
--- /dev/null
+++ b/src/dbus-swap.c
@@ -0,0 +1,73 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-swap.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Swap\">"
+ " <property name=\"What\" type=\"s\" access=\"read\"/>"
+ " <property name=\"Priority\" type=\"i\" access=\"read\"/>"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+static int bus_swap_append_priority(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Swap *s = data;
+ dbus_int32_t j;
+
+ assert(m);
+ 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 if (s->from_etc_fstab)
+ j = s->parameters_etc_fstab.priority;
+ else
+ j = -1;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, &j))
+ return -ENOMEM;
+
+ return 0;
+}
+
+DBusHandlerResult bus_swap_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { "org.freedesktop.systemd1.Swap", "What", bus_property_append_string, "s", u->swap.what },
+ { "org.freedesktop.systemd1.Swap", "Priority", bus_swap_append_priority, "i", u },
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-swap.h b/src/dbus-swap.h
new file mode 100644
index 0000000000..3bef6ad621
--- /dev/null
+++ b/src/dbus-swap.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusswaphfoo
+#define foodbusswaphfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_swap_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-target.c b/src/dbus-target.c
new file mode 100644
index 0000000000..be984b9bb4
--- /dev/null
+++ b/src/dbus-target.c
@@ -0,0 +1,42 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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-unit.h"
+#include "dbus-target.h"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ BUS_UNIT_INTERFACE
+ BUS_PROPERTIES_INTERFACE
+ " <interface name=\"org.freedesktop.systemd1.Target\">"
+ " </interface>"
+ BUS_INTROSPECTABLE_INTERFACE
+ "</node>";
+
+DBusHandlerResult bus_target_message_handler(Unit *u, DBusMessage *message) {
+ const BusProperty properties[] = {
+ BUS_UNIT_PROPERTIES,
+ { NULL, NULL, NULL, NULL, NULL }
+ };
+
+ return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+}
diff --git a/src/dbus-target.h b/src/dbus-target.h
new file mode 100644
index 0000000000..f6a1ac5e65
--- /dev/null
+++ b/src/dbus-target.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbustargethfoo
+#define foodbustargethfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_target_message_handler(Unit *u, DBusMessage *message);
+
+#endif
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
new file mode 100644
index 0000000000..e3e1be12ad
--- /dev/null
+++ b/src/dbus-unit.c
@@ -0,0 +1,451 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "dbus.h"
+#include "log.h"
+#include "dbus-unit.h"
+
+int bus_unit_append_names(Manager *m, 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->meta.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;
+}
+
+int bus_unit_append_dependencies(Manager *m, 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->meta.id))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_close_container(i, &sub))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_unit_append_description(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *d;
+
+ assert(m);
+ 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;
+}
+
+DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState);
+
+int bus_unit_append_active_state(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *state;
+
+ assert(m);
+ 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;
+}
+
+int bus_unit_append_sub_state(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ const char *state;
+
+ assert(m);
+ 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;
+}
+
+int bus_unit_append_can_start(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(u);
+
+ b = unit_can_start(u);
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_unit_append_can_reload(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ dbus_bool_t b;
+
+ assert(m);
+ 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;
+}
+
+int bus_unit_append_job(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ DBusMessageIter sub;
+ char *p;
+
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(u);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
+ return -ENOMEM;
+
+ if (u->meta.job) {
+
+ if (!(p = job_dbus_path(u->meta.job)))
+ return -ENOMEM;
+
+ if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->meta.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;
+}
+
+int bus_unit_append_default_cgroup(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ Unit *u = data;
+ char *t;
+ CGroupBonding *cgb;
+ bool success;
+
+ assert(m);
+ 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;
+}
+
+int bus_unit_append_cgroups(Manager *m, 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->meta.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;
+}
+
+DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_kill_mode, kill_mode, KillMode);
+
+static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusMessage *message) {
+ DBusMessage *reply = NULL;
+ Manager *m = u->meta.manager;
+ DBusError error;
+ JobType job_type = _JOB_TYPE_INVALID;
+
+ 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 (UNIT_VTABLE(u)->bus_message_handler)
+ return UNIT_VTABLE(u)->bus_message_handler(u, message);
+ else
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (job_type != _JOB_TYPE_INVALID) {
+ const char *smode;
+ JobMode mode;
+ Job *j;
+ int r;
+ char *path;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &smode,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(m, message, &error, -EINVAL);
+
+ if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID)
+ return bus_send_error_reply(m, message, NULL, -EINVAL);
+
+ if ((r = manager_add_job(m, job_type, u, mode, true, &j)) < 0)
+ return bus_send_error_reply(m, message, NULL, r);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!(path = job_dbus_path(j)))
+ goto oom;
+
+ if (!dbus_message_append_args(
+ reply,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ }
+
+ if (reply) {
+ if (!dbus_connection_send(m->api_bus, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ 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;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ 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 ((r = manager_get_unit_from_dbus_path(m, dbus_message_get_path(message), &u)) < 0) {
+
+ if (r == -ENOMEM)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ if (r == -ENOENT)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ return bus_send_error_reply(m, message, NULL, r);
+ }
+
+ return bus_unit_message_dispatch(u, message);
+}
+
+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);
+ assert(u->meta.in_dbus_queue);
+
+ LIST_REMOVE(Meta, dbus_queue, u->meta.manager->dbus_unit_queue, &u->meta);
+ u->meta.in_dbus_queue = false;
+
+ if (set_isempty(u->meta.manager->subscribed)) {
+ u->meta.sent_dbus_new_signal = true;
+ return;
+ }
+
+ if (!(p = unit_dbus_path(u)))
+ goto oom;
+
+ if (u->meta.sent_dbus_new_signal) {
+ /* Send a change signal */
+
+ if (!(m = dbus_message_new_signal(p, "org.freedesktop.systemd1.Unit", "Changed")))
+ 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->meta.id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_INVALID))
+ goto oom;
+ }
+
+ if (!dbus_connection_send(u->meta.manager->api_bus, m, NULL))
+ goto oom;
+
+ free(p);
+ dbus_message_unref(m);
+
+ u->meta.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 (set_isempty(u->meta.manager->subscribed) || !u->meta.sent_dbus_new_signal)
+ 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->meta.id,
+ DBUS_TYPE_OBJECT_PATH, &p,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (!dbus_connection_send(u->meta.manager->api_bus, m, NULL))
+ 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.");
+}
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
new file mode 100644
index 0000000000..c5840d5632
--- /dev/null
+++ b/src/dbus-unit.h
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbusunithfoo
+#define foodbusunithfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "manager.h"
+
+#define BUS_UNIT_INTERFACE \
+ " <interface name=\"org.freedesktop.systemd1.Unit\">" \
+ " <method name=\"Start\">" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"Stop\">" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"Restart\">" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <method name=\"Reload\">" \
+ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>" \
+ " <arg name=\"job\" type=\"o\" direction=\"out\"/>" \
+ " </method>" \
+ " <signal name=\"Changed\"/>" \
+ " <property name=\"Id\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"Names\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"Requires\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"RequiresOverridable\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"Requisite\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"RequisiteOverridable\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"Wants\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"RequiredBy\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"RequiredByOverridable\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"WantedBy\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"Conflicts\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"Before\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"After\" type=\"as\" access=\"read\"/>" \
+ " <property name=\"Description\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"LoadState\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"ActiveState\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"SubState\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"FragmentPath\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"InactiveExitTimestamp\" type=\"t\" access=\"read\"/>" \
+ " <property name=\"ActiveEnterTimestamp\" type=\"t\" access=\"read\"/>" \
+ " <property name=\"ActiveExitTimestamp\" type=\"t\" access=\"read\"/>" \
+ " <property name=\"InactiveEnterTimestamp\" type=\"t\" access=\"read\"/>" \
+ " <property name=\"CanReload\" type=\"b\" access=\"read\"/>" \
+ " <property name=\"CanStart\" type=\"b\" access=\"read\"/>" \
+ " <property name=\"Job\" type=\"(uo)\" access=\"read\"/>" \
+ " <property name=\"RecursiveStop\" type=\"b\" access=\"read\"/>" \
+ " <property name=\"StopWhenUneeded\" type=\"b\" access=\"read\"/>" \
+ " <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>" \
+ " <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>" \
+ " </interface>"
+
+#define BUS_UNIT_PROPERTIES \
+ { "org.freedesktop.systemd1.Unit", "Id", bus_property_append_string, "s", u->meta.id }, \
+ { "org.freedesktop.systemd1.Unit", "Names", bus_unit_append_names, "as", u }, \
+ { "org.freedesktop.systemd1.Unit", "Requires", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUIRES] }, \
+ { "org.freedesktop.systemd1.Unit", "RequiresOverridable", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUIRES_OVERRIDABLE] }, \
+ { "org.freedesktop.systemd1.Unit", "Requisite", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUISITE] }, \
+ { "org.freedesktop.systemd1.Unit", "RequisiteOverridable", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUISITE_OVERRIDABLE] }, \
+ { "org.freedesktop.systemd1.Unit", "Wants", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_WANTS] }, \
+ { "org.freedesktop.systemd1.Unit", "RequiredBy", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUIRED_BY] }, \
+ { "org.freedesktop.systemd1.Unit", "RequiredByOverridable",bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUIRED_BY_OVERRIDABLE] }, \
+ { "org.freedesktop.systemd1.Unit", "WantedBy", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_WANTED_BY] }, \
+ { "org.freedesktop.systemd1.Unit", "Conflicts", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_CONFLICTS] }, \
+ { "org.freedesktop.systemd1.Unit", "Before", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_BEFORE] }, \
+ { "org.freedesktop.systemd1.Unit", "After", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_AFTER] }, \
+ { "org.freedesktop.systemd1.Unit", "Description", bus_unit_append_description, "s", u }, \
+ { "org.freedesktop.systemd1.Unit", "LoadState", bus_unit_append_load_state, "s", &u->meta.load_state }, \
+ { "org.freedesktop.systemd1.Unit", "ActiveState", bus_unit_append_active_state, "s", u }, \
+ { "org.freedesktop.systemd1.Unit", "SubState", bus_unit_append_sub_state, "s", u }, \
+ { "org.freedesktop.systemd1.Unit", "FragmentPath", bus_property_append_string, "s", u->meta.fragment_path }, \
+ { "org.freedesktop.systemd1.Unit", "InactiveExitTimestamp",bus_property_append_uint64, "t", &u->meta.inactive_exit_timestamp }, \
+ { "org.freedesktop.systemd1.Unit", "ActiveEnterTimestamp", bus_property_append_uint64, "t", &u->meta.active_enter_timestamp }, \
+ { "org.freedesktop.systemd1.Unit", "ActiveExitTimestamp", bus_property_append_uint64, "t", &u->meta.active_exit_timestamp }, \
+ { "org.freedesktop.systemd1.Unit", "InactiveEnterTimestamp",bus_property_append_uint64, "t", &u->meta.inactive_enter_timestamp }, \
+ { "org.freedesktop.systemd1.Unit", "CanStart", bus_unit_append_can_start, "b", u }, \
+ { "org.freedesktop.systemd1.Unit", "CanReload", bus_unit_append_can_reload, "b", u }, \
+ { "org.freedesktop.systemd1.Unit", "Job", bus_unit_append_job, "(uo)", u }, \
+ { "org.freedesktop.systemd1.Unit", "RecursiveStop", bus_property_append_bool, "b", &u->meta.recursive_stop }, \
+ { "org.freedesktop.systemd1.Unit", "StopWhenUneeded", bus_property_append_bool, "b", &u->meta.stop_when_unneeded }, \
+ { "org.freedesktop.systemd1.Unit", "DefaultControlGroup", bus_unit_append_default_cgroup, "s", u }, \
+ { "org.freedesktop.systemd1.Unit", "ControlGroups", bus_unit_append_cgroups, "as", u }
+
+int bus_unit_append_names(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_dependencies(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_description(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_load_state(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_active_state(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_sub_state(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_can_start(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_can_reload(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_job(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_default_cgroup(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_cgroups(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_kill_mode(Manager *m, DBusMessageIter *i, const char *property, void *data);
+
+void bus_unit_send_change_signal(Unit *u);
+void bus_unit_send_removed_signal(Unit *u);
+
+extern const DBusObjectPathVTable bus_unit_vtable;
+
+#endif
diff --git a/src/dbus.c b/src/dbus.c
new file mode 100644
index 0000000000..6ed659a239
--- /dev/null
+++ b/src/dbus.c
@@ -0,0 +1,1136 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "dbus-unit.h"
+#include "dbus-job.h"
+#include "dbus-manager.h"
+
+static void api_bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) {
+ Manager *m = data;
+
+ assert(bus);
+ assert(m);
+
+ if (!m->api_bus)
+ return;
+
+ assert(m->api_bus == bus);
+
+ m->request_api_bus_dispatch = status != DBUS_DISPATCH_COMPLETE;
+}
+
+static void system_bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) {
+ Manager *m = data;
+
+ assert(bus);
+ assert(m);
+
+ if (!m->system_bus)
+ return;
+
+ assert(m->system_bus == bus);
+
+ m->request_system_bus_dispatch = status != DBUS_DISPATCH_COMPLETE;
+}
+
+static uint32_t bus_flags_to_events(DBusWatch *bus_watch) {
+ unsigned flags;
+ uint32_t events = 0;
+
+ assert(bus_watch);
+
+ /* no watch flags for disabled watches */
+ if (!dbus_watch_get_enabled(bus_watch))
+ return 0;
+
+ flags = dbus_watch_get_flags(bus_watch);
+
+ if (flags & DBUS_WATCH_READABLE)
+ events |= EPOLLIN;
+ if (flags & DBUS_WATCH_WRITABLE)
+ events |= EPOLLOUT;
+
+ return events | EPOLLHUP | EPOLLERR;
+}
+
+static unsigned events_to_bus_flags(uint32_t events) {
+ unsigned flags = 0;
+
+ if (events & EPOLLIN)
+ flags |= DBUS_WATCH_READABLE;
+ if (events & EPOLLOUT)
+ flags |= DBUS_WATCH_WRITABLE;
+ if (events & EPOLLHUP)
+ flags |= DBUS_WATCH_HANGUP;
+ if (events & EPOLLERR)
+ flags |= DBUS_WATCH_ERROR;
+
+ return flags;
+}
+
+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, events_to_bus_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) {
+ free(w);
+ close_nointr_nofail(w->fd);
+ 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);
+
+ if (!(w = dbus_watch_get_data(bus_watch)))
+ 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);
+
+ assert_se(w = dbus_watch_get_data(bus_watch));
+ 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_interval;
+ }
+
+ 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);
+
+ if (!(w = dbus_timeout_get_data(timeout)))
+ 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);
+
+ assert_se(w = dbus_timeout_get_data(timeout));
+ 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;
+
+ assert(connection);
+ assert(message);
+ assert(m);
+
+ dbus_error_init(&error);
+
+ /* 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_error("Warning! 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", error.message);
+ else {
+ if (set_remove(m->subscribed, (char*) name))
+ log_debug("Subscription client vanished: %s (left: %u)", name, set_size(m->subscribed));
+
+ 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);
+ }
+ }
+
+ dbus_error_free(&error);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+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);
+
+ /* 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_error("Warning! System D-Bus connection terminated.");
+ bus_done_system(m);
+
+ } if (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", error.message);
+ else
+ cgroup_notify_empty(m, cgroup);
+ }
+
+ dbus_error_free(&error);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+unsigned bus_dispatch(Manager *m) {
+ 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 (!dbus_connection_send(m->api_bus, m->queued_message, NULL))
+ return 0;
+
+ dbus_message_unref(m->queued_message);
+ m->queued_message = NULL;
+ }
+
+ if (m->request_api_bus_dispatch) {
+ if (dbus_connection_dispatch(m->api_bus) == DBUS_DISPATCH_COMPLETE)
+ m->request_api_bus_dispatch = false;
+
+ return 1;
+ }
+
+ if (m->request_system_bus_dispatch) {
+ if (dbus_connection_dispatch(m->system_bus) == DBUS_DISPATCH_COMPLETE)
+ m->request_system_bus_dispatch = false;
+
+ 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", error.message);
+ 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", error.message);
+ 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";
+ uint32_t flags = 0;
+ 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 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 -ENOMEM;
+
+ return 0;
+}
+
+int bus_init_system(Manager *m) {
+ DBusError error;
+ char *id;
+ int r;
+
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (m->system_bus)
+ return 0;
+
+ if (m->running_as != MANAGER_SESSION && m->api_bus)
+ m->system_bus = m->api_bus;
+ else {
+ if (!(m->system_bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) {
+ log_debug("Failed to get system D-Bus connection, retrying later: %s", error.message);
+ dbus_error_free(&error);
+ return 0;
+ }
+
+ dbus_connection_set_dispatch_status_function(m->system_bus, system_bus_dispatch_status, m, NULL);
+ m->request_system_bus_dispatch = true;
+
+ if ((r = bus_setup_loop(m, m->system_bus)) < 0) {
+ bus_done_system(m);
+ return r;
+ }
+ }
+
+ if (!dbus_connection_add_filter(m->system_bus, system_bus_message_filter, m, NULL)) {
+ bus_done_system(m);
+ return -ENOMEM;
+ }
+
+ dbus_bus_add_match(m->system_bus,
+ "type='signal',"
+ "interface='org.freedesktop.systemd1.Agent',"
+ "path='/org/freedesktop/systemd1/agent'",
+ &error);
+
+ if (dbus_error_is_set(&error)) {
+ log_error("Failed to register match: %s", error.message);
+ dbus_error_free(&error);
+ bus_done_system(m);
+ return -ENOMEM;
+ }
+
+ 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;
+}
+
+int bus_init_api(Manager *m) {
+ DBusError error;
+ char *id;
+ int r;
+
+ assert(m);
+
+ dbus_error_init(&error);
+
+ if (m->api_bus)
+ return 0;
+
+ if (m->name_data_slot < 0)
+ if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot))
+ return -ENOMEM;
+
+ if (m->running_as != MANAGER_SESSION && m->system_bus)
+ m->api_bus = m->system_bus;
+ else {
+ if (!(m->api_bus = dbus_bus_get_private(m->running_as == MANAGER_SESSION ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &error))) {
+ log_debug("Failed to get API D-Bus connection, retrying later: %s", error.message);
+ dbus_error_free(&error);
+ return 0;
+ }
+
+ dbus_connection_set_dispatch_status_function(m->api_bus, api_bus_dispatch_status, m, NULL);
+ m->request_api_bus_dispatch = true;
+
+ if ((r = bus_setup_loop(m, m->api_bus)) < 0) {
+ bus_done_api(m);
+ return 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)) {
+ bus_done_api(m);
+ return -ENOMEM;
+ }
+
+ dbus_bus_add_match(m->api_bus,
+ "type='signal',"
+ "sender='"DBUS_SERVICE_DBUS"',"
+ "interface='"DBUS_INTERFACE_DBUS"',"
+ "path='"DBUS_PATH_DBUS"'",
+ &error);
+
+ if (dbus_error_is_set(&error)) {
+ log_error("Failed to register match: %s", error.message);
+ dbus_error_free(&error);
+ bus_done_api(m);
+ return -ENOMEM;
+ }
+
+ if ((r = request_name(m)) < 0) {
+ bus_done_api(m);
+ return r;
+ }
+
+ 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);
+
+ if (!m->subscribed)
+ if (!(m->subscribed = set_new(string_hash_func, string_compare_func)))
+ return -ENOMEM;
+
+ return 0;
+}
+
+void bus_done_api(Manager *m) {
+ assert(m);
+
+ if (m->api_bus) {
+ if (m->system_bus == m->api_bus)
+ m->system_bus = NULL;
+
+ dbus_connection_set_dispatch_status_function(m->api_bus, NULL, NULL, NULL);
+ dbus_connection_flush(m->api_bus);
+ dbus_connection_close(m->api_bus);
+ dbus_connection_unref(m->api_bus);
+ m->api_bus = NULL;
+ }
+
+ if (m->subscribed) {
+ char *c;
+
+ while ((c = set_steal_first(m->subscribed)))
+ free(c);
+
+ set_free(m->subscribed);
+ m->subscribed = NULL;
+ }
+
+ if (m->name_data_slot >= 0)
+ dbus_pending_call_free_data_slot(&m->name_data_slot);
+
+ if (m->queued_message) {
+ dbus_message_unref(m->queued_message);
+ m->queued_message = NULL;
+ }
+}
+
+void bus_done_system(Manager *m) {
+ assert(m);
+
+ if (m->system_bus == m->api_bus)
+ bus_done_api(m);
+
+ if (m->system_bus) {
+ dbus_connection_set_dispatch_status_function(m->system_bus, NULL, NULL, NULL);
+ dbus_connection_flush(m->system_bus);
+ dbus_connection_close(m->system_bus);
+ dbus_connection_unref(m->system_bus);
+ m->system_bus = NULL;
+ }
+}
+
+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 = dbus_pending_call_get_data(pending, m->name_data_slot));
+ 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", error.message);
+ 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", error.message);
+ 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;
+}
+
+DBusHandlerResult bus_default_message_handler(Manager *m, DBusMessage *message, const char*introspection, const BusProperty *properties) {
+ DBusError error;
+ DBusMessage *reply = NULL;
+ int r;
+
+ assert(m);
+ assert(message);
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") && introspection) {
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID))
+ goto oom;
+
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get") && properties) {
+ const char *interface, *property;
+ const BusProperty *p;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_STRING, &property,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(m, message, &error, -EINVAL);
+
+ for (p = properties; p->property; p++)
+ if (streq(p->interface, interface) && streq(p->property, property))
+ break;
+
+ if (p->property) {
+ DBusMessageIter iter, sub;
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, p->signature, &sub))
+ goto oom;
+
+ if ((r = p->append(m, &sub, property, (void*) p->data)) < 0) {
+
+ if (r == -ENOMEM)
+ goto oom;
+
+ dbus_message_unref(reply);
+ return bus_send_error_reply(m, message, NULL, r);
+ }
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+ }
+ } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll") && properties) {
+ const char *interface;
+ const BusProperty *p;
+ DBusMessageIter iter, sub, sub2, sub3;
+ bool any = false;
+
+ if (!dbus_message_get_args(
+ message,
+ &error,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_INVALID))
+ return bus_send_error_reply(m, message, &error, -EINVAL);
+
+ if (!(reply = dbus_message_new_method_return(message)))
+ goto oom;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub))
+ goto oom;
+
+ for (p = properties; p->property; p++) {
+ if (!streq(p->interface, interface))
+ continue;
+
+ if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &sub2) ||
+ !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &p->property) ||
+ !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, p->signature, &sub3))
+ goto oom;
+
+ if ((r = p->append(m, &sub3, p->property, (void*) p->data)) < 0) {
+
+ if (r == -ENOMEM)
+ goto oom;
+
+ dbus_message_unref(reply);
+ return bus_send_error_reply(m, message, NULL, r);
+ }
+
+ if (!dbus_message_iter_close_container(&sub2, &sub3) ||
+ !dbus_message_iter_close_container(&sub, &sub2))
+ goto oom;
+
+ any = true;
+ }
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ goto oom;
+ }
+
+ if (reply) {
+ if (!dbus_connection_send(m->api_bus, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ 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 const char *error_to_dbus(int error) {
+
+ switch(error) {
+
+ case -EINVAL:
+ return DBUS_ERROR_INVALID_ARGS;
+
+ case -ENOMEM:
+ return DBUS_ERROR_NO_MEMORY;
+
+ case -EPERM:
+ case -EACCES:
+ return DBUS_ERROR_ACCESS_DENIED;
+
+ case -ESRCH:
+ return DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN;
+
+ case -ENOENT:
+ return DBUS_ERROR_FILE_NOT_FOUND;
+
+ case -EEXIST:
+ return DBUS_ERROR_FILE_EXISTS;
+
+ case -ETIMEDOUT:
+ return DBUS_ERROR_TIMEOUT;
+
+ case -EIO:
+ return DBUS_ERROR_IO_ERROR;
+
+ case -ENETRESET:
+ case -ECONNABORTED:
+ case -ECONNRESET:
+ return DBUS_ERROR_DISCONNECTED;
+ }
+
+ return DBUS_ERROR_FAILED;
+}
+
+DBusHandlerResult bus_send_error_reply(Manager *m, DBusMessage *message, DBusError *bus_error, int error) {
+ DBusMessage *reply = NULL;
+ const char *name, *text;
+
+ if (bus_error && dbus_error_is_set(bus_error)) {
+ name = bus_error->name;
+ text = bus_error->message;
+ } else {
+ name = error_to_dbus(error);
+ text = strerror(-error);
+ }
+
+ if (!(reply = dbus_message_new_error(message, name, text)))
+ goto oom;
+
+ if (!dbus_connection_send(m->api_bus, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+
+ if (bus_error)
+ dbus_error_free(bus_error);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ if (bus_error)
+ dbus_error_free(bus_error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+int bus_property_append_string(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ const char *t = data;
+
+ assert(m);
+ assert(i);
+ assert(property);
+
+ if (!t)
+ t = "";
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_strv(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ DBusMessageIter sub;
+ char **t = data;
+
+ assert(m);
+ assert(i);
+ assert(property);
+
+ if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
+ return -ENOMEM;
+
+ STRV_FOREACH(t, t)
+ 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;
+}
+
+int bus_property_append_bool(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ bool *b = data;
+ dbus_bool_t db;
+
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(b);
+
+ db = *b;
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_uint64(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(data);
+
+ /* Let's ensure that pid_t is actually 64bit, and hence this
+ * function can be used for usec_t */
+ assert_cc(sizeof(uint64_t) == sizeof(usec_t));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, data))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_uint32(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(data);
+
+ /* Let's ensure that pid_t and mode_t is actually 32bit, and
+ * hence this function can be used for pid_t/mode_t */
+ assert_cc(sizeof(uint32_t) == sizeof(pid_t));
+ assert_cc(sizeof(uint32_t) == sizeof(mode_t));
+ assert_cc(sizeof(uint32_t) == sizeof(unsigned));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, data))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_property_append_int32(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+ assert(m);
+ assert(i);
+ assert(property);
+ assert(data);
+
+ assert_cc(sizeof(int32_t) == sizeof(int));
+
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, data))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int bus_parse_strv(DBusMessage *m, char ***_l) {
+ DBusMessageIter iter, sub;
+ unsigned n = 0, i = 0;
+ char **l;
+
+ assert(m);
+ assert(_l);
+
+ if (!dbus_message_iter_init(m, &iter) ||
+ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING)
+ return -EINVAL;
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ n++;
+ dbus_message_iter_next(&sub);
+ }
+
+ if (!(l = new(char*, n+1)))
+ return -ENOMEM;
+
+ assert_se(dbus_message_iter_init(m, &iter));
+ dbus_message_iter_recurse(&iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ const char *s;
+
+ assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
+ dbus_message_iter_get_basic(&sub, &s);
+
+ if (!(l[i++] = strdup(s))) {
+ strv_free(l);
+ return -ENOMEM;
+ }
+
+ dbus_message_iter_next(&sub);
+ }
+
+ assert(i == n);
+ l[i] = NULL;
+
+ if (_l)
+ *_l = l;
+
+ return 0;
+}
diff --git a/src/dbus.h b/src/dbus.h
new file mode 100644
index 0000000000..51b71eac61
--- /dev/null
+++ b/src/dbus.h
@@ -0,0 +1,107 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbushfoo
+#define foodbushfoo
+
+/***
+ 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 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/dbus.h>
+
+#include "manager.h"
+
+typedef int (*BusPropertyCallback)(Manager *m, DBusMessageIter *iter, const char *property, void *data);
+
+typedef struct BusProperty {
+ const char *interface; /* interface of the property */
+ const char *property; /* name of the property */
+ BusPropertyCallback append; /* Function that is called to serialize this property */
+ const char *signature;
+ const void *data; /* The data of this property */
+} BusProperty;
+
+#define BUS_PROPERTIES_INTERFACE \
+ " <interface name=\"org.freedesktop.DBus.Properties\">" \
+ " <method name=\"Get\">" \
+ " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
+ " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
+ " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \
+ " </method>" \
+ " <method name=\"GetAll\">" \
+ " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
+ " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
+ " </method>" \
+ " </interface>"
+
+#define BUS_INTROSPECTABLE_INTERFACE \
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
+ " <method name=\"Introspect\">" \
+ " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
+ " </method>" \
+ " </interface>"
+
+int bus_init_system(Manager *m);
+int bus_init_api(Manager *m);
+void bus_done_system(Manager *m);
+void bus_done_api(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);
+
+DBusHandlerResult bus_default_message_handler(Manager *m, DBusMessage *message, const char* introspection, const BusProperty *properties);
+
+DBusHandlerResult bus_send_error_reply(Manager *m, DBusMessage *message, DBusError *bus_error, int error);
+
+int bus_property_append_string(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_strv(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_bool(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_int32(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_uint32(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_property_append_uint64(Manager *m, DBusMessageIter *i, const char *property, void *data);
+
+#define bus_property_append_int bus_property_append_int32
+#define bus_property_append_pid bus_property_append_uint32
+#define bus_property_append_mode bus_property_append_uint32
+#define bus_property_append_unsigned bus_property_append_uint32
+#define bus_property_append_usec bus_property_append_uint64
+
+#define DEFINE_BUS_PROPERTY_APPEND_ENUM(function,name,type) \
+ int function(Manager *m, DBusMessageIter *i, const char *property, void *data) { \
+ const char *value; \
+ type *field = data; \
+ \
+ assert(m); \
+ assert(i); \
+ assert(property); \
+ \
+ value = name##_to_string(*field); \
+ \
+ if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &value)) \
+ return -ENOMEM; \
+ \
+ return 0; \
+ }
+
+int bus_parse_strv(DBusMessage *m, char ***_l);
+
+#endif
diff --git a/src/device.c b/src/device.c
new file mode 100644
index 0000000000..e67d0a6c2d
--- /dev/null
+++ b/src/device.c
@@ -0,0 +1,471 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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"
+
+static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = {
+ [DEVICE_DEAD] = UNIT_INACTIVE,
+ [DEVICE_AVAILABLE] = UNIT_ACTIVE
+};
+
+static void device_done(Unit *u) {
+ Device *d = DEVICE(u);
+
+ assert(d);
+
+ free(d->sysfs);
+ d->sysfs = NULL;
+}
+
+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)->meta.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]);
+}
+
+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_AVAILABLE);
+
+ 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, bool make_id) {
+ char *e;
+ int r;
+
+ assert(u);
+ assert(dn);
+ assert(dn[0] == '/');
+
+ if (!(e = unit_name_from_path(dn, ".device")))
+ return -ENOMEM;
+
+ r = unit_add_name(u, e);
+
+ if (r >= 0 && make_id)
+ unit_choose_id(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);
+
+ if (!(e = unit_name_from_path(dn, ".device")))
+ return -ENOMEM;
+
+ u = manager_get_unit(m, e);
+ free(e);
+
+ if (u) {
+ *_u = u;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) {
+ const char *dn, *wants, *sysfs, *expose, *model, *alias;
+ Unit *u = NULL;
+ int r;
+ char *w, *state;
+ size_t l;
+ bool delete;
+ struct udev_list_entry *item = NULL, *first = NULL;
+ int b;
+
+ assert(m);
+
+ if (!(sysfs = udev_device_get_syspath(dev)))
+ return -ENOMEM;
+
+ if (!(expose = udev_device_get_property_value(dev, "SYSTEMD_EXPOSE")))
+ return 0;
+
+ if ((b = parse_boolean(expose)) < 0) {
+ log_error("Failed to parse SYSTEMD_EXPOSE udev property for device %s: %s", sysfs, expose);
+ return 0;
+ }
+
+ if (!b)
+ return 0;
+
+ /* Check whether this entry is even relevant for us. */
+ dn = udev_device_get_devnode(dev);
+ wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS");
+ alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS");
+
+ /* We allow exactly one alias to be configured a this time and
+ * it must be a path */
+
+ if (alias && !is_path(alias)) {
+ log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, alias);
+ alias = NULL;
+ }
+
+ if ((r = device_find_escape_name(m, sysfs, &u)) < 0)
+ return r;
+
+ if (r == 0 && dn)
+ if ((r = device_find_escape_name(m, dn, &u)) < 0)
+ return r;
+
+ if (r == 0) {
+ first = udev_device_get_devlinks_list_entry(dev);
+ udev_list_entry_foreach(item, first) {
+ if ((r = device_find_escape_name(m, udev_list_entry_get_name(item), &u)) < 0)
+ return r;
+
+ if (r > 0)
+ break;
+ }
+ }
+
+ if (r == 0 && alias)
+ if ((r = device_find_escape_name(m, alias, &u)) < 0)
+ return r;
+
+ /* FIXME: this needs proper merging */
+
+ assert((r > 0) == !!u);
+
+ /* If this is a different unit, then let's not merge things */
+ if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs))
+ u = NULL;
+
+ if (!u) {
+ delete = true;
+
+ if (!(u = unit_new(m)))
+ return -ENOMEM;
+
+ if ((r = device_add_escaped_name(u, sysfs, true)) < 0)
+ goto fail;
+
+ unit_add_to_load_queue(u);
+ } else
+ delete = false;
+
+ if (!(DEVICE(u)->sysfs))
+ if (!(DEVICE(u)->sysfs = strdup(sysfs))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (alias)
+ if ((r = device_add_escaped_name(u, alias, true)) < 0)
+ goto fail;
+
+ if (dn)
+ if ((r = device_add_escaped_name(u, dn, true)) < 0)
+ goto fail;
+
+ first = udev_device_get_devlinks_list_entry(dev);
+ udev_list_entry_foreach(item, first)
+ if ((r = device_add_escaped_name(u, udev_list_entry_get_name(item), false)) < 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 (dn) {
+ if ((r = unit_set_description(u, dn)) < 0)
+ goto fail;
+ } else
+ if ((r = unit_set_description(u, sysfs)) < 0)
+ goto fail;
+
+ if (wants) {
+ FOREACH_WORD(w, l, wants, state) {
+ char *e;
+
+ if (!(e = strndup(w, l))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = unit_add_dependency_by_name(u, UNIT_WANTS, NULL, e, true);
+ free(e);
+
+ if (r < 0)
+ goto fail;
+ }
+ }
+
+ if (update_state) {
+ manager_dispatch_load_queue(u->meta.manager);
+ device_set_state(DEVICE(u), DEVICE_AVAILABLE);
+ }
+
+ unit_add_to_dbus_queue(u);
+
+ return 0;
+
+fail:
+ if (delete && u)
+ unit_free(u);
+ return r;
+}
+
+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;
+ char *e;
+ Unit *u;
+ Device *d;
+
+ assert(m);
+ assert(dev);
+
+ if (!(sysfs = udev_device_get_syspath(dev)))
+ return -ENOMEM;
+
+ assert(sysfs[0] == '/');
+ if (!(e = unit_name_from_path(sysfs, ".device")))
+ return -ENOMEM;
+
+ u = manager_get_unit(m, e);
+ free(e);
+
+ if (!u)
+ return 0;
+
+ d = DEVICE(u);
+ free(d->sysfs);
+ d->sysfs = NULL;
+
+ device_set_state(d, DEVICE_DEAD);
+ return 0;
+}
+
+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;
+ }
+}
+
+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;
+ }
+
+ 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_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;
+
+ assert(m);
+ assert(events == EPOLLIN);
+
+ if (!(dev = udev_monitor_receive_device(m->udev_monitor))) {
+ log_error("Failed to receive device.");
+ return;
+ }
+
+ if (!(action = udev_device_get_action(dev))) {
+ log_error("Failed to get udev action string.");
+ goto fail;
+ }
+
+ if (streq(action, "remove")) {
+ 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_AVAILABLE] = "available"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState);
+
+const UnitVTable device_vtable = {
+ .suffix = ".device",
+
+ .no_requires = true,
+ .no_instances = true,
+ .no_snapshots = true,
+ .no_isolate = true,
+
+ .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_message_handler = bus_device_message_handler,
+
+ .enumerate = device_enumerate,
+ .shutdown = device_shutdown
+};
diff --git a/src/device.h b/src/device.h
new file mode 100644
index 0000000000..a5c5f745b8
--- /dev/null
+++ b/src/device.h
@@ -0,0 +1,53 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodevicehfoo
+#define foodevicehfoo
+
+/***
+ 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 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/>.
+***/
+
+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_AVAILABLE,
+ _DEVICE_STATE_MAX,
+ _DEVICE_STATE_INVALID = -1
+} DeviceState;
+
+struct Device {
+ Meta meta;
+
+ DeviceState state;
+
+ char *sysfs;
+};
+
+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);
+
+#endif
diff --git a/src/execute.c b/src/execute.c
new file mode 100644
index 0000000000..12f514504c
--- /dev/null
+++ b/src/execute.c
@@ -0,0 +1,1619 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "execute.h"
+#include "strv.h"
+#include "macro.h"
+#include "util.h"
+#include "log.h"
+#include "ioprio.h"
+#include "securebits.h"
+#include "cgroup.h"
+#include "namespace.h"
+
+/* 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";
+}
+
+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, int nfd) {
+ int fd, r;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ } sa;
+
+ assert(context);
+ assert(output < _EXEC_OUTPUT_MAX);
+ assert(ident);
+ assert(nfd >= 0);
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return -errno;
+
+ zero(sa);
+ sa.sa.sa_family = AF_UNIX;
+ strncpy(sa.un.sun_path+1, LOGGER_SOCKET, sizeof(sa.un.sun_path)-1);
+
+ if (connect(fd, &sa.sa, sizeof(sa)) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (shutdown(fd, SHUT_RD) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ /* We speak a very simple protocol between log server
+ * and client: one line for the log destination (kmsg
+ * or syslog), followed by the priority field,
+ * followed by the process name. Since we replaced
+ * stdin/stderr we simple use stdio to write to
+ * it. Note that we use stderr, to minimize buffer
+ * flushing issues. */
+
+ dprintf(fd,
+ "%s\n"
+ "%i\n"
+ "%s\n"
+ "%i\n",
+ output == EXEC_OUTPUT_KERNEL ? "kmsg" : "syslog",
+ context->syslog_priority,
+ context->syslog_identifier ? context->syslog_identifier : ident,
+ !context->syslog_no_prefix);
+
+ 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(const ExecContext *context, int socket_fd) {
+ assert(context);
+
+ if (socket_fd < 0 && context->std_input == EXEC_INPUT_SOCKET)
+ return EXEC_INPUT_NULL;
+
+ return context->std_input;
+}
+
+static int fixup_output(const ExecContext *context, int socket_fd) {
+ assert(context);
+
+ if (socket_fd < 0 && context->std_output == EXEC_OUTPUT_SOCKET)
+ return EXEC_OUTPUT_INHERIT;
+
+ return context->std_output;
+}
+
+static int fixup_error(const ExecContext *context, int socket_fd) {
+ assert(context);
+
+ if (socket_fd < 0 && context->std_error == EXEC_OUTPUT_SOCKET)
+ return EXEC_OUTPUT_INHERIT;
+
+ return context->std_error;
+}
+
+static int setup_input(const ExecContext *context, int socket_fd) {
+ ExecInput i;
+
+ assert(context);
+
+ i = fixup_input(context, socket_fd);
+
+ 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)) < 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) {
+ ExecOutput o;
+ ExecInput i;
+
+ assert(context);
+ assert(ident);
+
+ i = fixup_input(context, socket_fd);
+ o = fixup_output(context, socket_fd);
+
+ /* This expects the input is already set up */
+
+ switch (o) {
+
+ case EXEC_OUTPUT_INHERIT:
+
+ /* If the input is connected to a terminal, inherit that... */
+ if (is_terminal_input(i) || i == EXEC_INPUT_SOCKET)
+ return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
+
+ return STDIN_FILENO;
+
+ 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_KERNEL:
+ return connect_logger_as(context, o, ident, 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) {
+ ExecOutput o, e;
+ ExecInput i;
+
+ assert(context);
+ assert(ident);
+
+ i = fixup_input(context, socket_fd);
+ o = fixup_output(context, socket_fd);
+ e = fixup_error(context, 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 &&
+ !is_terminal_input(i))
+ return STDERR_FILENO;
+
+ /* Duplicate form 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_KERNEL:
+ return connect_logger_as(context, e, ident, 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(const ExecContext *context,
+ int *_saved_stdin,
+ int *_saved_stdout) {
+ int fd = -1, saved_stdin, saved_stdout = -1, r;
+
+ assert(context);
+ assert(_saved_stdin);
+ assert(_saved_stdout);
+
+ /* This returns positive EXIT_xxx return values instead of
+ * negative errno style values! */
+
+ if ((saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3)) < 0)
+ return EXIT_STDIN;
+
+ if ((saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3)) < 0) {
+ r = EXIT_STDOUT;
+ goto fail;
+ }
+
+ if ((fd = acquire_terminal(
+ tty_path(context),
+ context->std_input == EXEC_INPUT_TTY_FAIL,
+ context->std_input == EXEC_INPUT_TTY_FORCE)) < 0) {
+ r = EXIT_STDIN;
+ goto fail;
+ }
+
+ if (chown_terminal(fd, getuid()) < 0) {
+ r = EXIT_STDIN;
+ goto fail;
+ }
+
+ if (dup2(fd, STDIN_FILENO) < 0) {
+ r = EXIT_STDIN;
+ goto fail;
+ }
+
+ if (dup2(fd, STDOUT_FILENO) < 0) {
+ r = EXIT_STDOUT;
+ 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 restore_confirm_stdio(const ExecContext *context,
+ int *saved_stdin,
+ int *saved_stdout,
+ bool *keep_stdin,
+ bool *keep_stdout) {
+
+ assert(context);
+ assert(saved_stdin);
+ assert(*saved_stdin >= 0);
+ assert(saved_stdout);
+ assert(*saved_stdout >= 0);
+
+ /* This returns positive EXIT_xxx return values instead of
+ * negative errno style values! */
+
+ if (is_terminal_input(context->std_input)) {
+
+ /* The service wants terminal input. */
+
+ *keep_stdin = true;
+ *keep_stdout =
+ context->std_output == EXEC_OUTPUT_INHERIT ||
+ context->std_output == EXEC_OUTPUT_TTY;
+
+ } else {
+ /* If the service doesn't want a controlling terminal,
+ * then we need to get rid entirely of what we have
+ * already. */
+
+ if (release_terminal() < 0)
+ return EXIT_STDIN;
+
+ if (dup2(*saved_stdin, STDIN_FILENO) < 0)
+ return EXIT_STDIN;
+
+ if (dup2(*saved_stdout, STDOUT_FILENO) < 0)
+ return EXIT_STDOUT;
+
+ *keep_stdout = *keep_stdin = false;
+ }
+
+ return 0;
+}
+
+static int get_group_creds(const char *groupname, gid_t *gid) {
+ struct group *g;
+ unsigned long lu;
+
+ assert(groupname);
+ assert(gid);
+
+ /* We enforce some special rules for gid=0: in order to avoid
+ * NSS lookups for root we hardcode its data. */
+
+ if (streq(groupname, "root") || streq(groupname, "0")) {
+ *gid = 0;
+ return 0;
+ }
+
+ if (safe_atolu(groupname, &lu) >= 0) {
+ errno = 0;
+ g = getgrgid((gid_t) lu);
+ } else {
+ errno = 0;
+ g = getgrnam(groupname);
+ }
+
+ if (!g)
+ return errno != 0 ? -errno : -ESRCH;
+
+ *gid = g->gr_gid;
+ return 0;
+}
+
+static int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) {
+ struct passwd *p;
+ unsigned long lu;
+
+ assert(username);
+ assert(*username);
+ assert(uid);
+ assert(gid);
+ assert(home);
+
+ /* We enforce some special rules for uid=0: in order to avoid
+ * NSS lookups for root we hardcode its data. */
+
+ if (streq(*username, "root") || streq(*username, "0")) {
+ *username = "root";
+ *uid = 0;
+ *gid = 0;
+ *home = "/root";
+ return 0;
+ }
+
+ if (safe_atolu(*username, &lu) >= 0) {
+ errno = 0;
+ p = getpwuid((uid_t) lu);
+
+ /* If there are multiple users with the same id, make
+ * sure to leave $USER to the configured value instead
+ * of the first occurence in the database. However if
+ * the uid was configured by a numeric uid, then let's
+ * pick the real username from /etc/passwd. */
+ if (*username && p)
+ *username = p->pw_name;
+ } else {
+ errno = 0;
+ p = getpwnam(*username);
+ }
+
+ if (!p)
+ return errno != 0 ? -errno : -ESRCH;
+
+ *uid = p->pw_uid;
+ *gid = p->pw_gid;
+ *home = p->pw_dir;
+ return 0;
+}
+
+static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) {
+ bool keep_groups = false;
+ int r;
+
+ assert(context);
+
+ /* Lookup and ser GID and supplementary group list. Here too
+ * we avoid NSS lookups for gid=0. */
+
+ if (context->group || username) {
+
+ if (context->group)
+ if ((r = get_group_creds(context->group, &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 */
+ ngroups_max = (int) sysconf(_SC_NGROUPS_MAX);
+
+ 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) {
+
+ if (k >= ngroups_max) {
+ free(gids);
+ return -E2BIG;
+ }
+
+ if ((r = get_group_creds(*i, gids+k)) < 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 priviliges. */
+ 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 capabilites. 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;
+}
+
+int exec_spawn(ExecCommand *command,
+ char **argv,
+ const ExecContext *context,
+ int fds[], unsigned n_fds,
+ char **environment,
+ bool apply_permissions,
+ bool apply_chroot,
+ bool confirm_spawn,
+ CGroupBonding *cgroup_bondings,
+ pid_t *ret) {
+
+ pid_t pid;
+ int r;
+ char *line;
+ int socket_fd;
+
+ 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;
+
+ if (!argv)
+ argv = command->argv;
+
+ if (!(line = exec_command_line(argv)))
+ return -ENOMEM;
+
+ log_debug("About to execute: %s", line);
+ free(line);
+
+ if (cgroup_bondings)
+ if ((r = cgroup_bonding_realize_list(cgroup_bondings)))
+ return r;
+
+ if ((pid = fork()) < 0)
+ return -errno;
+
+ if (pid == 0) {
+ int i;
+ sigset_t ss;
+ const char *username = NULL, *home = NULL;
+ uid_t uid = (uid_t) -1;
+ gid_t gid = (gid_t) -1;
+ char **our_env = NULL, **final_env = NULL;
+ unsigned n_env = 0;
+ int saved_stdout = -1, saved_stdin = -1;
+ bool keep_stdout = false, keep_stdin = false;
+
+ /* child */
+
+ reset_all_signal_handlers();
+
+ if (sigemptyset(&ss) < 0 ||
+ sigprocmask(SIG_SETMASK, &ss, NULL) < 0) {
+ r = EXIT_SIGNAL_MASK;
+ goto fail;
+ }
+
+ if (!context->no_setsid)
+ if (setsid() < 0) {
+ r = EXIT_SETSID;
+ goto fail;
+ }
+
+ if (confirm_spawn) {
+ char response;
+
+ /* Set up terminal for the question */
+ if ((r = setup_confirm_stdio(context,
+ &saved_stdin, &saved_stdout)))
+ goto fail;
+
+ /* Now ask the question. */
+ if (!(line = exec_command_line(argv))) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ r = ask(&response, "yns", "Execute %s? [Yes, No, Skip] ", line);
+ free(line);
+
+ if (r < 0 || response == 'n') {
+ r = EXIT_CONFIRM;
+ goto fail;
+ } else if (response == 's') {
+ r = 0;
+ goto fail;
+ }
+
+ /* Release terminal for the question */
+ if ((r = restore_confirm_stdio(context,
+ &saved_stdin, &saved_stdout,
+ &keep_stdin, &keep_stdout)))
+ goto fail;
+ }
+
+ if (!keep_stdin)
+ if (setup_input(context, socket_fd) < 0) {
+ r = EXIT_STDIN;
+ goto fail;
+ }
+
+ if (!keep_stdout)
+ if (setup_output(context, socket_fd, file_name_from_path(command->path)) < 0) {
+ r = EXIT_STDOUT;
+ goto fail;
+ }
+
+ if (setup_error(context, socket_fd, file_name_from_path(command->path)) < 0) {
+ r = EXIT_STDERR;
+ goto fail;
+ }
+
+ if (cgroup_bondings)
+ if ((r = cgroup_bonding_install_list(cgroup_bondings, 0)) < 0) {
+ r = EXIT_CGROUP;
+ goto fail;
+ }
+
+ if (context->oom_adjust_set) {
+ char t[16];
+
+ snprintf(t, sizeof(t), "%i", context->oom_adjust);
+ char_array_0(t);
+
+ if (write_one_line_file("/proc/self/oom_adj", t) < 0) {
+ r = EXIT_OOM_ADJUST;
+ goto fail;
+ }
+ }
+
+ if (context->nice_set)
+ if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) {
+ r = EXIT_NICE;
+ goto fail;
+ }
+
+ 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) {
+ r = EXIT_SETSCHEDULER;
+ goto fail;
+ }
+ }
+
+ if (context->cpu_affinity_set)
+ if (sched_setaffinity(0, sizeof(context->cpu_affinity), &context->cpu_affinity) < 0) {
+ r = EXIT_CPUAFFINITY;
+ goto fail;
+ }
+
+ if (context->ioprio_set)
+ if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) {
+ r = EXIT_IOPRIO;
+ goto fail;
+ }
+
+ if (context->timer_slack_ns_set)
+ if (prctl(PR_SET_TIMERSLACK, context->timer_slack_ns_set) < 0) {
+ r = EXIT_TIMERSLACK;
+ goto fail;
+ }
+
+ if (strv_length(context->read_write_dirs) > 0 ||
+ strv_length(context->read_only_dirs) > 0 ||
+ strv_length(context->inaccessible_dirs) > 0 ||
+ context->mount_flags != MS_SHARED ||
+ context->private_tmp)
+ if ((r = setup_namespace(
+ context->read_write_dirs,
+ context->read_only_dirs,
+ context->inaccessible_dirs,
+ context->private_tmp,
+ context->mount_flags)) < 0)
+ goto fail;
+
+ if (context->user) {
+ username = context->user;
+ if (get_user_creds(&username, &uid, &gid, &home) < 0) {
+ r = EXIT_USER;
+ goto fail;
+ }
+
+ if (is_terminal_input(context->std_input))
+ if (chown_terminal(STDIN_FILENO, uid) < 0) {
+ r = EXIT_STDIN;
+ goto fail;
+ }
+ }
+
+ if (apply_permissions)
+ if (enforce_groups(context, username, uid) < 0) {
+ r = EXIT_GROUP;
+ goto fail;
+ }
+
+ umask(context->umask);
+
+ if (apply_chroot) {
+ if (context->root_directory)
+ if (chroot(context->root_directory) < 0) {
+ r = EXIT_CHROOT;
+ goto fail;
+ }
+
+ if (chdir(context->working_directory ? context->working_directory : "/") < 0) {
+ r = EXIT_CHDIR;
+ goto fail;
+ }
+ } else {
+
+ char *d;
+
+ if (asprintf(&d, "%s/%s",
+ context->root_directory ? context->root_directory : "",
+ context->working_directory ? context->working_directory : "") < 0) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ if (chdir(d) < 0) {
+ free(d);
+ r = EXIT_CHDIR;
+ goto fail;
+ }
+
+ free(d);
+ }
+
+ if (close_all_fds(fds, n_fds) < 0 ||
+ shift_fds(fds, n_fds) < 0 ||
+ flags_fds(fds, n_fds, context->non_blocking) < 0) {
+ r = EXIT_FDS;
+ goto fail;
+ }
+
+ if (apply_permissions) {
+
+ for (i = 0; i < RLIMIT_NLIMITS; i++) {
+ if (!context->rlimit[i])
+ continue;
+
+ if (setrlimit(i, context->rlimit[i]) < 0) {
+ r = EXIT_LIMITS;
+ goto fail;
+ }
+ }
+
+ if (context->user)
+ if (enforce_user(context, uid) < 0) {
+ r = EXIT_USER;
+ goto fail;
+ }
+
+ /* PR_GET_SECUREBITS is not priviliged, 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) {
+ r = EXIT_SECUREBITS;
+ goto fail;
+ }
+
+ if (context->capabilities)
+ if (cap_set_proc(context->capabilities) < 0) {
+ r = EXIT_CAPABILITIES;
+ goto fail;
+ }
+ }
+
+ if (!(our_env = new0(char*, 6))) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ if (n_fds > 0)
+ if (asprintf(our_env + n_env++, "LISTEN_PID=%llu", (unsigned long long) getpid()) < 0 ||
+ asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ if (home)
+ if (asprintf(our_env + n_env++, "HOME=%s", home) < 0) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ if (username)
+ if (asprintf(our_env + n_env++, "LOGNAME=%s", username) < 0 ||
+ asprintf(our_env + n_env++, "USER=%s", username) < 0) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ assert(n_env <= 6);
+
+ if (!(final_env = strv_env_merge(environment, our_env, context->environment, NULL))) {
+ r = EXIT_MEMORY;
+ goto fail;
+ }
+
+ execve(command->path, argv, final_env);
+ r = EXIT_EXEC;
+
+ fail:
+ strv_free(our_env);
+ strv_free(final_env);
+
+ if (saved_stdin >= 0)
+ close_nointr_nofail(saved_stdin);
+
+ if (saved_stdout >= 0)
+ close_nointr_nofail(saved_stdout);
+
+ _exit(r);
+ }
+
+ /* 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);
+
+ log_debug("Forked %s as %llu", command->path, (unsigned long long) pid);
+
+ command->exec_status.pid = pid;
+ command->exec_status.start_timestamp = now(CLOCK_REALTIME);
+
+ *ret = pid;
+ return 0;
+}
+
+void exec_context_init(ExecContext *c) {
+ assert(c);
+
+ c->umask = 0002;
+ c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0);
+ c->cpu_sched_policy = SCHED_OTHER;
+ c->syslog_priority = LOG_DAEMON|LOG_INFO;
+ c->mount_flags = MS_SHARED;
+}
+
+void exec_context_done(ExecContext *c) {
+ unsigned l;
+
+ assert(c);
+
+ strv_free(c->environment);
+ c->environment = 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->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;
+
+ 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;
+}
+
+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;
+ }
+}
+
+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",
+ 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));
+
+ if (c->environment)
+ for (e = c->environment; *e; e++)
+ fprintf(f, "%sEnvironment: %s\n", prefix, *e);
+
+ if (c->nice_set)
+ fprintf(f,
+ "%sNice: %i\n",
+ prefix, c->nice);
+
+ if (c->oom_adjust_set)
+ fprintf(f,
+ "%sOOMAdjust: %i\n",
+ prefix, c->oom_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)
+ fprintf(f,
+ "%sIOSchedulingClass: %s\n"
+ "%sIOPriority: %i\n",
+ prefix, ioprio_class_to_string(IOPRIO_PRIO_CLASS(c->ioprio)),
+ prefix, (int) IOPRIO_PRIO_DATA(c->ioprio));
+
+ if (c->cpu_sched_set)
+ fprintf(f,
+ "%sCPUSchedulingPolicy: %s\n"
+ "%sCPUSchedulingPriority: %i\n"
+ "%sCPUSchedulingResetOnFork: %s\n",
+ prefix, sched_policy_to_string(c->cpu_sched_policy),
+ prefix, c->cpu_sched_priority,
+ prefix, yes_no(c->cpu_sched_reset_on_fork));
+
+ if (c->cpu_affinity_set) {
+ fprintf(f, "%sCPUAffinity:", prefix);
+ for (i = 0; i < CPU_SETSIZE; i++)
+ if (CPU_ISSET(i, &c->cpu_affinity))
+ fprintf(f, " %i", i);
+ fputs("\n", f);
+ }
+
+ if (c->timer_slack_ns_set)
+ fprintf(f, "%sTimerSlackNS: %lu\n", prefix, c->timer_slack_ns);
+
+ 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",
+ prefix, c->tty_path);
+
+ if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KERNEL ||
+ c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KERNEL)
+ fprintf(f,
+ "%sSyslogFacility: %s\n"
+ "%sSyslogLevel: %s\n",
+ prefix, log_facility_to_string(LOG_FAC(c->syslog_priority)),
+ prefix, log_level_to_string(LOG_PRI(c->syslog_priority)));
+
+ 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) {
+ fprintf(f, "%sCapabilityBoundingSetDrop:", prefix);
+
+ for (i = 0; i <= CAP_LAST_CAP; i++)
+ if (c->capability_bounding_set_drop & (1 << i)) {
+ char *t;
+
+ if ((t = cap_to_name(i))) {
+ fprintf(f, " %s", t);
+ free(t);
+ }
+ }
+
+ fputs("\n", f);
+ }
+
+ if (c->user)
+ fprintf(f, "%sUser: %s", prefix, c->user);
+ if (c->group)
+ fprintf(f, "%sGroup: %s", prefix, c->group);
+
+ if (strv_length(c->supplementary_groups) > 0) {
+ fprintf(f, "%sSupplementaryGroups:", prefix);
+ strv_fprintf(f, c->supplementary_groups);
+ fputs("\n", f);
+ }
+
+ 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);
+ }
+}
+
+void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status) {
+ assert(s);
+
+ s->pid = pid;
+ s->exit_timestamp = now(CLOCK_REALTIME);
+
+ s->code = code;
+ s->status = status;
+}
+
+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: %llu\n",
+ prefix, (unsigned long long) s->pid);
+
+ if (s->start_timestamp > 0)
+ fprintf(f,
+ "%sStart Timestamp: %s\n",
+ prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp));
+
+ if (s->exit_timestamp > 0)
+ fprintf(f,
+ "%sExit Timestamp: %s\n"
+ "%sExit Code: %s\n"
+ "%sExit Status: %i\n",
+ prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp),
+ 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 kinda 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;
+}
+
+const char* exit_status_to_string(ExitStatus status) {
+
+ /* We cast to int here, so that -Wenum doesn't complain that
+ * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */
+
+ switch ((int) status) {
+
+ case EXIT_SUCCESS:
+ return "SUCCESS";
+
+ case EXIT_FAILURE:
+ return "FAILURE";
+
+ case EXIT_INVALIDARGUMENT:
+ return "INVALIDARGUMENT";
+
+ case EXIT_NOTIMPLEMENTED:
+ return "NOTIMPLEMENTED";
+
+ case EXIT_NOPERMISSION:
+ return "NOPERMISSION";
+
+ case EXIT_NOTINSTALLED:
+ return "NOTINSSTALLED";
+
+ case EXIT_NOTCONFIGURED:
+ return "NOTCONFIGURED";
+
+ case EXIT_NOTRUNNING:
+ return "NOTRUNNING";
+
+ case EXIT_CHDIR:
+ return "CHDIR";
+
+ case EXIT_NICE:
+ return "NICE";
+
+ case EXIT_FDS:
+ return "FDS";
+
+ case EXIT_EXEC:
+ return "EXEC";
+
+ case EXIT_MEMORY:
+ return "MEMORY";
+
+ case EXIT_LIMITS:
+ return "LIMITS";
+
+ case EXIT_OOM_ADJUST:
+ return "OOM_ADJUST";
+
+ case EXIT_SIGNAL_MASK:
+ return "SIGNAL_MASK";
+
+ case EXIT_STDIN:
+ return "STDIN";
+
+ case EXIT_STDOUT:
+ return "STDOUT";
+
+ case EXIT_CHROOT:
+ return "CHROOT";
+
+ case EXIT_IOPRIO:
+ return "IOPRIO";
+
+ case EXIT_TIMERSLACK:
+ return "TIMERSLACK";
+
+ case EXIT_SECUREBITS:
+ return "SECUREBITS";
+
+ case EXIT_SETSCHEDULER:
+ return "SETSCHEDULER";
+
+ case EXIT_CPUAFFINITY:
+ return "CPUAFFINITY";
+
+ case EXIT_GROUP:
+ return "GROUP";
+
+ case EXIT_USER:
+ return "USER";
+
+ case EXIT_CAPABILITIES:
+ return "CAPABILITIES";
+
+ case EXIT_CGROUP:
+ return "CGROUP";
+
+ case EXIT_SETSID:
+ return "SETSID";
+
+ case EXIT_CONFIRM:
+ return "CONFIRM";
+
+ case EXIT_STDERR:
+ return "STDERR";
+
+ default:
+ return NULL;
+ }
+}
+
+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"
+};
+
+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_KERNEL] = "kernel",
+ [EXEC_OUTPUT_SOCKET] = "socket"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
+
+DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
diff --git a/src/execute.h b/src/execute.h
new file mode 100644
index 0000000000..be73542d4b
--- /dev/null
+++ b/src/execute.h
@@ -0,0 +1,221 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooexecutehfoo
+#define fooexecutehfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct ExecStatus ExecStatus;
+typedef struct ExecCommand ExecCommand;
+typedef struct ExecContext ExecContext;
+
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/capability.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sched.h>
+
+struct CGroupBonding;
+
+#include "list.h"
+#include "util.h"
+
+/* Abstract namespace! */
+#define LOGGER_SOCKET "/org/freedesktop/systemd1/logger"
+
+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_KERNEL,
+ EXEC_OUTPUT_SOCKET,
+ _EXEC_OUTPUT_MAX,
+ _EXEC_OUTPUT_INVALID = -1
+} ExecOutput;
+
+struct ExecStatus {
+ usec_t start_timestamp;
+ usec_t 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 */
+};
+
+struct ExecContext {
+ char **environment;
+ struct rlimit *rlimit[RLIMIT_NLIMITS];
+ char *working_directory, *root_directory;
+
+ mode_t umask;
+ int oom_adjust;
+ int nice;
+ int ioprio;
+ int cpu_sched_policy;
+ int cpu_sched_priority;
+
+ cpu_set_t cpu_affinity;
+ unsigned long timer_slack_ns;
+
+ ExecInput std_input;
+ ExecOutput std_output;
+ ExecOutput std_error;
+
+ int syslog_priority;
+ char *syslog_identifier;
+ bool syslog_no_prefix;
+
+ char *tty_path;
+
+ /* 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 **read_write_dirs, **read_only_dirs, **inaccessible_dirs;
+ unsigned long mount_flags;
+
+ uint64_t capability_bounding_set_drop;
+
+ cap_t capabilities;
+ int secure_bits;
+
+ bool cpu_sched_reset_on_fork;
+ bool non_blocking;
+ bool private_tmp;
+
+ bool oom_adjust_set:1;
+ bool nice_set:1;
+ bool ioprio_set:1;
+ bool cpu_sched_set:1;
+ bool cpu_affinity_set:1;
+ bool timer_slack_ns_set:1;
+
+ /* 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 no_setsid:1;
+};
+
+typedef enum ExitStatus {
+ /* EXIT_SUCCESS defined by libc */
+ /* EXIT_FAILURE defined by libc */
+ EXIT_INVALIDARGUMENT = 2,
+ EXIT_NOTIMPLEMENTED = 3,
+ EXIT_NOPERMISSION = 4,
+ EXIT_NOTINSTALLED = 5,
+ EXIT_NOTCONFIGURED = 6,
+ EXIT_NOTRUNNING = 7,
+
+ /* The LSB suggests that error codes >= 200 are "reserved". We
+ * use them here under the assumption that they hence are
+ * unused by init scripts.
+ *
+ * http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
+
+ EXIT_CHDIR = 200,
+ EXIT_NICE,
+ EXIT_FDS,
+ EXIT_EXEC,
+ EXIT_MEMORY,
+ EXIT_LIMITS,
+ EXIT_OOM_ADJUST,
+ EXIT_SIGNAL_MASK,
+ EXIT_STDIN,
+ EXIT_STDOUT,
+ EXIT_CHROOT, /* 210 */
+ EXIT_IOPRIO,
+ EXIT_TIMERSLACK,
+ EXIT_SECUREBITS,
+ EXIT_SETSCHEDULER,
+ EXIT_CPUAFFINITY,
+ EXIT_GROUP,
+ EXIT_USER,
+ EXIT_CAPABILITIES,
+ EXIT_CGROUP,
+ EXIT_SETSID, /* 220 */
+ EXIT_CONFIRM,
+ EXIT_STDERR
+
+} ExitStatus;
+
+int exec_spawn(ExecCommand *command,
+ char **argv,
+ const ExecContext *context,
+ int fds[], unsigned n_fds,
+ char **environment,
+ bool apply_permissions,
+ bool apply_chroot,
+ bool confirm_spawn,
+ struct CGroupBonding *cgroup_bondings,
+ 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_status_fill(ExecStatus *s, 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);
+int exec_output_from_string(const char *s);
+
+const char* exec_input_to_string(ExecInput i);
+int exec_input_from_string(const char *s);
+
+const char* exit_status_to_string(ExitStatus status);
+
+#endif
diff --git a/src/fdset.c b/src/fdset.c
new file mode 100644
index 0000000000..b6d5286f82
--- /dev/null
+++ b/src/fdset.c
@@ -0,0 +1,162 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 */
+
+ log_warning("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/fdset.h b/src/fdset.h
new file mode 100644
index 0000000000..3483fc8323
--- /dev/null
+++ b/src/fdset.h
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foofdsethfoo
+#define foofdsethfoo
+
+/***
+ 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 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/>.
+***/
+
+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);
+
+#endif
diff --git a/src/hashmap.c b/src/hashmap.c
new file mode 100644
index 0000000000..5a993b6e47
--- /dev/null
+++ b/src/hashmap.c
@@ -0,0 +1,543 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "macro.h"
+
+#define NBUCKETS 127
+
+struct hashmap_entry {
+ const void *key;
+ void *value;
+ struct hashmap_entry *bucket_next, *bucket_previous;
+ struct hashmap_entry *iterate_next, *iterate_previous;
+};
+
+struct Hashmap {
+ hash_func_t hash_func;
+ compare_func_t compare_func;
+
+ struct hashmap_entry *iterate_list_head, *iterate_list_tail;
+ unsigned n_entries;
+};
+
+#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap))))
+
+unsigned string_hash_func(const void *p) {
+ unsigned hash = 0;
+ const char *c;
+
+ for (c = p; *c; c++)
+ hash = 31 * hash + (unsigned) *c;
+
+ return hash;
+}
+
+int string_compare_func(const void *a, const void *b) {
+ return strcmp(a, b);
+}
+
+unsigned trivial_hash_func(const void *p) {
+ return PTR_TO_UINT(p);
+}
+
+int trivial_compare_func(const void *a, const void *b) {
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
+ Hashmap *h;
+
+ if (!(h = malloc0(ALIGN(sizeof(Hashmap)) + NBUCKETS * ALIGN(sizeof(struct hashmap_entry*)))))
+ return NULL;
+
+ h->hash_func = hash_func ? hash_func : trivial_hash_func;
+ h->compare_func = compare_func ? compare_func : trivial_compare_func;
+
+ h->n_entries = 0;
+ h->iterate_list_head = h->iterate_list_tail = NULL;
+
+ return h;
+}
+
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
+ assert(h);
+
+ if (*h)
+ return 0;
+
+ if (!(*h = hashmap_new(hash_func, compare_func)))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
+ assert(h);
+ assert(e);
+
+ /* Insert into hash table */
+ e->bucket_next = BY_HASH(h)[hash];
+ e->bucket_previous = NULL;
+ if (BY_HASH(h)[hash])
+ BY_HASH(h)[hash]->bucket_previous = e;
+ BY_HASH(h)[hash] = e;
+
+ /* Insert into iteration list */
+ e->iterate_previous = h->iterate_list_tail;
+ e->iterate_next = NULL;
+ if (h->iterate_list_tail) {
+ assert(h->iterate_list_head);
+ h->iterate_list_tail->iterate_next = e;
+ } else {
+ assert(!h->iterate_list_head);
+ h->iterate_list_head = e;
+ }
+ h->iterate_list_tail = e;
+
+ h->n_entries++;
+ assert(h->n_entries >= 1);
+}
+
+static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
+ assert(h);
+ assert(e);
+
+ /* Remove from iteration list */
+ if (e->iterate_next)
+ e->iterate_next->iterate_previous = e->iterate_previous;
+ else
+ h->iterate_list_tail = e->iterate_previous;
+
+ if (e->iterate_previous)
+ e->iterate_previous->iterate_next = e->iterate_next;
+ else
+ h->iterate_list_head = e->iterate_next;
+
+ /* Remove from hash table bucket list */
+ if (e->bucket_next)
+ e->bucket_next->bucket_previous = e->bucket_previous;
+
+ if (e->bucket_previous)
+ e->bucket_previous->bucket_next = e->bucket_next;
+ else
+ BY_HASH(h)[hash] = e->bucket_next;
+
+ assert(h->n_entries >= 1);
+ h->n_entries--;
+}
+
+static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
+ unsigned hash;
+
+ assert(h);
+ assert(e);
+
+ hash = h->hash_func(e->key) % NBUCKETS;
+
+ unlink_entry(h, e, hash);
+ free(e);
+}
+
+void hashmap_free(Hashmap*h) {
+
+ if (!h)
+ return;
+
+ hashmap_clear(h);
+
+ free(h);
+}
+
+void hashmap_clear(Hashmap *h) {
+ if (!h)
+ return;
+
+ while (h->iterate_list_head)
+ remove_entry(h, h->iterate_list_head);
+}
+
+static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) {
+ struct hashmap_entry *e;
+ assert(h);
+ assert(hash < NBUCKETS);
+
+ for (e = BY_HASH(h)[hash]; e; e = e->bucket_next)
+ if (h->compare_func(e->key, key) == 0)
+ return e;
+
+ return NULL;
+}
+
+int hashmap_put(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if ((e = hash_scan(h, hash, key))) {
+
+ if (e->value == value)
+ return 0;
+
+ return -EEXIST;
+ }
+
+ if (!(e = new(struct hashmap_entry, 1)))
+ return -ENOMEM;
+
+ e->key = key;
+ e->value = value;
+
+ link_entry(h, e, hash);
+
+ return 1;
+}
+
+int hashmap_replace(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if ((e = hash_scan(h, hash, key))) {
+ e->key = key;
+ e->value = value;
+ return 0;
+ }
+
+ return hashmap_put(h, key, value);
+}
+
+void* hashmap_get(Hashmap *h, const void *key) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ return e->value;
+}
+
+void* hashmap_remove(Hashmap *h, const void *key) {
+ struct hashmap_entry *e;
+ unsigned hash;
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ data = e->value;
+ remove_entry(h, e);
+
+ return data;
+}
+
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+ struct hashmap_entry *e;
+ unsigned old_hash, new_hash;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = h->hash_func(old_key) % NBUCKETS;
+ if (!(e = hash_scan(h, old_hash, old_key)))
+ return -ENOENT;
+
+ new_hash = h->hash_func(new_key) % NBUCKETS;
+ if (hash_scan(h, new_hash, new_key))
+ return -EEXIST;
+
+ unlink_entry(h, e, old_hash);
+
+ e->key = new_key;
+ e->value = value;
+
+ link_entry(h, e, new_hash);
+
+ return 0;
+}
+
+void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ if (e->value != value)
+ return NULL;
+
+ remove_entry(h, e);
+
+ return value;
+}
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
+ struct hashmap_entry *e;
+
+ assert(i);
+
+ if (!h)
+ goto at_end;
+
+ if (*i == ITERATOR_LAST)
+ goto at_end;
+
+ if (*i == ITERATOR_FIRST && !h->iterate_list_head)
+ goto at_end;
+
+ e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
+
+ if (e->iterate_next)
+ *i = (Iterator) e->iterate_next;
+ else
+ *i = ITERATOR_LAST;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_end:
+ *i = ITERATOR_LAST;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
+ struct hashmap_entry *e;
+
+ assert(i);
+
+ if (!h)
+ goto at_beginning;
+
+ if (*i == ITERATOR_FIRST)
+ goto at_beginning;
+
+ if (*i == ITERATOR_LAST && !h->iterate_list_tail)
+ goto at_beginning;
+
+ e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
+
+ if (e->iterate_previous)
+ *i = (Iterator) e->iterate_previous;
+ else
+ *i = ITERATOR_FIRST;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_beginning:
+ *i = ITERATOR_FIRST;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ *i = (Iterator) e;
+
+ return e->value;
+}
+
+void* hashmap_first(Hashmap *h) {
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ return h->iterate_list_head->value;
+}
+
+void* hashmap_last(Hashmap *h) {
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_tail)
+ return NULL;
+
+ return h->iterate_list_tail->value;
+}
+
+void* hashmap_steal_first(Hashmap *h) {
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ data = h->iterate_list_head->value;
+ remove_entry(h, h->iterate_list_head);
+
+ return data;
+}
+
+unsigned hashmap_size(Hashmap *h) {
+
+ if (!h)
+ return 0;
+
+ return h->n_entries;
+}
+
+bool hashmap_isempty(Hashmap *h) {
+
+ if (!h)
+ return true;
+
+ return h->n_entries == 0;
+}
+
+int hashmap_merge(Hashmap *h, Hashmap *other) {
+ struct hashmap_entry *e;
+
+ assert(h);
+
+ if (!other)
+ return 0;
+
+ for (e = other->iterate_list_head; e; e = e->iterate_next) {
+ int r;
+
+ if ((r = hashmap_put(h, e->key, e->value)) < 0)
+ if (r != -EEXIST)
+ return r;
+ }
+
+ return 0;
+}
+
+void hashmap_move(Hashmap *h, Hashmap *other) {
+ struct hashmap_entry *e, *n;
+
+ assert(h);
+
+ /* The same as hashmap_merge(), but every new item from other
+ * is moved to h. This function is guaranteed to succeed. */
+
+ if (!other)
+ return;
+
+ for (e = other->iterate_list_head; e; e = n) {
+ unsigned h_hash, other_hash;
+
+ n = e->iterate_next;
+
+ h_hash = h->hash_func(e->key) % NBUCKETS;
+
+ if (hash_scan(h, h_hash, e->key))
+ continue;
+
+ other_hash = other->hash_func(e->key) % NBUCKETS;
+
+ unlink_entry(other, e, other_hash);
+ link_entry(h, e, h_hash);
+ }
+}
+
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
+ unsigned h_hash, other_hash;
+ struct hashmap_entry *e;
+
+ if (!other)
+ return 0;
+
+ assert(h);
+
+ h_hash = h->hash_func(key) % NBUCKETS;
+ if (hash_scan(h, h_hash, key))
+ return -EEXIST;
+
+ other_hash = other->hash_func(key) % NBUCKETS;
+ if (!(e = hash_scan(other, other_hash, key)))
+ return -ENOENT;
+
+ unlink_entry(other, e, other_hash);
+ link_entry(h, e, h_hash);
+
+ return 0;
+}
+
+Hashmap *hashmap_copy(Hashmap *h) {
+ Hashmap *copy;
+
+ assert(h);
+
+ if (!(copy = hashmap_new(h->hash_func, h->compare_func)))
+ return NULL;
+
+ if (hashmap_merge(copy, h) < 0) {
+ hashmap_free(copy);
+ return NULL;
+ }
+
+ return copy;
+}
diff --git a/src/hashmap.h b/src/hashmap.h
new file mode 100644
index 0000000000..3ff3efe8d1
--- /dev/null
+++ b/src/hashmap.h
@@ -0,0 +1,85 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foohashmaphfoo
+#define foohashmaphfoo
+
+/***
+ 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 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 <stdbool.h>
+
+/* Pretty straightforward hash table implementation. As a minor
+ * optimization a NULL hashmap object will be treated as empty hashmap
+ * for all read operations. That way it is not necessary to
+ * instantiate an object for each Hashmap use. */
+
+typedef struct Hashmap Hashmap;
+typedef struct _IteratorStruct _IteratorStruct;
+typedef _IteratorStruct* Iterator;
+
+#define ITERATOR_FIRST ((Iterator) 0)
+#define ITERATOR_LAST ((Iterator) -1)
+
+typedef unsigned (*hash_func_t)(const void *p);
+typedef int (*compare_func_t)(const void *a, const void *b);
+
+unsigned string_hash_func(const void *p);
+int string_compare_func(const void *a, const void *b);
+
+unsigned trivial_hash_func(const void *p);
+int trivial_compare_func(const void *a, const void *b);
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
+void hashmap_free(Hashmap *h);
+Hashmap *hashmap_copy(Hashmap *h);
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
+
+int hashmap_put(Hashmap *h, const void *key, void *value);
+int hashmap_replace(Hashmap *h, const void *key, void *value);
+void* hashmap_get(Hashmap *h, const void *key);
+void* hashmap_remove(Hashmap *h, const void *key);
+void* hashmap_remove_value(Hashmap *h, const void *key, void *value);
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+
+int hashmap_merge(Hashmap *h, Hashmap *other);
+void hashmap_move(Hashmap *h, Hashmap *other);
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key);
+
+unsigned hashmap_size(Hashmap *h);
+bool hashmap_isempty(Hashmap *h);
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
+
+void hashmap_clear(Hashmap *h);
+void *hashmap_steal_first(Hashmap *h);
+void* hashmap_first(Hashmap *h);
+void* hashmap_last(Hashmap *h);
+
+#define HASHMAP_FOREACH(e, h, i) \
+ for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
+
+#define HASHMAP_FOREACH_KEY(e, k, h, i) \
+ for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
+
+#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
+ for ((i) = ITERATOR_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
+
+#endif
diff --git a/src/hostname-setup.c b/src/hostname-setup.c
new file mode 100644
index 0000000000..3b988d4c8b
--- /dev/null
+++ b/src/hostname-setup.c
@@ -0,0 +1,168 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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"
+
+#define LINE_MAX 4096
+
+#if defined(TARGET_FEDORA)
+#define FILENAME "/etc/sysconfig/network"
+#elif defined(TARGET_SUSE) || defined(TARGET_SLACKWARE)
+#define FILENAME "/etc/HOSTNAME"
+#elif defined(TARGET_DEBIAN)
+#define FILENAME "/etc/hostname"
+#elif defined(TARGET_ARCH)
+#define FILENAME "/etc/rc.conf"
+#elif defined(TARGET_GENTOO)
+#define FILENAME "/etc/conf.d/hostname"
+#endif
+
+static char* strip_bad_chars(char *s) {
+ char *p, *d;
+
+ for (p = s, d = s; *p; p++)
+ if ((*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z') ||
+ (*p >= '0' && *p <= '9') ||
+ *p == '-' ||
+ *p == '_')
+ *(d++) = *p;
+
+ *d = 0;
+
+ return s;
+}
+
+static int read_hostname(char **hn) {
+
+#if defined(TARGET_FEDORA) || defined(TARGET_ARCH) || defined(TARGET_GENTOO)
+ int r;
+ FILE *f;
+
+ assert(hn);
+
+ if (!(f = fopen(FILENAME, "re")))
+ 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;
+
+ if (!(k = strdup(s+9))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ strip_bad_chars(k);
+
+ if (k[0] == 0) {
+ free(k);
+ r = -ENOENT;
+ goto finish;
+ }
+
+ *hn = k;
+ break;
+ }
+
+ r = 0;
+
+finish:
+ fclose(f);
+ return r;
+
+#elif defined(TARGET_SUSE) || defined(TARGET_DEBIAN) || defined(TARGET_SLACKWARE)
+ int r;
+ char *s, *k;
+
+ assert(hn);
+
+ if ((r = read_one_line_file(FILENAME, &s)) < 0)
+ return r;
+
+ k = strdup(strstrip(s));
+ free(s);
+
+ if (!k)
+ return -ENOMEM;
+
+ strip_bad_chars(k);
+
+ if (k[0] == 0) {
+ free(k);
+ return -ENOENT;
+ }
+
+ *hn = k;
+
+#else
+#warning "Don't know how to read the hostname"
+
+ return -ENOENT;
+#endif
+
+ return 0;
+}
+
+int hostname_setup(void) {
+ int r;
+ char *hn;
+
+ if ((r = read_hostname(&hn)) < 0) {
+ if (r != -ENOENT)
+ log_warning("Failed to read configured hostname: %s", strerror(-r));
+
+ return r;
+ }
+
+ r = sethostname(hn, strlen(hn)) < 0 ? -errno : 0;
+
+ if (r < 0)
+ log_warning("Failed to set hostname to <%s>: %s", hn, strerror(-r));
+ else
+ log_info("Set hostname to <%s>.", hn);
+
+ free(hn);
+
+ return r;
+}
diff --git a/src/hostname-setup.h b/src/hostname-setup.h
new file mode 100644
index 0000000000..18a406a1f5
--- /dev/null
+++ b/src/hostname-setup.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foohostnamesetuphfoo
+#define foohostnamesetuphfoo
+
+/***
+ 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 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/>.
+***/
+
+int hostname_setup(void);
+
+#endif
diff --git a/src/initctl.c b/src/initctl.c
new file mode 100644
index 0000000000..9d8eceea52
--- /dev/null
+++ b/src/initctl.c
@@ -0,0 +1,397 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <sys/socket.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/epoll.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include <dbus/dbus.h>
+
+#include "util.h"
+#include "log.h"
+#include "list.h"
+#include "initreq.h"
+#include "manager.h"
+#include "sd-daemon.h"
+
+#define SERVER_FD_MAX 16
+#define TIMEOUT ((int) (10*MSEC_PER_SEC))
+
+typedef struct Fifo Fifo;
+
+typedef struct Server {
+ int epoll_fd;
+
+ LIST_HEAD(Fifo, fifos);
+ unsigned n_fifos;
+
+ DBusConnection *bus;
+} Server;
+
+struct Fifo {
+ Server *server;
+
+ int fd;
+
+ struct init_request buffer;
+ size_t bytes_read;
+
+ LIST_FIELDS(Fifo, fifo);
+};
+
+static const char *translate_runlevel(int runlevel) {
+ static const struct {
+ const int runlevel;
+ const char *special;
+ } table[] = {
+ { '0', SPECIAL_RUNLEVEL0_TARGET },
+ { '1', SPECIAL_RUNLEVEL1_TARGET },
+ { 's', SPECIAL_RUNLEVEL1_TARGET },
+ { 'S', SPECIAL_RUNLEVEL1_TARGET },
+ { '2', SPECIAL_RUNLEVEL2_TARGET },
+ { '3', SPECIAL_RUNLEVEL3_TARGET },
+ { '4', SPECIAL_RUNLEVEL4_TARGET },
+ { '5', SPECIAL_RUNLEVEL5_TARGET },
+ { '6', SPECIAL_RUNLEVEL6_TARGET },
+ };
+
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (table[i].runlevel == runlevel)
+ return table[i].special;
+
+ return NULL;
+}
+
+static void change_runlevel(Server *s, int runlevel) {
+ const char *target;
+ DBusMessage *m = NULL, *reply = NULL;
+ DBusError error;
+ const char *path, *replace = "isolate";
+
+ assert(s);
+
+ dbus_error_init(&error);
+
+ if (!(target = translate_runlevel(runlevel))) {
+ log_warning("Got request for unknown runlevel %c, ignoring.", runlevel);
+ goto finish;
+ }
+
+ log_debug("Running request %s", target);
+
+ if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "LoadUnit"))) {
+ log_error("Could not allocate message.");
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &target,
+ DBUS_TYPE_INVALID)) {
+ log_error("Could not attach group information to signal message.");
+ goto finish;
+ }
+
+ if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) {
+ log_error("Failed to get unit path: %s", error.message);
+ goto finish;
+ }
+
+ if (!dbus_message_get_args(reply, &error,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID)) {
+ log_error("Failed to parse unit path: %s", error.message);
+ goto finish;
+ }
+
+ dbus_message_unref(m);
+ if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", path, "org.freedesktop.systemd1.Unit", "Start"))) {
+ log_error("Could not allocate message.");
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &replace,
+ DBUS_TYPE_INVALID)) {
+ log_error("Could not attach group information to signal message.");
+ goto finish;
+ }
+
+ dbus_message_unref(reply);
+ if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) {
+ log_error("Failed to start unit: %s", error.message);
+ goto finish;
+ }
+
+finish:
+ if (m)
+ dbus_message_unref(m);
+
+ if (reply)
+ dbus_message_unref(reply);
+
+ dbus_error_free(&error);
+}
+
+static void request_process(Server *s, const struct init_request *req) {
+ assert(s);
+ assert(req);
+
+ if (req->magic != INIT_MAGIC) {
+ log_error("Got initctl request with invalid magic. Ignoring.");
+ return;
+ }
+
+ switch (req->cmd) {
+
+ case INIT_CMD_RUNLVL:
+ if (!isprint(req->runlevel))
+ log_error("Got invalid runlevel. Ignoring.");
+ else
+ change_runlevel(s, req->runlevel);
+ return;
+
+ case INIT_CMD_POWERFAIL:
+ case INIT_CMD_POWERFAILNOW:
+ case INIT_CMD_POWEROK:
+ log_warning("Received UPS/power initctl request. This is not implemented in systemd. Upgrade your UPS daemon!");
+ return;
+
+ case INIT_CMD_CHANGECONS:
+ log_warning("Received console change initctl request. This is not implemented in systemd.");
+ return;
+
+ case INIT_CMD_SETENV:
+ case INIT_CMD_UNSETENV:
+ log_warning("Received environment initctl request. This is not implemented in systemd.");
+ return;
+
+ default:
+ log_warning("Received unknown initctl request. Ignoring.");
+ return;
+ }
+}
+
+static int fifo_process(Fifo *f) {
+ ssize_t l;
+
+ assert(f);
+
+ errno = EIO;
+ if ((l = read(f->fd, ((uint8_t*) &f->buffer) + f->bytes_read, sizeof(f->buffer) - f->bytes_read)) <= 0) {
+
+ if (errno == EAGAIN)
+ return 0;
+
+ log_warning("Failed to read from fifo: %s", strerror(errno));
+ return -1;
+ }
+
+ f->bytes_read += l;
+ assert(f->bytes_read <= sizeof(f->buffer));
+
+ if (f->bytes_read == sizeof(f->buffer)) {
+ request_process(f->server, &f->buffer);
+ f->bytes_read = 0;
+ }
+
+ return 0;
+}
+
+static void fifo_free(Fifo *f) {
+ assert(f);
+
+ if (f->server) {
+ assert(f->server->n_fifos > 0);
+ f->server->n_fifos--;
+ LIST_REMOVE(Fifo, fifo, f->server->fifos, f);
+ }
+
+ if (f->fd >= 0) {
+ if (f->server)
+ epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL);
+
+ close_nointr_nofail(f->fd);
+ }
+
+ free(f);
+}
+
+static void server_done(Server *s) {
+ assert(s);
+
+ while (s->fifos)
+ fifo_free(s->fifos);
+
+ if (s->epoll_fd >= 0)
+ close_nointr_nofail(s->epoll_fd);
+
+ if (s->bus)
+ dbus_connection_unref(s->bus);
+}
+
+static int server_init(Server *s, unsigned n_sockets) {
+ int r;
+ unsigned i;
+ DBusError error;
+
+ assert(s);
+ assert(n_sockets > 0);
+
+ dbus_error_init(&error);
+
+ zero(*s);
+
+ if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+ r = -errno;
+ log_error("Failed to create epoll object: %s", strerror(errno));
+ goto fail;
+ }
+
+ for (i = 0; i < n_sockets; i++) {
+ struct epoll_event ev;
+ Fifo *f;
+
+ if (!(f = new0(Fifo, 1))) {
+ r = -ENOMEM;
+ log_error("Failed to create fifo object: %s", strerror(errno));
+ goto fail;
+ }
+
+ f->fd = -1;
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.ptr = f;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, SD_LISTEN_FDS_START+i, &ev) < 0) {
+ r = -errno;
+ fifo_free(f);
+ log_error("Failed to add fifo fd to epoll object: %s", strerror(errno));
+ goto fail;
+ }
+
+ f->fd = SD_LISTEN_FDS_START+i;
+ LIST_PREPEND(Fifo, fifo, s->fifos, f);
+ f->server = s;
+ s->n_fifos ++;
+ }
+
+ if (!(s->bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) {
+ log_error("Failed to get D-Bus connection: %s", error.message);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ server_done(s);
+
+ dbus_error_free(&error);
+ return r;
+}
+
+static int process_event(Server *s, struct epoll_event *ev) {
+ int r;
+ Fifo *f;
+
+ assert(s);
+
+ if (!(ev->events & EPOLLIN)) {
+ log_info("Got invalid event from epoll. (3)");
+ return -EIO;
+ }
+
+ f = (Fifo*) ev->data.ptr;
+
+ if ((r = fifo_process(f)) < 0) {
+ log_info("Got error on fifo: %s", strerror(-r));
+ fifo_free(f);
+ return r;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ Server server;
+ int r = 3, n;
+
+ log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
+ log_parse_environment();
+
+ log_info("systemd-initctl running as pid %llu", (unsigned long long) getpid());
+
+ if ((n = sd_listen_fds(true)) < 0) {
+ log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
+ return 1;
+ }
+
+ if (n <= 0 || n > SERVER_FD_MAX) {
+ log_error("No or too many file descriptors passed.");
+ return 2;
+ }
+
+ if (server_init(&server, (unsigned) n) < 0)
+ return 2;
+
+ for (;;) {
+ struct epoll_event event;
+ int k;
+
+ if ((k = epoll_wait(server.epoll_fd,
+ &event, 1,
+ TIMEOUT)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("epoll_wait() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (k <= 0)
+ break;
+
+ if ((k = process_event(&server, &event)) < 0)
+ goto fail;
+ }
+ r = 0;
+
+fail:
+ server_done(&server);
+
+ log_info("systemd-initctl stopped as pid %llu", (unsigned long long) getpid());
+
+ dbus_shutdown();
+
+ return r;
+}
diff --git a/src/initreq.h b/src/initreq.h
new file mode 100644
index 0000000000..6f6547bcb9
--- /dev/null
+++ b/src/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 seperate 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/ioprio.h b/src/ioprio.h
new file mode 100644
index 0000000000..9800fc2553
--- /dev/null
+++ b/src/ioprio.h
@@ -0,0 +1,57 @@
+#ifndef IOPRIO_H
+#define IOPRIO_H
+
+/* This is minimal version of Linux' linux/ioprio.h header file, which
+ * is licensed GPL2 */
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+/*
+ * Gives us 8 prio classes with 13-bits of data for each class
+ */
+#define IOPRIO_BITS (16)
+#define IOPRIO_CLASS_SHIFT (13)
+#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
+
+#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
+#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
+#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
+
+#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE)
+
+/*
+ * These are the io priority groups as implemented by CFQ. RT is the realtime
+ * class, it always gets premium service. BE is the best-effort scheduling
+ * class, the default for any process. IDLE is the idle scheduling class, it
+ * is only served when no one else is using the disk.
+ */
+enum {
+ IOPRIO_CLASS_NONE,
+ IOPRIO_CLASS_RT,
+ IOPRIO_CLASS_BE,
+ IOPRIO_CLASS_IDLE,
+};
+
+/*
+ * 8 best effort priority levels are supported
+ */
+#define IOPRIO_BE_NR (8)
+
+enum {
+ IOPRIO_WHO_PROCESS = 1,
+ IOPRIO_WHO_PGRP,
+ IOPRIO_WHO_USER,
+};
+
+static inline int ioprio_set(int which, int who, int ioprio)
+{
+ return syscall(__NR_ioprio_set, which, who, ioprio);
+}
+
+static inline int ioprio_get(int which, int who)
+{
+ return syscall(__NR_ioprio_get, which, who);
+}
+
+#endif
diff --git a/src/job.c b/src/job.c
new file mode 100644
index 0000000000..887de92cad
--- /dev/null
+++ b/src/job.c
@@ -0,0 +1,589 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <assert.h>
+#include <errno.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"
+
+Job* job_new(Manager *m, JobType type, Unit *unit) {
+ Job *j;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(unit);
+
+ if (!(j = new0(Job, 1)))
+ return NULL;
+
+ j->manager = m;
+ j->id = m->current_job_id++;
+ j->type = type;
+ j->unit = unit;
+
+ /* We don't link it here, that's what job_dependency() is for */
+
+ return j;
+}
+
+void job_free(Job *j) {
+ assert(j);
+
+ /* Detach from next 'bigger' objects */
+ if (j->installed) {
+ bus_job_send_removed_signal(j);
+
+ if (j->unit->meta.job == j) {
+ j->unit->meta.job = NULL;
+ unit_add_to_gc_queue(j->unit);
+ }
+
+ hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
+ j->installed = false;
+ }
+
+ /* Detach from next 'smaller' objects */
+ manager_transaction_unlink_job(j->manager, j, true);
+
+ 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);
+
+ free(j);
+}
+
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters) {
+ 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
+ * explcitily asked for) is the requester. */
+
+ if (!(l = new0(JobDependency, 1)))
+ return NULL;
+
+ l->subject = subject;
+ l->object = object;
+ l->matters = matters;
+
+ if (subject)
+ LIST_PREPEND(JobDependency, subject, subject->subject_list, l);
+ else
+ LIST_PREPEND(JobDependency, subject, object->manager->transaction_anchor, 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);
+ else
+ LIST_REMOVE(JobDependency, subject, l->object->manager->transaction_anchor, l);
+
+ LIST_REMOVE(JobDependency, object, l->object->object_list, l);
+
+ free(l);
+}
+
+void job_dependency_delete(Job *subject, Job *object, bool *matters) {
+ JobDependency *l;
+
+ assert(object);
+
+ LIST_FOREACH(object, l, object->object_list) {
+ assert(l->object == object);
+
+ if (l->subject == subject)
+ break;
+ }
+
+ if (!l) {
+ if (matters)
+ *matters = false;
+ return;
+ }
+
+ if (matters)
+ *matters = l->matters;
+
+ job_dependency_free(l);
+}
+
+void job_dump(Job *j, FILE*f, const char *prefix) {
+
+
+ assert(j);
+ assert(f);
+
+ 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->meta.id, job_type_to_string(j->type),
+ prefix, job_state_to_string(j->state),
+ prefix, yes_no(j->override));
+}
+
+bool job_is_anchor(Job *j) {
+ JobDependency *l;
+
+ assert(j);
+
+ LIST_FOREACH(object, l, j->object_list)
+ if (!l->subject)
+ return true;
+
+ return false;
+}
+
+static bool types_match(JobType a, JobType b, JobType c, JobType d) {
+ return
+ (a == c && b == d) ||
+ (a == d && b == c);
+}
+
+int job_type_merge(JobType *a, JobType b) {
+ if (*a == b)
+ return 0;
+
+ /* 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 */
+
+ if (types_match(*a, b, JOB_START, JOB_VERIFY_ACTIVE))
+ *a = JOB_START;
+ else if (types_match(*a, b, JOB_START, JOB_RELOAD) ||
+ types_match(*a, b, JOB_START, JOB_RELOAD_OR_START) ||
+ types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD_OR_START) ||
+ types_match(*a, b, JOB_RELOAD, JOB_RELOAD_OR_START))
+ *a = JOB_RELOAD_OR_START;
+ else if (types_match(*a, b, JOB_START, JOB_RESTART) ||
+ types_match(*a, b, JOB_START, JOB_TRY_RESTART) ||
+ types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RESTART) ||
+ types_match(*a, b, JOB_RELOAD, JOB_RESTART) ||
+ types_match(*a, b, JOB_RELOAD_OR_START, JOB_RESTART) ||
+ types_match(*a, b, JOB_RELOAD_OR_START, JOB_TRY_RESTART) ||
+ types_match(*a, b, JOB_RESTART, JOB_TRY_RESTART))
+ *a = JOB_RESTART;
+ else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD))
+ *a = JOB_RELOAD;
+ else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_TRY_RESTART) ||
+ types_match(*a, b, JOB_RELOAD, JOB_TRY_RESTART))
+ *a = JOB_TRY_RESTART;
+ else
+ return -EEXIST;
+
+ return 0;
+}
+
+bool job_type_is_mergeable(JobType a, JobType b) {
+ return job_type_merge(&a, b) >= 0;
+}
+
+bool job_type_is_superset(JobType a, JobType b) {
+
+ /* Checks whether operation a is a "superset" of b in its
+ * actions */
+
+ if (a == b)
+ return true;
+
+ switch (a) {
+ case JOB_START:
+ return b == JOB_VERIFY_ACTIVE;
+
+ case JOB_RELOAD:
+ return
+ b == JOB_VERIFY_ACTIVE;
+
+ case JOB_RELOAD_OR_START:
+ return
+ b == JOB_RELOAD ||
+ b == JOB_START ||
+ b == JOB_VERIFY_ACTIVE;
+
+ case JOB_RESTART:
+ return
+ b == JOB_START ||
+ b == JOB_VERIFY_ACTIVE ||
+ b == JOB_RELOAD ||
+ b == JOB_RELOAD_OR_START ||
+ b == JOB_TRY_RESTART;
+
+ case JOB_TRY_RESTART:
+ return
+ b == JOB_VERIFY_ACTIVE ||
+ b == JOB_RELOAD;
+ default:
+ return false;
+
+ }
+}
+
+bool job_type_is_conflicting(JobType a, JobType b) {
+ assert(a >= 0 && a < _JOB_TYPE_MAX);
+ assert(b >= 0 && b < _JOB_TYPE_MAX);
+
+ return (a == JOB_STOP) != (b == JOB_STOP);
+}
+
+bool job_type_is_redundant(JobType a, UnitActiveState b) {
+ switch (a) {
+
+ case JOB_START:
+ return
+ b == UNIT_ACTIVE ||
+ b == UNIT_ACTIVE_RELOADING;
+
+ case JOB_STOP:
+ return
+ b == UNIT_INACTIVE;
+
+ case JOB_VERIFY_ACTIVE:
+ return
+ b == UNIT_ACTIVE ||
+ b == UNIT_ACTIVE_RELOADING;
+
+ case JOB_RELOAD:
+ return
+ b == UNIT_ACTIVE_RELOADING;
+
+ case JOB_RELOAD_OR_START:
+ return
+ b == UNIT_ACTIVATING ||
+ b == UNIT_ACTIVE_RELOADING;
+
+ case JOB_RESTART:
+ return
+ b == UNIT_ACTIVATING;
+
+ case JOB_TRY_RESTART:
+ return
+ b == UNIT_ACTIVATING;
+
+ default:
+ assert_not_reached("Invalid job type");
+ }
+}
+
+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
+ * . */
+
+ if (j->type == JOB_START ||
+ j->type == JOB_VERIFY_ACTIVE ||
+ j->type == JOB_RELOAD ||
+ j->type == JOB_RELOAD_OR_START) {
+
+ /* 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->meta.dependencies[UNIT_AFTER], i)
+ if (other->meta.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->meta.dependencies[UNIT_BEFORE], i)
+ if (other->meta.job &&
+ (other->meta.job->type == JOB_STOP ||
+ other->meta.job->type == JOB_RESTART ||
+ other->meta.job->type == JOB_TRY_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;
+}
+
+int job_run_and_invalidate(Job *j) {
+ int r;
+
+ assert(j);
+ assert(j->installed);
+
+ if (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);
+
+ switch (j->type) {
+
+ case JOB_START:
+ r = unit_start(j->unit);
+ 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:
+ r = unit_stop(j->unit);
+ break;
+
+ case JOB_RELOAD:
+ r = unit_reload(j->unit);
+ break;
+
+ case JOB_RELOAD_OR_START:
+ if (unit_active_state(j->unit) == UNIT_ACTIVE)
+ r = unit_reload(j->unit);
+ else
+ r = unit_start(j->unit);
+ break;
+
+ case JOB_RESTART: {
+ UnitActiveState t = unit_active_state(j->unit);
+ if (t == UNIT_INACTIVE || t == UNIT_ACTIVATING) {
+ j->type = JOB_START;
+ r = unit_start(j->unit);
+ } else
+ r = unit_stop(j->unit);
+ break;
+ }
+
+ case JOB_TRY_RESTART: {
+ UnitActiveState t = unit_active_state(j->unit);
+ if (t == UNIT_INACTIVE || t == UNIT_DEACTIVATING)
+ r = -ENOEXEC;
+ else if (t == UNIT_ACTIVATING) {
+ j->type = JOB_START;
+ r = unit_start(j->unit);
+ } else
+ r = unit_stop(j->unit);
+ break;
+ }
+
+ default:
+ assert_not_reached("Unknown job type");
+ }
+
+ if (r == -EALREADY)
+ r = job_finish_and_invalidate(j, true);
+ else if (r == -EAGAIN) {
+ j->state = JOB_WAITING;
+ return -EAGAIN;
+ } else if (r < 0)
+ r = job_finish_and_invalidate(j, false);
+
+ return r;
+}
+
+int job_finish_and_invalidate(Job *j, bool success) {
+ Unit *u;
+ Unit *other;
+ JobType t;
+ Iterator i;
+
+ assert(j);
+ assert(j->installed);
+
+ log_debug("Job %s/%s finished, success=%s", j->unit->meta.id, job_type_to_string(j->type), yes_no(success));
+ job_add_to_dbus_queue(j);
+
+ /* Patch restart jobs so that they become normal start jobs */
+ if (success && (j->type == JOB_RESTART || j->type == JOB_TRY_RESTART)) {
+
+ log_debug("Converting job %s/%s -> %s/%s",
+ j->unit->meta.id, job_type_to_string(j->type),
+ j->unit->meta.id, job_type_to_string(JOB_START));
+
+ j->state = JOB_RUNNING;
+ j->type = JOB_START;
+
+ job_add_to_run_queue(j);
+ return 0;
+ }
+
+ u = j->unit;
+ t = j->type;
+ job_free(j);
+
+ /* Fail depending jobs on failure */
+ if (!success) {
+
+ if (t == JOB_START ||
+ t == JOB_VERIFY_ACTIVE ||
+ t == JOB_RELOAD_OR_START) {
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY], i)
+ if (other->meta.job &&
+ (other->meta.job->type == JOB_START ||
+ other->meta.job->type == JOB_VERIFY_ACTIVE ||
+ other->meta.job->type == JOB_RELOAD_OR_START))
+ job_finish_and_invalidate(other->meta.job, false);
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i)
+ if (other->meta.job &&
+ !other->meta.job->override &&
+ (other->meta.job->type == JOB_START ||
+ other->meta.job->type == JOB_VERIFY_ACTIVE ||
+ other->meta.job->type == JOB_RELOAD_OR_START))
+ job_finish_and_invalidate(other->meta.job, false);
+
+ } else if (t == JOB_STOP) {
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_CONFLICTS], i)
+ if (other->meta.job &&
+ (other->meta.job->type == JOB_START ||
+ other->meta.job->type == JOB_VERIFY_ACTIVE ||
+ other->meta.job->type == JOB_RELOAD_OR_START))
+ job_finish_and_invalidate(other->meta.job, false);
+ }
+ }
+
+ /* Try to start the next jobs that can be started */
+ SET_FOREACH(other, u->meta.dependencies[UNIT_AFTER], i)
+ if (other->meta.job)
+ job_add_to_run_queue(other->meta.job);
+ SET_FOREACH(other, u->meta.dependencies[UNIT_BEFORE], i)
+ if (other->meta.job)
+ job_add_to_run_queue(other->meta.job);
+
+ return 0;
+}
+
+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;
+
+ if (set_isempty(j->manager->subscribed)) {
+ j->sent_dbus_new_signal = true;
+ return;
+ }
+
+ 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;
+}
+
+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",
+};
+
+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"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode);
diff --git a/src/job.h b/src/job.h
new file mode 100644
index 0000000000..1ae97b75d0
--- /dev/null
+++ b/src/job.h
@@ -0,0 +1,150 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foojobhfoo
+#define foojobhfoo
+
+/***
+ 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 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 <stdbool.h>
+#include <inttypes.h>
+
+typedef struct Job Job;
+typedef struct JobDependency JobDependency;
+typedef enum JobType JobType;
+typedef enum JobState JobState;
+typedef enum JobMode JobMode;
+
+#include "manager.h"
+#include "unit.h"
+#include "hashmap.h"
+#include "list.h"
+
+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 */
+ JOB_RELOAD_OR_START, /* if running reload, if not running start */
+
+ /* 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_TRY_RESTART, /* if running stop and then 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,
+ JOB_REPLACE,
+ JOB_ISOLATE,
+ _JOB_MODE_MAX,
+ _JOB_MODE_INVALID = -1
+};
+
+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;
+};
+
+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;
+
+ 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;
+};
+
+Job* job_new(Manager *m, JobType type, Unit *unit);
+void job_free(Job *job);
+void job_dump(Job *j, FILE*f, const char *prefix);
+
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters);
+void job_dependency_free(JobDependency *l);
+void job_dependency_delete(Job *subject, Job *object, bool *matters);
+
+bool job_is_anchor(Job *j);
+
+int job_merge(Job *j, Job *other);
+
+int job_type_merge(JobType *a, JobType b);
+bool job_type_is_mergeable(JobType a, JobType b);
+bool job_type_is_superset(JobType a, JobType b);
+bool job_type_is_conflicting(JobType a, JobType b);
+bool job_type_is_redundant(JobType a, UnitActiveState b);
+
+bool job_is_runnable(Job *j);
+
+void job_add_to_run_queue(Job *j);
+void job_add_to_dbus_queue(Job *j);
+
+int job_run_and_invalidate(Job *j);
+int job_finish_and_invalidate(Job *j, bool success);
+
+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);
+
+char *job_dbus_path(Job *j);
+
+#endif
diff --git a/src/linux/auto_dev-ioctl.h b/src/linux/auto_dev-ioctl.h
new file mode 100644
index 0000000000..850f39b33e
--- /dev/null
+++ b/src/linux/auto_dev-ioctl.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2008 Red Hat, Inc. All rights reserved.
+ * Copyright 2008 Ian Kent <raven@themaw.net>
+ *
+ * This file is part of the Linux kernel and is made available under
+ * the terms of the GNU General Public License, version 2, or at your
+ * option, any later version, incorporated herein by reference.
+ */
+
+#ifndef _LINUX_AUTO_DEV_IOCTL_H
+#define _LINUX_AUTO_DEV_IOCTL_H
+
+#include <linux/auto_fs.h>
+
+#ifdef __KERNEL__
+#include <linux/string.h>
+#else
+#include <string.h>
+#endif /* __KERNEL__ */
+
+#define AUTOFS_DEVICE_NAME "autofs"
+
+#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1
+#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0
+
+#define AUTOFS_DEVID_LEN 16
+
+#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl)
+
+/*
+ * An ioctl interface for autofs mount point control.
+ */
+
+struct args_protover {
+ __u32 version;
+};
+
+struct args_protosubver {
+ __u32 sub_version;
+};
+
+struct args_openmount {
+ __u32 devid;
+};
+
+struct args_ready {
+ __u32 token;
+};
+
+struct args_fail {
+ __u32 token;
+ __s32 status;
+};
+
+struct args_setpipefd {
+ __s32 pipefd;
+};
+
+struct args_timeout {
+ __u64 timeout;
+};
+
+struct args_requester {
+ __u32 uid;
+ __u32 gid;
+};
+
+struct args_expire {
+ __u32 how;
+};
+
+struct args_askumount {
+ __u32 may_umount;
+};
+
+struct args_ismountpoint {
+ union {
+ struct args_in {
+ __u32 type;
+ } in;
+ struct args_out {
+ __u32 devid;
+ __u32 magic;
+ } out;
+ };
+};
+
+/*
+ * All the ioctls use this structure.
+ * When sending a path size must account for the total length
+ * of the chunk of memory otherwise is is the size of the
+ * structure.
+ */
+
+struct autofs_dev_ioctl {
+ __u32 ver_major;
+ __u32 ver_minor;
+ __u32 size; /* total size of data passed in
+ * including this struct */
+ __s32 ioctlfd; /* automount command fd */
+
+ /* Command parameters */
+
+ union {
+ struct args_protover protover;
+ struct args_protosubver protosubver;
+ struct args_openmount openmount;
+ struct args_ready ready;
+ struct args_fail fail;
+ struct args_setpipefd setpipefd;
+ struct args_timeout timeout;
+ struct args_requester requester;
+ struct args_expire expire;
+ struct args_askumount askumount;
+ struct args_ismountpoint ismountpoint;
+ };
+
+ char path[0];
+};
+
+static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in)
+{
+ memset(in, 0, sizeof(struct autofs_dev_ioctl));
+ in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR;
+ in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR;
+ in->size = sizeof(struct autofs_dev_ioctl);
+ in->ioctlfd = -1;
+ return;
+}
+
+/*
+ * If you change this make sure you make the corresponding change
+ * to autofs-dev-ioctl.c:lookup_ioctl()
+ */
+enum {
+ /* Get various version info */
+ AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71,
+ AUTOFS_DEV_IOCTL_PROTOVER_CMD,
+ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD,
+
+ /* Open mount ioctl fd */
+ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD,
+
+ /* Close mount ioctl fd */
+ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD,
+
+ /* Mount/expire status returns */
+ AUTOFS_DEV_IOCTL_READY_CMD,
+ AUTOFS_DEV_IOCTL_FAIL_CMD,
+
+ /* Activate/deactivate autofs mount */
+ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD,
+ AUTOFS_DEV_IOCTL_CATATONIC_CMD,
+
+ /* Expiry timeout */
+ AUTOFS_DEV_IOCTL_TIMEOUT_CMD,
+
+ /* Get mount last requesting uid and gid */
+ AUTOFS_DEV_IOCTL_REQUESTER_CMD,
+
+ /* Check for eligible expire candidates */
+ AUTOFS_DEV_IOCTL_EXPIRE_CMD,
+
+ /* Request busy status */
+ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD,
+
+ /* Check if path is a mountpoint */
+ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD,
+};
+
+#define AUTOFS_IOCTL 0x93
+
+#define AUTOFS_DEV_IOCTL_VERSION \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOVER \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOSUBVER \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_OPENMOUNT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_READY \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_FAIL \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_SETPIPEFD \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CATATONIC \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_TIMEOUT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_REQUESTER \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_EXPIRE \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ASKUMOUNT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \
+ _IOWR(AUTOFS_IOCTL, \
+ AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl)
+
+#endif /* _LINUX_AUTO_DEV_IOCTL_H */
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000000..012dd12073
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,119 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foolisthfoo
+#define foolisthfoo
+
+/***
+ 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 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/>.
+***/
+
+/* The head of the linked list. Use this in the structure that shall
+ * contain the head of the linked list */
+#define LIST_HEAD(t,name) \
+ t *name
+
+/* The pointers in the linked list's items. Use this in the item structure */
+#define LIST_FIELDS(t,name) \
+ t *name##_next, *name##_prev
+
+/* Initialize the list's head */
+#define LIST_HEAD_INIT(t,head) \
+ do { \
+ (head) = NULL; } \
+ while(false)
+
+/* Initialize a list item */
+#define LIST_INIT(t,name,item) \
+ do { \
+ t *_item = (item); \
+ assert(_item); \
+ _item->name##_prev = _item->name##_next = NULL; \
+ } while(false)
+
+/* Prepend an item to the list */
+#define LIST_PREPEND(t,name,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ assert(_item); \
+ if ((_item->name##_next = *_head)) \
+ _item->name##_next->name##_prev = _item; \
+ _item->name##_prev = NULL; \
+ *_head = _item; \
+ } while(false)
+
+/* Remove an item from the list */
+#define LIST_REMOVE(t,name,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ assert(_item); \
+ if (_item->name##_next) \
+ _item->name##_next->name##_prev = _item->name##_prev; \
+ if (_item->name##_prev) \
+ _item->name##_prev->name##_next = _item->name##_next; \
+ else { \
+ assert(*_head == _item); \
+ *_head = _item->name##_next; \
+ } \
+ _item->name##_next = _item->name##_prev = NULL; \
+ } while(false)
+
+/* Find the head of the list */
+#define LIST_FIND_HEAD(t,name,item,head) \
+ do { \
+ t *_item = (item); \
+ assert(_item); \
+ while ((_item->name##_prev) \
+ _item = _item->name##_prev; \
+ (head) = _item; \
+ } while (false)
+
+/* Find the head of the list */
+#define LIST_FIND_TAIL(t,name,item,tail) \
+ do { \
+ t *_item = (item); \
+ assert(_item); \
+ while (_item->name##_next) \
+ _item = _item->name##_next; \
+ (tail) = _item; \
+ } while (false)
+
+/* Insert an item after another one (a = where, b = what) */
+#define LIST_INSERT_AFTER(t,name,head,a,b) \
+ do { \
+ t **_head = &(head), *_a = (a), *_b = (b); \
+ assert(_b); \
+ if (!_a) { \
+ if ((_b->name##_next = *_head)) \
+ _b->name##_next->name##_prev = _b; \
+ _b->name##_prev = NULL; \
+ *_head = _b; \
+ } else { \
+ if ((_b->name##_next = _a->name##_next)) \
+ _b->name##_next->name##_prev = _b; \
+ _b->name##_prev = _a; \
+ _a->name##_next = _b; \
+ } \
+ } while(false)
+
+#define LIST_FOREACH(name,i,head) \
+ for ((i) = (head); (i); (i) = (i)->name##_next)
+
+#define LIST_FOREACH_SAFE(name,i,n,head) \
+ for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
+
+#endif
diff --git a/src/load-dropin.c b/src/load-dropin.c
new file mode 100644
index 0000000000..2101e33004
--- /dev/null
+++ b/src/load-dropin.c
@@ -0,0 +1,117 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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) {
+ DIR *d;
+ struct dirent *de;
+ int r;
+
+ if (!(d = opendir(path))) {
+
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ while ((de = readdir(d))) {
+ char *f;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if (asprintf(&f, "%s/%s", path, de->d_name) < 0) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = unit_add_dependency_by_name(u, UNIT_WANTS, de->d_name, f, true);
+ free(f);
+
+ if (r < 0)
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ closedir(d);
+ return r;
+}
+
+int unit_load_dropin(Unit *u) {
+ Iterator i;
+ int r;
+ char *t;
+
+ assert(u);
+
+ /* Load dependencies from supplementary drop-in directories */
+
+ SET_FOREACH(t, u->meta.names, i) {
+ char *path;
+ char **p;
+
+ STRV_FOREACH(p, u->meta.manager->unit_path) {
+
+ if (asprintf(&path, "%s/%s.wants", *p, t) < 0)
+ return -ENOMEM;
+
+ r = iterate_dir(u, path);
+ free(path);
+
+ if (r < 0)
+ return r;
+
+ if (u->meta.instance) {
+ char *template;
+ /* Also try the template dir */
+
+ if (!(template = unit_name_template(t)))
+ return -ENOMEM;
+
+ r = asprintf(&path, "%s/%s.wants", *p, template);
+ free(template);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ r = iterate_dir(u, path);
+ free(path);
+
+ if (r < 0)
+ return r;
+ }
+
+ }
+ }
+
+ return 0;
+}
diff --git a/src/load-dropin.h b/src/load-dropin.h
new file mode 100644
index 0000000000..b39580b7cb
--- /dev/null
+++ b/src/load-dropin.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooloaddropinhfoo
+#define fooloaddropinhfoo
+
+/***
+ 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 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 "unit.h"
+
+/* Read service data supplementary drop-in directories */
+
+int unit_load_dropin(Unit *u);
+
+#endif
diff --git a/src/load-fragment.c b/src/load-fragment.c
new file mode 100644
index 0000000000..148a579d99
--- /dev/null
+++ b/src/load-fragment.c
@@ -0,0 +1,1483 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "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"
+
+#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
+ static int function( \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ const char *lvalue, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ type *i = data, x; \
+ \
+ assert(filename); \
+ assert(lvalue); \
+ assert(rvalue); \
+ assert(data); \
+ \
+ if ((x = name##_from_string(rvalue)) < 0) { \
+ log_error("[%s:%u] " msg ": %s", filename, line, rvalue); \
+ return -EBADMSG; \
+ } \
+ \
+ *i = x; \
+ \
+ return 0; \
+ }
+
+static int config_parse_deps(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ UnitDependency d = PTR_TO_UINT(data);
+ Unit *u = userdata;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ char *t, *k;
+ int r;
+
+ if (!(t = strndup(w, l)))
+ 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);
+ free(k);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_names(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *w;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ char *t, *k;
+ int r;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ k = unit_name_printf(u, t);
+ free(t);
+
+ if (!k)
+ return -ENOMEM;
+
+ r = unit_merge_by_name(u, k);
+ free(k);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_description(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *k;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (!(k = unit_full_printf(u, rvalue)))
+ return -ENOMEM;
+
+ free(u->meta.description);
+
+ if (*k)
+ u->meta.description = k;
+ else {
+ free(k);
+ u->meta.description = NULL;
+ }
+
+ return 0;
+}
+
+static int config_parse_listen(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int r;
+ SocketPort *p;
+ Socket *s;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ s = (Socket*) data;
+
+ if (!(p = new0(SocketPort, 1)))
+ return -ENOMEM;
+
+ if (streq(lvalue, "ListenFIFO")) {
+ p->type = SOCKET_FIFO;
+
+ if (!(p->path = strdup(rvalue))) {
+ free(p);
+ return -ENOMEM;
+ }
+ } else {
+ p->type = SOCKET_SOCKET;
+
+ if ((r = socket_address_parse(&p->address, rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse address value: %s", filename, line, rvalue);
+ free(p);
+ return r;
+ }
+
+ 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) {
+ free(p);
+ return -EPROTONOSUPPORT;
+ }
+ }
+
+ p->fd = -1;
+ LIST_PREPEND(SocketPort, port, s->ports, p);
+
+ return 0;
+}
+
+static int config_parse_socket_bind(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int r;
+ Socket *s;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ s = (Socket*) data;
+
+ if ((r = parse_boolean(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse bind IPv6 only value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH;
+
+ return 0;
+}
+
+static int config_parse_nice(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int priority, r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atoi(rvalue, &priority)) < 0) {
+ log_error("[%s:%u] Failed to parse nice priority: %s", filename, line, rvalue);
+ return r;
+ }
+
+ if (priority < PRIO_MIN || priority >= PRIO_MAX) {
+ log_error("[%s:%u] Nice priority out of range: %s", filename, line, rvalue);
+ return -ERANGE;
+ }
+
+ c->nice = priority;
+ c->nice_set = false;
+
+ return 0;
+}
+
+static int config_parse_oom_adjust(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int oa, r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atoi(rvalue, &oa)) < 0) {
+ log_error("[%s:%u] Failed to parse OOM adjust value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ if (oa < OOM_DISABLE || oa > OOM_ADJUST_MAX) {
+ log_error("[%s:%u] OOM adjust value out of range: %s", filename, line, rvalue);
+ return -ERANGE;
+ }
+
+ c->oom_adjust = oa;
+ c->oom_adjust_set = true;
+
+ return 0;
+}
+
+static int config_parse_mode(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ mode_t *m = data;
+ long l;
+ char *x = NULL;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ errno = 0;
+ l = strtol(rvalue, &x, 8);
+ if (!x || *x || errno) {
+ log_error("[%s:%u] Failed to parse mode value: %s", filename, line, rvalue);
+ return errno ? -errno : -EINVAL;
+ }
+
+ if (l < 0000 || l > 07777) {
+ log_error("[%s:%u] mode value out of range: %s", filename, line, rvalue);
+ return -ERANGE;
+ }
+
+ *m = (mode_t) l;
+ return 0;
+}
+
+static int config_parse_exec(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecCommand **e = data, *nce = NULL;
+ char **n;
+ char *w;
+ unsigned k;
+ size_t l;
+ char *state;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ k = 0;
+ FOREACH_WORD_QUOTED(w, l, rvalue, state)
+ k++;
+
+ if (!(n = new(char*, k+1)))
+ return -ENOMEM;
+
+ k = 0;
+ FOREACH_WORD_QUOTED(w, l, rvalue, state)
+ if (!(n[k++] = strndup(w, l)))
+ goto fail;
+
+ n[k] = NULL;
+
+ if (!n[0] || !path_is_absolute(n[0])) {
+ log_error("[%s:%u] Invalid executable path in command line: %s", filename, line, rvalue);
+ strv_free(n);
+ return -EINVAL;
+ }
+
+ if (!(nce = new0(ExecCommand, 1)))
+ goto fail;
+
+ nce->argv = n;
+ if (!(nce->path = strdup(n[0])))
+ goto fail;
+
+ exec_command_append_list(e, nce);
+
+ return 0;
+
+fail:
+ for (; k > 0; k--)
+ free(n[k-1]);
+ free(n);
+
+ free(nce);
+
+ return -ENOMEM;
+}
+
+static int config_parse_usec(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ usec_t *usec = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = parse_usec(rvalue, usec)) < 0) {
+ log_error("[%s:%u] Failed to parse time value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ return 0;
+}
+
+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");
+
+static int config_parse_bindtodevice(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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");
+
+static int config_parse_facility(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ int *o = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((x = log_facility_from_string(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse log facility: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ *o = LOG_MAKEPRI(x, LOG_PRI(*o));
+
+ return 0;
+}
+
+static int config_parse_level(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ int *o = data, x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((x = log_level_from_string(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse log level: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ *o = LOG_MAKEPRI(LOG_FAC(*o), x);
+ return 0;
+}
+
+static int config_parse_io_class(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ int x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((x = ioprio_class_from_string(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse IO scheduling class: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio));
+ c->ioprio_set = true;
+
+ return 0;
+}
+
+static int config_parse_io_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i);
+ c->ioprio_set = true;
+
+ return 0;
+}
+
+static int config_parse_cpu_sched_policy(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+
+ ExecContext *c = data;
+ int x;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((x = sched_policy_from_string(rvalue)) < 0) {
+ log_error("[%s:%u] Failed to parse CPU scheduling policy: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->cpu_sched_policy = x;
+ c->cpu_sched_set = true;
+
+ return 0;
+}
+
+static int config_parse_cpu_sched_prio(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->cpu_sched_priority = i;
+ c->cpu_sched_set = true;
+
+ return 0;
+}
+
+static int config_parse_cpu_affinity(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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(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 (r < 0 || cpu >= CPU_SETSIZE) {
+ log_error("[%s:%u] Failed to parse CPU affinity: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ CPU_SET(cpu, &c->cpu_affinity);
+ }
+
+ c->cpu_affinity_set = true;
+
+ return 0;
+}
+
+static int config_parse_capabilities(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ if (c->capabilities)
+ cap_free(c->capabilities);
+ c->capabilities = cap;
+
+ return 0;
+}
+
+static int config_parse_secure_bits(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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(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: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+ }
+
+ return 0;
+}
+
+static int config_parse_bounding_set(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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(w, l, rvalue, state) {
+ char *t;
+ int r;
+ cap_value_t cap;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ r = cap_from_name(t, &cap);
+ free(t);
+
+ if (r < 0) {
+ log_error("[%s:%u] Failed to parse capability bounding set: %s", filename, line, rvalue);
+ return -EBADMSG;
+ }
+
+ c->capability_bounding_set_drop |= 1 << cap;
+ }
+
+ return 0;
+}
+
+static int config_parse_timer_slack_ns(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = data;
+ unsigned long u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atolu(rvalue, &u)) < 0) {
+ log_error("[%s:%u] Failed to parse time slack value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ c->timer_slack_ns = u;
+
+ return 0;
+}
+
+static int config_parse_limit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ struct rlimit **rl = data;
+ unsigned long long u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atollu(rvalue, &u)) < 0) {
+ log_error("[%s:%u] Failed to parse resource value: %s", filename, line, rvalue);
+ return r;
+ }
+
+ if (!*rl)
+ if (!(*rl = new(struct rlimit, 1)))
+ return -ENOMEM;
+
+ (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u;
+ return 0;
+}
+
+static int config_parse_cgroup(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Unit *u = userdata;
+ char *w;
+ size_t l;
+ char *state;
+
+ FOREACH_WORD(w, l, rvalue, state) {
+ char *t;
+ int r;
+
+ if (!(t = strndup(w, l)))
+ return -ENOMEM;
+
+ r = unit_add_cgroup_from_text(u, t);
+ free(t);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int config_parse_sysv_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ int *priority = data;
+ int r, i;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if ((r = safe_atoi(rvalue, &i)) < 0 || i < 0) {
+ log_error("[%s:%u] Failed to parse SysV start priority: %s", filename, line, rvalue);
+ return r;
+ }
+
+ *priority = (int) i;
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode");
+
+static int config_parse_mount_flags(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ 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(w, l, rvalue, state) {
+ if (strncmp(w, "shared", l) == 0)
+ flags |= MS_SHARED;
+ else if (strncmp(w, "slave", l) == 0)
+ flags |= MS_SLAVE;
+ else if (strncmp(w, "private", l) == 0)
+ flags |= MS_PRIVATE;
+ else {
+ log_error("[%s:%u] Failed to parse mount flags: %s", filename, line, rvalue);
+ return -EINVAL;
+ }
+ }
+
+ c->mount_flags = flags;
+ 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, *k, *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 */
+ name = file_name_from_path(*filename);
+ if (!(id = set_get(names, name))) {
+
+ if (!(id = strdup(name)))
+ return -ENOMEM;
+
+ if ((r = set_put(names, id)) < 0) {
+ free(id);
+ return r;
+ }
+ }
+
+ /* Try to open the file name, but don't if its a symlink */
+ if ((fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW)) >= 0)
+ break;
+
+ if (errno != ELOOP)
+ return -errno;
+
+ /* Hmm, so this is a symlink. Let's read the name, and follow it manually */
+ if ((r = readlink_malloc(*filename, &target)) < 0)
+ return r;
+
+ k = file_in_same_dir(*filename, target);
+ free(target);
+
+ if (!k)
+ return -ENOMEM;
+
+ free(*filename);
+ *filename = k;
+ }
+
+ if (!(f = fdopen(fd, "r"))) {
+ 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 */
+ if ((r = unit_merge_by_name(*u, k)) < 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)->meta.manager, k);
+ free(k);
+
+ if (other)
+ if ((r = unit_merge(other, *u)) >= 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 void dump_items(FILE *f, const ConfigItem *items) {
+ const ConfigItem *i;
+ const char *prev_section = NULL;
+ bool not_first = false;
+
+ struct {
+ ConfigParserCallback callback;
+ const char *rvalue;
+ } table[] = {
+ { config_parse_int, "INTEGER" },
+ { config_parse_unsigned, "UNSIGNED" },
+ { config_parse_size, "SIZE" },
+ { config_parse_bool, "BOOLEAN" },
+ { config_parse_string, "STRING" },
+ { config_parse_path, "PATH" },
+ { config_parse_strv, "STRING [...]" },
+ { config_parse_nice, "NICE" },
+ { config_parse_oom_adjust, "OOMADJUST" },
+ { config_parse_io_class, "IOCLASS" },
+ { config_parse_io_priority, "IOPRIORITY" },
+ { config_parse_cpu_sched_policy, "CPUSCHEDPOLICY" },
+ { config_parse_cpu_sched_prio, "CPUSCHEDPRIO" },
+ { config_parse_cpu_affinity, "CPUAFFINITY" },
+ { config_parse_mode, "MODE" },
+ { config_parse_output, "OUTPUT" },
+ { config_parse_input, "INPUT" },
+ { config_parse_facility, "FACILITY" },
+ { config_parse_level, "LEVEL" },
+ { config_parse_capabilities, "CAPABILITIES" },
+ { config_parse_secure_bits, "SECUREBITS" },
+ { config_parse_bounding_set, "BOUNDINGSET" },
+ { config_parse_timer_slack_ns, "TIMERSLACK" },
+ { config_parse_limit, "LIMIT" },
+ { config_parse_cgroup, "CGROUP [...]" },
+ { config_parse_deps, "UNIT [...]" },
+ { config_parse_names, "UNIT [...]" },
+ { config_parse_exec, "PATH [ARGUMENT [...]]" },
+ { config_parse_service_type, "SERVICETYPE" },
+ { config_parse_service_restart, "SERVICERESTART" },
+ { config_parse_sysv_priority, "SYSVPRIORITY" },
+ { config_parse_kill_mode, "KILLMODE" },
+ { config_parse_listen, "SOCKET [...]" },
+ { config_parse_socket_bind, "SOCKETBIND" },
+ { config_parse_bindtodevice, "NETWORKINTERFACE" },
+ { config_parse_usec, "SECONDS" },
+ { config_parse_path_strv, "PATH [...]" },
+ { config_parse_mount_flags, "MOUNTFLAG [...]" },
+ { config_parse_description, "DESCRIPTION" },
+ };
+
+ assert(f);
+ assert(items);
+
+ for (i = items; i->lvalue; i++) {
+ unsigned j;
+ const char *rvalue = "OTHER";
+
+ if (!streq_ptr(i->section, prev_section)) {
+ if (!not_first)
+ not_first = true;
+ else
+ fputc('\n', f);
+
+ fprintf(f, "[%s]\n", i->section);
+ prev_section = i->section;
+ }
+
+ for (j = 0; j < ELEMENTSOF(table); j++)
+ if (i->parse == table[j].callback) {
+ rvalue = table[j].rvalue;
+ break;
+ }
+
+ fprintf(f, "%s=%s\n", i->lvalue, rvalue);
+ }
+}
+
+static int load_from_path(Unit *u, const char *path) {
+
+ static const char* const section_table[_UNIT_TYPE_MAX] = {
+ [UNIT_SERVICE] = "Service",
+ [UNIT_TIMER] = "Timer",
+ [UNIT_SOCKET] = "Socket",
+ [UNIT_TARGET] = "Target",
+ [UNIT_DEVICE] = "Device",
+ [UNIT_MOUNT] = "Mount",
+ [UNIT_AUTOMOUNT] = "Automount",
+ [UNIT_SNAPSHOT] = "Snapshot",
+ [UNIT_SWAP] = "Swap"
+ };
+
+#define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \
+ { "WorkingDirectory", config_parse_path, &(context).working_directory, section }, \
+ { "RootDirectory", config_parse_path, &(context).root_directory, section }, \
+ { "User", config_parse_string, &(context).user, section }, \
+ { "Group", config_parse_string, &(context).group, section }, \
+ { "SupplementaryGroups", config_parse_strv, &(context).supplementary_groups, section }, \
+ { "Nice", config_parse_nice, &(context), section }, \
+ { "OOMAdjust", config_parse_oom_adjust, &(context), section }, \
+ { "IOSchedulingClass", config_parse_io_class, &(context), section }, \
+ { "IOSchedulingPriority", config_parse_io_priority, &(context), section }, \
+ { "CPUSchedulingPolicy", config_parse_cpu_sched_policy,&(context), section }, \
+ { "CPUSchedulingPriority", config_parse_cpu_sched_prio, &(context), section }, \
+ { "CPUSchedulingResetOnFork", config_parse_bool, &(context).cpu_sched_reset_on_fork, section }, \
+ { "CPUAffinity", config_parse_cpu_affinity, &(context), section }, \
+ { "UMask", config_parse_mode, &(context).umask, section }, \
+ { "Environment", config_parse_strv, &(context).environment, section }, \
+ { "StandardInput", config_parse_input, &(context).std_input, section }, \
+ { "StandardOutput", config_parse_output, &(context).std_output, section }, \
+ { "StandardError", config_parse_output, &(context).std_output, section }, \
+ { "TTYPath", config_parse_path, &(context).tty_path, section }, \
+ { "SyslogIdentifier", config_parse_string, &(context).syslog_identifier, section }, \
+ { "SyslogFacility", config_parse_facility, &(context).syslog_priority, section }, \
+ { "SyslogLevel", config_parse_level, &(context).syslog_priority, section }, \
+ { "SyslogNoPrefix", config_parse_bool, &(context).syslog_no_prefix, section }, \
+ { "Capabilities", config_parse_capabilities, &(context), section }, \
+ { "SecureBits", config_parse_secure_bits, &(context), section }, \
+ { "CapabilityBoundingSetDrop", config_parse_bounding_set, &(context), section }, \
+ { "TimerSlackNS", config_parse_timer_slack_ns, &(context), section }, \
+ { "LimitCPU", config_parse_limit, &(context).rlimit[RLIMIT_CPU], section }, \
+ { "LimitFSIZE", config_parse_limit, &(context).rlimit[RLIMIT_FSIZE], section }, \
+ { "LimitDATA", config_parse_limit, &(context).rlimit[RLIMIT_DATA], section }, \
+ { "LimitSTACK", config_parse_limit, &(context).rlimit[RLIMIT_STACK], section }, \
+ { "LimitCORE", config_parse_limit, &(context).rlimit[RLIMIT_CORE], section }, \
+ { "LimitRSS", config_parse_limit, &(context).rlimit[RLIMIT_RSS], section }, \
+ { "LimitNOFILE", config_parse_limit, &(context).rlimit[RLIMIT_NOFILE], section }, \
+ { "LimitAS", config_parse_limit, &(context).rlimit[RLIMIT_AS], section }, \
+ { "LimitNPROC", config_parse_limit, &(context).rlimit[RLIMIT_NPROC], section }, \
+ { "LimitMEMLOCK", config_parse_limit, &(context).rlimit[RLIMIT_MEMLOCK], section }, \
+ { "LimitLOCKS", config_parse_limit, &(context).rlimit[RLIMIT_LOCKS], section }, \
+ { "LimitSIGPENDING", config_parse_limit, &(context).rlimit[RLIMIT_SIGPENDING], section }, \
+ { "LimitMSGQUEUE", config_parse_limit, &(context).rlimit[RLIMIT_MSGQUEUE], section }, \
+ { "LimitNICE", config_parse_limit, &(context).rlimit[RLIMIT_NICE], section }, \
+ { "LimitRTPRIO", config_parse_limit, &(context).rlimit[RLIMIT_RTPRIO], section }, \
+ { "LimitRTTIME", config_parse_limit, &(context).rlimit[RLIMIT_RTTIME], section }, \
+ { "ControlGroup", config_parse_cgroup, u, section }, \
+ { "ReadWriteDirectories", config_parse_path_strv, &(context).read_write_dirs, section }, \
+ { "ReadOnlyDirectories", config_parse_path_strv, &(context).read_only_dirs, section }, \
+ { "InaccessibleDirectories",config_parse_path_strv, &(context).inaccessible_dirs, section }, \
+ { "PrivateTmp", config_parse_bool, &(context).private_tmp, section }, \
+ { "MountFlags", config_parse_mount_flags, &(context), section }
+
+ const ConfigItem items[] = {
+ { "Names", config_parse_names, u, "Unit" },
+ { "Description", config_parse_description, u, "Unit" },
+ { "Requires", config_parse_deps, UINT_TO_PTR(UNIT_REQUIRES), "Unit" },
+ { "RequiresOverridable", config_parse_deps, UINT_TO_PTR(UNIT_REQUIRES_OVERRIDABLE), "Unit" },
+ { "Requisite", config_parse_deps, UINT_TO_PTR(UNIT_REQUISITE), "Unit" },
+ { "RequisiteOverridable", config_parse_deps, UINT_TO_PTR(UNIT_REQUISITE_OVERRIDABLE), "Unit" },
+ { "Wants", config_parse_deps, UINT_TO_PTR(UNIT_WANTS), "Unit" },
+ { "Conflicts", config_parse_deps, UINT_TO_PTR(UNIT_CONFLICTS), "Unit" },
+ { "Before", config_parse_deps, UINT_TO_PTR(UNIT_BEFORE), "Unit" },
+ { "After", config_parse_deps, UINT_TO_PTR(UNIT_AFTER), "Unit" },
+ { "RecursiveStop", config_parse_bool, &u->meta.recursive_stop, "Unit" },
+ { "StopWhenUnneeded", config_parse_bool, &u->meta.stop_when_unneeded, "Unit" },
+
+ { "PIDFile", config_parse_path, &u->service.pid_file, "Service" },
+ { "ExecStartPre", config_parse_exec, u->service.exec_command+SERVICE_EXEC_START_PRE, "Service" },
+ { "ExecStart", config_parse_exec, u->service.exec_command+SERVICE_EXEC_START, "Service" },
+ { "ExecStartPost", config_parse_exec, u->service.exec_command+SERVICE_EXEC_START_POST, "Service" },
+ { "ExecReload", config_parse_exec, u->service.exec_command+SERVICE_EXEC_RELOAD, "Service" },
+ { "ExecStop", config_parse_exec, u->service.exec_command+SERVICE_EXEC_STOP, "Service" },
+ { "ExecStopPost", config_parse_exec, u->service.exec_command+SERVICE_EXEC_STOP_POST, "Service" },
+ { "RestartSec", config_parse_usec, &u->service.restart_usec, "Service" },
+ { "TimeoutSec", config_parse_usec, &u->service.timeout_usec, "Service" },
+ { "Type", config_parse_service_type, &u->service.type, "Service" },
+ { "Restart", config_parse_service_restart, &u->service.restart, "Service" },
+ { "PermissionsStartOnly", config_parse_bool, &u->service.permissions_start_only, "Service" },
+ { "RootDirectoryStartOnly", config_parse_bool, &u->service.root_directory_start_only, "Service" },
+ { "ValidNoProcess", config_parse_bool, &u->service.valid_no_process, "Service" },
+ { "SysVStartPriority", config_parse_sysv_priority, &u->service.sysv_start_priority, "Service" },
+ { "KillMode", config_parse_kill_mode, &u->service.kill_mode, "Service" },
+ { "NonBlocking", config_parse_bool, &u->service.exec_context.non_blocking, "Service" },
+ { "BusName", config_parse_string, &u->service.bus_name, "Service" },
+ EXEC_CONTEXT_CONFIG_ITEMS(u->service.exec_context, "Service"),
+
+ { "ListenStream", config_parse_listen, &u->socket, "Socket" },
+ { "ListenDatagram", config_parse_listen, &u->socket, "Socket" },
+ { "ListenSequentialPacket", config_parse_listen, &u->socket, "Socket" },
+ { "ListenFIFO", config_parse_listen, &u->socket, "Socket" },
+ { "BindIPv6Only", config_parse_socket_bind, &u->socket, "Socket" },
+ { "Backlog", config_parse_unsigned, &u->socket.backlog, "Socket" },
+ { "BindToDevice", config_parse_bindtodevice, &u->socket, "Socket" },
+ { "ExecStartPre", config_parse_exec, u->socket.exec_command+SOCKET_EXEC_START_PRE, "Socket" },
+ { "ExecStartPost", config_parse_exec, u->socket.exec_command+SOCKET_EXEC_START_POST, "Socket" },
+ { "ExecStopPre", config_parse_exec, u->socket.exec_command+SOCKET_EXEC_STOP_PRE, "Socket" },
+ { "ExecStopPost", config_parse_exec, u->socket.exec_command+SOCKET_EXEC_STOP_POST, "Socket" },
+ { "TimeoutSec", config_parse_usec, &u->socket.timeout_usec, "Socket" },
+ { "DirectoryMode", config_parse_mode, &u->socket.directory_mode, "Socket" },
+ { "SocketMode", config_parse_mode, &u->socket.socket_mode, "Socket" },
+ { "KillMode", config_parse_kill_mode, &u->socket.kill_mode, "Socket" },
+ { "Accept", config_parse_bool, &u->socket.accept, "Socket" },
+ EXEC_CONTEXT_CONFIG_ITEMS(u->socket.exec_context, "Socket"),
+
+ { "What", config_parse_string, &u->mount.parameters_fragment.what, "Mount" },
+ { "Where", config_parse_path, &u->mount.where, "Mount" },
+ { "Options", config_parse_string, &u->mount.parameters_fragment.options, "Mount" },
+ { "Type", config_parse_string, &u->mount.parameters_fragment.fstype, "Mount" },
+ { "TimeoutSec", config_parse_usec, &u->mount.timeout_usec, "Mount" },
+ { "KillMode", config_parse_kill_mode, &u->mount.kill_mode, "Mount" },
+ EXEC_CONTEXT_CONFIG_ITEMS(u->mount.exec_context, "Mount"),
+
+ { "Where", config_parse_path, &u->automount.where, "Automount" },
+
+ { "What", config_parse_path, &u->swap.parameters_fragment.what, "Swap" },
+ { "Priority", config_parse_int, &u->swap.parameters_fragment.priority, "Swap" },
+
+ { NULL, NULL, NULL, NULL }
+ };
+
+#undef EXEC_CONTEXT_CONFIG_ITEMS
+
+ const char *sections[3];
+ char *k;
+ int r;
+ Set *symlink_names;
+ FILE *f = NULL;
+ char *filename = NULL, *id = NULL;
+ Unit *merged;
+
+ if (!u) {
+ /* Dirty dirty hack. */
+ dump_items((FILE*) path, items);
+ return 0;
+ }
+
+ assert(u);
+ assert(path);
+
+ sections[0] = "Unit";
+ sections[1] = section_table[u->meta.type];
+ sections[2] = NULL;
+
+ if (!(symlink_names = set_new(string_hash_func, string_compare_func)))
+ return -ENOMEM;
+
+ if (path_is_absolute(path)) {
+
+ if (!(filename = strdup(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((r = open_follow(&filename, &f, symlink_names, &id)) < 0) {
+ free(filename);
+ filename = NULL;
+
+ if (r != -ENOENT)
+ goto finish;
+ }
+
+ } else {
+ char **p;
+
+ STRV_FOREACH(p, u->meta.manager->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 */
+ if (!(filename = path_make_absolute(path, *p))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((r = open_follow(&filename, &f, symlink_names, &id)) < 0) {
+ char *sn;
+
+ free(filename);
+ filename = NULL;
+
+ if (r != -ENOENT)
+ goto finish;
+
+ /* Empty the symlink names for the next run */
+ while ((sn = set_steal_first(symlink_names)))
+ free(sn);
+
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (!filename) {
+ r = 0;
+ goto finish;
+ }
+
+ merged = u;
+ if ((r = merge_by_names(&merged, symlink_names, id)) < 0)
+ goto finish;
+
+ if (merged != u) {
+ u->meta.load_state = UNIT_MERGED;
+ r = 0;
+ goto finish;
+ }
+
+ /* Now, parse the file contents */
+ if ((r = config_parse(filename, f, sections, items, u)) < 0)
+ goto finish;
+
+ free(u->meta.fragment_path);
+ u->meta.fragment_path = filename;
+ filename = NULL;
+
+ u->meta.load_state = UNIT_LOADED;
+ r = 0;
+
+finish:
+ while ((k = set_steal_first(symlink_names)))
+ free(k);
+
+ set_free(symlink_names);
+ free(filename);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int unit_load_fragment(Unit *u) {
+ int r;
+
+ assert(u);
+
+ if (u->meta.fragment_path) {
+
+ if ((r = load_from_path(u, u->meta.fragment_path)) < 0)
+ return r;
+
+ } else {
+ Iterator i;
+ const char *t;
+
+ /* Try to find the unit under its id */
+ if ((r = load_from_path(u, u->meta.id)) < 0)
+ return r;
+
+ /* Try to find an alias we can load this with */
+ if (u->meta.load_state == UNIT_STUB)
+ SET_FOREACH(t, u->meta.names, i) {
+
+ if (t == u->meta.id)
+ continue;
+
+ if ((r = load_from_path(u, t)) < 0)
+ return r;
+
+ if (u->meta.load_state != UNIT_STUB)
+ break;
+ }
+
+ /* Now, follow the same logic, but look for a template */
+ if (u->meta.load_state == UNIT_STUB && u->meta.instance) {
+ char *k;
+
+ if (!(k = unit_name_template(u->meta.id)))
+ return -ENOMEM;
+
+ r = load_from_path(u, k);
+ free(k);
+
+ if (r < 0)
+ return r;
+
+ if (u->meta.load_state == UNIT_STUB)
+ SET_FOREACH(t, u->meta.names, i) {
+
+ if (t == u->meta.id)
+ continue;
+
+ if (!(k = unit_name_template(t)))
+ return -ENOMEM;
+
+ r = load_from_path(u, k);
+ free(k);
+
+ if (r < 0)
+ return r;
+
+ if (u->meta.load_state != UNIT_STUB)
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void unit_dump_config_items(FILE *f) {
+ /* OK, this wins a prize for extreme ugliness. */
+
+ load_from_path(NULL, (const void*) f);
+}
diff --git a/src/load-fragment.h b/src/load-fragment.h
new file mode 100644
index 0000000000..beba87cb7b
--- /dev/null
+++ b/src/load-fragment.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooloadfragmenthfoo
+#define fooloadfragmenthfoo
+
+/***
+ 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 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 "unit.h"
+
+/* Read service data from .desktop file style configuration fragments */
+
+int unit_load_fragment(Unit *u);
+
+void unit_dump_config_items(FILE *f);
+
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000000..5d17955e7e
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,439 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+
+#define SYSLOG_TIMEOUT_USEC (5*USEC_PER_SEC)
+#define LOG_BUFFER_MAX 1024
+
+static LogTarget log_target = LOG_TARGET_CONSOLE;
+static int log_max_level = LOG_DEBUG;
+
+static int console_fd = STDERR_FILENO;
+static int syslog_fd = -1;
+static int kmsg_fd = -1;
+
+/* Akin to glibc's __abort_msg; which is private and we hance cannot
+ * use here. */
+static char *log_abort_msg = NULL;
+
+void log_close_console(void) {
+
+ if (console_fd < 0)
+ return;
+
+ if (getpid() == 1 || console_fd != STDERR_FILENO) {
+ close_nointr_nofail(console_fd);
+ console_fd = -1;
+ }
+}
+
+static int log_open_console(void) {
+
+ if (console_fd >= 0)
+ return 0;
+
+ if (getpid() == 1) {
+
+ if ((console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0) {
+ log_error("Failed to open /dev/console for logging: %s", strerror(-console_fd));
+ return console_fd;
+ }
+
+ log_info("Succesfully opened /dev/console for logging.");
+ } else
+ console_fd = STDERR_FILENO;
+
+ return 0;
+}
+
+void log_close_kmsg(void) {
+
+ if (kmsg_fd < 0)
+ return;
+
+ close_nointr_nofail(kmsg_fd);
+ kmsg_fd = -1;
+}
+
+static int log_open_kmsg(void) {
+
+ if (kmsg_fd >= 0)
+ return 0;
+
+ if ((kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0) {
+ log_info("Failed to open /dev/kmsg for logging: %s", strerror(errno));
+ return -errno;
+ }
+
+ log_info("Succesfully opened /dev/kmsg for logging.");
+
+ return 0;
+}
+
+void log_close_syslog(void) {
+
+ if (syslog_fd < 0)
+ return;
+
+ close_nointr_nofail(syslog_fd);
+ syslog_fd = -1;
+}
+
+static int log_open_syslog(void) {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ } sa;
+ struct timeval tv;
+ int r;
+
+ if (syslog_fd >= 0)
+ return 0;
+
+ if ((syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Make sure we don't block for more than 5s when talking to
+ * syslog */
+ timeval_store(&tv, SYSLOG_TIMEOUT_USEC);
+ if (setsockopt(syslog_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
+
+ if (connect(syslog_fd, &sa.sa, sizeof(sa)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ log_info("Succesfully opened syslog for logging.");
+
+ return 0;
+
+fail:
+ log_close_syslog();
+ log_info("Failed to open syslog for logging: %s", strerror(-r));
+ return r;
+}
+
+int log_open(void) {
+ int r;
+
+ /* If we don't use the console we close it here, to not get
+ * killed by SAK. If we don't use syslog we close it here so
+ * that we are not confused by somebody deleting the socket in
+ * the fs. If we don't use /dev/kmsg we still keep it open,
+ * because there is no reason to close it. */
+
+ if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_SYSLOG)
+ if ((r = log_open_syslog()) >= 0) {
+ log_close_console();
+ return r;
+ }
+
+ if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_KMSG)
+ if ((r = log_open_kmsg()) >= 0) {
+ log_close_syslog();
+ log_close_console();
+ return r;
+ }
+
+ log_close_syslog();
+ return log_open_console();
+}
+
+void log_set_target(LogTarget target) {
+ assert(target >= 0);
+ assert(target < _LOG_TARGET_MAX);
+
+ log_target = target;
+}
+
+void log_set_max_level(int level) {
+ assert((level & LOG_PRIMASK) == level);
+
+ log_max_level = level;
+}
+
+static int write_to_console(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *buffer) {
+
+ char location[64];
+ struct iovec iovec[5];
+ unsigned n = 0;
+ bool highlight;
+
+ if (console_fd < 0)
+ return 0;
+
+ snprintf(location, sizeof(location), "(%s:%u) ", file, line);
+ char_array_0(location);
+
+ highlight = LOG_PRI(level) <= LOG_ERR;
+
+ zero(iovec);
+ IOVEC_SET_STRING(iovec[n++], location);
+ if (highlight)
+ IOVEC_SET_STRING(iovec[n++], "\x1B[1;31m");
+ IOVEC_SET_STRING(iovec[n++], buffer);
+ if (highlight)
+ IOVEC_SET_STRING(iovec[n++], "\x1B[0m");
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ if (writev(console_fd, iovec, n) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int write_to_syslog(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *buffer) {
+
+ char header_priority[16], header_time[64], header_pid[16];
+ struct iovec iovec[5];
+ struct msghdr msghdr;
+ time_t t;
+ struct tm *tm;
+
+ if (syslog_fd < 0)
+ return 0;
+
+ snprintf(header_priority, sizeof(header_priority), "<%i>", LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(level)));
+ char_array_0(header_priority);
+
+ t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC);
+ if (!(tm = localtime(&t)))
+ return -EINVAL;
+
+ if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+ return -EINVAL;
+
+ snprintf(header_pid, sizeof(header_pid), "[%llu]: ", (unsigned long long) getpid());
+ char_array_0(header_pid);
+
+ zero(iovec);
+ IOVEC_SET_STRING(iovec[0], header_priority);
+ IOVEC_SET_STRING(iovec[1], header_time);
+ IOVEC_SET_STRING(iovec[2], __progname);
+ IOVEC_SET_STRING(iovec[3], header_pid);
+ IOVEC_SET_STRING(iovec[4], buffer);
+
+ zero(msghdr);
+ msghdr.msg_iov = iovec;
+ msghdr.msg_iovlen = ELEMENTSOF(iovec);
+
+ if (sendmsg(syslog_fd, &msghdr, 0) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int write_to_kmsg(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *buffer) {
+
+ char header_priority[16], header_pid[16];
+ struct iovec iovec[5];
+
+ if (kmsg_fd < 0)
+ return 0;
+
+ snprintf(header_priority, sizeof(header_priority), "<%i>", LOG_PRI(level));
+ char_array_0(header_priority);
+
+ snprintf(header_pid, sizeof(header_pid), "[%llu]: ", (unsigned long long) getpid());
+ char_array_0(header_pid);
+
+ zero(iovec);
+ IOVEC_SET_STRING(iovec[0], header_priority);
+ IOVEC_SET_STRING(iovec[1], __progname);
+ IOVEC_SET_STRING(iovec[2], header_pid);
+ IOVEC_SET_STRING(iovec[3], buffer);
+ IOVEC_SET_STRING(iovec[4], "\n");
+
+ if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int log_dispatch(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *buffer) {
+
+ int r;
+
+ if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_SYSLOG) {
+
+ if ((r = write_to_syslog(level, file, line, func, buffer)) < 0) {
+ log_close_syslog();
+ log_open_kmsg();
+ } else if (r > 0)
+ return r;
+ }
+
+ if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
+ log_target == LOG_TARGET_KMSG) {
+
+ if ((r = write_to_kmsg(level, file, line, func, buffer)) < 0) {
+ log_close_kmsg();
+ log_open_console();
+ } else if (r > 0)
+ return r;
+ }
+
+ return write_to_console(level, file, line, func, buffer);
+}
+
+int log_meta(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) {
+
+ char buffer[LOG_BUFFER_MAX];
+ int saved_errno, r;
+ va_list ap;
+
+ if (_likely(LOG_PRI(level) > log_max_level))
+ return 0;
+
+ saved_errno = errno;
+
+ va_start(ap, format);
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ va_end(ap);
+
+ char_array_0(buffer);
+
+ r = log_dispatch(level, file, line, func, buffer);
+ errno = saved_errno;
+
+ return r;
+}
+
+void log_assert(
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) {
+
+ static char buffer[LOG_BUFFER_MAX];
+ int saved_errno = errno;
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ va_end(ap);
+
+ char_array_0(buffer);
+ log_abort_msg = buffer;
+
+ log_dispatch(LOG_CRIT, file, line, func, buffer);
+ abort();
+
+ /* If the user chose to ignore this SIGABRT, we are happy to go on, as if nothing happened. */
+ errno = saved_errno;
+}
+
+int log_set_target_from_string(const char *e) {
+ LogTarget t;
+
+ if ((t = log_target_from_string(e)) < 0)
+ return -EINVAL;
+
+ log_set_target(t);
+ return 0;
+}
+
+int log_set_max_level_from_string(const char *e) {
+ int t;
+
+ if ((t = log_level_from_string(e)) < 0)
+ return -EINVAL;
+
+ log_set_max_level(t);
+ return 0;
+}
+
+void log_parse_environment(void) {
+ const char *e;
+
+ if ((e = getenv("SYSTEMD_LOG_TARGET")))
+ if (log_set_target_from_string(e) < 0)
+ log_warning("Failed to parse log target %s. Ignoring.", e);
+
+ if ((e = getenv("SYSTEMD_LOG_LEVEL")))
+ if (log_set_max_level_from_string(e) < 0)
+ log_warning("Failed to parse log level %s. Ignoring.", e);
+}
+
+LogTarget log_get_target(void) {
+ return log_target;
+}
+
+int log_get_max_level(void) {
+ return log_max_level;
+}
+
+static const char *const log_target_table[] = {
+ [LOG_TARGET_CONSOLE] = "console",
+ [LOG_TARGET_SYSLOG] = "syslog",
+ [LOG_TARGET_KMSG] = "kmsg",
+ [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget);
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000000..6df1d59961
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,79 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foologhfoo
+#define foologhfoo
+
+/***
+ 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 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 <syslog.h>
+
+#include "macro.h"
+
+/* If set to SYSLOG and /dev/log can not be opened we fall back to
+ * KSMG. If KMSG fails, we fall back to CONSOLE */
+typedef enum LogTarget{
+ LOG_TARGET_CONSOLE,
+ LOG_TARGET_KMSG,
+ LOG_TARGET_SYSLOG,
+ LOG_TARGET_SYSLOG_OR_KMSG,
+ _LOG_TARGET_MAX,
+ _LOG_TARGET_INVALID = -1
+} LogTarget;
+
+void log_set_target(LogTarget target);
+void log_set_max_level(int level);
+
+int log_set_target_from_string(const char *e);
+int log_set_max_level_from_string(const char *e);
+
+LogTarget log_get_target(void);
+int log_get_max_level(void);
+
+int log_open(void);
+
+void log_close_syslog(void);
+void log_close_kmsg(void);
+void log_close_console(void);
+
+void log_parse_environment(void);
+
+int log_meta(
+ int level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) _printf_attr(5,6);
+
+_noreturn void log_assert(
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) _printf_attr(4,5);
+
+#define log_debug(...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_info(...) log_meta(LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_notice(...) log_meta(LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_warning(...) log_meta(LOG_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_error(...) log_meta(LOG_ERR, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+const char *log_target_to_string(LogTarget target);
+LogTarget log_target_from_string(const char *s);
+
+#endif
diff --git a/src/logger.c b/src/logger.c
new file mode 100644
index 0000000000..f81d2c5c8a
--- /dev/null
+++ b/src/logger.c
@@ -0,0 +1,565 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <sys/socket.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/epoll.h>
+#include <sys/un.h>
+#include <fcntl.h>
+
+#include "util.h"
+#include "log.h"
+#include "list.h"
+#include "sd-daemon.h"
+
+#define STREAM_BUFFER 2048
+#define STREAMS_MAX 256
+#define SERVER_FD_MAX 16
+#define TIMEOUT ((int) (10*MSEC_PER_SEC))
+
+typedef struct Stream Stream;
+
+typedef struct Server {
+ int syslog_fd;
+ int kmsg_fd;
+ int epoll_fd;
+
+ unsigned n_server_fd;
+
+ LIST_HEAD(Stream, streams);
+ unsigned n_streams;
+} Server;
+
+typedef enum StreamTarget {
+ STREAM_SYSLOG,
+ STREAM_KMSG
+} StreamTarget;
+
+typedef enum StreamState {
+ STREAM_TARGET,
+ STREAM_PRIORITY,
+ STREAM_PROCESS,
+ STREAM_PREFIX,
+ STREAM_RUNNING
+} StreamState;
+
+struct Stream {
+ Server *server;
+
+ StreamState state;
+
+ int fd;
+
+ LogTarget target;
+ int priority;
+ char *process;
+ pid_t pid;
+ uid_t uid;
+
+ bool prefix;
+
+ char buffer[STREAM_BUFFER];
+ size_t length;
+
+ LIST_FIELDS(Stream, stream);
+};
+
+static int stream_log(Stream *s, char *p, usec_t timestamp) {
+
+ char header_priority[16], header_time[64], header_pid[16];
+ struct iovec iovec[5];
+ int priority;
+
+ assert(s);
+ assert(p);
+
+ priority = s->priority;
+
+ if (s->prefix &&
+ p[0] == '<' &&
+ p[1] >= '0' && p[1] <= '7' &&
+ p[2] == '>') {
+
+ /* Detected priority prefix */
+ priority = LOG_MAKEPRI(LOG_FAC(priority), (p[1] - '0'));
+
+ p += 3;
+ }
+
+ if (*p == 0)
+ return 0;
+
+ /*
+ * The format glibc uses to talk to the syslog daemon is:
+ *
+ * <priority>time process[pid]: msg
+ *
+ * The format the kernel uses is:
+ *
+ * <priority>msg\n
+ *
+ * We extend the latter to include the process name and pid.
+ */
+
+ snprintf(header_priority, sizeof(header_priority), "<%i>",
+ s->target == STREAM_SYSLOG ? priority : LOG_PRI(priority));
+ char_array_0(header_priority);
+
+ if (s->target == STREAM_SYSLOG) {
+ time_t t;
+ struct tm *tm;
+
+ t = (time_t) (timestamp / USEC_PER_SEC);
+ if (!(tm = localtime(&t)))
+ return -EINVAL;
+
+ if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+ return -EINVAL;
+ }
+
+ snprintf(header_pid, sizeof(header_pid), "[%llu]: ", (unsigned long long) s->pid);
+ char_array_0(header_pid);
+
+ zero(iovec);
+ IOVEC_SET_STRING(iovec[0], header_priority);
+
+ if (s->target == STREAM_SYSLOG) {
+ struct msghdr msghdr;
+
+ IOVEC_SET_STRING(iovec[1], header_time);
+ IOVEC_SET_STRING(iovec[2], s->process);
+ IOVEC_SET_STRING(iovec[3], header_pid);
+ IOVEC_SET_STRING(iovec[4], p);
+
+ zero(msghdr);
+ msghdr.msg_iov = iovec;
+ msghdr.msg_iovlen = ELEMENTSOF(iovec);
+
+ if (sendmsg(s->server->syslog_fd, &msghdr, MSG_NOSIGNAL) < 0)
+ return -errno;
+
+ } else if (s->target == STREAM_KMSG) {
+ IOVEC_SET_STRING(iovec[1], s->process);
+ IOVEC_SET_STRING(iovec[2], header_pid);
+ IOVEC_SET_STRING(iovec[3], p);
+ IOVEC_SET_STRING(iovec[4], (char*) "\n");
+
+ if (writev(s->server->kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0)
+ return -errno;
+ } else
+ assert_not_reached("Unknown log target");
+
+ return 0;
+}
+
+static int stream_line(Stream *s, char *p, usec_t timestamp) {
+ int r;
+
+ assert(s);
+ assert(p);
+
+ p = strstrip(p);
+
+ switch (s->state) {
+
+ case STREAM_TARGET:
+ if (streq(p, "syslog"))
+ s->target = STREAM_SYSLOG;
+ else if (streq(p, "kmsg")) {
+
+ if (s->server->kmsg_fd >= 0 && s->uid == 0)
+ s->target = STREAM_KMSG;
+ else {
+ log_warning("/dev/kmsg logging not available.");
+ return -EPERM;
+ }
+ } else {
+ log_warning("Failed to parse log target line.");
+ return -EBADMSG;
+ }
+ s->state = STREAM_PRIORITY;
+ return 0;
+
+ case STREAM_PRIORITY:
+ if ((r = safe_atoi(p, &s->priority)) < 0) {
+ log_warning("Failed to parse log priority line: %s", strerror(errno));
+ return r;
+ }
+
+ if (s->priority < 0) {
+ log_warning("Log priority negative: %s", strerror(errno));
+ return -ERANGE;
+ }
+
+ s->state = STREAM_PROCESS;
+ return 0;
+
+ case STREAM_PROCESS:
+ if (!(s->process = strdup(p)))
+ return -ENOMEM;
+
+ s->state = STREAM_PREFIX;
+ return 0;
+
+ case STREAM_PREFIX:
+
+ if ((r = parse_boolean(p)) < 0)
+ return r;
+
+ s->prefix = r;
+ s->state = STREAM_RUNNING;
+ return 0;
+
+ case STREAM_RUNNING:
+ return stream_log(s, p, timestamp);
+ }
+
+ assert_not_reached("Unknown stream state");
+}
+
+static int stream_scan(Stream *s, usec_t timestamp) {
+ char *p;
+ size_t remaining;
+ int r = 0;
+
+ assert(s);
+
+ p = s->buffer;
+ remaining = s->length;
+ for (;;) {
+ char *newline;
+
+ if (!(newline = memchr(p, '\n', remaining)))
+ break;
+
+ *newline = 0;
+
+ if ((r = stream_line(s, p, timestamp)) >= 0) {
+ remaining -= newline-p+1;
+ p = newline+1;
+ }
+ }
+
+ if (p > s->buffer) {
+ memmove(s->buffer, p, remaining);
+ s->length = remaining;
+ }
+
+ return r;
+}
+
+static int stream_process(Stream *s, usec_t timestamp) {
+ ssize_t l;
+ int r;
+ assert(s);
+
+ if ((l = read(s->fd, s->buffer+s->length, STREAM_BUFFER-s->length)) < 0) {
+
+ if (errno == EAGAIN)
+ return 0;
+
+ log_warning("Failed to read from stream: %s", strerror(errno));
+ return -1;
+ }
+
+
+ if (l == 0)
+ return 0;
+
+ s->length += l;
+ r = stream_scan(s, timestamp);
+
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static void stream_free(Stream *s) {
+ assert(s);
+
+ if (s->server) {
+ assert(s->server->n_streams > 0);
+ s->server->n_streams--;
+ LIST_REMOVE(Stream, stream, s->server->streams, s);
+
+ }
+
+ if (s->fd >= 0) {
+ if (s->server)
+ epoll_ctl(s->server->epoll_fd, EPOLL_CTL_DEL, s->fd, NULL);
+
+ close_nointr_nofail(s->fd);
+ }
+
+ free(s->process);
+ free(s);
+}
+
+static int stream_new(Server *s, int server_fd) {
+ Stream *stream;
+ int fd;
+ struct ucred ucred;
+ socklen_t len = sizeof(ucred);
+ struct epoll_event ev;
+ int r;
+
+ assert(s);
+
+ if ((fd = accept4(server_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)) < 0)
+ return -errno;
+
+ if (s->n_streams >= STREAMS_MAX) {
+ log_warning("Too many connections, refusing connection.");
+ close_nointr_nofail(fd);
+ return 0;
+ }
+
+ if (!(stream = new0(Stream, 1))) {
+ close_nointr_nofail(fd);
+ return -ENOMEM;
+ }
+
+ stream->fd = fd;
+
+ if (getsockopt(stream->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (shutdown(fd, SHUT_WR) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ zero(ev);
+ ev.data.ptr = stream;
+ ev.events = EPOLLIN;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ stream->pid = ucred.pid;
+ stream->uid = ucred.uid;
+
+ stream->server = s;
+ LIST_PREPEND(Stream, stream, s->streams, stream);
+ s->n_streams ++;
+
+ return 0;
+
+fail:
+ stream_free(stream);
+ return r;
+}
+
+static void server_done(Server *s) {
+ unsigned i;
+ assert(s);
+
+ while (s->streams)
+ stream_free(s->streams);
+
+ for (i = 0; i < s->n_server_fd; i++)
+ close_nointr_nofail(SD_LISTEN_FDS_START+i);
+
+ if (s->syslog_fd >= 0)
+ close_nointr_nofail(s->syslog_fd);
+
+ if (s->epoll_fd >= 0)
+ close_nointr_nofail(s->epoll_fd);
+
+ if (s->kmsg_fd >= 0)
+ close_nointr_nofail(s->kmsg_fd);
+}
+
+static int server_init(Server *s, unsigned n_sockets) {
+ int r;
+ unsigned i;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ } sa;
+
+ assert(s);
+ assert(n_sockets > 0);
+
+ zero(*s);
+
+ s->n_server_fd = n_sockets;
+ s->syslog_fd = -1;
+ s->kmsg_fd = -1;
+
+ if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+ r = -errno;
+ log_error("Failed to create epoll object: %s", strerror(errno));
+ goto fail;
+ }
+
+ for (i = 0; i < n_sockets; i++) {
+ struct epoll_event ev;
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.ptr = UINT_TO_PTR(SD_LISTEN_FDS_START+i);
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, SD_LISTEN_FDS_START+i, &ev) < 0) {
+ r = -errno;
+ log_error("Failed to add server fd to epoll object: %s", strerror(errno));
+ goto fail;
+ }
+ }
+
+ if ((s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+ r = -errno;
+ log_error("Failed to create log fd: %s", strerror(errno));
+ goto fail;
+ }
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
+
+ if (connect(s->syslog_fd, &sa.sa, sizeof(sa)) < 0) {
+ r = -errno;
+ log_error("Failed to connect log socket to /dev/log: %s", strerror(errno));
+ goto fail;
+ }
+
+ /* /dev/kmsg logging is strictly optional */
+ if ((s->kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0)
+ log_debug("Failed to open /dev/kmsg for logging, disabling kernel log buffer support: %s", strerror(errno));
+
+ return 0;
+
+fail:
+ server_done(s);
+ return r;
+}
+
+static int process_event(Server *s, struct epoll_event *ev) {
+ int r;
+
+ assert(s);
+
+ /* Yes, this is a bit ugly, we assume that that valid pointers
+ * are > SD_LISTEN_FDS_START+SERVER_FD_MAX. Which is certainly
+ * true on Linux (and probably most other OSes, too, since the
+ * first 4k usually are part of a seperate null pointer
+ * dereference page. */
+
+ if (PTR_TO_UINT(ev->data.ptr) >= SD_LISTEN_FDS_START &&
+ PTR_TO_UINT(ev->data.ptr) < SD_LISTEN_FDS_START+s->n_server_fd) {
+
+ if (ev->events != EPOLLIN) {
+ log_info("Got invalid event from epoll. (1)");
+ return -EIO;
+ }
+
+ if ((r = stream_new(s, PTR_TO_UINT(ev->data.ptr))) < 0) {
+ log_info("Failed to accept new connection: %s", strerror(-r));
+ return r;
+ }
+
+ } else {
+ usec_t timestamp;
+ Stream *stream = ev->data.ptr;
+
+ timestamp = now(CLOCK_REALTIME);
+
+ if (!(ev->events & EPOLLIN)) {
+ log_info("Got invalid event from epoll. (2)");
+ stream_free(stream);
+ return 0;
+ }
+
+ if ((r = stream_process(stream, timestamp)) <= 0) {
+
+ if (r < 0)
+ log_info("Got error on stream: %s", strerror(-r));
+
+ stream_free(stream);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ Server server;
+ int r = 3, n;
+
+ log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
+ log_parse_environment();
+
+ log_info("systemd-logger running as pid %llu", (unsigned long long) getpid());
+
+ if ((n = sd_listen_fds(true)) < 0) {
+ log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
+ return 1;
+ }
+
+ if (n <= 0 || n > SERVER_FD_MAX) {
+ log_error("No or too many file descriptors passed.");
+ return 2;
+ }
+
+ if (server_init(&server, (unsigned) n) < 0)
+ return 3;
+
+ for (;;) {
+ struct epoll_event event;
+ int k;
+
+ if ((k = epoll_wait(server.epoll_fd,
+ &event, 1,
+ server.n_streams <= 0 ? TIMEOUT : -1)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("epoll_wait() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (k <= 0)
+ break;
+
+ if ((k = process_event(&server, &event)) < 0)
+ goto fail;
+ }
+ r = 0;
+
+fail:
+ server_done(&server);
+
+ log_info("systemd-logger stopped as pid %llu", (unsigned long long) getpid());
+
+ return r;
+}
diff --git a/src/loopback-setup.c b/src/loopback-setup.c
new file mode 100644
index 0000000000..e37bf4190b
--- /dev/null
+++ b/src/loopback-setup.c
@@ -0,0 +1,276 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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"
+
+enum {
+ REQUEST_NONE = 0,
+ REQUEST_ADDRESS_IPV4 = 1,
+ REQUEST_ADDRESS_IPV6 = 2,
+ REQUEST_FLAGS = 4,
+ REQUEST_ALL = 7
+};
+
+#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;
+
+ if ((l = sendto(fd, buf, buf_len, flags, sa, sa_len)) >= 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;
+
+ if ((l = recvfrom(fd, buf, buf_len, flags, sa, sa_len)) >= 0)
+ return l;
+
+ if (errno != EINTR)
+ return -errno;
+ }
+}
+
+static int add_adresses(int fd, int if_loopback) {
+ 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 = REQUEST_ADDRESS_IPV4;
+
+ 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;
+
+ if ((r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &ipv4_address, sizeof(ipv4_address))) < 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;
+
+ request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+ request.header.nlmsg_seq = REQUEST_ADDRESS_IPV6;
+
+ ifaddrmsg->ifa_family = AF_INET6;
+ ifaddrmsg->ifa_prefixlen = 128;
+
+ if ((r = add_rtattr(&request.header, sizeof(request), IFA_LOCAL, &in6addr_loopback, sizeof(in6addr_loopback))) < 0)
+ return r;
+
+ if (sendto_loop(fd, &request, request.header.nlmsg_len, 0, &sa.sa, sizeof(sa)) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int start_interface(int fd, int if_loopback) {
+ 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 = REQUEST_FLAGS;
+
+ 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;
+
+ return 0;
+}
+
+static int read_response(int fd) {
+ 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;
+
+ if ((l = recvfrom_loop(fd, &response, sizeof(response), 0, &sa.sa, &sa_len)) < 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 >= REQUEST_ALL)
+ 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;
+}
+
+int loopback_setup(void) {
+ int r, if_loopback;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_nl nl;
+ struct sockaddr_storage storage;
+ } sa;
+ int requests = REQUEST_NONE;
+
+ int fd;
+
+ errno = 0;
+ if ((if_loopback = (int) if_nametoindex("lo")) <= 0)
+ return errno ? -errno : -ENODEV;
+
+ if ((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0)
+ return -errno;
+
+ zero(sa);
+ sa.nl.nl_family = AF_NETLINK;
+
+ if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if ((r = add_adresses(fd, if_loopback)) < 0)
+ goto finish;
+
+ if ((r = start_interface(fd, if_loopback)) < 0)
+ goto finish;
+
+ do {
+ if ((r = read_response(fd)) < 0)
+ goto finish;
+
+ requests |= r;
+
+ } while (requests != REQUEST_ALL);
+
+ r = 0;
+
+finish:
+ if (r < 0)
+ log_error("Failed to configure loopback device: %s", strerror(-r));
+
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ return r;
+}
diff --git a/src/loopback-setup.h b/src/loopback-setup.h
new file mode 100644
index 0000000000..b4614fa5bd
--- /dev/null
+++ b/src/loopback-setup.h
@@ -0,0 +1,27 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooloopbacksetuphfoo
+#define fooloopbacksetuphfoo
+
+/***
+ 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 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/>.
+***/
+
+int loopback_setup(void);
+
+#endif
diff --git a/src/macro.h b/src/macro.h
new file mode 100644
index 0000000000..622c08eeda
--- /dev/null
+++ b/src/macro.h
@@ -0,0 +1,130 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foomacrohfoo
+#define foomacrohfoo
+
+/***
+ 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 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 <assert.h>
+#include <sys/types.h>
+
+#define _printf_attr(a,b) __attribute__ ((format (printf, a, b)))
+#define _sentinel __attribute__ ((sentinel))
+#define _noreturn __attribute__((noreturn))
+#define _unused __attribute__ ((unused))
+#define _destructor __attribute__ ((destructor))
+#define _pure __attribute__ ((pure))
+#define _const __attribute__ ((const))
+#define _deprecated __attribute__ ((deprecated))
+#define _packed __attribute__ ((packed))
+#define _malloc __attribute__ ((malloc))
+#define _weak __attribute__ ((weak))
+#define _likely(x) (__builtin_expect(!!(x),1))
+#define _unlikely(x) (__builtin_expect(!!(x),0))
+
+/* Rounds up */
+static inline size_t ALIGN(size_t l) {
+ return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1));
+}
+
+#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+#define MAX(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+
+#define MIN(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+
+#define CLAMP(x, low, high) \
+ __extension__ ({ \
+ typeof(x) _x = (x); \
+ typeof(low) _low = (low); \
+ typeof(high) _high = (high); \
+ ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \
+ })
+
+#define assert_se(expr) \
+ do { \
+ if (_unlikely(!(expr))) \
+ log_assert(__FILE__, __LINE__, __PRETTY_FUNCTION__, \
+ "Assertion '%s' failed at %s:%u, function %s(). Aborting.", \
+ #expr , __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false) \
+
+/* We override the glibc assert() here. */
+#undef assert
+#ifdef NDEBUG
+#define assert(expr) do {} while(false)
+#else
+#define assert(expr) assert_se(expr)
+#endif
+
+#define assert_not_reached(t) \
+ do { \
+ log_assert(__FILE__, __LINE__, __PRETTY_FUNCTION__, \
+ "Code should not be reached '%s' at %s:%u, function %s(). Aborting.", \
+ t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false)
+
+#define assert_cc(expr) \
+ do { \
+ switch (0) { \
+ case 0: \
+ case !!(expr): \
+ ; \
+ } \
+ } while (false)
+
+#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
+#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define INT_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define TO_INT32(p) ((int32_t) ((intptr_t) (p)))
+#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define memzero(x,l) (memset((x), 0, (l)))
+#define zero(x) (memzero(&(x), sizeof(x)))
+
+#define char_array_0(x) x[sizeof(x)-1] = 0;
+
+#define IOVEC_SET_STRING(i, s) \
+ do { \
+ struct iovec *_i = &(i); \
+ char *_s = (char *)(s); \
+ _i->iov_base = _s; \
+ _i->iov_len = strlen(_s); \
+ } while(false);
+
+#include "log.h"
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000000..bba2975e46
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,787 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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/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 "manager.h"
+#include "log.h"
+#include "mount-setup.h"
+#include "hostname-setup.h"
+#include "loopback-setup.h"
+#include "load-fragment.h"
+#include "fdset.h"
+
+static enum {
+ ACTION_RUN,
+ ACTION_HELP,
+ ACTION_TEST,
+ ACTION_DUMP_CONFIGURATION_ITEMS
+} action = ACTION_RUN;
+
+static char *default_unit = NULL;
+static ManagerRunningAs running_as = _MANAGER_RUNNING_AS_INVALID;
+
+static bool dump_core = true;
+static bool crash_shell = false;
+static int crash_chvt = -1;
+static bool confirm_spawn = false;
+static FILE* serialization = NULL;
+
+_noreturn static void freeze(void) {
+ for (;;)
+ pause();
+}
+
+static void nop_handler(int sig) {
+}
+
+_noreturn static void crash(int sig) {
+
+ if (!dump_core)
+ log_error("Caught <%s>, not dumping core.", strsignal(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", strsignal(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 {
+ int status, r;
+
+ /* Order things nicely. */
+ if ((r = waitpid(pid, &status, 0)) < 0)
+ log_error("Caught <%s>, waitpid() failed: %s", strsignal(sig), strerror(errno));
+ else if (!WCOREDUMP(status))
+ log_error("Caught <%s>, core dump failed.", strsignal(sig));
+ else
+ log_error("Caught <%s>, dumped core as pid %llu.", strsignal(sig), (unsigned long long) pid);
+ }
+ }
+
+ if (crash_chvt)
+ chvt(crash_chvt);
+
+ if (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);
+
+ if ((pid = fork()) < 0)
+ log_error("Failed to fork off crash shell: %s", strerror(errno));
+ else if (pid == 0) {
+ int fd, r;
+
+ if ((fd = acquire_terminal("/dev/console", false, true)) < 0) {
+ log_error("Failed to acquire terminal: %s", strerror(-fd));
+ _exit(1);
+ }
+
+ if ((r = make_stdio(fd)) < 0) {
+ log_error("Failed to duplicate terminal fd: %s", strerror(-r));
+ _exit(1);
+ }
+
+ execl("/bin/sh", "/bin/sh", NULL);
+
+ log_error("execl() failed: %s", strerror(errno));
+ _exit(1);
+ }
+
+ log_info("Successfully spawned crash shall as pid %llu.", (unsigned long 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;
+
+ assert_se(sigaction(SIGSEGV, &sa, NULL) == 0);
+ assert_se(sigaction(SIGILL, &sa, NULL) == 0);
+ assert_se(sigaction(SIGFPE, &sa, NULL) == 0);
+ assert_se(sigaction(SIGBUS, &sa, NULL) == 0);
+ assert_se(sigaction(SIGQUIT, &sa, NULL) == 0);
+ assert_se(sigaction(SIGABRT, &sa, NULL) == 0);
+}
+
+static int make_null_stdio(void) {
+ int null_fd, r;
+
+ if ((null_fd = open("/dev/null", O_RDWR)) < 0) {
+ log_error("Failed to open /dev/null: %m");
+ return -errno;
+ }
+
+ if ((r = make_stdio(null_fd)) < 0)
+ log_warning("Failed to dup2() device: %s", strerror(-r));
+
+ return r;
+}
+
+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;
+
+ if ((tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0) {
+ log_error("Failed to open /dev/console: %s", strerror(-tty_fd));
+ return -tty_fd;
+ }
+
+ if ((r = reset_terminal(tty_fd)) < 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);
+
+ if (!(c = strdup(u)))
+ return -ENOMEM;
+
+ free(default_unit);
+ default_unit = c;
+ return 0;
+}
+
+static int parse_proc_cmdline_word(const char *word) {
+
+ static const char * const rlmap[] = {
+ "single", SPECIAL_RUNLEVEL1_TARGET,
+ "-s", SPECIAL_RUNLEVEL1_TARGET,
+ "s", SPECIAL_RUNLEVEL1_TARGET,
+ "S", SPECIAL_RUNLEVEL1_TARGET,
+ "1", SPECIAL_RUNLEVEL1_TARGET,
+ "2", SPECIAL_RUNLEVEL2_TARGET,
+ "3", SPECIAL_RUNLEVEL3_TARGET,
+ "4", SPECIAL_RUNLEVEL4_TARGET,
+ "5", SPECIAL_RUNLEVEL5_TARGET
+ };
+
+ if (startswith(word, "systemd.default="))
+ 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.dump_core=")) {
+ int r;
+
+ if ((r = parse_boolean(word + 18)) < 0)
+ log_warning("Failed to parse dump core switch %s, Ignoring.", word + 18);
+ else
+ 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
+ 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
+ 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
+ crash_chvt = k;
+
+ } else if (startswith(word, "systemd.")) {
+
+ log_warning("Unknown kernel switch %s. Ignoring.", word);
+
+ log_info("Supported kernel switches:");
+ log_info("systemd.default=UNIT Default unit to start");
+ log_info("systemd.log_target=console|kmsg|syslog Log target");
+ log_info("systemd.log_level=LEVEL Log level");
+ log_info("systemd.dump_core=0|1 Dump core on crash");
+ log_info("systemd.crash_shell=0|1 On crash run shell");
+ log_info("systemd.crash_chvt=N Change to VT #N on crash");
+ log_info("systemd.confirm_spawn=0|1 Confirm every process spawn");
+
+ } else {
+ 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 parse_proc_cmdline(void) {
+ char *line;
+ int r;
+ char *w;
+ size_t l;
+ char *state;
+
+ if ((r = read_one_line_file("/proc/cmdline", &line)) < 0) {
+ log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(errno));
+ 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);
+ free(word);
+
+ if (r < 0)
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ free(line);
+ return r;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_LOG_LEVEL = 0x100,
+ ARG_LOG_TARGET,
+ ARG_DEFAULT,
+ ARG_RUNNING_AS,
+ ARG_TEST,
+ ARG_DUMP_CONFIGURATION_ITEMS,
+ ARG_CONFIRM_SPAWN,
+ ARG_DESERIALIZE
+ };
+
+ static const struct option options[] = {
+ { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
+ { "log-target", required_argument, NULL, ARG_LOG_TARGET },
+ { "default", required_argument, NULL, ARG_DEFAULT },
+ { "running-as", required_argument, NULL, ARG_RUNNING_AS },
+ { "test", no_argument, NULL, ARG_TEST },
+ { "help", no_argument, NULL, 'h' },
+ { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS },
+ { "confirm-spawn", no_argument, NULL, ARG_CONFIRM_SPAWN },
+ { "deserialize", required_argument, NULL, ARG_DESERIALIZE },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int c, r;
+
+ assert(argc >= 1);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", 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_DEFAULT:
+
+ if ((r = set_default_unit(optarg)) < 0) {
+ log_error("Failed to set default unit %s: %s", optarg, strerror(-r));
+ return r;
+ }
+
+ break;
+
+ case ARG_RUNNING_AS: {
+ ManagerRunningAs as;
+
+ if ((as = manager_running_as_from_string(optarg)) < 0) {
+ log_error("Failed to parse running as value %s", optarg);
+ return -EINVAL;
+ }
+
+ running_as = as;
+ break;
+ }
+
+ case ARG_TEST:
+ action = ACTION_TEST;
+ break;
+
+ case ARG_DUMP_CONFIGURATION_ITEMS:
+ action = ACTION_DUMP_CONFIGURATION_ITEMS;
+ break;
+
+ case ARG_CONFIRM_SPAWN:
+ confirm_spawn = true;
+ 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 'h':
+ action = ACTION_HELP;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ log_error("Unknown option code %c", c);
+ return -EINVAL;
+ }
+
+ /* PID 1 will get the kernel arguments as parameters, which we
+ * ignore and unconditionally read from
+ * /proc/cmdline. However, we need to ignore those arguments
+ * here. */
+ if (running_as != MANAGER_INIT && optind < argc) {
+ log_error("Excess arguments.");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int help(void) {
+
+ printf("%s [options]\n\n"
+ " -h --help Show this help\n"
+ " --default=UNIT Set default unit\n"
+ " --log-level=LEVEL Set log level\n"
+ " --log-target=TARGET Set log target (console, syslog, kmsg, syslog-or-kmsg)\n"
+ " --running-as=AS Set running as (init, system, session)\n"
+ " --test Determine startup sequence, dump it and exit\n"
+ " --dump-configuration-items Dump understood unit configuration items\n"
+ " --confirm-spawn Ask for confirmation when spawning processes\n",
+ __progname);
+
+ return 0;
+}
+
+static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds) {
+ FILE *f = NULL;
+ FDSet *fds = NULL;
+ int r;
+
+ assert(m);
+ assert(_f);
+ assert(_fds);
+
+ if ((r = manager_open_serialization(&f)) < 0) {
+ log_error("Failed to create serialization faile: %s", strerror(-r));
+ goto fail;
+ }
+
+ if (!(fds = fdset_new())) {
+ r = -ENOMEM;
+ log_error("Failed to allocate fd set: %s", strerror(-r));
+ goto fail;
+ }
+
+ if ((r = manager_serialize(m, f, fds)) < 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;
+ }
+
+ if ((r = fd_cloexec(fileno(f), false)) < 0) {
+ log_error("Failed to disable O_CLOEXEC for serialization: %s", strerror(-r));
+ goto fail;
+ }
+
+ if ((r = fdset_cloexec(fds, false)) < 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;
+}
+
+int main(int argc, char *argv[]) {
+ Manager *m = NULL;
+ Unit *target = NULL;
+ Job *job = NULL;
+ int r, retval = 1;
+ FDSet *fds = NULL;
+ bool reexecute = false;
+
+ if (getpid() == 1) {
+ running_as = MANAGER_INIT;
+ log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
+ } else
+ running_as = MANAGER_SESSION;
+
+ if (set_default_unit(SPECIAL_DEFAULT_TARGET) < 0)
+ goto finish;
+
+ /* Mount /proc, /sys and friends, so that /proc/cmdline and
+ * /proc/$PID/fd is available. */
+ if (mount_setup() < 0)
+ goto finish;
+
+ /* Reset all signal handlers. */
+ assert_se(reset_all_signal_handlers() == 0);
+
+ /* If we are init, we can block sigkill. Yay. */
+ ignore_signal(SIGKILL);
+ ignore_signal(SIGPIPE);
+
+ if (running_as != MANAGER_SESSION)
+ if (parse_proc_cmdline() < 0)
+ goto finish;
+
+ log_parse_environment();
+
+ if (parse_argv(argc, argv) < 0)
+ goto finish;
+
+ if (action == ACTION_HELP) {
+ retval = help();
+ goto finish;
+ } else if (action == ACTION_DUMP_CONFIGURATION_ITEMS) {
+ unit_dump_config_items(stdout);
+ retval = 0;
+ goto finish;
+ }
+
+ assert_se(action == ACTION_RUN || action == ACTION_TEST);
+
+ /* Remember open file descriptors for later deserialization */
+ if (serialization) {
+ if ((r = fdset_new_fill(&fds)) < 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",
+ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ running_as == MANAGER_INIT);
+
+ /* Move out of the way, so that we won't block unmounts */
+ assert_se(chdir("/") == 0);
+
+ if (running_as != MANAGER_SESSION) {
+ /* 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 (running_as != MANAGER_SESSION && action == ACTION_RUN) {
+ console_setup(getpid() == 1 && !serialization);
+ make_null_stdio();
+ }
+
+ /* 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();
+
+ log_debug("systemd running in %s mode.", manager_running_as_to_string(running_as));
+
+ if (running_as == MANAGER_INIT) {
+ hostname_setup();
+ loopback_setup();
+ }
+
+ if ((r = manager_new(running_as, confirm_spawn, &m)) < 0) {
+ log_error("Failed to allocate manager object: %s", strerror(-r));
+ goto finish;
+ }
+
+ if ((r = manager_startup(m, serialization, fds)) < 0)
+ log_error("Failed to fully start up daemon: %s", strerror(-r));
+
+ if (fds) {
+ /* This will close all file descriptors that were opened, but
+ * not claimed by any unit. */
+
+ fdset_free(fds);
+ fds = NULL;
+ }
+
+ if (serialization) {
+ fclose(serialization);
+ serialization = NULL;
+ } else {
+ log_debug("Activating default unit: %s", default_unit);
+
+ if ((r = manager_load_unit(m, default_unit, NULL, &target)) < 0) {
+ log_error("Failed to load default target: %s", strerror(-r));
+
+ log_info("Trying to load rescue target...");
+ if ((r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &target)) < 0) {
+ log_error("Failed to load rescue target: %s", strerror(-r));
+ goto finish;
+ }
+ }
+
+ if (action == ACTION_TEST) {
+ printf("-> By units:\n");
+ manager_dump_units(m, stdout, "\t");
+ }
+
+ if ((r = manager_add_job(m, JOB_START, target, JOB_REPLACE, false, &job)) < 0) {
+ log_error("Failed to start default target: %s", strerror(-r));
+ goto finish;
+ }
+
+ if (action == ACTION_TEST) {
+ printf("-> By jobs:\n");
+ manager_dump_jobs(m, stdout, "\t");
+ retval = 0;
+ goto finish;
+ }
+ }
+
+ for (;;) {
+ if ((r = manager_loop(m)) < 0) {
+ log_error("Failed to run mainloop: %s", strerror(-r));
+ goto finish;
+ }
+
+ switch (m->exit_code) {
+
+ case MANAGER_EXIT:
+ retval = 0;
+ log_debug("Exit.");
+ goto finish;
+
+ case MANAGER_RELOAD:
+ if ((r = manager_reload(m)) < 0)
+ log_error("Failed to reload: %s", strerror(-r));
+ break;
+
+ case MANAGER_REEXECUTE:
+ if (prepare_reexecute(m, &serialization, &fds) < 0)
+ goto finish;
+
+ reexecute = true;
+ log_debug("Reexecuting.");
+ goto finish;
+
+ default:
+ assert_not_reached("Unknown exit code.");
+ }
+ }
+
+finish:
+ if (m)
+ manager_free(m);
+
+ free(default_unit);
+
+ dbus_shutdown();
+
+ if (reexecute) {
+ const char *args[11];
+ unsigned i = 0;
+ char sfd[16];
+
+ assert(serialization);
+ assert(fds);
+
+ args[i++] = SYSTEMD_BINARY_PATH;
+
+ args[i++] = "--log-level";
+ args[i++] = log_level_to_string(log_get_max_level());
+
+ args[i++] = "--log-target";
+ args[i++] = log_target_to_string(log_get_target());
+
+ args[i++] = "--running-as";
+ args[i++] = manager_running_as_to_string(running_as);
+
+ snprintf(sfd, sizeof(sfd), "%i", fileno(serialization));
+ char_array_0(sfd);
+
+ args[i++] = "--deserialize";
+ args[i++] = sfd;
+
+ if (confirm_spawn)
+ args[i++] = "--confirm-spawn";
+
+ args[i++] = NULL;
+
+ assert(i <= ELEMENTSOF(args));
+
+ execv(args[0], (char* const*) args);
+
+ log_error("Failed to reexecute: %m");
+ }
+
+ if (serialization)
+ fclose(serialization);
+
+ if (fds)
+ fdset_free(fds);
+
+ if (getpid() == 1)
+ freeze();
+
+ return retval;
+}
diff --git a/src/manager.c b/src/manager.c
new file mode 100644
index 0000000000..688d9fa65b
--- /dev/null
+++ b/src/manager.c
@@ -0,0 +1,2291 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 <utmpx.h>
+#include <sys/poll.h>
+#include <sys/reboot.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+#include <libcgroup.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "manager.h"
+#include "hashmap.h"
+#include "macro.h"
+#include "strv.h"
+#include "log.h"
+#include "util.h"
+#include "ratelimit.h"
+#include "cgroup.h"
+#include "mount-setup.h"
+#include "utmp-wtmp.h"
+#include "unit-name.h"
+#include "dbus-unit.h"
+#include "dbus-job.h"
+#include "missing.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)
+
+static int enable_special_signals(Manager *m) {
+ char fd;
+
+ assert(m);
+
+ /* Enable that we get SIGINT on control-alt-del */
+ if (reboot(RB_DISABLE_CAD) < 0)
+ log_warning("Failed to enable ctrl-alt-del handling: %m");
+
+ if ((fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY)) < 0)
+ 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);
+ assert_se(sigaddset(&mask, SIGCHLD) == 0);
+ assert_se(sigaddset(&mask, SIGTERM) == 0);
+ assert_se(sigaddset(&mask, SIGHUP) == 0);
+ assert_se(sigaddset(&mask, SIGUSR1) == 0);
+ assert_se(sigaddset(&mask, SIGUSR2) == 0);
+ assert_se(sigaddset(&mask, SIGINT) == 0); /* Kernel sends us this on control-alt-del */
+ assert_se(sigaddset(&mask, SIGWINCH) == 0); /* Kernel sends us this on kbrequest (alt-arrowup) */
+ assert_se(sigaddset(&mask, SIGPWR) == 0); /* Some kernel drivers and upsd send us this on power failure */
+ 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 == MANAGER_INIT)
+ return enable_special_signals(m);
+
+ return 0;
+}
+
+static char** session_dirs(void) {
+ const char *home, *e;
+ char *config_home = NULL, *data_home = NULL;
+ char **config_dirs = NULL, **data_dirs = NULL;
+ char **r = NULL, **t;
+
+ /* Implement the mechanisms defined in
+ *
+ * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+ *
+ * We look in both the config and the data dirs because we
+ * want to encourage that distributors ship their unit files
+ * as data, and allow overriding as configuration.
+ */
+
+ home = getenv("HOME");
+
+ if ((e = getenv("XDG_CONFIG_HOME"))) {
+ if (asprintf(&config_home, "%s/systemd/session", e) < 0)
+ goto fail;
+
+ } else if (home) {
+ if (asprintf(&config_home, "%s/.config/systemd/session", home) < 0)
+ goto fail;
+ }
+
+ if ((e = getenv("XDG_CONFIG_DIRS")))
+ if (!(config_dirs = strv_split(e, ":")))
+ goto fail;
+
+ /* We don't treat /etc/xdg/systemd here as the spec
+ * suggests because we assume that that is a link to
+ * /etc/systemd/ anyway. */
+
+ if ((e = getenv("XDG_DATA_HOME"))) {
+ if (asprintf(&data_home, "%s/systemd/session", e) < 0)
+ goto fail;
+
+ } else if (home) {
+ if (asprintf(&data_home, "%s/.local/share/systemd/session", home) < 0)
+ goto fail;
+ }
+
+ if ((e = getenv("XDG_DATA_DIRS")))
+ data_dirs = strv_split(e, ":");
+ else
+ data_dirs = strv_new("/usr/local/share", "/usr/share", NULL);
+
+ if (!data_dirs)
+ goto fail;
+
+ /* Now merge everything we found. */
+ if (config_home) {
+ if (!(t = strv_append(r, config_home)))
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (!(t = strv_merge_concat(r, config_dirs, "/systemd/session")))
+ goto finish;
+ strv_free(r);
+ r = t;
+
+ if (!(t = strv_append(r, SESSION_CONFIG_UNIT_PATH)))
+ goto fail;
+ strv_free(r);
+ r = t;
+
+ if (data_home) {
+ if (!(t = strv_append(r, data_home)))
+ goto fail;
+ strv_free(r);
+ r = t;
+ }
+
+ if (!(t = strv_merge_concat(r, data_dirs, "/systemd/session")))
+ goto fail;
+ strv_free(r);
+ r = t;
+
+ if (!(t = strv_append(r, SESSION_DATA_UNIT_PATH)))
+ goto fail;
+ strv_free(r);
+ r = t;
+
+ if (!strv_path_make_absolute_cwd(r))
+ goto fail;
+
+finish:
+ free(config_home);
+ strv_free(config_dirs);
+ free(data_home);
+ strv_free(data_dirs);
+
+ return r;
+
+fail:
+ strv_free(r);
+ r = NULL;
+ goto finish;
+}
+
+static int manager_find_paths(Manager *m) {
+ const char *e;
+ char *t;
+
+ assert(m);
+
+ /* First priority is whatever has been passed to us via env
+ * vars */
+ if ((e = getenv("SYSTEMD_UNIT_PATH")))
+ if (!(m->unit_path = split_path_and_make_absolute(e)))
+ return -ENOMEM;
+
+ if (strv_isempty(m->unit_path)) {
+
+ /* Nothing is set, so let's figure something out. */
+ strv_free(m->unit_path);
+
+ if (m->running_as == MANAGER_SESSION) {
+ if (!(m->unit_path = session_dirs()))
+ return -ENOMEM;
+ } else
+ if (!(m->unit_path = strv_new(
+ SYSTEM_CONFIG_UNIT_PATH, /* /etc/systemd/system/ */
+ SYSTEM_DATA_UNIT_PATH, /* /lib/systemd/system/ */
+ NULL)))
+ return -ENOMEM;
+ }
+
+ if (m->running_as == MANAGER_INIT) {
+ /* /etc/init.d/ compatibility does not matter to users */
+
+ if ((e = getenv("SYSTEMD_SYSVINIT_PATH")))
+ if (!(m->sysvinit_path = split_path_and_make_absolute(e)))
+ return -ENOMEM;
+
+ if (strv_isempty(m->sysvinit_path)) {
+ strv_free(m->sysvinit_path);
+
+ if (!(m->sysvinit_path = strv_new(
+ SYSTEM_SYSVINIT_PATH, /* /etc/init.d/ */
+ NULL)))
+ return -ENOMEM;
+ }
+
+ if ((e = getenv("SYSTEMD_SYSVRCND_PATH")))
+ if (!(m->sysvrcnd_path = split_path_and_make_absolute(e)))
+ return -ENOMEM;
+
+ if (strv_isempty(m->sysvrcnd_path)) {
+ strv_free(m->sysvrcnd_path);
+
+ if (!(m->sysvrcnd_path = strv_new(
+ SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */
+ NULL)))
+ return -ENOMEM;
+ }
+ }
+
+ strv_uniq(m->unit_path);
+ strv_uniq(m->sysvinit_path);
+ strv_uniq(m->sysvrcnd_path);
+
+ assert(!strv_isempty(m->unit_path));
+ if (!(t = strv_join(m->unit_path, "\n\t")))
+ return -ENOMEM;
+ log_debug("Looking for unit files in:\n\t%s", t);
+ free(t);
+
+ if (!strv_isempty(m->sysvinit_path)) {
+
+ if (!(t = strv_join(m->sysvinit_path, "\n\t")))
+ return -ENOMEM;
+
+ log_debug("Looking for SysV init scripts in:\n\t%s", t);
+ free(t);
+ } else
+ log_debug("Ignoring SysV init scripts.");
+
+ if (!strv_isempty(m->sysvrcnd_path)) {
+
+ if (!(t = strv_join(m->sysvrcnd_path, "\n\t")))
+ return -ENOMEM;
+
+ log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
+ free(t);
+ } else
+ log_debug("Ignoring SysV rcN.d links.");
+
+ return 0;
+}
+
+int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
+ Manager *m;
+ int r = -ENOMEM;
+
+ assert(_m);
+ assert(running_as >= 0);
+ assert(running_as < _MANAGER_RUNNING_AS_MAX);
+
+ if (!(m = new0(Manager, 1)))
+ return -ENOMEM;
+
+ m->boot_timestamp = now(CLOCK_REALTIME);
+
+ m->running_as = running_as;
+ m->confirm_spawn = confirm_spawn;
+ m->name_data_slot = -1;
+ m->exit_code = _MANAGER_EXIT_CODE_INVALID;
+
+ m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = -1;
+ m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
+
+ if (!(m->environment = strv_copy(environ)))
+ 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->transaction_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_find_paths(m)) < 0)
+ goto fail;
+
+ if ((r = manager_setup_signals(m)) < 0)
+ goto fail;
+
+ if ((r = manager_setup_cgroup(m)) < 0)
+ goto fail;
+
+ /* Try to connect to the busses, if possible. */
+ if ((r = bus_init_system(m)) < 0 ||
+ (r = bus_init_api(m)) < 0)
+ goto fail;
+
+ *_m = m;
+ return 0;
+
+fail:
+ manager_free(m);
+ return r;
+}
+
+static unsigned manager_dispatch_cleanup_queue(Manager *m) {
+ Meta *meta;
+ unsigned n = 0;
+
+ assert(m);
+
+ while ((meta = m->cleanup_queue)) {
+ assert(meta->in_cleanup_queue);
+
+ unit_free(UNIT(meta));
+ n++;
+ }
+
+ return n;
+}
+
+enum {
+ GC_OFFSET_IN_PATH, /* This one is on the path we were travelling */
+ 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->meta.gc_marker == gc_marker + GC_OFFSET_GOOD ||
+ u->meta.gc_marker == gc_marker + GC_OFFSET_BAD ||
+ u->meta.gc_marker == gc_marker + GC_OFFSET_IN_PATH)
+ return;
+
+ if (u->meta.in_cleanup_queue)
+ goto bad;
+
+ if (unit_check_gc(u))
+ goto good;
+
+ u->meta.gc_marker = gc_marker + GC_OFFSET_IN_PATH;
+
+ is_bad = true;
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REFERENCED_BY], i) {
+ unit_gc_sweep(other, gc_marker);
+
+ if (other->meta.gc_marker == gc_marker + GC_OFFSET_GOOD)
+ goto good;
+
+ if (other->meta.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->meta.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->meta.gc_marker = gc_marker + GC_OFFSET_BAD;
+ unit_add_to_cleanup_queue(u);
+ return;
+
+good:
+ u->meta.gc_marker = gc_marker + GC_OFFSET_GOOD;
+}
+
+static unsigned manager_dispatch_gc_queue(Manager *m) {
+ Meta *meta;
+ 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 ((meta = m->gc_queue)) {
+ assert(meta->in_gc_queue);
+
+ unit_gc_sweep(UNIT(meta), gc_marker);
+
+ LIST_REMOVE(Meta, gc_queue, m->gc_queue, meta);
+ meta->in_gc_queue = false;
+
+ n++;
+
+ if (meta->gc_marker == gc_marker + GC_OFFSET_BAD ||
+ meta->gc_marker == gc_marker + GC_OFFSET_UNSURE) {
+ log_debug("Collecting %s", meta->id);
+ meta->gc_marker = gc_marker + GC_OFFSET_BAD;
+ unit_add_to_cleanup_queue(UNIT(meta));
+ }
+ }
+
+ m->n_in_gc_queue = 0;
+ m->gc_queue_timestamp = 0;
+
+ return n;
+}
+
+static void manager_clear_jobs_and_units(Manager *m) {
+ Job *j;
+ Unit *u;
+
+ assert(m);
+
+ while ((j = hashmap_first(m->transaction_jobs)))
+ job_free(j);
+
+ while ((u = hashmap_first(m->units)))
+ unit_free(u);
+}
+
+void manager_free(Manager *m) {
+ UnitType c;
+
+ assert(m);
+
+ manager_dispatch_cleanup_queue(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);
+
+ bus_done_api(m);
+ bus_done_system(m);
+
+ hashmap_free(m->units);
+ hashmap_free(m->jobs);
+ hashmap_free(m->transaction_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);
+
+ strv_free(m->unit_path);
+ strv_free(m->sysvinit_path);
+ strv_free(m->sysvrcnd_path);
+ strv_free(m->environment);
+
+ free(m->cgroup_controller);
+ free(m->cgroup_hierarchy);
+
+ hashmap_free(m->cgroup_bondings);
+
+ 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->meta.id != k)
+ continue;
+
+ if (UNIT_VTABLE(u)->coldplug)
+ if ((q = UNIT_VTABLE(u)->coldplug(u)) < 0)
+ r = q;
+ }
+
+ return r;
+}
+
+int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
+ int r, q;
+
+ assert(m);
+
+ /* First, enumerate what we can from all config files */
+ r = manager_enumerate(m);
+
+ /* Second, deserialize if there is something to deserialize */
+ if (serialization)
+ if ((q = manager_deserialize(m, serialization, fds)) < 0)
+ r = q;
+
+ /* Third, fire things up! */
+ if ((q = manager_coldplug(m)) < 0)
+ r = q;
+
+ /* Now that the initial devices are available, let's see if we
+ * can write the utmp file */
+ manager_write_utmp_reboot(m);
+
+ return r;
+}
+
+static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) {
+ assert(m);
+ assert(j);
+
+ /* Deletes one job from the transaction */
+
+ manager_transaction_unlink_job(m, j, delete_dependencies);
+
+ if (!j->installed)
+ job_free(j);
+}
+
+static void transaction_delete_unit(Manager *m, Unit *u) {
+ Job *j;
+
+ /* Deletes all jobs associated with a certain unit from the
+ * transaction */
+
+ while ((j = hashmap_get(m->transaction_jobs, u)))
+ transaction_delete_job(m, j, true);
+}
+
+static void transaction_clean_dependencies(Manager *m) {
+ Iterator i;
+ Job *j;
+
+ assert(m);
+
+ /* Drops all dependencies of all installed jobs */
+
+ HASHMAP_FOREACH(j, m->jobs, i) {
+ while (j->subject_list)
+ job_dependency_free(j->subject_list);
+ while (j->object_list)
+ job_dependency_free(j->object_list);
+ }
+
+ assert(!m->transaction_anchor);
+}
+
+static void transaction_abort(Manager *m) {
+ Job *j;
+
+ assert(m);
+
+ while ((j = hashmap_first(m->transaction_jobs)))
+ if (j->installed)
+ transaction_delete_job(m, j, true);
+ else
+ job_free(j);
+
+ assert(hashmap_isempty(m->transaction_jobs));
+
+ transaction_clean_dependencies(m);
+}
+
+static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) {
+ JobDependency *l;
+
+ assert(m);
+
+ /* 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. */
+
+ if (j)
+ l = j->subject_list;
+ else
+ l = m->transaction_anchor;
+
+ LIST_FOREACH(subject, l, l) {
+
+ /* This link does not matter */
+ if (!l->matters)
+ continue;
+
+ /* This unit has already been marked */
+ if (l->object->generation == generation)
+ continue;
+
+ l->object->matters_to_anchor = true;
+ l->object->generation = generation;
+
+ transaction_find_jobs_that_matter_to_anchor(m, l->object, generation);
+ }
+}
+
+static void transaction_merge_and_delete_job(Manager *m, 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 j. */
+
+ 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(m, other, true);
+}
+
+static int delete_one_unmergeable_job(Manager *m, Job *j) {
+ Job *k;
+
+ assert(j);
+
+ /* Tries to delete one item in the linked list
+ * j->transaction_next->transaction_next->... that conflicts
+ * whith 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)
+ 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("Trying to fix job merging by deleting job %s/%s", d->unit->meta.id, job_type_to_string(d->type));
+ transaction_delete_job(m, d, true);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int transaction_merge_jobs(Manager *m) {
+ Job *j;
+ Iterator i;
+ int r;
+
+ assert(m);
+
+ /* First step, check whether any of the jobs for one specific
+ * task conflict. If so, try to drop one of them. */
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ JobType t;
+ Job *k;
+
+ t = j->type;
+ LIST_FOREACH(transaction, k, j->transaction_next) {
+ if ((r = job_type_merge(&t, k->type)) >= 0)
+ continue;
+
+ /* OK, we could not merge all jobs for this
+ * action. Let's see if we can get rid of one
+ * of them */
+
+ if ((r = delete_one_unmergeable_job(m, j)) >= 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 */
+ return r;
+ }
+ }
+
+ /* Second step, merge the jobs. */
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ JobType t = j->type;
+ Job *k;
+
+ /* Merge all transactions */
+ LIST_FOREACH(transaction, k, j->transaction_next)
+ assert_se(job_type_merge(&t, k->type) == 0);
+
+ /* If an active job is mergeable, merge it too */
+ if (j->unit->meta.job)
+ job_type_merge(&t, j->unit->meta.job->type); /* Might fail. Which is OK */
+
+ while ((k = j->transaction_next)) {
+ if (j->installed) {
+ transaction_merge_and_delete_job(m, k, j, t);
+ j = k;
+ } else
+ transaction_merge_and_delete_job(m, j, k, t);
+ }
+
+ assert(!j->transaction_next);
+ assert(!j->transaction_prev);
+ }
+
+ return 0;
+}
+
+static void transaction_drop_redundant(Manager *m) {
+ bool again;
+
+ assert(m);
+
+ /* Goes through the transaction and removes all jobs that are
+ * a noop */
+
+ do {
+ Job *j;
+ Iterator i;
+
+ again = false;
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ bool changes_something = false;
+ Job *k;
+
+ LIST_FOREACH(transaction, k, j) {
+
+ if (!job_is_anchor(k) &&
+ job_type_is_redundant(k->type, unit_active_state(k->unit)))
+ continue;
+
+ changes_something = true;
+ break;
+ }
+
+ if (changes_something)
+ continue;
+
+ log_debug("Found redundant job %s/%s, dropping.", j->unit->meta.id, job_type_to_string(j->type));
+ transaction_delete_job(m, j, false);
+ again = true;
+ break;
+ }
+
+ } while (again);
+}
+
+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(Manager *m, Job *j, Job *from, unsigned generation) {
+ Iterator i;
+ Unit *u;
+ int r;
+
+ assert(m);
+ 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. */
+
+ /* Did we find a cycle? */
+ if (j->marker && j->generation == generation) {
+ Job *k;
+
+ /* So, 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_debug("Found ordering cycle on %s/%s", j->unit->meta.id, job_type_to_string(j->type));
+
+ for (k = from; k; k = (k->generation == generation ? k->marker : NULL)) {
+
+ log_debug("Walked on cycle path to %s/%s", k->unit->meta.id, job_type_to_string(k->type));
+
+ if (!k->installed &&
+ !unit_matters_to_anchor(k->unit, k)) {
+ /* Ok, we can drop this one, so let's
+ * do so. */
+ log_debug("Breaking order cycle by deleting job %s/%s", k->unit->meta.id, job_type_to_string(k->type));
+ transaction_delete_unit(m, k->unit);
+ return -EAGAIN;
+ }
+
+ /* Check if this in fact was the beginning of
+ * the cycle */
+ if (k == j)
+ break;
+ }
+
+ log_debug("Unable to break cycle");
+
+ 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 */
+ j->marker = from;
+ j->generation = generation;
+
+ /* We assume that the the dependencies are bidirectional, and
+ * hence can ignore UNIT_AFTER */
+ SET_FOREACH(u, j->unit->meta.dependencies[UNIT_BEFORE], i) {
+ Job *o;
+
+ /* Is there a job for this unit? */
+ if (!(o = hashmap_get(m->transaction_jobs, u)))
+
+ /* Ok, there is no job for this in the
+ * transaction, but maybe there is already one
+ * running? */
+ if (!(o = u->meta.job))
+ continue;
+
+ if ((r = transaction_verify_order_one(m, o, j, generation)) < 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(Manager *m, unsigned *generation) {
+ Job *j;
+ int r;
+ Iterator i;
+
+ assert(m);
+ assert(generation);
+
+ /* Check if the ordering graph is cyclic. If it is, try to fix
+ * that up by dropping one of the jobs. */
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i)
+ if ((r = transaction_verify_order_one(m, j, NULL, (*generation)++)) < 0)
+ return r;
+
+ return 0;
+}
+
+static void transaction_collect_garbage(Manager *m) {
+ bool again;
+
+ assert(m);
+
+ /* Drop jobs that are not required by any other job */
+
+ do {
+ Iterator i;
+ Job *j;
+
+ again = false;
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ if (j->object_list)
+ continue;
+
+ log_debug("Garbage collecting job %s/%s", j->unit->meta.id, job_type_to_string(j->type));
+ transaction_delete_job(m, j, true);
+ again = true;
+ break;
+ }
+
+ } while (again);
+}
+
+static int transaction_is_destructive(Manager *m) {
+ Iterator i;
+ Job *j;
+
+ assert(m);
+
+ /* Checks whether applying this transaction means that
+ * existing jobs would be replaced */
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+
+ /* Assume merged */
+ assert(!j->transaction_prev);
+ assert(!j->transaction_next);
+
+ if (j->unit->meta.job &&
+ j->unit->meta.job != j &&
+ !job_type_is_superset(j->type, j->unit->meta.job->type))
+ return -EEXIST;
+ }
+
+ return 0;
+}
+
+static void transaction_minimize_impact(Manager *m) {
+ bool again;
+ assert(m);
+
+ /* Drops all unnecessary jobs that reverse already active jobs
+ * or that stop a running service. */
+
+ do {
+ Job *j;
+ Iterator i;
+
+ again = false;
+
+ HASHMAP_FOREACH(j, m->transaction_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->meta.job && job_type_is_conflicting(j->type, j->unit->meta.job->state);
+
+ if (!stops_running_service && !changes_existing_job)
+ continue;
+
+ if (stops_running_service)
+ log_debug("%s/%s would stop a running service.", j->unit->meta.id, job_type_to_string(j->type));
+
+ if (changes_existing_job)
+ log_debug("%s/%s would change existing job.", j->unit->meta.id, job_type_to_string(j->type));
+
+ /* Ok, let's get rid of this */
+ log_debug("Deleting %s/%s to minimize impact.", j->unit->meta.id, job_type_to_string(j->type));
+
+ transaction_delete_job(m, j, true);
+ again = true;
+ break;
+ }
+
+ if (again)
+ break;
+ }
+
+ } while (again);
+}
+
+static int transaction_apply(Manager *m) {
+ Iterator i;
+ Job *j;
+ int r;
+
+ /* Moves the transaction jobs to the set of active jobs */
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ /* Assume merged */
+ assert(!j->transaction_prev);
+ assert(!j->transaction_next);
+
+ if (j->installed)
+ continue;
+
+ if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0)
+ goto rollback;
+ }
+
+ while ((j = hashmap_steal_first(m->transaction_jobs))) {
+ if (j->installed)
+ continue;
+
+ if (j->unit->meta.job)
+ job_free(j->unit->meta.job);
+
+ j->unit->meta.job = j;
+ j->installed = true;
+
+ /* We're fully installed. Now let's free data we don't
+ * need anymore. */
+
+ assert(!j->transaction_next);
+ assert(!j->transaction_prev);
+
+ job_add_to_run_queue(j);
+ job_add_to_dbus_queue(j);
+ }
+
+ /* As last step, kill all remaining job dependencies. */
+ transaction_clean_dependencies(m);
+
+ return 0;
+
+rollback:
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ if (j->installed)
+ continue;
+
+ hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
+ }
+
+ return r;
+}
+
+static int transaction_activate(Manager *m, JobMode mode) {
+ int r;
+ unsigned generation = 1;
+
+ assert(m);
+
+ /* This applies the changes recorded in transaction_jobs to
+ * the actual list of jobs, if possible. */
+
+ /* First step: figure out which jobs matter */
+ transaction_find_jobs_that_matter_to_anchor(m, NULL, 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. */
+ transaction_minimize_impact(m);
+
+ /* Third step: Drop redundant jobs */
+ transaction_drop_redundant(m);
+
+ for (;;) {
+ /* Fourth step: Let's remove unneeded jobs that might
+ * be lurking. */
+ transaction_collect_garbage(m);
+
+ /* Fifth step: verify order makes sense and correct
+ * cycles if necessary and possible */
+ if ((r = transaction_verify_order(m, &generation)) >= 0)
+ break;
+
+ if (r != -EAGAIN) {
+ log_debug("Requested transaction contains an unfixable cyclic ordering dependency: %s", strerror(-r));
+ goto rollback;
+ }
+
+ /* 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 */
+ if ((r = transaction_merge_jobs(m)) >= 0)
+ break;
+
+ if (r != -EAGAIN) {
+ log_debug("Requested transaction contains unmergable jobs: %s", strerror(-r));
+ goto rollback;
+ }
+
+ /* Seventh step: an entry got dropped, let's garbage
+ * collect its dependencies. */
+ transaction_collect_garbage(m);
+
+ /* 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(m);
+
+ /* Ninth step: check whether we can actually apply this */
+ if (mode == JOB_FAIL)
+ if ((r = transaction_is_destructive(m)) < 0) {
+ log_debug("Requested transaction contradicts existing jobs: %s", strerror(-r));
+ goto rollback;
+ }
+
+ /* Tenth step: apply changes */
+ if ((r = transaction_apply(m)) < 0) {
+ log_debug("Failed to apply transaction: %s", strerror(-r));
+ goto rollback;
+ }
+
+ assert(hashmap_isempty(m->transaction_jobs));
+ assert(!m->transaction_anchor);
+
+ return 0;
+
+rollback:
+ transaction_abort(m);
+ return r;
+}
+
+static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool override, bool *is_new) {
+ Job *j, *f;
+ int r;
+
+ assert(m);
+ assert(unit);
+
+ /* Looks for an axisting prospective job and returns that. If
+ * it doesn't exist it is created and added to the prospective
+ * jobs list. */
+
+ f = hashmap_get(m->transaction_jobs, unit);
+
+ LIST_FOREACH(transaction, j, f) {
+ assert(j->unit == unit);
+
+ if (j->type == type) {
+ if (is_new)
+ *is_new = false;
+ return j;
+ }
+ }
+
+ if (unit->meta.job && unit->meta.job->type == type)
+ j = unit->meta.job;
+ else if (!(j = job_new(m, type, unit)))
+ return NULL;
+
+ j->generation = 0;
+ j->marker = NULL;
+ j->matters_to_anchor = false;
+ j->override = override;
+
+ LIST_PREPEND(Job, transaction, f, j);
+
+ if ((r = hashmap_replace(m->transaction_jobs, unit, f)) < 0) {
+ job_free(j);
+ return NULL;
+ }
+
+ if (is_new)
+ *is_new = true;
+
+ log_debug("Added job %s/%s to transaction.", unit->meta.id, job_type_to_string(type));
+
+ return j;
+}
+
+void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) {
+ assert(m);
+ assert(j);
+
+ if (j->transaction_prev)
+ j->transaction_prev->transaction_next = j->transaction_next;
+ else if (j->transaction_next)
+ hashmap_replace(m->transaction_jobs, j->unit, j->transaction_next);
+ else
+ hashmap_remove_value(m->transaction_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->meta.id, job_type_to_string(other->type),
+ j->unit->meta.id, job_type_to_string(j->type));
+ transaction_delete_job(m, other, delete_dependencies);
+ }
+ }
+}
+
+static int transaction_add_job_and_dependencies(
+ Manager *m,
+ JobType type,
+ Unit *unit,
+ Job *by,
+ bool matters,
+ bool override,
+ Job **_ret) {
+ Job *ret;
+ Iterator i;
+ Unit *dep;
+ int r;
+ bool is_new;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(unit);
+
+ if (unit->meta.load_state != UNIT_LOADED)
+ return -EINVAL;
+
+ if (!unit_job_is_applicable(unit, type))
+ return -EBADR;
+
+ /* First add the job. */
+ if (!(ret = transaction_add_one_job(m, type, unit, override, &is_new)))
+ return -ENOMEM;
+
+ /* Then, add a link to the job. */
+ if (!job_dependency_new(by, ret, matters))
+ return -ENOMEM;
+
+ if (is_new) {
+ /* Finally, recursively add in all dependencies. */
+ if (type == JOB_START || type == JOB_RELOAD_OR_START) {
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUIRES], i)
+ if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, NULL)) < 0 && r != -EBADR)
+ goto fail;
+
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
+ if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, NULL)) < 0 && r != -EBADR)
+ log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->meta.id, strerror(-r));
+
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_WANTS], i)
+ if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, NULL)) < 0)
+ log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->meta.id, strerror(-r));
+
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUISITE], i)
+ if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, NULL)) < 0 && r != -EBADR)
+ goto fail;
+
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUISITE_OVERRIDABLE], i)
+ if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, NULL)) < 0 && r != -EBADR)
+ log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->meta.id, strerror(-r));
+
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_CONFLICTS], i)
+ if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, NULL)) < 0 && r != -EBADR)
+ goto fail;
+
+ } else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
+
+ SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUIRED_BY], i)
+ if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, NULL)) < 0 && r != -EBADR)
+ goto fail;
+ }
+
+ /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */
+ }
+
+ if (_ret)
+ *_ret = ret;
+
+ return 0;
+
+fail:
+ return r;
+}
+
+static int transaction_add_isolate_jobs(Manager *m) {
+ Iterator i;
+ Unit *u;
+ char *k;
+ int r;
+
+ assert(m);
+
+ HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+
+ /* ignore aliases */
+ if (u->meta.id != k)
+ continue;
+
+ if (UNIT_VTABLE(u)->no_isolate)
+ continue;
+
+ /* No need to stop inactive jobs */
+ if (unit_active_state(u) == UNIT_INACTIVE)
+ continue;
+
+ /* Is there already something listed for this? */
+ if (hashmap_get(m->transaction_jobs, u))
+ continue;
+
+ if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, NULL)) < 0)
+ log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->meta.id, strerror(-r));
+ }
+
+ return 0;
+}
+
+int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, Job **_ret) {
+ int r;
+ Job *ret;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(unit);
+ assert(mode < _JOB_MODE_MAX);
+
+ if (mode == JOB_ISOLATE && type != JOB_START)
+ return -EINVAL;
+
+ log_debug("Trying to enqueue job %s/%s", unit->meta.id, job_type_to_string(type));
+
+ if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, &ret)) < 0) {
+ transaction_abort(m);
+ return r;
+ }
+
+ if (mode == JOB_ISOLATE)
+ if ((r = transaction_add_isolate_jobs(m)) < 0) {
+ transaction_abort(m);
+ return r;
+ }
+
+ if ((r = transaction_activate(m, mode)) < 0)
+ return r;
+
+ log_debug("Enqueued job %s/%s as %u", unit->meta.id, job_type_to_string(type), (unsigned) ret->id);
+
+ if (_ret)
+ *_ret = ret;
+
+ return 0;
+}
+
+int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, Job **_ret) {
+ Unit *unit;
+ int r;
+
+ assert(m);
+ assert(type < _JOB_TYPE_MAX);
+ assert(name);
+ assert(mode < _JOB_MODE_MAX);
+
+ if ((r = manager_load_unit(m, name, NULL, &unit)) < 0)
+ return r;
+
+ return manager_add_job(m, type, unit, mode, override, _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) {
+ Meta *meta;
+ 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 ((meta = m->load_queue)) {
+ assert(meta->in_load_queue);
+
+ unit_load(UNIT(meta));
+ n++;
+ }
+
+ m->dispatching_load_queue = false;
+ return n;
+}
+
+int manager_load_unit_prepare(Manager *m, const char *name, const char *path, Unit **_ret) {
+ Unit *ret;
+ 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))
+ return -EINVAL;
+
+ if (!name)
+ name = file_name_from_path(path);
+
+ if (!unit_name_is_valid(name))
+ return -EINVAL;
+
+ if ((ret = manager_get_unit(m, name))) {
+ *_ret = ret;
+ return 1;
+ }
+
+ if (!(ret = unit_new(m)))
+ return -ENOMEM;
+
+ if (path)
+ if (!(ret->meta.fragment_path = strdup(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, Unit **_ret) {
+ int r;
+
+ assert(m);
+
+ /* This will load the service information files, but not actually
+ * start any services or anything. */
+
+ if ((r = manager_load_unit_prepare(m, name, path, _ret)) != 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->meta.id == t)
+ unit_dump(u, f, prefix);
+}
+
+void manager_clear_jobs(Manager *m) {
+ Job *j;
+
+ assert(m);
+
+ transaction_abort(m);
+
+ while ((j = hashmap_first(m->jobs)))
+ job_free(j);
+}
+
+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;
+ Meta *meta;
+ unsigned n = 0;
+
+ assert(m);
+
+ if (m->dispatching_dbus_queue)
+ return 0;
+
+ m->dispatching_dbus_queue = true;
+
+ while ((meta = m->dbus_unit_queue)) {
+ assert(meta->in_dbus_queue);
+
+ bus_unit_send_change_signal(UNIT(meta));
+ 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_dispatch_sigchld(Manager *m) {
+ assert(m);
+
+ for (;;) {
+ siginfo_t si;
+ Unit *u;
+
+ 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_name(si.si_pid, &name);
+ log_debug("Got SIGCHLD for process %llu (%s)", (unsigned long long) si.si_pid, strna(name));
+ free(name);
+ }
+
+ /* 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 %llu died (code=%s, status=%i/%s)",
+ (long 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) : strsignal(si.si_status)));
+
+ if (!(u = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
+ continue;
+
+ log_debug("Child %llu belongs to %s", (long long unsigned) si.si_pid, u->meta.id);
+
+ UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status);
+ }
+
+ return 0;
+}
+
+static void manager_start_target(Manager *m, const char *name) {
+ int r;
+
+ if ((r = manager_add_job_by_name(m, JOB_START, name, JOB_REPLACE, true, NULL)) < 0)
+ log_error("Failed to enqueue %s job: %s", name, strerror(-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 == EAGAIN)
+ break;
+
+ return -errno;
+ }
+
+ switch (sfsi.ssi_signo) {
+
+ case SIGCHLD:
+ sigchld = true;
+ break;
+
+ case SIGTERM:
+ if (m->running_as == MANAGER_INIT)
+ /* This is for compatibility with the
+ * original sysvinit */
+ m->exit_code = MANAGER_REEXECUTE;
+ else
+ m->exit_code = MANAGER_EXIT;
+
+ return 0;
+
+ case SIGINT:
+ if (m->running_as == MANAGER_INIT) {
+ manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET);
+ break;
+ }
+
+ m->exit_code = MANAGER_EXIT;
+ return 0;
+
+ case SIGWINCH:
+ if (m->running_as == MANAGER_INIT)
+ manager_start_target(m, SPECIAL_KBREQUEST_TARGET);
+
+ /* This is a nop on non-init */
+ break;
+
+ case SIGPWR:
+ if (m->running_as == MANAGER_INIT)
+ manager_start_target(m, SPECIAL_SIGPWR_TARGET);
+
+ /* 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_system(m);
+ bus_init_api(m);
+ }
+
+ if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) {
+ log_info("Loading D-Bus service...");
+ manager_start_target(m, SPECIAL_DBUS_SERVICE);
+ }
+
+ break;
+ }
+
+ case SIGUSR2:
+ manager_dump_units(m, stdout, "\t");
+ manager_dump_jobs(m, stdout, "\t");
+ break;
+
+ case SIGHUP:
+ m->exit_code = MANAGER_RELOAD;
+ break;
+
+ default:
+ log_info("Got unhandled signal <%s>.", strsignal(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(w = ev->data.ptr);
+
+ 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_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_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;
+ }
+
+ UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w);
+ break;
+ }
+
+ case WATCH_MOUNT:
+ /* Some mount table change, intended for the mount subsystem */
+ mount_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:
+ assert_not_reached("Unknown epoll event type.");
+ }
+
+ return 0;
+}
+
+int manager_loop(Manager *m) {
+ int r;
+
+ RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 1000);
+
+ assert(m);
+ m->exit_code = MANAGER_RUNNING;
+
+ while (m->exit_code == MANAGER_RUNNING) {
+ struct epoll_event event;
+ int n;
+
+ if (!ratelimit_test(&rl)) {
+ /* Yay, something is going seriously wrong, pause a little */
+ log_warning("Looping too fast. Throttling execution a little.");
+ sleep(1);
+ }
+
+ 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 ((n = epoll_wait(m->epoll_fd, &event, 1, -1)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ assert(n == 1);
+
+ if ((r = process_event(m, &event)) < 0)
+ return r;
+ }
+
+ return m->exit_code;
+}
+
+int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) {
+ char *n;
+ Unit *u;
+
+ assert(m);
+ assert(s);
+ assert(_u);
+
+ if (!startswith(s, "/org/freedesktop/systemd1/unit/"))
+ return -EINVAL;
+
+ if (!(n = bus_path_unescape(s+31)))
+ return -ENOMEM;
+
+ u = manager_get_unit(m, n);
+ free(n);
+
+ if (!u)
+ return -ENOENT;
+
+ *_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;
+}
+
+static bool manager_utmp_good(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if ((r = mount_path_is_mounted(m, _PATH_UTMPX)) <= 0) {
+
+ if (r < 0)
+ log_warning("Failed to determine whether " _PATH_UTMPX " is mounted: %s", strerror(-r));
+
+ return false;
+ }
+
+ return true;
+}
+
+void manager_write_utmp_reboot(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (m->utmp_reboot_written)
+ return;
+
+ if (m->running_as != MANAGER_INIT)
+ return;
+
+ if (!manager_utmp_good(m))
+ return;
+
+ if ((r = utmp_put_reboot(m->boot_timestamp)) < 0) {
+
+ if (r != -ENOENT && r != -EROFS)
+ log_warning("Failed to write utmp/wtmp: %s", strerror(-r));
+
+ return;
+ }
+
+ m->utmp_reboot_written = true;
+}
+
+void manager_write_utmp_runlevel(Manager *m, Unit *u) {
+ int runlevel, r;
+
+ assert(m);
+ assert(u);
+
+ if (u->meta.type != UNIT_TARGET)
+ return;
+
+ if (m->running_as != MANAGER_INIT)
+ return;
+
+ if (!manager_utmp_good(m))
+ return;
+
+ if ((runlevel = target_get_runlevel(TARGET(u))) <= 0)
+ return;
+
+ if ((r = utmp_put_runlevel(0, runlevel, 0)) < 0) {
+
+ if (r != -ENOENT && r != -EROFS)
+ log_warning("Failed to write utmp/wtmp: %s", strerror(-r));
+ }
+}
+
+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(FILE **_f) {
+ char *path;
+ mode_t saved_umask;
+ int fd;
+ FILE *f;
+
+ assert(_f);
+
+ if (asprintf(&path, "/dev/shm/systemd-%u.dump-XXXXXX", (unsigned) getpid()) < 0)
+ 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+")) < 0)
+ return -errno;
+
+ *_f = f;
+
+ return 0;
+}
+
+int manager_serialize(Manager *m, FILE *f, FDSet *fds) {
+ Iterator i;
+ Unit *u;
+ const char *t;
+ int r;
+
+ assert(m);
+ assert(f);
+ assert(fds);
+
+ HASHMAP_FOREACH_KEY(u, t, m->units, i) {
+ if (u->meta.id != t)
+ continue;
+
+ if (!unit_can_serialize(u))
+ continue;
+
+ /* Start marker */
+ fputs(u->meta.id, f);
+ fputc('\n', f);
+
+ if ((r = unit_serialize(u, f, fds)) < 0)
+ return r;
+ }
+
+ if (ferror(f))
+ return -EIO;
+
+ return 0;
+}
+
+int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
+ int r = 0;
+
+ assert(m);
+ assert(f);
+
+ log_debug("Deserializing state...");
+
+ for (;;) {
+ Unit *u;
+ char name[UNIT_NAME_MAX+2];
+
+ /* Start marker */
+ if (!fgets(name, sizeof(name), f)) {
+ if (feof(f))
+ break;
+
+ return -errno;
+ }
+
+ char_array_0(name);
+
+ if ((r = manager_load_unit(m, strstrip(name), NULL, &u)) < 0)
+ return r;
+
+ if ((r = unit_deserialize(u, f, fds)) < 0)
+ return r;
+ }
+
+ if (ferror(f))
+ return -EIO;
+
+ return 0;
+}
+
+int manager_reload(Manager *m) {
+ int r, q;
+ FILE *f;
+ FDSet *fds;
+
+ assert(m);
+
+ if ((r = manager_open_serialization(&f)) < 0)
+ return r;
+
+ if (!(fds = fdset_new())) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((r = manager_serialize(m, f, fds)) < 0)
+ goto finish;
+
+ if (fseeko(f, 0, SEEK_SET) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ /* From here on there is no way back. */
+ manager_clear_jobs_and_units(m);
+
+ /* First, enumerate what we can from all config files */
+ if ((q = manager_enumerate(m)) < 0)
+ r = q;
+
+ /* Second, deserialize our stored data */
+ if ((q = manager_deserialize(m, f, fds)) < 0)
+ r = q;
+
+ fclose(f);
+ f = NULL;
+
+ /* Third, fire things up! */
+ if ((q = manager_coldplug(m)) < 0)
+ r = q;
+
+finish:
+ if (f)
+ fclose(f);
+
+ if (fds)
+ fdset_free(fds);
+
+ return r;
+}
+
+static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = {
+ [MANAGER_INIT] = "init",
+ [MANAGER_SYSTEM] = "system",
+ [MANAGER_SESSION] = "session"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(manager_running_as, ManagerRunningAs);
diff --git a/src/manager.h b/src/manager.h
new file mode 100644
index 0000000000..a6500ac600
--- /dev/null
+++ b/src/manager.h
@@ -0,0 +1,284 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foomanagerhfoo
+#define foomanagerhfoo
+
+/***
+ 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 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 <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 2048
+
+typedef struct Manager Manager;
+typedef enum WatchType WatchType;
+typedef struct Watch Watch;
+
+typedef enum ManagerExitCode {
+ MANAGER_RUNNING,
+ MANAGER_EXIT,
+ MANAGER_RELOAD,
+ MANAGER_REEXECUTE,
+ _MANAGER_EXIT_CODE_MAX,
+ _MANAGER_EXIT_CODE_INVALID = -1
+} ManagerExitCode;
+
+typedef enum ManagerRunningAs {
+ MANAGER_INIT, /* root and pid=1 */
+ MANAGER_SYSTEM, /* root and pid!=1 */
+ MANAGER_SESSION, /* non-root, for a session */
+ _MANAGER_RUNNING_AS_MAX,
+ _MANAGER_RUNNING_AS_INVALID = -1
+} ManagerRunningAs;
+
+enum WatchType {
+ WATCH_INVALID,
+ WATCH_SIGNAL,
+ WATCH_FD,
+ WATCH_TIMER,
+ WATCH_MOUNT,
+ WATCH_UDEV,
+ WATCH_DBUS_WATCH,
+ WATCH_DBUS_TIMEOUT
+};
+
+struct Watch {
+ int fd;
+ WatchType type;
+ union {
+ union Unit *unit;
+ 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"
+
+#define SPECIAL_DEFAULT_TARGET "default.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_LOGGER_SOCKET "systemd-logger.socket"
+
+#define SPECIAL_KBREQUEST_TARGET "kbrequest.target"
+#define SPECIAL_SIGPWR_TARGET "sigpwr.target"
+#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target"
+
+#define SPECIAL_LOCAL_FS_TARGET "local-fs.target"
+#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target"
+#define SPECIAL_SWAP_TARGET "swap.target"
+#define SPECIAL_NETWORK_TARGET "network.target"
+#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" /* Should pull in syslog.socket or syslog.service */
+#define SPECIAL_RTC_SET_TARGET "rtc-set.target" /* LSB's $time */
+
+#define SPECIAL_BASIC_TARGET "basic.target"
+#define SPECIAL_RESCUE_TARGET "rescue.target"
+
+#ifndef SPECIAL_DBUS_SERVICE
+#define SPECIAL_DBUS_SERVICE "dbus.service"
+#endif
+
+#ifndef SPECIAL_SYSLOG_SERVICE
+#define SPECIAL_SYSLOG_SERVICE "syslog.service"
+#endif
+
+/* For SysV compatibility. Usually an alias for a saner target. On
+ * SysV-free systems this doesn't exist. */
+#define SPECIAL_RUNLEVEL0_TARGET "runlevel0.target"
+#define SPECIAL_RUNLEVEL1_TARGET "runlevel1.target"
+#define SPECIAL_RUNLEVEL2_TARGET "runlevel2.target"
+#define SPECIAL_RUNLEVEL3_TARGET "runlevel3.target"
+#define SPECIAL_RUNLEVEL4_TARGET "runlevel4.target"
+#define SPECIAL_RUNLEVEL5_TARGET "runlevel5.target"
+#define SPECIAL_RUNLEVEL6_TARGET "runlevel6.target"
+
+struct Manager {
+ uint32_t current_job_id;
+
+ /* Note that the set of units we know of is allowed to be
+ * incosistent. 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(Meta, units_per_type[_UNIT_TYPE_MAX]);
+
+ /* Units that need to be loaded */
+ LIST_HEAD(Meta, 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(Meta, dbus_unit_queue);
+ LIST_HEAD(Job, dbus_job_queue);
+
+ /* Units to remove */
+ LIST_HEAD(Meta, cleanup_queue);
+
+ /* Units to check when doing GC */
+ LIST_HEAD(Meta, gc_queue);
+
+ /* Jobs to be added */
+ Hashmap *transaction_jobs; /* Unit object => Job object list 1:1 */
+ JobDependency *transaction_anchor;
+
+ Hashmap *watch_pids; /* pid => Unit object n:1 */
+
+ Watch signal_watch;
+
+ int epoll_fd;
+
+ unsigned n_snapshots;
+
+ char **unit_path;
+ char **sysvinit_path;
+ char **sysvrcnd_path;
+
+ char **environment;
+
+ usec_t boot_timestamp;
+
+ /* Data specific to the device subsystem */
+ struct udev* udev;
+ struct udev_monitor* udev_monitor;
+ Watch udev_watch;
+
+ /* Data specific to the mount subsystem */
+ FILE *proc_self_mountinfo;
+ Watch mount_watch;
+
+ /* Data specific to the swap filesystem */
+ FILE *proc_swaps;
+
+ /* Data specific to the D-Bus subsystem */
+ DBusConnection *api_bus, *system_bus;
+ Set *subscribed;
+ DBusMessage *queued_message; /* This is used during reloading:
+ * before the reload we queue the
+ * reply message here, and
+ * afterwards we send it */
+
+ Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */
+ int32_t name_data_slot;
+
+ /* 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_controller;
+ char *cgroup_hierarchy;
+
+ usec_t gc_queue_timestamp;
+
+ int gc_marker;
+ unsigned n_in_gc_queue;
+
+ /* Flags */
+ ManagerRunningAs running_as;
+ ManagerExitCode exit_code:4;
+
+ bool dispatching_load_queue:1;
+ bool dispatching_run_queue:1;
+ bool dispatching_dbus_queue:1;
+
+ bool request_api_bus_dispatch:1;
+ bool request_system_bus_dispatch:1;
+
+ bool utmp_reboot_written:1;
+
+ bool confirm_spawn:1;
+};
+
+int manager_new(ManagerRunningAs running_as, bool confirm_spawn, 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_unit_from_dbus_path(Manager *m, const char *s, Unit **_u);
+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, Unit **_ret);
+int manager_load_unit(Manager *m, const char *name, const char *path, Unit **_ret);
+
+int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool force, Job **_ret);
+int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool force, 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_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies);
+
+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_loop(Manager *m);
+
+void manager_write_utmp_reboot(Manager *m);
+void manager_write_utmp_runlevel(Manager *m, Unit *t);
+
+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(FILE **_f);
+
+int manager_serialize(Manager *m, FILE *f, FDSet *fds);
+int manager_deserialize(Manager *m, FILE *f, FDSet *fds);
+
+int manager_reload(Manager *m);
+
+const char *manager_running_as_to_string(ManagerRunningAs i);
+ManagerRunningAs manager_running_as_from_string(const char *s);
+
+#endif
diff --git a/src/missing.h b/src/missing.h
new file mode 100644
index 0000000000..7db7d7d2e8
--- /dev/null
+++ b/src/missing.h
@@ -0,0 +1,38 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foomissinghfoo
+#define foomissinghfoo
+
+/***
+ 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 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/>.
+***/
+
+/* Missing glibc definitions to access certain kernel APIs */
+
+#include <sys/resource.h>
+#include <sys/syscall.h>
+
+#ifndef RLIMIT_RTTIME
+#define RLIMIT_RTTIME 15
+#endif
+
+static inline int pivot_root(const char *new_root, const char *put_old) {
+ return syscall(SYS_pivot_root, new_root, put_old);
+}
+
+#endif
diff --git a/src/mount-setup.c b/src/mount-setup.c
new file mode 100644
index 0000000000..cb91e181bf
--- /dev/null
+++ b/src/mount-setup.c
@@ -0,0 +1,168 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <sys/mount.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <assert.h>
+
+#include "mount-setup.h"
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+
+typedef struct MountPoint {
+ const char *what;
+ const char *where;
+ const char *type;
+ const char *options;
+ unsigned long flags;
+ bool fatal;
+} MountPoint;
+
+static const MountPoint mount_table[] = {
+ { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
+ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
+ { "devtmps", "/dev", "devtmpfs", "mode=755", MS_NOSUID, true },
+ { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
+ { "devpts", "/dev/pts", "devpts", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+ { "cgroup", "/cgroup/debug", "cgroup", "debug", MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
+ { "debugfs", "/sys/kernel/debug", "debugfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+ { "binfmt_misc", "/proc/sys/fs/binfmt_misc", "binfmt_misc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+ { "mqueue", "/dev/mqueue", "mqueue", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
+};
+
+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_startswith(path, mount_table[i].where))
+ return true;
+
+ return path_startswith(path, "/cgroup/");
+}
+
+static int mount_one(const MountPoint *p) {
+ int r;
+
+ assert(p);
+
+ if ((r = path_is_mount_point(p->where)) < 0)
+ return r;
+
+ if (r > 0)
+ return 0;
+
+ /* The access mode here doesn't really matter too much, since
+ * the mounted file system will take precedence anyway. */
+ mkdir_p(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_error("Failed to mount %s: %s", p->where, strerror(errno));
+ return p->fatal ? -errno : 0;
+ }
+
+ return 0;
+}
+
+static int mount_cgroup_controllers(void) {
+ int r;
+ FILE *f;
+ char buf [256];
+
+ /* Mount all available cgroup controllers. */
+
+ if (!(f = fopen("/proc/cgroups", "re")))
+ return -ENOENT;
+
+ /* Ignore the header line */
+ (void) fgets(buf, sizeof(buf), f);
+
+ for (;;) {
+ MountPoint p;
+ char *controller, *where;
+
+ if (fscanf(f, "%ms %*i %*i %*i", &controller) != 1) {
+
+ if (feof(f))
+ break;
+
+ log_error("Failed to parse /proc/cgroups.");
+ r = -EIO;
+ goto finish;
+ }
+
+ if (asprintf(&where, "/cgroup/%s", controller) < 0) {
+ free(controller);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ zero(p);
+ p.what = "cgroup";
+ p.where = where;
+ p.type = "cgroup";
+ p.options = controller;
+ p.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV;
+ p.fatal = false;
+
+ r = mount_one(&p);
+ free(controller);
+ free(where);
+
+ if (r < 0)
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ fclose(f);
+
+ return r;
+}
+
+int mount_setup(void) {
+ int r;
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(mount_table); i ++)
+ if ((r = mount_one(mount_table+i)) < 0)
+ return r;
+
+ return mount_cgroup_controllers();
+}
diff --git a/src/mount-setup.h b/src/mount-setup.h
new file mode 100644
index 0000000000..bb13e0184e
--- /dev/null
+++ b/src/mount-setup.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foomountsetuphfoo
+#define foomountsetuphfoo
+
+/***
+ 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 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 <stdbool.h>
+
+int mount_setup(void);
+
+bool mount_point_is_api(const char *path);
+
+#endif
diff --git a/src/mount.c b/src/mount.c
new file mode 100644
index 0000000000..ec03a52f6a
--- /dev/null
+++ b/src/mount.c
@@ -0,0 +1,1539 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "mount-setup.h"
+#include "unit-name.h"
+#include "mount.h"
+#include "dbus-mount.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_ACTIVE_RELOADING,
+ [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING,
+ [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING,
+ [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING,
+ [MOUNT_REMOUNTING_SIGTERM] = UNIT_ACTIVE_RELOADING,
+ [MOUNT_REMOUNTING_SIGKILL] = UNIT_ACTIVE_RELOADING,
+ [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING,
+ [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING,
+ [MOUNT_MAINTAINANCE] = UNIT_INACTIVE,
+};
+
+static void mount_init(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(u);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ m->timeout_usec = DEFAULT_TIMEOUT_USEC;
+ exec_context_init(&m->exec_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.no_setsid = true;
+
+ m->timer_watch.type = WATCH_INVALID;
+
+ m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID;
+}
+
+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_etc_fstab);
+ 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 int mount_add_mount_links(Mount *m) {
+ Meta *other;
+ int r;
+
+ assert(m);
+
+ /* Adds in links to other mount points that might lie below or
+ * above us in the hierarchy */
+
+ LIST_FOREACH(units_per_type, other, m->meta.manager->units_per_type[UNIT_MOUNT]) {
+ Mount *n = (Mount*) other;
+
+ if (n == m)
+ continue;
+
+ if (n->meta.load_state != UNIT_LOADED)
+ continue;
+
+ if (path_startswith(m->where, n->where)) {
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0)
+ return r;
+
+ if (n->from_etc_fstab || n->from_fragment)
+ 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(m), UNIT_BEFORE, UNIT(n), true)) < 0)
+ return r;
+
+ if (m->from_etc_fstab || m->from_fragment)
+ 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) {
+ Meta *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_per_type, other, m->meta.manager->units_per_type[UNIT_SWAP])
+ if ((r = swap_add_one_mount_link((Swap*) other, m)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int mount_add_automount_links(Mount *m) {
+ Meta *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_per_type, other, m->meta.manager->units_per_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) {
+ Meta *other;
+ int r;
+
+ assert(m);
+
+ LIST_FOREACH(units_per_type, other, m->meta.manager->units_per_type[UNIT_SOCKET])
+ if ((r = socket_add_one_mount_link((Socket*) other, m)) < 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 false;
+
+ zero(me);
+ me.mnt_opts = (char*) haystack;
+
+ return hasmntopt(&me, needle);
+}
+
+static int mount_add_target_links(Mount *m) {
+ const char *target;
+ MountParameters *p;
+ Unit *tu;
+ int r;
+ bool noauto, handle, automount;
+
+ assert(m);
+
+ if (m->from_fragment)
+ p = &m->parameters_fragment;
+ else if (m->from_etc_fstab)
+ p = &m->parameters_etc_fstab;
+ else
+ return 0;
+
+ noauto = !!mount_test_option(p->options, MNTOPT_NOAUTO);
+ handle = !!mount_test_option(p->options, "comment=systemd.mount");
+ automount = !!mount_test_option(p->options, "comment=systemd.automount");
+
+ if (mount_test_option(p->options, "_netdev") ||
+ fstype_is_network(p->fstype))
+ target = SPECIAL_REMOTE_FS_TARGET;
+ else
+ target = SPECIAL_LOCAL_FS_TARGET;
+
+ if ((r = manager_load_unit(UNIT(m)->meta.manager, target, NULL, &tu)) < 0)
+ return r;
+
+ if (automount) {
+ Unit *am;
+
+ if ((r = unit_load_related_unit(UNIT(m), ".automount", &am)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(am), true)) < 0)
+ return r;
+
+ return unit_add_dependency(UNIT(am), UNIT_BEFORE, tu, true);
+
+ } else {
+
+ if (!noauto && handle)
+ if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true)) < 0)
+ return r;
+
+ return unit_add_dependency(UNIT(m), UNIT_BEFORE, tu, true);
+ }
+}
+
+static int mount_verify(Mount *m) {
+ bool b;
+ char *e;
+ assert(m);
+
+ if (UNIT(m)->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ 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)->meta.id);
+ return -EINVAL;
+ }
+
+ if (m->meta.fragment_path && !m->parameters_fragment.what) {
+ log_error("%s's What setting is missing. Refusing.", UNIT(m)->meta.id);
+ return -EBADMSG;
+ }
+
+ return 0;
+}
+
+static int mount_load(Unit *u) {
+ Mount *m = MOUNT(u);
+ int r;
+
+ assert(u);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ if ((r = unit_load_fragment_and_dropin_optional(u)) < 0)
+ return r;
+
+ /* This is a new unit? Then let's add in some extras */
+ if (u->meta.load_state == UNIT_LOADED) {
+ const char *what = NULL;
+
+ if (m->meta.fragment_path)
+ m->from_fragment = true;
+
+ if (!m->where)
+ if (!(m->where = unit_name_to_path(u->meta.id)))
+ return -ENOMEM;
+
+ path_kill_slashes(m->where);
+
+ if (!m->meta.description)
+ if ((r = unit_set_description(u, m->where)) < 0)
+ return r;
+
+ if (m->from_fragment && m->parameters_fragment.what)
+ what = m->parameters_fragment.what;
+ else if (m->from_etc_fstab && m->parameters_etc_fstab.what)
+ what = m->parameters_etc_fstab.what;
+ else if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what)
+ what = m->parameters_proc_self_mountinfo.what;
+
+ if (what)
+ if ((r = unit_add_node_link(u, what,
+ (u->meta.manager->running_as == MANAGER_INIT ||
+ u->meta.manager->running_as == MANAGER_SYSTEM))) < 0)
+ return r;
+
+ if ((r = mount_add_mount_links(m)) < 0)
+ return r;
+
+ if ((r = mount_add_socket_links(m)) < 0)
+ return r;
+
+ if ((r = mount_add_swap_links(m)) < 0)
+ return r;
+
+ if ((r = mount_add_automount_links(m)) < 0)
+ return r;
+
+ if ((r = mount_add_target_links(m)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroup(u)) < 0)
+ return r;
+ }
+
+ return mount_verify(m);
+}
+
+static int mount_notify_automount(Mount *m, int status) {
+ Unit *p;
+ int r;
+
+ assert(m);
+
+ if ((r = unit_get_related_unit(UNIT(m), ".automount", &p)) < 0)
+ return r == -ENOENT ? 0 : r;
+
+ return automount_send_ready(AUTOMOUNT(p), status);
+}
+
+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_MAINTAINANCE)
+ mount_notify_automount(m, -ENODEV);
+
+ if (state != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(m)->meta.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]);
+}
+
+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);
+
+ if (m->from_proc_self_mountinfo)
+ p = &m->parameters_proc_self_mountinfo;
+ else if (m->from_fragment)
+ p = &m->parameters_fragment;
+ else
+ p = &m->parameters_etc_fstab;
+
+ fprintf(f,
+ "%sMount State: %s\n"
+ "%sWhere: %s\n"
+ "%sWhat: %s\n"
+ "%sFile System Type: %s\n"
+ "%sOptions: %s\n"
+ "%sFrom /etc/fstab: %s\n"
+ "%sFrom /proc/self/mountinfo: %s\n"
+ "%sFrom fragment: %s\n"
+ "%sKillMode: %s\n",
+ prefix, mount_state_to_string(m->state),
+ prefix, m->where,
+ prefix, strna(p->what),
+ prefix, strna(p->fstype),
+ prefix, strna(p->options),
+ prefix, yes_no(m->from_etc_fstab),
+ prefix, yes_no(m->from_proc_self_mountinfo),
+ prefix, yes_no(m->from_fragment),
+ prefix, kill_mode_to_string(m->kill_mode));
+
+ if (m->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %llu\n",
+ prefix, (unsigned long long) m->control_pid);
+
+ exec_context_dump(&m->exec_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,
+ m->meta.manager->environment,
+ true,
+ true,
+ UNIT(m)->meta.manager->confirm_spawn,
+ UNIT(m)->meta.cgroup_bondings,
+ &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, bool success) {
+ assert(m);
+
+ if (!success)
+ m->failure = true;
+
+ mount_set_state(m, m->failure ? MOUNT_MAINTAINANCE : MOUNT_DEAD);
+}
+
+static void mount_enter_mounted(Mount *m, bool success) {
+ assert(m);
+
+ if (!success)
+ m->failure = true;
+
+ mount_set_state(m, MOUNT_MOUNTED);
+}
+
+static void mount_enter_signal(Mount *m, MountState state, bool success) {
+ int r;
+ bool sent = false;
+
+ assert(m);
+
+ if (!success)
+ m->failure = true;
+
+ if (m->kill_mode != KILL_NONE) {
+ int sig = (state == MOUNT_MOUNTING_SIGTERM ||
+ state == MOUNT_UNMOUNTING_SIGTERM ||
+ state == MOUNT_REMOUNTING_SIGTERM) ? SIGTERM : SIGKILL;
+
+ if (m->kill_mode == KILL_CONTROL_GROUP) {
+
+ if ((r = cgroup_bonding_kill_list(UNIT(m)->meta.cgroup_bondings, sig)) < 0) {
+ if (r != -EAGAIN && r != -ESRCH)
+ goto fail;
+ } else
+ sent = true;
+ }
+
+ if (!sent && m->control_pid > 0)
+ if (kill(m->kill_mode == KILL_PROCESS ? m->control_pid : -m->control_pid, sig) < 0 && errno != ESRCH) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ if (sent) {
+ 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, true);
+ else
+ mount_enter_dead(m, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", UNIT(m)->meta.id, strerror(-r));
+
+ if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL)
+ mount_enter_mounted(m, false);
+ else
+ mount_enter_dead(m, false);
+}
+
+static void mount_enter_unmounting(Mount *m, bool success) {
+ int r;
+
+ assert(m);
+
+ if (!success)
+ m->failure = true;
+
+ 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 exectuable: %s", UNIT(m)->meta.id, strerror(-r));
+ mount_enter_mounted(m, false);
+}
+
+static void mount_enter_mounting(Mount *m) {
+ int r;
+
+ assert(m);
+
+ m->control_command_id = MOUNT_EXEC_MOUNT;
+ m->control_command = m->exec_command + MOUNT_EXEC_MOUNT;
+
+ 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.options ? "-o" : NULL, m->parameters_fragment.options,
+ NULL);
+ else if (m->from_etc_fstab)
+ r = exec_command_set(
+ m->control_command,
+ "/bin/mount",
+ m->where,
+ NULL);
+ 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_MOUNTING);
+
+ return;
+
+fail:
+ log_warning("%s failed to run mount exectuable: %s", UNIT(m)->meta.id, strerror(-r));
+ mount_enter_dead(m, false);
+}
+
+static void mount_enter_mounting_done(Mount *m) {
+ assert(m);
+
+ mount_set_state(m, MOUNT_MOUNTING_DONE);
+}
+
+static void mount_enter_remounting(Mount *m, bool success) {
+ int r;
+
+ assert(m);
+
+ if (!success)
+ m->failure = true;
+
+ 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,
+ "-o", o,
+ NULL);
+
+ free(buf);
+ } else if (m->from_etc_fstab)
+ r = exec_command_set(
+ m->control_command,
+ "/bin/mount",
+ m->where,
+ "-o", "remount",
+ NULL);
+ else
+ r = -ENOENT;
+
+ if (r < 0) {
+ r = -ENOMEM;
+ 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:
+ mount_enter_mounted(m, false);
+}
+
+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)
+ return -EAGAIN;
+
+ /* Already on it! */
+ if (m->state == MOUNT_MOUNTING ||
+ m->state == MOUNT_MOUNTING_SIGTERM ||
+ m->state == MOUNT_MOUNTING_SIGKILL)
+ return 0;
+
+ assert(m->state == MOUNT_DEAD || m->state == MOUNT_MAINTAINANCE);
+
+ m->failure = false;
+ mount_enter_mounting(m);
+ return 0;
+}
+
+static int mount_stop(Unit *u) {
+ Mount *m = MOUNT(u);
+
+ assert(m);
+
+ /* Cann't do this right now. */
+ if (m->state == MOUNT_MOUNTING ||
+ m->state == MOUNT_MOUNTING_DONE ||
+ m->state == MOUNT_MOUNTING_SIGTERM ||
+ m->state == MOUNT_MOUNTING_SIGKILL ||
+ m->state == MOUNT_REMOUNTING ||
+ m->state == MOUNT_REMOUNTING_SIGTERM ||
+ m->state == MOUNT_REMOUNTING_SIGKILL)
+ return -EAGAIN;
+
+ /* Already on it */
+ if (m->state == MOUNT_UNMOUNTING ||
+ m->state == MOUNT_UNMOUNTING_SIGKILL ||
+ m->state == MOUNT_UNMOUNTING_SIGTERM)
+ return 0;
+
+ assert(m->state == MOUNT_MOUNTED);
+
+ mount_enter_unmounting(m, true);
+ 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, true);
+ 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, "failure", yes_no(m->failure));
+
+ if (m->control_pid > 0)
+ unit_serialize_item_format(u, f, "control-pid", "%u", (unsigned) 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);
+ int r;
+
+ 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, "failure")) {
+ int b;
+
+ if ((b = parse_boolean(value)) < 0)
+ log_debug("Failed to parse failure value %s", value);
+ else
+ m->failure = b || m->failure;
+
+ } else if (streq(key, "control-pid")) {
+ unsigned pid;
+
+ if ((r = safe_atou(value, &pid)) < 0 || pid <= 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ m->control_pid = (pid_t) 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_etc_fstab || m->from_proc_self_mountinfo;
+}
+
+static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Mount *m = MOUNT(u);
+ bool success;
+
+ assert(m);
+ assert(pid >= 0);
+
+ success = is_clean_exit(code, status);
+ m->failure = m->failure || !success;
+
+ assert(m->control_pid == pid);
+ m->control_pid = 0;
+
+ if (m->control_command) {
+ exec_status_fill(&m->control_command->exec_status, pid, code, status);
+ m->control_command = NULL;
+ m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID;
+ }
+
+ log_debug("%s control process exited, code=%s status=%i", u->meta.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:
+ case MOUNT_REMOUNTING:
+ case MOUNT_REMOUNTING_SIGKILL:
+ case MOUNT_REMOUNTING_SIGTERM:
+
+ if (success && m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, true);
+ else if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, false);
+ else
+ mount_enter_dead(m, false);
+ break;
+
+ case MOUNT_UNMOUNTING:
+ case MOUNT_UNMOUNTING_SIGKILL:
+ case MOUNT_UNMOUNTING_SIGTERM:
+
+ if (success)
+ mount_enter_dead(m, true);
+ else if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, false);
+ else
+ mount_enter_dead(m, false);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+}
+
+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->meta.id);
+ mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, false);
+ break;
+
+ case MOUNT_REMOUNTING:
+ log_warning("%s remounting timed out. Stopping.", u->meta.id);
+ mount_enter_signal(m, MOUNT_REMOUNTING_SIGTERM, false);
+ break;
+
+ case MOUNT_UNMOUNTING:
+ log_warning("%s unmounting timed out. Stopping.", u->meta.id);
+ mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, false);
+ break;
+
+ case MOUNT_MOUNTING_SIGTERM:
+ log_warning("%s mounting timed out. Killing.", u->meta.id);
+ mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, false);
+ break;
+
+ case MOUNT_REMOUNTING_SIGTERM:
+ log_warning("%s remounting timed out. Killing.", u->meta.id);
+ mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, false);
+ break;
+
+ case MOUNT_UNMOUNTING_SIGTERM:
+ log_warning("%s unmounting timed out. Killing.", u->meta.id);
+ mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, false);
+ break;
+
+ case MOUNT_MOUNTING_SIGKILL:
+ case MOUNT_REMOUNTING_SIGKILL:
+ case MOUNT_UNMOUNTING_SIGKILL:
+ log_warning("%s mount process still around after SIGKILL. Ignoring.", u->meta.id);
+
+ if (m->from_proc_self_mountinfo)
+ mount_enter_mounted(m, false);
+ else
+ mount_enter_dead(m, false);
+ 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,
+ bool from_proc_self_mountinfo,
+ bool set_flags) {
+ int r;
+ Unit *u;
+ bool delete;
+ char *e, *w = NULL, *o = NULL, *f = NULL;
+ MountParameters *p;
+
+ assert(m);
+ assert(what);
+ assert(where);
+ assert(options);
+ assert(fstype);
+
+ assert(!set_flags || from_proc_self_mountinfo);
+
+ /* Ignore API mount points. They should never be referenced in
+ * dependencies ever. */
+ if (mount_point_is_api(where))
+ return 0;
+
+ if (streq(fstype, "autofs"))
+ return 0;
+
+ /* probably some kind of swap, ignore */
+ if (!is_path(where))
+ return 0;
+
+ if (!(e = unit_name_from_path(where, ".mount")))
+ return -ENOMEM;
+
+ if (!(u = manager_get_unit(m, e))) {
+ delete = true;
+
+ if (!(u = unit_new(m))) {
+ free(e);
+ return -ENOMEM;
+ }
+
+ r = unit_add_name(u, e);
+ free(e);
+
+ if (r < 0)
+ goto fail;
+
+ if (!(MOUNT(u)->where = strdup(where))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ unit_add_to_load_queue(u);
+ } else {
+ delete = false;
+ free(e);
+ }
+
+ if (!(w = strdup(what)) ||
+ !(o = strdup(options)) ||
+ !(f = strdup(fstype))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (from_proc_self_mountinfo) {
+ 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;
+ } else {
+ p = &MOUNT(u)->parameters_etc_fstab;
+ MOUNT(u)->from_etc_fstab = true;
+ }
+
+ free(p->what);
+ p->what = w;
+
+ free(p->options);
+ p->options = o;
+
+ free(p->fstype);
+ p->fstype = f;
+
+ unit_add_to_dbus_queue(u);
+
+ return 0;
+
+fail:
+ free(w);
+ free(o);
+ free(f);
+
+ if (delete && u)
+ unit_free(u);
+
+ return r;
+}
+
+static char *fstab_node_to_udev_node(char *p) {
+ char *dn, *t;
+ int r;
+
+ /* FIXME: to follow udev's logic 100% we need to leave valid
+ * UTF8 chars unescaped */
+
+ if (startswith(p, "LABEL=")) {
+
+ if (!(t = xescape(p+6, "/ ")))
+ return NULL;
+
+ r = asprintf(&dn, "/dev/disk/by-label/%s", t);
+ free(t);
+
+ if (r < 0)
+ return NULL;
+
+ return dn;
+ }
+
+ if (startswith(p, "UUID=")) {
+
+ if (!(t = xescape(p+5, "/ ")))
+ return NULL;
+
+ r = asprintf(&dn, "/dev/disk/by-uuid/%s", ascii_strlower(t));
+ free(t);
+
+ if (r < 0)
+ return NULL;
+
+ return dn;
+ }
+
+ return strdup(p);
+}
+
+static int mount_find_pri(char *options) {
+ char *end, *pri;
+ unsigned long r;
+
+ if (!(pri = mount_test_option(options, "pri=")))
+ return 0;
+
+ pri += 4;
+
+ errno = 0;
+ r = strtoul(pri, &end, 10);
+
+ if (errno != 0)
+ return -errno;
+
+ if (end == pri || (*end != ',' && *end != 0))
+ return -EINVAL;
+
+ return (int) r;
+}
+
+static int mount_load_etc_fstab(Manager *m) {
+ FILE *f;
+ int r;
+ struct mntent* me;
+
+ assert(m);
+
+ errno = 0;
+ if (!(f = setmntent("/etc/fstab", "r")))
+ return -errno;
+
+ while ((me = getmntent(f))) {
+ char *where, *what;
+
+ if (!(what = fstab_node_to_udev_node(me->mnt_fsname))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(where = strdup(me->mnt_dir))) {
+ free(what);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (what[0] == '/')
+ path_kill_slashes(what);
+
+ if (where[0] == '/')
+ path_kill_slashes(where);
+
+ if (streq(me->mnt_type, "swap")) {
+ int pri;
+
+ if ((pri = mount_find_pri(me->mnt_opts)) < 0)
+ r = pri;
+ else
+ r = swap_add_one(m,
+ what,
+ pri,
+ !!mount_test_option(me->mnt_opts, MNTOPT_NOAUTO),
+ !!mount_test_option(me->mnt_opts, "comment=systemd.swapon"),
+ false);
+ } else
+ r = mount_add_one(m, what, where, me->mnt_opts, me->mnt_type, false, false);
+
+ free(what);
+ free(where);
+
+ if (r < 0)
+ goto finish;
+ }
+
+ r = 0;
+finish:
+
+ endmntent(f);
+ return r;
+}
+
+static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) {
+ int r;
+ char *device, *path, *options, *fstype, *d, *p;
+
+ assert(m);
+
+ rewind(m->proc_self_mountinfo);
+
+ for (;;) {
+ int k;
+
+ device = path = options = fstype = d = p = 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) seperator */
+ "%ms " /* (9) file system type */
+ "%ms" /* (10) mount source */
+ "%*[^\n]", /* some rubbish at the end */
+ &path,
+ &options,
+ &fstype,
+ &device)) != 4) {
+
+ if (k == EOF)
+ break;
+
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ if (!(d = cunescape(device)) ||
+ !(p = cunescape(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if ((r = mount_add_one(m, d, p, options, fstype, true, set_flags)) < 0)
+ goto finish;
+
+ free(device);
+ free(path);
+ free(options);
+ free(fstype);
+ free(d);
+ free(p);
+ }
+
+ r = 0;
+
+finish:
+ free(device);
+ free(path);
+ free(options);
+ free(fstype);
+ free(d);
+ free(p);
+
+ 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 = EPOLLERR;
+ 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_etc_fstab(m)) < 0)
+ goto fail;
+
+ 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) {
+ Meta *meta;
+ int r;
+
+ assert(m);
+ assert(events == EPOLLERR);
+
+ /* 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(errno));
+
+ /* Reset flags, just in case, for later calls */
+ LIST_FOREACH(units_per_type, meta, m->units_per_type[UNIT_MOUNT]) {
+ Mount *mount = (Mount*) meta;
+
+ mount->is_mounted = mount->just_mounted = mount->just_changed = false;
+ }
+
+ return;
+ }
+
+ manager_dispatch_load_queue(m);
+
+ LIST_FOREACH(units_per_type, meta, m->units_per_type[UNIT_MOUNT]) {
+ Mount *mount = (Mount*) meta;
+
+ 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, true);
+ break;
+
+ default:
+ mount_set_state(mount, mount->state);
+ break;
+
+ }
+
+ } else if (mount->just_mounted || mount->just_changed) {
+
+ /* New or changed entrymount */
+
+ switch (mount->state) {
+
+ case MOUNT_DEAD:
+ case MOUNT_MAINTAINANCE:
+ mount_enter_mounted(mount, true);
+ 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;
+ }
+}
+
+int mount_path_is_mounted(Manager *m, const char* path) {
+ char *t;
+ int r;
+
+ assert(m);
+ assert(path);
+
+ if (path[0] != '/')
+ return 1;
+
+ if (!(t = strdup(path)))
+ return -ENOMEM;
+
+ path_kill_slashes(t);
+
+ for (;;) {
+ char *e, *slash;
+ Unit *u;
+
+ if (!(e = unit_name_from_path(t, ".mount"))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ u = manager_get_unit(m, e);
+ free(e);
+
+ if (u &&
+ (MOUNT(u)->from_etc_fstab || MOUNT(u)->from_fragment) &&
+ MOUNT(u)->state != MOUNT_MOUNTED) {
+ r = 0;
+ goto finish;
+ }
+
+ assert_se(slash = strrchr(t, '/'));
+
+ if (slash == t) {
+ r = 1;
+ goto finish;
+ }
+
+ *slash = 0;
+ }
+
+ r = 1;
+
+finish:
+ free(t);
+ 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_MAINTAINANCE] = "maintainance"
+};
+
+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);
+
+const UnitVTable mount_vtable = {
+ .suffix = ".mount",
+
+ .no_alias = true,
+ .no_instances = true,
+ .no_isolate = 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,
+
+ .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,
+
+ .bus_message_handler = bus_mount_message_handler,
+
+ .enumerate = mount_enumerate,
+ .shutdown = mount_shutdown
+};
diff --git a/src/mount.h b/src/mount.h
new file mode 100644
index 0000000000..3b28e89ed3
--- /dev/null
+++ b/src/mount.h
@@ -0,0 +1,110 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foomounthfoo
+#define foomounthfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Mount Mount;
+
+#include "unit.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_MAINTAINANCE,
+ _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;
+} MountParameters;
+
+struct Mount {
+ Meta meta;
+
+ char *where;
+
+ MountParameters parameters_etc_fstab;
+ MountParameters parameters_proc_self_mountinfo;
+ MountParameters parameters_fragment;
+
+ bool from_etc_fstab:1;
+ 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;
+
+ bool failure:1;
+
+ usec_t timeout_usec;
+
+ ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX];
+ ExecContext exec_context;
+
+ MountState state, deserialized_state;
+
+ KillMode kill_mode;
+
+ 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);
+
+int mount_path_is_mounted(Manager *m, const char* path);
+
+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);
+
+#endif
diff --git a/src/namespace.c b/src/namespace.c
new file mode 100644
index 0000000000..09bcaff968
--- /dev/null
+++ b/src/namespace.c
@@ -0,0 +1,331 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "namespace.h"
+#include "missing.h"
+
+typedef enum PathMode {
+ /* This is ordered by priority! */
+ INACCESSIBLE,
+ READONLY,
+ PRIVATE,
+ READWRITE
+} PathMode;
+
+typedef struct Path {
+ const char *path;
+ PathMode mode;
+} 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, bool *need_private) {
+ Path *f, *t, *previous;
+
+ assert(p);
+ assert(n);
+ assert(need_inaccessible);
+ assert(need_private);
+
+ for (f = p, t = p, previous = NULL; f < p+*n; f++) {
+
+ if (previous && path_equal(f->path, previous->path))
+ continue;
+
+ t->path = f->path;
+ t->mode = f->mode;
+
+ if (t->mode == PRIVATE)
+ *need_private = true;
+
+ if (t->mode == INACCESSIBLE)
+ *need_inaccessible = true;
+
+ previous = t;
+
+ t++;
+ }
+
+ *n = t - p;
+}
+
+static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) {
+ const char *what;
+ char *where;
+ int r;
+
+ assert(p);
+ assert(root_dir);
+ assert(inaccessible_dir);
+ assert(private_dir);
+
+ if (!(where = strappend(root_dir, p->path)))
+ return -ENOMEM;
+
+ switch (p->mode) {
+
+ case INACCESSIBLE:
+ what = inaccessible_dir;
+ flags |= MS_RDONLY;
+ break;
+
+ case READONLY:
+ flags |= MS_RDONLY;
+ /* Fall through */
+
+ case READWRITE:
+ what = p->path;
+ break;
+
+ case PRIVATE:
+ what = private_dir;
+ break;
+ }
+
+ if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) {
+ log_debug("Successfully mounted %s to %s", what, where);
+
+ /* The bind mount will always inherit the original
+ * flags. If we want to set any flag we need
+ * to do so in a second indepdant step. */
+ if (flags)
+ r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL);
+
+ /* Avoid expontial growth of trees */
+ if (r >= 0 && path_equal(p->path, "/"))
+ r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_UNBINDABLE|flags, NULL);
+
+ if (r < 0) {
+ r = -errno;
+ umount2(where, MNT_DETACH);
+ }
+ }
+
+ free(where);
+ return r;
+}
+
+int setup_namespace(
+ char **writable,
+ char **readable,
+ char **inaccessible,
+ bool private_tmp,
+ unsigned long flags) {
+
+ char
+ tmp_dir[] = "/tmp/systemd-namespace-XXXXXX",
+ root_dir[] = "/tmp/systemd-namespace-XXXXXX/root",
+ old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX",
+ inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible",
+ private_dir[] = "/tmp/systemd-namespace-XXXXXX/private";
+
+ Path *paths, *p;
+ unsigned n;
+ bool need_private = false, need_inaccessible = false;
+ bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false;
+ int r;
+ const char *t;
+
+ n =
+ strv_length(writable) +
+ strv_length(readable) +
+ strv_length(inaccessible) +
+ (private_tmp ? 2 : 1);
+
+ if (!(paths = new(Path, n)))
+ return -ENOMEM;
+
+ p = paths;
+ 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;
+ p++;
+ }
+
+ p->path = "/";
+ p->mode = READWRITE;
+ p++;
+
+ assert(paths + n == p);
+
+ qsort(paths, n, sizeof(Path), path_compare);
+ drop_duplicates(paths, &n, &need_inaccessible, &need_private);
+
+ if (!mkdtemp(tmp_dir)) {
+ r = -errno;
+ goto fail;
+ }
+ remove_tmp = true;
+
+ memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1);
+ if (mkdir(root_dir, 0777) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ remove_root = true;
+
+ if (need_inaccessible) {
+ memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1);
+ if (mkdir(inaccessible_dir, 0) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ remove_inaccessible = true;
+ }
+
+ if (need_private) {
+ memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1);
+ if (mkdir(private_dir, 0777 + S_ISVTX) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ remove_private = true;
+ }
+
+ if (unshare(CLONE_NEWNS) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* We assume that by default mount events from us won't be
+ * propagated to the root namespace. */
+
+ for (p = paths; p < paths + n; p++)
+ if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0)
+ goto undo_mounts;
+
+ memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1);
+ if (!mkdtemp(old_root_dir)) {
+ r = -errno;
+ goto undo_mounts;
+ }
+ remove_old_root = true;
+
+ if (chdir(root_dir) < 0) {
+ r = -errno;
+ goto undo_mounts;
+ }
+
+ if (pivot_root(root_dir, old_root_dir) < 0) {
+ r = -errno;
+ goto undo_mounts;
+ }
+
+ t = old_root_dir + sizeof(root_dir) - 1;
+ if (umount2(t, MNT_DETACH) < 0)
+ /* At this point it's too late to turn anything back,
+ * since we are already in the new root. */
+ return -errno;
+
+ if (rmdir(t) < 0)
+ return -errno;
+
+ return 0;
+
+undo_mounts:
+
+ for (p--; p >= paths; p--) {
+ char full_path[PATH_MAX];
+
+ snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path);
+ char_array_0(full_path);
+
+ umount2(full_path, MNT_DETACH);
+ }
+
+fail:
+ if (remove_old_root)
+ rmdir(old_root_dir);
+
+ if (remove_inaccessible)
+ rmdir(inaccessible_dir);
+
+ if (remove_private)
+ rmdir(private_dir);
+
+ if (remove_root)
+ rmdir(root_dir);
+
+ if (remove_tmp)
+ rmdir(tmp_dir);
+
+ free(paths);
+
+ return r;
+}
diff --git a/src/namespace.h b/src/namespace.h
new file mode 100644
index 0000000000..6128646391
--- /dev/null
+++ b/src/namespace.h
@@ -0,0 +1,34 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foonamespacehfoo
+#define foonamespacehfoo
+
+/***
+ 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 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 <stdbool.h>
+
+int setup_namespace(
+ char **writable,
+ char **readable,
+ char **inaccessible,
+ bool private_tmp,
+ unsigned long flags);
+
+#endif
diff --git a/src/ratelimit.c b/src/ratelimit.c
new file mode 100644
index 0000000000..1e5ed03c55
--- /dev/null
+++ b/src/ratelimit.c
@@ -0,0 +1,62 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <assert.h>
+
+#include "ratelimit.h"
+#include "log.h"
+
+/* Modelled after Linux' lib/ratelimit.c by Dave Young
+ * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */
+
+bool ratelimit_test(RateLimit *r) {
+ usec_t timestamp;
+
+ timestamp = now(CLOCK_MONOTONIC);
+
+ assert(r);
+ assert(r->interval > 0);
+ assert(r->burst > 0);
+
+ if (r->begin <= 0 ||
+ r->begin + r->interval < timestamp) {
+
+ if (r->n_missed > 0)
+ log_warning("%u events suppressed", r->n_missed);
+
+ r->begin = timestamp;
+
+ /* Reset counters */
+ r->n_printed = 0;
+ r->n_missed = 0;
+ goto good;
+ }
+
+ if (r->n_printed <= r->burst)
+ goto good;
+
+ r->n_missed++;
+ return false;
+
+good:
+ r->n_printed++;
+ return true;
+}
diff --git a/src/ratelimit.h b/src/ratelimit.h
new file mode 100644
index 0000000000..e7dffb8bfa
--- /dev/null
+++ b/src/ratelimit.h
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooratelimithfoo
+#define fooratelimithfoo
+
+/***
+ 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 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 "util.h"
+
+typedef struct RateLimit {
+ usec_t interval;
+ usec_t begin;
+ unsigned burst;
+ unsigned n_printed, n_missed;
+} RateLimit;
+
+#define RATELIMIT_DEFINE(_name, _interval, _burst) \
+ RateLimit _name = { \
+ .interval = (_interval), \
+ .burst = (_burst), \
+ .n_printed = 0, \
+ .n_missed = 0, \
+ .begin = 0 \
+ }
+
+#define RATELIMIT_INIT(v, _interval, _burst) \
+ do { \
+ RateLimit *_r = &(v); \
+ _r->interval = (_interval); \
+ _r->burst = (_burst); \
+ _r->n_printed = 0; \
+ _r->n_missed = 0; \
+ _r->begin = 0; \
+ } while (false);
+
+bool ratelimit_test(RateLimit *r);
+
+#endif
diff --git a/src/sd-daemon.c b/src/sd-daemon.c
new file mode 100644
index 0000000000..cc972dabd7
--- /dev/null
+++ b/src/sd-daemon.c
@@ -0,0 +1,96 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sd-daemon.h"
+
+int sd_listen_fds(int unset_environment) {
+
+#ifdef DISABLE_SYSTEMD
+ return 0;
+#else
+ int r;
+ const char *e;
+ char *p = NULL;
+ unsigned long l;
+
+ if (!(e = getenv("LISTEN_PID"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p || l <= 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Is this for us? */
+ if (getpid() != (pid_t) l) {
+ r = 0;
+ goto finish;
+ }
+
+ if (!(e = getenv("LISTEN_FDS"))) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || *p) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ r = (int) l;
+
+finish:
+ if (unset_environment) {
+ unsetenv("LISTEN_PID");
+ unsetenv("LISTEN_FDS");
+ }
+
+ return r;
+#endif
+}
diff --git a/src/sd-daemon.h b/src/sd-daemon.h
new file mode 100644
index 0000000000..c7f5c1d6b8
--- /dev/null
+++ b/src/sd-daemon.h
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+/* Reference implementation of a few systemd related interfaces for
+ * writing daemons. These interfaces are trivial to implement, however
+ * to simplify porting we provide this reference
+ * implementation. Applications are free to reimplement the algorithms
+ * described here. */
+
+/*
+ Log levels for usage on stderr:
+
+ fprintf(stderr, SD_NOTICE "Hello World!");
+
+ This is similar to printk() usage in the kernel.
+*/
+
+#define SD_EMERG "<0>" /* system is unusable */
+#define SD_ALERT "<1>" /* action must be taken immediately */
+#define SD_CRIT "<2>" /* critical conditions */
+#define SD_ERR "<3>" /* error conditions */
+#define SD_WARNING "<4>" /* warning conditions */
+#define SD_NOTICE "<5>" /* normal but significant condition */
+#define SD_INFO "<6>" /* informational */
+#define SD_DEBUG "<7>" /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/* Returns how many file descriptors have been passed, or a negative
+ * errno code on failure. Optionally removes the $LISTEN_FDS and
+ * $LISTEN_PID file descriptors from the environment (recommended). */
+int sd_listen_fds(int unset_environment);
+
+#endif
diff --git a/src/securebits.h b/src/securebits.h
new file mode 100644
index 0000000000..a5b99a3dbf
--- /dev/null
+++ b/src/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 compatiblility 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/service.c b/src/service.c
new file mode 100644
index 0000000000..bf91561901
--- /dev/null
+++ b/src/service.c
@@ -0,0 +1,2463 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <signal.h>
+#include <dirent.h>
+#include <unistd.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 "dbus-service.h"
+
+#define COMMENTS "#;\n"
+#define NEWLINES "\n\r"
+#define LINE_MAX 4096
+
+typedef enum RunlevelType {
+ RUNLEVEL_UP,
+ RUNLEVEL_DOWN,
+ RUNLEVEL_BASIC
+} RunlevelType;
+
+static const struct {
+ const char *path;
+ const char *target;
+ const RunlevelType type;
+} rcnd_table[] = {
+ /* Standard SysV runlevels */
+ { "rc0.d", SPECIAL_RUNLEVEL0_TARGET, RUNLEVEL_DOWN },
+ { "rc1.d", SPECIAL_RUNLEVEL1_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 },
+ { "rc6.d", SPECIAL_RUNLEVEL6_TARGET, RUNLEVEL_DOWN },
+
+ /* SuSE style boot.d */
+ { "boot.d", SPECIAL_BASIC_TARGET, RUNLEVEL_BASIC },
+
+ /* Debian style rcS.d */
+ { "rcS.d", SPECIAL_BASIC_TARGET, RUNLEVEL_BASIC },
+};
+
+#define RUNLEVELS_UP "12345"
+/* #define RUNLEVELS_DOWN "06" */
+/* #define RUNLEVELS_BOOT "bBsS" */
+
+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_ACTIVE_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_MAINTAINANCE] = UNIT_INACTIVE,
+ [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
+};
+
+static void service_init(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(u);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ s->timeout_usec = DEFAULT_TIMEOUT_USEC;
+ s->restart_usec = DEFAULT_RESTART_USEC;
+ s->timer_watch.type = WATCH_INVALID;
+ s->sysv_start_priority = -1;
+ s->socket_fd = -1;
+
+ exec_context_init(&s->exec_context);
+
+ RATELIMIT_INIT(s->ratelimit, 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_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_done(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ free(s->pid_file);
+ s->pid_file = NULL;
+
+ free(s->sysv_path);
+ s->sysv_path = NULL;
+
+ free(s->sysv_runlevels);
+ s->sysv_runlevels = NULL;
+
+ exec_context_done(&s->exec_context);
+ exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
+ s->control_command = 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);
+
+ if (s->bus_name) {
+ unit_unwatch_bus_name(UNIT(u), s->bus_name);
+ free(s->bus_name);
+ s->bus_name = NULL;
+ }
+
+ service_close_socket_fd(s);
+
+ unit_unwatch_timer(u, &s->timer_watch);
+}
+
+static int sysv_translate_name(const char *name, char **_r) {
+
+ static const char * const table[] = {
+ "$local_fs", SPECIAL_LOCAL_FS_TARGET,
+ "$network", SPECIAL_NETWORK_TARGET,
+ "$named", SPECIAL_NSS_LOOKUP_TARGET,
+ "$portmap", SPECIAL_RPCBIND_TARGET,
+ "$remote_fs", SPECIAL_REMOTE_FS_TARGET,
+ "$syslog", SPECIAL_SYSLOG_TARGET,
+ "$time", SPECIAL_RTC_SET_TARGET
+ };
+
+ unsigned i;
+ char *r;
+
+ for (i = 0; i < ELEMENTSOF(table); i += 2)
+ if (streq(table[i], name)) {
+ if (!(r = strdup(table[i+1])))
+ return -ENOMEM;
+
+ goto finish;
+ }
+
+ if (*name == '$')
+ return 0;
+
+ if (asprintf(&r, "%s.service", name) < 0)
+ return -ENOMEM;
+
+finish:
+
+ if (_r)
+ *_r = r;
+
+ return 1;
+}
+
+static int sysv_chkconfig_order(Service *s) {
+ Meta *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_per_type, other, UNIT(s)->meta.manager->units_per_type[UNIT_SERVICE]) {
+ Service *t;
+ UnitDependency d;
+
+ t = (Service*) other;
+
+ if (s == t)
+ continue;
+
+ if (t->sysv_start_priority < 0)
+ continue;
+
+ /* If both units have modern headers we don't care
+ * about the priorities */
+ if ((!s->sysv_path || s->sysv_has_lsb) &&
+ (!t->sysv_path || t->sysv_has_lsb))
+ continue;
+
+ 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) {
+ ExecCommand *c;
+
+ assert(s);
+ assert(s->sysv_path);
+
+ if (!(c = exec_command_new(s->sysv_path, "start")))
+ return -ENOMEM;
+ exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c);
+
+ if (!(c = exec_command_new(s->sysv_path, "stop")))
+ return -ENOMEM;
+ exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c);
+
+ if (!(c = exec_command_new(s->sysv_path, "reload")))
+ return -ENOMEM;
+ exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c);
+
+ return 0;
+}
+
+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
+ } state = NORMAL;
+
+ assert(s);
+ assert(path);
+
+ u = UNIT(s);
+
+ if (!(f = fopen(path, "re"))) {
+ r = errno == ENOENT ? 0 : -errno;
+ goto finish;
+ }
+
+ s->type = SERVICE_FORKING;
+ s->restart = SERVICE_ONCE;
+
+ free(s->sysv_path);
+ if (!(s->sysv_path = strdup(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ 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 != '#')
+ 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(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 if (s->sysv_start_priority < 0)
+ 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(t, "description:")) {
+
+ size_t k = strlen(t);
+ char *d;
+
+ if (t[k-1] == '\\') {
+ state = DESCRIPTION;
+ t[k-1] = 0;
+ }
+
+ if (!(d = strdup(strstrip(t+12)))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(u->meta.description);
+ u->meta.description = d;
+
+ } else if (startswith(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 *d;
+
+ if (t[k-1] == '\\')
+ t[k-1] = 0;
+ else
+ state = NORMAL;
+
+ assert(u->meta.description);
+ if (asprintf(&d, "%s %s", u->meta.description, strstrip(t)) < 0) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(u->meta.description);
+ u->meta.description = d;
+
+ } else if (state == LSB || state == LSB_DESCRIPTION) {
+
+ if (startswith(t, "Provides:")) {
+ char *i, *w;
+ size_t z;
+
+ state = LSB;
+
+ FOREACH_WORD(w, z, t+9, i) {
+ char *n, *m;
+
+ if (!(n = strndup(w, z))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = sysv_translate_name(n, &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 {
+ if ((r = unit_add_dependency_by_name_inverse(u, UNIT_REQUIRES, m, NULL, true)) >= 0)
+ r = unit_add_dependency_by_name(u, UNIT_BEFORE, m, NULL, true);
+ }
+
+ free(m);
+
+ if (r < 0)
+ goto finish;
+ }
+
+ } else if (startswith(t, "Required-Start:") ||
+ startswith(t, "Should-Start:")) {
+ char *i, *w;
+ size_t z;
+
+ state = LSB;
+
+ FOREACH_WORD(w, z, strchr(t, ':')+1, i) {
+ char *n, *m;
+
+ if (!(n = strndup(w, z))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ r = sysv_translate_name(n, &m);
+ free(n);
+
+ if (r < 0)
+ goto finish;
+
+ if (r == 0)
+ continue;
+
+ r = unit_add_dependency_by_name(u, UNIT_AFTER, m, NULL, true);
+ free(m);
+
+ if (r < 0)
+ goto finish;
+ }
+ } else if (startswith(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(t, "Description:")) {
+ char *d;
+
+ state = LSB_DESCRIPTION;
+
+ if (!(d = strdup(strstrip(t+12)))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(u->meta.description);
+ u->meta.description = d;
+
+ } else if (startswith(t, "Short-Description:") &&
+ !u->meta.description) {
+ char *d;
+
+ /* We use the short description only
+ * if no long description is set. */
+
+ state = LSB;
+
+ if (!(d = strdup(strstrip(t+18)))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ u->meta.description = d;
+
+ } else if (state == LSB_DESCRIPTION) {
+
+ if (startswith(l, "#\t") || startswith(l, "# ")) {
+ char *d;
+
+ assert(u->meta.description);
+ if (asprintf(&d, "%s %s", u->meta.description, t) < 0) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ free(u->meta.description);
+ u->meta.description = d;
+ } else
+ state = LSB;
+ }
+ }
+ }
+
+ if ((r = sysv_exec_commands(s)) < 0)
+ goto finish;
+
+ 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. */
+
+ if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0 ||
+ (r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
+ goto finish;
+
+ } else
+ /* Don't timeout special services during boot (like fsck) */
+ s->timeout_usec = 0;
+
+ /* Special setting for all SysV services */
+ s->valid_no_process = true;
+ s->kill_mode = KILL_PROCESS_GROUP;
+
+ u->meta.load_state = UNIT_LOADED;
+ r = 0;
+
+finish:
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+static int service_load_sysv_name(Service *s, const char *name) {
+ char **p;
+
+ assert(s);
+ assert(name);
+
+ STRV_FOREACH(p, UNIT(s)->meta.manager->sysvinit_path) {
+ char *path;
+ int r;
+
+ if (asprintf(&path, "%s/%s", *p, name) < 0)
+ return -ENOMEM;
+
+ assert(endswith(path, ".service"));
+ path[strlen(path)-8] = 0;
+
+ r = service_load_sysv_path(s, path);
+
+ if (r >= 0 && UNIT(s)->meta.load_state == UNIT_STUB) {
+ /* Try Debian style .sh source'able init scripts */
+ strcat(path, ".sh");
+ r = service_load_sysv_path(s, path);
+ }
+
+ free(path);
+
+ if (r >= 0 && UNIT(s)->meta.load_state == UNIT_STUB) {
+ /* Try Suse style boot.xxxx init scripts */
+
+ if (asprintf(&path, "%s/boot.%s", *p, name) < 0)
+ return -ENOMEM;
+
+ path[strlen(path)-8] = 0;
+ r = service_load_sysv_path(s, path);
+ free(path);
+ }
+
+ if (r < 0)
+ return r;
+
+ if ((UNIT(s)->meta.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)->meta.manager->sysvinit_path))
+ return 0;
+
+ if ((t = UNIT(s)->meta.id))
+ if ((r = service_load_sysv_name(s, t)) < 0)
+ return r;
+
+ if (UNIT(s)->meta.load_state == UNIT_STUB)
+ SET_FOREACH(t, UNIT(s)->meta.names, i) {
+ if (t == UNIT(s)->meta.id)
+ continue;
+
+ if ((r == service_load_sysv_name(s, t)) < 0)
+ return r;
+
+ if (UNIT(s)->meta.load_state != UNIT_STUB)
+ break;
+ }
+
+ return 0;
+}
+
+static int service_add_bus_name(Service *s) {
+ char *n;
+ int r;
+
+ assert(s);
+ assert(s->bus_name);
+
+ if (asprintf(&n, "dbus-%s.service", s->bus_name) < 0)
+ return 0;
+
+ r = unit_merge_by_name(UNIT(s), n);
+ free(n);
+
+ return r;
+}
+
+static int service_verify(Service *s) {
+ assert(s);
+
+ if (UNIT(s)->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ if (!s->exec_command[SERVICE_EXEC_START]) {
+ log_error("%s lacks ExecStart setting. Refusing.", UNIT(s)->meta.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)->meta.id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+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;
+
+ /* Load a classic init script as a fallback, if we couldn't find anything */
+ if (u->meta.load_state == UNIT_STUB)
+ if ((r = service_load_sysv(s)) < 0)
+ return r;
+
+ /* Still nothing found? Then let's give up */
+ if (u->meta.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->meta.load_state == UNIT_LOADED) {
+ if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroup(u)) < 0)
+ return r;
+
+ if ((r = sysv_chkconfig_order(s)) < 0)
+ return r;
+
+ if (s->bus_name) {
+ if ((r = service_add_bus_name(s)) < 0)
+ return r;
+
+ if ((r = unit_watch_bus_name(u, s->bus_name)) < 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"
+ "%sPermissionsStartOnly: %s\n"
+ "%sRootDirectoryStartOnly: %s\n"
+ "%sValidNoProcess: %s\n"
+ "%sKillMode: %s\n"
+ "%sType: %s\n",
+ prefix, service_state_to_string(s->state),
+ prefix, yes_no(s->permissions_start_only),
+ prefix, yes_no(s->root_directory_start_only),
+ prefix, yes_no(s->valid_no_process),
+ prefix, kill_mode_to_string(s->kill_mode),
+ prefix, service_type_to_string(s->type));
+
+ if (s->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %llu\n",
+ prefix, (unsigned long long) s->control_pid);
+
+ if (s->main_pid > 0)
+ fprintf(f,
+ "%sMain PID: %llu\n",
+ prefix, (unsigned long long) s->main_pid);
+
+ 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));
+
+ 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);
+ }
+
+ if (s->sysv_path)
+ fprintf(f,
+ "%sSysV Init Script Path: %s\n"
+ "%sSysV Init Script has LSB Header: %s\n",
+ prefix, s->sysv_path,
+ prefix, yes_no(s->sysv_has_lsb));
+
+ 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);
+
+ free(p2);
+}
+
+static int service_load_pid_file(Service *s) {
+ char *k;
+ unsigned long p;
+ int r;
+
+ assert(s);
+
+ if (s->main_pid_known)
+ return 0;
+
+ assert(s->main_pid <= 0);
+
+ if (!s->pid_file)
+ return -ENOENT;
+
+ if ((r = read_one_line_file(s->pid_file, &k)) < 0)
+ return r;
+
+ if ((r = safe_atolu(k, &p)) < 0) {
+ free(k);
+ return r;
+ }
+
+ if ((unsigned long) (pid_t) p != p)
+ return -ERANGE;
+
+ if (kill((pid_t) p, 0) < 0 && errno != EPERM) {
+ log_warning("PID %llu read from file %s does not exist. Your service or init script might be broken.",
+ (unsigned long long) p, s->pid_file);
+ return -ESRCH;
+ }
+
+ if ((r = unit_watch_pid(UNIT(s), (pid_t) p)) < 0)
+ /* FIXME: we need to do something here */
+ return r;
+
+ s->main_pid = (pid_t) p;
+ s->main_pid_known = true;
+
+ return 0;
+}
+
+static int service_get_sockets(Service *s, Set **_set) {
+ Set *set;
+ Iterator i;
+ char *t;
+ int r;
+
+ assert(s);
+ assert(_set);
+
+ /* Collects all Socket objects that belong to this
+ * service. Note that a service might have multiple sockets
+ * via multiple names. */
+
+ if (!(set = set_new(NULL, NULL)))
+ return -ENOMEM;
+
+ SET_FOREACH(t, UNIT(s)->meta.names, i) {
+ char *k;
+ Unit *p;
+
+ /* Look for all socket objects that go by any of our
+ * units and collect their fds */
+
+ if (!(k = unit_name_change_suffix(t, ".socket"))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ p = manager_get_unit(UNIT(s)->meta.manager, k);
+ free(k);
+
+ if (!p)
+ continue;
+
+ if ((r = set_put(set, p)) < 0)
+ goto fail;
+ }
+
+ *_set = set;
+ return 0;
+
+fail:
+ set_free(set);
+ return r;
+}
+
+static int service_notify_sockets_dead(Service *s) {
+ Iterator i;
+ Set *set;
+ Socket *sock;
+ int r;
+
+ assert(s);
+
+ /* Notifies all our sockets when we die */
+ if ((r = service_get_sockets(s, &set)) < 0)
+ return r;
+
+ SET_FOREACH(sock, set, i)
+ socket_notify_service_dead(sock);
+
+ set_free(set);
+
+ return 0;
+}
+
+static void service_set_state(Service *s, ServiceState state) {
+ ServiceState old_state;
+ assert(s);
+
+ old_state = s->state;
+ s->state = state;
+
+ 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);
+
+ 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_MAINTAINANCE ||
+ state == SERVICE_AUTO_RESTART)
+ service_notify_sockets_dead(s);
+
+ if (state != SERVICE_START_PRE &&
+ state != SERVICE_START &&
+ !(state == SERVICE_DEAD && UNIT(s)->meta.job))
+ service_close_socket_fd(s);
+
+ if (old_state != state)
+ log_debug("%s changed %s -> %s", UNIT(s)->meta.id, service_state_to_string(old_state), service_state_to_string(state));
+
+ unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state]);
+}
+
+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_usec > 0) {
+ usec_t k;
+
+ k = s->deserialized_state == SERVICE_AUTO_RESTART ? s->restart_usec : s->timeout_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->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;
+
+ 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;
+ Set *set;
+ Socket *sock;
+
+ assert(s);
+ assert(fds);
+ assert(n_fds);
+
+ if ((r = service_get_sockets(s, &set)) < 0)
+ return r;
+
+ SET_FOREACH(sock, set, i) {
+ int *cfds;
+ unsigned cn_fds;
+
+ 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);
+ memcpy(t+rn_fds, cfds, cn_fds);
+ free(rfds);
+ free(cfds);
+
+ rfds = t;
+ rn_fds = rn_fds+cn_fds;
+ }
+ }
+
+ *fds = rfds;
+ *n_fds = rn_fds;
+
+ set_free(set);
+
+ return 0;
+
+fail:
+ set_free(set);
+ free(rfds);
+
+ return r;
+}
+
+static int service_spawn(
+ Service *s,
+ ExecCommand *c,
+ bool timeout,
+ bool pass_fds,
+ bool apply_permissions,
+ bool apply_chroot,
+ pid_t *_pid) {
+
+ pid_t pid;
+ int r;
+ int *fds = NULL;
+ unsigned n_fds = 0;
+ char **argv;
+
+ assert(s);
+ assert(c);
+ assert(_pid);
+
+ if (pass_fds) {
+ if (s->socket_fd >= 0) {
+ fds = &s->socket_fd;
+ n_fds = 1;
+ } else if ((r = service_collect_fds(s, &fds, &n_fds)) < 0)
+ goto fail;
+ }
+
+ if (timeout && s->timeout_usec) {
+ if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 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;
+ }
+
+ r = exec_spawn(c,
+ argv,
+ &s->exec_context,
+ fds, n_fds,
+ s->meta.manager->environment,
+ apply_permissions,
+ apply_chroot,
+ UNIT(s)->meta.manager->confirm_spawn,
+ UNIT(s)->meta.cgroup_bondings,
+ &pid);
+
+ strv_free(argv);
+ if (r < 0)
+ goto fail;
+
+ if (fds) {
+ if (s->socket_fd >= 0)
+ service_close_socket_fd(s);
+ else
+ free(fds);
+ }
+
+ if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
+ /* FIXME: we need to do something here */
+ goto fail;
+
+ *_pid = pid;
+
+ return 0;
+
+fail:
+ free(fds);
+
+ 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)
+ 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 (s->valid_no_process)
+ return -EAGAIN;
+
+ if ((r = cgroup_bonding_is_empty_list(UNIT(s)->meta.cgroup_bondings)) < 0)
+ return r;
+
+ return !r;
+}
+
+static void service_enter_dead(Service *s, bool success, bool allow_restart) {
+ int r;
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ if (allow_restart &&
+ s->allow_restart &&
+ (s->restart == SERVICE_RESTART_ALWAYS ||
+ (s->restart == SERVICE_RESTART_ON_SUCCESS && !s->failure))) {
+
+ if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_AUTO_RESTART);
+ } else
+ service_set_state(s, s->failure ? SERVICE_MAINTAINANCE : SERVICE_DEAD);
+
+ return;
+
+fail:
+ log_warning("%s failed to run install restart timer: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_dead(s, false, false);
+}
+
+static void service_enter_signal(Service *s, ServiceState state, bool success);
+
+static void service_enter_stop_post(Service *s, bool success) {
+ int r;
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ service_unwatch_control_pid(s);
+
+ s->control_command_id = SERVICE_EXEC_STOP_POST;
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) {
+ if ((r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ &s->control_pid)) < 0)
+ goto fail;
+
+
+ service_set_state(s, SERVICE_STOP_POST);
+ } else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to run stop-post executable: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+}
+
+static void service_enter_signal(Service *s, ServiceState state, bool success) {
+ int r;
+ bool sent = false;
+
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ if (s->kill_mode != KILL_NONE) {
+ int sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? SIGTERM : SIGKILL;
+
+ if (s->kill_mode == KILL_CONTROL_GROUP) {
+
+ if ((r = cgroup_bonding_kill_list(UNIT(s)->meta.cgroup_bondings, sig)) < 0) {
+ if (r != -EAGAIN && r != -ESRCH)
+ goto fail;
+ } else
+ sent = true;
+ }
+
+ if (!sent) {
+ r = 0;
+
+ if (s->main_pid > 0) {
+ if (kill(s->kill_mode == KILL_PROCESS ? s->main_pid : -s->main_pid, sig) < 0 && errno != ESRCH)
+ r = -errno;
+ else
+ sent = true;
+ }
+
+ if (s->control_pid > 0) {
+ if (kill(s->kill_mode == KILL_PROCESS ? s->control_pid : -s->control_pid, sig) < 0 && errno != ESRCH)
+ r = -errno;
+ else
+ sent = true;
+ }
+
+ if (r < 0)
+ goto fail;
+ }
+ }
+
+ if (sent && (s->main_pid > 0 || s->control_pid > 0)) {
+ if (s->timeout_usec > 0)
+ if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
+ goto fail;
+
+ service_set_state(s, state);
+ } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+ service_enter_stop_post(s, true);
+ else
+ service_enter_dead(s, true, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", UNIT(s)->meta.id, strerror(-r));
+
+ if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+ service_enter_stop_post(s, false);
+ else
+ service_enter_dead(s, false, true);
+}
+
+static void service_enter_stop(Service *s, bool success) {
+ int r;
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ service_unwatch_control_pid(s);
+
+ s->control_command_id = SERVICE_EXEC_STOP;
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) {
+ if ((r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ &s->control_pid)) < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_STOP);
+ } else
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to run stop executable: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
+}
+
+static void service_enter_running(Service *s, bool success) {
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ if (main_pid_good(s) != 0 &&
+ cgroup_good(s) != 0 &&
+ (s->bus_name_good || s->type != SERVICE_DBUS))
+ service_set_state(s, SERVICE_RUNNING);
+ else if (s->valid_no_process)
+ service_set_state(s, SERVICE_EXITED);
+ else
+ service_enter_stop(s, true);
+}
+
+static void service_enter_start_post(Service *s) {
+ int r;
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ s->control_command_id = SERVICE_EXEC_START_POST;
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) {
+ if ((r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ &s->control_pid)) < 0)
+ goto fail;
+
+
+ service_set_state(s, SERVICE_START_POST);
+ } else
+ service_enter_running(s, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to run start-post executable: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_stop(s, false);
+}
+
+static void service_enter_start(Service *s) {
+ pid_t pid;
+ int r;
+
+ assert(s);
+
+ assert(s->exec_command[SERVICE_EXEC_START]);
+ assert(!s->exec_command[SERVICE_EXEC_START]->command_next);
+
+ if (s->type == SERVICE_FORKING)
+ service_unwatch_control_pid(s);
+ else
+ service_unwatch_main_pid(s);
+
+ if ((r = service_spawn(s,
+ s->exec_command[SERVICE_EXEC_START],
+ s->type == SERVICE_FORKING || s->type == SERVICE_DBUS,
+ true,
+ true,
+ true,
+ &pid)) < 0)
+ goto fail;
+
+ if (s->type == SERVICE_SIMPLE) {
+ /* For simple services we immediately start
+ * the START_POST binaries. */
+
+ s->main_pid = pid;
+ s->main_pid_known = true;
+
+ 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;
+
+ s->control_command_id = SERVICE_EXEC_START;
+ s->control_command = s->exec_command[SERVICE_EXEC_START];
+ service_set_state(s, SERVICE_START);
+
+ } else if (s->type == SERVICE_FINISH ||
+ s->type == SERVICE_DBUS) {
+
+ /* For finishing 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. */
+
+ s->main_pid = pid;
+ s->main_pid_known = true;
+
+ service_set_state(s, SERVICE_START);
+ } else
+ assert_not_reached("Unknown service type");
+
+ return;
+
+fail:
+ log_warning("%s failed to run start exectuable: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+}
+
+static void service_enter_start_pre(Service *s) {
+ int r;
+
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ s->control_command_id = SERVICE_EXEC_START_PRE;
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) {
+ if ((r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ &s->control_pid)) < 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 executable: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_dead(s, false, true);
+}
+
+static void service_enter_restart(Service *s) {
+ int r;
+ assert(s);
+
+ service_enter_dead(s, true, false);
+
+ if ((r = manager_add_job(UNIT(s)->meta.manager, JOB_START, UNIT(s), JOB_FAIL, false, NULL)) < 0)
+ goto fail;
+
+ log_debug("%s scheduled restart job.", UNIT(s)->meta.id);
+ return;
+
+fail:
+
+ log_warning("%s failed to schedule restart job: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_dead(s, false, false);
+}
+
+static void service_enter_reload(Service *s) {
+ int r;
+
+ assert(s);
+
+ service_unwatch_control_pid(s);
+
+ s->control_command_id = SERVICE_EXEC_RELOAD;
+ if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) {
+ if ((r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ &s->control_pid)) < 0)
+ goto fail;
+
+ service_set_state(s, SERVICE_RELOAD);
+ } else
+ service_enter_running(s, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to run reload executable: %s", UNIT(s)->meta.id, strerror(-r));
+ service_enter_stop(s, false);
+}
+
+static void service_run_next(Service *s, bool success) {
+ int r;
+
+ assert(s);
+ assert(s->control_command);
+ assert(s->control_command->command_next);
+
+ if (!success)
+ s->failure = true;
+
+ s->control_command = s->control_command->command_next;
+
+ service_unwatch_control_pid(s);
+
+ if ((r = service_spawn(s,
+ s->control_command,
+ true,
+ false,
+ !s->permissions_start_only,
+ !s->root_directory_start_only,
+ &s->control_pid)) < 0)
+ goto fail;
+
+ return;
+
+fail:
+ log_warning("%s failed to run spawn next executable: %s", UNIT(s)->meta.id, strerror(-r));
+
+ if (s->state == SERVICE_START_PRE)
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+ else if (s->state == SERVICE_STOP)
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
+ else if (s->state == SERVICE_STOP_POST)
+ service_enter_dead(s, false, true);
+ else
+ service_enter_stop(s, false);
+}
+
+static int service_start(Unit *u) {
+ Service *s = SERVICE(u);
+
+ 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;
+
+ assert(s->state == SERVICE_DEAD || s->state == SERVICE_MAINTAINANCE || s->state == SERVICE_AUTO_RESTART);
+
+ /* Make sure we don't enter a busy loop of some kind. */
+ if (!ratelimit_test(&s->ratelimit)) {
+ log_warning("%s start request repeated too quickly, refusing to start.", u->meta.id);
+ return -EAGAIN;
+ }
+
+ s->failure = false;
+ s->main_pid_known = false;
+ s->allow_restart = true;
+
+ service_enter_start_pre(s);
+ return 0;
+}
+
+static int service_stop(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ /* Cannot do this now */
+ if (s->state == SERVICE_START_PRE ||
+ s->state == SERVICE_START ||
+ s->state == SERVICE_START_POST ||
+ s->state == SERVICE_RELOAD)
+ return -EAGAIN;
+
+ /* 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;
+
+ if (s->state == SERVICE_AUTO_RESTART) {
+ service_set_state(s, SERVICE_DEAD);
+ return 0;
+ }
+
+ assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED);
+
+ /* This is a user request, so don't do restarts on this
+ * shutdown. */
+ s->allow_restart = false;
+
+ service_enter_stop(s, true);
+ 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, "failure", yes_no(s->failure));
+
+ if (s->control_pid > 0)
+ unit_serialize_item_format(u, f, "control-pid", "%u", (unsigned) (s->control_pid));
+
+ if (s->main_pid > 0)
+ unit_serialize_item_format(u, f, "main-pid", "%u", (unsigned) (s->main_pid));
+
+ unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known));
+
+ /* 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);
+ }
+
+ return 0;
+}
+
+static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+ Service *s = SERVICE(u);
+ int r;
+
+ 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, "failure")) {
+ int b;
+
+ if ((b = parse_boolean(value)) < 0)
+ log_debug("Failed to parse failure value %s", value);
+ else
+ s->failure = b || s->failure;
+ } else if (streq(key, "control-pid")) {
+ unsigned pid;
+
+ if ((r = safe_atou(value, &pid)) < 0 || pid <= 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ s->control_pid = (pid_t) pid;
+ } else if (streq(key, "main-pid")) {
+ unsigned pid;
+
+ if ((r = safe_atou(value, &pid)) < 0 || pid <= 0)
+ log_debug("Failed to parse main-pid value %s", value);
+ else
+ s->main_pid = (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, "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
+ log_debug("Unknown serialization key '%s'", key);
+
+ return 0;
+}
+
+static UnitActiveState service_active_state(Unit *u) {
+ assert(u);
+
+ return state_translation_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);
+
+ return !!s->sysv_path;
+}
+
+static bool service_check_snapshot(Unit *u) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ return !s->got_socket_fd;
+}
+
+static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Service *s = SERVICE(u);
+ bool success;
+
+ assert(s);
+ assert(pid >= 0);
+
+ success = is_clean_exit(code, status);
+ s->failure = s->failure || !success;
+
+ if (s->main_pid == pid) {
+
+ exec_status_fill(&s->main_exec_status, pid, code, status);
+ s->main_pid = 0;
+
+ if (s->type == SERVICE_SIMPLE || s->type == SERVICE_FINISH) {
+ assert(s->exec_command[SERVICE_EXEC_START]);
+ s->exec_command[SERVICE_EXEC_START]->exec_status = s->main_exec_status;
+ }
+
+ log_debug("%s: main process exited, code=%s, status=%i", u->meta.id, sigchld_code_to_string(code), status);
+
+ /* The service exited, so the service is officially
+ * gone. */
+
+ 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:
+ assert(s->type == SERVICE_FINISH);
+
+ /* This was our main goal, so let's go on */
+ if (success)
+ service_enter_start_post(s);
+ else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+ break;
+
+ case SERVICE_RUNNING:
+ service_enter_running(s, success);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+
+ if (!control_pid_good(s))
+ service_enter_stop_post(s, success);
+
+ /* 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) {
+
+ if (s->control_command)
+ exec_status_fill(&s->control_command->exec_status, pid, code, status);
+
+ s->control_pid = 0;
+
+ log_debug("%s: control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
+
+ /* If we are shutting things down anyway we
+ * don't care about failing commands. */
+
+ if (s->control_command && s->control_command->command_next && success) {
+
+ /* There is another command to *
+ * execute, so let's do that. */
+
+ log_debug("%s running next command for state %s", u->meta.id, service_state_to_string(s->state));
+ service_run_next(s, success);
+
+ } 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->meta.id, service_state_to_string(s->state));
+
+ switch (s->state) {
+
+ case SERVICE_START_PRE:
+ if (success)
+ service_enter_start(s);
+ else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+ break;
+
+ case SERVICE_START:
+ assert(s->type == SERVICE_FORKING);
+
+ /* Let's try to load the pid
+ * file here if we can. We
+ * ignore the return value,
+ * since the PID file might
+ * actually be created by a
+ * START_POST script */
+
+ if (success) {
+ if (s->pid_file)
+ service_load_pid_file(s);
+
+ service_enter_start_post(s);
+ } else
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+
+ break;
+
+ case SERVICE_START_POST:
+ if (success && s->pid_file && !s->main_pid_known) {
+ int r;
+
+ /* Hmm, let's see if we can
+ * load the pid now after the
+ * start-post scripts got
+ * executed. */
+
+ if ((r = service_load_pid_file(s)) < 0)
+ log_warning("%s: failed to load PID file %s: %s", UNIT(s)->meta.id, s->pid_file, strerror(-r));
+ }
+
+ /* Fall through */
+
+ case SERVICE_RELOAD:
+ if (success)
+ service_enter_running(s, true);
+ else
+ service_enter_stop(s, false);
+
+ break;
+
+ case SERVICE_STOP:
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, success);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+ if (main_pid_good(s) <= 0)
+ service_enter_stop_post(s, success);
+
+ /* 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, success, true);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+ }
+ } else
+ assert_not_reached("Got SIGCHLD for unkown PID");
+}
+
+static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+ assert(elapsed == 1);
+
+ assert(w == &s->timer_watch);
+
+ switch (s->state) {
+
+ case SERVICE_START_PRE:
+ case SERVICE_START:
+ log_warning("%s operation timed out. Terminating.", u->meta.id);
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+ break;
+
+ case SERVICE_START_POST:
+ case SERVICE_RELOAD:
+ log_warning("%s operation timed out. Stopping.", u->meta.id);
+ service_enter_stop(s, false);
+ break;
+
+ case SERVICE_STOP:
+ log_warning("%s stopping timed out. Terminating.", u->meta.id);
+ service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
+ break;
+
+ case SERVICE_STOP_SIGTERM:
+ log_warning("%s stopping timed out. Killing.", u->meta.id);
+ service_enter_signal(s, SERVICE_STOP_SIGKILL, false);
+ break;
+
+ case SERVICE_STOP_SIGKILL:
+ /* Uh, wie 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->meta.id);
+ service_enter_stop_post(s, false);
+ break;
+
+ case SERVICE_STOP_POST:
+ log_warning("%s stopping timed out (2). Terminating.", u->meta.id);
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+ break;
+
+ case SERVICE_FINAL_SIGTERM:
+ log_warning("%s stopping timed out (2). Killing.", u->meta.id);
+ service_enter_signal(s, SERVICE_FINAL_SIGKILL, false);
+ break;
+
+ case SERVICE_FINAL_SIGKILL:
+ log_warning("%s still around after SIGKILL (2). Entering maintainance mode.", u->meta.id);
+ service_enter_dead(s, false, true);
+ break;
+
+ case SERVICE_AUTO_RESTART:
+ log_debug("%s holdoff time over, scheduling restart.", u->meta.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->meta.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_RUNNING:
+ service_enter_running(s, true);
+ break;
+
+ default:
+ ;
+ }
+}
+
+static int service_enumerate(Manager *m) {
+ char **p;
+ unsigned i;
+ DIR *d = NULL;
+ char *path = NULL, *fpath = NULL, *name = NULL;
+ int r;
+
+ assert(m);
+
+ STRV_FOREACH(p, m->sysvrcnd_path)
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
+ struct dirent *de;
+
+ free(path);
+ path = NULL;
+ if (asprintf(&path, "%s/%s", *p, rcnd_table[i].path) < 0) {
+ 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))) {
+ Unit *service;
+ 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 = NULL;
+ if (asprintf(&fpath, "%s/%s/%s", *p, rcnd_table[i].path, de->d_name) < 0) {
+ 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 = new(char, strlen(de->d_name) - 3 + 8 + 1))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (startswith(de->d_name+3, "boot."))
+ /* Drop SuSE-style boot. prefix */
+ strcpy(stpcpy(name, de->d_name + 3 + 5), ".service");
+ else if (endswith(de->d_name+3, ".sh"))
+ /* Drop Debian-style .sh suffix */
+ strcpy(stpcpy(name, de->d_name + 3) - 3, ".service");
+ else
+ /* Normal init scripts */
+ strcpy(stpcpy(name, de->d_name + 3), ".service");
+
+ if ((r = manager_load_unit_prepare(m, name, NULL, &service)) < 0) {
+ log_warning("Failed to prepare unit %s: %s", name, strerror(-r));
+ continue;
+ }
+
+ if (de->d_name[0] == 'S' &&
+ (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_BASIC))
+ SERVICE(service)->sysv_start_priority =
+ MAX(a*10 + b, SERVICE(service)->sysv_start_priority);
+
+ manager_dispatch_load_queue(m);
+ service = unit_follow_merge(service);
+
+ if (de->d_name[0] == 'S') {
+ Unit *runlevel_target;
+
+ if ((r = manager_load_unit(m, rcnd_table[i].target, NULL, &runlevel_target)) < 0)
+ goto finish;
+
+ if ((r = unit_add_dependency(runlevel_target, UNIT_WANTS, service, true)) < 0)
+ goto finish;
+
+ if ((r = unit_add_dependency(service, UNIT_BEFORE, runlevel_target, true)) < 0)
+ goto finish;
+
+ } else if (de->d_name[0] == 'K' && rcnd_table[i].type == RUNLEVEL_DOWN) {
+ Unit *shutdown_target;
+
+ /* 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 distuingish here
+ * between the runlevels 0 and
+ * 6 and just add them to the
+ * special shutdown target. */
+
+ if ((r = manager_load_unit(m, SPECIAL_SHUTDOWN_TARGET, NULL, &shutdown_target)) < 0)
+ goto finish;
+
+ if ((r = unit_add_dependency(service, UNIT_CONFLICTS, shutdown_target, true)) < 0)
+ goto finish;
+
+ if ((r = unit_add_dependency(service, UNIT_BEFORE, shutdown_target, true)) < 0)
+ goto finish;
+ }
+ }
+ }
+
+ r = 0;
+
+finish:
+ free(path);
+ free(fpath);
+ free(name);
+
+ if (d)
+ closedir(d);
+
+ return r;
+}
+
+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->meta.id, name, old_owner, new_owner);
+ else if (old_owner)
+ log_debug("%s's D-Bus name %s no longer registered by %s", u->meta.id, name, old_owner);
+ else
+ log_debug("%s's D-Bus name %s now registered by %s", u->meta.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, true);
+ 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->meta.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->meta.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))
+ s->main_pid = pid;
+}
+
+int service_set_socket_fd(Service *s, int fd) {
+ 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)->meta.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;
+ return 0;
+}
+
+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_MAINTAINANCE] = "maintainance",
+ [SERVICE_AUTO_RESTART] = "auto-restart",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
+
+static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
+ [SERVICE_ONCE] = "once",
+ [SERVICE_RESTART_ON_SUCCESS] = "restart-on-success",
+ [SERVICE_RESTART_ALWAYS] = "restart-always",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart);
+
+static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
+ [SERVICE_FORKING] = "forking",
+ [SERVICE_SIMPLE] = "simple",
+ [SERVICE_FINISH] = "finish",
+ [SERVICE_DBUS] = "dbus"
+};
+
+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);
+
+const UnitVTable service_vtable = {
+ .suffix = ".service",
+
+ .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,
+
+ .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,
+
+ .cgroup_notify_empty = service_cgroup_notify_event,
+
+ .bus_name_owner_change = service_bus_name_owner_change,
+ .bus_query_pid_done = service_bus_query_pid_done,
+
+ .bus_message_handler = bus_service_message_handler,
+
+ .enumerate = service_enumerate
+};
diff --git a/src/service.h b/src/service.h
new file mode 100644
index 0000000000..40bd57e256
--- /dev/null
+++ b/src/service.h
@@ -0,0 +1,147 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooservicehfoo
+#define fooservicehfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Service Service;
+
+#include "unit.h"
+#include "ratelimit.h"
+
+typedef enum ServiceState {
+ SERVICE_DEAD,
+ SERVICE_START_PRE,
+ SERVICE_START,
+ SERVICE_START_POST,
+ SERVICE_RUNNING,
+ SERVICE_EXITED, /* Nothing is running anymore, but ValidNoProcess is true, ehnce 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_MAINTAINANCE,
+ SERVICE_AUTO_RESTART,
+ _SERVICE_STATE_MAX,
+ _SERVICE_STATE_INVALID = -1
+} ServiceState;
+
+typedef enum ServiceRestart {
+ SERVICE_ONCE,
+ SERVICE_RESTART_ON_SUCCESS,
+ SERVICE_RESTART_ALWAYS,
+ _SERVICE_RESTART_MAX,
+ _SERVICE_RESTART_INVALID = -1
+} ServiceRestart;
+
+typedef enum ServiceType {
+ SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */
+ SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */
+ SERVICE_FINISH, /* 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_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;
+
+struct Service {
+ Meta meta;
+
+ ServiceType type;
+ ServiceRestart restart;
+
+ /* If set we'll read the main daemon PID from this file */
+ char *pid_file;
+
+ usec_t restart_usec;
+ usec_t timeout_usec;
+
+ ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX];
+ ExecContext exec_context;
+
+ bool permissions_start_only;
+ bool root_directory_start_only;
+ bool valid_no_process;
+
+ ServiceState state, deserialized_state;
+
+ KillMode kill_mode;
+
+ ExecStatus main_exec_status;
+
+ ExecCommand *control_command;
+ ServiceExecCommand control_command_id;
+ pid_t main_pid, control_pid;
+ bool main_pid_known:1;
+
+ /* If we shut down, remember why */
+ bool failure:1;
+
+ bool bus_name_good:1;
+
+ bool allow_restart:1;
+
+ bool got_socket_fd:1;
+
+ bool sysv_has_lsb:1;
+ char *sysv_path;
+ int sysv_start_priority;
+ char *sysv_runlevels;
+
+ char *bus_name;
+
+ RateLimit ratelimit;
+
+ int socket_fd;
+
+ Watch timer_watch;
+};
+
+extern const UnitVTable service_vtable;
+
+int service_set_socket_fd(Service *s, int fd);
+
+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);
+
+#endif
diff --git a/src/set.c b/src/set.c
new file mode 100644
index 0000000000..efd20db536
--- /dev/null
+++ b/src/set.c
@@ -0,0 +1,114 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <stdlib.h>
+
+#include "set.h"
+#include "hashmap.h"
+
+#define MAKE_SET(h) ((Set*) (h))
+#define MAKE_HASHMAP(s) ((Hashmap*) (s))
+
+/* For now this is not much more than a wrapper around a hashmap */
+
+Set *set_new(hash_func_t hash_func, compare_func_t compare_func) {
+ return MAKE_SET(hashmap_new(hash_func, compare_func));
+}
+
+void set_free(Set* s) {
+ hashmap_free(MAKE_HASHMAP(s));
+}
+
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) {
+ return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func);
+}
+
+int set_put(Set *s, void *value) {
+ return hashmap_put(MAKE_HASHMAP(s), value, value);
+}
+
+int set_replace(Set *s, void *value) {
+ return hashmap_replace(MAKE_HASHMAP(s), value, value);
+}
+
+void *set_get(Set *s, void *value) {
+ return hashmap_get(MAKE_HASHMAP(s), value);
+}
+
+void *set_remove(Set *s, void *value) {
+ return hashmap_remove(MAKE_HASHMAP(s), value);
+}
+
+int set_remove_and_put(Set *s, void *old_value, void *new_value) {
+ return hashmap_remove_and_put(MAKE_HASHMAP(s), old_value, new_value, new_value);
+}
+
+unsigned set_size(Set *s) {
+ return hashmap_size(MAKE_HASHMAP(s));
+}
+
+bool set_isempty(Set *s) {
+ return hashmap_isempty(MAKE_HASHMAP(s));
+}
+
+void *set_iterate(Set *s, Iterator *i) {
+ return hashmap_iterate(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_backwards(Set *s, Iterator *i) {
+ return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_skip(Set *s, void *value, Iterator *i) {
+ return hashmap_iterate_skip(MAKE_HASHMAP(s), value, i);
+}
+
+void *set_steal_first(Set *s) {
+ return hashmap_steal_first(MAKE_HASHMAP(s));
+}
+
+void* set_first(Set *s) {
+ return hashmap_first(MAKE_HASHMAP(s));
+}
+
+void* set_last(Set *s) {
+ return hashmap_last(MAKE_HASHMAP(s));
+}
+
+int set_merge(Set *s, Set *other) {
+ return hashmap_merge(MAKE_HASHMAP(s), MAKE_HASHMAP(other));
+}
+
+void set_move(Set *s, Set *other) {
+ return hashmap_move(MAKE_HASHMAP(s), MAKE_HASHMAP(other));
+}
+
+int set_move_one(Set *s, Set *other, void *value) {
+ return hashmap_move_one(MAKE_HASHMAP(s), MAKE_HASHMAP(other), value);
+}
+
+Set* set_copy(Set *s) {
+ return MAKE_SET(hashmap_copy(MAKE_HASHMAP(s)));
+}
+
+void set_clear(Set *s) {
+ hashmap_clear(MAKE_HASHMAP(s));
+}
diff --git a/src/set.h b/src/set.h
new file mode 100644
index 0000000000..dd2e91dd11
--- /dev/null
+++ b/src/set.h
@@ -0,0 +1,68 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foosethfoo
+#define foosethfoo
+
+/***
+ 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 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/>.
+***/
+
+/* Pretty straightforward set implementation. Internally based on the
+ * hashmap. That means that as a minor optimization a NULL set
+ * object will be treated as empty set for all read
+ * operations. That way it is not necessary to instantiate an object
+ * for each set use. */
+
+#include "hashmap.h"
+
+typedef struct Set Set;
+
+Set *set_new(hash_func_t hash_func, compare_func_t compare_func);
+void set_free(Set* s);
+Set* set_copy(Set *s);
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func);
+
+int set_put(Set *s, void *value);
+int set_replace(Set *s, void *value);
+void *set_get(Set *s, void *value);
+void *set_remove(Set *s, void *value);
+int set_remove_and_put(Set *s, void *old_value, void *new_value);
+
+int set_merge(Set *s, Set *other);
+void set_move(Set *s, Set *other);
+int set_move_one(Set *s, Set *other, void *value);
+
+unsigned set_size(Set *s);
+bool set_isempty(Set *s);
+
+void *set_iterate(Set *s, Iterator *i);
+void *set_iterate_backwards(Set *s, Iterator *i);
+void *set_iterate_skip(Set *s, void *value, Iterator *i);
+
+void set_clear(Set *s);
+void *set_steal_first(Set *s);
+void* set_first(Set *s);
+void* set_last(Set *s);
+
+#define SET_FOREACH(e, s, i) \
+ for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i)))
+
+#define SET_FOREACH_BACKWARDS(e, s, i) \
+ for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i)))
+
+#endif
diff --git a/src/snapshot.c b/src/snapshot.c
new file mode 100644
index 0000000000..513bf66475
--- /dev/null
+++ b/src/snapshot.c
@@ -0,0 +1,276 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "unit.h"
+#include "snapshot.h"
+#include "unit-name.h"
+#include "dbus-snapshot.h"
+
+static const UnitActiveState state_translation_table[_SNAPSHOT_STATE_MAX] = {
+ [SNAPSHOT_DEAD] = UNIT_INACTIVE,
+ [SNAPSHOT_ACTIVE] = UNIT_ACTIVE
+};
+
+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)->meta.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]);
+}
+
+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->meta.dependencies[UNIT_REQUIRES], i)
+ unit_serialize_item(u, f, "requires", other->meta.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, "requires")) {
+
+ if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, value, NULL, true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, 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, 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))
+ return -EINVAL;
+
+ if (unit_name_to_type(name) != UNIT_SNAPSHOT)
+ return -EINVAL;
+
+ if (manager_get_unit(m, 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(m, name, NULL, &u);
+ free(n);
+
+ if (r < 0)
+ goto fail;
+
+ HASHMAP_FOREACH_KEY(other, k, m->units, i) {
+
+ if (UNIT_VTABLE(other)->no_snapshots)
+ continue;
+
+ if (k != other->meta.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_dependency(u, UNIT_REQUIRES, other, true)) < 0)
+ goto fail;
+
+ if ((r = unit_add_dependency(u, UNIT_AFTER, 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 = {
+ .suffix = ".snapshot",
+
+ .no_alias = true,
+ .no_instances = true,
+ .no_snapshots = true,
+ .no_gc = true,
+
+ .load = unit_load_nop,
+ .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_message_handler = bus_snapshot_message_handler
+};
diff --git a/src/snapshot.h b/src/snapshot.h
new file mode 100644
index 0000000000..959a5090ec
--- /dev/null
+++ b/src/snapshot.h
@@ -0,0 +1,52 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foosnapshothfoo
+#define foosnapshothfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Snapshot Snapshot;
+
+#include "unit.h"
+
+typedef enum SnapshotState {
+ SNAPSHOT_DEAD,
+ SNAPSHOT_ACTIVE,
+ _SNAPSHOT_STATE_MAX,
+ _SNAPSHOT_STATE_INVALID = -1
+} SnapshotState;
+
+struct Snapshot {
+ Meta meta;
+
+ SnapshotState state, deserialized_state;
+
+ bool cleanup;
+};
+
+extern const UnitVTable snapshot_vtable;
+
+int snapshot_create(Manager *m, const char *name, bool cleanup, Snapshot **s);
+void snapshot_remove(Snapshot *s);
+
+const char* snapshot_state_to_string(SnapshotState i);
+SnapshotState snapshot_state_from_string(const char *s);
+
+#endif
diff --git a/src/socket-util.c b/src/socket-util.c
new file mode 100644
index 0000000000..32f6bcb941
--- /dev/null
+++ b/src/socket-util.c
@@ -0,0 +1,468 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "macro.h"
+#include "util.h"
+#include "socket-util.h"
+
+int socket_address_parse(SocketAddress *a, const char *s) {
+ int r;
+ char *e, *n;
+ unsigned u;
+
+ assert(a);
+ assert(s);
+
+ zero(*a);
+ a->type = SOCK_STREAM;
+
+ if (*s == '[') {
+ /* IPv6 in [x:.....:z]:p notation */
+
+ if (!(e = strchr(s+1, ']')))
+ return -EINVAL;
+
+ if (!(n = strndup(s+1, e-s-1)))
+ return -ENOMEM;
+
+ errno = 0;
+ if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) {
+ free(n);
+ return errno != 0 ? -errno : -EINVAL;
+ }
+
+ free(n);
+
+ e++;
+ if (*e != ':')
+ return -EINVAL;
+
+ e++;
+ if ((r = safe_atou(e, &u)) < 0)
+ return r;
+
+ if (u <= 0 || u > 0xFFFF)
+ return -EINVAL;
+
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->size = sizeof(struct sockaddr_in6);
+
+ } else if (*s == '/') {
+ /* AF_UNIX socket */
+
+ size_t l;
+
+ l = strlen(s);
+ if (l >= sizeof(a->sockaddr.un.sun_path))
+ return -EINVAL;
+
+ a->sockaddr.un.sun_family = AF_UNIX;
+ memcpy(a->sockaddr.un.sun_path, s, l);
+ a->size = sizeof(sa_family_t) + l + 1;
+
+ } else if (*s == '@') {
+ /* Abstract AF_UNIX socket */
+ size_t l;
+
+ l = strlen(s+1);
+ if (l >= sizeof(a->sockaddr.un.sun_path) - 1)
+ return -EINVAL;
+
+ a->sockaddr.un.sun_family = AF_UNIX;
+ memcpy(a->sockaddr.un.sun_path+1, s+1, l);
+ a->size = sizeof(struct sockaddr_un);
+
+ } else {
+
+ if ((e = strchr(s, ':'))) {
+
+ if ((r = safe_atou(e+1, &u)) < 0)
+ return r;
+
+ if (u <= 0 || u > 0xFFFF)
+ return -EINVAL;
+
+ if (!(n = strndup(s, e-s)))
+ return -ENOMEM;
+
+ /* IPv4 in w.x.y.z:p notation? */
+ if ((r = inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr)) < 0) {
+ free(n);
+ return -errno;
+ }
+
+ if (r > 0) {
+ /* Gotcha, it's a traditional IPv4 address */
+ free(n);
+
+ a->sockaddr.in4.sin_family = AF_INET;
+ a->sockaddr.in4.sin_port = htons((uint16_t) u);
+ a->size = sizeof(struct sockaddr_in);
+ } else {
+ unsigned idx;
+
+ if (strlen(n) > IF_NAMESIZE-1) {
+ free(n);
+ return -EINVAL;
+ }
+
+ /* Uh, our last resort, an interface name */
+ idx = if_nametoindex(n);
+ free(n);
+
+ if (idx == 0)
+ return -EINVAL;
+
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_scope_id = idx;
+ a->sockaddr.in6.sin6_addr = in6addr_any;
+ a->size = sizeof(struct sockaddr_in6);
+
+ }
+ } else {
+
+ /* Just a port */
+ if ((r = safe_atou(s, &u)) < 0)
+ return r;
+
+ if (u <= 0 || u > 0xFFFF)
+ return -EINVAL;
+
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_addr = in6addr_any;
+ a->size = sizeof(struct sockaddr_in6);
+ }
+ }
+
+ return 0;
+}
+
+int socket_address_verify(const SocketAddress *a) {
+ assert(a);
+
+ switch (socket_address_family(a)) {
+ case AF_INET:
+ if (a->size != sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ if (a->sockaddr.in4.sin_port == 0)
+ return -EINVAL;
+
+ return 0;
+
+ case AF_INET6:
+ if (a->size != sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ if (a->sockaddr.in6.sin6_port == 0)
+ return -EINVAL;
+
+ return 0;
+
+ case AF_UNIX:
+ if (a->size < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (a->size > sizeof(sa_family_t)) {
+
+ if (a->sockaddr.un.sun_path[0] == 0) {
+ /* abstract */
+ if (a->size != sizeof(struct sockaddr_un))
+ return -EINVAL;
+ } else {
+ char *e;
+
+ /* path */
+ if (!(e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path))))
+ return -EINVAL;
+
+ if (a->size != sizeof(sa_family_t) + (e - a->sockaddr.un.sun_path) + 1)
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+
+ default:
+ return -EAFNOSUPPORT;
+ }
+}
+
+int socket_address_print(const SocketAddress *a, char **p) {
+ int r;
+ assert(a);
+ assert(p);
+
+ if ((r = socket_address_verify(a)) < 0)
+ return r;
+
+ switch (socket_address_family(a)) {
+ case AF_INET: {
+ char *ret;
+
+ if (!(ret = new(char, INET_ADDRSTRLEN+1+5+1)))
+ return -ENOMEM;
+
+ if (!inet_ntop(AF_INET, &a->sockaddr.in4.sin_addr, ret, INET_ADDRSTRLEN)) {
+ free(ret);
+ return -errno;
+ }
+
+ sprintf(strchr(ret, 0), ":%u", ntohs(a->sockaddr.in4.sin_port));
+ *p = ret;
+ return 0;
+ }
+
+ case AF_INET6: {
+ char *ret;
+
+ if (!(ret = new(char, 1+INET6_ADDRSTRLEN+2+5+1)))
+ return -ENOMEM;
+
+ ret[0] = '[';
+ if (!inet_ntop(AF_INET6, &a->sockaddr.in6.sin6_addr, ret+1, INET6_ADDRSTRLEN)) {
+ free(ret);
+ return -errno;
+ }
+
+ sprintf(strchr(ret, 0), "]:%u", ntohs(a->sockaddr.in6.sin6_port));
+ *p = ret;
+ return 0;
+ }
+
+ case AF_UNIX: {
+ char *ret;
+
+ if (a->size <= sizeof(sa_family_t)) {
+
+ if (!(ret = strdup("<unamed>")))
+ return -ENOMEM;
+
+ } else if (a->sockaddr.un.sun_path[0] == 0) {
+ /* abstract */
+
+ /* FIXME: We assume we can print the
+ * socket path here and that it hasn't
+ * more than one NUL byte. That is
+ * actually an invalid assumption */
+
+ if (!(ret = new(char, sizeof(a->sockaddr.un.sun_path)+1)))
+ return -ENOMEM;
+
+ ret[0] = '@';
+ memcpy(ret+1, a->sockaddr.un.sun_path+1, sizeof(a->sockaddr.un.sun_path)-1);
+ ret[sizeof(a->sockaddr.un.sun_path)] = 0;
+
+ } else {
+
+ if (!(ret = strdup(a->sockaddr.un.sun_path)))
+ return -ENOMEM;
+ }
+
+ *p = ret;
+ return 0;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+int socket_address_listen(
+ const SocketAddress *a,
+ int backlog,
+ SocketAddressBindIPv6Only only,
+ const char *bind_to_device,
+ mode_t directory_mode,
+ mode_t socket_mode,
+ int *ret) {
+
+ int r, fd, one;
+ assert(a);
+ assert(ret);
+
+ if ((r = socket_address_verify(a)) < 0)
+ return r;
+
+ if ((fd = socket(socket_address_family(a), a->type | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) < 0)
+ return -errno;
+
+ if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) {
+ int flag = only == SOCKET_ADDRESS_IPV6_ONLY;
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0)
+ goto fail;
+ }
+
+ if (bind_to_device)
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0)
+ goto fail;
+
+ one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
+ goto fail;
+
+ if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) {
+ mode_t old_mask;
+
+ /* Create parents */
+ mkdir_parents(a->sockaddr.un.sun_path, directory_mode);
+
+ /* Enforce the right access mode for the socket*/
+ old_mask = umask(~ socket_mode);
+
+ /* Include the original umask in our mask */
+ umask(~socket_mode | old_mask);
+
+ r = bind(fd, &a->sockaddr.sa, a->size);
+
+ if (r < 0 && errno == EADDRINUSE) {
+ /* Unlink and try again */
+ unlink(a->sockaddr.un.sun_path);
+ r = bind(fd, &a->sockaddr.sa, a->size);
+ }
+
+ umask(old_mask);
+ } else
+ r = bind(fd, &a->sockaddr.sa, a->size);
+
+ if (r < 0)
+ goto fail;
+
+ if (a->type == SOCK_STREAM)
+ if (listen(fd, backlog) < 0)
+ goto fail;
+
+ *ret = fd;
+ return 0;
+
+fail:
+ r = -errno;
+ close_nointr_nofail(fd);
+ return r;
+}
+
+bool socket_address_can_accept(const SocketAddress *a) {
+ assert(a);
+
+ return
+ a->type == SOCK_STREAM ||
+ a->type == SOCK_SEQPACKET;
+}
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
+ assert(a);
+ assert(b);
+
+ /* Invalid addresses are unequal to all */
+ if (socket_address_verify(a) < 0 ||
+ socket_address_verify(b) < 0)
+ return false;
+
+ if (a->type != b->type)
+ return false;
+
+ if (a->size != b->size)
+ return false;
+
+ if (socket_address_family(a) != socket_address_family(b))
+ return false;
+
+ switch (socket_address_family(a)) {
+
+ case AF_INET:
+ if (a->sockaddr.in4.sin_addr.s_addr != b->sockaddr.in4.sin_addr.s_addr)
+ return false;
+
+ if (a->sockaddr.in4.sin_port != b->sockaddr.in4.sin_port)
+ return false;
+
+ break;
+
+ case AF_INET6:
+ if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0)
+ return false;
+
+ if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port)
+ return false;
+
+ break;
+
+ case AF_UNIX:
+
+ if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0))
+ return false;
+
+ if (a->sockaddr.un.sun_path[0]) {
+ if (strncmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)) != 0)
+ return false;
+ } else {
+ if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path)) != 0)
+ return false;
+ }
+
+ break;
+
+ default:
+ /* Cannot compare, so we assume the addresses are different */
+ return false;
+ }
+
+ return true;
+}
+
+bool socket_address_is(const SocketAddress *a, const char *s) {
+ struct SocketAddress b;
+
+ assert(a);
+ assert(s);
+
+ if (socket_address_parse(&b, s) < 0)
+ return false;
+
+ return socket_address_equal(a, &b);
+}
+
+bool socket_address_needs_mount(const SocketAddress *a, const char *prefix) {
+ assert(a);
+
+ if (socket_address_family(a) != AF_UNIX)
+ return false;
+
+ if (a->sockaddr.un.sun_path[0] == 0)
+ return false;
+
+ return path_startswith(a->sockaddr.un.sun_path, prefix);
+}
diff --git a/src/socket-util.h b/src/socket-util.h
new file mode 100644
index 0000000000..14192167aa
--- /dev/null
+++ b/src/socket-util.h
@@ -0,0 +1,79 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foosocketutilhfoo
+#define foosocketutilhfoo
+
+/***
+ 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 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 <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <net/if.h>
+
+#include "macro.h"
+#include "util.h"
+
+typedef struct SocketAddress {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_storage storage;
+ } sockaddr;
+
+ /* We store the size here explicitly due to the weird
+ * sockaddr_un semantics for abstract sockets */
+ socklen_t size;
+
+ /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */
+ int type;
+} SocketAddress;
+
+typedef enum SocketAddressBindIPv6Only {
+ SOCKET_ADDRESS_DEFAULT,
+ SOCKET_ADDRESS_BOTH,
+ SOCKET_ADDRESS_IPV6_ONLY
+} SocketAddressBindIPv6Only;
+
+#define socket_address_family(a) ((a)->sockaddr.sa.sa_family)
+
+int socket_address_parse(SocketAddress *a, const char *s);
+int socket_address_print(const SocketAddress *a, char **p);
+int socket_address_verify(const SocketAddress *a);
+
+bool socket_address_can_accept(const SocketAddress *a);
+
+int socket_address_listen(
+ const SocketAddress *a,
+ int backlog,
+ SocketAddressBindIPv6Only only,
+ const char *bind_to_device,
+ mode_t directory_mode,
+ mode_t socket_mode,
+ int *ret);
+
+bool socket_address_is(const SocketAddress *a, const char *s);
+
+bool socket_address_equal(const SocketAddress *a, const SocketAddress *b);
+
+bool socket_address_needs_mount(const SocketAddress *a, const char *prefix);
+
+#endif
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000000..259f2733cc
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,1411 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "unit.h"
+#include "socket.h"
+#include "log.h"
+#include "load-dropin.h"
+#include "load-fragment.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "dbus-socket.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_MAINTAINANCE] = UNIT_INACTIVE,
+};
+
+static void socket_init(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(u);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ s->timer_watch.type = WATCH_INVALID;
+ s->backlog = SOMAXCONN;
+ s->timeout_usec = DEFAULT_TIMEOUT_USEC;
+ s->directory_mode = 0755;
+ s->socket_mode = 0666;
+
+ exec_context_init(&s->exec_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);
+
+ s->service = NULL;
+
+ free(s->bind_to_device);
+ s->bind_to_device = NULL;
+
+ unit_unwatch_timer(u, &s->timer_watch);
+}
+
+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)->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ if (!s->ports) {
+ log_error("%s lacks Listen setting. Refusing.", UNIT(s)->meta.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 {
+ assert(p->type == SOCKET_FIFO);
+ 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 (s->meta.load_state != UNIT_LOADED ||
+ m->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ if (!socket_needs_mount(s, m->where))
+ return 0;
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(s), true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(UNIT(s), UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int socket_add_mount_links(Socket *s) {
+ Meta *other;
+ int r;
+
+ assert(s);
+
+ LIST_FOREACH(units_per_type, other, s->meta.manager->units_per_type[UNIT_MOUNT])
+ if ((r = socket_add_one_mount_link(s, (Mount*) other)) < 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_load(Unit *u) {
+ Socket *s = SOCKET(u);
+ int r;
+
+ assert(u);
+ assert(u->meta.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->meta.load_state == UNIT_LOADED) {
+
+ if (have_non_accept_socket(s)) {
+ if ((r = unit_load_related_unit(u, ".service", (Unit**) &s->service)))
+ return r;
+
+ if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service), true)) < 0)
+ return r;
+ }
+
+ if ((r = socket_add_mount_links(s)) < 0)
+ return r;
+
+ if ((r = socket_add_device_link(s)) < 0)
+ return r;
+
+ if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroup(u)) < 0)
+ return r;
+ }
+
+ return socket_verify(s);
+}
+
+static const char* listen_lookup(int type) {
+
+ 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"
+ "%sBindIPv6Only: %s\n"
+ "%sBacklog: %u\n"
+ "%sKillMode: %s\n"
+ "%sSocketMode: %04o\n"
+ "%sDirectoryMode: %04o\n",
+ prefix, socket_state_to_string(s->state),
+ prefix, yes_no(s->bind_ipv6_only),
+ prefix, s->backlog,
+ prefix, kill_mode_to_string(s->kill_mode),
+ prefix, s->socket_mode,
+ prefix, s->directory_mode);
+
+ if (s->control_pid > 0)
+ fprintf(f,
+ "%sControl PID: %llu\n",
+ prefix, (unsigned long 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",
+ prefix, s->n_accepted);
+
+ LIST_FOREACH(port, p, s->ports) {
+
+ if (p->type == SOCKET_SOCKET) {
+ const char *t;
+ int r;
+ char *k;
+
+ if ((r = socket_address_print(&p->address, &k)) < 0)
+ t = strerror(-r);
+ else
+ t = k;
+
+ fprintf(f, "%s%s: %s\n", prefix, listen_lookup(p->address.type), k);
+ free(k);
+ } else
+ fprintf(f, "%sListenFIFO: %s\n", prefix, p->path);
+ }
+
+ exec_context_dump(&s->exec_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: {
+ 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-%llu-%llu",
+ nr,
+ (unsigned long long) ucred.pid,
+ (unsigned long 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 int socket_open_fds(Socket *s) {
+ SocketPort *p;
+ int r;
+
+ assert(s);
+
+ LIST_FOREACH(port, p, s->ports) {
+
+ if (p->fd >= 0)
+ continue;
+
+ if (p->type == SOCKET_SOCKET) {
+
+ if ((r = socket_address_listen(
+ &p->address,
+ s->backlog,
+ s->bind_ipv6_only,
+ s->bind_to_device,
+ s->directory_mode,
+ s->socket_mode,
+ &p->fd)) < 0)
+ goto rollback;
+
+ } else {
+ struct stat st;
+ assert(p->type == SOCKET_FIFO);
+
+ mkdir_parents(p->path, s->directory_mode);
+
+ if (mkfifo(p->path, s->socket_mode) < 0 && errno != EEXIST) {
+ r = -errno;
+ goto rollback;
+ }
+
+ if ((p->fd = open(p->path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW)) < 0) {
+ r = -errno;
+ goto rollback;
+ }
+
+ if (fstat(p->fd, &st) < 0) {
+ r = -errno;
+ goto rollback;
+ }
+
+ /* FIXME verify user, access mode */
+
+ if (!S_ISFIFO(st.st_mode)) {
+ r = -EEXIST;
+ goto rollback;
+ }
+ }
+ }
+
+ return 0;
+
+rollback:
+ socket_close_fds(s);
+ 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",
+ s->meta.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]);
+}
+
+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,
+ s->meta.manager->environment,
+ true,
+ true,
+ UNIT(s)->meta.manager->confirm_spawn,
+ UNIT(s)->meta.cgroup_bondings,
+ &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, bool success) {
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ socket_set_state(s, s->failure ? SOCKET_MAINTAINANCE : SOCKET_DEAD);
+}
+
+static void socket_enter_signal(Socket *s, SocketState state, bool success);
+
+static void socket_enter_stop_post(Socket *s, bool success) {
+ int r;
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ 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, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to run stop-post executable: %s", s->meta.id, strerror(-r));
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
+}
+
+static void socket_enter_signal(Socket *s, SocketState state, bool success) {
+ int r;
+ bool sent = false;
+
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ if (s->kill_mode != KILL_NONE) {
+ int sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_FINAL_SIGTERM) ? SIGTERM : SIGKILL;
+
+ if (s->kill_mode == KILL_CONTROL_GROUP) {
+
+ if ((r = cgroup_bonding_kill_list(UNIT(s)->meta.cgroup_bondings, sig)) < 0) {
+ if (r != -EAGAIN && r != -ESRCH)
+ goto fail;
+ } else
+ sent = true;
+ }
+
+ if (!sent && s->control_pid > 0)
+ if (kill(s->kill_mode == KILL_PROCESS ? s->control_pid : -s->control_pid, sig) < 0 && errno != ESRCH) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ if (sent && s->control_pid > 0) {
+ 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, true);
+ else
+ socket_enter_dead(s, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to kill processes: %s", s->meta.id, strerror(-r));
+
+ if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL)
+ socket_enter_stop_post(s, false);
+ else
+ socket_enter_dead(s, false);
+}
+
+static void socket_enter_stop_pre(Socket *s, bool success) {
+ int r;
+ assert(s);
+
+ if (!success)
+ s->failure = true;
+
+ 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, true);
+
+ return;
+
+fail:
+ log_warning("%s failed to run stop-pre executable: %s", s->meta.id, strerror(-r));
+ socket_enter_stop_post(s, false);
+}
+
+static void socket_enter_listening(Socket *s) {
+ int r;
+ assert(s);
+
+ if ((r = socket_watch_fds(s)) < 0) {
+ log_warning("%s failed to watch sockets: %s", s->meta.id, strerror(-r));
+ goto fail;
+ }
+
+ socket_set_state(s, SOCKET_LISTENING);
+ return;
+
+fail:
+ socket_enter_stop_pre(s, false);
+}
+
+static void socket_enter_start_post(Socket *s) {
+ int r;
+ assert(s);
+
+ if ((r = socket_open_fds(s)) < 0) {
+ log_warning("%s failed to listen on sockets: %s", s->meta.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])) {
+ if ((r = socket_spawn(s, s->control_command, &s->control_pid)) < 0) {
+ log_warning("%s failed to run start-post executable: %s", s->meta.id, strerror(-r));
+ goto fail;
+ }
+
+ socket_set_state(s, SOCKET_START_POST);
+ } else
+ socket_enter_listening(s);
+
+ return;
+
+fail:
+ socket_enter_stop_pre(s, false);
+}
+
+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 exectuable: %s", s->meta.id, strerror(-r));
+ socket_enter_dead(s, false);
+}
+
+static void socket_enter_running(Socket *s, int cfd) {
+ int r;
+
+ assert(s);
+
+ if (cfd < 0) {
+ if ((r = manager_add_job(UNIT(s)->meta.manager, JOB_START, UNIT(s->service), JOB_REPLACE, true, NULL)) < 0)
+ goto fail;
+
+ socket_set_state(s, SOCKET_RUNNING);
+ } else {
+ Unit *u;
+ char *prefix, *instance, *name;
+
+ if ((r = instance_from_socket(cfd, s->n_accepted++, &instance)))
+ goto fail;
+
+ if (!(prefix = unit_name_to_prefix(UNIT(s)->meta.id))) {
+ free(instance);
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ name = unit_name_build(prefix, instance, ".service");
+ free(prefix);
+ free(instance);
+
+ if (!name)
+ r = -ENOMEM;
+
+ r = manager_load_unit(UNIT(s)->meta.manager, name, NULL, &u);
+ free(name);
+
+ if (r < 0)
+ goto fail;
+
+ if ((r = service_set_socket_fd(SERVICE(u), cfd) < 0))
+ goto fail;
+
+ cfd = -1;
+
+ if ((r = manager_add_job(u->meta.manager, JOB_START, u, JOB_REPLACE, true, NULL)) < 0)
+ goto fail;
+ }
+
+ return;
+
+fail:
+ log_warning("%s failed to queue socket startup job: %s", s->meta.id, strerror(-r));
+ socket_enter_stop_pre(s, false);
+
+ if (cfd >= 0)
+ close_nointr_nofail(cfd);
+}
+
+static void socket_run_next(Socket *s, bool success) {
+ int r;
+
+ assert(s);
+ assert(s->control_command);
+ assert(s->control_command->command_next);
+
+ if (!success)
+ s->failure = true;
+
+ 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 spawn next executable: %s", s->meta.id, strerror(-r));
+
+ if (s->state == SOCKET_START_POST)
+ socket_enter_stop_pre(s, false);
+ else if (s->state == SOCKET_STOP_POST)
+ socket_enter_dead(s, false);
+ else
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
+}
+
+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 (s->service) {
+ if (s->service->meta.load_state != UNIT_LOADED)
+ return -ENOENT;
+
+ /* If the service is alredy actvie we cannot start the
+ * socket */
+ if (s->service->state != SERVICE_DEAD &&
+ s->service->state != SERVICE_MAINTAINANCE &&
+ s->service->state != SERVICE_AUTO_RESTART)
+ return -EBUSY;
+ }
+
+ assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTAINANCE);
+
+ s->failure = false;
+ socket_enter_start_pre(s);
+ return 0;
+}
+
+static int socket_stop(Unit *u) {
+ Socket *s = SOCKET(u);
+
+ assert(s);
+
+ /* We cannot fulfill this request right now, try again later
+ * please! */
+ if (s->state == SOCKET_START_PRE ||
+ s->state == SOCKET_START_POST)
+ return -EAGAIN;
+
+ /* 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_SIGTERM)
+ return 0;
+
+ assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
+
+ socket_enter_stop_pre(s, true);
+ 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, "failure", yes_no(s->failure));
+ 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", "%u", (unsigned) 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;
+
+ unit_serialize_item_format(u, f, "socket", "%i %s", copy, t);
+ free(t);
+ } 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);
+ int r;
+
+ 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, "failure")) {
+ int b;
+
+ if ((b = parse_boolean(value)) < 0)
+ log_debug("Failed to parse failure value %s", value);
+ else
+ s->failure = b || s->failure;
+
+ } else if (streq(key, "n-accepted")) {
+ unsigned k;
+
+ if ((r = 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")) {
+ unsigned pid;
+
+ if ((r = safe_atou(value, &pid)) < 0 || pid <= 0)
+ log_debug("Failed to parse control-pid value %s", value);
+ else
+ s->control_pid = (pid_t) 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 (streq(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, 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(&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 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);
+
+ log_debug("Incoming traffic on %s", u->meta.id);
+
+ if (events != EPOLLIN) {
+ log_error("Got invalid poll event on socket.");
+ goto fail;
+ }
+
+ if (w->socket_accept) {
+ for (;;) {
+
+ if ((cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("Failed to accept socket: %m");
+ goto fail;
+ }
+
+ break;
+ }
+ }
+
+ socket_enter_running(s, cfd);
+ return;
+
+fail:
+ socket_enter_stop_pre(s, false);
+}
+
+static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
+ Socket *s = SOCKET(u);
+ bool success;
+
+ assert(s);
+ assert(pid >= 0);
+
+ success = is_clean_exit(code, status);
+ s->failure = s->failure || !success;
+
+ assert(s->control_pid == pid);
+ s->control_pid = 0;
+
+ if (s->control_command)
+ exec_status_fill(&s->control_command->exec_status, pid, code, status);
+
+ log_debug("%s control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
+
+ if (s->control_command && s->control_command->command_next && success) {
+ log_debug("%s running next command for state %s", u->meta.id, socket_state_to_string(s->state));
+ socket_run_next(s, success);
+ } 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->meta.id, socket_state_to_string(s->state));
+
+ switch (s->state) {
+
+ case SOCKET_START_PRE:
+ if (success)
+ socket_enter_start_post(s);
+ else
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
+ break;
+
+ case SOCKET_START_POST:
+ if (success)
+ socket_enter_listening(s);
+ else
+ socket_enter_stop_pre(s, false);
+ break;
+
+ case SOCKET_STOP_PRE:
+ case SOCKET_STOP_PRE_SIGTERM:
+ case SOCKET_STOP_PRE_SIGKILL:
+ socket_enter_stop_post(s, success);
+ break;
+
+ case SOCKET_STOP_POST:
+ case SOCKET_FINAL_SIGTERM:
+ case SOCKET_FINAL_SIGKILL:
+ socket_enter_dead(s, success);
+ break;
+
+ default:
+ assert_not_reached("Uh, control process died at wrong time.");
+ }
+ }
+}
+
+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->meta.id);
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
+
+ case SOCKET_START_POST:
+ log_warning("%s starting timed out. Stopping.", u->meta.id);
+ socket_enter_stop_pre(s, false);
+ break;
+
+ case SOCKET_STOP_PRE:
+ log_warning("%s stopping timed out. Terminating.", u->meta.id);
+ socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, false);
+ break;
+
+ case SOCKET_STOP_PRE_SIGTERM:
+ log_warning("%s stopping timed out. Killing.", u->meta.id);
+ socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, false);
+ break;
+
+ case SOCKET_STOP_PRE_SIGKILL:
+ log_warning("%s still around after SIGKILL. Ignoring.", u->meta.id);
+ socket_enter_stop_post(s, false);
+ break;
+
+ case SOCKET_STOP_POST:
+ log_warning("%s stopping timed out (2). Terminating.", u->meta.id);
+ socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
+ break;
+
+ case SOCKET_FINAL_SIGTERM:
+ log_warning("%s stopping timed out (2). Killing.", u->meta.id);
+ socket_enter_signal(s, SOCKET_FINAL_SIGKILL, false);
+ break;
+
+ case SOCKET_FINAL_SIGKILL:
+ log_warning("%s still around after SIGKILL (2). Entering maintainance mode.", u->meta.id);
+ socket_enter_dead(s, false);
+ 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 (!(rfds = new(int, rn_fds)) < 0)
+ 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) {
+ assert(s);
+
+ /* The service is dead. Dang. */
+
+ if (s->state == SOCKET_RUNNING) {
+ log_debug("%s got notified about service death.", s->meta.id);
+ socket_enter_listening(s);
+ }
+}
+
+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_MAINTAINANCE] = "maintainance"
+};
+
+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);
+
+const UnitVTable socket_vtable = {
+ .suffix = ".socket",
+
+ .init = socket_init,
+ .done = socket_done,
+ .load = socket_load,
+
+ .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,
+
+ .fd_event = socket_fd_event,
+ .sigchld_event = socket_sigchld_event,
+ .timer_event = socket_timer_event,
+
+ .bus_message_handler = bus_socket_message_handler
+};
diff --git a/src/socket.h b/src/socket.h
new file mode 100644
index 0000000000..43d28d7e04
--- /dev/null
+++ b/src/socket.h
@@ -0,0 +1,132 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foosockethfoo
+#define foosockethfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Socket Socket;
+
+#include "manager.h"
+#include "unit.h"
+#include "socket-util.h"
+#include "mount.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_MAINTAINANCE,
+ _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_FIFO_MAX,
+ _SOCKET_FIFO_INVALID = -1
+} SocketType;
+
+typedef struct SocketPort SocketPort;
+
+struct SocketPort {
+ SocketType type;
+ int fd;
+
+ SocketAddress address;
+ char *path;
+
+ Watch fd_watch;
+
+ LIST_FIELDS(SocketPort, port);
+};
+
+struct Socket {
+ Meta meta;
+
+ LIST_HEAD(SocketPort, ports);
+
+ /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */
+ bool bind_ipv6_only;
+ unsigned backlog;
+
+ usec_t timeout_usec;
+
+ ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX];
+ ExecContext exec_context;
+
+ Service *service;
+
+ SocketState state, deserialized_state;
+
+ KillMode kill_mode;
+
+ ExecCommand* control_command;
+ SocketExecCommand control_command_id;
+ pid_t control_pid;
+
+ char *bind_to_device;
+ mode_t directory_mode;
+ mode_t socket_mode;
+
+ bool accept;
+ unsigned n_accepted;
+
+ bool failure;
+ Watch timer_watch;
+};
+
+/* 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);
+
+/* 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);
+
+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);
+
+#endif
diff --git a/src/specifier.c b/src/specifier.c
new file mode 100644
index 0000000000..d8472e99ed
--- /dev/null
+++ b/src/specifier.c
@@ -0,0 +1,110 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <string.h>
+
+#include "macro.h"
+#include "util.h"
+#include "specifier.h"
+
+/*
+ * Generic infrastructure for replacing %x style specifiers in
+ * strings. Will call a callback for each replacement.
+ *
+ */
+
+char *specifier_printf(const char *text, const Specifier table[], void *userdata) {
+ char *r, *t;
+ const char *f;
+ bool percent = false;
+ size_t l;
+
+ assert(text);
+ assert(table);
+
+ l = strlen(text);
+ if (!(r = new(char, l+1)))
+ return NULL;
+
+ t = r;
+
+ for (f = text; *f; f++, l--) {
+
+ if (percent) {
+ if (*f == '%')
+ *(t++) = '%';
+ else {
+ const Specifier *i;
+
+ for (i = table; i->specifier; i++)
+ if (i->specifier == *f)
+ break;
+
+ if (i->lookup) {
+ char *n, *w;
+ size_t k, j;
+
+ if (!(w = i->lookup(i->specifier, i->data, userdata))) {
+ free(r);
+ return NULL;
+ }
+
+ j = t - r;
+ k = strlen(w);
+
+ if (!(n = new(char, j + k + l + 1))) {
+ free(r);
+ free(w);
+ return NULL;
+ }
+
+ memcpy(n, r, j);
+ memcpy(n + j, w, k);
+
+ free(r);
+ free(w);
+
+ r = n;
+ t = n + j + k;
+ } else {
+ *(t++) = '%';
+ *(t++) = *f;
+ }
+ }
+
+ percent = false;
+ } else if (*f == '%')
+ percent = true;
+ else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+ return r;
+}
+
+/* Generic handler for simple string replacements */
+
+char* specifier_string(char specifier, void *data, void *userdata) {
+ assert(data);
+
+ return strdup(strempty(data));
+}
diff --git a/src/specifier.h b/src/specifier.h
new file mode 100644
index 0000000000..4b3b94c857
--- /dev/null
+++ b/src/specifier.h
@@ -0,0 +1,37 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foospecifierhfoo
+#define foospecifierhfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef char* (*SpecifierCallback)(char specifier, void *data, void *userdata);
+
+typedef struct Specifier {
+ const char specifier;
+ const SpecifierCallback lookup;
+ void *data;
+} Specifier;
+
+char *specifier_printf(const char *text, const Specifier table[], void *userdata);
+
+char* specifier_string(char specifier, void *data, void *userdata);
+
+#endif
diff --git a/src/strv.c b/src/strv.c
new file mode 100644
index 0000000000..a749096f9a
--- /dev/null
+++ b/src/strv.c
@@ -0,0 +1,503 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "strv.h"
+
+char *strv_find(char **l, const char *name) {
+ char **i;
+
+ assert(l);
+ assert(name);
+
+ STRV_FOREACH(i, l)
+ if (streq(*i, name))
+ return *i;
+
+ return NULL;
+}
+
+void strv_free(char **l) {
+ char **k;
+
+ if (!l)
+ return;
+
+ for (k = l; *k; k++)
+ free(*k);
+
+ free(l);
+}
+
+char **strv_copy(char **l) {
+ char **r, **k;
+
+ if (!(r = new(char*, strv_length(l)+1)))
+ return NULL;
+
+ for (k = r; *l; k++, l++)
+ if (!(*k = strdup(*l)))
+ goto fail;
+
+ *k = NULL;
+ return r;
+
+fail:
+ for (k--, l--; k >= r; k--, l--)
+ free(*k);
+
+ return NULL;
+}
+
+unsigned strv_length(char **l) {
+ unsigned n = 0;
+
+ if (!l)
+ return 0;
+
+ for (; *l; l++)
+ n++;
+
+ return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap) {
+ const char *s;
+ char **a;
+ unsigned n = 0, i = 0;
+ va_list aq;
+
+
+ if (x) {
+ n = 1;
+
+ va_copy(aq, ap);
+ while (va_arg(aq, const char*))
+ n++;
+ va_end(aq);
+ }
+
+ if (!(a = new(char*, n+1)))
+ return NULL;
+
+ if (x) {
+ if (!(a[i] = strdup(x))) {
+ free(a);
+ return NULL;
+ }
+
+ i++;
+
+ while ((s = va_arg(ap, const char*))) {
+ if (!(a[i] = strdup(s)))
+ goto fail;
+
+ i++;
+ }
+ }
+
+ a[i] = NULL;
+
+ return a;
+
+fail:
+
+ for (; i > 0; i--)
+ if (a[i-1])
+ free(a[i-1]);
+
+ free(a);
+
+ return NULL;
+}
+
+char **strv_new(const char *x, ...) {
+ char **r;
+ va_list ap;
+
+ va_start(ap, x);
+ r = strv_new_ap(x, ap);
+ va_end(ap);
+
+ return r;
+}
+
+char **strv_merge(char **a, char **b) {
+ char **r, **k;
+
+ if (!a)
+ return strv_copy(b);
+
+ if (!b)
+ return strv_copy(a);
+
+ if (!(r = new(char*, strv_length(a)+strv_length(b)+1)))
+ return NULL;
+
+ for (k = r; *a; k++, a++)
+ if (!(*k = strdup(*a)))
+ goto fail;
+ for (; *b; k++, b++)
+ if (!(*k = strdup(*b)))
+ goto fail;
+
+ *k = NULL;
+ return r;
+
+fail:
+ for (k--; k >= r; k--)
+ free(*k);
+
+ free(r);
+
+ return NULL;
+}
+
+char **strv_merge_concat(char **a, char **b, const char *suffix) {
+ char **r, **k;
+
+ /* Like strv_merge(), but appends suffix to all strings in b, before adding */
+
+ if (!b)
+ return strv_copy(a);
+
+ if (!(r = new(char*, strv_length(a)+strv_length(b)+1)))
+ return NULL;
+
+ for (k = r; *a; k++, a++)
+ if (!(*k = strdup(*a)))
+ goto fail;
+ for (; *b; k++, b++)
+ if (!(*k = strappend(*b, suffix)))
+ goto fail;
+
+ *k = NULL;
+ return r;
+
+fail:
+ for (k--; k >= r; k--)
+ free(*k);
+
+ free(r);
+
+ return NULL;
+
+}
+
+char **strv_split(const char *s, const char *separator) {
+ char *state;
+ char *w;
+ size_t l;
+ unsigned n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_SEPARATOR(w, l, s, separator, state)
+ n++;
+
+ if (!(r = new(char*, n+1)))
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_SEPARATOR(w, l, s, separator, state)
+ if (!(r[i++] = strndup(w, l))) {
+ strv_free(r);
+ return NULL;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char **strv_split_quoted(const char *s) {
+ char *state;
+ char *w;
+ size_t l;
+ unsigned n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_QUOTED(w, l, s, state)
+ n++;
+
+ if (!(r = new(char*, n+1)))
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_QUOTED(w, l, s, state)
+ if (!(r[i++] = strndup(w, l))) {
+ strv_free(r);
+ return NULL;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char *strv_join(char **l, const char *separator) {
+ char *r, *e;
+ char **s;
+ size_t n, k;
+
+ if (!separator)
+ separator = " ";
+
+ k = strlen(separator);
+
+ n = 0;
+ STRV_FOREACH(s, l) {
+ if (n != 0)
+ n += k;
+ n += strlen(*s);
+ }
+
+ if (!(r = new(char, n+1)))
+ return NULL;
+
+ e = r;
+ STRV_FOREACH(s, l) {
+ if (e != r)
+ e = stpcpy(e, separator);
+
+ e = stpcpy(e, *s);
+ }
+
+ *e = 0;
+
+ return r;
+}
+
+char **strv_append(char **l, const char *s) {
+ char **r, **k;
+
+ if (!l)
+ return strv_new(s, NULL);
+
+ if (!s)
+ return strv_copy(l);
+
+ if (!(r = new(char*, strv_length(l)+2)))
+ return NULL;
+
+ for (k = r; *l; k++, l++)
+ if (!(*k = strdup(*l)))
+ goto fail;
+
+ if (!(*(k++) = strdup(s)))
+ goto fail;
+
+ *k = NULL;
+ return r;
+
+fail:
+ for (k--; k >= r; k--)
+ free(*k);
+
+ free(r);
+
+ return NULL;
+}
+
+char **strv_uniq(char **l) {
+ char **i;
+
+ /* Drops duplicate entries. The first identical string will be
+ * kept, the others dropped */
+
+ STRV_FOREACH(i, l)
+ strv_remove(i+1, *i);
+
+ return l;
+}
+
+char **strv_remove(char **l, const char *s) {
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ /* Drops every occurence of s in the string list */
+
+ for (f = t = l; *f; f++) {
+
+ if (streq(*f, s)) {
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return l;
+}
+
+static int env_append(char **r, char ***k, char **a) {
+ assert(r);
+ assert(k);
+ assert(a);
+
+ /* Add the entries of a to *k unless they already exist in *r
+ * in which case they are overriden instead. This assumes
+ * there is enough space in the r */
+
+ for (; *a; a++) {
+ char **j;
+ size_t n = strcspn(*a, "=") + 1;
+
+ for (j = r; j < *k; j++)
+ if (strncmp(*j, *a, n) == 0)
+ break;
+
+ if (j >= *k)
+ (*k)++;
+ else
+ free(*j);
+
+ if (!(*j = strdup(*a)))
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+char **strv_env_merge(char **x, ...) {
+ size_t n = 0;
+ char **l, **k, **r;
+ va_list ap;
+
+ /* Merges an arbitrary number of environment sets */
+
+ if (x) {
+ n += strv_length(x);
+
+ va_start(ap, x);
+ while ((l = va_arg(ap, char**)))
+ n += strv_length(l);
+ va_end(ap);
+ }
+
+
+ if (!(r = new(char*, n+1)))
+ return NULL;
+
+ k = r;
+
+ if (x) {
+ if (env_append(r, &k, x) < 0)
+ goto fail;
+
+ va_start(ap, x);
+ while ((l = va_arg(ap, char**)))
+ if (env_append(r, &k, l) < 0)
+ goto fail;
+ va_end(ap);
+ }
+
+ *k = NULL;
+
+ return r;
+
+fail:
+ for (k--; k >= r; k--)
+ free(*k);
+
+ free(r);
+
+ return NULL;
+}
+
+static bool env_match(const char *t, const char *pattern) {
+ assert(t);
+ assert(pattern);
+
+ /* pattern a matches string a
+ * a matches a=
+ * a matches a=b
+ * a= matches a=
+ * a=b matches a=b
+ * a= does not match a
+ * a=b does not match a=
+ * a=b does not match a
+ * a=b does not match a=c */
+
+ if (streq(t, pattern))
+ return true;
+
+ if (!strchr(pattern, '=')) {
+ size_t l = strlen(pattern);
+
+ return strncmp(t, pattern, l) == 0 && t[l] == '=';
+ }
+
+ return false;
+}
+
+char **strv_env_delete(char **x, ...) {
+ size_t n = 0, i = 0;
+ char **l, **k, **r, **j;
+ va_list ap;
+
+ /* Deletes every entry fromx that is mentioned in the other
+ * string lists */
+
+ n = strv_length(x);
+
+ if (!(r = new(char*, n+1)))
+ return NULL;
+
+ STRV_FOREACH(k, x) {
+ va_start(ap, x);
+
+ while ((l = va_arg(ap, char**)))
+ STRV_FOREACH(j, l)
+ if (env_match(*k, *j))
+ goto delete;
+
+ va_end(ap);
+
+ if (!(r[i++] = strdup(*k))) {
+ strv_free(r);
+ return NULL;
+ }
+
+ continue;
+
+ delete:
+ va_end(ap);
+ }
+
+ r[i] = NULL;
+
+ assert(i <= n);
+
+ return r;
+}
diff --git a/src/strv.h b/src/strv.h
new file mode 100644
index 0000000000..f0be83dd59
--- /dev/null
+++ b/src/strv.h
@@ -0,0 +1,65 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foostrvhfoo
+#define foostrvhfoo
+
+/***
+ 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 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 <stdarg.h>
+#include <stdbool.h>
+
+#include "macro.h"
+
+char *strv_find(char **l, const char *name);
+void strv_free(char **l);
+char **strv_copy(char **l) _malloc;
+unsigned strv_length(char **l);
+
+char **strv_merge(char **a, char **b);
+char **strv_merge_concat(char **a, char **b, const char *suffix);
+char **strv_append(char **l, const char *s);
+
+char **strv_remove(char **l, const char *s);
+char **strv_uniq(char **l);
+
+#define strv_contains(l, s) (!!strv_find((l), (s)))
+
+char **strv_new(const char *x, ...) _sentinel _malloc;
+char **strv_new_ap(const char *x, va_list ap) _malloc;
+
+static inline bool strv_isempty(char **l) {
+ return !l || !*l;
+}
+
+char **strv_split(const char *s, const char *separator) _malloc;
+char **strv_split_quoted(const char *s) _malloc;
+
+char *strv_join(char **l, const char *separator) _malloc;
+
+char **strv_env_merge(char **x, ...) _sentinel;
+char **strv_env_delete(char **x, ...) _sentinel;
+
+#define STRV_FOREACH(s, l) \
+ for ((s) = (l); (s) && *(s); (s)++)
+
+#define STRV_FOREACH_BACKWARDS(s, l) \
+ for (; (l) && ((s) >= (l)); (s)--)
+
+#endif
diff --git a/src/swap.c b/src/swap.c
new file mode 100644
index 0000000000..bd49e1ea2b
--- /dev/null
+++ b/src/swap.c
@@ -0,0 +1,578 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/swap.h>
+
+#include "unit.h"
+#include "swap.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "unit-name.h"
+#include "dbus-swap.h"
+
+static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = {
+ [SWAP_DEAD] = UNIT_INACTIVE,
+ [SWAP_ACTIVE] = UNIT_ACTIVE,
+ [SWAP_MAINTAINANCE] = UNIT_INACTIVE
+};
+
+static void swap_init(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(s->meta.load_state == UNIT_STUB);
+
+ s->parameters_etc_fstab.priority = s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1;
+}
+
+static void swap_done(Unit *u) {
+ Swap *s = SWAP(u);
+
+ assert(s);
+
+ free(s->what);
+ free(s->parameters_etc_fstab.what);
+ free(s->parameters_proc_swaps.what);
+ free(s->parameters_fragment.what);
+}
+
+int swap_add_one_mount_link(Swap *s, Mount *m) {
+ int r;
+
+ assert(s);
+ assert(m);
+
+ if (s->meta.load_state != UNIT_LOADED ||
+ m->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ if (is_device_path(s->what))
+ return 0;
+
+ if (!path_startswith(s->what, m->where))
+ return 0;
+
+ if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(s), true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(UNIT(s), UNIT_REQUIRES, UNIT(m), true)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int swap_add_mount_links(Swap *s) {
+ Meta *other;
+ int r;
+
+ assert(s);
+
+ LIST_FOREACH(units_per_type, other, s->meta.manager->units_per_type[UNIT_MOUNT])
+ if ((r = swap_add_one_mount_link(s, (Mount*) other)) < 0)
+ return r;
+
+ return 0;
+}
+
+static int swap_add_target_links(Swap *s) {
+ Unit *tu;
+ SwapParameters *p;
+ int r;
+
+ assert(s);
+
+ if (s->from_fragment)
+ p = &s->parameters_fragment;
+ else if (s->from_etc_fstab)
+ p = &s->parameters_etc_fstab;
+ else
+ return 0;
+
+ if ((r = manager_load_unit(s->meta.manager, SPECIAL_SWAP_TARGET, NULL, &tu)) < 0)
+ return r;
+
+ if (!p->noauto && p->handle)
+ if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(s), true)) < 0)
+ return r;
+
+ return unit_add_dependency(UNIT(s), UNIT_BEFORE, tu, true);
+}
+
+static int swap_verify(Swap *s) {
+ bool b;
+ char *e;
+
+ if (UNIT(s)->meta.load_state != UNIT_LOADED)
+ return 0;
+
+ if (!(e = unit_name_from_path(s->what, ".swap")))
+ return -ENOMEM;
+
+ b = unit_has_name(UNIT(s), e);
+ free(e);
+
+ if (!b) {
+ log_error("%s: Value of \"What\" and unit name do not match, not loading.\n", UNIT(s)->meta.id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int swap_load(Unit *u) {
+ int r;
+ Swap *s = SWAP(u);
+
+ assert(s);
+ assert(u->meta.load_state == UNIT_STUB);
+
+ /* Load a .swap file */
+ if ((r = unit_load_fragment_and_dropin_optional(u)) < 0)
+ return r;
+
+ if (u->meta.load_state == UNIT_LOADED) {
+
+ if (s->meta.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_etc_fstab.what)
+ s->what = strdup(s->parameters_etc_fstab.what);
+ else if (s->parameters_proc_swaps.what)
+ s->what = strdup(s->parameters_proc_swaps.what);
+ else
+ s->what = unit_name_to_path(u->meta.id);
+
+ if (!s->what)
+ return -ENOMEM;
+ }
+
+ path_kill_slashes(s->what);
+
+ if (!s->meta.description)
+ if ((r = unit_set_description(u, s->what)) < 0)
+ return r;
+
+ if ((r = unit_add_node_link(u, s->what,
+ (u->meta.manager->running_as == MANAGER_INIT ||
+ u->meta.manager->running_as == MANAGER_SYSTEM))) < 0)
+ return r;
+
+ if ((r = swap_add_mount_links(s)) < 0)
+ return r;
+
+ if ((r = swap_add_target_links(s)) < 0)
+ return r;
+ }
+
+ return swap_verify(s);
+}
+
+static int swap_find(Manager *m, const char *what, Unit **_u) {
+ Unit *u;
+ char *e;
+
+ assert(m);
+ assert(what);
+ assert(_u);
+
+ /* /proc/swaps and /etc/fstab might refer to this device by
+ * different names (e.g. one by uuid, the other by the kernel
+ * name), we hence need to look for all aliases we are aware
+ * of for this device */
+
+ if (!(e = unit_name_from_path(what, ".device")))
+ return -ENOMEM;
+
+ u = manager_get_unit(m, e);
+ free(e);
+
+ if (u) {
+ Iterator i;
+ const char *d;
+
+ SET_FOREACH(d, u->meta.names, i) {
+ Unit *k;
+
+ if (!(e = unit_name_change_suffix(d, ".swap")))
+ return -ENOMEM;
+
+ k = manager_get_unit(m, e);
+ free(e);
+
+ if (k) {
+ *_u = k;
+ return 0;
+ }
+ }
+ }
+
+ *_u = NULL;
+ return 0;
+}
+
+int swap_add_one(
+ Manager *m,
+ const char *what,
+ int priority,
+ bool noauto,
+ bool handle,
+ bool from_proc_swaps) {
+ Unit *u = NULL;
+ char *e = NULL, *w = NULL;
+ bool delete;
+ int r;
+ SwapParameters *p;
+
+ assert(m);
+ assert(what);
+
+ if (!(e = unit_name_from_path(what, ".swap")))
+ return -ENOMEM;
+
+ if (!(u = manager_get_unit(m, e)))
+ if ((r = swap_find(m, what, &u)) < 0)
+ goto fail;
+
+ if (!u) {
+ delete = true;
+
+ if (!(u = unit_new(m))) {
+ free(e);
+ return -ENOMEM;
+ }
+ } else
+ delete = false;
+
+ if ((r = unit_add_name(u, e)) < 0)
+ goto fail;
+
+ if (!(w = strdup(what))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (from_proc_swaps) {
+ p = &SWAP(u)->parameters_proc_swaps;
+ SWAP(u)->from_proc_swaps = true;
+ } else {
+ p = &SWAP(u)->parameters_etc_fstab;
+ SWAP(u)->from_etc_fstab = true;
+ }
+
+ free(p->what);
+ p->what = w;
+
+ p->priority = priority;
+ p->noauto = noauto;
+ p->handle = handle;
+
+ if (delete)
+ unit_add_to_load_queue(u);
+
+ unit_add_to_dbus_queue(u);
+
+ free(e);
+
+ return 0;
+
+fail:
+ free(w);
+ free(e);
+
+ if (delete && u)
+ unit_free(u);
+
+ 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 != old_state)
+ log_debug("%s changed %s -> %s",
+ UNIT(s)->meta.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]);
+}
+
+static int swap_coldplug(Unit *u) {
+ Swap *s = SWAP(u);
+ SwapState new_state = SWAP_DEAD;
+
+ 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)
+ 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 = &s->parameters_etc_fstab;
+
+ fprintf(f,
+ "%sSwap State: %s\n"
+ "%sWhat: %s\n"
+ "%sPriority: %i\n"
+ "%sNoAuto: %s\n"
+ "%sHandle: %s\n"
+ "%sFrom /etc/fstab: %s\n"
+ "%sFrom /proc/swaps: %s\n"
+ "%sFrom fragment: %s\n",
+ prefix, swap_state_to_string(s->state),
+ prefix, s->what,
+ prefix, p->priority,
+ prefix, yes_no(p->noauto),
+ prefix, yes_no(p->handle),
+ prefix, yes_no(s->from_etc_fstab),
+ prefix, yes_no(s->from_proc_swaps),
+ prefix, yes_no(s->from_fragment));
+}
+
+static void swap_enter_dead(Swap *s, bool success) {
+ assert(s);
+
+ swap_set_state(s, success ? SWAP_MAINTAINANCE : SWAP_DEAD);
+}
+
+static int swap_start(Unit *u) {
+ Swap *s = SWAP(u);
+ int priority = -1;
+ int r;
+
+ assert(s);
+ assert(s->state == SWAP_DEAD || s->state == SWAP_MAINTAINANCE);
+
+ if (s->from_fragment)
+ priority = s->parameters_fragment.priority;
+ else if (s->from_etc_fstab)
+ priority = s->parameters_etc_fstab.priority;
+
+ r = swapon(s->what, (priority << SWAP_FLAG_PRIO_SHIFT) & SWAP_FLAG_PRIO_MASK);
+
+ if (r < 0 && errno != EBUSY) {
+ r = -errno;
+ swap_enter_dead(s, false);
+ return r;
+ }
+
+ swap_set_state(s, SWAP_ACTIVE);
+ return 0;
+}
+
+static int swap_stop(Unit *u) {
+ Swap *s = SWAP(u);
+ int r;
+
+ assert(s);
+
+ assert(s->state == SWAP_ACTIVE);
+
+ r = swapoff(s->what);
+ swap_enter_dead(s, r >= 0 || errno == EINVAL);
+
+ 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));
+
+ 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;
+
+ if ((state = swap_state_from_string(value)) < 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 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_etc_fstab || s->from_proc_swaps;
+}
+
+static int swap_load_proc_swaps(Manager *m) {
+ rewind(m->proc_swaps);
+
+ (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (;;) {
+ char *dev = NULL, *d;
+ int prio = 0, k;
+
+ if ((k = fscanf(m->proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%i\n", /* priority */
+ &dev, &prio)) != 2) {
+
+ if (k == EOF)
+ break;
+
+ free(dev);
+ return -EBADMSG;
+ }
+
+ d = cunescape(dev);
+ free(dev);
+
+ if (!d)
+ return -ENOMEM;
+
+ k = swap_add_one(m, d, prio, false, false, true);
+ free(d);
+
+ if (k < 0)
+ return k;
+ }
+
+ return 0;
+}
+
+static void swap_shutdown(Manager *m) {
+ assert(m);
+
+ if (m->proc_swaps) {
+ fclose(m->proc_swaps);
+ m->proc_swaps = NULL;
+ }
+}
+
+static const char* const swap_state_table[_SWAP_STATE_MAX] = {
+ [SWAP_DEAD] = "dead",
+ [SWAP_ACTIVE] = "active",
+ [SWAP_MAINTAINANCE] = "maintainance"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState);
+
+static int swap_enumerate(Manager *m) {
+ int r;
+ assert(m);
+
+ if (!m->proc_swaps)
+ if (!(m->proc_swaps = fopen("/proc/swaps", "re")))
+ return -errno;
+
+ if ((r = swap_load_proc_swaps(m)) < 0)
+ swap_shutdown(m);
+
+ return r;
+}
+
+const UnitVTable swap_vtable = {
+ .suffix = ".swap",
+
+ .no_instances = true,
+ .no_isolate = true,
+
+ .init = swap_init,
+ .load = swap_load,
+ .done = swap_done,
+
+ .coldplug = swap_coldplug,
+
+ .dump = swap_dump,
+
+ .start = swap_start,
+ .stop = swap_stop,
+
+ .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,
+
+ .bus_message_handler = bus_swap_message_handler,
+
+ .enumerate = swap_enumerate,
+ .shutdown = swap_shutdown
+};
diff --git a/src/swap.h b/src/swap.h
new file mode 100644
index 0000000000..f54a9ee434
--- /dev/null
+++ b/src/swap.h
@@ -0,0 +1,71 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooswaphfoo
+#define fooswaphfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Swap Swap;
+
+#include "unit.h"
+
+typedef enum SwapState {
+ SWAP_DEAD,
+ SWAP_ACTIVE,
+ SWAP_MAINTAINANCE,
+ _SWAP_STATE_MAX,
+ _SWAP_STATE_INVALID = -1
+} SwapState;
+
+typedef struct SwapParameters {
+ char *what;
+ int priority;
+ bool noauto:1;
+ bool handle:1;
+} SwapParameters;
+
+struct Swap {
+ Meta meta;
+
+ SwapParameters parameters_etc_fstab;
+ SwapParameters parameters_proc_swaps;
+ SwapParameters parameters_fragment;
+
+ char *what;
+
+ bool from_etc_fstab:1;
+ bool from_proc_swaps:1;
+ bool from_fragment:1;
+
+ SwapState state, deserialized_state;
+};
+
+extern const UnitVTable swap_vtable;
+
+int swap_add_one(Manager *m, const char *what, int prio, bool no_auto, bool handle, bool from_proc_swap);
+
+int swap_add_one_mount_link(Swap *s, Mount *m);
+
+const char* swap_state_to_string(SwapState i);
+SwapState swap_state_from_string(const char *s);
+
+
+#endif
diff --git a/src/systemadm.vala b/src/systemadm.vala
new file mode 100644
index 0000000000..bd0062a069
--- /dev/null
+++ b/src/systemadm.vala
@@ -0,0 +1,956 @@
+/***
+ 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 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/>.
+***/
+
+using Gtk;
+using GLib;
+using DBus;
+using Pango;
+
+static bool session = false;
+
+public class LeftLabel : Label {
+ public LeftLabel(string? text = null) {
+ if (text != null)
+ set_markup("<b>%s</b>".printf(text));
+ set_alignment(0, 0);
+ set_padding(6, 0);
+ }
+}
+
+public class RightLabel : Label {
+ public RightLabel(string? text = null) {
+ set_text_or_na(text);
+ set_alignment(0, 0);
+ set_ellipsize(EllipsizeMode.START);
+ set_selectable(true);
+ }
+
+ public void set_text_or_na(string? text = null) {
+ if (text == null || text == "")
+ set_markup("<i>n/a</i>");
+ else
+ set_text(text);
+ }
+
+ public void set_markup_or_na(string? text = null) {
+ if (text == null || text == "")
+ set_markup("<i>n/a</i>");
+ else
+ set_markup(text);
+ }
+}
+
+public class MainWindow : Window {
+
+ private string? current_unit_id;
+ private uint32 current_job_id;
+
+ private TreeView unit_view;
+ private TreeView job_view;
+
+ private ListStore unit_model;
+ private ListStore job_model;
+
+ private Button start_button;
+ private Button stop_button;
+ private Button restart_button;
+ private Button reload_button;
+ private Button cancel_button;
+
+ private Entry unit_load_entry;
+ private Button unit_load_button;
+
+ private Button server_snapshot_button;
+ private Button server_reload_button;
+
+ private Connection bus;
+ private Manager manager;
+
+ private RightLabel unit_id_label;
+ private RightLabel unit_aliases_label;
+ private RightLabel unit_dependency_label;
+ private RightLabel unit_description_label;
+ private RightLabel unit_load_state_label;
+ private RightLabel unit_active_state_label;
+ private RightLabel unit_sub_state_label;
+ private RightLabel unit_fragment_path_label;
+ private RightLabel unit_active_enter_timestamp_label;
+ private RightLabel unit_active_exit_timestamp_label;
+ private RightLabel unit_can_start_label;
+ private RightLabel unit_can_reload_label;
+ private RightLabel unit_cgroup_label;
+
+ private RightLabel job_id_label;
+ private RightLabel job_state_label;
+ private RightLabel job_type_label;
+
+ private ComboBox unit_type_combo_box;
+
+ public MainWindow() throws DBus.Error {
+ title = session ? "systemd Session Manager" : "systemd System Manager";
+ position = WindowPosition.CENTER;
+ set_default_size(1000, 700);
+ set_border_width(12);
+ destroy += Gtk.main_quit;
+
+ Notebook notebook = new Notebook();
+ add(notebook);
+
+ Box unit_vbox = new VBox(false, 12);
+ notebook.append_page(unit_vbox, new Label("Units"));
+ unit_vbox.set_border_width(12);
+
+ Box job_vbox = new VBox(false, 12);
+ notebook.append_page(job_vbox, new Label("Jobs"));
+ job_vbox.set_border_width(12);
+
+ unit_type_combo_box = new ComboBox.text();
+ Box type_hbox = new HBox(false, 6);
+ type_hbox.pack_start(unit_type_combo_box, false, false, 0);
+ unit_vbox.pack_start(type_hbox, false, false, 0);
+
+ unit_type_combo_box.append_text("Show All Units");
+ unit_type_combo_box.append_text("Show Only Live Units");
+ unit_type_combo_box.append_text("Services");
+ unit_type_combo_box.append_text("Sockets");
+ unit_type_combo_box.append_text("Devices");
+ unit_type_combo_box.append_text("Mounts");
+ unit_type_combo_box.append_text("Automounts");
+ unit_type_combo_box.append_text("Targets");
+ unit_type_combo_box.append_text("Snapshots");
+ unit_type_combo_box.set_active(1);
+ unit_type_combo_box.changed += unit_type_changed;
+
+ unit_load_entry = new Entry();
+ unit_load_button = new Button.with_mnemonic("_Load");
+ unit_load_button.set_sensitive(false);
+
+ unit_load_entry.changed += on_unit_load_entry_changed;
+ unit_load_entry.activate += on_unit_load;
+ unit_load_button.clicked += on_unit_load;
+
+ Box unit_load_hbox = new HBox(false, 6);
+ unit_load_hbox.pack_start(unit_load_entry, false, true, 0);
+ unit_load_hbox.pack_start(unit_load_button, false, true, 0);
+
+ server_snapshot_button = new Button.with_mnemonic("Take S_napshot");
+ server_reload_button = new Button.with_mnemonic("Reload _Configuration");
+
+ server_snapshot_button.clicked += on_server_snapshot;
+ server_reload_button.clicked += on_server_reload;
+
+ type_hbox.pack_end(server_snapshot_button, false, true, 0);
+ type_hbox.pack_end(server_reload_button, false, true, 0);
+ type_hbox.pack_end(unit_load_hbox, false, true, 24);
+
+ unit_model = new ListStore(7, typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(Unit));
+ job_model = new ListStore(6, typeof(string), typeof(string), typeof(string), typeof(string), typeof(Job), typeof(uint32));
+
+ TreeModelFilter unit_model_filter;
+ unit_model_filter = new TreeModelFilter(unit_model, null);
+ unit_model_filter.set_visible_func(unit_filter);
+
+ unit_view = new TreeView.with_model(unit_model_filter);
+ job_view = new TreeView.with_model(job_model);
+
+ unit_view.cursor_changed += unit_changed;
+ job_view.cursor_changed += job_changed;
+
+ unit_view.insert_column_with_attributes(-1, "Load State", new CellRendererText(), "text", 2);
+ unit_view.insert_column_with_attributes(-1, "Active State", new CellRendererText(), "text", 3);
+ unit_view.insert_column_with_attributes(-1, "Unit State", new CellRendererText(), "text", 4);
+ unit_view.insert_column_with_attributes(-1, "Unit", new CellRendererText(), "text", 0);
+ unit_view.insert_column_with_attributes(-1, "Job", new CellRendererText(), "text", 5);
+
+ job_view.insert_column_with_attributes(-1, "Job", new CellRendererText(), "text", 0);
+ job_view.insert_column_with_attributes(-1, "Unit", new CellRendererText(), "text", 1);
+ job_view.insert_column_with_attributes(-1, "Type", new CellRendererText(), "text", 2);
+ job_view.insert_column_with_attributes(-1, "State", new CellRendererText(), "text", 3);
+
+ ScrolledWindow scroll = new ScrolledWindow(null, null);
+ scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
+ scroll.set_shadow_type(ShadowType.IN);
+ scroll.add(unit_view);
+ unit_vbox.pack_start(scroll, true, true, 0);
+
+ scroll = new ScrolledWindow(null, null);
+ scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
+ scroll.set_shadow_type(ShadowType.IN);
+ scroll.add(job_view);
+ job_vbox.pack_start(scroll, true, true, 0);
+
+ unit_id_label = new RightLabel();
+ unit_aliases_label = new RightLabel();
+ unit_dependency_label = new RightLabel();
+ unit_description_label = new RightLabel();
+ unit_load_state_label = new RightLabel();
+ unit_active_state_label = new RightLabel();
+ unit_sub_state_label = new RightLabel();
+ unit_fragment_path_label = new RightLabel();
+ unit_active_enter_timestamp_label = new RightLabel();
+ unit_active_exit_timestamp_label = new RightLabel();
+ unit_can_start_label = new RightLabel();
+ unit_can_reload_label = new RightLabel();
+ unit_cgroup_label = new RightLabel();
+
+ job_id_label = new RightLabel();
+ job_state_label = new RightLabel();
+ job_type_label = new RightLabel();
+
+ unit_dependency_label.set_track_visited_links(false);
+ unit_dependency_label.set_selectable(false);
+ unit_dependency_label.activate_link += on_activate_link;
+
+ Table unit_table = new Table(8, 6, false);
+ unit_table.set_row_spacings(6);
+ unit_table.set_border_width(0);
+ unit_vbox.pack_start(unit_table, false, true, 0);
+
+ Table job_table = new Table(2, 2, false);
+ job_table.set_row_spacings(6);
+ job_table.set_border_width(0);
+ job_vbox.pack_start(job_table, false, true, 0);
+
+ unit_table.attach(new LeftLabel("Id:"), 0, 1, 0, 1, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_id_label, 1, 6, 0, 1, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Aliases:"), 0, 1, 1, 2, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_aliases_label, 1, 6, 1, 2, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Description:"), 0, 1, 2, 3, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_description_label, 1, 6, 2, 3, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Dependencies:"), 0, 1, 3, 4, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_dependency_label, 1, 6, 3, 4, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Fragment Path:"), 0, 1, 4, 5, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_fragment_path_label, 1, 6, 4, 5, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Control Group:"), 0, 1, 5, 6, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_cgroup_label, 1, 6, 5, 6, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+
+ unit_table.attach(new LeftLabel("Load State:"), 0, 1, 6, 7, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_load_state_label, 1, 2, 6, 7, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Active State:"), 0, 1, 7, 8, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_active_state_label, 1, 2, 7, 8, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Unit State:"), 0, 1, 8, 9, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_sub_state_label, 1, 2, 8, 9, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+
+ unit_table.attach(new LeftLabel("Active Enter Timestamp:"), 2, 3, 7, 8, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_active_enter_timestamp_label, 3, 4, 7, 8, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Active Exit Timestamp:"), 2, 3, 8, 9, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_active_exit_timestamp_label, 3, 4, 8, 9, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+
+ unit_table.attach(new LeftLabel("Can Start/Stop:"), 4, 5, 7, 8, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_can_start_label, 5, 6, 7, 8, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(new LeftLabel("Can Reload:"), 4, 5, 8, 9, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ unit_table.attach(unit_can_reload_label, 5, 6, 8, 9, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+
+ job_table.attach(new LeftLabel("Id:"), 0, 1, 0, 1, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ job_table.attach(job_id_label, 1, 2, 0, 1, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ job_table.attach(new LeftLabel("State:"), 0, 1, 1, 2, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ job_table.attach(job_state_label, 1, 2, 1, 2, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ job_table.attach(new LeftLabel("Type:"), 0, 1, 2, 3, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ job_table.attach(job_type_label, 1, 2, 2, 3, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+
+ ButtonBox bbox = new HButtonBox();
+ bbox.set_layout(ButtonBoxStyle.START);
+ bbox.set_spacing(6);
+ unit_vbox.pack_start(bbox, false, true, 0);
+
+ start_button = new Button.with_mnemonic("_Start");
+ stop_button = new Button.with_mnemonic("Sto_p");
+ reload_button = new Button.with_mnemonic("_Reload");
+ restart_button = new Button.with_mnemonic("Res_tart");
+
+ start_button.clicked += on_start;
+ stop_button.clicked += on_stop;
+ reload_button.clicked += on_reload;
+ restart_button.clicked += on_restart;
+
+ bbox.pack_start(start_button, false, true, 0);
+ bbox.pack_start(stop_button, false, true, 0);
+ bbox.pack_start(restart_button, false, true, 0);
+ bbox.pack_start(reload_button, false, true, 0);
+
+ bbox = new HButtonBox();
+ bbox.set_layout(ButtonBoxStyle.START);
+ bbox.set_spacing(6);
+ job_vbox.pack_start(bbox, false, true, 0);
+
+ cancel_button = new Button.with_mnemonic("_Cancel");
+
+ cancel_button.clicked += on_cancel;
+
+ bbox.pack_start(cancel_button, false, true, 0);
+
+ bus = Bus.get(session ? BusType.SESSION : BusType.SYSTEM);
+
+ manager = bus.get_object(
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager") as Manager;
+
+ manager.unit_new += on_unit_new;
+ manager.job_new += on_job_new;
+ manager.unit_removed += on_unit_removed;
+ manager.job_removed += on_job_removed;
+
+ manager.subscribe();
+
+ clear_unit();
+ clear_job();
+ populate_unit_model();
+ populate_job_model();
+ }
+
+ public void populate_unit_model() throws DBus.Error {
+ unit_model.clear();
+
+ var list = manager.list_units();
+
+ foreach (var i in list) {
+ TreeIter iter;
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ i.unit_path,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ u.changed += on_unit_changed;
+
+ unit_model.append(out iter);
+ unit_model.set(iter,
+ 0, i.id,
+ 1, i.description,
+ 2, i.load_state,
+ 3, i.active_state,
+ 4, i.sub_state,
+ 5, i.job_type != "" ? "→ %s".printf(i.job_type) : "",
+ 6, u);
+ }
+ }
+
+ public void populate_job_model() throws DBus.Error {
+ job_model.clear();
+
+ var list = manager.list_jobs();
+
+ foreach (var i in list) {
+ TreeIter iter;
+
+ Job j = bus.get_object(
+ "org.freedesktop.systemd1",
+ i.job_path,
+ "org.freedesktop.systemd1.Job") as Job;
+
+ j.changed += on_job_changed;
+
+ job_model.append(out iter);
+ job_model.set(iter,
+ 0, "%u".printf(i.id),
+ 1, i.name,
+ 2, "→ %s".printf(i.type),
+ 3, i.state,
+ 4, j,
+ 5, i.id);
+ }
+ }
+
+ public Unit? get_current_unit() {
+ TreePath p;
+ unit_view.get_cursor(out p, null);
+
+ if (p == null)
+ return null;
+
+ TreeModel model = unit_view.get_model();
+ TreeIter iter;
+ Unit u;
+
+ model.get_iter(out iter, p);
+ model.get(iter, 6, out u);
+
+ return u;
+ }
+
+ public void unit_changed() {
+ Unit u = get_current_unit();
+
+ if (u == null)
+ clear_unit();
+ else
+ show_unit(u);
+ }
+
+ public void clear_unit() {
+ current_unit_id = null;
+
+ start_button.set_sensitive(false);
+ stop_button.set_sensitive(false);
+ reload_button.set_sensitive(false);
+ restart_button.set_sensitive(false);
+
+ unit_id_label.set_text_or_na();
+ unit_aliases_label.set_text_or_na();
+ unit_description_label.set_text_or_na();
+ unit_description_label.set_text_or_na();
+ unit_load_state_label.set_text_or_na();
+ unit_active_state_label.set_text_or_na();
+ unit_sub_state_label.set_text_or_na();
+ unit_fragment_path_label.set_text_or_na();
+ unit_active_enter_timestamp_label.set_text_or_na();
+ unit_active_exit_timestamp_label.set_text_or_na();
+ unit_can_reload_label.set_text_or_na();
+ unit_can_start_label.set_text_or_na();
+ unit_cgroup_label.set_text_or_na();
+ }
+
+ public string make_dependency_string(string? prefix, string word, string[] dependencies) {
+ bool first = true;
+ string r;
+
+ if (prefix == null)
+ r = "";
+ else
+ r = prefix;
+
+ foreach (string i in dependencies) {
+ if (r != "")
+ r += first ? "\n" : ",";
+
+ if (first) {
+ r += word;
+ first = false;
+ }
+
+ r += " <a href=\"" + i + "\">" + i + "</a>";
+ }
+
+ return r;
+ }
+
+ public void show_unit(Unit unit) {
+ current_unit_id = unit.id;
+
+ unit_id_label.set_text_or_na(current_unit_id);
+
+ string a = "";
+ foreach (string i in unit.names) {
+ if (i == current_unit_id)
+ continue;
+
+ if (a == "")
+ a = i;
+ else
+ a += "\n" + i;
+ }
+
+ unit_aliases_label.set_text_or_na(a);
+
+ string[]
+ requires = unit.requires,
+ requires_overridable = unit.requires_overridable,
+ requisite = unit.requisite,
+ requisite_overridable = unit.requisite_overridable,
+ wants = unit.wants,
+ required_by = unit.required_by,
+ required_by_overridable = unit.required_by_overridable,
+ wanted_by = unit.wanted_by,
+ conflicts = unit.conflicts,
+ before = unit.before,
+ after = unit.after;
+
+ unit_dependency_label.set_markup_or_na(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(
+ make_dependency_string(null,
+ "requires", requires),
+ "overridable requires", requires_overridable),
+ "requisite", requisite),
+ "overridable requisite", requisite_overridable),
+ "wants", wants),
+ "conflicts", conflicts),
+ "required by", required_by),
+ "overridable required by", required_by_overridable),
+ "wanted by", wanted_by),
+ "after", after),
+ "before", before));
+
+ unit_description_label.set_text_or_na(unit.description);
+ unit_load_state_label.set_text_or_na(unit.load_state);
+ unit_active_state_label.set_text_or_na(unit.active_state);
+ unit_sub_state_label.set_text_or_na(unit.sub_state);
+ unit_fragment_path_label.set_text_or_na(unit.fragment_path);
+
+ uint64 t = unit.active_enter_timestamp;
+ if (t > 0) {
+ Time timestamp = Time.local((time_t) (t / 1000000));
+ unit_active_enter_timestamp_label.set_text_or_na(timestamp.format("%a, %d %b %Y %H:%M:%S %z"));
+ } else
+ unit_active_enter_timestamp_label.set_text_or_na();
+
+ t = unit.active_exit_timestamp;
+ if (t > 0) {
+ Time timestamp = Time.local((time_t) (t / 1000000));
+ unit_active_exit_timestamp_label.set_text_or_na(timestamp.format("%a, %d %b %Y %H:%M:%S %z"));
+ } else
+ unit_active_exit_timestamp_label.set_text_or_na();
+
+ bool b = unit.can_start;
+ start_button.set_sensitive(b);
+ stop_button.set_sensitive(b);
+ restart_button.set_sensitive(b);
+ unit_can_start_label.set_text_or_na(b ? "Yes" : "No");
+
+ b = unit.can_reload;
+ reload_button.set_sensitive(b);
+ unit_can_reload_label.set_text_or_na(b ? "Yes" : "No");
+
+ unit_cgroup_label.set_text_or_na(unit.default_control_group);
+ }
+
+ public Job? get_current_job() {
+ TreePath p;
+ job_view.get_cursor(out p, null);
+
+ if (p == null)
+ return null;
+
+ TreeIter iter;
+ TreeModel model = job_view.get_model();
+ Job *j;
+
+ model.get_iter(out iter, p);
+ model.get(iter, 4, out j);
+
+ return j;
+ }
+
+ public void job_changed() {
+ Job j = get_current_job();
+
+ if (j == null)
+ clear_job();
+ else
+ show_job(j);
+ }
+
+ public void clear_job() {
+ current_job_id = 0;
+
+ job_id_label.set_text_or_na();
+ job_state_label.set_text_or_na();
+ job_type_label.set_text_or_na();
+
+ cancel_button.set_sensitive(false);
+ }
+
+ public void show_job(Job job) {
+ current_job_id = job.id;
+
+ job_id_label.set_text_or_na("%u".printf(current_job_id));
+ job_state_label.set_text_or_na(job.state);
+ job_type_label.set_text_or_na(job.job_type);
+
+ cancel_button.set_sensitive(true);
+ }
+
+ public void on_start() {
+ Unit u = get_current_unit();
+
+ if (u == null)
+ return;
+
+ try {
+ u.start("replace");
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_stop() {
+ Unit u = get_current_unit();
+
+ if (u == null)
+ return;
+
+ try {
+ u.stop("replace");
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_reload() {
+ Unit u = get_current_unit();
+
+ if (u == null)
+ return;
+
+ try {
+ u.reload("replace");
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_restart() {
+ Unit u = get_current_unit();
+
+ if (u == null)
+ return;
+
+ try {
+ u.restart("replace");
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_cancel() {
+ Job j = get_current_job();
+
+ if (j == null)
+ return;
+
+ try {
+ j.cancel();
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void update_unit_iter(TreeIter iter, string id, Unit u) {
+
+ string t = "";
+ Unit.JobLink jl = u.job;
+
+ if (jl.id != 0) {
+ Job j = bus.get_object(
+ "org.freedesktop.systemd1",
+ jl.path,
+ "org.freedesktop.systemd1.Job") as Job;
+
+ t = j.job_type;
+ }
+
+ unit_model.set(iter,
+ 0, id,
+ 1, u.description,
+ 2, u.load_state,
+ 3, u.active_state,
+ 4, u.sub_state,
+ 5, t != "" ? "→ %s".printf(t) : "",
+ 6, u);
+ }
+
+ public void on_unit_new(string id, ObjectPath path) {
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ u.changed += on_unit_changed;
+
+ TreeIter iter;
+ unit_model.append(out iter);
+ update_unit_iter(iter, id, u);
+ }
+
+ public void update_job_iter(TreeIter iter, uint32 id, Job j) {
+ job_model.set(iter,
+ 0, "%u".printf(id),
+ 1, j.unit.id,
+ 2, "→ %s".printf(j.job_type),
+ 3, j.state,
+ 4, j,
+ 5, id);
+ }
+
+ public void on_job_new(uint32 id, ObjectPath path) {
+ Job j = bus.get_object(
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Job") as Job;
+
+ j.changed += on_job_changed;
+
+ TreeIter iter;
+ job_model.append(out iter);
+ update_job_iter(iter, id, j);
+ }
+
+ public void on_unit_removed(string id, ObjectPath path) {
+ TreeIter iter;
+ if (!(unit_model.get_iter_first(out iter)))
+ return;
+
+ do {
+ string name;
+
+ unit_model.get(iter, 0, out name);
+
+ if (id == name) {
+ if (current_unit_id == name)
+ clear_unit();
+
+ unit_model.remove(iter);
+ break;
+ }
+
+ } while (unit_model.iter_next(ref iter));
+ }
+
+ public void on_job_removed(uint32 id, ObjectPath path) {
+ TreeIter iter;
+ if (!(job_model.get_iter_first(out iter)))
+ return;
+
+ do {
+ uint32 j;
+
+ job_model.get(iter, 5, out j);
+
+ if (id == j) {
+ if (current_job_id == j)
+ clear_job();
+
+ job_model.remove(iter);
+
+ break;
+ }
+
+ } while (job_model.iter_next(ref iter));
+ }
+
+ public void on_unit_changed(Unit u) {
+ TreeIter iter;
+ string id;
+
+ if (!(unit_model.get_iter_first(out iter)))
+ return;
+
+ id = u.id;
+
+ do {
+ string name;
+
+ unit_model.get(iter, 0, out name);
+
+ if (id == name) {
+ update_unit_iter(iter, id, u);
+
+ if (current_unit_id == id)
+ show_unit(u);
+
+ break;
+ }
+
+ } while (unit_model.iter_next(ref iter));
+ }
+
+ public void on_job_changed(Job j) {
+ TreeIter iter;
+ uint32 id;
+
+ if (!(job_model.get_iter_first(out iter)))
+ return;
+
+ id = j.id;
+
+ do {
+ uint32 k;
+
+ job_model.get(iter, 5, out k);
+
+ if (id == k) {
+ update_job_iter(iter, id, j);
+
+ if (current_job_id == id)
+ show_job(j);
+
+ break;
+ }
+
+ } while (job_model.iter_next(ref iter));
+ }
+
+ public bool unit_filter(TreeModel model, TreeIter iter) {
+ string id, active_state, job;
+
+ model.get(iter, 0, out id, 3, out active_state, 5, out job);
+
+ if (id == null)
+ return false;
+
+ switch (unit_type_combo_box.get_active()) {
+
+ case 0:
+ return true;
+
+ case 1:
+ return active_state != "inactive" || job != "";
+
+ case 2:
+ return id.has_suffix(".service");
+
+ case 3:
+ return id.has_suffix(".socket");
+
+ case 4:
+ return id.has_suffix(".device");
+
+ case 5:
+ return id.has_suffix(".mount");
+
+ case 6:
+ return id.has_suffix(".automount");
+
+ case 7:
+ return id.has_suffix(".target");
+
+ case 8:
+ return id.has_suffix(".snapshot");
+ }
+
+ return false;
+ }
+
+ public void unit_type_changed() {
+ TreeModelFilter model = (TreeModelFilter) unit_view.get_model();
+
+ model.refilter();
+ }
+
+ public void on_server_reload() {
+ try {
+ manager.reload();
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_server_snapshot() {
+ try {
+ manager.create_snapshot();
+
+ if (unit_type_combo_box.get_active() != 0)
+ unit_type_combo_box.set_active(8);
+
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_unit_load() {
+ string t = unit_load_entry.get_text();
+
+ if (t == "")
+ return;
+
+ try {
+ var path = manager.load_unit(t);
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ var m = new MessageDialog(this,
+ DialogFlags.DESTROY_WITH_PARENT,
+ MessageType.INFO,
+ ButtonsType.CLOSE,
+ "Unit available as id %s", u.id);
+ m.title = "Unit";
+ m.run();
+ m.destroy();
+
+ show_unit(u);
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+ }
+
+ public void on_unit_load_entry_changed() {
+ unit_load_button.set_sensitive(unit_load_entry.get_text() != "");
+ }
+
+ public bool on_activate_link(string uri) {
+
+ try {
+ string path = manager.get_unit(uri);
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ show_unit(u);
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ }
+
+ return true;
+ }
+
+ public void show_error(string e) {
+ var m = new MessageDialog(this,
+ DialogFlags.DESTROY_WITH_PARENT,
+ MessageType.ERROR,
+ ButtonsType.CLOSE, "%s", e);
+ m.title = "Error";
+ m.run();
+ m.destroy();
+ }
+
+}
+
+static const OptionEntry entries[] = {
+ { "session", 0, 0, OptionArg.NONE, out session, "Connect to session bus", null },
+ { "system", 0, OptionFlags.REVERSE, OptionArg.NONE, out session, "Connect to system bus", null },
+ { null }
+};
+
+void show_error(string e) {
+ var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e);
+ m.run();
+ m.destroy();
+}
+
+int main (string[] args) {
+
+ try {
+ Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemadm");
+
+ MainWindow window = new MainWindow();
+ window.show_all();
+
+ Gtk.main();
+ } catch (DBus.Error e) {
+ show_error(e.message);
+ } catch (GLib.Error e) {
+ show_error(e.message);
+ }
+
+ return 0;
+}
diff --git a/src/systemctl.vala b/src/systemctl.vala
new file mode 100644
index 0000000000..821be5a4f8
--- /dev/null
+++ b/src/systemctl.vala
@@ -0,0 +1,321 @@
+/***
+ 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 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/>.
+***/
+
+using DBus;
+using GLib;
+
+static string type = null;
+static bool all = false;
+static bool replace = false;
+static bool session = false;
+static Connection bus = null;
+
+public static int job_info_compare(void* key1, void* key2) {
+ Manager.JobInfo *j1 = (Manager.JobInfo*) key1;
+ Manager.JobInfo *j2 = (Manager.JobInfo*) key2;
+
+ return j1->id < j2->id ? -1 : (j1->id > j2->id ? 1 : 0);
+}
+
+public static int unit_info_compare(void* key1, void* key2) {
+ Manager.UnitInfo *u1 = (Manager.UnitInfo*) key1;
+ Manager.UnitInfo *u2 = (Manager.UnitInfo*) key2;
+
+ int r = Posix.strcmp(Posix.strrchr(u1->id, '.'), Posix.strrchr(u2->id, '.'));
+ if (r != 0)
+ return r;
+
+ return Posix.strcmp(u1->id, u2->id);
+}
+
+public void on_unit_changed(Unit u) {
+ stdout.printf("Unit %s changed.\n", u.id);
+}
+
+public void on_unit_new(string id, ObjectPath path) {
+ stdout.printf("Unit %s added.\n", id);
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ u.changed += on_unit_changed;
+
+ /* FIXME: We leak memory here */
+ u.ref();
+}
+
+public void on_job_changed(Job j) {
+ stdout.printf("Job %u changed.\n", j.id);
+}
+
+public void on_job_new(uint32 id, ObjectPath path) {
+ stdout.printf("Job %u added.\n", id);
+
+ Job j = bus.get_object(
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Job") as Job;
+
+ j.changed += on_job_changed;
+
+ /* FIXME: We leak memory here */
+ j.ref();
+}
+
+public void on_unit_removed(string id, ObjectPath path) {
+ stdout.printf("Unit %s removed.\n", id);
+}
+
+public void on_job_removed(uint32 id, ObjectPath path) {
+ stdout.printf("Job %u removed.\n", id);
+}
+
+static const OptionEntry entries[] = {
+ { "type", 't', 0, OptionArg.STRING, out type, "List only particular type of units", "TYPE" },
+ { "all", 'a', 0, OptionArg.NONE, out all, "Show all units, including dead ones", null },
+ { "replace", 0, 0, OptionArg.NONE, out replace, "When installing a new job, replace existing conflicting ones", null },
+ { "session", 0, 0, OptionArg.NONE, out session, "Connect to session bus", null },
+ { "system", 0, OptionFlags.REVERSE, OptionArg.NONE, out session, "Connect to system bus", null },
+ { null }
+};
+
+int main (string[] args) {
+
+ OptionContext context = new OptionContext("[COMMAND [ARGUMENT...]]");
+ context.add_main_entries(entries, null);
+ context.set_description(
+ "Commands:\n" +
+ " list-units List units\n" +
+ " list-jobs List jobs\n" +
+ " clear-jobs Cancel all jobs\n" +
+ " load [NAME...] Load one or more units\n" +
+ " cancel [JOB...] Cancel one or more jobs\n" +
+ " start [NAME...] Start on or more units\n" +
+ " stop [NAME...] Stop on or more units\n" +
+ " enter [NAME] Start one unit and stop all others\n" +
+ " restart [NAME...] Restart on or more units\n" +
+ " reload [NAME...] Reload on or more units\n" +
+ " monitor Monitor unit/job changes\n" +
+ " dump Dump server status\n" +
+ " snapshot [NAME] Create a snapshot\n" +
+ " daemon-reload Reload daemon configuration\n" +
+ " daemon-reexecute Reexecute daemon\n" +
+ " show-environment Dump environment\n" +
+ " set-environment [NAME=VALUE...] Set one or more environment variables\n" +
+ " unset-environment [NAME...] Unset one or more environment variables\n");
+
+ try {
+ context.parse(ref args);
+ } catch (GLib.OptionError e) {
+ message("Failed to parse command line: %s".printf(e.message));
+ }
+
+ try {
+ bus = Bus.get(session ? BusType.SESSION : BusType.SYSTEM);
+
+ Manager manager = bus.get_object (
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager") as Manager;
+
+ if (args[1] == "list-units" || args.length <= 1) {
+ var list = manager.list_units();
+ uint n = 0;
+ Posix.qsort(list, list.length, sizeof(Manager.UnitInfo), unit_info_compare);
+
+ stdout.printf("%-45s %-6s %-12s %-12s %-17s\n", "UNIT", "LOAD", "ACTIVE", "SUB", "JOB");
+
+ foreach (var i in list) {
+
+ if (type != null && !i.id.has_suffix(".%s".printf(type)))
+ continue;
+
+ if (!all && i.active_state == "inactive")
+ continue;
+
+ stdout.printf("%-45s %-6s %-12s %-12s", i.id, i.load_state, i.active_state, i.sub_state);
+
+ if (i.job_id != 0)
+ stdout.printf(" -> %-15s", i.job_type);
+
+ stdout.puts("\n");
+ n++;
+ }
+
+ if (all)
+ stdout.printf("\n%u units listed.\n", n);
+ else
+ stdout.printf("\n%u live units listed. Pass --all to see dead units, too.\n", n);
+
+
+ } else if (args[1] == "list-jobs") {
+ var list = manager.list_jobs();
+ Posix.qsort(list, list.length, sizeof(Manager.JobInfo), job_info_compare);
+
+ stdout.printf("%4s %-45s %-17s %-7s\n", "JOB", "UNIT", "TYPE", "STATE");
+
+ foreach (var i in list)
+ stdout.printf("%4u %-45s → %-15s %-7s\n", i.id, i.name, i.type, i.state);
+
+ stdout.printf("\n%u jobs listed.\n", list.length);
+
+ } else if (args[1] == "clear-jobs") {
+
+ manager.clear_jobs();
+
+ } else if (args[1] == "load") {
+
+ if (args.length < 3) {
+ stderr.printf("Missing argument.\n");
+ return 1;
+ }
+
+ for (int i = 2; i < args.length; i++)
+ manager.load_unit(args[i]);
+
+ } else if (args[1] == "cancel") {
+
+ if (args.length < 3) {
+ stderr.printf("Missing argument.\n");
+ return 1;
+ }
+
+ for (int i = 2; i < args.length; i++) {
+ uint32 id;
+
+ if (args[i].scanf("%u", out id) != 1) {
+ stderr.printf("Failed to parse argument.\n");
+ return 1;
+ }
+
+ ObjectPath p = manager.get_job(id);
+
+ Job j = bus.get_object (
+ "org.freedesktop.systemd1",
+ p,
+ "org.freedesktop.systemd1.Job") as Job;
+
+ j.cancel();
+ }
+
+ } else if (args[1] == "start" ||
+ args[1] == "stop" ||
+ args[1] == "reload" ||
+ args[1] == "restart") {
+
+ if (args.length < 3) {
+ stderr.printf("Missing argument.\n");
+ return 1;
+ }
+
+ for (int i = 2; i < args.length; i++) {
+
+ ObjectPath p = manager.load_unit(args[i]);
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ p,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ string mode = replace ? "replace" : "fail";
+
+ if (args[1] == "start")
+ u.start(mode);
+ else if (args[1] == "stop")
+ u.stop(mode);
+ else if (args[1] == "restart")
+ u.restart(mode);
+ else if (args[1] == "reload")
+ u.reload(mode);
+ }
+
+ } else if (args[1] == "isolate") {
+
+ if (args.length != 3) {
+ stderr.printf("Missing argument.\n");
+ return 1;
+ }
+
+ ObjectPath p = manager.load_unit(args[2]);
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ p,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ u.start("isolate");
+
+ } else if (args[1] == "monitor") {
+
+ manager.subscribe();
+
+ manager.unit_new += on_unit_new;
+ manager.unit_removed += on_unit_removed;
+ manager.job_new += on_job_new;
+ manager.job_removed += on_job_removed;
+
+ MainLoop l = new MainLoop();
+ l.run();
+
+ } else if (args[1] == "dump")
+ stdout.puts(manager.dump());
+
+ else if (args[1] == "snapshot") {
+
+ ObjectPath p = manager.create_snapshot(args.length > 2 ? args[2] : "");
+
+ Unit u = bus.get_object(
+ "org.freedesktop.systemd1",
+ p,
+ "org.freedesktop.systemd1.Unit") as Unit;
+
+ stdout.printf("%s\n", u.id);
+
+ } else if (args[1] == "daemon-reload")
+ manager.reload();
+
+ else if (args[1] == "daemon-reexecute" || args[1] == "daemon-reexec")
+ manager.reexecute();
+
+ else if (args[1] == "daemon-exit")
+ manager.exit();
+
+ else if (args[1] == "show-environment") {
+ foreach(var x in manager.environment)
+ stderr.printf("%s\n", x);
+
+ } else if (args[1] == "set-environment")
+ manager.set_environment(args[2:args.length]);
+
+ else if (args[1] == "unset-environment")
+ manager.unset_environment(args[2:args.length]);
+
+ else {
+ stderr.printf("Unknown command %s.\n", args[1]);
+ return 1;
+ }
+
+ } catch (DBus.Error e) {
+ stderr.printf("%s\n".printf(e.message));
+ }
+
+ return 0;
+}
diff --git a/src/systemd-interfaces.vala b/src/systemd-interfaces.vala
new file mode 100644
index 0000000000..7282bf3dd8
--- /dev/null
+++ b/src/systemd-interfaces.vala
@@ -0,0 +1,137 @@
+/***
+ 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 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/>.
+***/
+
+using DBus;
+
+[DBus (name = "org.freedesktop.systemd1.Manager")]
+public interface Manager : DBus.Object {
+
+ public struct UnitInfo {
+ string id;
+ string description;
+ string load_state;
+ string active_state;
+ string sub_state;
+ ObjectPath unit_path;
+ uint32 job_id;
+ string job_type;
+ ObjectPath job_path;
+ }
+
+ public struct JobInfo {
+ uint32 id;
+ string name;
+ string type;
+ string state;
+ ObjectPath job_path;
+ ObjectPath unit_path;
+ }
+
+ public abstract string[] environment { owned get; }
+
+ public abstract UnitInfo[] list_units() throws DBus.Error;
+ public abstract JobInfo[] list_jobs() throws DBus.Error;
+
+ public abstract ObjectPath get_unit(string name) throws DBus.Error;
+ public abstract ObjectPath load_unit(string name) throws DBus.Error;
+ public abstract ObjectPath get_job(uint32 id) throws DBus.Error;
+
+ public abstract void clear_jobs() throws DBus.Error;
+
+ public abstract void subscribe() throws DBus.Error;
+ public abstract void unsubscribe() throws DBus.Error;
+
+ public abstract string dump() throws DBus.Error;
+
+ public abstract void reload() throws DBus.Error;
+ public abstract void reexecute() throws DBus.Error;
+ public abstract void exit() throws DBus.Error;
+
+ public abstract ObjectPath create_snapshot(string name = "", bool cleanup = false) throws DBus.Error;
+
+ public abstract void set_environment(string[] names) throws DBus.Error;
+ public abstract void unset_environment(string[] names) throws DBus.Error;
+
+ public abstract signal void unit_new(string id, ObjectPath path);
+ public abstract signal void unit_removed(string id, ObjectPath path);
+ public abstract signal void job_new(uint32 id, ObjectPath path);
+ public abstract signal void job_removed(uint32 id, ObjectPath path);
+}
+
+[DBus (name = "org.freedesktop.systemd1.Unit")]
+public interface Unit : DBus.Object {
+ public struct JobLink {
+ uint32 id;
+ ObjectPath path;
+ }
+
+ public abstract string id { owned get; }
+ public abstract string[] names { owned get; }
+ public abstract string[] requires { owned get; }
+ public abstract string[] requires_overridable { owned get; }
+ public abstract string[] requisite { owned get; }
+ public abstract string[] requisite_overridable { owned get; }
+ public abstract string[] wants { owned get; }
+ public abstract string[] required_by { owned get; }
+ public abstract string[] required_by_overridable { owned get; }
+ public abstract string[] wanted_by { owned get; }
+ public abstract string[] conflicts { owned get; }
+ public abstract string[] before { owned get; }
+ public abstract string[] after { owned get; }
+ public abstract string description { owned get; }
+ public abstract string load_state { owned get; }
+ public abstract string active_state { owned get; }
+ public abstract string sub_state { owned get; }
+ public abstract string fragment_path { owned get; }
+ public abstract uint64 inactive_exit_timestamp { owned get; }
+ public abstract uint64 active_enter_timestamp { owned get; }
+ public abstract uint64 active_exit_timestamp { owned get; }
+ public abstract uint64 inactive_enter_timestamp { owned get; }
+ public abstract bool can_start { owned get; }
+ public abstract bool can_reload { owned get; }
+ public abstract JobLink job { owned get; }
+ public abstract bool recursive_stop { owned get; }
+ public abstract bool stop_when_unneeded { owned get; }
+ public abstract string default_control_group { owned get; }
+ public abstract string[] control_groups { owned get; }
+
+ public abstract ObjectPath start(string mode) throws DBus.Error;
+ public abstract ObjectPath stop(string mode) throws DBus.Error;
+ public abstract ObjectPath restart(string mode) throws DBus.Error;
+ public abstract ObjectPath reload(string mode) throws DBus.Error;
+
+ public abstract signal void changed();
+}
+
+[DBus (name = "org.freedesktop.systemd1.Job")]
+public interface Job : DBus.Object {
+ public struct UnitLink {
+ string id;
+ ObjectPath path;
+ }
+
+ public abstract uint32 id { owned get; }
+ public abstract string state { owned get; }
+ public abstract string job_type { owned get; }
+ public abstract UnitLink unit { owned get; }
+
+ public abstract void cancel() throws DBus.Error;
+
+ public abstract signal void changed();
+}
diff --git a/src/target.c b/src/target.c
new file mode 100644
index 0000000000..75f8ef8948
--- /dev/null
+++ b/src/target.c
@@ -0,0 +1,194 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <signal.h>
+
+#include "unit.h"
+#include "target.h"
+#include "load-fragment.h"
+#include "log.h"
+#include "dbus-target.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)->meta.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]);
+}
+
+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;
+
+ if ((state = target_state_from_string(value)) < 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);
+}
+
+int target_get_runlevel(Target *t) {
+
+ static const struct {
+ const char *special;
+ const int runlevel;
+ } table[] = {
+ { SPECIAL_RUNLEVEL5_TARGET, '5' },
+ { SPECIAL_RUNLEVEL4_TARGET, '4' },
+ { SPECIAL_RUNLEVEL3_TARGET, '3' },
+ { SPECIAL_RUNLEVEL2_TARGET, '2' },
+ { SPECIAL_RUNLEVEL1_TARGET, '1' },
+ { SPECIAL_RUNLEVEL0_TARGET, '0' },
+ { SPECIAL_RUNLEVEL6_TARGET, '6' },
+ };
+
+ unsigned i;
+
+ assert(t);
+
+ /* Tries to determine if this is a SysV runlevel and returns
+ * it if that is so. */
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (unit_has_name(UNIT(t), table[i].special))
+ return table[i].runlevel;
+
+ return 0;
+}
+
+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 = {
+ .suffix = ".target",
+
+ .load = unit_load_fragment_and_dropin,
+ .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_message_handler = bus_target_message_handler
+};
diff --git a/src/target.h b/src/target.h
new file mode 100644
index 0000000000..5397d50d7c
--- /dev/null
+++ b/src/target.h
@@ -0,0 +1,49 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef footargethfoo
+#define footargethfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Target Target;
+
+#include "unit.h"
+
+typedef enum TargetState {
+ TARGET_DEAD,
+ TARGET_ACTIVE,
+ _TARGET_STATE_MAX,
+ _TARGET_STATE_INVALID = -1
+} TargetState;
+
+struct Target {
+ Meta meta;
+
+ TargetState state, deserialized_state;
+};
+
+extern const UnitVTable target_vtable;
+
+int target_get_runlevel(Target *t);
+
+const char* target_state_to_string(TargetState i);
+TargetState target_state_from_string(const char *s);
+
+#endif
diff --git a/src/test-engine.c b/src/test-engine.c
new file mode 100644
index 0000000000..27e16f3484
--- /dev/null
+++ b/src/test-engine.c
@@ -0,0 +1,99 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "manager.h"
+
+int main(int argc, char *argv[]) {
+ Manager *m = NULL;
+ Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, *h = NULL;
+ Job *j;
+
+ assert_se(set_unit_path("test2") >= 0);
+
+ assert_se(manager_new(MANAGER_INIT, false, &m) >= 0);
+
+ printf("Load1:\n");
+ assert_se(manager_load_unit(m, "a.service", NULL, &a) == 0);
+ assert_se(manager_load_unit(m, "b.service", NULL, &b) == 0);
+ assert_se(manager_load_unit(m, "c.service", NULL, &c) == 0);
+ manager_dump_units(m, stdout, "\t");
+
+ printf("Test1: (Trivial)\n");
+ assert_se(manager_add_job(m, JOB_START, c, JOB_REPLACE, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Load2:\n");
+ manager_clear_jobs(m);
+ assert_se(manager_load_unit(m, "d.service", NULL, &d) == 0);
+ assert_se(manager_load_unit(m, "e.service", NULL, &e) == 0);
+ manager_dump_units(m, stdout, "\t");
+
+ printf("Test2: (Cyclic Order, Unfixable)\n");
+ assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, false, &j) == -ENOEXEC);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Test3: (Cyclic Order, Fixable, Garbage Collector)\n");
+ assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Test4: (Identical transaction)\n");
+ assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Load3:\n");
+ assert_se(manager_load_unit(m, "g.service", NULL, &g) == 0);
+ manager_dump_units(m, stdout, "\t");
+
+ printf("Test5: (Colliding transaction, fail)\n");
+ assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, false, &j) == -EEXIST);
+
+ printf("Test6: (Colliding transaction, replace)\n");
+ assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Test7: (Unmeargable job type, fail)\n");
+ assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, false, &j) == -EEXIST);
+
+ printf("Test8: (Mergeable job type, fail)\n");
+ assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Test9: (Unmeargable job type, replace)\n");
+ assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Load4:\n");
+ assert_se(manager_load_unit(m, "h.service", NULL, &h) == 0);
+ manager_dump_units(m, stdout, "\t");
+
+ printf("Test10: (Unmeargable job type of auxiliary job, fail)\n");
+ assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, false, &j) == 0);
+ manager_dump_jobs(m, stdout, "\t");
+
+ manager_free(m);
+
+ return 0;
+}
diff --git a/src/test-job-type.c b/src/test-job-type.c
new file mode 100644
index 0000000000..b531262cfd
--- /dev/null
+++ b/src/test-job-type.c
@@ -0,0 +1,84 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "job.h"
+
+int main(int argc, char*argv[]) {
+ JobType a, b, c, d, e, f, g;
+
+ for (a = 0; a < _JOB_TYPE_MAX; a++)
+ for (b = 0; b < _JOB_TYPE_MAX; b++) {
+
+ if (!job_type_is_mergeable(a, b))
+ printf("Not mergeable: %s + %s\n", job_type_to_string(a), job_type_to_string(b));
+
+ for (c = 0; c < _JOB_TYPE_MAX; c++) {
+
+ /* Verify transitivity of mergeability
+ * of job types */
+ assert(!job_type_is_mergeable(a, b) ||
+ !job_type_is_mergeable(b, c) ||
+ job_type_is_mergeable(a, c));
+
+ d = a;
+ if (job_type_merge(&d, b) >= 0) {
+
+ printf("%s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(d));
+
+ /* Verify that merged entries can be
+ * merged with the same entries they
+ * can be merged with seperately */
+ assert(!job_type_is_mergeable(a, c) || job_type_is_mergeable(d, c));
+ assert(!job_type_is_mergeable(b, c) || job_type_is_mergeable(d, c));
+
+ /* Verify that if a merged
+ * with b is not mergable with
+ * c then either a or b is not
+ * mergeable with c either. */
+ assert(job_type_is_mergeable(d, c) || !job_type_is_mergeable(a, c) || !job_type_is_mergeable(b, c));
+
+ e = b;
+ if (job_type_merge(&e, c) >= 0) {
+
+ /* Verify associativity */
+
+ f = d;
+ assert(job_type_merge(&f, c) == 0);
+
+ g = e;
+ assert(job_type_merge(&g, a) == 0);
+
+ assert(f == g);
+
+ printf("%s + %s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(c), job_type_to_string(d));
+ }
+ }
+ }
+ }
+
+
+ return 0;
+}
diff --git a/src/test-loopback.c b/src/test-loopback.c
new file mode 100644
index 0000000000..5cd7b41e19
--- /dev/null
+++ b/src/test-loopback.c
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "loopback-setup.h"
+
+int main(int argc, char* argv[]) {
+ int r;
+
+ if ((r = loopback_setup()) < 0)
+ fprintf(stderr, "loopback: %s\n", strerror(-r));
+
+ return 0;
+}
diff --git a/src/test-ns.c b/src/test-ns.c
new file mode 100644
index 0000000000..a54011e300
--- /dev/null
+++ b/src/test-ns.c
@@ -0,0 +1,60 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+
+#include "namespace.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+ const char * const writable[] = {
+ "/home",
+ NULL
+ };
+
+ const char * const readable[] = {
+ "/",
+ "/usr",
+ "/boot",
+ NULL
+ };
+
+ const char * const inaccessible[] = {
+ "/home/lennart/projects",
+ NULL
+ };
+
+ int r;
+
+ if ((r = setup_namespace((char**) writable, (char**) readable, (char**) inaccessible, true, MS_SHARED)) < 0) {
+ log_error("Failed to setup namespace: %s", strerror(-r));
+ return 1;
+ }
+
+ execl("/bin/sh", "/bin/sh", NULL);
+ log_error("execl(): %m");
+
+ return 1;
+}
diff --git a/src/timer.c b/src/timer.c
new file mode 100644
index 0000000000..41aeb7f3a5
--- /dev/null
+++ b/src/timer.c
@@ -0,0 +1,51 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+
+#include "unit.h"
+#include "timer.h"
+
+static void timer_done(Unit *u) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+}
+
+static UnitActiveState timer_active_state(Unit *u) {
+
+ static const UnitActiveState table[_TIMER_STATE_MAX] = {
+ [TIMER_DEAD] = UNIT_INACTIVE,
+ [TIMER_WAITING] = UNIT_ACTIVE,
+ [TIMER_RUNNING] = UNIT_ACTIVE
+ };
+
+ return table[TIMER(u)->state];
+}
+
+const UnitVTable timer_vtable = {
+ .suffix = ".timer",
+
+ .load = unit_load_fragment_and_dropin,
+ .done = timer_done,
+
+ .active_state = timer_active_state
+};
diff --git a/src/timer.h b/src/timer.h
new file mode 100644
index 0000000000..0ec3e0d9bb
--- /dev/null
+++ b/src/timer.h
@@ -0,0 +1,49 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef footimerhfoo
+#define footimerhfoo
+
+/***
+ 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 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/>.
+***/
+
+typedef struct Timer Timer;
+
+#include "unit.h"
+
+typedef enum TimerState {
+ TIMER_DEAD,
+ TIMER_WAITING,
+ TIMER_RUNNING,
+ _TIMER_STATE_MAX
+} TimerState;
+
+struct Timer {
+ Meta meta;
+
+ TimerState state;
+
+ clockid_t clock_id;
+ usec_t next_elapse;
+
+ Service *service;
+};
+
+extern const UnitVTable timer_vtable;
+
+#endif
diff --git a/src/unit-name.c b/src/unit-name.c
new file mode 100644
index 0000000000..c5901cacfa
--- /dev/null
+++ b/src/unit-name.c
@@ -0,0 +1,424 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <errno.h>
+#include <string.h>
+
+#include "unit.h"
+#include "unit-name.h"
+
+#define VALID_CHARS \
+ "0123456789" \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ ":-_.\\"
+
+UnitType unit_name_to_type(const char *n) {
+ UnitType t;
+
+ assert(n);
+
+ for (t = 0; t < _UNIT_TYPE_MAX; t++)
+ if (endswith(n, unit_vtable[t]->suffix))
+ return t;
+
+ return _UNIT_TYPE_INVALID;
+}
+
+bool unit_name_is_valid(const char *n) {
+ UnitType t;
+ const char *e, *i, *at;
+
+ /* Valid formats:
+ *
+ * string@instance.suffix
+ * string.suffix
+ */
+
+ assert(n);
+
+ if (strlen(n) >= UNIT_NAME_MAX)
+ return false;
+
+ t = unit_name_to_type(n);
+ if (t < 0 || t >= _UNIT_TYPE_MAX)
+ return false;
+
+ assert_se(e = strrchr(n, '.'));
+
+ if (e == n)
+ return false;
+
+ for (i = n, at = NULL; i < e; i++) {
+
+ if (*i == '@' && !at)
+ at = i;
+
+ if (!strchr("@" VALID_CHARS, *i))
+ return false;
+ }
+
+ if (at) {
+ if (at == n)
+ return false;
+
+ if (at[1] == '.')
+ return false;
+ }
+
+ return true;
+}
+
+bool unit_instance_is_valid(const char *i) {
+ assert(i);
+
+ /* The max length depends on the length of the string, so we
+ * don't really check this here. */
+
+ if (i[0] == 0)
+ return false;
+
+ /* We allow additional @ in the instance string, we do not
+ * allow them in the prefix! */
+
+ for (; *i; i++)
+ if (!strchr("@" VALID_CHARS, *i))
+ return false;
+
+ return true;
+}
+
+bool unit_prefix_is_valid(const char *p) {
+
+ /* We don't allow additional @ in the instance string */
+
+ if (p[0] == 0)
+ return false;
+
+ for (; *p; p++)
+ if (!strchr(VALID_CHARS, *p))
+ return false;
+
+ return true;
+}
+
+int unit_name_to_instance(const char *n, char **instance) {
+ const char *p, *d;
+ char *i;
+
+ assert(n);
+ assert(instance);
+
+ /* Everything past the first @ and before the last . is the instance */
+ if (!(p = strchr(n, '@'))) {
+ *instance = NULL;
+ return 0;
+ }
+
+ assert_se(d = strrchr(n, '.'));
+ assert(p < d);
+
+ if (!(i = strndup(p+1, d-p-1)))
+ return -ENOMEM;
+
+ *instance = i;
+ return 0;
+}
+
+char *unit_name_to_prefix_and_instance(const char *n) {
+ const char *d;
+
+ assert(n);
+
+ assert_se(d = strrchr(n, '.'));
+
+ return strndup(n, d - n);
+}
+
+char *unit_name_to_prefix(const char *n) {
+ const char *p;
+
+ if ((p = strchr(n, '@')))
+ return strndup(n, p - n);
+
+ return unit_name_to_prefix_and_instance(n);
+}
+
+char *unit_name_change_suffix(const char *n, const char *suffix) {
+ char *e, *r;
+ size_t a, b;
+
+ assert(n);
+ assert(unit_name_is_valid(n));
+ assert(suffix);
+
+ assert_se(e = strrchr(n, '.'));
+ a = e - n;
+ b = strlen(suffix);
+
+ if (!(r = new(char, a + b + 1)))
+ return NULL;
+
+ memcpy(r, n, a);
+ memcpy(r+a, suffix, b+1);
+
+ return r;
+}
+
+char *unit_name_build(const char *prefix, const char *instance, const char *suffix) {
+ char *r;
+
+ assert(prefix);
+ assert(unit_prefix_is_valid(prefix));
+ assert(!instance || unit_instance_is_valid(instance));
+ assert(suffix);
+ assert(unit_name_to_type(suffix) >= 0);
+
+ if (!instance)
+ return strappend(prefix, suffix);
+
+ if (asprintf(&r, "%s@%s%s", prefix, instance, suffix) < 0)
+ return NULL;
+
+ return r;
+}
+
+static char* do_escape(const char *f, char *t) {
+ assert(f);
+ assert(t);
+
+ for (; *f; f++) {
+ if (*f == '/')
+ *(t++) = '-';
+ else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) {
+ *(t++) = '\\';
+ *(t++) = 'x';
+ *(t++) = hexchar(*f > 4);
+ *(t++) = hexchar(*f);
+ } else
+ *(t++) = *f;
+ }
+
+ return t;
+}
+
+char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix) {
+ char *r, *t;
+ size_t a, b, c;
+
+ assert(prefix);
+ assert(suffix);
+ assert(unit_name_to_type(suffix) >= 0);
+
+ /* Takes a arbitrary string for prefix and instance plus a
+ * suffix and makes a nice string suitable as unit name of it,
+ * escaping all weird chars on the way.
+ *
+ * / becomes ., and all chars not alloweed in a unit name get
+ * escaped as \xFF, including \ and ., of course. This
+ * escaping is hence reversible.
+ *
+ * This is primarily useful to make nice unit names from
+ * strings, but is actually useful for any kind of string.
+ */
+
+ a = strlen(prefix);
+ c = strlen(suffix);
+
+ if (instance) {
+ b = strlen(instance);
+
+ if (!(r = new(char, a*4 + 1 + b*4 + c + 1)))
+ return NULL;
+
+ t = do_escape(prefix, r);
+ *(t++) = '@';
+ t = do_escape(instance, t);
+ } else {
+
+ if (!(r = new(char, a*4 + c + 1)))
+ return NULL;
+
+ t = do_escape(prefix, r);
+ }
+
+ strcpy(t, suffix);
+ return r;
+}
+
+char *unit_name_escape(const char *f) {
+ char *r, *t;
+
+ if (!(r = new(char, strlen(f)*4+1)))
+ return NULL;
+
+ t = do_escape(f, r);
+ *t = 0;
+
+ return r;
+
+}
+
+char *unit_name_unescape(const char *f) {
+ char *r, *t;
+
+ assert(f);
+
+ if (!(r = strdup(f)))
+ return NULL;
+
+ for (t = r; *f; f++) {
+ if (*f == '-')
+ *(t++) = '/';
+ else if (*f == '\\') {
+ int a, b;
+
+ if ((a = unhexchar(f[1])) < 0 ||
+ (b = unhexchar(f[2])) < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ } else {
+ *(t++) = (char) ((a << 4) | b);
+ f += 2;
+ }
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+bool unit_name_is_template(const char *n) {
+ const char *p;
+
+ assert(n);
+
+ if (!(p = strchr(n, '@')))
+ return false;
+
+ return p[1] == '.';
+}
+
+char *unit_name_replace_instance(const char *f, const char *i) {
+ const char *p, *e;
+ char *r, *k;
+ size_t a;
+
+ assert(f);
+
+ p = strchr(f, '@');
+ assert_se(e = strrchr(f, '.'));
+
+ a = p - f;
+
+ if (p) {
+ size_t b;
+
+ b = strlen(i);
+
+ if (!(r = new(char, a + 1 + b + strlen(e) + 1)))
+ return NULL;
+
+ k = mempcpy(r, f, a + 1);
+ k = mempcpy(k, i, b);
+ } else {
+
+ if (!(r = new(char, a + strlen(e) + 1)))
+ return NULL;
+
+ k = mempcpy(r, f, a);
+ }
+
+ strcpy(k, e);
+ return r;
+}
+
+char *unit_name_template(const char *f) {
+ const char *p, *e;
+ char *r;
+ size_t a;
+
+ if (!(p = strchr(f, '@')))
+ return strdup(f);
+
+ assert_se(e = strrchr(f, '.'));
+ a = p - f + 1;
+
+ if (!(r = new(char, a + strlen(e) + 1)))
+ return NULL;
+
+ strcpy(mempcpy(r, f, a), e);
+ return r;
+
+}
+
+char *unit_name_from_path(const char *path, const char *suffix) {
+ char *p, *r;
+
+ assert(path);
+ assert(suffix);
+
+ if (!(p = strdup(path)))
+ return NULL;
+
+ path_kill_slashes(p);
+
+ path = p[0] == '/' ? p + 1 : p;
+
+ if (path[0] == 0) {
+ free(p);
+ return strappend("-", suffix);
+ }
+
+ r = unit_name_build_escape(path, NULL, suffix);
+ free(p);
+
+ return r;
+}
+
+char *unit_name_to_path(const char *name) {
+ char *w, *e;
+
+ assert(name);
+
+ if (!(w = unit_name_to_prefix(name)))
+ return NULL;
+
+ e = unit_name_unescape(w);
+ free(w);
+
+ if (!e)
+ return NULL;
+
+ if (e[0] != '/') {
+ w = strappend("/", e);
+ free(e);
+
+ if (!w)
+ return NULL;
+
+ e = w;
+ }
+
+ return e;
+}
diff --git a/src/unit-name.h b/src/unit-name.h
new file mode 100644
index 0000000000..b6dd2c9123
--- /dev/null
+++ b/src/unit-name.h
@@ -0,0 +1,54 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foounitnamehfoo
+#define foounitnamehfoo
+
+/***
+ 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 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 "unit.h"
+
+UnitType unit_name_to_type(const char *n);
+
+int unit_name_to_instance(const char *n, char **instance);
+char* unit_name_to_prefix(const char *n);
+char* unit_name_to_prefix_and_instance(const char *n);
+
+bool unit_name_is_valid(const char *n);
+bool unit_prefix_is_valid(const char *p);
+bool unit_instance_is_valid(const char *i);
+
+char *unit_name_change_suffix(const char *n, const char *suffix);
+
+char *unit_name_build(const char *prefix, const char *instance, const char *suffix);
+char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix);
+
+char *unit_name_escape(const char *f);
+char *unit_name_unescape(const char *f);
+
+bool unit_name_is_template(const char *n);
+
+char *unit_name_replace_instance(const char *f, const char *i);
+
+char *unit_name_template(const char *f);
+
+char *unit_name_from_path(const char *path, const char *suffix);
+char *unit_name_to_path(const char *name);
+
+#endif
diff --git a/src/unit.c b/src/unit.c
new file mode 100644
index 0000000000..1959b1b940
--- /dev/null
+++ b/src/unit.c
@@ -0,0 +1,1949 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <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 "set.h"
+#include "unit.h"
+#include "macro.h"
+#include "strv.h"
+#include "load-fragment.h"
+#include "load-dropin.h"
+#include "log.h"
+#include "unit-name.h"
+#include "specifier.h"
+#include "dbus-unit.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 *unit_new(Manager *m) {
+ Unit *u;
+
+ assert(m);
+
+ if (!(u = new0(Unit, 1)))
+ return NULL;
+
+ if (!(u->meta.names = set_new(string_hash_func, string_compare_func))) {
+ free(u);
+ return NULL;
+ }
+
+ u->meta.manager = m;
+ u->meta.type = _UNIT_TYPE_INVALID;
+
+ return u;
+}
+
+bool unit_has_name(Unit *u, const char *name) {
+ assert(u);
+ assert(name);
+
+ return !!set_get(u->meta.names, (char*) name);
+}
+
+int unit_add_name(Unit *u, const char *text) {
+ UnitType t;
+ char *s = NULL, *i = NULL;
+ int r;
+
+ assert(u);
+ assert(text);
+
+ if (unit_name_is_template(text)) {
+ if (!u->meta.instance)
+ return -EINVAL;
+
+ s = unit_name_replace_instance(text, u->meta.instance);
+ } else
+ s = strdup(text);
+
+ if (!s)
+ return -ENOMEM;
+
+ if (!unit_name_is_valid(s)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ assert_se((t = unit_name_to_type(s)) >= 0);
+
+ if (u->meta.type != _UNIT_TYPE_INVALID && t != u->meta.type) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if ((r = unit_name_to_instance(s, &i)) < 0)
+ goto fail;
+
+ if (i && unit_vtable[t]->no_instances)
+ goto fail;
+
+ if (u->meta.type != _UNIT_TYPE_INVALID && !streq_ptr(u->meta.instance, i)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (unit_vtable[t]->no_alias &&
+ !set_isempty(u->meta.names) &&
+ !set_get(u->meta.names, s)) {
+ r = -EEXIST;
+ goto fail;
+ }
+
+ if (hashmap_size(u->meta.manager->units) >= MANAGER_MAX_NAMES) {
+ r = -E2BIG;
+ goto fail;
+ }
+
+ if ((r = set_put(u->meta.names, s)) < 0) {
+ if (r == -EEXIST)
+ r = 0;
+ goto fail;
+ }
+
+ if ((r = hashmap_put(u->meta.manager->units, s, u)) < 0) {
+ set_remove(u->meta.names, s);
+ goto fail;
+ }
+
+ if (u->meta.type == _UNIT_TYPE_INVALID) {
+
+ u->meta.type = t;
+ u->meta.id = s;
+ u->meta.instance = i;
+
+ LIST_PREPEND(Meta, units_per_type, u->meta.manager->units_per_type[t], &u->meta);
+
+ 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;
+
+ assert(u);
+ assert(name);
+
+ if (unit_name_is_template(name)) {
+
+ if (!u->meta.instance)
+ return -EINVAL;
+
+ if (!(t = unit_name_replace_instance(name, u->meta.instance)))
+ return -ENOMEM;
+
+ name = t;
+ }
+
+ /* Selects one of the names of this unit as the id */
+ s = set_get(u->meta.names, (char*) name);
+ free(t);
+
+ if (!s)
+ return -ENOENT;
+
+ u->meta.id = s;
+ 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->meta.description);
+ u->meta.description = s;
+
+ unit_add_to_dbus_queue(u);
+ return 0;
+}
+
+bool unit_check_gc(Unit *u) {
+ assert(u);
+
+ if (UNIT_VTABLE(u)->no_gc)
+ return true;
+
+ if (u->meta.job)
+ return true;
+
+ if (unit_active_state(u) != UNIT_INACTIVE)
+ 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->meta.type != _UNIT_TYPE_INVALID);
+
+ if (u->meta.load_state != UNIT_STUB || u->meta.in_load_queue)
+ return;
+
+ LIST_PREPEND(Meta, load_queue, u->meta.manager->load_queue, &u->meta);
+ u->meta.in_load_queue = true;
+}
+
+void unit_add_to_cleanup_queue(Unit *u) {
+ assert(u);
+
+ if (u->meta.in_cleanup_queue)
+ return;
+
+ LIST_PREPEND(Meta, cleanup_queue, u->meta.manager->cleanup_queue, &u->meta);
+ u->meta.in_cleanup_queue = true;
+}
+
+void unit_add_to_gc_queue(Unit *u) {
+ assert(u);
+
+ if (u->meta.in_gc_queue || u->meta.in_cleanup_queue)
+ return;
+
+ if (unit_check_gc(u))
+ return;
+
+ LIST_PREPEND(Meta, gc_queue, u->meta.manager->gc_queue, &u->meta);
+ u->meta.in_gc_queue = true;
+
+ u->meta.manager->n_in_gc_queue ++;
+
+ if (u->meta.manager->gc_queue_timestamp <= 0)
+ u->meta.manager->gc_queue_timestamp = now(CLOCK_MONOTONIC);
+}
+
+void unit_add_to_dbus_queue(Unit *u) {
+ assert(u);
+ assert(u->meta.type != _UNIT_TYPE_INVALID);
+
+ if (u->meta.load_state == UNIT_STUB || u->meta.in_dbus_queue)
+ return;
+
+ if (set_isempty(u->meta.manager->subscribed)) {
+ u->meta.sent_dbus_new_signal = true;
+ return;
+ }
+
+ LIST_PREPEND(Meta, dbus_queue, u->meta.manager->dbus_unit_queue, &u->meta);
+ u->meta.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->meta.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);
+
+ /* Detach from next 'bigger' objects */
+ SET_FOREACH(t, u->meta.names, i)
+ hashmap_remove_value(u->meta.manager->units, t, u);
+
+ if (u->meta.type != _UNIT_TYPE_INVALID)
+ LIST_REMOVE(Meta, units_per_type, u->meta.manager->units_per_type[u->meta.type], &u->meta);
+
+ if (u->meta.in_load_queue)
+ LIST_REMOVE(Meta, load_queue, u->meta.manager->load_queue, &u->meta);
+
+ if (u->meta.in_dbus_queue)
+ LIST_REMOVE(Meta, dbus_queue, u->meta.manager->dbus_unit_queue, &u->meta);
+
+ if (u->meta.in_cleanup_queue)
+ LIST_REMOVE(Meta, cleanup_queue, u->meta.manager->cleanup_queue, &u->meta);
+
+ if (u->meta.in_gc_queue) {
+ LIST_REMOVE(Meta, gc_queue, u->meta.manager->gc_queue, &u->meta);
+ u->meta.manager->n_in_gc_queue--;
+ }
+
+ /* Free data and next 'smaller' objects */
+ if (u->meta.job)
+ job_free(u->meta.job);
+
+ if (u->meta.load_state != UNIT_STUB)
+ if (UNIT_VTABLE(u)->done)
+ UNIT_VTABLE(u)->done(u);
+
+ cgroup_bonding_free_list(u->meta.cgroup_bondings);
+
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
+ bidi_set_free(u, u->meta.dependencies[d]);
+
+ free(u->meta.description);
+ free(u->meta.fragment_path);
+
+ while ((t = set_steal_first(u->meta.names)))
+ free(t);
+ set_free(u->meta.names);
+
+ free(u->meta.instance);
+
+ free(u);
+}
+
+UnitActiveState unit_active_state(Unit *u) {
+ assert(u);
+
+ if (u->meta.load_state != UNIT_LOADED)
+ return UNIT_INACTIVE;
+
+ 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->meta.names, &other->meta.names);
+
+ while ((t = set_steal_first(other->meta.names)))
+ free(t);
+
+ set_free(other->meta.names);
+ other->meta.names = NULL;
+ other->meta.id = NULL;
+
+ SET_FOREACH(t, u->meta.names, i)
+ assert_se(hashmap_replace(u->meta.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);
+
+ SET_FOREACH(back, other->meta.dependencies[d], i) {
+ UnitDependency k;
+
+ for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++)
+ if ((r = set_remove_and_put(back->meta.dependencies[k], other, u)) < 0) {
+
+ if (r == -EEXIST)
+ set_remove(back->meta.dependencies[k], other);
+ else
+ assert(r == -ENOENT);
+ }
+ }
+
+ complete_move(&u->meta.dependencies[d], &other->meta.dependencies[d]);
+
+ set_free(other->meta.dependencies[d]);
+ other->meta.dependencies[d] = NULL;
+}
+
+int unit_merge(Unit *u, Unit *other) {
+ UnitDependency d;
+
+ assert(u);
+ assert(other);
+ assert(u->meta.manager == other->meta.manager);
+ assert(u->meta.type != _UNIT_TYPE_INVALID);
+
+ other = unit_follow_merge(other);
+
+ if (other == u)
+ return 0;
+
+ if (u->meta.type != other->meta.type)
+ return -EINVAL;
+
+ if (!streq_ptr(u->meta.instance, other->meta.instance))
+ return -EINVAL;
+
+ if (other->meta.load_state != UNIT_STUB &&
+ other->meta.load_state != UNIT_FAILED)
+ return -EEXIST;
+
+ if (other->meta.job)
+ return -EEXIST;
+
+ if (unit_active_state(other) != UNIT_INACTIVE)
+ return -EEXIST;
+
+ /* Merge names */
+ merge_names(u, other);
+
+ /* Merge dependencies */
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
+ merge_dependencies(u, other, d);
+
+ other->meta.load_state = UNIT_MERGED;
+ other->meta.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->meta.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->meta.instance)
+ return -EINVAL;
+
+ if (!(s = unit_name_replace_instance(name, u->meta.instance)))
+ return -ENOMEM;
+
+ name = s;
+ }
+
+ if (!(other = manager_get_unit(u->meta.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->meta.load_state == UNIT_MERGED)
+ assert_se(u = u->meta.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_KERNEL && c->std_output != EXEC_OUTPUT_SYSLOG)
+ return 0;
+
+ /* If syslog or kernel logging is requested, make sure our own
+ * logging daemon is run first. */
+
+ if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_LOGGER_SOCKET, NULL, true)) < 0)
+ return r;
+
+ if (u->meta.manager->running_as != MANAGER_SESSION)
+ if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_LOGGER_SOCKET, NULL, true)) < 0)
+ return r;
+
+ return 0;
+}
+
+const char *unit_description(Unit *u) {
+ assert(u);
+
+ if (u->meta.description)
+ return u->meta.description;
+
+ return u->meta.id;
+}
+
+void unit_dump(Unit *u, FILE *f, const char *prefix) {
+ char *t;
+ UnitDependency d;
+ Iterator i;
+ char *p2;
+ const char *prefix2;
+ CGroupBonding *b;
+ char
+ timestamp1[FORMAT_TIMESTAMP_MAX],
+ timestamp2[FORMAT_TIMESTAMP_MAX],
+ timestamp3[FORMAT_TIMESTAMP_MAX],
+ timestamp4[FORMAT_TIMESTAMP_MAX];
+
+ assert(u);
+ assert(u->meta.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",
+ prefix, u->meta.id,
+ prefix, unit_description(u),
+ prefix, strna(u->meta.instance),
+ prefix, unit_load_state_to_string(u->meta.load_state),
+ prefix, unit_active_state_to_string(unit_active_state(u)),
+ prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.inactive_exit_timestamp)),
+ prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_enter_timestamp)),
+ prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->meta.active_exit_timestamp)),
+ prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->meta.inactive_enter_timestamp)),
+ prefix, yes_no(unit_check_gc(u)));
+
+ SET_FOREACH(t, u->meta.names, i)
+ fprintf(f, "%s\tName: %s\n", prefix, t);
+
+ if (u->meta.fragment_path)
+ fprintf(f, "%s\tFragment Path: %s\n", prefix, u->meta.fragment_path);
+
+ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) {
+ Unit *other;
+
+ SET_FOREACH(other, u->meta.dependencies[d], i)
+ fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->meta.id);
+ }
+
+ if (u->meta.load_state == UNIT_LOADED) {
+ fprintf(f,
+ "%s\tRecursive Stop: %s\n"
+ "%s\tStop When Unneeded: %s\n",
+ prefix, yes_no(u->meta.recursive_stop),
+ prefix, yes_no(u->meta.stop_when_unneeded));
+
+ LIST_FOREACH(by_unit, b, u->meta.cgroup_bondings)
+ fprintf(f, "%s\tControlGroup: %s:%s\n",
+ prefix, b->controller, b->path);
+
+ if (UNIT_VTABLE(u)->dump)
+ UNIT_VTABLE(u)->dump(u, f, prefix2);
+
+ } else if (u->meta.load_state == UNIT_MERGED)
+ fprintf(f,
+ "%s\tMerged into: %s\n",
+ prefix, u->meta.merged_into->meta.id);
+
+ if (u->meta.job)
+ job_dump(u->meta.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->meta.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->meta.load_state == UNIT_STUB)
+ u->meta.load_state = UNIT_LOADED;
+
+ /* 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_nop(Unit *u) {
+ assert(u);
+
+ if (u->meta.load_state == UNIT_STUB)
+ u->meta.load_state = UNIT_LOADED;
+
+ return 0;
+}
+
+int unit_load(Unit *u) {
+ int r;
+
+ assert(u);
+
+ if (u->meta.in_load_queue) {
+ LIST_REMOVE(Meta, load_queue, u->meta.manager->load_queue, &u->meta);
+ u->meta.in_load_queue = false;
+ }
+
+ if (u->meta.type == _UNIT_TYPE_INVALID)
+ return -EINVAL;
+
+ if (u->meta.load_state != UNIT_STUB)
+ return 0;
+
+ if (UNIT_VTABLE(u)->load)
+ if ((r = UNIT_VTABLE(u)->load(u)) < 0)
+ goto fail;
+
+ if (u->meta.load_state == UNIT_STUB) {
+ r = -ENOENT;
+ goto fail;
+ }
+
+ assert((u->meta.load_state != UNIT_MERGED) == !u->meta.merged_into);
+
+ unit_add_to_dbus_queue(unit_follow_merge(u));
+ unit_add_to_gc_queue(u);
+
+ return 0;
+
+fail:
+ u->meta.load_state = UNIT_FAILED;
+ unit_add_to_dbus_queue(u);
+
+ log_debug("Failed to load configuration for %s: %s", u->meta.id, strerror(-r));
+
+ return r;
+}
+
+/* Errors:
+ * -EBADR: This unit type does not support starting.
+ * -EALREADY: Unit is already started.
+ * -EAGAIN: An operation is already in progress. Retry later.
+ */
+int unit_start(Unit *u) {
+ UnitActiveState state;
+
+ assert(u);
+
+ /* If this is already (being) 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 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;
+}
+
+/* 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;
+
+ assert(u);
+
+ state = unit_active_state(u);
+ if (state == UNIT_INACTIVE)
+ return -EALREADY;
+
+ 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;
+
+ assert(u);
+
+ if (!unit_can_reload(u))
+ return -EBADR;
+
+ state = unit_active_state(u);
+ if (unit_active_state(u) == UNIT_ACTIVE_RELOADING)
+ return -EALREADY;
+
+ if (unit_active_state(u) != UNIT_ACTIVE)
+ return -ENOEXEC;
+
+ 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_uneeded(Unit *u) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+
+ /* If this service shall be shut down when unneeded then do
+ * so. */
+
+ if (!u->meta.stop_when_unneeded)
+ return;
+
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
+ return;
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ return;
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ return;
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_WANTED_BY], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ return;
+
+ log_debug("Service %s is not needed anymore. Stopping.", u->meta.id);
+
+ /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
+ manager_add_job(u->meta.manager, JOB_STOP, u, JOB_FAIL, true, 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->meta.dependencies[UNIT_REQUIRES], i)
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUISITE], i)
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_WANTS], i)
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
+
+ SET_FOREACH(other, u->meta.dependencies[UNIT_CONFLICTS], i)
+ if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
+ manager_add_job(u->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
+}
+
+static void retroactively_stop_dependencies(Unit *u) {
+ Iterator i;
+ Unit *other;
+
+ assert(u);
+ assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)));
+
+ if (u->meta.recursive_stop) {
+ /* Pull down units need us recursively if enabled */
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRED_BY], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ manager_add_job(u->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
+ }
+
+ /* Garbage collect services that might not be needed anymore, if enabled */
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRES], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_uneeded(other);
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_uneeded(other);
+ SET_FOREACH(other, u->meta.dependencies[UNIT_WANTS], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_uneeded(other);
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUISITE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_uneeded(other);
+ SET_FOREACH(other, u->meta.dependencies[UNIT_REQUISITE_OVERRIDABLE], i)
+ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
+ unit_check_uneeded(other);
+}
+
+void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
+ bool unexpected = false;
+ usec_t ts;
+
+ 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
+ * behaviour here. For example: if a mount point is remounted
+ * this function will be called too and the utmp code below
+ * relies on that! */
+
+ ts = now(CLOCK_REALTIME);
+
+ if (os == UNIT_INACTIVE && ns != UNIT_INACTIVE)
+ u->meta.inactive_exit_timestamp = ts;
+ else if (os != UNIT_INACTIVE && ns == UNIT_INACTIVE)
+ u->meta.inactive_enter_timestamp = ts;
+
+ if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns))
+ u->meta.active_enter_timestamp = ts;
+ else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns))
+ u->meta.active_exit_timestamp = ts;
+
+ if (u->meta.job) {
+
+ if (u->meta.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->meta.job);
+
+ else {
+ assert(u->meta.job->state == JOB_RUNNING);
+
+ /* Let's check whether this state change
+ * constitutes a finished job, or maybe
+ * cotradicts a running job and hence needs to
+ * invalidate jobs. */
+
+ switch (u->meta.job->type) {
+
+ case JOB_START:
+ case JOB_VERIFY_ACTIVE:
+
+ if (UNIT_IS_ACTIVE_OR_RELOADING(ns))
+ job_finish_and_invalidate(u->meta.job, true);
+ else if (ns != UNIT_ACTIVATING) {
+ unexpected = true;
+ job_finish_and_invalidate(u->meta.job, false);
+ }
+
+ break;
+
+ case JOB_RELOAD:
+ case JOB_RELOAD_OR_START:
+
+ if (ns == UNIT_ACTIVE)
+ job_finish_and_invalidate(u->meta.job, true);
+ else if (ns != UNIT_ACTIVATING && ns != UNIT_ACTIVE_RELOADING) {
+ unexpected = true;
+ job_finish_and_invalidate(u->meta.job, false);
+ }
+
+ break;
+
+ case JOB_STOP:
+ case JOB_RESTART:
+ case JOB_TRY_RESTART:
+
+ if (ns == UNIT_INACTIVE)
+ job_finish_and_invalidate(u->meta.job, true);
+ else if (ns != UNIT_DEACTIVATING) {
+ unexpected = true;
+ job_finish_and_invalidate(u->meta.job, false);
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Job type unknown");
+ }
+ }
+ }
+
+ /* If this state change happened without being requested by a
+ * job, then let's retroactively start or stop dependencies */
+
+ if (unexpected) {
+ if (UNIT_IS_INACTIVE_OR_DEACTIVATING(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);
+ }
+
+ /* 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_system(u->meta.manager);
+ bus_init_api(u->meta.manager);
+ }
+
+ if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE))
+ /* The syslog daemon just might have become
+ * available, hence try to connect to it, if
+ * we aren't yet connected. */
+ log_open();
+
+ if (u->meta.type == UNIT_MOUNT)
+ /* Another directory became available, let's
+ * check if that is enough to write our utmp
+ * entry. */
+ manager_write_utmp_reboot(u->meta.manager);
+
+ if (u->meta.type == UNIT_TARGET)
+ /* A target got activated, maybe this is a runlevel? */
+ manager_write_utmp_runlevel(u->meta.manager, u);
+
+ } else if (!UNIT_IS_ACTIVE_OR_RELOADING(ns)) {
+
+ if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE))
+ /* The syslog daemon might just have
+ * terminated, hence try to disconnect from
+ * it. */
+ log_close_syslog();
+
+ /* We don't care about D-Bus here, since we'll get an
+ * asynchronous notification for it anyway. */
+ }
+
+ /* Maybe we finished startup and are now ready for being
+ * stopped because unneeded? */
+ unit_check_uneeded(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->meta.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->meta.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->meta.manager->watch_pids, UINT32_TO_PTR(pid), u);
+}
+
+void unit_unwatch_pid(Unit *u, pid_t pid) {
+ assert(u);
+ assert(pid >= 1);
+
+ hashmap_remove_value(u->meta.manager->watch_pids, UINT32_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_TIMER && w->data.unit == u));
+
+ /* This will try to reuse the old timer if there is one */
+
+ if (w->type == WATCH_TIMER) {
+ ours = false;
+ fd = w->fd;
+ } else {
+ ours = true;
+ if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
+ return -errno;
+ }
+
+ 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->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
+ goto fail;
+ }
+
+ w->fd = fd;
+ w->type = WATCH_TIMER;
+ 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_TIMER && w->data.unit == u);
+
+ assert_se(epoll_ctl(u->meta.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:
+ return true;
+
+ case JOB_STOP:
+ 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_REQUIRED_BY] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_REQUIRED_BY_OVERRIDABLE] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID,
+ [UNIT_CONFLICTS] = UNIT_CONFLICTS,
+ [UNIT_BEFORE] = UNIT_AFTER,
+ [UNIT_AFTER] = UNIT_BEFORE,
+ [UNIT_REFERENCES] = UNIT_REFERENCED_BY,
+ [UNIT_REFERENCED_BY] = UNIT_REFERENCES
+ };
+ int r, q = 0, v = 0, w = 0;
+
+ assert(u);
+ assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX);
+ assert(inverse_table[d] != _UNIT_DEPENDENCY_INVALID);
+ assert(other);
+
+ /* We won't allow dependencies on ourselves. We will not
+ * consider them an error however. */
+ if (u == other)
+ return 0;
+
+ if (UNIT_VTABLE(u)->no_requires &&
+ (d == UNIT_REQUIRES ||
+ d == UNIT_REQUIRES_OVERRIDABLE ||
+ d == UNIT_REQUISITE ||
+ d == UNIT_REQUISITE_OVERRIDABLE)) {
+ return -EINVAL;
+ }
+
+ if ((r = set_ensure_allocated(&u->meta.dependencies[d], trivial_hash_func, trivial_compare_func)) < 0 ||
+ (r = set_ensure_allocated(&other->meta.dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0)
+ return r;
+
+ if (add_reference)
+ if ((r = set_ensure_allocated(&u->meta.dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 ||
+ (r = set_ensure_allocated(&other->meta.dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0)
+ return r;
+
+ if ((q = set_put(u->meta.dependencies[d], other)) < 0)
+ return q;
+
+ if ((v = set_put(other->meta.dependencies[inverse_table[d]], u)) < 0) {
+ r = v;
+ goto fail;
+ }
+
+ if (add_reference) {
+ if ((w = set_put(u->meta.dependencies[UNIT_REFERENCES], other)) < 0) {
+ r = w;
+ goto fail;
+ }
+
+ if ((r = set_put(other->meta.dependencies[UNIT_REFERENCED_BY], u)) < 0)
+ goto fail;
+ }
+
+ unit_add_to_dbus_queue(u);
+ return 0;
+
+fail:
+ if (q > 0)
+ set_remove(u->meta.dependencies[d], other);
+
+ if (v > 0)
+ set_remove(other->meta.dependencies[inverse_table[d]], u);
+
+ if (w > 0)
+ set_remove(u->meta.dependencies[UNIT_REFERENCES], other);
+
+ return r;
+}
+
+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 = file_name_from_path(path);
+
+ if (!unit_name_is_template(name)) {
+ *p = NULL;
+ return name;
+ }
+
+ if (u->meta.instance)
+ s = unit_name_replace_instance(name, u->meta.instance);
+ else {
+ char *i;
+
+ if (!(i = unit_name_to_prefix(u->meta.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->meta.manager, name, path, &other)) < 0)
+ goto finish;
+
+ r = unit_add_dependency(u, d, 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->meta.manager, name, path, &other)) < 0)
+ goto finish;
+
+ r = unit_add_dependency(other, d, u, add_reference);
+
+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) {
+ char *p, *e;
+
+ assert(u);
+
+ if (!(e = bus_path_escape(u->meta.id)))
+ return NULL;
+
+ if (asprintf(&p, "/org/freedesktop/systemd1/unit/%s", e) < 0) {
+ free(e);
+ return NULL;
+ }
+
+ free(e);
+ return p;
+}
+
+int unit_add_cgroup(Unit *u, CGroupBonding *b) {
+ CGroupBonding *l;
+ int r;
+
+ assert(u);
+ assert(b);
+ assert(b->path);
+
+ /* Ensure this hasn't been added yet */
+ assert(!b->unit);
+
+ l = hashmap_get(u->meta.manager->cgroup_bondings, b->path);
+ LIST_PREPEND(CGroupBonding, by_path, l, b);
+
+ if ((r = hashmap_replace(u->meta.manager->cgroup_bondings, b->path, l)) < 0) {
+ LIST_REMOVE(CGroupBonding, by_path, l, b);
+ return r;
+ }
+
+ LIST_PREPEND(CGroupBonding, by_unit, u->meta.cgroup_bondings, b);
+ b->unit = u;
+
+ return 0;
+}
+
+static char *default_cgroup_path(Unit *u) {
+ char *p;
+ int r;
+
+ assert(u);
+
+ if (u->meta.instance) {
+ char *t;
+
+ if (!(t = unit_name_template(u->meta.id)))
+ return NULL;
+
+ r = asprintf(&p, "%s/%s/%s", u->meta.manager->cgroup_hierarchy, t, u->meta.instance);
+ free(t);
+ } else
+ r = asprintf(&p, "%s/%s", u->meta.manager->cgroup_hierarchy, u->meta.id);
+
+ return r < 0 ? NULL : p;
+}
+
+int unit_add_cgroup_from_text(Unit *u, const char *name) {
+ size_t n;
+ char *controller = NULL, *path = NULL;
+ CGroupBonding *b = NULL;
+ int r;
+
+ assert(u);
+ assert(name);
+
+ /* Detect controller name */
+ n = strcspn(name, ":");
+
+ if (name[n] == 0 ||
+ (name[n] == ':' && name[n+1] == 0)) {
+
+ /* Only controller name, no path? */
+
+ if (!(path = default_cgroup_path(u)))
+ return -ENOMEM;
+
+ } else {
+ const char *p;
+
+ /* Controller name, and path. */
+ p = name+n+1;
+
+ if (!path_is_absolute(p))
+ return -EINVAL;
+
+ if (!(path = strdup(p)))
+ return -ENOMEM;
+ }
+
+ if (n > 0)
+ controller = strndup(name, n);
+ else
+ controller = strdup(u->meta.manager->cgroup_controller);
+
+ if (!controller) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (cgroup_bonding_find_list(u->meta.cgroup_bondings, controller)) {
+ r = -EEXIST;
+ goto fail;
+ }
+
+ if (!(b = new0(CGroupBonding, 1))) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ b->controller = controller;
+ b->path = path;
+ b->only_us = false;
+ b->clean_up = false;
+
+ if ((r = unit_add_cgroup(u, b)) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ free(path);
+ free(controller);
+ free(b);
+
+ return r;
+}
+
+int unit_add_default_cgroup(Unit *u) {
+ CGroupBonding *b;
+ int r = -ENOMEM;
+
+ assert(u);
+
+ /* Adds in the default cgroup data, if it wasn't specified yet */
+
+ if (unit_get_default_cgroup(u))
+ return 0;
+
+ if (!(b = new0(CGroupBonding, 1)))
+ return -ENOMEM;
+
+ if (!(b->controller = strdup(u->meta.manager->cgroup_controller)))
+ goto fail;
+
+ if (!(b->path = default_cgroup_path(u)))
+ goto fail;
+
+ b->clean_up = true;
+ b->only_us = true;
+
+ if ((r = unit_add_cgroup(u, b)) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ free(b->path);
+ free(b->controller);
+ free(b);
+
+ return r;
+}
+
+CGroupBonding* unit_get_default_cgroup(Unit *u) {
+ assert(u);
+
+ return cgroup_bonding_find_list(u->meta.cgroup_bondings, u->meta.manager->cgroup_controller);
+}
+
+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->meta.id, type)))
+ return -ENOMEM;
+
+ assert(!unit_has_name(u, t));
+
+ r = manager_load_unit(u->meta.manager, t, 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->meta.id, type)))
+ return -ENOMEM;
+
+ assert(!unit_has_name(u, t));
+
+ found = manager_get_unit(u->meta.manager, t);
+ free(t);
+
+ if (!found)
+ return -ENOENT;
+
+ *_found = found;
+ return 0;
+}
+
+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->meta.id);
+}
+
+static char *specifier_prefix(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ assert(u);
+
+ return unit_name_to_prefix(u->meta.id);
+}
+
+static char *specifier_prefix_unescaped(char specifier, void *data, void *userdata) {
+ Unit *u = userdata;
+ char *p, *r;
+
+ assert(u);
+
+ if (!(p = unit_name_to_prefix(u->meta.id)))
+ 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->meta.instance)
+ return unit_name_unescape(u->meta.instance);
+
+ return strdup("");
+}
+
+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->meta.id },
+ { 'N', specifier_prefix_and_instance, NULL },
+ { 'p', specifier_prefix, NULL },
+ { 'i', specifier_string, u->meta.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 */
+
+ const Specifier table[] = {
+ { 'n', specifier_string, u->meta.id },
+ { 'N', specifier_prefix_and_instance, NULL },
+ { 'p', specifier_prefix, NULL },
+ { 'P', specifier_prefix_unescaped, NULL },
+ { 'i', specifier_string, u->meta.instance },
+ { 'I', specifier_instance_unescaped, 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);
+ if (!(r = new(char*, n+1)))
+ return NULL;
+
+ for (i = l, j = r; *i; i++, j++)
+ if (!(*j = unit_full_printf(u, *i)))
+ goto fail;
+
+ *j = NULL;
+ return r;
+
+fail:
+ j--;
+ while (j >= r)
+ free(*j);
+
+ free(r);
+
+ return NULL;
+}
+
+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->meta.manager->watch_bus, name, u);
+}
+
+void unit_unwatch_bus_name(Unit *u, const char *name) {
+ assert(u);
+ assert(name);
+
+ hashmap_remove_value(u->meta.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) {
+ 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;
+
+ /* 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[1024], *l, *v;
+ size_t k;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ return 0;
+ return -errno;
+ }
+
+ 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 ((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;
+
+ if (!(e = unit_name_build_escape(what+1, NULL, ".device")))
+ return -ENOMEM;
+
+ r = manager_load_unit(u->meta.manager, e, NULL, &device);
+ free(e);
+
+ if (r < 0)
+ return r;
+
+ if ((r = unit_add_dependency(u, UNIT_AFTER, device, true)) < 0)
+ return r;
+
+ if ((r = unit_add_dependency(u, UNIT_REQUIRES, device, true)) < 0)
+ return r;
+
+ if (wants)
+ if ((r = unit_add_dependency(device, UNIT_WANTS, u, false)) < 0)
+ return r;
+
+ return 0;
+}
+
+static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
+ [UNIT_SERVICE] = "service",
+ [UNIT_TIMER] = "timer",
+ [UNIT_SOCKET] = "socket",
+ [UNIT_TARGET] = "target",
+ [UNIT_DEVICE] = "device",
+ [UNIT_MOUNT] = "mount",
+ [UNIT_AUTOMOUNT] = "automount",
+ [UNIT_SNAPSHOT] = "snapshot",
+ [UNIT_SWAP] = "swap"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
+
+static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
+ [UNIT_STUB] = "stub",
+ [UNIT_LOADED] = "loaded",
+ [UNIT_FAILED] = "failed",
+ [UNIT_MERGED] = "merged"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
+
+static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
+ [UNIT_ACTIVE] = "active",
+ [UNIT_INACTIVE] = "inactive",
+ [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_WANTS] = "Wants",
+ [UNIT_REQUISITE] = "Requisite",
+ [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
+ [UNIT_REQUIRED_BY] = "RequiredBy",
+ [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
+ [UNIT_WANTED_BY] = "WantedBy",
+ [UNIT_CONFLICTS] = "Conflicts",
+ [UNIT_BEFORE] = "Before",
+ [UNIT_AFTER] = "After",
+ [UNIT_REFERENCES] = "References",
+ [UNIT_REFERENCED_BY] = "ReferencedBy"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);
+
+static const char* const kill_mode_table[_KILL_MODE_MAX] = {
+ [KILL_CONTROL_GROUP] = "control-group",
+ [KILL_PROCESS_GROUP] = "process-group",
+ [KILL_PROCESS] = "process",
+ [KILL_NONE] = "none"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode);
diff --git a/src/unit.h b/src/unit.h
new file mode 100644
index 0000000000..8f9d9e9860
--- /dev/null
+++ b/src/unit.h
@@ -0,0 +1,448 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foounithfoo
+#define foounithfoo
+
+/***
+ 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 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 <stdbool.h>
+#include <stdlib.h>
+
+typedef union Unit Unit;
+typedef struct Meta Meta;
+typedef struct UnitVTable UnitVTable;
+typedef enum UnitType UnitType;
+typedef enum UnitLoadState UnitLoadState;
+typedef enum UnitActiveState UnitActiveState;
+typedef enum UnitDependency UnitDependency;
+
+#include "set.h"
+#include "util.h"
+#include "list.h"
+#include "socket-util.h"
+#include "execute.h"
+
+#define UNIT_NAME_MAX 128
+#define DEFAULT_TIMEOUT_USEC (20*USEC_PER_SEC)
+#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
+
+typedef enum KillMode {
+ KILL_CONTROL_GROUP = 0,
+ KILL_PROCESS_GROUP,
+ KILL_PROCESS,
+ KILL_NONE,
+ _KILL_MODE_MAX,
+ _KILL_MODE_INVALID = -1
+} KillMode;
+
+enum UnitType {
+ UNIT_SERVICE = 0,
+ UNIT_SOCKET,
+ UNIT_TARGET,
+ UNIT_DEVICE,
+ UNIT_MOUNT,
+ UNIT_AUTOMOUNT,
+ UNIT_SNAPSHOT,
+ UNIT_TIMER,
+ UNIT_SWAP,
+ _UNIT_TYPE_MAX,
+ _UNIT_TYPE_INVALID = -1
+};
+
+enum UnitLoadState {
+ UNIT_STUB,
+ UNIT_LOADED,
+ UNIT_FAILED,
+ UNIT_MERGED,
+ _UNIT_LOAD_STATE_MAX,
+ _UNIT_LOAD_STATE_INVALID = -1
+};
+
+enum UnitActiveState {
+ UNIT_ACTIVE,
+ UNIT_ACTIVE_RELOADING,
+ UNIT_INACTIVE,
+ 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_ACTIVE_RELOADING;
+}
+
+static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) {
+ return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_ACTIVE_RELOADING;
+}
+
+static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) {
+ return t == UNIT_INACTIVE || t == UNIT_DEACTIVATING;
+}
+
+enum UnitDependency {
+ /* Positive dependencies */
+ UNIT_REQUIRES,
+ UNIT_REQUIRES_OVERRIDABLE,
+ UNIT_REQUISITE,
+ UNIT_REQUISITE_OVERRIDABLE,
+ UNIT_WANTS,
+
+ /* Inverse of the above */
+ UNIT_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */
+ UNIT_REQUIRED_BY_OVERRIDABLE, /* inverse of 'soft_requires' and 'soft_requisite' is 'soft_required_by' */
+ UNIT_WANTED_BY, /* inverse of 'wants' */
+
+ /* Negative dependencies */
+ UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicts' */
+
+ /* Order */
+ UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */
+ UNIT_AFTER,
+
+ /* 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"
+
+struct Meta {
+ 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 *description;
+ char *fragment_path; /* if loaded from a config file this is the primary path to it */
+
+ /* If there is something to do with this unit, then this is
+ * the job for it */
+ Job *job;
+
+ usec_t inactive_exit_timestamp;
+ usec_t active_enter_timestamp;
+ usec_t active_exit_timestamp;
+ usec_t inactive_enter_timestamp;
+
+ /* Counterparts in the cgroup filesystem */
+ CGroupBonding *cgroup_bondings;
+
+ /* Per type list */
+ LIST_FIELDS(Meta, units_per_type);
+
+ /* Load queue */
+ LIST_FIELDS(Meta, load_queue);
+
+ /* D-Bus queue */
+ LIST_FIELDS(Meta, dbus_queue);
+
+ /* Cleanup queue */
+ LIST_FIELDS(Meta, cleanup_queue);
+
+ /* GC queue */
+ LIST_FIELDS(Meta, gc_queue);
+
+ /* Used during GC sweeps */
+ unsigned gc_marker;
+
+ /* If we go down, pull down everything that depends on us, too */
+ bool recursive_stop;
+
+ /* Garbage collect us we nobody wants or requires us anymore */
+ bool stop_when_unneeded;
+
+ 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;
+};
+
+#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"
+
+union Unit {
+ Meta meta;
+ Service service;
+ Timer timer;
+ Socket socket;
+ Target target;
+ Device device;
+ Mount mount;
+ Automount automount;
+ Snapshot snapshot;
+ Swap swap;
+};
+
+struct UnitVTable {
+ const char *suffix;
+
+ /* 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 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);
+
+ 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 finegrained 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);
+
+ /* Called whenever any of the cgroups this unit watches for
+ * ran empty */
+ void (*cgroup_notify_empty)(Unit *u);
+
+ /* 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, DBusMessage *message);
+
+ /* 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);
+
+ /* Can units of this type have multiple names? */
+ bool no_alias:1;
+
+ /* If true units of this types can never have "Requires"
+ * dependencies, because state changes can only be observed,
+ * not triggered */
+ bool no_requires:1;
+
+ /* Instances make no sense for this type */
+ bool no_instances:1;
+
+ /* Exclude this type from snapshots */
+ bool no_snapshots:1;
+
+ /* Exclude from automatic gc */
+ bool no_gc:1;
+
+ /* Exclude from isolation requests */
+ bool no_isolate:1;
+};
+
+extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX];
+
+#define UNIT_VTABLE(u) unit_vtable[(u)->meta.type]
+
+/* For casting a unit into the various unit types */
+#define DEFINE_CAST(UPPERCASE, MixedCase) \
+ static inline MixedCase* UPPERCASE(Unit *u) { \
+ if (!u || u->meta.type != UNIT_##UPPERCASE) \
+ return NULL; \
+ \
+ return (MixedCase*) u; \
+ }
+
+/* For casting the various unit types into a unit */
+#define UNIT(u) ((Unit*) (u))
+
+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);
+
+Unit *unit_new(Manager *m);
+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_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, 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_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_cgroup(Unit *u);
+CGroupBonding* unit_get_default_cgroup(Unit *u);
+
+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_nop(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);
+
+int unit_start(Unit *u);
+int unit_stop(Unit *u);
+int unit_reload(Unit *u);
+
+void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns);
+
+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);
+
+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);
+
+bool unit_can_serialize(Unit *u);
+int unit_serialize(Unit *u, FILE *f, FDSet *fds);
+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);
+
+const char *unit_type_to_string(UnitType i);
+UnitType unit_type_from_string(const char *s);
+
+const char *unit_load_state_to_string(UnitLoadState i);
+UnitLoadState unit_load_state_from_string(const char *s);
+
+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);
+
+const char *kill_mode_to_string(KillMode k);
+KillMode kill_mode_from_string(const char *s);
+
+#endif
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000000..f7d538aaac
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,2027 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <linux/sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#include <linux/tiocl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <libgen.h>
+#include <ctype.h>
+
+#include "macro.h"
+#include "util.h"
+#include "ioprio.h"
+#include "missing.h"
+#include "log.h"
+#include "strv.h"
+
+bool streq_ptr(const char *a, const char *b) {
+
+ /* Like streq(), but tries to make sense of NULL pointers */
+
+ if (a && b)
+ return streq(a, b);
+
+ if (!a && !b)
+ return true;
+
+ return false;
+}
+
+usec_t now(clockid_t clock_id) {
+ struct timespec ts;
+
+ assert_se(clock_gettime(clock_id, &ts) == 0);
+
+ return timespec_load(&ts);
+}
+
+usec_t timespec_load(const struct timespec *ts) {
+ assert(ts);
+
+ return
+ (usec_t) ts->tv_sec * USEC_PER_SEC +
+ (usec_t) ts->tv_nsec / NSEC_PER_USEC;
+}
+
+struct timespec *timespec_store(struct timespec *ts, usec_t u) {
+ assert(ts);
+
+ ts->tv_sec = (time_t) (u / USEC_PER_SEC);
+ ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
+
+ return ts;
+}
+
+usec_t timeval_load(const struct timeval *tv) {
+ assert(tv);
+
+ return
+ (usec_t) tv->tv_sec * USEC_PER_SEC +
+ (usec_t) tv->tv_usec;
+}
+
+struct timeval *timeval_store(struct timeval *tv, usec_t u) {
+ assert(tv);
+
+ tv->tv_sec = (time_t) (u / USEC_PER_SEC);
+ tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
+
+ return tv;
+}
+
+bool endswith(const char *s, const char *postfix) {
+ size_t sl, pl;
+
+ assert(s);
+ assert(postfix);
+
+ sl = strlen(s);
+ pl = strlen(postfix);
+
+ if (pl == 0)
+ return true;
+
+ if (sl < pl)
+ return false;
+
+ return memcmp(s + sl - pl, postfix, pl) == 0;
+}
+
+bool startswith(const char *s, const char *prefix) {
+ size_t sl, pl;
+
+ assert(s);
+ assert(prefix);
+
+ sl = strlen(s);
+ pl = strlen(prefix);
+
+ if (pl == 0)
+ return true;
+
+ if (sl < pl)
+ return false;
+
+ return memcmp(s, prefix, pl) == 0;
+}
+
+bool startswith_no_case(const char *s, const char *prefix) {
+ size_t sl, pl;
+ unsigned i;
+
+ assert(s);
+ assert(prefix);
+
+ sl = strlen(s);
+ pl = strlen(prefix);
+
+ if (pl == 0)
+ return true;
+
+ if (sl < pl)
+ return false;
+
+ for(i = 0; i < pl; ++i) {
+ if (tolower(s[i]) != tolower(prefix[i]))
+ return false;
+ }
+
+ return true;
+}
+
+bool first_word(const char *s, const char *word) {
+ size_t sl, wl;
+
+ assert(s);
+ assert(word);
+
+ sl = strlen(s);
+ wl = strlen(word);
+
+ if (sl < wl)
+ return false;
+
+ if (wl == 0)
+ return true;
+
+ if (memcmp(s, word, wl) != 0)
+ return false;
+
+ return s[wl] == 0 ||
+ strchr(WHITESPACE, s[wl]);
+}
+
+int close_nointr(int fd) {
+ assert(fd >= 0);
+
+ for (;;) {
+ int r;
+
+ if ((r = close(fd)) >= 0)
+ return r;
+
+ if (errno != EINTR)
+ return r;
+ }
+}
+
+void close_nointr_nofail(int fd) {
+ int saved_errno = errno;
+
+ /* like close_nointr() but cannot fail, and guarantees errno
+ * is unchanged */
+
+ assert_se(close_nointr(fd) == 0);
+
+ errno = saved_errno;
+}
+
+int parse_boolean(const char *v) {
+ assert(v);
+
+ if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on"))
+ return 1;
+ else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off"))
+ return 0;
+
+ return -EINVAL;
+}
+
+int safe_atou(const char *s, unsigned *ret_u) {
+ char *x = NULL;
+ unsigned long l;
+
+ assert(s);
+ assert(ret_u);
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ if ((unsigned long) (unsigned) l != l)
+ return -ERANGE;
+
+ *ret_u = (unsigned) l;
+ return 0;
+}
+
+int safe_atoi(const char *s, int *ret_i) {
+ char *x = NULL;
+ long l;
+
+ assert(s);
+ assert(ret_i);
+
+ errno = 0;
+ l = strtol(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ if ((long) (int) l != l)
+ return -ERANGE;
+
+ *ret_i = (int) l;
+ return 0;
+}
+
+int safe_atolu(const char *s, long unsigned *ret_lu) {
+ char *x = NULL;
+ unsigned long l;
+
+ assert(s);
+ assert(ret_lu);
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_lu = l;
+ return 0;
+}
+
+int safe_atoli(const char *s, long int *ret_li) {
+ char *x = NULL;
+ long l;
+
+ assert(s);
+ assert(ret_li);
+
+ errno = 0;
+ l = strtol(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_li = l;
+ return 0;
+}
+
+int safe_atollu(const char *s, long long unsigned *ret_llu) {
+ char *x = NULL;
+ unsigned long long l;
+
+ assert(s);
+ assert(ret_llu);
+
+ errno = 0;
+ l = strtoull(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_llu = l;
+ return 0;
+}
+
+int safe_atolli(const char *s, long long int *ret_lli) {
+ char *x = NULL;
+ long long l;
+
+ assert(s);
+ assert(ret_lli);
+
+ errno = 0;
+ l = strtoll(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ *ret_lli = l;
+ return 0;
+}
+
+/* Split a string into words. */
+char *split(const char *c, size_t *l, const char *separator, char **state) {
+ char *current;
+
+ current = *state ? *state : (char*) c;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, separator);
+ *l = strcspn(current, separator);
+ *state = current+*l;
+
+ return (char*) current;
+}
+
+/* Split a string into words, but consider strings enclosed in '' and
+ * "" as words even if they include spaces. */
+char *split_quoted(const char *c, size_t *l, char **state) {
+ char *current;
+
+ current = *state ? *state : (char*) c;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, WHITESPACE);
+
+ if (*current == '\'') {
+ current ++;
+ *l = strcspn(current, "'");
+ *state = current+*l;
+
+ if (**state == '\'')
+ (*state)++;
+ } else if (*current == '\"') {
+ current ++;
+ *l = strcspn(current, "\"");
+ *state = current+*l;
+
+ if (**state == '\"')
+ (*state)++;
+ } else {
+ *l = strcspn(current, WHITESPACE);
+ *state = current+*l;
+ }
+
+ /* FIXME: Cannot deal with strings that have spaces AND ticks
+ * in them */
+
+ return (char*) current;
+}
+
+char **split_path_and_make_absolute(const char *p) {
+ char **l;
+ assert(p);
+
+ if (!(l = strv_split(p, ":")))
+ return NULL;
+
+ if (!strv_path_make_absolute_cwd(l)) {
+ strv_free(l);
+ return NULL;
+ }
+
+ return l;
+}
+
+int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
+ int r;
+ FILE *f;
+ char fn[132], line[256], *p;
+ long long unsigned ppid;
+
+ assert(pid >= 0);
+ assert(_ppid);
+
+ assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%llu/stat", (unsigned long long) pid) < (int) (sizeof(fn)-1));
+ fn[sizeof(fn)-1] = 0;
+
+ if (!(f = fopen(fn, "r")))
+ return -errno;
+
+ if (!(fgets(line, sizeof(line), f))) {
+ r = -errno;
+ fclose(f);
+ return r;
+ }
+
+ fclose(f);
+
+ /* Let's skip the pid and comm fields. The latter is enclosed
+ * in () but does not escape any () in its value, so let's
+ * skip over it manually */
+
+ if (!(p = strrchr(line, ')')))
+ return -EIO;
+
+ p++;
+
+ if (sscanf(p, " "
+ "%*c " /* state */
+ "%llu ", /* ppid */
+ &ppid) != 1)
+ return -EIO;
+
+ if ((long long unsigned) (pid_t) ppid != ppid)
+ return -ERANGE;
+
+ *_ppid = (pid_t) ppid;
+
+ return 0;
+}
+
+int write_one_line_file(const char *fn, const char *line) {
+ FILE *f;
+ int r;
+
+ assert(fn);
+ assert(line);
+
+ if (!(f = fopen(fn, "we")))
+ return -errno;
+
+ if (fputs(line, f) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = 0;
+finish:
+ fclose(f);
+ return r;
+}
+
+int read_one_line_file(const char *fn, char **line) {
+ FILE *f;
+ int r;
+ char t[2048], *c;
+
+ assert(fn);
+ assert(line);
+
+ if (!(f = fopen(fn, "re")))
+ return -errno;
+
+ if (!(fgets(t, sizeof(t), f))) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!(c = strdup(t))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ *line = c;
+ r = 0;
+
+finish:
+ fclose(f);
+ return r;
+}
+
+char *truncate_nl(char *s) {
+ assert(s);
+
+ s[strcspn(s, NEWLINE)] = 0;
+ return s;
+}
+
+int get_process_name(pid_t pid, char **name) {
+ char *p;
+ int r;
+
+ assert(pid >= 1);
+ assert(name);
+
+ if (asprintf(&p, "/proc/%llu/comm", (unsigned long long) pid) < 0)
+ return -ENOMEM;
+
+ r = read_one_line_file(p, name);
+ free(p);
+
+ if (r < 0)
+ return r;
+
+ truncate_nl(*name);
+ return 0;
+}
+
+char *strappend(const char *s, const char *suffix) {
+ size_t a, b;
+ char *r;
+
+ assert(s);
+ assert(suffix);
+
+ a = strlen(s);
+ b = strlen(suffix);
+
+ if (!(r = new(char, a+b+1)))
+ return NULL;
+
+ memcpy(r, s, a);
+ memcpy(r+a, suffix, b);
+ r[a+b] = 0;
+
+ return r;
+}
+
+int readlink_malloc(const char *p, char **r) {
+ size_t l = 100;
+
+ assert(p);
+ assert(r);
+
+ for (;;) {
+ char *c;
+ ssize_t n;
+
+ if (!(c = new(char, l)))
+ return -ENOMEM;
+
+ if ((n = readlink(p, c, l-1)) < 0) {
+ int ret = -errno;
+ free(c);
+ return ret;
+ }
+
+ if ((size_t) n < l-1) {
+ c[n] = 0;
+ *r = c;
+ return 0;
+ }
+
+ free(c);
+ l *= 2;
+ }
+}
+
+char *file_name_from_path(const char *p) {
+ char *r;
+
+ assert(p);
+
+ if ((r = strrchr(p, '/')))
+ return r + 1;
+
+ return (char*) p;
+}
+
+bool path_is_absolute(const char *p) {
+ assert(p);
+
+ return p[0] == '/';
+}
+
+bool is_path(const char *p) {
+
+ return !!strchr(p, '/');
+}
+
+char *path_make_absolute(const char *p, const char *prefix) {
+ char *r;
+
+ assert(p);
+
+ /* Makes every item in the list an absolute path by prepending
+ * the prefix, if specified and necessary */
+
+ if (path_is_absolute(p) || !prefix)
+ return strdup(p);
+
+ if (asprintf(&r, "%s/%s", prefix, p) < 0)
+ return NULL;
+
+ return r;
+}
+
+char *path_make_absolute_cwd(const char *p) {
+ char *cwd, *r;
+
+ assert(p);
+
+ /* Similar to path_make_absolute(), but prefixes with the
+ * current working directory. */
+
+ if (path_is_absolute(p))
+ return strdup(p);
+
+ if (!(cwd = get_current_dir_name()))
+ return NULL;
+
+ r = path_make_absolute(p, cwd);
+ free(cwd);
+
+ return r;
+}
+
+char **strv_path_make_absolute_cwd(char **l) {
+ char **s;
+
+ /* Goes through every item in the string list and makes it
+ * absolute. This works in place and won't rollback any
+ * changes on failure. */
+
+ STRV_FOREACH(s, l) {
+ char *t;
+
+ if (!(t = path_make_absolute_cwd(*s)))
+ return NULL;
+
+ free(*s);
+ *s = t;
+ }
+
+ return l;
+}
+
+int reset_all_signal_handlers(void) {
+ int sig;
+
+ for (sig = 1; sig < _NSIG; sig++) {
+ struct sigaction sa;
+
+ if (sig == SIGKILL || sig == SIGSTOP)
+ continue;
+
+ zero(sa);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = SA_RESTART;
+
+ /* On Linux the first two RT signals are reserved by
+ * glibc, and sigaction() will return EINVAL for them. */
+ if ((sigaction(sig, &sa, NULL) < 0))
+ if (errno != EINVAL)
+ return -errno;
+ }
+
+ return 0;
+}
+
+char *strstrip(char *s) {
+ char *e, *l = NULL;
+
+ /* Drops trailing whitespace. Modifies the string in
+ * place. Returns pointer to first non-space character */
+
+ s += strspn(s, WHITESPACE);
+
+ for (e = s; *e; e++)
+ if (!strchr(WHITESPACE, *e))
+ l = e;
+
+ if (l)
+ *(l+1) = 0;
+ else
+ *s = 0;
+
+ return s;
+}
+
+char *delete_chars(char *s, const char *bad) {
+ char *f, *t;
+
+ /* Drops all whitespace, regardless where in the string */
+
+ for (f = s, t = s; *f; f++) {
+ if (strchr(bad, *f))
+ continue;
+
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return s;
+}
+
+char *file_in_same_dir(const char *path, const char *filename) {
+ char *e, *r;
+ size_t k;
+
+ assert(path);
+ assert(filename);
+
+ /* This removes the last component of path and appends
+ * filename, unless the latter is absolute anyway or the
+ * former isn't */
+
+ if (path_is_absolute(filename))
+ return strdup(filename);
+
+ if (!(e = strrchr(path, '/')))
+ return strdup(filename);
+
+ k = strlen(filename);
+ if (!(r = new(char, e-path+1+k+1)))
+ return NULL;
+
+ memcpy(r, path, e-path+1);
+ memcpy(r+(e-path)+1, filename, k+1);
+
+ return r;
+}
+
+int mkdir_parents(const char *path, mode_t mode) {
+ const char *p, *e;
+
+ assert(path);
+
+ /* Creates every parent directory in the path except the last
+ * component. */
+
+ p = path + strspn(path, "/");
+ for (;;) {
+ int r;
+ char *t;
+
+ e = p + strcspn(p, "/");
+ p = e + strspn(e, "/");
+
+ /* Is this the last component? If so, then we're
+ * done */
+ if (*p == 0)
+ return 0;
+
+ if (!(t = strndup(path, e - path)))
+ return -ENOMEM;
+
+ r = mkdir(t, mode);
+
+ free(t);
+
+ if (r < 0 && errno != EEXIST)
+ return -errno;
+ }
+}
+
+int mkdir_p(const char *path, mode_t mode) {
+ int r;
+
+ /* Like mkdir -p */
+
+ if ((r = mkdir_parents(path, mode)) < 0)
+ return r;
+
+ if (mkdir(path, mode) < 0)
+ return -errno;
+
+ return 0;
+}
+
+char hexchar(int x) {
+ static const char table[16] = "0123456789abcdef";
+
+ return table[x & 15];
+}
+
+int unhexchar(char c) {
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ return -1;
+}
+
+char octchar(int x) {
+ return '0' + (x & 7);
+}
+
+int unoctchar(char c) {
+
+ if (c >= '0' && c <= '7')
+ return c - '0';
+
+ return -1;
+}
+
+char decchar(int x) {
+ return '0' + (x % 10);
+}
+
+int undecchar(char c) {
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ return -1;
+}
+
+char *cescape(const char *s) {
+ char *r, *t;
+ const char *f;
+
+ assert(s);
+
+ /* Does C style string escaping. */
+
+ if (!(r = new(char, strlen(s)*4 + 1)))
+ return NULL;
+
+ for (f = s, t = r; *f; f++)
+
+ switch (*f) {
+
+ case '\a':
+ *(t++) = '\\';
+ *(t++) = 'a';
+ break;
+ case '\b':
+ *(t++) = '\\';
+ *(t++) = 'b';
+ break;
+ case '\f':
+ *(t++) = '\\';
+ *(t++) = 'f';
+ break;
+ case '\n':
+ *(t++) = '\\';
+ *(t++) = 'n';
+ break;
+ case '\r':
+ *(t++) = '\\';
+ *(t++) = 'r';
+ break;
+ case '\t':
+ *(t++) = '\\';
+ *(t++) = 't';
+ break;
+ case '\v':
+ *(t++) = '\\';
+ *(t++) = 'v';
+ break;
+ case '\\':
+ *(t++) = '\\';
+ *(t++) = '\\';
+ break;
+ case '"':
+ *(t++) = '\\';
+ *(t++) = '"';
+ break;
+ case '\'':
+ *(t++) = '\\';
+ *(t++) = '\'';
+ break;
+
+ default:
+ /* For special chars we prefer octal over
+ * hexadecimal encoding, simply because glib's
+ * g_strescape() does the same */
+ if ((*f < ' ') || (*f >= 127)) {
+ *(t++) = '\\';
+ *(t++) = octchar((unsigned char) *f >> 6);
+ *(t++) = octchar((unsigned char) *f >> 3);
+ *(t++) = octchar((unsigned char) *f);
+ } else
+ *(t++) = *f;
+ break;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *cunescape(const char *s) {
+ char *r, *t;
+ const char *f;
+
+ assert(s);
+
+ /* Undoes C style string escaping */
+
+ if (!(r = new(char, strlen(s)+1)))
+ return r;
+
+ for (f = s, t = r; *f; f++) {
+
+ if (*f != '\\') {
+ *(t++) = *f;
+ continue;
+ }
+
+ f++;
+
+ switch (*f) {
+
+ case 'a':
+ *(t++) = '\a';
+ break;
+ case 'b':
+ *(t++) = '\b';
+ break;
+ case 'f':
+ *(t++) = '\f';
+ break;
+ case 'n':
+ *(t++) = '\n';
+ break;
+ case 'r':
+ *(t++) = '\r';
+ break;
+ case 't':
+ *(t++) = '\t';
+ break;
+ case 'v':
+ *(t++) = '\v';
+ break;
+ case '\\':
+ *(t++) = '\\';
+ break;
+ case '"':
+ *(t++) = '"';
+ break;
+ case '\'':
+ *(t++) = '\'';
+ break;
+
+ case 'x': {
+ /* hexadecimal encoding */
+ int a, b;
+
+ if ((a = unhexchar(f[1])) < 0 ||
+ (b = unhexchar(f[2])) < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = 'x';
+ } else {
+ *(t++) = (char) ((a << 4) | b);
+ f += 2;
+ }
+
+ break;
+ }
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ /* octal encoding */
+ int a, b, c;
+
+ if ((a = unoctchar(f[0])) < 0 ||
+ (b = unoctchar(f[1])) < 0 ||
+ (c = unoctchar(f[2])) < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = f[0];
+ } else {
+ *(t++) = (char) ((a << 6) | (b << 3) | c);
+ f += 2;
+ }
+
+ break;
+ }
+
+ case 0:
+ /* premature end of string.*/
+ *(t++) = '\\';
+ goto finish;
+
+ default:
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = 'f';
+ break;
+ }
+ }
+
+finish:
+ *t = 0;
+ return r;
+}
+
+
+char *xescape(const char *s, const char *bad) {
+ char *r, *t;
+ const char *f;
+
+ /* Escapes all chars in bad, in addition to \ and all special
+ * chars, in \xFF style escaping. May be reversed with
+ * cunescape. */
+
+ if (!(r = new(char, strlen(s)*4+1)))
+ return NULL;
+
+ for (f = s, t = r; *f; f++) {
+
+ if ((*f < ' ') || (*f >= 127) ||
+ (*f == '\\') || strchr(bad, *f)) {
+ *(t++) = '\\';
+ *(t++) = 'x';
+ *(t++) = hexchar(*f >> 4);
+ *(t++) = hexchar(*f);
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *bus_path_escape(const char *s) {
+ char *r, *t;
+ const char *f;
+
+ assert(s);
+
+ /* Escapes all chars that D-Bus' object path cannot deal
+ * with. Can be reverse with bus_path_unescape() */
+
+ if (!(r = new(char, strlen(s)*3+1)))
+ return NULL;
+
+ for (f = s, t = r; *f; f++) {
+
+ if (!(*f >= 'A' && *f <= 'Z') &&
+ !(*f >= 'a' && *f <= 'z') &&
+ !(*f >= '0' && *f <= '9')) {
+ *(t++) = '_';
+ *(t++) = hexchar(*f >> 4);
+ *(t++) = hexchar(*f);
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *bus_path_unescape(const char *f) {
+ char *r, *t;
+
+ assert(f);
+
+ if (!(r = strdup(f)))
+ return NULL;
+
+ for (t = r; *f; f++) {
+
+ if (*f == '_') {
+ int a, b;
+
+ if ((a = unhexchar(f[1])) < 0 ||
+ (b = unhexchar(f[2])) < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '_';
+ } else {
+ *(t++) = (char) ((a << 4) | b);
+ f += 2;
+ }
+ } else
+ *(t++) = *f;
+ }
+
+ *t = 0;
+
+ return r;
+}
+
+char *path_kill_slashes(char *path) {
+ char *f, *t;
+ bool slash = false;
+
+ /* Removes redundant inner and trailing slashes. Modifies the
+ * passed string in-place.
+ *
+ * ///foo///bar/ becomes /foo/bar
+ */
+
+ for (f = path, t = path; *f; f++) {
+
+ if (*f == '/') {
+ slash = true;
+ continue;
+ }
+
+ if (slash) {
+ slash = false;
+ *(t++) = '/';
+ }
+
+ *(t++) = *f;
+ }
+
+ /* Special rule, if we are talking of the root directory, a
+ trailing slash is good */
+
+ if (t == path && slash)
+ *(t++) = '/';
+
+ *t = 0;
+ return path;
+}
+
+bool path_startswith(const char *path, const char *prefix) {
+ assert(path);
+ assert(prefix);
+
+ if ((path[0] == '/') != (prefix[0] == '/'))
+ return false;
+
+ for (;;) {
+ size_t a, b;
+
+ path += strspn(path, "/");
+ prefix += strspn(prefix, "/");
+
+ if (*prefix == 0)
+ return true;
+
+ if (*path == 0)
+ return false;
+
+ a = strcspn(path, "/");
+ b = strcspn(prefix, "/");
+
+ if (a != b)
+ return false;
+
+ if (memcmp(path, prefix, a) != 0)
+ return false;
+
+ path += a;
+ prefix += b;
+ }
+}
+
+bool path_equal(const char *a, const char *b) {
+ assert(a);
+ assert(b);
+
+ if ((a[0] == '/') != (b[0] == '/'))
+ return false;
+
+ for (;;) {
+ size_t j, k;
+
+ a += strspn(a, "/");
+ b += strspn(b, "/");
+
+ if (*a == 0 && *b == 0)
+ return true;
+
+ if (*a == 0 || *b == 0)
+ return false;
+
+ j = strcspn(a, "/");
+ k = strcspn(b, "/");
+
+ if (j != k)
+ return false;
+
+ if (memcmp(a, b, j) != 0)
+ return false;
+
+ a += j;
+ b += k;
+ }
+}
+
+char *ascii_strlower(char *t) {
+ char *p;
+
+ assert(t);
+
+ for (p = t; *p; p++)
+ if (*p >= 'A' && *p <= 'Z')
+ *p = *p - 'A' + 'a';
+
+ return t;
+}
+
+bool ignore_file(const char *filename) {
+ assert(filename);
+
+ return
+ filename[0] == '.' ||
+ streq(filename, "lost+found") ||
+ endswith(filename, "~") ||
+ endswith(filename, ".rpmnew") ||
+ endswith(filename, ".rpmsave") ||
+ endswith(filename, ".rpmorig") ||
+ endswith(filename, ".dpkg-old") ||
+ endswith(filename, ".dpkg-new") ||
+ endswith(filename, ".swp");
+}
+
+int fd_nonblock(int fd, bool nonblock) {
+ int flags;
+
+ assert(fd >= 0);
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ return -errno;
+
+ if (nonblock)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int fd_cloexec(int fd, bool cloexec) {
+ int flags;
+
+ assert(fd >= 0);
+
+ if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+ return -errno;
+
+ if (cloexec)
+ flags |= FD_CLOEXEC;
+ else
+ flags &= ~FD_CLOEXEC;
+
+ if (fcntl(fd, F_SETFD, flags) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int close_all_fds(const int except[], unsigned n_except) {
+ DIR *d;
+ struct dirent *de;
+ int r = 0;
+
+ if (!(d = opendir("/proc/self/fd")))
+ return -errno;
+
+ 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 (except) {
+ bool found;
+ unsigned i;
+
+ found = false;
+ for (i = 0; i < n_except; i++)
+ if (except[i] == fd) {
+ found = true;
+ break;
+ }
+
+ if (found)
+ continue;
+ }
+
+ if ((r = close_nointr(fd)) < 0) {
+ /* Valgrind has its own FD and doesn't want to have it closed */
+ if (errno != EBADF)
+ goto finish;
+ }
+ }
+
+ r = 0;
+
+finish:
+ closedir(d);
+ return r;
+}
+
+bool chars_intersect(const char *a, const char *b) {
+ const char *p;
+
+ /* Returns true if any of the chars in a are in b. */
+ for (p = a; *p; p++)
+ if (strchr(b, *p))
+ return true;
+
+ return false;
+}
+
+char *format_timestamp(char *buf, size_t l, usec_t t) {
+ struct tm tm;
+ time_t sec;
+
+ assert(buf);
+ assert(l > 0);
+
+ if (t <= 0)
+ return NULL;
+
+ sec = (time_t) t / USEC_PER_SEC;
+
+ if (strftime(buf, l, "%a, %d %b %Y %H:%M:%S %z", localtime_r(&sec, &tm)) <= 0)
+ return NULL;
+
+ return buf;
+}
+
+bool fstype_is_network(const char *fstype) {
+ static const char * const table[] = {
+ "cifs",
+ "smbfs",
+ "ncpfs",
+ "nfs",
+ "nfs4",
+ "gfs",
+ "gfs2"
+ };
+
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (streq(table[i], fstype))
+ return true;
+
+ return false;
+}
+
+int chvt(int vt) {
+ int fd, r = 0;
+
+ if ((fd = open("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+ return -errno;
+
+ if (vt < 0) {
+ int tiocl[2] = {
+ TIOCL_GETKMSGREDIRECT,
+ 0
+ };
+
+ if (ioctl(fd, TIOCLINUX, tiocl) < 0)
+ return -errno;
+
+ vt = tiocl[0] <= 0 ? 1 : tiocl[0];
+ }
+
+ if (ioctl(fd, VT_ACTIVATE, vt) < 0)
+ r = -errno;
+
+ close_nointr_nofail(r);
+ return r;
+}
+
+int read_one_char(FILE *f, char *ret, bool *need_nl) {
+ struct termios old_termios, new_termios;
+ char c;
+ char line[1024];
+
+ assert(f);
+ assert(ret);
+
+ if (tcgetattr(fileno(f), &old_termios) >= 0) {
+ new_termios = old_termios;
+
+ new_termios.c_lflag &= ~ICANON;
+ new_termios.c_cc[VMIN] = 1;
+ new_termios.c_cc[VTIME] = 0;
+
+ if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
+ size_t k;
+
+ k = fread(&c, 1, 1, f);
+
+ tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+
+ if (k <= 0)
+ return -EIO;
+
+ if (need_nl)
+ *need_nl = c != '\n';
+
+ *ret = c;
+ return 0;
+ }
+ }
+
+ if (!(fgets(line, sizeof(line), f)))
+ return -EIO;
+
+ truncate_nl(line);
+
+ if (strlen(line) != 1)
+ return -EBADMSG;
+
+ if (need_nl)
+ *need_nl = false;
+
+ *ret = line[0];
+ return 0;
+}
+
+int ask(char *ret, const char *replies, const char *text, ...) {
+ assert(ret);
+ assert(replies);
+ assert(text);
+
+ for (;;) {
+ va_list ap;
+ char c;
+ int r;
+ bool need_nl = true;
+
+ fputs("\x1B[1m", stdout);
+
+ va_start(ap, text);
+ vprintf(text, ap);
+ va_end(ap);
+
+ fputs("\x1B[0m", stdout);
+
+ fflush(stdout);
+
+ if ((r = read_one_char(stdin, &c, &need_nl)) < 0) {
+
+ if (r == -EBADMSG) {
+ puts("Bad input, please try again.");
+ continue;
+ }
+
+ putchar('\n');
+ return r;
+ }
+
+ if (need_nl)
+ putchar('\n');
+
+ if (strchr(replies, c)) {
+ *ret = c;
+ return 0;
+ }
+
+ puts("Read unexpected character, please try again.");
+ }
+}
+
+int reset_terminal(int fd) {
+ struct termios termios;
+ int r = 0;
+
+ assert(fd >= 0);
+
+ /* Set terminal to some sane defaults */
+
+ if (tcgetattr(fd, &termios) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ /* We only reset the stuff that matters to the software. How
+ * hardware is set up we don't touch assuming that somebody
+ * else will do that for us */
+
+ termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
+ termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
+ termios.c_oflag |= ONLCR;
+ termios.c_cflag |= CREAD;
+ termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
+
+ termios.c_cc[VINTR] = 03; /* ^C */
+ termios.c_cc[VQUIT] = 034; /* ^\ */
+ termios.c_cc[VERASE] = 0177;
+ termios.c_cc[VKILL] = 025; /* ^X */
+ termios.c_cc[VEOF] = 04; /* ^D */
+ termios.c_cc[VSTART] = 021; /* ^Q */
+ termios.c_cc[VSTOP] = 023; /* ^S */
+ termios.c_cc[VSUSP] = 032; /* ^Z */
+ termios.c_cc[VLNEXT] = 026; /* ^V */
+ termios.c_cc[VWERASE] = 027; /* ^W */
+ termios.c_cc[VREPRINT] = 022; /* ^R */
+ termios.c_cc[VEOL] = 0;
+ termios.c_cc[VEOL2] = 0;
+
+ termios.c_cc[VTIME] = 0;
+ termios.c_cc[VMIN] = 1;
+
+ if (tcsetattr(fd, TCSANOW, &termios) < 0)
+ r = -errno;
+
+finish:
+ /* Just in case, flush all crap out */
+ tcflush(fd, TCIOFLUSH);
+
+ return r;
+}
+
+int open_terminal(const char *name, int mode) {
+ int fd, r;
+
+ if ((fd = open(name, mode)) < 0)
+ return -errno;
+
+ if ((r = isatty(fd)) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (!r) {
+ close_nointr_nofail(fd);
+ return -ENOTTY;
+ }
+
+ return fd;
+}
+
+int flush_fd(int fd) {
+ struct pollfd pollfd;
+
+ zero(pollfd);
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+
+ for (;;) {
+ char buf[1024];
+ ssize_t l;
+ int r;
+
+ if ((r = poll(&pollfd, 1, 0)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ if (r == 0)
+ return 0;
+
+ if ((l = read(fd, buf, sizeof(buf))) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN)
+ return 0;
+
+ return -errno;
+ }
+
+ if (l <= 0)
+ return 0;
+ }
+}
+
+int acquire_terminal(const char *name, bool fail, bool force) {
+ int fd = -1, notify = -1, r, wd = -1;
+
+ assert(name);
+
+ /* We use inotify to be notified when the tty is closed. We
+ * create the watch before checking if we can actually acquire
+ * it, so that we don't lose any event.
+ *
+ * Note: strictly speaking this actually watches for the
+ * device being closed, it does *not* really watch whether a
+ * tty loses its controlling process. However, unless some
+ * rogue process uses TIOCNOTTY on /dev/tty *after* closing
+ * its tty otherwise this will not become a problem. As long
+ * as the administrator makes sure not configure any service
+ * on the same tty as an untrusted user this should not be a
+ * problem. (Which he probably should not do anyway.) */
+
+ if (!fail && !force) {
+ if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ for (;;) {
+ if (notify >= 0)
+ if ((r = flush_fd(notify)) < 0)
+ goto fail;
+
+ /* We pass here O_NOCTTY only so that we can check the return
+ * value TIOCSCTTY and have a reliable way to figure out if we
+ * successfully became the controlling process of the tty */
+ if ((fd = open_terminal(name, O_RDWR|O_NOCTTY)) < 0)
+ return -errno;
+
+ /* First, try to get the tty */
+ if ((r = ioctl(fd, TIOCSCTTY, force)) < 0 &&
+ (force || fail || errno != EPERM)) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (r >= 0)
+ break;
+
+ assert(!fail);
+ assert(!force);
+ assert(notify >= 0);
+
+ for (;;) {
+ struct inotify_event e;
+ ssize_t l;
+
+ if ((l = read(notify, &e, sizeof(e))) != sizeof(e)) {
+
+ if (l < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ r = -errno;
+ } else
+ r = -EIO;
+
+ goto fail;
+ }
+
+ if (e.wd != wd || !(e.mask & IN_CLOSE)) {
+ r = -errno;
+ goto fail;
+ }
+
+ break;
+ }
+
+ /* We close the tty fd here since if the old session
+ * ended our handle will be dead. It's important that
+ * we do this after sleeping, so that we don't enter
+ * an endless loop. */
+ close_nointr_nofail(fd);
+ }
+
+ if (notify >= 0)
+ close_nointr_nofail(notify);
+
+ if ((r = reset_terminal(fd)) < 0)
+ log_warning("Failed to reset terminal: %s", strerror(-r));
+
+ return fd;
+
+fail:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ if (notify >= 0)
+ close_nointr_nofail(notify);
+
+ return r;
+}
+
+int release_terminal(void) {
+ int r = 0, fd;
+ struct sigaction sa_old, sa_new;
+
+ if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
+ return -errno;
+
+ /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+ * by our own TIOCNOTTY */
+
+ zero(sa_new);
+ sa_new.sa_handler = SIG_IGN;
+ sa_new.sa_flags = SA_RESTART;
+ assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
+ if (ioctl(fd, TIOCNOTTY) < 0)
+ r = -errno;
+
+ assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
+
+ close_nointr_nofail(fd);
+ return r;
+}
+
+int ignore_signal(int sig) {
+ struct sigaction sa;
+
+ zero(sa);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_RESTART;
+
+ return sigaction(sig, &sa, NULL);
+}
+
+int close_pipe(int p[]) {
+ int a = 0, b = 0;
+
+ assert(p);
+
+ if (p[0] >= 0) {
+ a = close_nointr(p[0]);
+ p[0] = -1;
+ }
+
+ if (p[1] >= 0) {
+ b = close_nointr(p[1]);
+ p[1] = -1;
+ }
+
+ return a < 0 ? a : b;
+}
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes) {
+ uint8_t *p;
+ ssize_t n = 0;
+
+ assert(fd >= 0);
+ assert(buf);
+
+ p = buf;
+
+ while (nbytes > 0) {
+ ssize_t k;
+
+ if ((k = read(fd, p, nbytes)) <= 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN) {
+ struct pollfd pollfd;
+
+ zero(pollfd);
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+
+ if (poll(&pollfd, 1, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return n > 0 ? n : -errno;
+ }
+
+ if (pollfd.revents != POLLIN)
+ return n > 0 ? n : -EIO;
+
+ continue;
+ }
+
+ return n > 0 ? n : (k < 0 ? -errno : 0);
+ }
+
+ p += k;
+ nbytes -= k;
+ n += k;
+ }
+
+ return n;
+}
+
+int path_is_mount_point(const char *t) {
+ struct stat a, b;
+ char *copy;
+
+ if (lstat(t, &a) < 0) {
+
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ if (!(copy = strdup(t)))
+ return -ENOMEM;
+
+ if (lstat(dirname(copy), &b) < 0) {
+ free(copy);
+ return -errno;
+ }
+
+ free(copy);
+
+ return a.st_dev != b.st_dev;
+}
+
+int parse_usec(const char *t, usec_t *usec) {
+ static const struct {
+ const char *suffix;
+ usec_t usec;
+ } table[] = {
+ { "sec", USEC_PER_SEC },
+ { "s", USEC_PER_SEC },
+ { "min", USEC_PER_MINUTE },
+ { "hr", USEC_PER_HOUR },
+ { "h", USEC_PER_HOUR },
+ { "d", USEC_PER_DAY },
+ { "w", USEC_PER_WEEK },
+ { "msec", USEC_PER_MSEC },
+ { "ms", USEC_PER_MSEC },
+ { "m", USEC_PER_MINUTE },
+ { "usec", 1ULL },
+ { "us", 1ULL },
+ { "", USEC_PER_SEC },
+ };
+
+ const char *p;
+ usec_t r = 0;
+
+ assert(t);
+ assert(usec);
+
+ p = t;
+ do {
+ long long l;
+ char *e;
+ unsigned i;
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno != 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ELEMENTSOF(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ r += (usec_t) l * table[i].usec;
+ p = e + strlen(table[i].suffix);
+ break;
+ }
+
+ if (i >= ELEMENTSOF(table))
+ return -EINVAL;
+
+ } while (*p != 0);
+
+ *usec = r;
+
+ return 0;
+}
+
+int make_stdio(int fd) {
+ int r, s, t;
+
+ assert(fd >= 0);
+
+ r = dup2(fd, STDIN_FILENO);
+ s = dup2(fd, STDOUT_FILENO);
+ t = dup2(fd, STDERR_FILENO);
+
+ if (fd >= 3)
+ close_nointr_nofail(fd);
+
+ if (r < 0 || s < 0 || t < 0)
+ return -errno;
+
+ return 0;
+}
+
+bool is_clean_exit(int code, int status) {
+
+ if (code == CLD_EXITED)
+ return status == 0;
+
+ /* If a daemon does not implement handlers for some of the
+ * signals that's not considered an unclean shutdown */
+ if (code == CLD_KILLED)
+ return
+ status == SIGHUP ||
+ status == SIGINT ||
+ status == SIGTERM ||
+ status == SIGPIPE;
+
+ return false;
+}
+
+bool is_device_path(const char *path) {
+
+ /* Returns true on paths that refer to a device, either in
+ * sysfs or in /dev */
+
+ return
+ path_startswith(path, "/dev/") ||
+ path_startswith(path, "/sys/");
+}
+
+static const char *const ioprio_class_table[] = {
+ [IOPRIO_CLASS_NONE] = "none",
+ [IOPRIO_CLASS_RT] = "realtime",
+ [IOPRIO_CLASS_BE] = "best-effort",
+ [IOPRIO_CLASS_IDLE] = "idle"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ioprio_class, int);
+
+static const char *const sigchld_code_table[] = {
+ [CLD_EXITED] = "exited",
+ [CLD_KILLED] = "killed",
+ [CLD_DUMPED] = "dumped",
+ [CLD_TRAPPED] = "trapped",
+ [CLD_STOPPED] = "stopped",
+ [CLD_CONTINUED] = "continued",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
+
+static const char *const log_facility_table[LOG_NFACILITIES] = {
+ [LOG_FAC(LOG_KERN)] = "kern",
+ [LOG_FAC(LOG_USER)] = "user",
+ [LOG_FAC(LOG_MAIL)] = "mail",
+ [LOG_FAC(LOG_DAEMON)] = "daemon",
+ [LOG_FAC(LOG_AUTH)] = "auth",
+ [LOG_FAC(LOG_SYSLOG)] = "syslog",
+ [LOG_FAC(LOG_LPR)] = "lpr",
+ [LOG_FAC(LOG_NEWS)] = "news",
+ [LOG_FAC(LOG_UUCP)] = "uucp",
+ [LOG_FAC(LOG_CRON)] = "cron",
+ [LOG_FAC(LOG_AUTHPRIV)] = "authpriv",
+ [LOG_FAC(LOG_FTP)] = "ftp",
+ [LOG_FAC(LOG_LOCAL0)] = "local0",
+ [LOG_FAC(LOG_LOCAL1)] = "local1",
+ [LOG_FAC(LOG_LOCAL2)] = "local2",
+ [LOG_FAC(LOG_LOCAL3)] = "local3",
+ [LOG_FAC(LOG_LOCAL4)] = "local4",
+ [LOG_FAC(LOG_LOCAL5)] = "local5",
+ [LOG_FAC(LOG_LOCAL6)] = "local6",
+ [LOG_FAC(LOG_LOCAL7)] = "local7"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_facility, int);
+
+static const char *const log_level_table[] = {
+ [LOG_EMERG] = "emerg",
+ [LOG_ALERT] = "alert",
+ [LOG_CRIT] = "crit",
+ [LOG_ERR] = "err",
+ [LOG_WARNING] = "warning",
+ [LOG_NOTICE] = "notice",
+ [LOG_INFO] = "info",
+ [LOG_DEBUG] = "debug"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_level, int);
+
+static const char* const sched_policy_table[] = {
+ [SCHED_OTHER] = "other",
+ [SCHED_BATCH] = "batch",
+ [SCHED_IDLE] = "idle",
+ [SCHED_FIFO] = "fifo",
+ [SCHED_RR] = "rr"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sched_policy, int);
+
+static const char* const rlimit_table[] = {
+ [RLIMIT_CPU] = "LimitCPU",
+ [RLIMIT_FSIZE] = "LimitFSIZE",
+ [RLIMIT_DATA] = "LimitDATA",
+ [RLIMIT_STACK] = "LimitSTACK",
+ [RLIMIT_CORE] = "LimitCORE",
+ [RLIMIT_RSS] = "LimitRSS",
+ [RLIMIT_NOFILE] = "LimitNOFILE",
+ [RLIMIT_AS] = "LimitAS",
+ [RLIMIT_NPROC] = "LimitNPROC",
+ [RLIMIT_MEMLOCK] = "LimitMEMLOCK",
+ [RLIMIT_LOCKS] = "LimitLOCKS",
+ [RLIMIT_SIGPENDING] = "LimitSIGPENDING",
+ [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE",
+ [RLIMIT_NICE] = "LimitNICE",
+ [RLIMIT_RTPRIO] = "LimitRTPRIO",
+ [RLIMIT_RTTIME] = "LimitRTTIME"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(rlimit, int);
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000000..a77a952ed6
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,256 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooutilhfoo
+#define fooutilhfoo
+
+/***
+ 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 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 <inttypes.h>
+#include <time.h>
+#include <sys/time.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+typedef uint64_t usec_t;
+
+#define MSEC_PER_SEC 1000ULL
+#define USEC_PER_SEC 1000000ULL
+#define USEC_PER_MSEC 1000ULL
+#define NSEC_PER_SEC 1000000000ULL
+#define NSEC_PER_MSEC 1000000ULL
+#define NSEC_PER_USEC 1000ULL
+
+#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC)
+#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE)
+#define USEC_PER_DAY (24ULL*USEC_PER_HOUR)
+#define USEC_PER_WEEK (7ULL*USEC_PER_DAY)
+
+/* What is interpreted as whitespace? */
+#define WHITESPACE " \t\n\r"
+#define NEWLINE "\n\r"
+
+#define FORMAT_TIMESTAMP_MAX 64
+
+usec_t now(clockid_t clock);
+
+usec_t timespec_load(const struct timespec *ts);
+struct timespec *timespec_store(struct timespec *ts, usec_t u);
+
+usec_t timeval_load(const struct timeval *tv);
+struct timeval *timeval_store(struct timeval *tv, usec_t u);
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+
+bool streq_ptr(const char *a, const char *b);
+
+#define new(t, n) ((t*) malloc(sizeof(t)*(n)))
+
+#define new0(t, n) ((t*) calloc((n), sizeof(t)))
+
+#define malloc0(n) (calloc((n), 1))
+
+static inline const char* yes_no(bool b) {
+ return b ? "yes" : "no";
+}
+
+static inline const char* strempty(const char *s) {
+ return s ? s : "";
+}
+
+static inline const char* strnull(const char *s) {
+ return s ? s : "(null)";
+}
+
+static inline const char *strna(const char *s) {
+ return s ? s : "n/a";
+}
+
+static inline bool is_path_absolute(const char *p) {
+ return *p == '/';
+}
+
+bool endswith(const char *s, const char *postfix);
+bool startswith(const char *s, const char *prefix);
+bool startswith_no_case(const char *s, const char *prefix);
+
+bool first_word(const char *s, const char *word);
+
+int close_nointr(int fd);
+void close_nointr_nofail(int fd);
+
+int parse_boolean(const char *v);
+int parse_usec(const char *t, usec_t *usec);
+
+int safe_atou(const char *s, unsigned *ret_u);
+int safe_atoi(const char *s, int *ret_i);
+
+int safe_atolu(const char *s, unsigned long *ret_u);
+int safe_atoli(const char *s, long int *ret_i);
+
+int safe_atollu(const char *s, unsigned long long *ret_u);
+int safe_atolli(const char *s, long long int *ret_i);
+
+char *split(const char *c, size_t *l, const char *separator, char **state);
+char *split_quoted(const char *c, size_t *l, char **state);
+
+#define FOREACH_WORD(word, length, s, state) \
+ for ((state) = NULL, (word) = split((s), &(length), WHITESPACE, &(state)); (word); (word) = split((s), &(length), WHITESPACE, &(state)))
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \
+ for ((state) = NULL, (word) = split((s), &(length), (separator), &(state)); (word); (word) = split((s), &(length), (separator), &(state)))
+
+#define FOREACH_WORD_QUOTED(word, length, s, state) \
+ for ((state) = NULL, (word) = split_quoted((s), &(length), &(state)); (word); (word) = split_quoted((s), &(length), &(state)))
+
+char **split_path_and_make_absolute(const char *p);
+
+pid_t get_parent_of_pid(pid_t pid, pid_t *ppid);
+
+int write_one_line_file(const char *fn, const char *line);
+int read_one_line_file(const char *fn, char **line);
+
+char *strappend(const char *s, const char *suffix);
+
+int readlink_malloc(const char *p, char **r);
+
+char *file_name_from_path(const char *p);
+bool is_path(const char *p);
+
+bool path_is_absolute(const char *p);
+char *path_make_absolute(const char *p, const char *prefix);
+char *path_make_absolute_cwd(const char *p);
+char **strv_path_make_absolute_cwd(char **l);
+
+int reset_all_signal_handlers(void);
+
+char *strstrip(char *s);
+char *delete_chars(char *s, const char *bad);
+char *truncate_nl(char *s);
+
+char *file_in_same_dir(const char *path, const char *filename);
+int mkdir_parents(const char *path, mode_t mode);
+int mkdir_p(const char *path, mode_t mode);
+
+int get_process_name(pid_t pid, char **name);
+
+char hexchar(int x);
+int unhexchar(char c);
+char octchar(int x);
+int unoctchar(char c);
+char decchar(int x);
+int undecchar(char c);
+
+char *cescape(const char *s);
+char *cunescape(const char *s);
+
+char *path_kill_slashes(char *path);
+
+bool path_startswith(const char *path, const char *prefix);
+bool path_equal(const char *a, const char *b);
+
+char *ascii_strlower(char *path);
+
+char *xescape(const char *s, const char *bad);
+
+char *bus_path_escape(const char *s);
+char *bus_path_unescape(const char *s);
+
+bool ignore_file(const char *filename);
+
+bool chars_intersect(const char *a, const char *b);
+
+char *format_timestamp(char *buf, size_t l, usec_t t);
+
+int make_stdio(int fd);
+
+bool is_clean_exit(int code, int status);
+
+#define DEFINE_STRING_TABLE_LOOKUP(name,type) \
+ const char *name##_to_string(type i) { \
+ if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \
+ return NULL; \
+ return name##_table[i]; \
+ } \
+ type name##_from_string(const char *s) { \
+ type i; \
+ unsigned u = 0; \
+ assert(s); \
+ for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \
+ if (streq(name##_table[i], s)) \
+ return i; \
+ if (safe_atou(s, &u) >= 0 && \
+ u < ELEMENTSOF(name##_table)) \
+ return (type) u; \
+ return (type) -1; \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+
+int fd_nonblock(int fd, bool nonblock);
+int fd_cloexec(int fd, bool cloexec);
+
+int close_all_fds(const int except[], unsigned n_except);
+
+bool fstype_is_network(const char *fstype);
+
+int chvt(int vt);
+
+int read_one_char(FILE *f, char *ret, bool *need_nl);
+int ask(char *ret, const char *replies, const char *text, ...);
+
+int reset_terminal(int fd);
+int open_terminal(const char *name, int mode);
+int acquire_terminal(const char *name, bool fail, bool force);
+int release_terminal(void);
+
+int flush_fd(int fd);
+
+int ignore_signal(int sig);
+
+int close_pipe(int p[]);
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes);
+
+int path_is_mount_point(const char *path);
+
+bool is_device_path(const char *path);
+
+extern char * __progname;
+
+const char *ioprio_class_to_string(int i);
+int ioprio_class_from_string(const char *s);
+
+const char *sigchld_code_to_string(int i);
+int sigchld_code_from_string(const char *s);
+
+const char *log_facility_to_string(int i);
+int log_facility_from_string(const char *s);
+
+const char *log_level_to_string(int i);
+int log_level_from_string(const char *s);
+
+const char *sched_policy_to_string(int i);
+int sched_policy_from_string(const char *s);
+
+const char *rlimit_to_string(int i);
+int rlimit_from_string(const char *s);
+
+#endif
diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c
new file mode 100644
index 0000000000..cb3f201322
--- /dev/null
+++ b/src/utmp-wtmp.c
@@ -0,0 +1,214 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ 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 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 <utmpx.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/utsname.h>
+
+#include "macro.h"
+#include "utmp-wtmp.h"
+
+int utmp_get_runlevel(int *runlevel, int *previous) {
+ struct utmpx lookup, *found;
+ int r;
+ const char *e;
+
+ assert(runlevel);
+
+ /* If these values are set in the environment this takes
+ * precedence. Presumably, sysvinit does this to work around a
+ * race condition that would otherwise exist where we'd always
+ * go to disk and hence might read runlevel data that might be
+ * very new and does not apply to the current script being
+ * executed. */
+
+ if ((e = getenv("RUNLEVEL")) && e[0] > 0) {
+ *runlevel = e[0];
+
+ if (previous) {
+ /* $PREVLEVEL seems to be an Upstart thing */
+
+ if ((e = getenv("PREVLEVEL")) && e[0] > 0)
+ *previous = e[0];
+ else
+ *previous = 0;
+ }
+
+ return 0;
+ }
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return -errno;
+
+ setutxent();
+
+ zero(lookup);
+ lookup.ut_type = RUN_LVL;
+
+ if (!(found = getutxid(&lookup)))
+ r = -errno;
+ else {
+ int a, b;
+
+ a = found->ut_pid & 0xFF;
+ b = (found->ut_pid >> 8) & 0xFF;
+
+ if (a < 0 || b < 0)
+ r = -EIO;
+ else {
+ *runlevel = a;
+
+ if (previous)
+ *previous = b;
+ r = 0;
+ }
+ }
+
+ endutxent();
+
+ return r;
+}
+
+static void init_entry(struct utmpx *store, usec_t timestamp) {
+ struct utsname uts;
+
+ assert(store);
+
+ zero(*store);
+ zero(uts);
+
+ if (timestamp <= 0)
+ timestamp = now(CLOCK_REALTIME);
+
+ store->ut_tv.tv_sec = timestamp / USEC_PER_SEC;
+ store->ut_tv.tv_usec = timestamp % USEC_PER_SEC;
+
+ if (uname(&uts) >= 0)
+ strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
+
+ strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */
+ strncpy(store->ut_id, "~~", sizeof(store->ut_id));
+}
+
+static int write_entry_utmp(const struct utmpx *store) {
+ int r;
+
+ assert(store);
+
+ /* utmp is similar to wtmp, but there is only one entry for
+ * each entry type resp. user; i.e. basically a key/value
+ * table. */
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return -errno;
+
+ setutxent();
+
+ if (!pututxline(store))
+ r = -errno;
+ else
+ r = 0;
+
+ endutxent();
+
+ return r;
+}
+
+static int write_entry_wtmp(const struct utmpx *store) {
+ assert(store);
+
+ /* wtmp is a simple append-only file where each entry is
+ simply appended to * the end; i.e. basically a log. */
+
+ errno = 0;
+ updwtmpx(_PATH_WTMPX, store);
+ return -errno;
+}
+
+static int write_entry_both(const struct utmpx *store) {
+ int r, s;
+
+ r = write_entry_utmp(store);
+ s = write_entry_wtmp(store);
+
+ if (r >= 0)
+ r = s;
+
+ /* If utmp/wtmp have been disabled, that's a good thing, hence
+ * ignore the errors */
+ if (r == -ENOENT)
+ r = 0;
+
+ return r;
+}
+
+int utmp_put_shutdown(usec_t timestamp) {
+ struct utmpx store;
+
+ init_entry(&store, timestamp);
+
+ store.ut_type = RUN_LVL;
+ strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
+
+int utmp_put_reboot(usec_t timestamp) {
+ struct utmpx store;
+
+ init_entry(&store, timestamp);
+
+ store.ut_type = BOOT_TIME;
+ strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
+
+int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous) {
+ struct utmpx store;
+ int r;
+
+ assert(runlevel > 0);
+
+ if (previous <= 0) {
+ /* Find the old runlevel automatically */
+
+ if ((r = utmp_get_runlevel(&previous, NULL)) < 0) {
+ if (r != -ESRCH)
+ return r;
+
+ previous = 0;
+ }
+
+ if (previous == runlevel)
+ return 0;
+ }
+
+ init_entry(&store, timestamp);
+
+ store.ut_type = RUN_LVL;
+ store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
+ strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
+
+ return write_entry_both(&store);
+}
diff --git a/src/utmp-wtmp.h b/src/utmp-wtmp.h
new file mode 100644
index 0000000000..34c3222c99
--- /dev/null
+++ b/src/utmp-wtmp.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooutmpwtmphfoo
+#define fooutmpwtmphfoo
+
+/***
+ 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 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 "util.h"
+
+int utmp_get_runlevel(int *runlevel, int *previous);
+
+int utmp_put_shutdown(usec_t timestamp);
+int utmp_put_reboot(usec_t timestamp);
+int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous);
+
+#endif