chiark / gitweb /
logind: add infrastructure to keep track of machines, and move to slices
[elogind.git] / src / shared / cgroup-util.c
index 9ec4f40c889ef2b80606abf6bde9f45874dc39e2..05d026a5873e5e0cc8d76db7c5240f339ed4611e 100644 (file)
@@ -38,6 +38,7 @@
 #include "strv.h"
 #include "unit-name.h"
 #include "fileio.h"
+#include "special.h"
 
 int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) {
         _cleanup_free_ char *fs = NULL;
@@ -58,25 +59,6 @@ int cg_enumerate_processes(const char *controller, const char *path, FILE **_f)
         return 0;
 }
 
-int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) {
-        _cleanup_free_ char *fs = NULL;
-        FILE *f;
-        int r;
-
-        assert(_f);
-
-        r = cg_get_path(controller, path, "tasks", &fs);
-        if (r < 0)
-                return r;
-
-        f = fopen(fs, "re");
-        if (!f)
-                return -errno;
-
-        *_f = f;
-        return 0;
-}
-
 int cg_read_pid(FILE *f, pid_t *_pid) {
         unsigned long ul;
 
@@ -159,16 +141,28 @@ 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)
+                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)
+                        return 0;
+
+                /* Compatibility ... */
+                fn = strappend(p, "/tasks");
+                if (!fn)
+                        return -ENOMEM;
+
+                r = file_is_priv_sticky(fn);
+                free(fn);
 
                 if (r > 0)
                         return 0;
@@ -365,7 +359,7 @@ int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char
                 pid_t pid = 0;
                 done = true;
 
-                r = cg_enumerate_tasks(cfrom, pfrom, &f);
+                r = cg_enumerate_processes(cfrom, pfrom, &f);
                 if (r < 0) {
                         if (ret >= 0 && r != -ENOENT)
                                 return r;
@@ -477,19 +471,19 @@ static const char *normalize_controller(const char *controller) {
 static int join_path(const char *controller, const char *path, const char *suffix, char **fs) {
         char *t = NULL;
 
-        if (controller) {
-                if (path && suffix)
+        if (!isempty(controller)) {
+                if (!isempty(path) && !isempty(suffix))
                         t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL);
-                else if (path)
+                else if (!isempty(path))
                         t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL);
-                else if (suffix)
+                else if (!isempty(suffix))
                         t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL);
                 else
                         t = strappend("/sys/fs/cgroup/", controller);
         } else {
-                if (path && suffix)
+                if (!isempty(path) && !isempty(suffix))
                         t = strjoin(path, "/", suffix, NULL);
-                else if (path)
+                else if (!isempty(path))
                         t = strdup(path);
                 else
                         return -EINVAL;
@@ -510,6 +504,9 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch
 
         assert(fs);
 
+        if (controller && !cg_controller_is_valid(controller, true))
+                return -EINVAL;
+
         if (_unlikely_(!good)) {
                 int r;
 
@@ -546,7 +543,7 @@ int cg_get_path_and_check(const char *controller, const char *path, const char *
 
         assert(fs);
 
-        if (isempty(controller))
+        if (!cg_controller_is_valid(controller, true))
                 return -EINVAL;
 
         /* Normalize the controller syntax */
@@ -570,6 +567,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;
@@ -604,13 +614,22 @@ int cg_trim(const char *controller, const char *path, bool delete_root) {
                 bool is_sticky;
                 char *p;
 
-                p = strappend(fs, "/tasks");
+                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 && r == 0)
                                 return -errno;
@@ -641,7 +660,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid) {
         assert(path);
         assert(pid >= 0);
 
-        r = cg_get_path_and_check(controller, path, "tasks", &fs);
+        r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs);
         if (r < 0)
                 return r;
 
@@ -694,7 +713,7 @@ int cg_set_task_access(
         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;
 
@@ -724,8 +743,9 @@ int cg_set_task_access(
         if (r < 0)
                 return r;
 
-        /* Always keep values for "cgroup.procs" in sync with "tasks" */
-        r = cg_get_path(controller, path, "cgroup.procs", &procs);
+        /* Compatibility, Always keep values for "tasks" in sync with
+         * "cgroup.procs" */
+        r = cg_get_path(controller, path, "tasks", &procs);
         if (r < 0)
                 return r;
 
@@ -733,21 +753,27 @@ int cg_set_task_access(
 }
 
 int cg_pid_get_path(const char *controller, pid_t pid, char **path) {
-        char fs[sizeof("/proc/") - 1 + DECIMAL_STR_MAX(pid_t) + sizeof("/cgroup")];
         _cleanup_fclose_ FILE *f = NULL;
         char line[LINE_MAX];
+        const char *fs;
         size_t cs;
 
         assert(path);
         assert(pid >= 0);
 
-        if (!controller)
+        if (controller) {
+                if (!cg_controller_is_valid(controller, true))
+                        return -EINVAL;
+
+                controller = normalize_controller(controller);
+        } else
                 controller = SYSTEMD_CGROUP_CONTROLLER;
 
         if (pid == 0)
-                pid = getpid();
+                fs = "/proc/self/cgroup";
+        else
+                fs = procfs_file_alloca(pid, "cgroup");
 
-        sprintf(fs, "/proc/%lu/cgroup", (unsigned long) pid);
         f = fopen(fs, "re");
         if (!f)
                 return errno == ENOENT ? -ESRCH : -errno;
@@ -755,7 +781,10 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path) {
         cs = strlen(controller);
 
         FOREACH_LINE(line, f, return -errno) {
-                char *l, *p;
+                char *l, *p, *w, *e;
+                size_t k;
+                char *state;
+                bool found = false;
 
                 truncate_nl(line);
 
@@ -764,13 +793,31 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path) {
                         continue;
 
                 l++;
-                if (!strneq(l, controller, cs))
+                e = strchr(l, ':');
+                if (!e)
                         continue;
 
-                if (l[cs] != ':')
+                *e = 0;
+
+                FOREACH_WORD_SEPARATOR(w, k, l, ",", state) {
+
+                        if (k == cs && memcmp(w, controller, cs) == 0) {
+                                found = true;
+                                break;
+                        }
+
+                        if (k == 5 + cs &&
+                            memcmp(w, "name=", 5) == 0 &&
+                            memcmp(w+5, controller, cs) == 0) {
+                                found = true;
+                                break;
+                        }
+                }
+
+                if (!found)
                         continue;
 
-                p = strdup(l + cs + 1);
+                p = strdup(e + 1);
                 if (!p)
                         return -ENOMEM;
 
@@ -839,7 +886,7 @@ int cg_is_empty(const char *controller, const char *path, bool ignore_self) {
 
         assert(path);
 
-        r = cg_enumerate_tasks(controller, path, &f);
+        r = cg_enumerate_processes(controller, path, &f);
         if (r < 0)
                 return r == -ENOENT ? 1 : r;
 
@@ -910,6 +957,7 @@ int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_
 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);
 
@@ -922,6 +970,7 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
                         if (!t)
                                 return -ENOMEM;
 
+                        path_kill_slashes(t);
                         *path = t;
                 }
 
@@ -933,11 +982,11 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
 
         e = strchr(spec, ':');
         if (!e) {
-                if (!filename_is_safe(spec))
+                if (!cg_controller_is_valid(spec, true))
                         return -EINVAL;
 
                 if (controller) {
-                        t = strdup(spec);
+                        t = strdup(normalize_controller(spec));
                         if (!t)
                                 return -ENOMEM;
 
@@ -950,10 +999,13 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
                 return 0;
         }
 
-        t = strndup(spec, e-spec);
+        v = strndup(spec, e-spec);
+        if (!v)
+                return -ENOMEM;
+        t = strdup(normalize_controller(v));
         if (!t)
                 return -ENOMEM;
-        if (!filename_is_safe(t)) {
+        if (!cg_controller_is_valid(t, true)) {
                 free(t);
                 return -EINVAL;
         }
@@ -963,12 +1015,15 @@ int cg_split_spec(const char *spec, char **controller, char **path) {
                 free(t);
                 return -ENOMEM;
         }
-        if (!path_is_safe(u)) {
+        if (!path_is_safe(u) ||
+            !path_is_absolute(u)) {
                 free(t);
                 free(u);
                 return -EINVAL;
         }
 
+        path_kill_slashes(u);
+
         if (controller)
                 *controller = t;
         else
@@ -989,26 +1044,29 @@ int cg_join_spec(const char *controller, const char *path, char **spec) {
 
         if (!controller)
                 controller = "systemd";
-        else if (controller[0] == 0 ||
-                 strchr(controller, ':') ||
-                 strchr(controller, '/'))
-                return -EINVAL;
+        else {
+                if (!cg_controller_is_valid(controller, true))
+                        return -EINVAL;
+
+                controller = normalize_controller(controller);
+        }
 
         if (!path_is_absolute(path))
                 return -EINVAL;
 
-        controller = normalize_controller(controller);
-
         s = strjoin(controller, ":", path, NULL);
         if (!s)
                 return -ENOMEM;
 
+        path_kill_slashes(s + strlen(controller) + 1);
+
         *spec = s;
         return 0;
 }
 
 int cg_mangle_path(const char *path, char **result) {
-        char *t, *c, *p;
+        _cleanup_free_ char *c = NULL, *p = NULL;
+        char *t;
         int r;
 
         assert(path);
@@ -1021,6 +1079,7 @@ int cg_mangle_path(const char *path, char **result) {
                 if (!t)
                         return -ENOMEM;
 
+                path_kill_slashes(t);
                 *result = t;
                 return 0;
         }
@@ -1030,98 +1089,23 @@ int cg_mangle_path(const char *path, char **result) {
         if (r < 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_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;
+        return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
 }
 
 int cg_get_root_path(char **path) {
-        char *root, *e;
+        char *p, *e;
         int r;
 
         assert(path);
 
-        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &root);
+        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
         if (r < 0)
                 return r;
 
-        e = endswith(root, "/system");
-        if (e == root)
-                e[1] = 0;
-        else if (e)
+        e = endswith(p, "/" SPECIAL_SYSTEM_SLICE);
+        if (e)
                 *e = 0;
 
-        *path = root;
-        return 0;
-}
-
-int cg_get_user_path(char **path) {
-        _cleanup_free_ char *root = NULL;
-        char *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_root_path(&root) < 0 || streq(root, "/"))
-                p = strdup("/user");
-        else
-                p = strappend(root, "/user");
-
-        if (!p)
-                return -ENOMEM;
-
-        *path = p;
-        return 0;
-}
-
-int cg_get_machine_path(char **path) {
-        _cleanup_free_ char *root = NULL;
-        char *p;
-
-        assert(path);
-
-        if (cg_get_root_path(&root) < 0 || streq(root, "/"))
-                p = strdup("/machine");
-        else
-                p = strappend(root, "/machine");
-
-        if (!p)
-                return -ENOMEM;
-
         *path = p;
         return 0;
 }
@@ -1138,14 +1122,20 @@ char **cg_shorten_controllers(char **controllers) {
 
                 p = normalize_controller(*f);
 
-                if (streq(*f, "systemd")) {
+                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.", *f);
+                        log_debug("Controller %s is not available, removing from controllers list.", p);
                         free(*f);
                         continue;
                 }
@@ -1198,7 +1188,6 @@ int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup) {
 }
 
 int cg_path_decode_unit(const char *cgroup, char **unit){
-        _cleanup_free_ char *unescaped = NULL;
         char *p, *e, *c, *s, *k;
 
         assert(cgroup);
@@ -1237,15 +1226,29 @@ int cg_path_decode_unit(const char *cgroup, char **unit){
         return 0;
 }
 
+static const char *skip_slices(const char *p) {
+        size_t n;
+
+        /* Skips over all slice assignments */
+
+        for (;;) {
+                p += strspn(p, "/");
+
+                n = strcspn(p, "/");
+                if (n <= 6 || memcmp(p + n - 6, ".slice", 6) != 0)
+                        return p;
+
+                p += n;
+        }
+}
+
 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;
+        e = skip_slices(path);
 
         return cg_path_decode_unit(e, unit);
 }
@@ -1263,15 +1266,55 @@ int cg_pid_get_unit(pid_t pid, char **unit) {
         return cg_path_get_unit(cgroup, unit);
 }
 
-static const char *skip_label(const char *e) {
-        assert(e);
+static const char *skip_user(const char *p) {
+        size_t n;
 
-        e = strchr(e, '/');
-        if (!e)
+        assert(p);
+
+        p += strspn(p, "/");
+
+        n = strcspn(p, "/");
+        if (n <= 5 || memcmp(p + n - 5, ".user", 5) != 0)
+                return p;
+
+        p += n;
+        p += strspn(p, "/");
+
+        return p;
+}
+
+static const char *skip_session(const char *p) {
+        size_t n;
+
+        assert(p);
+
+        p += strspn(p, "/");
+
+        n = strcspn(p, "/");
+        if (n <= 8 || memcmp(p + n - 8, ".session", 8) != 0)
                 return NULL;
 
-        e += strspn(e, "/");
-        return e;
+        p += n;
+        p += strspn(p, "/");
+
+        return p;
+}
+
+static const char *skip_systemd_label(const char *p) {
+        size_t n;
+
+        assert(p);
+
+        p += strspn(p, "/");
+
+        n = strcspn(p, "/");
+        if (n < 8 || memcmp(p, "systemd-", 8) != 0)
+                return p;
+
+        p += n;
+        p += strspn(p, "/");
+
+        return p;
 }
 
 int cg_path_get_user_unit(const char *path, char **unit) {
@@ -1284,24 +1327,19 @@ int cg_path_get_user_unit(const char *path, char **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 slices, if there are any */
+        e = skip_slices(path);
 
-        /* Skip the user name */
-        e = skip_label(e);
-        if (!e)
-                return -ENOENT;
+        /* Skip the user name, if there is one */
+        e = skip_user(e);
 
-        /* Skip the session ID */
-        e = skip_label(e);
+        /* Skip the session ID, require that there is one */
+        e = skip_session(e);
         if (!e)
                 return -ENOENT;
 
-        /* Skip the systemd cgroup */
-        e = skip_label(e);
-        if (!e)
-                return -ENOENT;
+        /* Skip the systemd cgroup, if there is one */
+        e = skip_systemd_label(e);
 
         return cg_path_decode_unit(e, unit);
 }
@@ -1320,23 +1358,27 @@ int cg_pid_get_user_unit(pid_t pid, char **unit) {
 }
 
 int cg_path_get_machine_name(const char *path, char **machine) {
-        const char *e, *n;
+        const char *e, *n, *x;
         char *s, *r;
 
         assert(path);
         assert(machine);
 
-        e = path_startswith(path, "/machine/");
-        if (!e)
-                return -ENOENT;
+        /* Skip slices, if there are any */
+        e = skip_slices(path);
 
         n = strchrnul(e, '/');
         if (e == n)
                 return -ENOENT;
 
         s = strndupa(e, n - e);
+        s = cg_unescape(s);
+
+        x = endswith(s, ".machine");
+        if (!x)
+                return -ENOENT;
 
-        r = strdup(cg_unescape(s));
+        r = strndup(s, x - s);
         if (!r)
                 return -ENOMEM;
 
@@ -1364,14 +1406,11 @@ int cg_path_get_session(const char *path, char **session) {
         assert(path);
         assert(session);
 
-        e = path_startswith(path, "/user/");
-        if (!e)
-                return -ENOENT;
+        /* Skip slices, if there are any */
+        e = skip_slices(path);
 
-        /* Skip the user name */
-        e = skip_label(e);
-        if (!e)
-                return -ENOENT;
+        /* Skip the user name, if there is one */
+        e = skip_user(e);
 
         n = strchrnul(e, '/');
         if (n - e < 8)
@@ -1407,9 +1446,8 @@ int cg_path_get_owner_uid(const char *path, uid_t *uid) {
         assert(path);
         assert(uid);
 
-        e = path_startswith(path, "/user/");
-        if (!e)
-                return -ENOENT;
+        /* Skip slices, if there are any */
+        e = skip_slices(path);
 
         n = strchrnul(e, '/');
         if (n - e < 5)
@@ -1457,7 +1495,7 @@ int cg_controller_from_attr(const char *attr, char **controller) {
         if (!c)
                 return -ENOMEM;
 
-        if (!filename_is_safe(c)) {
+        if (!cg_controller_is_valid(c, false)) {
                 free(c);
                 return -EINVAL;
         }
@@ -1479,7 +1517,12 @@ char *cg_escape(const char *p) {
         /* The return value of this function (unlike cg_unescape())
          * needs free()! */
 
-        if (p[0] == '_' || streq(p, "notify_on_release") || streq(p, "release_agent") || streq(p, "tasks"))
+        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;
@@ -1517,3 +1560,84 @@ char *cg_unescape(const char *p) {
 
         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;
+}
+
+int cg_slice_to_path(const char *unit, char **ret) {
+        _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL;
+        const char *dash;
+
+        assert(unit);
+        assert(ret);
+
+        if (!unit_name_is_valid(unit, false))
+                return -EINVAL;
+
+        if (!endswith(unit, ".slice"))
+                return -EINVAL;
+
+        p = unit_name_to_prefix(unit);
+        if (!p)
+                return -ENOMEM;
+
+        dash = strchr(p, '-');
+        while (dash) {
+                _cleanup_free_ char *escaped = NULL;
+                char n[dash - p + sizeof(".slice")];
+
+                strcpy(stpncpy(n, p, dash - p), ".slice");
+
+                if (!unit_name_is_valid(n, false))
+                        return -EINVAL;
+
+                escaped = cg_escape(n);
+                if (!escaped)
+                        return -ENOMEM;
+
+                if (!strextend(&s, escaped, "/", NULL))
+                        return -ENOMEM;
+
+                dash = strchr(dash+1, '-');
+        }
+
+        e = cg_escape(unit);
+        if (!e)
+                return -ENOMEM;
+
+        if (!strextend(&s, e, NULL))
+                return -ENOMEM;
+
+        *ret = s;
+        s = NULL;
+
+        return 0;
+}