diff options
Diffstat (limited to 'src/cgroup-util.c')
-rw-r--r-- | src/cgroup-util.c | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/src/cgroup-util.c b/src/cgroup-util.c new file mode 100644 index 0000000000..9337817b6b --- /dev/null +++ b/src/cgroup-util.c @@ -0,0 +1,694 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <stdlib.h> + +#include <libcgroup.h> + +#include "cgroup-util.h" +#include "log.h" +#include "set.h" +#include "macro.h" +#include "util.h" + +int cg_translate_error(int error, int _errno) { + + switch (error) { + + case ECGROUPNOTCOMPILED: + case ECGROUPNOTMOUNTED: + case ECGROUPNOTEXIST: + case ECGROUPNOTCREATED: + return -ENOENT; + + case ECGINVAL: + return -EINVAL; + + case ECGROUPNOTALLOWED: + return -EPERM; + + case ECGOTHER: + return -_errno; + } + + return -EIO; +} + +static struct cgroup* cg_new(const char *controller, const char *path) { + struct cgroup *cgroup; + + assert(path); + assert(controller); + + if (!(cgroup = cgroup_new_cgroup(path))) + return NULL; + + if (!cgroup_add_controller(cgroup, controller)) { + cgroup_free(&cgroup); + return NULL; + } + + return cgroup; +} + +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 r, ret = 0; + + 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 = 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 (pid == my_pid && ignore_self) + goto next; + + if (set_get(s, INT_TO_PTR(pid)) == INT_TO_PTR(pid)) + goto next; + + /* If we haven't killed this process yet, kill + * it */ + + if (kill(pid, sig) < 0 && errno != ESRCH) { + if (ret == 0) + ret = -errno; + } + + killed = true; + done = false; + + if ((r = set_put(s, INT_TO_PTR(pid))) < 0) + goto loop_exit; + + next: + r = cgroup_get_task_next(&iterator, &pid); + } + + if (r == 0 || r == ECGEOF) + r = 0; + else if (r == ECGOTHER && errno == ENOENT) + r = -ESRCH; + else + r = cg_translate_error(r, errno); + + loop_exit: + assert_se(cgroup_get_task_end(&iterator) == 0); + + /* 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); + + set_free(s); + + if (ret < 0) + return ret; + + if (r < 0) + return r; + + return !!killed; +} + +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; + + assert(path); + assert(controller); + assert(sig >= 0); + + zero(info); + + r = cgroup_walk_tree_begin(controller, path, 0, &iterator, &info, &level); + while (r == 0) { + int k; + char *p; + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (asprintf(&p, "%s/%s", path, info.path) < 0) { + ret = -ENOMEM; + break; + } + + k = cg_kill(controller, p, sig, ignore_self); + free(p); + + if (k < 0) { + if (ret == 0) + ret = k; + } else if (k > 0) + killed = true; + + next: + + r = cgroup_walk_tree_next(0, &iterator, &info, level); + } + + 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); + } + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return ret; +} + +int cg_kill_recursive_and_wait(const char *controller, const char *path) { + 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 */ + + for (i = 0; i < 10; 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)) <= 0) + return r; + + usleep(50 * 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; + int r, ret = 0; + pid_t my_pid; + + assert(controller); + assert(from); + assert(to); + + if (!(dest = cg_new(controller, to))) + 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 (pid == my_pid && ignore_self) + goto next; + + if ((r = cgroup_attach_task_pid(dest, pid)) != 0) { + if (ret == 0) + r = cg_translate_error(r, errno); + } + + migrated = true; + done = false; + + next: + + r = cgroup_get_task_next(&iterator, &pid); + } + + if (r == 0 || r == ECGEOF) + r = 0; + else if (r == ECGOTHER && errno == ENOENT) + r = -ESRCH; + else + r = cg_translate_error(r, errno); + + assert_se(cgroup_get_task_end(&iterator) == 0); + + } while (!done && r >= 0); + + cgroup_free(&dest); + + if (ret < 0) + return ret; + + if (r < 0) + return r; + + return !!migrated; +} + +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; + + assert(controller); + assert(from); + assert(to); + + zero(info); + + r = cgroup_walk_tree_begin(controller, from, 0, &iterator, &info, &level); + while (r == 0) { + int k; + char *p; + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (asprintf(&p, "%s/%s", from, info.path) < 0) { + ret = -ENOMEM; + break; + } + + k = cg_migrate(controller, p, to, ignore_self); + 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 (ret == 0) { + if (r == 0 || r == ECGEOF) + r = !!migrated; + else if (r == ECGOTHER && errno == ENOENT) + r = -ESRCH; + else + r = cg_translate_error(r, errno); + } + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return ret; +} + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { + char *mp; + int r; + + assert(controller); + assert(path); + + if ((r = cgroup_get_subsys_mount_point(controller, &mp)) != 0) + return cg_translate_error(r, errno); + + if (suffix) + r = asprintf(fs, "%s/%s/%s", mp, path, suffix); + else + r = asprintf(fs, "%s/%s", mp, path); + + free(mp); + + return r < 0 ? -ENOMEM : 0; +} + +int cg_trim(const char *controller, const char *path, bool delete_root) { + char *fs; + int r; + + assert(controller); + assert(path); + + if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + return r; + + r = rm_rf(fs, true, delete_root); + free(fs); + + return r; +} + +int cg_delete(const char *controller, const char *path) { + struct cgroup *cg; + 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; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_create(const char *controller, const char *path) { + struct cgroup *cg; + int r; + + assert(controller); + assert(path); + + if (!(cg = cg_new(controller, path))) + return -ENOMEM; + + if ((r = cgroup_create_cgroup(cg, 1)) != 0) { + r = cg_translate_error(r, errno); + goto finish; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_attach(const char *controller, const char *path, pid_t pid) { + struct cgroup *cg; + int r; + + assert(controller); + assert(path); + assert(pid >= 0); + + if (!(cg = cg_new(controller, path))) + return -ENOMEM; + + if (pid == 0) + pid = getpid(); + + if ((r = cgroup_attach_task_pid(cg, pid))) { + r = cg_translate_error(r, errno); + goto finish; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + struct cgroup *cg; + int r; + + 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; + } + + r = 0; + +finish: + cgroup_free(&cg); + + return r; +} + +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); + + if ((r = cg_get_path(controller, path, NULL, &fs)) < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + free(fs); + + return r; +} + +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid) { + char *fs; + int r; + + assert(controller); + assert(path); + + if ((r = cg_get_path(controller, path, "tasks", &fs)) < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + free(fs); + + return r; +} + +int cg_get_by_pid(const char *controller, pid_t pid, char **path) { + int r; + char *p = NULL; + + assert(controller); + assert(pid > 0); + assert(path); + + if ((r = cgroup_get_current_controller_path(pid, controller, &p)) != 0) + return cg_translate_error(r, errno); + + assert(p); + + *path = p; + return 0; +} + +int cg_install_release_agent(const char *controller, const char *agent) { + char *mp = NULL, *path = 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 = read_one_line_file(path, &contents)) < 0) + goto finish; + + sc = strstrip(contents); + + if (sc[0] == 0) { + + if (asprintf(&line, "%s\n", agent) < 0) { + r = -ENOMEM; + goto finish; + } + + if ((r = write_one_line_file(path, line)) < 0) + goto finish; + + } else if (!streq(sc, agent)) { + r = -EEXIST; + goto finish; + } + + free(path); + path = NULL; + if (asprintf(&path, "%s/notify_on_release", mp) < 0) { + r = -ENOMEM; + goto finish; + } + + free(contents); + contents = NULL; + if ((r = read_one_line_file(path, &contents)) < 0) + goto finish; + + sc = strstrip(contents); + + if (streq(sc, "0")) { + if ((r = write_one_line_file(path, "1\n")) < 0) + goto finish; + } else if (!streq(sc, "1")) { + r = -EIO; + goto finish; + } + + r = 0; + +finish: + free(mp); + free(path); + free(contents); + free(line); + + return r; +} + +int cg_is_empty(const char *controller, const char *path, bool ignore_self) { + void *iterator = NULL; + pid_t pid = 0; + int r; + + assert(controller); + assert(path); + + r = cgroup_get_task_begin(path, controller, &iterator, &pid); + while (r == 0) { + + if (ignore_self&& pid == getpid()) + goto next; + + break; + + next: + r = cgroup_get_task_next(&iterator, &pid); + } + + + 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); + + return r; +} + +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; + + assert(controller); + assert(path); + + zero(info); + + r = cgroup_walk_tree_begin(controller, path, 0, &iterator, &info, &level); + while (r == 0) { + int k; + char *p; + + if (info.type != CGROUP_FILE_TYPE_DIR) + goto next; + + if (asprintf(&p, "%s/%s", path, info.path) < 0) { + ret = -ENOMEM; + break; + } + + k = cg_is_empty(controller, p, ignore_self); + free(p); + + if (k < 0) { + ret = k; + break; + } else if (k == 0) { + empty = false; + break; + } + + next: + r = cgroup_walk_tree_next(0, &iterator, &info, level); + } + + 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); + } + + assert_se(cgroup_walk_tree_end(&iterator) == 0); + + return ret; +} |