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