diff options
author | Lennart Poettering <lennart@poettering.net> | 2010-05-16 18:45:24 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2010-05-16 18:45:24 +0200 |
commit | e99e38bbdcca3fe5956823bdb3d38544ccf93221 (patch) | |
tree | 109871cf2bdcad19105d55f4eea62d5495a8f614 /src | |
parent | e9da3678fcfc774b325dc1eaa054d0e00028a1fc (diff) |
build-sys: move source files to subdirectory
Diffstat (limited to 'src')
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(¶m); + if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 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(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) < 0) + return -errno; + + major = param.protover.version; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) < 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(¶m); + param.ioctlfd = ioctl_fd; + param.timeout.timeout = sec; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 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(¶m); + 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, ¶m) < 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, §ion, 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), ¶m) < 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 |