chiark / gitweb /
util: place a couple of _likely_ around cache TLS vars
[elogind.git] / src / cgroup-util.c
index 002c131f7f4580ab3069ceb5355d799e37afae5c..fd0ac98e28faad081e1b2faa17425b592f2e9ea7 100644 (file)
@@ -1,4 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8 -*-*/
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
 
 /***
   This file is part of systemd.
@@ -24,8 +24,9 @@
 #include <signal.h>
 #include <string.h>
 #include <stdlib.h>
-
-#include <libcgroup.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 #include "cgroup-util.h"
 #include "log.h"
 #include "macro.h"
 #include "util.h"
 
-int cg_translate_error(int error, int _errno) {
+int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) {
+        char *fs;
+        int r;
+        FILE *f;
+
+        assert(controller);
+        assert(path);
+        assert(_f);
+
+        if ((r = cg_get_path(controller, path, "cgroup.procs", &fs)) < 0)
+                return r;
 
-        switch (error) {
+        f = fopen(fs, "re");
+        free(fs);
 
-        case ECGROUPNOTCOMPILED:
-        case ECGROUPNOTMOUNTED:
-        case ECGROUPNOTEXIST:
-        case ECGROUPNOTCREATED:
-                return -ENOENT;
+        if (!f)
+                return -errno;
 
-        case ECGINVAL:
-                return -EINVAL;
+        *_f = f;
+        return 0;
+}
+
+int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) {
+        char *fs;
+        int r;
+        FILE *f;
 
-        case ECGROUPNOTALLOWED:
-                return -EPERM;
+        assert(controller);
+        assert(path);
+        assert(_f);
+
+        if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0)
+                return r;
 
-        case ECGOTHER:
-                return -_errno;
+        f = fopen(fs, "re");
+        free(fs);
+
+        if (!f)
+                return -errno;
+
+        *_f = f;
+        return 0;
+}
+
+int cg_read_pid(FILE *f, pid_t *_pid) {
+        unsigned long ul;
+
+        /* Note that the cgroup.procs might contain duplicates! See
+         * cgroups.txt for details. */
+
+        errno = 0;
+        if (fscanf(f, "%lu", &ul) != 1) {
+
+                if (feof(f))
+                        return 0;
+
+                return errno ? -errno : -EIO;
         }
 
-        return -EIO;
+        if (ul <= 0)
+                return -EIO;
+
+        *_pid = (pid_t) ul;
+        return 1;
 }
 
-static struct cgroup* cg_new(const char *controller, const char *path) {
-        struct cgroup *cgroup;
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
+        char *fs;
+        int r;
+        DIR *d;
 
-        assert(path);
         assert(controller);
+        assert(path);
+        assert(_d);
+
+        /* This is not recursive! */
+
+        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+                return r;
 
-        if (!(cgroup = cgroup_new_cgroup(path)))
-                return NULL;
+        d = opendir(fs);
+        free(fs);
+
+        if (!d)
+                return -errno;
+
+        *_d = d;
+        return 0;
+}
+
+int cg_read_subgroup(DIR *d, char **fn) {
+        struct dirent *de;
+
+        assert(d);
+
+        errno = 0;
+        while ((de = readdir(d))) {
+                char *b;
+
+                if (de->d_type != DT_DIR)
+                        continue;
+
+                if (streq(de->d_name, ".") ||
+                    streq(de->d_name, ".."))
+                        continue;
 
-        if (!cgroup_add_controller(cgroup, controller)) {
-                cgroup_free(&cgroup);
-                return NULL;
+                if (!(b = strdup(de->d_name)))
+                        return -ENOMEM;
+
+                *fn = b;
+                return 1;
         }
 
-        return cgroup;
+        if (errno)
+                return -errno;
+
+        return 0;
 }
 
-int cg_kill(const char *controller, const char *path, int sig, bool ignore_self) {
-        bool killed = false, done = false;
-        Set *s;
-        pid_t my_pid;
+int cg_rmdir(const char *controller, const char *path) {
+        char *p;
+        int r;
+
+        if ((r = cg_get_path(controller, path, NULL, &p)) < 0)
+                return r;
+
+        r = rmdir(p);
+        free(p);
+
+        return r < 0 ? -errno : 0;
+}
+
+int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) {
+        bool done = false;
         int r, ret = 0;
+        pid_t my_pid;
+        FILE *f = NULL;
+        Set *allocated_set = NULL;
 
         assert(controller);
         assert(path);
@@ -87,286 +181,356 @@ int cg_kill(const char *controller, const char *path, int sig, bool ignore_self)
          * is repeated until no further processes are added to the
          * tasks list, to properly handle forking processes */
 
-        if (!(s = set_new(trivial_hash_func, trivial_compare_func)))
-                return -ENOMEM;
+        if (!s)
+                if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+                        return -ENOMEM;
 
         my_pid = getpid();
 
         do {
-                void *iterator = NULL;
                 pid_t pid = 0;
-
                 done = true;
 
-                r = cgroup_get_task_begin(path, controller, &iterator, &pid);
-                while (r == 0) {
+                if ((r = cg_enumerate_processes(controller, path, &f)) < 0) {
+                        if (ret >= 0 && r != -ENOENT)
+                                ret = r;
+
+                        goto finish;
+                }
+
+                while ((r = cg_read_pid(f, &pid)) > 0) {
 
                         if (pid == my_pid && ignore_self)
-                                goto next;
+                                continue;
 
-                        if (set_get(s, INT_TO_PTR(pid)) == INT_TO_PTR(pid))
-                                goto next;
+                        if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
+                                continue;
 
                         /* If we haven't killed this process yet, kill
                          * it */
-
-                        if (kill(pid, sig) < 0 && errno != ESRCH) {
-                                if (ret == 0)
+                        if (kill(pid, sig) < 0) {
+                                if (ret >= 0 && errno != ESRCH)
                                         ret = -errno;
+                        } else if (ret == 0) {
+
+                                if (sigcont)
+                                        kill(pid, SIGCONT);
+
+                                ret = 1;
                         }
 
-                        killed = true;
                         done = false;
 
-                        if ((r = set_put(s, INT_TO_PTR(pid))) < 0)
-                                goto loop_exit;
+                        if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) {
+                                if (ret >= 0)
+                                        ret = r;
 
-                next:
-                        r = cgroup_get_task_next(&iterator, &pid);
+                                goto finish;
+                        }
                 }
 
-                if (r == 0 || r == ECGEOF)
-                        r = 0;
-                else if (r == ECGOTHER && errno == ENOENT)
-                        r = -ESRCH;
-                else
-                        r = cg_translate_error(r, errno);
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = r;
+
+                        goto finish;
+                }
 
-        loop_exit:
-                assert_se(cgroup_get_task_end(&iterator) == 0);
+                fclose(f);
+                f = NULL;
 
                 /* 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);
+        } while (!done);
 
-        set_free(s);
-
-        if (ret < 0)
-                return ret;
+finish:
+        if (allocated_set)
+                set_free(allocated_set);
 
-        if (r < 0)
-                return r;
+        if (f)
+                fclose(f);
 
-        return !!killed;
+        return ret;
 }
 
-int cg_kill_recursive(const char *controller, const char *path, int sig, bool ignore_self) {
-        struct cgroup_file_info info;
-        int level = 0, r, ret = 0;
-        void *iterator = NULL;
-        bool killed = false;
+int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) {
+        int r, ret = 0;
+        DIR *d = NULL;
+        char *fn;
+        Set *allocated_set = NULL;
 
         assert(path);
         assert(controller);
         assert(sig >= 0);
 
-        zero(info);
+        if (!s)
+                if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+                        return -ENOMEM;
+
+        ret = cg_kill(controller, path, sig, sigcont, ignore_self, s);
+
+        if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0) {
+                if (ret >= 0 && r != -ENOENT)
+                        ret = r;
+
+                goto finish;
+        }
+
+        while ((r = cg_read_subgroup(d, &fn)) > 0) {
+                char *p = NULL;
 
-        r = cgroup_walk_tree_begin(controller, path, 0, &iterator, &info, &level);
-        while (r == 0) {
-                int k;
-                char *p;
+                r = asprintf(&p, "%s/%s", path, fn);
+                free(fn);
 
-                if (info.type != CGROUP_FILE_TYPE_DIR)
-                        goto next;
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = -ENOMEM;
 
-                if (asprintf(&p, "%s/%s", path, info.path) < 0) {
-                        ret = -ENOMEM;
-                        break;
+                        goto finish;
                 }
 
-                k = cg_kill(controller, p, sig, ignore_self);
+                r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s);
                 free(p);
 
-                if (k < 0) {
-                        if (ret == 0)
-                                ret = k;
-                } else if (k > 0)
-                        killed = true;
+                if (r != 0 && ret >= 0)
+                        ret = r;
+        }
 
-        next:
+        if (r < 0 && ret >= 0)
+                ret = r;
 
-                r = cgroup_walk_tree_next(0, &iterator, &info, level);
-        }
+        if (rem)
+                if ((r = cg_rmdir(controller, path)) < 0) {
+                        if (ret >= 0 &&
+                            r != -ENOENT &&
+                            r != -EBUSY)
+                                ret = r;
+                }
 
-        if (ret == 0) {
-                if (r == 0 || r == ECGEOF)
-                        ret = !!killed;
-                else if (r == ECGOTHER && errno == ENOENT)
-                        ret = -ESRCH;
-                else
-                        ret = cg_translate_error(r, errno);
-        }
+finish:
+        if (d)
+                closedir(d);
 
-        assert_se(cgroup_walk_tree_end(&iterator) == 0);
+        if (allocated_set)
+                set_free(allocated_set);
 
         return ret;
 }
 
-int cg_kill_recursive_and_wait(const char *controller, const char *path) {
+int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) {
         unsigned i;
 
         assert(path);
         assert(controller);
 
         /* This safely kills all processes; first it sends a SIGTERM,
-         * then checks 8 times after 50ms whether the group is
-         * now empty, and finally kills everything that is left with
-         * SIGKILL */
+         * then checks 8 times after 200ms whether the group is now
+         * empty, then kills everything that is left with SIGKILL and
+         * finally checks 5 times after 200ms each whether the group
+         * is finally empty. */
 
-        for (i = 0; i < 10; i++) {
+        for (i = 0; i < 15; i++) {
                 int sig, r;
 
                 if (i <= 0)
                         sig = SIGTERM;
-                else if (i >= 9)
+                else if (i == 9)
                         sig = SIGKILL;
                 else
                         sig = 0;
 
-                if ((r = cg_kill_recursive(controller, path, sig, true)) <= 0)
+                if ((r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL)) <= 0)
                         return r;
 
-                usleep(50 * USEC_PER_MSEC);
+                usleep(200 * USEC_PER_MSEC);
         }
 
         return 0;
 }
 
 int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self) {
-        bool migrated = false, done = false;
-        struct cgroup *dest;
+        bool done = false;
+        Set *s;
         int r, ret = 0;
         pid_t my_pid;
+        FILE *f = NULL;
 
         assert(controller);
         assert(from);
         assert(to);
 
-        if (!(dest = cg_new(controller, to)))
+        if (!(s = set_new(trivial_hash_func, trivial_compare_func)))
                 return -ENOMEM;
 
         my_pid = getpid();
 
         do {
-                void *iterator = NULL;
                 pid_t pid = 0;
-
                 done = true;
 
-                r = cgroup_get_task_begin(from, controller, &iterator, &pid);
-                while (r == 0) {
+                if ((r = cg_enumerate_tasks(controller, from, &f)) < 0) {
+                        if (ret >= 0 && r != -ENOENT)
+                                ret = r;
+
+                        goto finish;
+                }
+
+                while ((r = cg_read_pid(f, &pid)) > 0) {
 
+                        /* This might do weird stuff if we aren't a
+                         * single-threaded program. However, we
+                         * luckily know we are not */
                         if (pid == my_pid && ignore_self)
-                                goto next;
+                                continue;
 
-                        if ((r = cgroup_attach_task_pid(dest, pid)) != 0) {
-                                if (ret == 0)
-                                        r = cg_translate_error(r, errno);
-                        }
+                        if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
+                                continue;
+
+                        if ((r = cg_attach(controller, to, pid)) < 0) {
+                                if (ret >= 0 && r != -ESRCH)
+                                        ret = r;
+                        } else if (ret == 0)
+                                ret = 1;
 
-                        migrated = true;
                         done = false;
 
-                next:
+                        if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) {
+                                if (ret >= 0)
+                                        ret = r;
 
-                        r = cgroup_get_task_next(&iterator, &pid);
+                                goto finish;
+                        }
                 }
 
-                if (r == 0 || r == ECGEOF)
-                        r = 0;
-                else if (r == ECGOTHER && errno == ENOENT)
-                        r = -ESRCH;
-                else
-                        r = cg_translate_error(r, errno);
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = r;
 
-                assert_se(cgroup_get_task_end(&iterator) == 0);
+                        goto finish;
+                }
 
-        } while (!done && r >= 0);
+                fclose(f);
+                f = NULL;
 
-        cgroup_free(&dest);
+        } while (!done);
 
-        if (ret < 0)
-                return ret;
+finish:
+        set_free(s);
 
-        if (r < 0)
-                return r;
+        if (f)
+                fclose(f);
 
-        return !!migrated;
+        return ret;
 }
 
-int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self) {
-        struct cgroup_file_info info;
-        int level = 0, r, ret = 0;
-        void *iterator = NULL;
-        bool migrated = false;
+int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self, bool rem) {
+        int r, ret = 0;
+        DIR *d = NULL;
+        char *fn;
 
         assert(controller);
         assert(from);
         assert(to);
 
-        zero(info);
+        ret = cg_migrate(controller, from, to, ignore_self);
+
+        if ((r = cg_enumerate_subgroups(controller, from, &d)) < 0) {
+                if (ret >= 0 && r != -ENOENT)
+                        ret = r;
+                goto finish;
+        }
+
+        while ((r = cg_read_subgroup(d, &fn)) > 0) {
+                char *p = NULL;
 
-        r = cgroup_walk_tree_begin(controller, from, 0, &iterator, &info, &level);
-        while (r == 0) {
-                int k;
-                char *p;
+                r = asprintf(&p, "%s/%s", from, fn);
+                free(fn);
 
-                if (info.type != CGROUP_FILE_TYPE_DIR)
-                        goto next;
+                if (r < 0) {
+                        if (ret >= 0)
+                                ret = -ENOMEM;
 
-                if (asprintf(&p, "%s/%s", from, info.path) < 0) {
-                        ret = -ENOMEM;
-                        break;
+                        goto finish;
                 }
 
-                k = cg_migrate(controller, p, to, ignore_self);
+                r = cg_migrate_recursive(controller, p, to, ignore_self, rem);
                 free(p);
 
-                if (k < 0) {
-                        if (ret == 0)
-                                ret = k;
-                } else if (k > 0)
-                        migrated = true;
-
-        next:
-                r = cgroup_walk_tree_next(0, &iterator, &info, level);
+                if (r != 0 && ret >= 0)
+                        ret = r;
         }
 
-        if (ret == 0) {
-                if (r == 0 || r == ECGEOF)
-                        r = !!migrated;
-                else if (r == ECGOTHER && errno == ENOENT)
-                        r = -ESRCH;
-                else
-                        r = cg_translate_error(r, errno);
-        }
+        if (r < 0 && ret >= 0)
+                ret = r;
 
-        assert_se(cgroup_walk_tree_end(&iterator) == 0);
+        if (rem)
+                if ((r = cg_rmdir(controller, from)) < 0) {
+                        if (ret >= 0 &&
+                            r != -ENOENT &&
+                            r != -EBUSY)
+                                ret = r;
+                }
+
+finish:
+        if (d)
+                closedir(d);
 
         return ret;
 }
 
 int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) {
-        char *mp;
-        int r;
+        const char *p;
+        char *t;
+        static __thread bool good = false;
 
         assert(controller);
-        assert(path);
+        assert(fs);
+
+        if (_unlikely_(!good)) {
+                int r;
+
+                r = path_is_mount_point("/sys/fs/cgroup");
+                if (r <= 0)
+                        return r < 0 ? r : -ENOENT;
+
+                /* Cache this to save a few stat()s */
+                good = true;
+        }
+
+        if (isempty(controller))
+                return -EINVAL;
 
-        if ((r = cgroup_get_subsys_mount_point(controller, &mp)) != 0)
-                return cg_translate_error(r, errno);
+        /* This is a very minimal lookup from controller names to
+         * paths. Since we have mounted most hierarchies ourselves
+         * should be kinda safe, but eventually we might want to
+         * extend this to have a fallback to actually check
+         * /proc/mounts. Might need caching then. */
 
-        if (suffix)
-                r = asprintf(fs, "%s/%s/%s", mp, path, suffix);
+        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                p = "systemd";
+        else if (startswith(controller, "name="))
+                p = controller + 5;
+        else
+                p = controller;
+
+        if (path && suffix)
+                t = join("/sys/fs/cgroup/", p, "/", path, "/", suffix, NULL);
+        else if (path)
+                t = join("/sys/fs/cgroup/", p, "/", path, NULL);
+        else if (suffix)
+                t = join("/sys/fs/cgroup/", p, "/", suffix, NULL);
         else
-                r = asprintf(fs, "%s/%s", mp, path);
+                t = join("/sys/fs/cgroup/", p, NULL);
 
-        free(mp);
+        if (!t)
+                return -ENOMEM;
 
-        return r < 0 ? -ENOMEM : 0;
+        path_kill_slashes(t);
+
+        *fs = t;
+        return 0;
 }
 
 int cg_trim(const char *controller, const char *path, bool delete_root) {
@@ -382,110 +546,89 @@ int cg_trim(const char *controller, const char *path, bool delete_root) {
         r = rm_rf(fs, true, delete_root);
         free(fs);
 
-        return r;
+        return r == -ENOENT ? 0 : r;
 }
 
 int cg_delete(const char *controller, const char *path) {
-        struct cgroup *cg;
+        char *parent;
         int r;
 
         assert(controller);
         assert(path);
 
-        if (!(cg = cg_new(controller, path)))
-                return -ENOMEM;
-
-        if ((r = cgroup_delete_cgroup_ext(cg, CGFLAG_DELETE_RECURSIVE|CGFLAG_DELETE_IGNORE_MIGRATION)) != 0) {
-                r = cg_translate_error(r, errno);
-                goto finish;
-        }
-
-        r = 0;
+        if ((r = parent_of_path(path, &parent)) < 0)
+                return r;
 
-finish:
-        cgroup_free(&cg);
+        r = cg_migrate_recursive(controller, path, parent, false, true);
+        free(parent);
 
-        return r;
+        return r == -ENOENT ? 0 : r;
 }
 
 int cg_create(const char *controller, const char *path) {
-        struct cgroup *cg;
+        char *fs;
         int r;
 
         assert(controller);
         assert(path);
 
-        if (!(cg = cg_new(controller, path)))
-                return -ENOMEM;
+        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+                return r;
 
-        if ((r = cgroup_create_cgroup(cg, 1)) != 0) {
-                r = cg_translate_error(r, errno);
-                goto finish;
-        }
+        r = mkdir_parents(fs, 0755);
 
-        r = 0;
+        if (r >= 0) {
+                if (mkdir(fs, 0755) >= 0)
+                        r = 1;
+                else if (errno == EEXIST)
+                        r = 0;
+                else
+                        r = -errno;
+        }
 
-finish:
-        cgroup_free(&cg);
+        free(fs);
 
         return r;
 }
 
 int cg_attach(const char *controller, const char *path, pid_t pid) {
-        struct cgroup *cg;
+        char *fs;
         int r;
+        char c[32];
 
         assert(controller);
         assert(path);
         assert(pid >= 0);
 
-        if (!(cg = cg_new(controller, path)))
-                return -ENOMEM;
+        if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0)
+                return r;
 
         if (pid == 0)
                 pid = getpid();
 
-        if ((r = cgroup_attach_task_pid(cg, pid))) {
-                r = cg_translate_error(r, errno);
-                goto finish;
-        }
-
-        r = 0;
+        snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid);
+        char_array_0(c);
 
-finish:
-        cgroup_free(&cg);
+        r = write_one_line_file(fs, c);
+        free(fs);
 
         return r;
 }
 
 int cg_create_and_attach(const char *controller, const char *path, pid_t pid) {
-        struct cgroup *cg;
-        int r;
+        int r, q;
 
         assert(controller);
         assert(path);
         assert(pid >= 0);
 
-        if (!(cg = cg_new(controller, path)))
-                return -ENOMEM;
-
-        if ((r = cgroup_create_cgroup(cg, 1)) != 0) {
-                r = cg_translate_error(r, errno);
-                goto finish;
-        }
-
-        if (pid == 0)
-                pid = getpid();
-
-        if ((r = cgroup_attach_task_pid(cg, pid))) {
-                r = cg_translate_error(r, errno);
-                goto finish;
-        }
+        if ((r = cg_create(controller, path)) < 0)
+                return r;
 
-        r = 0;
+        if ((q = cg_attach(controller, path, pid)) < 0)
+                return q;
 
-finish:
-        cgroup_free(&cg);
+        /* This does not remove the cgroup on failure */
 
         return r;
 }
@@ -525,40 +668,85 @@ int cg_set_task_access(const char *controller, const char *path, mode_t mode, ui
 int cg_get_by_pid(const char *controller, pid_t pid, char **path) {
         int r;
         char *p = NULL;
+        FILE *f;
+        char *fs;
+        size_t cs;
 
         assert(controller);
-        assert(pid > 0);
         assert(path);
+        assert(pid >= 0);
 
-        if ((r = cgroup_get_current_controller_path(pid, controller, &p)) != 0)
-                return cg_translate_error(r, errno);
+        if (pid == 0)
+                pid = getpid();
 
-        assert(p);
+        if (asprintf(&fs, "/proc/%lu/cgroup", (unsigned long) pid) < 0)
+                return -ENOMEM;
 
-        *path = p;
-        return 0;
+        f = fopen(fs, "re");
+        free(fs);
+
+        if (!f)
+                return errno == ENOENT ? -ESRCH : -errno;
+
+        cs = strlen(controller);
+
+        while (!feof(f)) {
+                char line[LINE_MAX];
+                char *l;
+
+                errno = 0;
+                if (!(fgets(line, sizeof(line), f))) {
+                        if (feof(f))
+                                break;
+
+                        r = errno ? -errno : -EIO;
+                        goto finish;
+                }
+
+                truncate_nl(line);
+
+                if (!(l = strchr(line, ':')))
+                        continue;
+
+                l++;
+                if (strncmp(l, controller, cs) != 0)
+                        continue;
+
+                if (l[cs] != ':')
+                        continue;
+
+                if (!(p = strdup(l + cs + 1))) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                *path = p;
+                r = 0;
+                goto finish;
+        }
+
+        r = -ENOENT;
+
+finish:
+        fclose(f);
+
+        return r;
 }
 
 int cg_install_release_agent(const char *controller, const char *agent) {
-        char *mp = NULL, *path = NULL, *contents = NULL, *line = NULL, *sc;
+        char *fs = NULL, *contents = NULL, *line = NULL, *sc;
         int r;
 
         assert(controller);
         assert(agent);
 
-        if ((r = cgroup_get_subsys_mount_point(controller, &mp)) != 0)
-                return cg_translate_error(r, errno);
-
-        if (asprintf(&path, "%s/release_agent", mp) < 0) {
-                r = -ENOMEM;
-                goto finish;
-        }
+        if ((r = cg_get_path(controller, NULL, "release_agent", &fs)) < 0)
+                return r;
 
-        if ((r = read_one_line_file(path, &contents)) < 0)
+        if ((r = read_one_line_file(fs, &contents)) < 0)
                 goto finish;
 
         sc = strstrip(contents);
-
         if (sc[0] == 0) {
 
                 if (asprintf(&line, "%s\n", agent) < 0) {
@@ -566,7 +754,7 @@ int cg_install_release_agent(const char *controller, const char *agent) {
                         goto finish;
                 }
 
-                if ((r = write_one_line_file(path, line)) < 0)
+                if ((r = write_one_line_file(fs, line)) < 0)
                         goto finish;
 
         } else if (!streq(sc, agent)) {
@@ -574,33 +762,31 @@ int cg_install_release_agent(const char *controller, const char *agent) {
                 goto finish;
         }
 
-        free(path);
-        path = NULL;
-        if (asprintf(&path, "%s/notify_on_release", mp) < 0) {
-                r = -ENOMEM;
+        free(fs);
+        fs = NULL;
+        if ((r = cg_get_path(controller, NULL, "notify_on_release", &fs)) < 0)
                 goto finish;
-        }
 
         free(contents);
         contents = NULL;
-        if ((r = read_one_line_file(path, &contents)) < 0)
+        if ((r = read_one_line_file(fs, &contents)) < 0)
                 goto finish;
 
         sc = strstrip(contents);
 
         if (streq(sc, "0")) {
-                if ((r = write_one_line_file(path, "1\n")) < 0)
+                if ((r = write_one_line_file(fs, "1\n")) < 0)
                         goto finish;
+
+                r = 1;
         } else if (!streq(sc, "1")) {
                 r = -EIO;
                 goto finish;
-        }
-
-        r = 0;
+        } else
+                r = 0;
 
 finish:
-        free(mp);
-        free(path);
+        free(fs);
         free(contents);
         free(line);
 
@@ -608,87 +794,211 @@ finish:
 }
 
 int cg_is_empty(const char *controller, const char *path, bool ignore_self) {
-        void *iterator = NULL;
         pid_t pid = 0;
         int r;
+        FILE *f = NULL;
+        bool found = false;
 
         assert(controller);
         assert(path);
 
-        r = cgroup_get_task_begin(path, controller, &iterator, &pid);
-        while (r == 0) {
+        if ((r = cg_enumerate_tasks(controller, path, &f)) < 0)
+                return r == -ENOENT ? 1 : r;
 
-                if (ignore_self&& pid == getpid())
-                        goto next;
+        while ((r = cg_read_pid(f, &pid)) > 0) {
 
-                break;
+                if (ignore_self && pid == getpid())
+                        continue;
 
-        next:
-                r = cgroup_get_task_next(&iterator, &pid);
+                found = true;
+                break;
         }
 
+        fclose(f);
 
-        if (r == ECGEOF)
-                r = 1;
-        else if (r != 0)
-                r = cg_translate_error(r, errno);
-        else
-                r = 0;
-
-        assert_se(cgroup_get_task_end(&iterator) == 0);
+        if (r < 0)
+                return r;
 
-        return r;
+        return !found;
 }
 
 int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) {
-        struct cgroup_file_info info;
-        int level = 0, r, ret = 0;
-        void *iterator = NULL;
-        bool empty = true;
+        int r;
+        DIR *d = NULL;
+        char *fn;
 
         assert(controller);
         assert(path);
 
-        zero(info);
+        if ((r = cg_is_empty(controller, path, ignore_self)) <= 0)
+                return r;
+
+        if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0)
+                return r == -ENOENT ? 1 : r;
 
-        r = cgroup_walk_tree_begin(controller, path, 0, &iterator, &info, &level);
-        while (r == 0) {
-                int k;
-                char *p;
+        while ((r = cg_read_subgroup(d, &fn)) > 0) {
+                char *p = NULL;
 
-                if (info.type != CGROUP_FILE_TYPE_DIR)
-                        goto next;
+                r = asprintf(&p, "%s/%s", path, fn);
+                free(fn);
 
-                if (asprintf(&p, "%s/%s", path, info.path) < 0) {
-                        ret = -ENOMEM;
-                        break;
+                if (r < 0) {
+                        r = -ENOMEM;
+                        goto finish;
                 }
 
-                k = cg_is_empty(controller, p, ignore_self);
+                r = cg_is_empty_recursive(controller, p, ignore_self);
                 free(p);
 
-                if (k < 0) {
-                        ret = k;
-                        break;
-                } else if (k == 0) {
-                        empty = false;
-                        break;
+                if (r <= 0)
+                        goto finish;
+        }
+
+        if (r >= 0)
+                r = 1;
+
+finish:
+
+        if (d)
+                closedir(d);
+
+        return r;
+}
+
+int cg_split_spec(const char *spec, char **controller, char **path) {
+        const char *e;
+        char *t = NULL, *u = NULL;
+
+        assert(spec);
+        assert(controller || path);
+
+        if (*spec == '/') {
+
+                if (path) {
+                        if (!(t = strdup(spec)))
+                                return -ENOMEM;
+
+                        *path = t;
                 }
 
-        next:
-                r = cgroup_walk_tree_next(0, &iterator, &info, level);
+                if (controller)
+                        *controller = NULL;
+
+                return 0;
         }
 
-        if (ret == 0) {
-                if (r == 0 || r == ECGEOF)
-                        ret = !!empty;
-                else if (r == ECGOTHER && errno == ENOENT)
-                        ret = -ESRCH;
-                else
-                        ret = cg_translate_error(r, errno);
+        if (!(e = strchr(spec, ':'))) {
+
+                if (strchr(spec, '/') || spec[0] == 0)
+                        return -EINVAL;
+
+                if (controller) {
+                        if (!(t = strdup(spec)))
+                                return -ENOMEM;
+
+                        *controller = t;
+                }
+
+                if (path)
+                        *path = NULL;
+
+                return 0;
         }
 
-        assert_se(cgroup_walk_tree_end(&iterator) == 0);
+        if (e[1] != '/' ||
+            e == spec ||
+            memchr(spec, '/', e-spec))
+                return -EINVAL;
 
-        return ret;
+        if (controller)
+                if (!(t = strndup(spec, e-spec)))
+                        return -ENOMEM;
+
+        if (path)
+                if (!(u = strdup(e+1))) {
+                        free(t);
+                        return -ENOMEM;
+                }
+
+        if (controller)
+                *controller = t;
+
+        if (path)
+                *path = u;
+
+        return 0;
+}
+
+int cg_join_spec(const char *controller, const char *path, char **spec) {
+        assert(controller);
+        assert(path);
+
+        if (!path_is_absolute(path) ||
+            controller[0] == 0 ||
+            strchr(controller, ':') ||
+            strchr(controller, '/'))
+                return -EINVAL;
+
+        if (asprintf(spec, "%s:%s", controller, path) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+int cg_fix_path(const char *path, char **result) {
+        char *t, *c, *p;
+        int r;
+
+        assert(path);
+        assert(result);
+
+        /* First check if it already is a filesystem path */
+        if (path_is_absolute(path) &&
+            path_startswith(path, "/sys/fs/cgroup") &&
+            access(path, F_OK) >= 0) {
+
+                if (!(t = strdup(path)))
+                        return -ENOMEM;
+
+                *result = t;
+                return 0;
+        }
+
+        /* Otherwise treat it as cg spec */
+        if ((r = cg_split_spec(path, &c, &p)) < 0)
+                return r;
+
+        r = cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
+        free(c);
+        free(p);
+
+        return r;
+}
+
+int cg_get_user_path(char **path) {
+        char *root, *p;
+
+        assert(path);
+
+        /* Figure out the place to put user cgroups below. We use the
+         * same as PID 1 has but with the "/system" suffix replaced by
+         * "/user" */
+
+        if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root) < 0)
+                p = strdup("/user");
+        else {
+                if (endswith(root, "/system"))
+                        root[strlen(root) - 7] = 0;
+                else if (streq(root, "/"))
+                        root[0] = 0;
+
+                p = strappend(root, "/user");
+                free(root);
+        }
+
+        if (!p)
+                return -ENOMEM;
+
+        *path = p;
+        return 0;
 }