chiark / gitweb /
cgroup: the "tasks" attribute is obsolete, cgroup.procs is the new replacement
[elogind.git] / src / shared / cgroup-util.c
index ad677d42622a2d563ec5f6baf9bc4b90387d5c93..7af0c3c1243c5ddf5d3977ef4bfd70ea37771eb4 100644 (file)
@@ -6,16 +6,16 @@
   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
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
   (at your option) any later version.
 
   systemd is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  General Public License for more details.
+  Lesser General Public License for more details.
 
-  You should have received a copy of the GNU General Public License
+  You should have received a copy of the GNU Lesser General Public License
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #include "set.h"
 #include "macro.h"
 #include "util.h"
+#include "path-util.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "fileio.h"
 
 int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) {
-        char *fs;
-        int r;
+        _cleanup_free_ char *fs = NULL;
         FILE *f;
-
-        assert(controller);
-        assert(path);
-        assert(_f);
-
-        if ((r = cg_get_path(controller, path, "cgroup.procs", &fs)) < 0)
-                return r;
-
-        f = fopen(fs, "re");
-        free(fs);
-
-        if (!f)
-                return -errno;
-
-        *_f = f;
-        return 0;
-}
-
-int cg_enumerate_tasks(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, "tasks", &fs)) < 0)
+        r = cg_get_path(controller, path, "cgroup.procs", &fs);
+        if (r < 0)
                 return r;
 
         f = fopen(fs, "re");
-        free(fs);
-
         if (!f)
                 return -errno;
 
@@ -85,6 +64,9 @@ int cg_read_pid(FILE *f, pid_t *_pid) {
         /* Note that the cgroup.procs might contain duplicates! See
          * cgroups.txt for details. */
 
+        assert(f);
+        assert(_pid);
+
         errno = 0;
         if (fscanf(f, "%lu", &ul) != 1) {
 
@@ -102,22 +84,19 @@ int cg_read_pid(FILE *f, pid_t *_pid) {
 }
 
 int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
-        char *fs;
+        _cleanup_free_ char *fs = NULL;
         int r;
         DIR *d;
 
-        assert(controller);
-        assert(path);
         assert(_d);
 
         /* This is not recursive! */
 
-        if ((r = cg_get_path(controller, path, NULL, &fs)) < 0)
+        r = cg_get_path(controller, path, NULL, &fs);
+        if (r < 0)
                 return r;
 
         d = opendir(fs);
-        free(fs);
-
         if (!d)
                 return -errno;
 
@@ -129,9 +108,9 @@ int cg_read_subgroup(DIR *d, char **fn) {
         struct dirent *de;
 
         assert(d);
+        assert(fn);
 
-        errno = 0;
-        while ((de = readdir(d))) {
+        FOREACH_DIRENT(de, d, return -errno) {
                 char *b;
 
                 if (de->d_type != DT_DIR)
@@ -141,21 +120,19 @@ int cg_read_subgroup(DIR *d, char **fn) {
                     streq(de->d_name, ".."))
                         continue;
 
-                if (!(b = strdup(de->d_name)))
+                b = strdup(de->d_name);
+                if (!b)
                         return -ENOMEM;
 
                 *fn = b;
                 return 1;
         }
 
-        if (errno)
-                return -errno;
-
         return 0;
 }
 
 int cg_rmdir(const char *controller, const char *path, bool honour_sticky) {
-        char *p;
+        _cleanup_free_ char *p = NULL;
         int r;
 
         r = cg_get_path(controller, path, NULL, &p);
@@ -163,66 +140,76 @@ int cg_rmdir(const char *controller, const char *path, bool honour_sticky) {
                 return r;
 
         if (honour_sticky) {
-                char *tasks;
+                char *fn;
 
-                /* If the sticky bit is set don't remove the directory */
+                /* If the sticky bit is set on cgroup.procs, don't
+                 * remove the directory */
 
-                tasks = strappend(p, "/tasks");
-                if (!tasks) {
-                        free(p);
+                fn = strappend(p, "/cgroup.procs");
+                if (!fn)
                         return -ENOMEM;
-                }
 
-                r = file_is_priv_sticky(tasks);
-                free(tasks);
+                r = file_is_priv_sticky(fn);
+                free(fn);
 
-                if (r > 0) {
-                        free(p);
+                if (r > 0)
+                        return 0;
+
+                /* Compatibility ... */
+                fn = strappend(p, "/tasks");
+                if (!fn)
+                        return -ENOMEM;
+
+                r = file_is_priv_sticky(fn);
+                free(fn);
+
+                if (r > 0)
                         return 0;
-                }
         }
 
         r = rmdir(p);
-        free(p);
+        if (r < 0 && errno != ENOENT)
+                return -errno;
 
-        return (r < 0 && errno != ENOENT) ? -errno : 0;
+        return 0;
 }
 
 int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) {
+        _cleanup_set_free_ Set *allocated_set = NULL;
         bool done = false;
         int r, ret = 0;
         pid_t my_pid;
-        FILE *f = NULL;
-        Set *allocated_set = NULL;
 
-        assert(controller);
-        assert(path);
         assert(sig >= 0);
 
         /* This goes through the tasks list and kills them all. This
          * is repeated until no further processes are added to the
          * tasks list, to properly handle forking processes */
 
-        if (!s)
-                if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+        if (!s) {
+                s = allocated_set = set_new(trivial_hash_func, trivial_compare_func);
+                if (!s)
                         return -ENOMEM;
+        }
 
         my_pid = getpid();
 
         do {
+                _cleanup_fclose_ FILE *f = NULL;
                 pid_t pid = 0;
                 done = true;
 
-                if ((r = cg_enumerate_processes(controller, path, &f)) < 0) {
+                r = cg_enumerate_processes(controller, path, &f);
+                if (r < 0) {
                         if (ret >= 0 && r != -ENOENT)
-                                ret = r;
+                                return r;
 
-                        goto finish;
+                        return ret;
                 }
 
                 while ((r = cg_read_pid(f, &pid)) > 0) {
 
-                        if (pid == my_pid && ignore_self)
+                        if (ignore_self && pid == my_pid)
                                 continue;
 
                         if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
@@ -243,100 +230,77 @@ int cg_kill(const char *controller, const char *path, int sig, bool sigcont, boo
 
                         done = false;
 
-                        if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) {
+                        r = set_put(s, LONG_TO_PTR(pid));
+                        if (r < 0) {
                                 if (ret >= 0)
-                                        ret = r;
+                                        return r;
 
-                                goto finish;
+                                return ret;
                         }
                 }
 
                 if (r < 0) {
                         if (ret >= 0)
-                                ret = r;
+                                return r;
 
-                        goto finish;
+                        return ret;
                 }
 
-                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);
 
-finish:
-        if (allocated_set)
-                set_free(allocated_set);
-
-        if (f)
-                fclose(f);
-
         return ret;
 }
 
 int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) {
+        _cleanup_set_free_ Set *allocated_set = NULL;
+        _cleanup_closedir_ DIR *d = NULL;
         int r, ret = 0;
-        DIR *d = NULL;
         char *fn;
-        Set *allocated_set = NULL;
 
         assert(path);
-        assert(controller);
         assert(sig >= 0);
 
-        if (!s)
-                if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func)))
+        if (!s) {
+                s = allocated_set = set_new(trivial_hash_func, trivial_compare_func);
+                if (!s)
                         return -ENOMEM;
+        }
 
         ret = cg_kill(controller, path, sig, sigcont, ignore_self, s);
 
-        if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0) {
+        r = cg_enumerate_subgroups(controller, path, &d);
+        if (r < 0) {
                 if (ret >= 0 && r != -ENOENT)
-                        ret = r;
+                        return r;
 
-                goto finish;
+                return ret;
         }
 
         while ((r = cg_read_subgroup(d, &fn)) > 0) {
-                char *p = NULL;
+                _cleanup_free_ char *p = NULL;
 
-                r = asprintf(&p, "%s/%s", path, fn);
+                p = strjoin(path, "/", fn, NULL);
                 free(fn);
-
-                if (r < 0) {
-                        if (ret >= 0)
-                                ret = -ENOMEM;
-
-                        goto finish;
-                }
+                if (!p)
+                        return -ENOMEM;
 
                 r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s);
-                free(p);
-
-                if (r != 0 && ret >= 0)
+                if (ret >= 0 && r != 0)
                         ret = r;
         }
 
-        if (r < 0 && ret >= 0)
+        if (ret >= 0 && r < 0)
                 ret = r;
 
-        if (rem)
-                if ((r = cg_rmdir(controller, path, true)) < 0) {
-                        if (ret >= 0 &&
-                            r != -ENOENT &&
-                            r != -EBUSY)
-                                ret = r;
-                }
-
-finish:
-        if (d)
-                closedir(d);
-
-        if (allocated_set)
-                set_free(allocated_set);
+        if (rem) {
+                r = cg_rmdir(controller, path, true);
+                if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
+                        return r;
+        }
 
         return ret;
 }
@@ -345,7 +309,6 @@ int cg_kill_recursive_and_wait(const char *controller, const char *path, bool re
         unsigned i;
 
         assert(path);
-        assert(controller);
 
         /* This safely kills all processes; first it sends a SIGTERM,
          * then checks 8 times after 200ms whether the group is now
@@ -363,7 +326,8 @@ int cg_kill_recursive_and_wait(const char *controller, const char *path, bool re
                 else
                         sig = 0;
 
-                if ((r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL)) <= 0)
+                r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL);
+                if (r <= 0)
                         return r;
 
                 usleep(200 * USEC_PER_MSEC);
@@ -372,31 +336,34 @@ int cg_kill_recursive_and_wait(const char *controller, const char *path, bool re
         return 0;
 }
 
-int cg_migrate(const char *controller, const char *from, const char *to, bool ignore_self) {
+int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) {
         bool done = false;
-        Set *s;
+        _cleanup_set_free_ Set *s = NULL;
         int r, ret = 0;
         pid_t my_pid;
-        FILE *f = NULL;
 
-        assert(controller);
-        assert(from);
-        assert(to);
+        assert(cfrom);
+        assert(pfrom);
+        assert(cto);
+        assert(pto);
 
-        if (!(s = set_new(trivial_hash_func, trivial_compare_func)))
+        s = set_new(trivial_hash_func, trivial_compare_func);
+        if (!s)
                 return -ENOMEM;
 
         my_pid = getpid();
 
         do {
+                _cleanup_fclose_ FILE *f = NULL;
                 pid_t pid = 0;
                 done = true;
 
-                if ((r = cg_enumerate_tasks(controller, from, &f)) < 0) {
+                r = cg_enumerate_processes(cfrom, pfrom, &f);
+                if (r < 0) {
                         if (ret >= 0 && r != -ENOENT)
-                                ret = r;
+                                return r;
 
-                        goto finish;
+                        return ret;
                 }
 
                 while ((r = cg_read_pid(f, &pid)) > 0) {
@@ -404,13 +371,14 @@ int cg_migrate(const char *controller, const char *from, const char *to, bool ig
                         /* 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)
+                        if (ignore_self && pid == my_pid)
                                 continue;
 
                         if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid))
                                 continue;
 
-                        if ((r = cg_attach(controller, to, pid)) < 0) {
+                        r = cg_attach(cto, pto, pid);
+                        if (r < 0) {
                                 if (ret >= 0 && r != -ESRCH)
                                         ret = r;
                         } else if (ret == 0)
@@ -418,68 +386,59 @@ int cg_migrate(const char *controller, const char *from, const char *to, bool ig
 
                         done = false;
 
-                        if ((r = set_put(s, LONG_TO_PTR(pid))) < 0) {
+                        r = set_put(s, LONG_TO_PTR(pid));
+                        if (r < 0) {
                                 if (ret >= 0)
-                                        ret = r;
+                                        return r;
 
-                                goto finish;
+                                return ret;
                         }
                 }
 
                 if (r < 0) {
                         if (ret >= 0)
-                                ret = r;
+                                return r;
 
-                        goto finish;
+                        return ret;
                 }
-
-                fclose(f);
-                f = NULL;
-
         } while (!done);
 
-finish:
-        set_free(s);
-
-        if (f)
-                fclose(f);
-
         return ret;
 }
 
-int cg_migrate_recursive(const char *controller, const char *from, const char *to, bool ignore_self, bool rem) {
+int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem) {
+        _cleanup_closedir_ DIR *d = NULL;
         int r, ret = 0;
-        DIR *d = NULL;
         char *fn;
 
-        assert(controller);
-        assert(from);
-        assert(to);
+        assert(cfrom);
+        assert(pfrom);
+        assert(cto);
+        assert(pto);
 
-        ret = cg_migrate(controller, from, to, ignore_self);
+        ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self);
 
-        if ((r = cg_enumerate_subgroups(controller, from, &d)) < 0) {
+        r = cg_enumerate_subgroups(cfrom, pfrom, &d);
+        if (r < 0) {
                 if (ret >= 0 && r != -ENOENT)
-                        ret = r;
-                goto finish;
+                        return r;
+
+                return ret;
         }
 
         while ((r = cg_read_subgroup(d, &fn)) > 0) {
-                char *p = NULL;
+                _cleanup_free_ char *p = NULL;
 
-                r = asprintf(&p, "%s/%s", from, fn);
+                p = strjoin(pfrom, "/", fn, NULL);
                 free(fn);
-
-                if (r < 0) {
+                if (!p) {
                         if (ret >= 0)
-                                ret = -ENOMEM;
+                                return -ENOMEM;
 
-                        goto finish;
+                        return ret;
                 }
 
-                r = cg_migrate_recursive(controller, p, to, ignore_self, rem);
-                free(p);
-
+                r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem);
                 if (r != 0 && ret >= 0)
                         ret = r;
         }
@@ -487,29 +446,66 @@ int cg_migrate_recursive(const char *controller, const char *from, const char *t
         if (r < 0 && ret >= 0)
                 ret = r;
 
-        if (rem)
-                if ((r = cg_rmdir(controller, from, true)) < 0) {
-                        if (ret >= 0 &&
-                            r != -ENOENT &&
-                            r != -EBUSY)
-                                ret = r;
-                }
-
-finish:
-        if (d)
-                closedir(d);
+        if (rem) {
+                r = cg_rmdir(cfrom, pfrom, true);
+                if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
+                        return r;
+        }
 
         return ret;
 }
 
+static const char *normalize_controller(const char *controller) {
+
+        assert(controller);
+
+        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                return "systemd";
+        else if (startswith(controller, "name="))
+                return controller + 5;
+        else
+                return controller;
+}
+
+static int join_path(const char *controller, const char *path, const char *suffix, char **fs) {
+        char *t = NULL;
+
+        if (controller) {
+                if (path && suffix)
+                        t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL);
+                else if (path)
+                        t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL);
+                else if (suffix)
+                        t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL);
+                else
+                        t = strappend("/sys/fs/cgroup/", controller);
+        } else {
+                if (path && suffix)
+                        t = strjoin(path, "/", suffix, NULL);
+                else if (path)
+                        t = strdup(path);
+                else
+                        return -EINVAL;
+        }
+
+        if (!t)
+                return -ENOMEM;
+
+        path_kill_slashes(t);
+
+        *fs = t;
+        return 0;
+}
+
 int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) {
         const char *p;
-        char *t;
         static __thread bool good = false;
 
-        assert(controller);
         assert(fs);
 
+        if (controller && !cg_controller_is_valid(controller, true))
+                return -EINVAL;
+
         if (_unlikely_(!good)) {
                 int r;
 
@@ -521,40 +517,45 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch
                 good = true;
         }
 
-        if (isempty(controller))
-                return -EINVAL;
+        p = controller ? normalize_controller(controller) : NULL;
 
-        /* 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. */
+        return join_path(p, path, suffix, fs);
+}
 
-        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
-                t = join("/sys/fs/cgroup/", p, NULL);
+static int check_hierarchy(const char *p) {
+        char *cc;
 
-        if (!t)
-                return -ENOMEM;
+        assert(p);
 
-        path_kill_slashes(t);
+        /* Check if this controller actually really exists */
+        cc = alloca(sizeof("/sys/fs/cgroup/") + strlen(p));
+        strcpy(stpcpy(cc, "/sys/fs/cgroup/"), p);
+        if (access(cc, F_OK) < 0)
+                return -errno;
 
-        *fs = t;
         return 0;
 }
 
+int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) {
+        const char *p;
+        int r;
+
+        assert(fs);
+
+        if (!cg_controller_is_valid(controller, true))
+                return -EINVAL;
+
+        /* Normalize the controller syntax */
+        p = normalize_controller(controller);
+
+        /* Check if this controller actually really exists */
+        r = check_hierarchy(p);
+        if (r < 0)
+                return r;
+
+        return join_path(p, path, suffix, fs);
+}
+
 static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
         char *p;
         bool is_sticky;
@@ -565,6 +566,19 @@ static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct
         if (ftwbuf->level < 1)
                 return 0;
 
+        p = strappend(path, "/cgroup.procs");
+        if (!p) {
+                errno = ENOMEM;
+                return 1;
+        }
+
+        is_sticky = file_is_priv_sticky(p) > 0;
+        free(p);
+
+        if (is_sticky)
+                return 0;
+
+        /* Compatibility */
         p = strappend(path, "/tasks");
         if (!p) {
                 errno = ENOMEM;
@@ -582,10 +596,9 @@ static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct
 }
 
 int cg_trim(const char *controller, const char *path, bool delete_root) {
-        char *fs;
+        _cleanup_free_ char *fs = NULL;
         int r = 0;
 
-        assert(controller);
         assert(path);
 
         r = cg_get_path(controller, path, NULL, &fs);
@@ -593,79 +606,81 @@ int cg_trim(const char *controller, const char *path, bool delete_root) {
                 return r;
 
         errno = 0;
-        if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0)
+        if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0)
                 r = errno ? -errno : -EIO;
 
         if (delete_root) {
                 bool is_sticky;
                 char *p;
 
-                p = strappend(fs, "/tasks");
-                if (!p) {
-                        free(fs);
+                p = strappend(fs, "/cgroup.procs");
+                if (!p)
                         return -ENOMEM;
-                }
 
                 is_sticky = file_is_priv_sticky(p) > 0;
                 free(p);
 
+                if (!is_sticky) {
+                        p = strappend(fs, "/tasks");
+                        if (!p)
+                                return -ENOMEM;
+
+                        is_sticky = file_is_priv_sticky(p) > 0;
+                        free(p);
+                }
+
                 if (!is_sticky)
-                        if (rmdir(fs) < 0 && errno != ENOENT) {
-                                if (r == 0)
-                                        r = -errno;
-                        }
+                        if (rmdir(fs) < 0 && errno != ENOENT && r == 0)
+                                return -errno;
         }
 
-        free(fs);
-
         return r;
 }
 
 int cg_delete(const char *controller, const char *path) {
-        char *parent;
+        _cleanup_free_ char *parent = NULL;
         int r;
 
-        assert(controller);
         assert(path);
 
-        if ((r = parent_of_path(path, &parent)) < 0)
+        r = path_get_parent(path, &parent);
+        if (r < 0)
                 return r;
 
-        r = cg_migrate_recursive(controller, path, parent, false, true);
-        free(parent);
-
+        r = cg_migrate_recursive(controller, path, controller, parent, false, true);
         return r == -ENOENT ? 0 : r;
 }
 
 int cg_attach(const char *controller, const char *path, pid_t pid) {
-        char *fs;
+        _cleanup_free_ char *fs = NULL;
+        char c[DECIMAL_STR_MAX(pid_t) + 2];
         int r;
-        char c[32];
 
-        assert(controller);
         assert(path);
         assert(pid >= 0);
 
-        if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0)
+        r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs);
+        if (r < 0)
                 return r;
 
         if (pid == 0)
                 pid = getpid();
 
         snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid);
-        char_array_0(c);
 
-        r = write_one_line_file(fs, c);
-        free(fs);
-
-        return r;
+        return write_string_file(fs, c);
 }
 
-int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) {
-        char *fs;
+int cg_set_group_access(
+                const char *controller,
+                const char *path,
+                mode_t mode,
+                uid_t uid,
+                gid_t gid) {
+
+        _cleanup_free_ char *fs = NULL;
         int r;
 
-        assert(controller);
         assert(path);
 
         if (mode != (mode_t) -1)
@@ -675,17 +690,20 @@ int cg_set_group_access(const char *controller, const char *path, mode_t mode, u
         if (r < 0)
                 return r;
 
-        r = chmod_and_chown(fs, mode, uid, gid);
-        free(fs);
-
-        return r;
+        return chmod_and_chown(fs, mode, uid, gid);
 }
 
-int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky) {
-        char *fs;
+int cg_set_task_access(
+                const char *controller,
+                const char *path,
+                mode_t mode,
+                uid_t uid,
+                gid_t gid,
+                int sticky) {
+
+        _cleanup_free_ char *fs = NULL, *procs = NULL;
         int r;
 
-        assert(controller);
         assert(path);
 
         if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0)
@@ -694,7 +712,7 @@ int cg_set_task_access(const char *controller, const char *path, mode_t mode, ui
         if (mode != (mode_t) -1)
                 mode &= 0666;
 
-        r = cg_get_path(controller, path, "tasks", &fs);
+        r = cg_get_path(controller, path, "cgroup.procs", &fs);
         if (r < 0)
                 return r;
 
@@ -709,10 +727,8 @@ int cg_set_task_access(const char *controller, const char *path, mode_t mode, ui
                  * mode from the file itself */
 
                 r = lstat(fs, &st);
-                if (r < 0) {
-                        free(fs);
+                if (r < 0)
                         return -errno;
-                }
 
                 if (mode == (mode_t) -1)
                         /* No mode set, we just shall set the sticky bit */
@@ -723,224 +739,237 @@ int cg_set_task_access(const char *controller, const char *path, mode_t mode, ui
         }
 
         r = chmod_and_chown(fs, mode, uid, gid);
-        free(fs);
+        if (r < 0)
+                return r;
 
-        return r;
+        /* Compatibility, Always keep values for "tasks" in sync with
+         * "cgroup.procs" */
+        r = cg_get_path(controller, path, "tasks", &procs);
+        if (r < 0)
+                return r;
+
+        return chmod_and_chown(procs, mode, uid, gid);
 }
 
-int cg_get_by_pid(const char *controller, pid_t pid, char **path) {
-        int r;
-        char *p = NULL;
-        FILE *f;
-        char *fs;
+int cg_pid_get_path(const char *controller, pid_t pid, char **path) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char line[LINE_MAX];
+        const char *fs;
         size_t cs;
 
-        assert(controller);
         assert(path);
         assert(pid >= 0);
 
-        if (pid == 0)
-                pid = getpid();
+        if (controller) {
+                if (!cg_controller_is_valid(controller, true))
+                        return -EINVAL;
 
-        if (asprintf(&fs, "/proc/%lu/cgroup", (unsigned long) pid) < 0)
-                return -ENOMEM;
+                controller = normalize_controller(controller);
+        } else
+                controller = SYSTEMD_CGROUP_CONTROLLER;
 
-        f = fopen(fs, "re");
-        free(fs);
+        if (pid == 0)
+                fs = "/proc/self/cgroup";
+        else
+                fs = procfs_file_alloca(pid, "cgroup");
 
+        f = fopen(fs, "re");
         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;
-                }
+        FOREACH_LINE(line, f, return -errno) {
+                char *l, *p, *w, *e;
+                size_t k;
+                char *state;
+                bool found = false;
 
                 truncate_nl(line);
 
-                if (!(l = strchr(line, ':')))
+                l = strchr(line, ':');
+                if (!l)
                         continue;
 
                 l++;
-                if (strncmp(l, controller, cs) != 0)
+                e = strchr(l, ':');
+                if (!e)
                         continue;
 
-                if (l[cs] != ':')
-                        continue;
+                *e = 0;
+
+                FOREACH_WORD_SEPARATOR(w, k, l, ",", state) {
+
+                        if (k == cs && memcmp(w, controller, cs) == 0) {
+                                found = true;
+                                break;
+                        }
 
-                if (!(p = strdup(l + cs + 1))) {
-                        r = -ENOMEM;
-                        goto finish;
+                        if (k == 5 + cs &&
+                            memcmp(w, "name=", 5) == 0 &&
+                            memcmp(w+5, controller, cs) == 0) {
+                                found = true;
+                                break;
+                        }
                 }
 
-                *path = p;
-                r = 0;
-                goto finish;
-        }
+                if (!found)
+                        continue;
 
-        r = -ENOENT;
+                p = strdup(e + 1);
+                if (!p)
+                        return -ENOMEM;
 
-finish:
-        fclose(f);
+                *path = p;
+                return 0;
+        }
 
-        return r;
+        return -ENOENT;
 }
 
 int cg_install_release_agent(const char *controller, const char *agent) {
-        char *fs = NULL, *contents = NULL, *line = NULL, *sc;
+        _cleanup_free_ char *fs = NULL, *contents = NULL;
+        char *sc;
         int r;
 
-        assert(controller);
         assert(agent);
 
-        if ((r = cg_get_path(controller, NULL, "release_agent", &fs)) < 0)
+        r = cg_get_path(controller, NULL, "release_agent", &fs);
+        if (r < 0)
                 return r;
 
-        if ((r = read_one_line_file(fs, &contents)) < 0)
-                goto finish;
+        r = read_one_line_file(fs, &contents);
+        if (r < 0)
+                return r;
 
         sc = strstrip(contents);
         if (sc[0] == 0) {
-
-                if (asprintf(&line, "%s\n", agent) < 0) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
-
-                if ((r = write_one_line_file(fs, line)) < 0)
-                        goto finish;
-
-        } else if (!streq(sc, agent)) {
-                r = -EEXIST;
-                goto finish;
-        }
+                r = write_string_file(fs, agent);
+                if (r < 0)
+                        return r;
+        } else if (!streq(sc, agent))
+                return -EEXIST;
 
         free(fs);
         fs = NULL;
-        if ((r = cg_get_path(controller, NULL, "notify_on_release", &fs)) < 0)
-                goto finish;
+        r = cg_get_path(controller, NULL, "notify_on_release", &fs);
+        if (r < 0)
+                return r;
 
         free(contents);
         contents = NULL;
-        if ((r = read_one_line_file(fs, &contents)) < 0)
-                goto finish;
+        r = read_one_line_file(fs, &contents);
+        if (r < 0)
+                return r;
 
         sc = strstrip(contents);
-
         if (streq(sc, "0")) {
-                if ((r = write_one_line_file(fs, "1\n")) < 0)
-                        goto finish;
+                r = write_string_file(fs, "1");
+                if (r < 0)
+                        return r;
 
-                r = 1;
-        } else if (!streq(sc, "1")) {
-                r = -EIO;
-                goto finish;
-        } else
-                r = 0;
+                return 1;
+        }
 
-finish:
-        free(fs);
-        free(contents);
-        free(line);
+        if (!streq(sc, "1"))
+                return -EIO;
 
-        return r;
+        return 0;
 }
 
 int cg_is_empty(const char *controller, const char *path, bool ignore_self) {
-        pid_t pid = 0;
-        int r;
-        FILE *f = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        pid_t pid = 0, self_pid;
         bool found = false;
+        int r;
 
-        assert(controller);
         assert(path);
 
-        if ((r = cg_enumerate_tasks(controller, path, &f)) < 0)
+        r = cg_enumerate_processes(controller, path, &f);
+        if (r < 0)
                 return r == -ENOENT ? 1 : r;
 
+        self_pid = getpid();
+
         while ((r = cg_read_pid(f, &pid)) > 0) {
 
-                if (ignore_self && pid == getpid())
+                if (ignore_self && pid == self_pid)
                         continue;
 
                 found = true;
                 break;
         }
 
-        fclose(f);
-
         if (r < 0)
                 return r;
 
         return !found;
 }
 
-int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) {
+int cg_is_empty_by_spec(const char *spec, bool ignore_self) {
+        _cleanup_free_ char *controller = NULL, *path = NULL;
         int r;
-        DIR *d = NULL;
+
+        assert(spec);
+
+        r = cg_split_spec(spec, &controller, &path);
+        if (r < 0)
+                return r;
+
+        return cg_is_empty(controller, path, ignore_self);
+}
+
+int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) {
+        _cleanup_closedir_ DIR *d = NULL;
         char *fn;
+        int r;
 
-        assert(controller);
         assert(path);
 
-        if ((r = cg_is_empty(controller, path, ignore_self)) <= 0)
+        r = cg_is_empty(controller, path, ignore_self);
+        if (r <= 0)
                 return r;
 
-        if ((r = cg_enumerate_subgroups(controller, path, &d)) < 0)
+        r = cg_enumerate_subgroups(controller, path, &d);
+        if (r < 0)
                 return r == -ENOENT ? 1 : r;
 
         while ((r = cg_read_subgroup(d, &fn)) > 0) {
-                char *p = NULL;
+                _cleanup_free_ char *p = NULL;
 
-                r = asprintf(&p, "%s/%s", path, fn);
+                p = strjoin(path, "/", fn, NULL);
                 free(fn);
-
-                if (r < 0) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                if (!p)
+                        return -ENOMEM;
 
                 r = cg_is_empty_recursive(controller, p, ignore_self);
-                free(p);
-
                 if (r <= 0)
-                        goto finish;
+                        return r;
         }
 
-        if (r >= 0)
-                r = 1;
-
-finish:
-
-        if (d)
-                closedir(d);
+        if (r < 0)
+                return r;
 
-        return r;
+        return 1;
 }
 
 int cg_split_spec(const char *spec, char **controller, char **path) {
         const char *e;
         char *t = NULL, *u = NULL;
+        _cleanup_free_ char *v = NULL;
 
         assert(spec);
-        assert(controller || path);
 
         if (*spec == '/') {
+                if (!path_is_safe(spec))
+                        return -EINVAL;
 
                 if (path) {
-                        if (!(t = strdup(spec)))
+                        t = strdup(spec);
+                        if (!t)
                                 return -ENOMEM;
 
+                        path_kill_slashes(t);
                         *path = t;
                 }
 
@@ -950,13 +979,14 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
                 return 0;
         }
 
-        if (!(e = strchr(spec, ':'))) {
-
-                if (strchr(spec, '/') || spec[0] == 0)
+        e = strchr(spec, ':');
+        if (!e) {
+                if (!cg_controller_is_valid(spec, true))
                         return -EINVAL;
 
                 if (controller) {
-                        if (!(t = strdup(spec)))
+                        t = strdup(normalize_controller(spec));
+                        if (!t)
                                 return -ENOMEM;
 
                         *controller = t;
@@ -968,78 +998,151 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
                 return 0;
         }
 
-        if (e[1] != '/' ||
-            e == spec ||
-            memchr(spec, '/', e-spec))
+        v = strndup(spec, e-spec);
+        if (!v)
+                return -ENOMEM;
+        t = strdup(normalize_controller(v));
+        if (!t)
+                return -ENOMEM;
+        if (!cg_controller_is_valid(t, true)) {
+                free(t);
                 return -EINVAL;
+        }
 
-        if (controller)
-                if (!(t = strndup(spec, e-spec)))
-                        return -ENOMEM;
+        u = strdup(e+1);
+        if (!u) {
+                free(t);
+                return -ENOMEM;
+        }
+        if (!path_is_safe(u) ||
+            !path_is_absolute(u)) {
+                free(t);
+                free(u);
+                return -EINVAL;
+        }
 
-        if (path)
-                if (!(u = strdup(e+1))) {
-                        free(t);
-                        return -ENOMEM;
-                }
+        path_kill_slashes(u);
 
         if (controller)
                 *controller = t;
+        else
+                free(t);
 
         if (path)
                 *path = u;
+        else
+                free(u);
 
         return 0;
 }
 
 int cg_join_spec(const char *controller, const char *path, char **spec) {
-        assert(controller);
+        char *s;
+
         assert(path);
 
-        if (!path_is_absolute(path) ||
-            controller[0] == 0 ||
-            strchr(controller, ':') ||
-            strchr(controller, '/'))
+        if (!controller)
+                controller = "systemd";
+        else {
+                if (!cg_controller_is_valid(controller, true))
+                        return -EINVAL;
+
+                controller = normalize_controller(controller);
+        }
+
+        if (!path_is_absolute(path))
                 return -EINVAL;
 
-        if (asprintf(spec, "%s:%s", controller, path) < 0)
+        s = strjoin(controller, ":", path, NULL);
+        if (!s)
                 return -ENOMEM;
 
+        path_kill_slashes(s + strlen(controller) + 1);
+
+        *spec = s;
         return 0;
 }
 
-int cg_fix_path(const char *path, char **result) {
-        char *t, *c, *p;
+int cg_mangle_path(const char *path, char **result) {
+        _cleanup_free_ char *c = NULL, *p = NULL;
+        char *t;
         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 (path_startswith(path, "/sys/fs/cgroup")) {
 
-                if (!(t = strdup(path)))
+                t = strdup(path);
+                if (!t)
                         return -ENOMEM;
 
+                path_kill_slashes(t);
                 *result = t;
                 return 0;
         }
 
         /* Otherwise treat it as cg spec */
-        if ((r = cg_split_spec(path, &c, &p)) < 0)
+        r = cg_split_spec(path, &c, &p);
+        if (r < 0)
                 return r;
 
-        r = cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
-        free(c);
-        free(p);
+        return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
+}
 
-        return r;
+int cg_get_system_path(char **path) {
+        char *p;
+        int r;
+
+        assert(path);
+
+        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
+        if (r < 0) {
+                p = strdup("/system");
+                if (!p)
+                        return -ENOMEM;
+        }
+
+        if (endswith(p, "/system"))
+                *path = p;
+        else {
+                char *q;
+
+                q = strappend(p, "/system");
+                free(p);
+                if (!q)
+                        return -ENOMEM;
+
+                *path = q;
+        }
+
+        return 0;
+}
+
+int cg_get_root_path(char **path) {
+        char *root, *e;
+        int r;
+
+        assert(path);
+
+        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &root);
+        if (r < 0)
+                return r;
+
+        e = endswith(root, "/system");
+        if (e == root)
+                e[1] = 0;
+        else if (e)
+                *e = 0;
+
+        *path = root;
+        return 0;
 }
 
 int cg_get_user_path(char **path) {
-        char *root, *p;
+        _cleanup_free_ char *root = NULL;
+        char *p;
 
         assert(path);
 
@@ -1047,21 +1150,470 @@ int cg_get_user_path(char **path) {
          * same as PID 1 has but with the "/system" suffix replaced by
          * "/user" */
 
-        if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root) < 0)
+        if (cg_get_root_path(&root) < 0 || streq(root, "/"))
                 p = strdup("/user");
-        else {
-                if (endswith(root, "/system"))
-                        root[strlen(root) - 7] = 0;
-                else if (streq(root, "/"))
-                        root[0] = 0;
-
+        else
                 p = strappend(root, "/user");
-                free(root);
+
+        if (!p)
+                return -ENOMEM;
+
+        *path = p;
+        return 0;
+}
+
+int cg_get_machine_path(const char *machine, char **path) {
+        _cleanup_free_ char *root = NULL, *escaped = NULL;
+        char *p;
+
+        assert(path);
+
+        if (machine) {
+                const char *name = strappenda(machine, ".nspawn");
+
+                escaped = cg_escape(name);
+                if (!escaped)
+                        return -ENOMEM;
         }
 
+        p = strjoin(cg_get_root_path(&root) >= 0 && !streq(root, "/") ? root : "",
+                    "/machine", machine ? "/" : "", machine ? escaped : "", NULL);
         if (!p)
                 return -ENOMEM;
 
         *path = p;
         return 0;
 }
+
+char **cg_shorten_controllers(char **controllers) {
+        char **f, **t;
+
+        if (!controllers)
+                return controllers;
+
+        for (f = controllers, t = controllers; *f; f++) {
+                const char *p;
+                int r;
+
+                p = normalize_controller(*f);
+
+                if (streq(p, "systemd")) {
+                        free(*f);
+                        continue;
+                }
+
+                if (!cg_controller_is_valid(p, true)) {
+                        log_warning("Controller %s is not valid, removing from controllers list.", p);
+                        free(*f);
+                        continue;
+                }
+
+                r = check_hierarchy(p);
+                if (r < 0) {
+                        log_debug("Controller %s is not available, removing from controllers list.", p);
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return strv_uniq(controllers);
+}
+
+int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup) {
+        _cleanup_free_ char *cg_root = NULL;
+        char *cg_process, *p;
+        int r;
+
+        r = cg_get_root_path(&cg_root);
+        if (r < 0)
+                return r;
+
+        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cg_process);
+        if (r < 0)
+                return r;
+
+        p = path_startswith(cg_process, cg_root);
+        if (p)
+                p--;
+        else
+                p = cg_process;
+
+        if (cgroup) {
+                char* c;
+
+                c = strdup(p);
+                if (!c) {
+                        free(cg_process);
+                        return -ENOMEM;
+                }
+
+                *cgroup = c;
+        }
+
+        if (root) {
+                cg_process[p-cg_process] = 0;
+                *root = cg_process;
+        } else
+                free(cg_process);
+
+        return 0;
+}
+
+int cg_path_decode_unit(const char *cgroup, char **unit){
+        char *p, *e, *c, *s, *k;
+
+        assert(cgroup);
+        assert(unit);
+
+        e = strchrnul(cgroup, '/');
+        c = strndupa(cgroup, e - cgroup);
+        c = cg_unescape(c);
+
+        /* Could this be a valid unit name? */
+        if (!unit_name_is_valid(c, true))
+                return -EINVAL;
+
+        if (!unit_name_is_template(c))
+                s = strdup(c);
+        else {
+                if (*e != '/')
+                        return -EINVAL;
+
+                e += strspn(e, "/");
+
+                p = strchrnul(e, '/');
+                k = strndupa(e, p - e);
+                k = cg_unescape(k);
+
+                if (!unit_name_is_valid(k, false))
+                        return -EINVAL;
+
+                s = strdup(k);
+        }
+
+        if (!s)
+                return -ENOMEM;
+
+        *unit = s;
+        return 0;
+}
+
+int cg_path_get_unit(const char *path, char **unit) {
+        const char *e;
+
+        assert(path);
+        assert(unit);
+
+        e = path_startswith(path, "/system/");
+        if (!e)
+                return -ENOENT;
+
+        return cg_path_decode_unit(e, unit);
+}
+
+int cg_pid_get_unit(pid_t pid, char **unit) {
+        _cleanup_free_ char *cgroup = NULL;
+        int r;
+
+        assert(unit);
+
+        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        return cg_path_get_unit(cgroup, unit);
+}
+
+_pure_ static const char *skip_label(const char *e) {
+        assert(e);
+
+        e = strchr(e, '/');
+        if (!e)
+                return NULL;
+
+        e += strspn(e, "/");
+        return e;
+}
+
+int cg_path_get_user_unit(const char *path, char **unit) {
+        const char *e;
+
+        assert(path);
+        assert(unit);
+
+        /* We always have to parse the path from the beginning as unit
+         * cgroups might have arbitrary child cgroups and we shouldn't get
+         * confused by those */
+
+        e = path_startswith(path, "/user/");
+        if (!e)
+                return -ENOENT;
+
+        /* Skip the user name */
+        e = skip_label(e);
+        if (!e)
+                return -ENOENT;
+
+        /* Skip the session ID */
+        e = skip_label(e);
+        if (!e)
+                return -ENOENT;
+
+        /* Skip the systemd cgroup */
+        e = skip_label(e);
+        if (!e)
+                return -ENOENT;
+
+        return cg_path_decode_unit(e, unit);
+}
+
+int cg_pid_get_user_unit(pid_t pid, char **unit) {
+        _cleanup_free_ char *cgroup = NULL;
+        int r;
+
+        assert(unit);
+
+        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        return cg_path_get_user_unit(cgroup, unit);
+}
+
+int cg_path_get_machine_name(const char *path, char **machine) {
+        const char *e, *n;
+        char *s, *r;
+
+        assert(path);
+        assert(machine);
+
+        e = path_startswith(path, "/machine/");
+        if (!e)
+                return -ENOENT;
+
+        n = strchrnul(e, '/');
+        if (e == n)
+                return -ENOENT;
+
+        s = strndupa(e, n - e);
+
+        r = strdup(cg_unescape(s));
+        if (!r)
+                return -ENOMEM;
+
+        *machine = r;
+        return 0;
+}
+
+int cg_pid_get_machine_name(pid_t pid, char **machine) {
+        _cleanup_free_ char *cgroup = NULL;
+        int r;
+
+        assert(machine);
+
+        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        return cg_path_get_machine_name(cgroup, machine);
+}
+
+int cg_path_get_session(const char *path, char **session) {
+        const char *e, *n;
+        char *s;
+
+        assert(path);
+        assert(session);
+
+        e = path_startswith(path, "/user/");
+        if (!e)
+                return -ENOENT;
+
+        /* Skip the user name */
+        e = skip_label(e);
+        if (!e)
+                return -ENOENT;
+
+        n = strchrnul(e, '/');
+        if (n - e < 8)
+                return -ENOENT;
+        if (memcmp(n - 8, ".session", 8) != 0)
+                return -ENOENT;
+
+        s = strndup(e, n - e - 8);
+        if (!s)
+                return -ENOMEM;
+
+        *session = s;
+        return 0;
+}
+
+int cg_pid_get_session(pid_t pid, char **session) {
+        _cleanup_free_ char *cgroup = NULL;
+        int r;
+
+        assert(session);
+
+        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        return cg_path_get_session(cgroup, session);
+}
+
+int cg_path_get_owner_uid(const char *path, uid_t *uid) {
+        const char *e, *n;
+        char *s;
+
+        assert(path);
+        assert(uid);
+
+        e = path_startswith(path, "/user/");
+        if (!e)
+                return -ENOENT;
+
+        n = strchrnul(e, '/');
+        if (n - e < 5)
+                return -ENOENT;
+        if (memcmp(n - 5, ".user", 5) != 0)
+                return -ENOENT;
+
+        s = strndupa(e, n - e - 5);
+        if (!s)
+                return -ENOMEM;
+
+        return parse_uid(s, uid);
+}
+
+int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) {
+        _cleanup_free_ char *cgroup = NULL;
+        int r;
+
+        assert(uid);
+
+        r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+        if (r < 0)
+                return r;
+
+        return cg_path_get_owner_uid(cgroup, uid);
+}
+
+int cg_controller_from_attr(const char *attr, char **controller) {
+        const char *dot;
+        char *c;
+
+        assert(attr);
+        assert(controller);
+
+        if (!filename_is_safe(attr))
+                return -EINVAL;
+
+        dot = strchr(attr, '.');
+        if (!dot) {
+                *controller = NULL;
+                return 0;
+        }
+
+        c = strndup(attr, dot - attr);
+        if (!c)
+                return -ENOMEM;
+
+        if (!cg_controller_is_valid(c, false)) {
+                free(c);
+                return -EINVAL;
+        }
+
+        *controller = c;
+        return 1;
+}
+
+char *cg_escape(const char *p) {
+        bool need_prefix = false;
+
+        /* This implements very minimal escaping for names to be used
+         * as file names in the cgroup tree: any name which might
+         * conflict with a kernel name or is prefixed with '_' is
+         * prefixed with a '_'. That way, when reading cgroup names it
+         * is sufficient to remove a single prefixing underscore if
+         * there is one. */
+
+        /* The return value of this function (unlike cg_unescape())
+         * needs free()! */
+
+        if (p[0] == 0 ||
+            p[0] == '_' ||
+            p[0] == '.' ||
+            streq(p, "notify_on_release") ||
+            streq(p, "release_agent") ||
+            streq(p, "tasks"))
+                need_prefix = true;
+        else {
+                const char *dot;
+
+                dot = strrchr(p, '.');
+                if (dot) {
+
+                        if (dot - p == 6 && memcmp(p, "cgroup", 6) == 0)
+                                need_prefix = true;
+                        else {
+                                char *n;
+
+                                n = strndupa(p, dot - p);
+
+                                if (check_hierarchy(n) >= 0)
+                                        need_prefix = true;
+                        }
+                }
+        }
+
+        if (need_prefix)
+                return strappend("_", p);
+        else
+                return strdup(p);
+}
+
+char *cg_unescape(const char *p) {
+        assert(p);
+
+        /* The return value of this function (unlike cg_escape())
+         * doesn't need free()! */
+
+        if (p[0] == '_')
+                return (char*) p+1;
+
+        return (char*) p;
+}
+
+#define CONTROLLER_VALID                        \
+        "0123456789"                            \
+        "abcdefghijklmnopqrstuvwxyz"            \
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"            \
+        "_"
+
+bool cg_controller_is_valid(const char *p, bool allow_named) {
+        const char *t, *s;
+
+        if (!p)
+                return false;
+
+        if (allow_named) {
+                s = startswith(p, "name=");
+                if (s)
+                        p = s;
+        }
+
+        if (*p == 0 || *p == '_')
+                return false;
+
+        for (t = p; *t; t++)
+                if (!strchr(CONTROLLER_VALID, *t))
+                        return false;
+
+        if (t - p > FILENAME_MAX)
+                return false;
+
+        return true;
+}