diff options
author | Lennart Poettering <lennart@poettering.net> | 2010-03-31 16:29:55 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2010-03-31 16:29:55 +0200 |
commit | 8e27452380193a5f81bfd08a59aab8b07008ba0b (patch) | |
tree | 7ea3398a0cff784e5c45416bec5104597aaa3625 | |
parent | c9dae904f3a07ae563bd58fb2b39529c1f108915 (diff) |
cgroup: add cgroupsification
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 64 | ||||
-rw-r--r-- | cgroup.c | 523 | ||||
-rw-r--r-- | cgroup.h | 83 | ||||
-rw-r--r-- | cgroups-agent.c | 72 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | dbus.c | 137 | ||||
-rw-r--r-- | execute.c | 15 | ||||
-rw-r--r-- | execute.h | 6 | ||||
-rw-r--r-- | load-fragment.c | 35 | ||||
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | manager.c | 38 | ||||
-rw-r--r-- | manager.h | 10 | ||||
-rw-r--r-- | mount-setup.c | 112 | ||||
-rw-r--r-- | mount-setup.h | 27 | ||||
-rw-r--r-- | service.c | 55 | ||||
-rw-r--r-- | service.h | 1 | ||||
-rw-r--r-- | socket.c | 12 | ||||
-rw-r--r-- | test-engine.c | 2 | ||||
-rw-r--r-- | test1/exec-demo.service | 6 | ||||
-rw-r--r-- | unit.c | 140 | ||||
-rw-r--r-- | unit.h | 11 | ||||
-rw-r--r-- | util.c | 2 |
23 files changed, 1314 insertions, 46 deletions
diff --git a/.gitignore b/.gitignore index 581edaed97..06ef3d0323 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +systemd-cgroups-agent systemd *.o test-engine diff --git a/Makefile.am b/Makefile.am index 3fc221c7f0..57a2dd98dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,15 +25,19 @@ AM_CPPFLAGS = \ -DSYSTEM_DATA_UNIT_PATH=\"$(pkgdatadir)/system\" \ -DSYSTEM_SYSVINIT_PATH=\"$(sysconfdir)/init.d\" \ -DSESSION_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/session\" \ - -DSESSION_DATA_UNIT_PATH=\"$(pkgdatadir)/session\" + -DSESSION_DATA_UNIT_PATH=\"$(pkgdatadir)/session\" \ + -DCGROUP_AGENT_PATH=\"$(pkglibexecdir)/systemd-cgroups-agent\" sbin_PROGRAMS = \ systemd bin_PROGRAMS = \ systemctl \ - systemadm \ - systemd-logger + systemadm + +pkglibexec_PROGRAMS = \ + systemd-logger \ + systemd-cgroups-agent noinst_PROGRAMS = \ test-engine \ @@ -41,34 +45,64 @@ noinst_PROGRAMS = \ BASIC_SOURCES= \ util.c \ + util.h \ hashmap.c \ + hashmap.h \ set.c \ + set.h \ strv.c \ + strv.h \ conf-parser.c \ + conf-parser.h \ socket-util.c \ + socket-util.h \ log.c \ - ratelimit.c + log.h \ + ratelimit.c \ + ratelimit.h COMMON_SOURCES= \ $(BASIC_SOURCES) \ unit.c \ + unit.h \ job.c \ + job.h \ manager.c \ + manager.h \ load-fragment.c \ + load-fragment.h \ service.c \ + service.h \ automount.c \ + automount.h \ mount.c \ + mount.h \ device.c \ + device.h \ target.c \ + target.h \ snapshot.c \ + snapshot.h \ socket.c \ + socket.h \ timer.c \ + timer.h \ load-dropin.c \ + load-dropin.h \ execute.c \ + execute.h \ dbus.c \ + dbus.h \ dbus-manager.c \ + dbus-manager.h \ dbus-unit.c \ - dbus-job.c + dbus-unit.h \ + dbus-job.c \ + dbus-job.h \ + cgroup.c \ + cgroup.h \ + mount-setup.c \ + mount-setup.h systemd_SOURCES = \ $(COMMON_SOURCES) \ @@ -77,11 +111,13 @@ systemd_SOURCES = \ systemd_CPPFLAGS = \ $(AM_CPPFLAGS) \ $(DBUS_CFLAGS) \ - $(UDEV_CFLAGS) + $(UDEV_CFLAGS) \ + $(CGROUP_CFLAGS) systemd_LDADD = \ $(DBUS_LIBS) \ - $(UDEV_LIBS) + $(UDEV_LIBS) \ + $(CGROUP_LIBS) test_engine_SOURCES = \ $(COMMON_SOURCES) \ @@ -101,6 +137,17 @@ systemd_logger_SOURCES = \ $(BASIC_SOURCES) \ logger.c +systemd_cgroups_agent_SOURCES = \ + $(BASIC_SOURCES) \ + cgroups-agent.c + +systemd_cgroups_agent_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DBUS_CFLAGS) + +systemd_cgroups_agent_LDADD = \ + $(DBUS_LIBS) + VALAFLAGS = -g --save-temps --pkg=dbus-glib-1 --pkg=posix --pkg gee-1.0 --pkg gtk+-2.0 systemctl_SOURCES = \ @@ -120,4 +167,5 @@ systemadm_LDADD = $(DBUSGLIB_LIBS) $(GTK_LIBS) CLEANFILES = \ systemd-interfaces.c \ systemctl.c \ - systemadm.c + systemadm.c \ + systemd-cgroups-agent diff --git a/cgroup.c b/cgroup.c new file mode 100644 index 0000000000..04736accea --- /dev/null +++ b/cgroup.c @@ -0,0 +1,523 @@ +/*-*- 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); + } + + free(b->controller); + free(b->path); + + if (b->cgroup) { + + if (b->only_us && b->clean_up) + cgroup_delete_cgroup(b->cgroup, true); + + cgroup_free(&b->cgroup); + } + + 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; + + assert(b); + assert(sig > 0); + + 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; + } + + 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); + return r; +} + +int cgroup_bonding_kill_list(CGroupBonding *first, int sig) { + CGroupBonding *b; + + LIST_FOREACH(by_unit, b, first) { + int r; + + if ((r = cgroup_bonding_kill(b, sig)) < 0) + return r; + } + + return 0; +} + +/* 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; + } + + 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; + + 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); + } + + free(m->cgroup_hierarchy); + m->cgroup_hierarchy = NULL; + if (asprintf(&m->cgroup_hierarchy, "%s/systemd-%llu", strcmp(cp, "/") == 0 ? "" : cp, (unsigned long long) pid) < 0) { + free(cp); + free(mp); + return -ENOMEM; + } + + log_info("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_info("Installed release agent, or already installed."); + + free(mp); + free(cp); + + if ((r = create_hierarchy_cgroup(m)) < 0) + log_error("Failed to create root cgroup hierarchy: %s", strerror(-r)); + else + log_info("Created root group."); + + return r; +} + +int manager_shutdown_cgroup(Manager *m) { + 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; + } + + if ((r = cgroup_delete_cgroup_ext(cg, CGFLAG_DELETE_IGNORE_MIGRATION|CGFLAG_DELETE_RECURSIVE)) != 0) { + log_error("Failed to delete cgroup hierarchy group: %s", cgroup_strerror(r)); + r = translate_error(r, errno); + goto finish; + } + 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; +} diff --git a/cgroup.h b/cgroup.h new file mode 100644 index 0000000000..66ddb9579a --- /dev/null +++ b/cgroup.h @@ -0,0 +1,83 @@ +/*-*- 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; + + /* When shutting down, kill all tasks? */ + bool kill_all:1; + + /* 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; + + /* For the Unit::cgroup_bondings list */ + LIST_FIELDS(CGroupBonding, by_unit); + + /* For the Manager::cgroup_bondings hashmap */ + LIST_FIELDS(CGroupBonding, by_path); +}; + +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); + +#include "manager.h" + +int manager_setup_cgroup(Manager *m); +int manager_shutdown_cgroup(Manager *m); + +int cgroup_notify_empty(Manager *m, const char *group); + +#endif diff --git a/cgroups-agent.c b/cgroups-agent.c new file mode 100644 index 0000000000..232b63e2da --- /dev/null +++ b/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/configure.ac b/configure.ac index 95a9ef5ca4..f0beb1fc15 100644 --- a/configure.ac +++ b/configure.ac @@ -69,6 +69,10 @@ PKG_CHECK_MODULES(GTK, [ gtk+-2.0 ]) AC_SUBST(GTK_CFLAGS) AC_SUBST(GTK_LIBS) +PKG_CHECK_MODULES(CGROUP, [ libcgroup ]) +AC_SUBST(CGROUP_CFLAGS) +AC_SUBST(CGROUP_LIBS) + AM_PROG_VALAC() AC_SUBST(VAPIDIR) @@ -29,16 +29,28 @@ #include "dbus.h" #include "log.h" #include "strv.h" +#include "cgroup.h" static void bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) { Manager *m = data; assert(bus); assert(m); + assert(m->bus == bus); m->request_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); + 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; @@ -81,7 +93,7 @@ void bus_watch_event(Manager *m, Watch *w, int events) { /* 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))) + if (!dbus_watch_get_enabled(w->data.bus_watch)) return; dbus_watch_handle(w->data.bus_watch, events_to_bus_flags(events)); @@ -315,16 +327,57 @@ static DBusHandlerResult bus_message_filter(DBusConnection *connection, DBusMes 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! D-Bus connection terminated."); + + /* FIXME: we probably should restart D-Bus here */ + + } 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->request_bus_dispatch) - return 0; + if (m->request_bus_dispatch) + if (dbus_connection_dispatch(m->bus) == DBUS_DISPATCH_COMPLETE) { + m->request_bus_dispatch = false; + return 1; + } - if (dbus_connection_dispatch(m->bus) == DBUS_DISPATCH_COMPLETE) - m->request_bus_dispatch = false; + 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 1; + return 0; } static int request_name(Manager *m) { @@ -362,6 +415,18 @@ static int request_name(Manager *m) { return 0; } +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(Manager *m) { DBusError error; char *id; @@ -381,17 +446,39 @@ int bus_init(Manager *m) { if (!(m->bus = dbus_bus_get_private(m->running_as == MANAGER_SESSION ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &error))) { log_error("Failed to get D-Bus connection: %s", error.message); dbus_error_free(&error); + bus_done(m); return -ECONNREFUSED; } - dbus_connection_set_exit_on_disconnect(m->bus, FALSE); + if ((r = bus_setup_loop(m, m->bus)) < 0) { + bus_done(m); + return r; + } + dbus_connection_set_dispatch_status_function(m->bus, bus_dispatch_status, m, NULL); - if (!dbus_connection_set_watch_functions(m->bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || - !dbus_connection_set_timeout_functions(m->bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL) || - !dbus_connection_register_object_path(m->bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || + + if (m->running_as == MANAGER_SESSION) { + if (!(m->system_bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) { + log_error("Failed to get D-Bus connection: %s", error.message); + dbus_error_free(&error); + bus_done(m); + return -ECONNREFUSED; + } + + if ((r = bus_setup_loop(m, m->system_bus)) < 0) { + bus_done(m); + return r; + } + + dbus_connection_set_dispatch_status_function(m->system_bus, system_bus_dispatch_status, m, NULL); + } else + m->system_bus = m->bus; + + if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || !dbus_connection_register_fallback(m->bus, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || !dbus_connection_register_fallback(m->bus, "/org/freedesktop/systemd1/job", &bus_job_vtable, m) || - !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) { + !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL) || + !dbus_connection_add_filter(m->system_bus, system_bus_message_filter, m, NULL)) { bus_done(m); return -ENOMEM; } @@ -406,7 +493,6 @@ int bus_init(Manager *m) { if (dbus_error_is_set(&error)) { log_error("Failed to register match: %s", error.message); dbus_error_free(&error); - bus_done(m); return -ENOMEM; } @@ -415,12 +501,31 @@ int bus_init(Manager *m) { return r; } + 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(m); + return -ENOMEM; + } + log_debug("Successfully connected to D-Bus bus %s as %s", strnull((id = dbus_connection_get_server_id(m->bus))), strnull(dbus_bus_get_unique_name(m->bus))); dbus_free(id); + 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); + m->request_bus_dispatch = true; + m->request_system_bus_dispatch = true; return 0; } @@ -428,6 +533,12 @@ int bus_init(Manager *m) { void bus_done(Manager *m) { assert(m); + if (m->system_bus && m->system_bus != m->bus) { + dbus_connection_close(m->system_bus); + dbus_connection_unref(m->system_bus); + m->system_bus = NULL; + } + if (m->bus) { dbus_connection_close(m->bus); dbus_connection_unref(m->bus); @@ -571,8 +682,6 @@ oom: return DBUS_HANDLER_RESULT_NEED_MEMORY; } - - static const char *error_to_dbus(int error) { switch(error) { @@ -42,6 +42,7 @@ #include "log.h" #include "ioprio.h" #include "securebits.h" +#include "cgroup.h" static int close_fds(int except[], unsigned n_except) { DIR *d; @@ -508,9 +509,11 @@ int exec_spawn(const ExecCommand *command, int *fds, unsigned n_fds, bool apply_permissions, bool apply_chroot, + CGroupBonding *cgroup_bondings, pid_t *ret) { pid_t pid; + int r; assert(command); assert(context); @@ -519,11 +522,15 @@ int exec_spawn(const ExecCommand *command, log_debug("About to execute %s", command->path); + if (cgroup_bondings) + if ((r = cgroup_bonding_realize_list(cgroup_bondings))) + return r; + if ((pid = fork()) < 0) return -errno; if (pid == 0) { - int i, r; + int i; sigset_t ss; const char *username = NULL, *home = NULL; uid_t uid = (uid_t) -1; @@ -556,6 +563,12 @@ int exec_spawn(const ExecCommand *command, 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]; @@ -33,6 +33,8 @@ typedef struct ExecContext ExecContext; #include <stdio.h> #include <sched.h> +struct CGroupBonding; + #include "list.h" #include "util.h" @@ -145,7 +147,8 @@ typedef enum ExitStatus { EXIT_CPUAFFINITY, EXIT_GROUP, EXIT_USER, - EXIT_CAPABILITIES + EXIT_CAPABILITIES, + EXIT_CGROUP } ExitStatus; int exec_spawn(const ExecCommand *command, @@ -153,6 +156,7 @@ int exec_spawn(const ExecCommand *command, int *fds, unsigned n_fds, bool apply_permissions, bool apply_chroot, + struct CGroupBonding *cgroup_bondings, pid_t *ret); void exec_command_free_list(ExecCommand *c); diff --git a/load-fragment.c b/load-fragment.c index 0b43c81194..bf17111482 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -943,6 +943,37 @@ static int config_parse_limit( 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; +} + #define FOLLOW_MAX 8 static int open_follow(char **filename, FILE **_f, Set *names, char **_id) { @@ -1067,7 +1098,8 @@ static int load_from_path(Unit *u, const char *path) { { "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 }, \ - { "NonBlocking", config_parse_bool, &(context).non_blocking, section } + { "NonBlocking", config_parse_bool, &(context).non_blocking, section }, \ + { "ControlGroup", config_parse_cgroup, u, section } \ const ConfigItem items[] = { { "Names", config_parse_names, u, "Meta" }, @@ -1096,6 +1128,7 @@ static int load_from_path(Unit *u, const char *path) { { "Restart", config_parse_service_restart, &u->service, "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" }, EXEC_CONTEXT_CONFIG_ITEMS(u->service.exec_context, "Service"), { "ListenStream", config_parse_listen, &u->socket, "Socket" }, @@ -37,8 +37,8 @@ int main(int argc, char *argv[]) { assert_se(set_unit_path("test1") >= 0); - if (!(m = manager_new())) { - log_error("Failed to allocate manager object: %s", strerror(ENOMEM)); + if ((r = manager_new(&m)) < 0) { + log_error("Failed to allocate manager object: %s", strerror(-r)); goto finish; } @@ -31,6 +31,7 @@ #include <sys/reboot.h> #include <sys/ioctl.h> #include <linux/kd.h> +#include <libcgroup.h> #include "manager.h" #include "hashmap.h" @@ -39,6 +40,8 @@ #include "log.h" #include "util.h" #include "ratelimit.h" +#include "cgroup.h" +#include "mount-setup.h" static int manager_setup_signals(Manager *m) { sigset_t mask; @@ -254,11 +257,14 @@ static int manager_find_paths(Manager *m) { return 0; } -Manager* manager_new(void) { +int manager_new(Manager **_m) { Manager *m; + int r = -ENOMEM; + + assert(_m); if (!(m = new0(Manager, 1))) - return NULL; + return -ENOMEM; m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = -1; m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ @@ -275,6 +281,9 @@ Manager* manager_new(void) { 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->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) goto fail; @@ -287,7 +296,7 @@ Manager* manager_new(void) { log_debug("systemd running in %s mode.", manager_running_as_to_string(m->running_as)); - if (manager_find_paths(m) < 0) + if ((r = manager_find_paths(m)) < 0) goto fail; if (chdir("/") < 0) @@ -296,18 +305,25 @@ Manager* manager_new(void) { /* Become a session leader if we aren't one yet. */ setsid(); - if (manager_setup_signals(m) < 0) + if ((r = manager_setup_signals(m)) < 0) + goto fail; + + if ((r = mount_setup()) < 0) + goto fail; + + if ((r = manager_setup_cgroup(m)) < 0) goto fail; /* FIXME: this should be called only when the D-Bus bus daemon is running */ - if (bus_init(m) < 0) + if ((r = bus_init(m)) < 0) goto fail; - return m; + *_m = m; + return 0; fail: manager_free(m); - return NULL; + return r; } void manager_free(Manager *m) { @@ -327,6 +343,8 @@ void manager_free(Manager *m) { if (unit_vtable[c]->shutdown) unit_vtable[c]->shutdown(m); + manager_shutdown_cgroup(m); + bus_done(m); hashmap_free(m->units); @@ -342,6 +360,12 @@ void manager_free(Manager *m) { strv_free(m->unit_path); strv_free(m->sysvinit_path); + free(m->cgroup_controller); + free(m->cgroup_hierarchy); + + assert(hashmap_isempty(m->cgroup_bondings)); + hashmap_free(m->cgroup_bondings); + free(m); } @@ -133,6 +133,7 @@ struct Manager { bool dispatching_dbus_queue:1; bool request_bus_dispatch:1; + bool request_system_bus_dispatch:1; Hashmap *watch_pids; /* pid => Unit object n:1 */ @@ -153,11 +154,16 @@ struct Manager { Watch mount_watch; /* Data specific to the D-Bus subsystem */ - DBusConnection *bus; + DBusConnection *bus, *system_bus; Set *subscribed; + + /* Data specific to the cgroup subsystem */ + Hashmap *cgroup_bondings; /* path string => CGroupBonding object 1:n */ + char *cgroup_controller; + char *cgroup_hierarchy; }; -Manager* manager_new(void); +int manager_new(Manager **m); void manager_free(Manager *m); int manager_coldplug(Manager *m); diff --git a/mount-setup.c b/mount-setup.c new file mode 100644 index 0000000000..4a5e2cc760 --- /dev/null +++ b/mount-setup.c @@ -0,0 +1,112 @@ +/*-*- 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" + +enum { + MOUNT_WHAT, + MOUNT_WHERE, + MOUNT_TYPE, + MOUNT_OPTIONS, + MOUNT_SKIP +}; + +static const char *table[] = { + "/proc", "/proc", "proc", "rw", + "/sys", "/sys", "sysfs", "rw", + "cgroup", "/cgroup/debug", "cgroup", "debug", + NULL +}; + +static int 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; + +} + +static int mount_one(const char *t[]) { + int r; + + assert(t); + + if ((r = is_mount_point(t[MOUNT_WHERE])) < 0) + return r; + + if (r > 0) + return 0; + + log_debug("Mounting %s to %s of type %s with options %s.", + t[MOUNT_WHAT], + t[MOUNT_WHERE], + t[MOUNT_TYPE], + t[MOUNT_OPTIONS]); + + if (mount(t[MOUNT_WHAT], + t[MOUNT_WHERE], + t[MOUNT_TYPE], + 0, + t[MOUNT_OPTIONS]) < 0) { + log_error("Failed to mount %s: %s", t[MOUNT_WHERE], strerror(errno)); + return -errno; + } + + return 0; +} + +int mount_setup(void) { + int r; + const char **t; + + for (t = table; *t; t += MOUNT_SKIP) + if ((r = mount_one(t)) < 0) + return r; + + return 0; +} diff --git a/mount-setup.h b/mount-setup.h new file mode 100644 index 0000000000..df768de94f --- /dev/null +++ b/mount-setup.h @@ -0,0 +1,27 @@ +/*-*- 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/>. +***/ + +int mount_setup(void); + +#endif @@ -581,6 +581,12 @@ static int service_init(Unit *u) { return r; } + /* Add default cgroup */ + if ((r = unit_add_default_cgroup(u)) < 0) { + service_done(u); + return r; + } + return 0; } @@ -599,10 +605,12 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { fprintf(f, "%sService State: %s\n" "%sPermissionsStartOnly: %s\n" - "%sRootDirectoryStartOnly: %s\n", + "%sRootDirectoryStartOnly: %s\n" + "%sValidNoProcess: %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->root_directory_start_only), + prefix, yes_no(s->valid_no_process)); if (s->pid_file) fprintf(f, @@ -898,6 +906,7 @@ static int service_spawn( fds, n_fds, apply_permissions, apply_chroot, + UNIT(s)->meta.cgroup_bondings, &pid)) < 0) goto fail; @@ -1336,7 +1345,7 @@ static int main_pid_good(Service *s) { return s->main_pid > 0; /* We don't know the pid */ - return -1; + return -EAGAIN; } static bool control_pid_good(Service *s) { @@ -1345,6 +1354,15 @@ static bool control_pid_good(Service *s) { return s->control_pid > 0; } +static int cgroup_good(Service *s) { + assert(s); + + if (s->valid_no_process) + return -EAGAIN; + + return cgroup_bonding_is_empty_list(UNIT(s)->meta.cgroup_bondings); +} + static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { Service *s = SERVICE(u); bool success; @@ -1477,7 +1495,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { case SERVICE_RELOAD: if (success) { - if (main_pid_good(s) != 0) + if (main_pid_good(s) != 0 && cgroup_good(s) != 0) service_set_state(s, SERVICE_RUNNING); else service_enter_stop(s, true); @@ -1580,6 +1598,33 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { } } +static void service_cgroup_notify_event(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + + log_debug("%s: cgroup is empty", unit_id(u)); + + 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: + + if (!s->valid_no_process && main_pid_good(s) <= 0) + service_enter_stop(s, true); + + break; + + default: + ; + } +} + static int service_enumerate(Manager *m) { static const char * const rcnd[] = { @@ -1753,5 +1798,7 @@ const UnitVTable service_vtable = { .sigchld_event = service_sigchld_event, .timer_event = service_timer_event, + .cgroup_notify_empty = service_cgroup_notify_event, + .enumerate = service_enumerate }; @@ -90,6 +90,7 @@ struct Service { bool permissions_start_only; bool root_directory_start_only; + bool valid_no_process; ServiceState state; @@ -129,6 +129,10 @@ static int socket_init(Unit *u) { if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service))) < 0) goto fail; + /* Add default cgroup */ + if ((r = unit_add_default_cgroup(u)) < 0) + goto fail; + return 0; fail: @@ -394,7 +398,13 @@ static int socket_spawn(Socket *s, ExecCommand *c, bool timeout, pid_t *_pid) { } else unit_unwatch_timer(UNIT(s), &s->timer_watch); - if ((r = exec_spawn(c, &s->exec_context, NULL, 0, true, true, &pid)) < 0) + if ((r = exec_spawn(c, + &s->exec_context, + NULL, 0, + true, + true, + UNIT(s)->meta.cgroup_bondings, + &pid)) < 0) goto fail; if ((r = unit_watch_pid(UNIT(s), pid)) < 0) diff --git a/test-engine.c b/test-engine.c index 5914f5f193..43a68fcdbd 100644 --- a/test-engine.c +++ b/test-engine.c @@ -33,7 +33,7 @@ int main(int argc, char *argv[]) { assert_se(set_unit_path("test2") >= 0); - assert_se(m = manager_new()); + assert_se(manager_new(&m) >= 0); printf("Load1:\n"); assert_se(manager_load_unit(m, "a.service", &a) == 0); diff --git a/test1/exec-demo.service b/test1/exec-demo.service index b772518e3e..5802e37697 100644 --- a/test1/exec-demo.service +++ b/test1/exec-demo.service @@ -2,6 +2,8 @@ Description=Simple Execution Demo [Service] -ExecStart=/bin/cat /etc/hosts -Type=simple +ExecStartPre=/bin/ps -eo pid,uid,args,cgroup +ExecStartPre=/bin/cat /etc/hosts +ExecStart=/bin/bash -c '/bin/sleep 5 &' +Type=forking Output=syslog @@ -260,6 +260,8 @@ void unit_free(Unit *u) { /* Detach from next 'bigger' objects */ + cgroup_bonding_free_list(u->meta.cgroup_bondings); + SET_FOREACH(t, u->meta.names, i) hashmap_remove_value(u->meta.manager->units, t, u); @@ -369,12 +371,12 @@ const char *unit_description(Unit *u) { } void unit_dump(Unit *u, FILE *f, const char *prefix) { - char *t; UnitDependency d; Iterator i; char *p2; const char *prefix2; + CGroupBonding *b; assert(u); @@ -413,6 +415,10 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), unit_id(other)); } + 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); @@ -1060,6 +1066,138 @@ char *unit_dbus_path(Unit *u) { 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; +} + +int unit_add_cgroup_from_text(Unit *u, const char *name) { + size_t n; + const char *p; + char *controller; + CGroupBonding *b; + int r; + + assert(u); + assert(name); + + /* Detect controller name */ + n = strcspn(name, ":/"); + + /* Only controller name, no path? No path? */ + if (name[n] == 0) + return -EINVAL; + + if (n > 0) { + if (name[n] != ':') + return -EINVAL; + + p = name+n+1; + } else + p = name; + + /* Insist in absolute paths */ + if (p[0] != '/') + return -EINVAL; + + if (!(controller = strndup(name, n))) + return -ENOMEM; + + if (cgroup_bonding_find_list(u->meta.cgroup_bondings, controller)) { + free(controller); + return -EEXIST; + } + + if (!(b = new0(CGroupBonding, 1))) { + free(controller); + return -ENOMEM; + } + + b->controller = controller; + + if (!(b->path = strdup(p))) { + r = -ENOMEM; + goto fail; + } + + b->only_us = false; + b->clean_up = false; + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(b->path); + free(b->controller); + free(b); + + return r; +} + +int unit_add_default_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 (asprintf(&b->path, "%s/%s", u->meta.manager->cgroup_hierarchy, unit_id(u)) < 0) + 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); +} + static const char* const unit_type_table[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = "service", [UNIT_TIMER] = "timer", @@ -112,6 +112,7 @@ enum UnitDependency { #include "manager.h" #include "job.h" +#include "cgroup.h" struct Meta { Manager *manager; @@ -143,6 +144,9 @@ struct Meta { usec_t active_enter_timestamp; usec_t active_exit_timestamp; + /* Counterparts in the cgroup filesystem */ + CGroupBonding *cgroup_bondings; + /* Load queue */ LIST_FIELDS(Meta, load_queue); @@ -197,6 +201,8 @@ struct UnitVTable { void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w); + void (*cgroup_notify_empty)(Unit *u); + /* 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 @@ -244,6 +250,11 @@ int unit_add_name(Unit *u, const char *name); int unit_add_dependency(Unit *u, UnitDependency d, Unit *other); int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name); +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); @@ -585,7 +585,7 @@ int reset_all_signal_handlers(void) { return -errno; } - return 0; + return 0; } char *strstrip(char *s) { |