cgroup-util.c revision 5bf8002a3a6723ce50331c024122078552fb600a
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
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 <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <ftw.h>
#include "set.h"
#include "macro.h"
#include "util.h"
#include "formats-util.h"
#include "process-util.h"
#include "path-util.h"
#include "unit-name.h"
#include "fileio.h"
#include "special.h"
#include "mkdir.h"
#include "login-util.h"
#include "cgroup-util.h"
FILE *f;
int r;
if (r < 0)
return r;
if (!f)
return -errno;
*_f = f;
return 0;
}
unsigned long ul;
/* Note that the cgroup.procs might contain duplicates! See
* cgroups.txt for details. */
assert(f);
errno = 0;
if (feof(f))
return 0;
}
if (ul <= 0)
return -EIO;
return 1;
}
int r;
DIR *d;
/* This is not recursive! */
if (r < 0)
return r;
if (!d)
return -errno;
*_d = d;
return 0;
}
assert(d);
char *b;
continue;
continue;
if (!b)
return -ENOMEM;
*fn = b;
return 1;
}
return 0;
}
_cleanup_free_ char *p = NULL;
int r;
if (r < 0)
return r;
r = rmdir(p);
return -errno;
return 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;
/* 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)
return -ENOMEM;
}
do {
done = true;
if (r < 0) {
return r;
return ret;
}
while ((r = cg_read_pid(f, &pid)) > 0) {
continue;
continue;
/* If we haven't killed this process yet, kill
* it */
} else {
if (ret == 0)
ret = 1;
}
done = false;
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;
}
int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) {
int r, ret;
char *fn;
if (!s) {
if (!s)
return -ENOMEM;
}
if (r < 0) {
return r;
return ret;
}
while ((r = cg_read_subgroup(d, &fn)) > 0) {
_cleanup_free_ char *p = NULL;
if (!p)
return -ENOMEM;
if (r != 0 && ret >= 0)
ret = r;
}
if (ret >= 0 && r < 0)
ret = r;
if (rem) {
return r;
}
return ret;
}
int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) {
bool done = false;
int r, ret = 0;
if (!s)
return -ENOMEM;
do {
done = true;
if (r < 0) {
return r;
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 */
continue;
continue;
/* Ignore kernel threads. Since they can only
* exist in the root cgroup, we only check for
* them there. */
if (cfrom &&
is_kernel_thread(pid) > 0)
continue;
if (r < 0) {
ret = r;
} else if (ret == 0)
ret = 1;
done = false;
if (r < 0) {
if (ret >= 0)
return r;
return ret;
}
}
if (r < 0) {
if (ret >= 0)
return r;
return ret;
}
} while (!done);
return ret;
}
int cg_migrate_recursive(
const char *cfrom,
const char *pfrom,
const char *cto,
const char *pto,
bool ignore_self,
bool rem) {
int r, ret = 0;
char *fn;
if (r < 0) {
return r;
return ret;
}
while ((r = cg_read_subgroup(d, &fn)) > 0) {
_cleanup_free_ char *p = NULL;
if (!p)
return -ENOMEM;
if (r != 0 && ret >= 0)
ret = r;
}
if (r < 0 && ret >= 0)
ret = r;
if (rem) {
return r;
}
return ret;
}
const char *cfrom,
const char *pfrom,
const char *cto,
const char *pto,
bool ignore_self,
bool rem) {
int r;
if (r < 0) {
/* This didn't work? Then let's try all prefixes of the destination */
int q;
if (q >= 0)
return q;
}
}
return r;
}
static const char *controller_to_dirname(const char *controller) {
const char *e;
/* Converts a controller name to the directory name below
* just cuts off the name= prefixed used for named
* hierarchies, if it is specified. */
if (e)
return e;
return controller;
}
static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **fs) {
const char *dn;
char *t = NULL;
else
if (!t)
return -ENOMEM;
*fs = t;
return 0;
}
char *t;
else
if (!t)
return -ENOMEM;
*fs = t;
return 0;
}
int unified, r;
if (!controller) {
char *t;
/* If no controller is specified, we return the path
* *below* the controllers, without any prefix. */
return -EINVAL;
if (!suffix)
else if (!path)
else
if (!t)
return -ENOMEM;
*fs = path_kill_slashes(t);
return 0;
}
if (!cg_controller_is_valid(controller))
return -EINVAL;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0)
else
if (r < 0)
return r;
return 0;
}
static int controller_is_accessible(const char *controller) {
int unified;
/* Checks whether a specific controller is accessible,
* i.e. its hierarchy mounted. In the unified hierarchy all
* controllers are considered accessible, except for the named
* hierarchies */
if (!cg_controller_is_valid(controller))
return -EINVAL;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0) {
/* We don't support named hierarchies if we are using
* the unified hierarchy. */
return 0;
return -EOPNOTSUPP;
} else {
return -errno;
}
return 0;
}
int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) {
int r;
/* Check if the specified controller is actually accessible */
if (r < 0)
return r;
}
return 0;
return 0;
return 0;
}
int r = 0;
if (r < 0)
return r;
errno = 0;
r = 0;
else if (errno != 0)
r = -errno;
else
r = -EIO;
}
if (delete_root) {
return -errno;
}
return r;
}
int r;
if (r < 0)
return r;
if (r < 0)
return r;
return 0;
return -errno;
}
return 1;
}
int r, q;
if (r < 0)
return r;
if (q < 0)
return q;
/* This does not remove the cgroup on failure */
return r;
}
int r;
if (r < 0)
return r;
if (pid == 0)
return write_string_file(fs, c, 0);
}
int r;
if (r < 0) {
/* This didn't work? Then let's try all prefixes of
* the destination */
int q;
if (q >= 0)
return q;
}
}
return r;
}
int cg_set_group_access(
const char *controller,
const char *path,
int r;
return 0;
if (mode != MODE_INVALID)
mode &= 0777;
if (r < 0)
return r;
}
int cg_set_task_access(
const char *controller,
const char *path,
int r, unified;
return 0;
if (mode != MODE_INVALID)
mode &= 0666;
if (r < 0)
return r;
if (r < 0)
return r;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified)
return 0;
/* Compatibility, Always keep values for "tasks" in sync with
* "cgroup.procs" */
return 0;
}
const char *fs;
int unified;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified == 0) {
if (controller) {
if (!cg_controller_is_valid(controller))
return -EINVAL;
} else
}
if (!f)
char *e, *p;
if (unified) {
if (!e)
continue;
e = strchr(e, ':');
if (!e)
continue;
} else {
char *l;
size_t k;
bool found = false;
if (!l)
continue;
l++;
e = strchr(l, ':');
if (!e)
continue;
*e = 0;
found = true;
break;
}
}
if (!found)
continue;
}
p = strdup(e + 1);
if (!p)
return -ENOMEM;
*path = p;
return 0;
}
return -ENODATA;
}
const char *sc;
int r, unified;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified) /* doesn't apply to unified hierarchy */
return -EOPNOTSUPP;
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
return -EEXIST;
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
return 1;
}
return -EIO;
return 0;
}
int cg_uninstall_release_agent(const char *controller) {
int r, unified;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified) /* Doesn't apply to unified hierarchy */
return -EOPNOTSUPP;
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
return 0;
}
int r;
if (r == -ENOENT)
return 1;
if (r < 0)
return r;
r = cg_read_pid(f, &pid);
if (r < 0)
return r;
return r == 0;
}
int unified, r;
/* The root cgroup is always populated */
return false;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0) {
/* On the unified hierarchy we can check empty state
* via the "cgroup.populated" attribute. */
if (r < 0)
return r;
r = read_one_line_file(populated, &t);
if (r == -ENOENT)
return 1;
if (r < 0)
return r;
return streq(t, "0");
} else {
char *fn;
if (r <= 0)
return r;
if (r == -ENOENT)
return 1;
if (r < 0)
return r;
while ((r = cg_read_subgroup(d, &fn)) > 0) {
_cleanup_free_ char *p = NULL;
if (!p)
return -ENOMEM;
r = cg_is_empty_recursive(controller, p);
if (r <= 0)
return r;
}
if (r < 0)
return r;
return true;
}
}
const char *e;
if (*spec == '/') {
if (!path_is_safe(spec))
return -EINVAL;
if (path) {
if (!t)
return -ENOMEM;
*path = path_kill_slashes(t);
}
if (controller)
*controller = NULL;
return 0;
}
if (!e) {
if (!cg_controller_is_valid(spec))
return -EINVAL;
if (controller) {
if (!t)
return -ENOMEM;
*controller = t;
}
if (path)
return 0;
}
if (!t)
return -ENOMEM;
if (!cg_controller_is_valid(t)) {
free(t);
return -EINVAL;
}
if (isempty(e+1))
u = NULL;
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;
}
}
if (controller)
*controller = t;
else
free(t);
if (path)
*path = u;
else
free(u);
return 0;
}
char *t;
int r;
/* First, check if it already is a filesystem 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;
}
int cg_get_root_path(char **path) {
char *p, *e;
int r;
if (r < 0)
return r;
if (!e)
if (!e)
if (e)
*e = 0;
*path = p;
return 0;
}
char *p;
int r;
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;
}
if (p && p > cgroup)
*shifted = p - 1;
else
return 0;
}
const char *c;
int r;
if (r < 0)
return r;
if (r < 0)
return r;
if (c == raw) {
} else {
char *n;
n = strdup(c);
if (!n)
return -ENOMEM;
*cgroup = n;
}
return 0;
}
char *c, *s;
size_t n;
if (n < 3)
return -ENXIO;
c = cg_unescape(c);
return -ENXIO;
s = strdup(c);
if (!s)
return -ENOMEM;
*unit = s;
return 0;
}
static bool valid_slice_name(const char *p, size_t n) {
if (!p)
return false;
return false;
char buf[n+1], *c;
buf[n] = 0;
c = cg_unescape(buf);
return unit_name_is_valid(c, UNIT_NAME_PLAIN);
}
return false;
}
static const char *skip_slices(const char *p) {
assert(p);
/* Skips over all slice assignments */
for (;;) {
size_t n;
p += strspn(p, "/");
n = strcspn(p, "/");
if (!valid_slice_name(p, n))
return p;
p += n;
}
}
const char *e;
char *unit;
int r;
e = skip_slices(path);
r = cg_path_decode_unit(e, &unit);
if (r < 0)
return r;
/* We skipped over the slices, don't accept any now */
return -ENXIO;
}
return 0;
}
int r;
if (r < 0)
return r;
}
/**
* Skip session-*.scope, but require it to be there.
*/
static const char *skip_session(const char *p) {
size_t n;
if (isempty(p))
return NULL;
p += strspn(p, "/");
n = strcspn(p, "/");
if (n < strlen("session-x.scope"))
return NULL;
/* Note that session scopes never need unescaping,
* since they cannot conflict with the kernel's own
* names, hence we don't need to call cg_unescape()
* here. */
if (!session_id_valid(buf))
return false;
p += n;
p += strspn(p, "/");
return p;
}
return NULL;
}
/**
* Skip user@*.service, but require it to be there.
*/
static const char *skip_user_manager(const char *p) {
size_t n;
if (isempty(p))
return NULL;
p += strspn(p, "/");
n = strcspn(p, "/");
if (n < strlen("user@x.service"))
return NULL;
/* Note that user manager services never need unescaping,
* since they cannot conflict with the kernel's own
* names, hence we don't need to call cg_unescape()
* here. */
return NULL;
p += n;
p += strspn(p, "/");
return p;
}
return NULL;
}
static const char *skip_user_prefix(const char *path) {
const char *e, *t;
/* Skip slices, if there are any */
e = skip_slices(path);
/* Skip the user manager, if it's in the path now... */
t = skip_user_manager(e);
if (t)
return t;
/* Alternatively skip the user session if it is in the path... */
return skip_session(e);
}
const char *t;
t = skip_user_prefix(path);
if (!t)
return -ENXIO;
/* And from here on it looks pretty much the same as for a
* system unit, hence let's use the same parser from here
* on. */
return cg_path_get_unit(t, ret);
}
int r;
if (r < 0)
return r;
}
_cleanup_free_ char *u = NULL;
const char *sl;
int r;
r = cg_path_get_unit(path, &u);
if (r < 0)
return r;
}
int r;
if (r < 0)
return r;
}
int r;
if (r < 0)
return r;
if (!start)
return -ENXIO;
if (!end)
return -ENXIO;
*end = 0;
if (!session_id_valid(start))
return -ENXIO;
if (session) {
char *rr;
if (!rr)
return -ENOMEM;
}
return 0;
}
int r;
if (r < 0)
return r;
}
int r;
if (r < 0)
return r;
if (!start)
return -ENXIO;
if (!end)
return -ENXIO;
*end = 0;
return -ENXIO;
return 0;
}
int r;
if (r < 0)
return r;
}
int cg_path_get_slice(const char *p, char **slice) {
const char *e = NULL;
assert(p);
/* Finds the right-most slice unit from the beginning, but
* stops before we come to the first non-slice unit. */
for (;;) {
size_t n;
p += strspn(p, "/");
n = strcspn(p, "/");
if (!valid_slice_name(p, n)) {
if (!e) {
char *s;
s = strdup("-.slice");
if (!s)
return -ENOMEM;
*slice = s;
return 0;
}
return cg_path_decode_unit(e, slice);
}
e = p;
p += n;
}
}
int r;
if (r < 0)
return r;
}
int cg_path_get_user_slice(const char *p, char **slice) {
const char *t;
assert(p);
t = skip_user_prefix(p);
if (!t)
return -ENXIO;
/* And now it looks pretty much the same as for a system
* slice, so let's just use the same parser from here on. */
return cg_path_get_slice(t, slice);
}
int r;
if (r < 0)
return r;
}
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") ||
startswith(p, "cgroup."))
need_prefix = true;
else {
const char *dot;
if (dot) {
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *n;
n = cgroup_controller_to_string(c);
if (l != strlen(n))
continue;
if (memcmp(p, n, l) != 0)
continue;
need_prefix = true;
break;
}
}
}
if (need_prefix)
return strappend("_", p);
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 \
"_"
bool cg_controller_is_valid(const char *p) {
const char *t, *s;
if (!p)
return false;
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;
}
const char *dash;
int r;
char *x;
x = strdup("");
if (!x)
return -ENOMEM;
*ret = x;
return 0;
}
return -EINVAL;
return -EINVAL;
r = unit_name_to_prefix(unit, &p);
if (r < 0)
return r;
/* Don't allow initial dashes */
if (dash == p)
return -EINVAL;
while (dash) {
char n[dash - p + sizeof(".slice")];
/* Don't allow trailing or double dashes */
return -EINVAL;
if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
return -EINVAL;
if (!escaped)
return -ENOMEM;
return -ENOMEM;
}
if (!e)
return -ENOMEM;
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;
if (r < 0)
return r;
return write_string_file(p, value, 0);
}
_cleanup_free_ char *p = NULL;
int r;
if (r < 0)
return r;
return read_one_line_file(p, ret);
}
int r, unified;
/* 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. */
if (r < 0)
return r;
/* If we are in the unified hierarchy, we are done now */
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0)
return 0;
/* Otherwise, do the same in the other hierarchies */
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *n;
n = cgroup_controller_to_string(c);
}
return 0;
}
int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) {
int r, unified;
if (r < 0)
return r;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0)
return 0;
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *p = NULL;
continue;
if (path_callback)
if (!p)
p = path;
}
return 0;
}
int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t path_callback, void *userdata) {
Iterator i;
void *pidp;
int r = 0;
int q;
if (q < 0 && r >= 0)
r = q;
}
return r;
}
int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) {
int r = 0, unified;
r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true);
if (r < 0)
return r;
}
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0)
return r;
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *p = NULL;
continue;
if (to_callback)
if (!p)
p = to;
(void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, false, false);
}
return 0;
}
int r, unified;
if (r < 0)
return r;
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0)
return r;
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
continue;
}
return 0;
}
CGroupMask mask = 0;
int r, unified;
/* Determines the mask of supported cgroup controllers. Only
* includes controllers we can make sense of and that are
* actually accessible. */
unified = cg_unified();
if (unified < 0)
return unified;
if (unified > 0) {
const char *c;
/* In the unified hierarchy we can read the supported
* and accessible controllers from a the top-level
* cgroup attribute */
r = cg_get_root_path(&root);
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
c = controllers;
for (;;) {
_cleanup_free_ char *n = NULL;
r = extract_first_word(&c, &n, NULL, 0);
if (r < 0)
return r;
if (r == 0)
break;
v = cgroup_controller_from_string(n);
if (v < 0)
continue;
mask |= CGROUP_CONTROLLER_TO_MASK(v);
}
/* Currently, we only support the memory controller in
* the unified hierarchy, mask everything else off. */
} else {
/* In the legacy hierarchy, we check whether which
* hierarchies are mounted. */
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *n;
n = cgroup_controller_to_string(c);
if (controller_is_accessible(n) >= 0)
mask |= CGROUP_CONTROLLER_TO_MASK(c);
}
}
return 0;
}
int r;
/* Determines the full list of kernel-known controllers. Might
* include controllers we don't actually support, arbitrary
* named hierarchies and controllers that aren't currently
* accessible (because not mounted). */
if (!f) {
return 0;
return -errno;
}
/* Ignore the header line */
for (;;) {
char *controller;
int enabled = 0;
errno = 0;
if (feof(f))
break;
return -errno;
return -EBADMSG;
}
if (!enabled) {
continue;
}
if (!cg_controller_is_valid(controller)) {
return -EBADMSG;
}
if (r < 0)
return r;
}
return 0;
}
int cg_unified(void) {
/* Checks if we support the unified hierarchy. Returns an
* error when the cgroup hierarchies aren't mounted yet or we
* have any other trouble determining if the unified hierarchy
* is supported. */
if (unified_cache >= 0)
return unified_cache;
return -errno;
unified_cache = true;
unified_cache = false;
else
return -ENOEXEC;
return unified_cache;
}
void cg_unified_flush(void) {
unified_cache = -1;
}
int r, unified;
assert(p);
if (supported == 0)
return 0;
unified = cg_unified();
if (unified < 0)
return unified;
if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */
return 0;
if (r < 0)
return r;
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *n;
continue;
n = cgroup_controller_to_string(c);
{
strcpy(s + 1, n);
r = write_string_file(fs, s, 0);
if (r < 0)
}
}
return 0;
}
bool cg_is_unified_wanted(void) {
int r, unified;
/* If the hierarchy is already mounted, then follow whatever
* was chosen for it. */
unified = cg_unified();
if (unified >= 0)
return unified;
/* Otherwise, let's see what the kernel command line has to
* say. Since checking that is expensive, let's cache the
* result. */
if (wanted >= 0)
return wanted;
if (r > 0)
return (wanted = true);
else {
if (r < 0)
return false;
if (r == 0)
return (wanted = false);
}
}
bool cg_is_legacy_wanted(void) {
return !cg_is_unified_wanted();
}
static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
[CGROUP_CONTROLLER_CPU] = "cpu",
[CGROUP_CONTROLLER_CPUACCT] = "cpuacct",
[CGROUP_CONTROLLER_BLKIO] = "blkio",
[CGROUP_CONTROLLER_MEMORY] = "memory",
[CGROUP_CONTROLLER_DEVICE] = "devices",
};