/***
This file is part of eudev, forked from systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see .
***/
#include
#include
#include
#include
#include
#include "cgroup-util.h"
#include "set.h"
#include "process-util.h"
#include "path-util.h"
int cg_enumerate_processes(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, "cgroup.procs", &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;
/* 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) {
if (feof(f))
return 0;
return errno ? -errno : -EIO;
}
if (ul <= 0)
return -EIO;
*_pid = (pid_t) ul;
return 1;
}
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;
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) {
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;
r = cg_enumerate_processes(controller, path, &f);
if (r < 0) {
if (ret >= 0 && r != -ENOENT)
return r;
return ret;
}
while ((r = cg_read_pid(f, &pid)) > 0) {
if (ignore_self && pid == my_pid)
continue;
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) {
if (ret >= 0 && errno != ESRCH)
ret = -errno;
} else {
if (sigcont && sig != SIGKILL)
kill(pid, SIGCONT);
if (ret == 0)
ret = 1;
}
done = false;
r = set_put(s, LONG_TO_PTR(pid));
if (r < 0) {
if (ret >= 0)
return r;
return ret;
}
}
if (r < 0) {
if (ret >= 0)
return r;
return ret;
}
/* 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);
return ret;
}
static const char *normalize_controller(const char *controller) {
assert(controller);
if (streq(controller, SYSTEMD_CGROUP_CONTROLLER))
return "systemd";
else if (startswith(controller, "name="))
return controller + 5;
else
return controller;
}
static int join_path(const char *controller, const char *path, const char *suffix, char **fs) {
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;
*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_local bool good = false;
assert(fs);
if (controller && !cg_controller_is_valid(controller, true))
return -EINVAL;
if (_unlikely_(!good)) {
int r;
r = path_is_mount_point("/sys/fs/cgroup", false);
if (r < 0)
return r;
if (r == 0)
return -ENOENT;
/* Cache this to save a few stat()s */
good = true;
}
p = controller ? normalize_controller(controller) : NULL;
return join_path(p, path, suffix, fs);
}
#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;
}