#include "set.h"
#include "macro.h"
#include "util.h"
+#include "path-util.h"
+#include "strv.h"
+#include "unit-name.h"
+#include "fileio.h"
+#include "special.h"
+#include "mkdir.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;
/* 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) {
}
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;
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)
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;
+int cg_rmdir(const char *controller, const char *path) {
+ _cleanup_free_ char *p = NULL;
int r;
r = cg_get_path(controller, path, NULL, &p);
if (r < 0)
return r;
- if (honour_sticky) {
- char *tasks;
-
- /* If the sticky bit is set don't remove the directory */
-
- tasks = strappend(p, "/tasks");
- if (!tasks) {
- free(p);
- return -ENOMEM;
- }
-
- r = file_is_priv_sticky(tasks);
- free(tasks);
-
- if (r > 0) {
- free(p);
- 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(NULL);
+ 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))
if (kill(pid, sig) < 0) {
if (ret >= 0 && errno != ESRCH)
ret = -errno;
- } else if (ret == 0) {
-
- if (sigcont)
+ } else {
+ if (sigcont && sig != SIGKILL)
kill(pid, SIGCONT);
- ret = 1;
+ if (ret == 0)
+ ret = 1;
}
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(NULL);
+ 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);
-
- return ret;
-}
-
-int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) {
- unsigned i;
-
- assert(path);
- assert(controller);
-
- /* This safely kills all processes; first it sends a SIGTERM,
- * then checks 8 times after 200ms whether the group is now
- * empty, then kills everything that is left with SIGKILL and
- * finally checks 5 times after 200ms each whether the group
- * is finally empty. */
-
- for (i = 0; i < 15; i++) {
- int sig, r;
-
- if (i <= 0)
- sig = SIGTERM;
- else if (i == 9)
- sig = SIGKILL;
- else
- sig = 0;
-
- if ((r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL)) <= 0)
+ if (rem) {
+ r = cg_rmdir(controller, path);
+ if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
return r;
-
- usleep(200 * USEC_PER_MSEC);
}
- return 0;
+ return ret;
}
-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(NULL);
+ 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) {
/* 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)
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;
}
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);
+ if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
+ return r;
+ }
return ret;
}
+int cg_migrate_recursive_fallback(
+ const char *cfrom,
+ const char *pfrom,
+ const char *cto,
+ const char *pto,
+ bool ignore_self,
+ bool rem) {
+
+ int r;
+
+ assert(cfrom);
+ assert(pfrom);
+ assert(cto);
+ assert(pto);
+
+ r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem);
+ if (r < 0) {
+ char prefix[strlen(pto) + 1];
+
+ /* This didn't work? Then let's try all prefixes of the destination */
+
+ PATH_FOREACH_PREFIX(prefix, pto) {
+ r = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem);
+ if (r >= 0)
+ break;
+ }
+ }
+
+ return 0;
+}
+
static const char *normalize_controller(const char *controller) {
+ assert(controller);
+
if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
return "systemd";
else if (startswith(controller, "name="))
}
static int join_path(const char *controller, const char *path, const char *suffix, char **fs) {
- char *t;
-
- if (path && suffix)
- t = join("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL);
- else if (path)
- t = join("/sys/fs/cgroup/", controller, "/", path, NULL);
- else if (suffix)
- t = join("/sys/fs/cgroup/", controller, "/", suffix, NULL);
- else
- t = join("/sys/fs/cgroup/", controller, NULL);
+ char *t = NULL;
+
+ if (!isempty(controller)) {
+ if (!isempty(path) && !isempty(suffix))
+ t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL);
+ else if (!isempty(path))
+ t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL);
+ else if (!isempty(suffix))
+ t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL);
+ else
+ t = strappend("/sys/fs/cgroup/", controller);
+ } else {
+ if (!isempty(path) && !isempty(suffix))
+ t = strjoin(path, "/", suffix, NULL);
+ else if (!isempty(path))
+ t = strdup(path);
+ else
+ return -EINVAL;
+ }
if (!t)
return -ENOMEM;
- path_kill_slashes(t);
-
- *fs = t;
+ *fs = path_kill_slashes(t);
return 0;
}
int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) {
const char *p;
- static __thread bool good = false;
+ static thread_local bool good = false;
- assert(controller);
assert(fs);
- if (isempty(controller))
+ if (controller && !cg_controller_is_valid(controller, true))
return -EINVAL;
if (_unlikely_(!good)) {
good = true;
}
- p = normalize_controller(controller);
+ p = controller ? normalize_controller(controller) : NULL;
return join_path(p, path, suffix, fs);
}
+static int check_hierarchy(const char *p) {
+ char *cc;
+
+ assert(p);
+
+ /* Check if this controller actually really exists */
+ cc = alloca(strlen("/sys/fs/cgroup/") + strlen(p) + 1);
+ strcpy(stpcpy(cc, "/sys/fs/cgroup/"), p);
+ if (access(cc, F_OK) < 0)
+ return -errno;
+
+ return 0;
+}
+
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) {
const char *p;
- char *cc;
+ int r;
- assert(controller);
assert(fs);
- if (isempty(controller))
+ if (!cg_controller_is_valid(controller, true))
return -EINVAL;
+ /* Normalize the controller syntax */
p = normalize_controller(controller);
/* 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;
+ 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;
+ assert(path);
+ assert(sb);
+ assert(ftwbuf);
if (typeflag != FTW_DP)
return 0;
if (ftwbuf->level < 1)
return 0;
- p = strappend(path, "/tasks");
- if (!p) {
- errno = ENOMEM;
- return 1;
- }
-
- is_sticky = file_is_priv_sticky(p) > 0;
- free(p);
-
- if (is_sticky)
- return 0;
-
rmdir(path);
return 0;
}
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);
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);
- 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)
+ 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;
+int cg_create(const char *controller, const char *path) {
+ _cleanup_free_ char *fs = NULL;
int r;
- char c[32];
- assert(controller);
- assert(path);
- assert(pid >= 0);
+ r = cg_get_path_and_check(controller, path, NULL, &fs);
+ if (r < 0)
+ return r;
- r = cg_get_path_and_check(controller, path, "tasks", &fs);
+ r = mkdir_parents(fs, 0755);
if (r < 0)
return r;
- if (pid == 0)
- pid = getpid();
+ if (mkdir(fs, 0755) < 0) {
- snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid);
- char_array_0(c);
+ if (errno == EEXIST)
+ return 0;
- r = write_one_line_file(fs, c);
- free(fs);
+ return -errno;
+ }
- return r;
+ return 1;
}
-int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) {
- char *fs;
- int r;
-
- assert(controller);
- assert(path);
+int cg_create_and_attach(const char *controller, const char *path, pid_t pid) {
+ int r, q;
- if (mode != (mode_t) -1)
- mode &= 0777;
+ assert(pid >= 0);
- r = cg_get_path(controller, path, NULL, &fs);
+ r = cg_create(controller, path);
if (r < 0)
return r;
- r = chmod_and_chown(fs, mode, uid, gid);
- free(fs);
+ q = cg_attach(controller, path, pid);
+ if (q < 0)
+ return q;
+ /* This does not remove the cgroup on failure */
return r;
}
-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_attach(const char *controller, const char *path, pid_t pid) {
+ _cleanup_free_ char *fs = NULL;
+ char c[DECIMAL_STR_MAX(pid_t) + 2];
int r;
- assert(controller);
assert(path);
+ assert(pid >= 0);
- if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0)
- return 0;
-
- if (mode != (mode_t) -1)
- mode &= 0666;
-
- r = cg_get_path(controller, path, "tasks", &fs);
+ r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs);
if (r < 0)
return r;
- if (sticky >= 0 && mode != (mode_t) -1)
- /* Both mode and sticky param are passed */
- mode |= (sticky ? S_ISVTX : 0);
- else if ((sticky >= 0 && mode == (mode_t) -1) ||
- (mode != (mode_t) -1 && sticky < 0)) {
- struct stat st;
-
- /* Only one param is passed, hence read the current
- * mode from the file itself */
-
- r = lstat(fs, &st);
- if (r < 0) {
- free(fs);
- return -errno;
- }
-
- if (mode == (mode_t) -1)
- /* No mode set, we just shall set the sticky bit */
- mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0);
- else
- /* Only mode set, leave sticky bit untouched */
- mode = (st.st_mode & ~0777) | mode;
- }
+ if (pid == 0)
+ pid = getpid();
- r = chmod_and_chown(fs, mode, uid, gid);
- free(fs);
+ snprintf(c, sizeof(c), PID_FMT"\n", pid);
- return r;
+ return write_string_file_no_create(fs, c);
}
-int cg_get_by_pid(const char *controller, pid_t pid, char **path) {
+int cg_attach_fallback(const char *controller, const char *path, pid_t pid) {
int r;
- char *p = NULL;
- FILE *f;
- char *fs;
- size_t cs;
assert(controller);
assert(path);
assert(pid >= 0);
- if (pid == 0)
- pid = getpid();
+ r = cg_attach(controller, path, pid);
+ if (r < 0) {
+ char prefix[strlen(path) + 1];
- if (asprintf(&fs, "/proc/%lu/cgroup", (unsigned long) pid) < 0)
- return -ENOMEM;
+ /* This didn't work? Then let's try all prefixes of
+ * the destination */
- f = fopen(fs, "re");
- free(fs);
+ PATH_FOREACH_PREFIX(prefix, path) {
+ r = cg_attach(controller, prefix, pid);
+ if (r >= 0)
+ break;
+ }
+ }
- if (!f)
- return errno == ENOENT ? -ESRCH : -errno;
+ return 0;
+}
- cs = strlen(controller);
+int cg_set_group_access(
+ const char *controller,
+ const char *path,
+ mode_t mode,
+ uid_t uid,
+ gid_t gid) {
- while (!feof(f)) {
- char line[LINE_MAX];
- char *l;
+ _cleanup_free_ char *fs = NULL;
+ int r;
- errno = 0;
- if (!(fgets(line, sizeof(line), f))) {
- if (feof(f))
- break;
+ assert(path);
- r = errno ? -errno : -EIO;
- goto finish;
- }
+ if (mode != MODE_INVALID)
+ mode &= 0777;
- truncate_nl(line);
+ r = cg_get_path(controller, path, NULL, &fs);
+ if (r < 0)
+ return r;
- if (!(l = strchr(line, ':')))
- continue;
+ return chmod_and_chown(fs, mode, uid, gid);
+}
- l++;
- if (strncmp(l, controller, cs) != 0)
- continue;
+int cg_set_task_access(
+ const char *controller,
+ const char *path,
+ mode_t mode,
+ uid_t uid,
+ gid_t gid) {
- if (l[cs] != ':')
- continue;
+ _cleanup_free_ char *fs = NULL, *procs = NULL;
+ int r;
- if (!(p = strdup(l + cs + 1))) {
- r = -ENOMEM;
- goto finish;
- }
+ assert(path);
- *path = p;
- r = 0;
- goto finish;
- }
+ if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID)
+ return 0;
- r = -ENOENT;
+ if (mode != MODE_INVALID)
+ mode &= 0666;
-finish:
- fclose(f);
+ r = cg_get_path(controller, path, "cgroup.procs", &fs);
+ if (r < 0)
+ return r;
- return r;
+ r = chmod_and_chown(fs, mode, uid, gid);
+ if (r < 0)
+ 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_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(path);
+ assert(pid >= 0);
+
+ if (controller) {
+ if (!cg_controller_is_valid(controller, true))
+ return -EINVAL;
+
+ controller = normalize_controller(controller);
+ } else
+ controller = SYSTEMD_CGROUP_CONTROLLER;
+
+ fs = procfs_file_alloca(pid, "cgroup");
+
+ f = fopen(fs, "re");
+ if (!f)
+ return errno == ENOENT ? -ESRCH : -errno;
+
+ cs = strlen(controller);
+
+ FOREACH_LINE(line, f, return -errno) {
+ char *l, *p, *e;
+ size_t k;
+ const char *word, *state;
+ bool found = false;
+
+ truncate_nl(line);
+
+ l = strchr(line, ':');
+ if (!l)
+ continue;
+
+ l++;
+ e = strchr(l, ':');
+ if (!e)
+ continue;
+
+ *e = 0;
+
+ FOREACH_WORD_SEPARATOR(word, k, l, ",", state) {
+
+ if (k == cs && memcmp(word, controller, cs) == 0) {
+ found = true;
+ break;
+ }
+
+ if (k == 5 + cs &&
+ memcmp(word, "name=", 5) == 0 &&
+ memcmp(word+5, controller, cs) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ continue;
+
+ p = strdup(e + 1);
+ if (!p)
+ return -ENOMEM;
+
+ *path = p;
+ return 0;
+ }
+
+ 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_no_create(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_no_create(fs, "1");
+ if (r < 0)
+ return r;
- r = 1;
- } else if (!streq(sc, "1")) {
- r = -EIO;
- goto finish;
- } else
- r = 0;
+ return 1;
+ }
+
+ if (!streq(sc, "1"))
+ return -EIO;
+
+ return 0;
+}
+
+int cg_uninstall_release_agent(const char *controller) {
+ _cleanup_free_ char *fs = NULL;
+ int r;
+
+ r = cg_get_path(controller, NULL, "notify_on_release", &fs);
+ if (r < 0)
+ return r;
+
+ r = write_string_file_no_create(fs, "0");
+ if (r < 0)
+ return r;
-finish:
free(fs);
- free(contents);
- free(line);
+ fs = NULL;
- return r;
+ r = cg_get_path(controller, NULL, "release_agent", &fs);
+ if (r < 0)
+ return r;
+
+ r = write_string_file_no_create(fs, "");
+ if (r < 0)
+ 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;
}
int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) {
- int r;
- DIR *d = NULL;
+ _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 = t;
+ *path = path_kill_slashes(t);
}
if (controller)
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;
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)))
+ if (streq(e+1, "")) {
+ u = strdup("/");
+ if (!u) {
+ free(t);
return -ENOMEM;
-
- if (path)
- if (!(u = strdup(e+1))) {
+ }
+ } else {
+ 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;
+ }
+
+ 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);
+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_startswith(path, "/sys/fs/cgroup")) {
+
+ t = strdup(path);
+ if (!t)
+ return -ENOMEM;
+
+ *result = path_kill_slashes(t);
+ return 0;
+ }
+
+ /* Otherwise, treat it as cg spec */
+ r = cg_split_spec(path, &c, &p);
+ if (r < 0)
+ return r;
+
+ return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
+}
+
+int cg_get_root_path(char **path) {
+ char *p, *e;
+ int r;
+
assert(path);
- if (!path_is_absolute(path) ||
- controller[0] == 0 ||
- strchr(controller, ':') ||
- strchr(controller, '/'))
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p);
+ if (r < 0)
+ return r;
+
+ e = endswith(p, "/" SPECIAL_SYSTEM_SLICE);
+ if (e)
+ *e = 0;
+
+ *path = p;
+ return 0;
+}
+
+int cg_shift_path(const char *cgroup, const char *root, const char **shifted) {
+ _cleanup_free_ char *rt = NULL;
+ char *p;
+ int r;
+
+ assert(cgroup);
+ assert(shifted);
+
+ if (!root) {
+ /* If the root was specified let's use that, otherwise
+ * let's determine it from PID 1 */
+
+ r = cg_get_root_path(&rt);
+ if (r < 0)
+ return r;
+
+ root = rt;
+ }
+
+ p = path_startswith(cgroup, root);
+ if (p)
+ *shifted = p - 1;
+ else
+ *shifted = cgroup;
+
+ return 0;
+}
+
+int cg_pid_get_path_shifted(pid_t pid, const char *root, char **cgroup) {
+ _cleanup_free_ char *raw = NULL;
+ const char *c;
+ int r;
+
+ assert(pid >= 0);
+ assert(cgroup);
+
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw);
+ if (r < 0)
+ return r;
+
+ r = cg_shift_path(raw, root, &c);
+ if (r < 0)
+ return r;
+
+ if (c == raw) {
+ *cgroup = raw;
+ raw = NULL;
+ } else {
+ char *n;
+
+ n = strdup(c);
+ if (!n)
+ return -ENOMEM;
+
+ *cgroup = n;
+ }
+
+ return 0;
+}
+
+int cg_path_decode_unit(const char *cgroup, char **unit){
+ char *e, *c, *s;
+
+ assert(cgroup);
+ assert(unit);
+
+ e = strchrnul(cgroup, '/');
+ c = strndupa(cgroup, e - cgroup);
+ c = cg_unescape(c);
+
+ if (!unit_name_is_valid(c, TEMPLATE_INVALID))
return -EINVAL;
- if (asprintf(spec, "%s:%s", controller, path) < 0)
+ s = strdup(c);
+ if (!s)
return -ENOMEM;
+ *unit = s;
return 0;
}
-int cg_fix_path(const char *path, char **result) {
- char *t, *c, *p;
+static const char *skip_slices(const char *p) {
+ /* Skips over all slice assignments */
+
+ for (;;) {
+ size_t n;
+
+ 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 = skip_slices(path);
+
+ 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);
+}
+
+/**
+ * Skip session-*.scope, but require it to be there.
+ */
+static const char *skip_session(const char *p) {
+ size_t n;
+
+ assert(p);
+
+ p += strspn(p, "/");
+
+ n = strcspn(p, "/");
+ if (n < strlen("session-x.scope") || memcmp(p, "session-", 8) != 0 || memcmp(p + n - 6, ".scope", 6) != 0)
+ return NULL;
+
+ p += n;
+ p += strspn(p, "/");
+
+ return p;
+}
+
+/**
+ * Skip user@*.service, but require it to be there.
+ */
+static const char *skip_user_manager(const char *p) {
+ size_t n;
+
+ assert(p);
+
+ p += strspn(p, "/");
+
+ n = strcspn(p, "/");
+ if (n < strlen("user@x.service") || memcmp(p, "user@", 5) != 0 || memcmp(p + n - 8, ".service", 8) != 0)
+ return NULL;
+
+ p += n;
+ p += strspn(p, "/");
+
+ return p;
+}
+
+int cg_path_get_user_unit(const char *path, char **unit) {
+ const char *e, *t;
+
+ 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 */
+
+ /* Skip slices, if there are any */
+ e = skip_slices(path);
+
+ /* Skip the session scope... */
+ t = skip_session(e);
+ if (t)
+ /* ... and skip more slices if there's one */
+ e = skip_slices(t);
+ else {
+ /* ... or require a user manager unit to be there */
+ e = skip_user_manager(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) {
+ _cleanup_free_ char *u = NULL, *sl = NULL;
+ int r;
+
+ r = cg_path_get_unit(path, &u);
+ if (r < 0)
+ return r;
+
+ sl = strjoin("/run/systemd/machines/unit:", u, NULL);
+ if (!sl)
+ return -ENOMEM;
+
+ return readlink_malloc(sl, machine);
+}
+
+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, *x, *y;
+ char *s;
+
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) {
+ /* 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 = startswith(s, "session-");
+ if (!x)
+ return -ENOENT;
+ y = endswith(x, ".scope");
+ if (!y || x == y)
+ return -ENOENT;
- if (!(t = strdup(path)))
+ if (session) {
+ char *r;
+
+ r = strndup(x, y - x);
+ if (!r)
return -ENOMEM;
- *result = t;
- return 0;
+ *session = r;
}
- /* Otherwise treat it as cg spec */
- if ((r = cg_split_spec(path, &c, &p)) < 0)
- return r;
+ return 0;
+}
- r = cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result);
- free(c);
- free(p);
+int cg_pid_get_session(pid_t pid, char **session) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
- return r;
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_session(cgroup, session);
}
-int cg_get_user_path(char **path) {
- char *root, *p;
+int cg_path_get_owner_uid(const char *path, uid_t *uid) {
+ _cleanup_free_ char *slice = NULL;
+ const char *start, *end;
+ char *s;
+ uid_t u;
+ int r;
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" */
+ r = cg_path_get_slice(path, &slice);
+ if (r < 0)
+ return r;
+
+ start = startswith(slice, "user-");
+ if (!start)
+ return -ENOENT;
+ end = endswith(slice, ".slice");
+ if (!end)
+ return -ENOENT;
+
+ s = strndupa(start, end - start);
+ if (!s)
+ return -ENOENT;
+
+ if (parse_uid(s, &u) < 0)
+ return -EIO;
+
+ if (uid)
+ *uid = u;
+
+ return 0;
+}
+
+int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_owner_uid(cgroup, uid);
+}
+
+int cg_path_get_slice(const char *p, char **slice) {
+ const char *e = NULL;
+ size_t m = 0;
+
+ assert(p);
+ assert(slice);
- if (cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &root) < 0)
- p = strdup("/user");
+ for (;;) {
+ size_t n;
+
+ p += strspn(p, "/");
+
+ n = strcspn(p, "/");
+ if (n <= 6 || memcmp(p + n - 6, ".slice", 6) != 0) {
+ char *s;
+
+ if (!e)
+ return -ENOENT;
+
+ s = strndup(e, m);
+ if (!s)
+ return -ENOMEM;
+
+ *slice = s;
+ return 0;
+ }
+
+ e = p;
+ m = n;
+
+ p += n;
+ }
+}
+
+int cg_pid_get_slice(pid_t pid, char **slice) {
+ _cleanup_free_ char *cgroup = NULL;
+ int r;
+
+ assert(slice);
+
+ r = cg_pid_get_path_shifted(pid, NULL, &cgroup);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_slice(cgroup, slice);
+}
+
+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 {
- if (endswith(root, "/system"))
- root[strlen(root) - 7] = 0;
- else if (streq(root, "/"))
- root[0] = 0;
+ 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;
- p = strappend(root, "/user");
- free(root);
+ return (char*) p;
+}
+
+#define CONTROLLER_VALID \
+ DIGITS LETTERS \
+ "_"
+
+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, TEMPLATE_INVALID))
+ return -EINVAL;
+
+ if (!endswith(unit, ".slice"))
+ return -EINVAL;
+
+ p = unit_name_to_prefix(unit);
if (!p)
return -ENOMEM;
- *path = p;
+ 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, TEMPLATE_INVALID))
+ 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;
}
+
+int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) {
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ r = cg_get_path(controller, path, attribute, &p);
+ if (r < 0)
+ return r;
+
+ return write_string_file_no_create(p, value);
+}
+
+static const char mask_names[] =
+ "cpu\0"
+ "cpuacct\0"
+ "blkio\0"
+ "memory\0"
+ "devices\0";
+
+int cg_create_everywhere(CGroupControllerMask supported, CGroupControllerMask mask, const char *path) {
+ CGroupControllerMask bit = 1;
+ const char *n;
+ int r;
+
+ /* This one will create a cgroup in our private tree, but also
+ * duplicate it in the trees specified in mask, and remove it
+ * in all others */
+
+ /* First create the cgroup in our own hierarchy. */
+ r = cg_create(SYSTEMD_CGROUP_CONTROLLER, path);
+ if (r < 0)
+ return r;
+
+ /* Then, do the same in the other hierarchies */
+ NULSTR_FOREACH(n, mask_names) {
+ if (mask & bit)
+ cg_create(n, path);
+ else if (supported & bit)
+ cg_trim(n, path, true);
+
+ bit <<= 1;
+ }
+
+ return 0;
+}
+
+int cg_attach_everywhere(CGroupControllerMask supported, const char *path, pid_t pid) {
+ CGroupControllerMask bit = 1;
+ const char *n;
+ int r;
+
+ r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid);
+ if (r < 0)
+ return r;
+
+ NULSTR_FOREACH(n, mask_names) {
+ if (supported & bit)
+ cg_attach_fallback(n, path, pid);
+
+ bit <<= 1;
+ }
+
+ return 0;
+}
+
+int cg_attach_many_everywhere(CGroupControllerMask supported, const char *path, Set* pids) {
+ Iterator i;
+ void *pidp;
+ int r = 0;
+
+ SET_FOREACH(pidp, pids, i) {
+ pid_t pid = PTR_TO_LONG(pidp);
+ int q;
+
+ q = cg_attach_everywhere(supported, path, pid);
+ if (q < 0)
+ r = q;
+ }
+
+ return r;
+}
+
+int cg_migrate_everywhere(CGroupControllerMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) {
+ CGroupControllerMask bit = 1;
+ const char *n;
+ int r;
+
+ if (!path_equal(from, to)) {
+ r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true);
+ if (r < 0)
+ return r;
+ }
+
+ NULSTR_FOREACH(n, mask_names) {
+ if (supported & bit) {
+ const char *p = NULL;
+
+ if (to_callback)
+ p = to_callback(bit, userdata);
+
+ if (!p)
+ p = to;
+
+ cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, n, p, false, false);
+ }
+
+ bit <<= 1;
+ }
+
+ return 0;
+}
+
+int cg_trim_everywhere(CGroupControllerMask supported, const char *path, bool delete_root) {
+ CGroupControllerMask bit = 1;
+ const char *n;
+ int r;
+
+ r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root);
+ if (r < 0)
+ return r;
+
+ NULSTR_FOREACH(n, mask_names) {
+ if (supported & bit)
+ cg_trim(n, path, delete_root);
+
+ bit <<= 1;
+ }
+
+ return 0;
+}
+
+CGroupControllerMask cg_mask_supported(void) {
+ CGroupControllerMask bit = 1, mask = 0;
+ const char *n;
+
+ NULSTR_FOREACH(n, mask_names) {
+ if (check_hierarchy(n) >= 0)
+ mask |= bit;
+
+ bit <<= 1;
+ }
+
+ return mask;
+}