#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;
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;
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;
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;
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;
assert(fs);
+ if (controller && !cg_controller_is_valid(controller, true))
+ return -EINVAL;
+
if (_unlikely_(!good)) {
int r;
assert(fs);
- if (isempty(controller))
+ if (!cg_controller_is_valid(controller, true))
return -EINVAL;
/* Normalize the controller syntax */
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;
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;
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;
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;
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;
}
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;
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);
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;
assert(path);
- r = cg_enumerate_tasks(controller, path, &f);
+ r = cg_enumerate_processes(controller, path, &f);
if (r < 0)
return r == -ENOENT ? 1 : r;
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);
if (!t)
return -ENOMEM;
+ path_kill_slashes(t);
*path = t;
}
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;
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;
}
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
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);
if (!t)
return -ENOMEM;
+ path_kill_slashes(t);
*result = t;
return 0;
}
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;
}
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;
}
return 0;
}
-/* non-static only for testing purposes */
int cg_path_decode_unit(const char *cgroup, char **unit){
char *p, *e, *c, *s, *k;
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;
e += strspn(e, "/");
+
p = strchrnul(e, '/');
+ k = strndupa(e, p - e);
+ k = cg_unescape(k);
- /* Don't allow empty instance strings */
- if (p == e)
+ if (!unit_name_is_valid(k, false))
return -EINVAL;
- k = strndupa(e, p - e);
-
- s = unit_name_replace_instance(c, k);
+ s = strdup(k);
}
if (!s)
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);
}
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) {
* 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);
}
}
int cg_path_get_machine_name(const char *path, char **machine) {
- const char *e, *n;
- char *s;
+ 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 = strndup(e, n - e);
- if (!s)
+ s = strndupa(e, n - e);
+ s = cg_unescape(s);
+
+ x = endswith(s, ".machine");
+ if (!x)
+ return -ENOENT;
+
+ r = strndup(s, x - s);
+ if (!r)
return -ENOMEM;
- *machine = s;
+ *machine = r;
return 0;
}
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 (e == n)
+ if (n - e < 8)
return -ENOENT;
-
- if (n - e == 6 && memcmp(e, "shared", 6) == 0)
+ if (memcmp(n - 8, ".session", 8) != 0)
return -ENOENT;
- s = strndup(e, n - e);
+ s = strndup(e, n - e - 8);
if (!s)
return -ENOMEM;
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);
+
+ /* Skip slices, if there are any */
+ e = skip_slices(path);
+
+ 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;
if (!c)
return -ENOMEM;
- if (!filename_is_safe(c)) {
+ 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;
+}
+
+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;
+}