cgroup.c revision 50f48ad37aad99c54de4db34b07c3825cdedcf41
/***
This file is part of systemd.
Copyright 2013 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 <fcntl.h>
#include <fnmatch.h>
#include "alloc-util.h"
#include "cgroup-util.h"
#include "cgroup.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "special.h"
#include "string-table.h"
#include "string-util.h"
void cgroup_context_init(CGroupContext *c) {
assert(c);
/* Initialize everything to the kernel defaults, assuming the
* structure is preinitialized to 0 */
}
assert(c);
assert(a);
free(a);
}
assert(c);
assert(w);
free(w);
}
void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) {
assert(c);
assert(b);
free(b);
}
void cgroup_context_done(CGroupContext *c) {
assert(c);
while (c->blockio_device_weights)
while (c->blockio_device_bandwidths)
while (c->device_allow)
}
char u[FORMAT_TIMESPAN_MAX];
assert(c);
assert(f);
fprintf(f,
"%sCPUAccounting=%s\n"
"%sBlockIOAccounting=%s\n"
"%sMemoryAccounting=%s\n"
"%sTasksAccounting=%s\n"
"%sCPUQuotaPerSecSec=%s\n"
"%sDevicePolicy=%s\n"
"%sDelegate=%s\n",
prefix, c->cpu_shares,
prefix, c->startup_cpu_shares,
prefix, c->blockio_weight,
prefix, c->memory_limit,
fprintf(f,
"%sDeviceAllow=%s %s%s%s\n",
a->path,
a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : "");
fprintf(f,
"%sBlockIODeviceWeight=%s %" PRIu64,
w->path,
w->weight);
char buf[FORMAT_BYTES_MAX];
fprintf(f,
"%s%s=%s %s\n",
b->path,
}
}
int r;
assert(p);
if (r < 0)
/* If this is not a device node then find the block
* device this file is stored on */
/* If this is a partition, try to get the originating
* block device */
} else {
log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p);
return -ENODEV;
}
return 0;
}
int r;
return -errno;
}
return -ENODEV;
}
"%c %u:%u %s",
acc);
if (r < 0)
"Failed to set devices.allow on %s: %m", path);
return r;
}
bool good = false;
int r;
if (!f)
unsigned maj;
good = true;
continue;
}
good = true;
continue;
}
good = false;
continue;
}
if (!good)
continue;
w = strpbrk(p, WHITESPACE);
if (!w)
continue;
*w = 0;
if (r < 0)
continue;
if (maj <= 0)
continue;
w++;
w += strspn(w, WHITESPACE);
continue;
"%c %u:* %s",
type,
maj,
acc);
if (r < 0)
"Failed to set devices.allow on %s: %m", path);
}
return 0;
fail:
return -errno;
}
void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state) {
bool is_root;
int r;
assert(c);
if (mask == 0)
return;
/* Some cgroup attributes are not supported on the root cgroup,
* hence silently ignore */
if (is_root)
/* Make sure we don't try to display messages with an empty path. */
path = "/";
/* We generally ignore errors caused by read-only mounted
* cgroup trees (assuming we are running in a container then),
* and missing cgroups, i.e. EROFS and ENOENT. */
IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->startup_cpu_shares :
if (r < 0)
if (r < 0)
if (c->cpu_quota_per_sec_usec != USEC_INFINITY) {
sprintf(buf, USEC_FMT "\n", c->cpu_quota_per_sec_usec * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC);
} else
if (r < 0)
}
if (mask & CGROUP_MASK_BLKIO) {
if (!is_root) {
IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID ? c->startup_blockio_weight :
c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID ? c->blockio_weight : CGROUP_BLKIO_WEIGHT_DEFAULT);
if (r < 0)
"Failed to set blkio.weight on %s: %m", path);
/* FIXME: no way to reset this list */
if (r < 0)
continue;
if (r < 0)
"Failed to set blkio.weight_device on %s: %m", path);
}
}
/* FIXME: no way to reset this list */
const char *a;
if (r < 0)
continue;
if (r < 0)
"Failed to set %s on %s: %m", a, path);
}
}
if (cg_unified() <= 0)
else
} else {
if (cg_unified() <= 0)
else
}
if (r < 0)
"Failed to set memory.limit_in_bytes/memory.max on %s: %m", path);
}
/* Changing the devices list of a populated cgroup
* might result in EINVAL, hence ignore EINVAL
* here. */
else
if (r < 0)
"Failed to reset devices.list on %s: %m", path);
if (c->device_policy == CGROUP_CLOSED ||
static const char auto_devices[] =
const char *x, *y;
NULSTR_FOREACH_PAIR(x, y, auto_devices)
whitelist_device(path, x, y);
}
char acc[4];
unsigned k = 0;
if (a->r)
acc[k++] = 'r';
if (a->w)
acc[k++] = 'w';
if (a->m)
acc[k++] = 'm';
if (k == 0)
continue;
acc[k++] = 0;
else
}
}
} else
if (r < 0)
"Failed to set pids.max on %s: %m", path);
}
}
CGroupMask mask = 0;
/* Figure out which controllers we need */
if (c->cpu_accounting ||
c->cpu_shares != CGROUP_CPU_SHARES_INVALID ||
if (c->blockio_accounting ||
c->blockio_device_weights ||
if (c->memory_accounting ||
if (c->device_allow ||
c->device_policy != CGROUP_AUTO)
if (c->tasks_accounting ||
mask |= CGROUP_MASK_PIDS;
return mask;
}
CGroupContext *c;
/* Returns the mask of controllers the unit needs for itself */
c = unit_get_cgroup_context(u);
if (!c)
return 0;
/* If delegation is turned on, then turn on all cgroups,
* unless we are on the legacy hierarchy and the process we
* fork into it is known to drop privileges, and hence
* shouldn't get access to the controllers.
*
* Note that on the unified hierarchy it is safe to delegate
* controllers to unprivileged services. */
if (c->delegate) {
ExecContext *e;
e = unit_get_exec_context(u);
if (!e ||
cg_unified() > 0)
return _CGROUP_MASK_ALL;
}
return cgroup_context_get_mask(c);
}
assert(u);
/* Returns the mask of controllers all of the unit's children
* require, merged */
if (u->cgroup_members_mask_valid)
return u->cgroup_members_mask;
u->cgroup_members_mask = 0;
if (u->type == UNIT_SLICE) {
Iterator i;
if (member == u)
continue;
continue;
u->cgroup_members_mask |=
}
}
u->cgroup_members_mask_valid = true;
return u->cgroup_members_mask;
}
assert(u);
/* Returns the mask of controllers all of the unit's siblings
* require, i.e. the members mask of the unit's parent slice
* if there is one. */
if (UNIT_ISSET(u->slice))
return unit_get_own_mask(u) | unit_get_members_mask(u);
}
/* Returns the mask of this subtree, meaning of the group
* itself and its children. */
return unit_get_own_mask(u) | unit_get_members_mask(u);
}
/* This returns the cgroup mask of all controllers to enable
* for a specific cgroup, i.e. everything it needs itself,
* plus all that its children need, plus all that its siblings
* need. This is primarily useful on the legacy cgroup
* hierarchy, where we need to duplicate each cgroup in each
* hierarchy that shall be enabled for it. */
return mask;
}
/* This returns the cgroup mask of all controllers to enable
* for the children of a specific cgroup. This is primarily
* useful for the unified cgroup hierarchy, where each cgroup
* controls which controllers are enabled for its children. */
mask = unit_get_members_mask(u);
return mask;
}
/* Recurse from a unit up through its containing slices, propagating
* mask bits upward. A unit is also member of itself. */
void unit_update_cgroup_members_masks(Unit *u) {
CGroupMask m;
bool more;
assert(u);
/* Calculate subtree mask */
m = unit_get_subtree_mask(u);
/* See if anything changed from the previous invocation. If
* not, we're done. */
if (u->cgroup_subtree_mask_valid && m == u->cgroup_subtree_mask)
return;
more =
((m & ~u->cgroup_subtree_mask) != 0) &&
((~m & u->cgroup_subtree_mask) == 0);
u->cgroup_subtree_mask = m;
u->cgroup_subtree_mask_valid = true;
if (UNIT_ISSET(u->slice)) {
if (more)
/* There's more set now than before. We
* propagate the new mask to the parent's mask
* (not caring if it actually was valid or
* not). */
s->cgroup_members_mask |= m;
else
/* There's less set now than before (or we
* don't know), we need to recalculate
* everything, so let's invalidate the
* parent's members mask */
s->cgroup_members_mask_valid = false;
/* And now make sure that this change also hits our
* grandparents */
}
}
assert(u);
while (u) {
if (u->cgroup_path &&
u->cgroup_realized &&
return u->cgroup_path;
u = UNIT_DEREF(u->slice);
}
return NULL;
}
char *unit_default_cgroup_path(Unit *u) {
int r;
assert(u);
if (unit_has_name(u, SPECIAL_ROOT_SLICE))
if (r < 0)
return NULL;
}
if (!escaped)
return NULL;
if (slice)
else
}
_cleanup_free_ char *p = NULL;
int r;
assert(u);
if (path) {
if (!p)
return -ENOMEM;
} else
p = NULL;
if (streq_ptr(u->cgroup_path, p))
return 0;
if (p) {
if (r < 0)
return r;
}
u->cgroup_path = p;
p = NULL;
return 1;
}
int unit_watch_cgroup(Unit *u) {
int r;
assert(u);
if (!u->cgroup_path)
return 0;
if (u->cgroup_inotify_wd >= 0)
return 0;
/* Only applies to the unified hierarchy */
r = cg_unified();
if (r < 0)
return log_unit_error_errno(u, r, "Failed detect wether the unified hierarchy is used: %m");
if (r == 0)
return 0;
/* Don't watch the root slice, it's pointless. */
if (unit_has_name(u, SPECIAL_ROOT_SLICE))
return 0;
if (r < 0)
return log_oom();
if (r < 0)
return log_oom();
if (u->cgroup_inotify_wd < 0) {
* gone we don't need to track
* it, so this is not an error */
return 0;
return log_unit_error_errno(u, errno, "Failed to add inotify watch descriptor for control group %s: %m", u->cgroup_path);
}
if (r < 0)
return log_unit_error_errno(u, r, "Failed to add inotify watch descriptor to hash map: %m");
return 0;
}
static int unit_create_cgroup(
Unit *u,
CGroupContext *c;
int r;
assert(u);
c = unit_get_cgroup_context(u);
if (!c)
return 0;
if (!u->cgroup_path) {
path = unit_default_cgroup_path(u);
if (!path)
return log_oom();
r = unit_set_cgroup_path(u, path);
if (r == -EEXIST)
if (r < 0)
}
/* First, create our own group */
if (r < 0)
/* Start watching it */
(void) unit_watch_cgroup(u);
/* Enable all controllers we need */
if (r < 0)
log_unit_warning_errno(u, r, "Failed to enable controllers on cgroup %s, ignoring: %m", u->cgroup_path);
/* Keep track that this is now realized */
u->cgroup_realized = true;
/* Then, possibly move things over, but not if
* subgroups may contain processes, which is the case
* for slice and delegation units. */
r = cg_migrate_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->cgroup_path, migrate_callback, u);
if (r < 0)
}
return 0;
}
int unit_attach_pids_to_cgroup(Unit *u) {
int r;
assert(u);
r = unit_realize_cgroup(u);
if (r < 0)
return r;
r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->pids, migrate_callback, u);
if (r < 0)
return r;
return 0;
}
assert(u);
}
/* Check if necessary controllers and attributes for a unit are in place.
*
* If so, do nothing.
* If not, create paths, move processes over, and set attributes.
*
* Returns 0 on success and < 0 on failure. */
int r;
assert(u);
if (u->in_cgroup_queue) {
u->in_cgroup_queue = false;
}
if (unit_has_mask_realized(u, target_mask))
return 0;
/* First, realize parents */
if (UNIT_ISSET(u->slice)) {
if (r < 0)
return r;
}
/* And then do the real work */
if (r < 0)
return r;
/* Finally, apply the necessary attributes. */
return 0;
}
static void unit_add_to_cgroup_queue(Unit *u) {
if (u->in_cgroup_queue)
return;
u->in_cgroup_queue = true;
}
unsigned manager_dispatch_cgroup_queue(Manager *m) {
unsigned n = 0;
Unit *i;
int r;
state = manager_state(m);
while ((i = m->cgroup_queue)) {
assert(i->in_cgroup_queue);
r = unit_realize_cgroup_now(i, state);
if (r < 0)
n++;
}
return n;
}
static void unit_queue_siblings(Unit *u) {
/* This adds the siblings of the specified unit and the
* siblings of all parent units to the cgroup queue. (But
* neither the specified unit itself nor the parents.) */
Iterator i;
Unit *m;
if (m == u)
continue;
/* Skip units that have a dependency on the slice
* but aren't actually in it. */
continue;
/* No point in doing cgroup application for units
* without active processes. */
continue;
/* If the unit doesn't need any new controllers
* and has current ones realized, it doesn't need
* any changes. */
if (unit_has_mask_realized(m, unit_get_target_mask(m)))
continue;
}
u = slice;
}
}
int unit_realize_cgroup(Unit *u) {
assert(u);
if (!UNIT_HAS_CGROUP_CONTEXT(u))
return 0;
/* So, here's the deal: when realizing the cgroups for this
* unit, we need to first create all parents, but there's more
* actually: for the weight-based controllers we also need to
* make sure that all our siblings (i.e. units that are in the
* same slice as we are) have cgroups, too. Otherwise, things
* would become very uneven as each of their processes would
* get as much resources as all our group together. This call
* will synchronously create the parent cgroups, but will
* defer work on the siblings to the next event loop
* iteration. */
/* Add all sibling slices to the cgroup queue. */
/* And realize this one now (and apply the values) */
}
void unit_release_cgroup(Unit *u) {
assert(u);
/* Forgets all cgroup details for this cgroup */
if (u->cgroup_path) {
}
if (u->cgroup_inotify_wd >= 0) {
log_unit_debug_errno(u, errno, "Failed to remove cgroup inotify watch %i for %s, ignoring", u->cgroup_inotify_wd, u->id);
u->cgroup_inotify_wd = -1;
}
}
void unit_prune_cgroup(Unit *u) {
int r;
bool is_root_slice;
assert(u);
/* Removes the cgroup, if empty and possible, and stops watching it. */
if (!u->cgroup_path)
return;
if (r < 0) {
return;
}
if (is_root_slice)
return;
u->cgroup_realized = false;
u->cgroup_realized_mask = 0;
}
int r;
assert(u);
if (!u->cgroup_path)
return -ENXIO;
if (r < 0)
return r;
while (cg_read_pid(f, &npid) > 0) {
continue;
/* Ignore processes that aren't our kids */
continue;
if (pid != 0)
/* Dang, there's more than one daemonized PID
in this group, so we don't know what process
is the main process. */
return -ENODATA;
}
return 0;
}
int ret = 0, r;
assert(u);
if (r < 0)
ret = r;
else {
while ((r = cg_read_pid(f, &pid)) > 0) {
r = unit_watch_pid(u, pid);
if (r < 0 && ret >= 0)
ret = r;
}
if (r < 0 && ret >= 0)
ret = r;
}
if (r < 0) {
if (ret >= 0)
ret = r;
} else {
char *fn;
while ((r = cg_read_subgroup(d, &fn)) > 0) {
_cleanup_free_ char *p = NULL;
if (!p)
return -ENOMEM;
r = unit_watch_pids_in_path(u, p);
if (r < 0 && ret >= 0)
ret = r;
}
if (r < 0 && ret >= 0)
ret = r;
}
return ret;
}
int unit_watch_all_pids(Unit *u) {
assert(u);
/* Adds all PIDs from our cgroup to the set of PIDs we
* watch. This is a fallback logic for cases where we do not
* get reliable cgroup empty notifications: we try to use
* SIGCHLD as replacement. */
if (!u->cgroup_path)
return -ENOENT;
if (cg_unified() > 0) /* On unified we can use proper notifications */
return 0;
return unit_watch_pids_in_path(u, u->cgroup_path);
}
int unit_notify_cgroup_empty(Unit *u) {
int r;
assert(u);
if (!u->cgroup_path)
return 0;
if (r <= 0)
return r;
if (UNIT_VTABLE(u)->notify_cgroup_empty)
UNIT_VTABLE(u)->notify_cgroup_empty(u);
return 0;
}
assert(s);
assert(m);
for (;;) {
union inotify_event_buffer buffer;
struct inotify_event *e;
ssize_t l;
if (l < 0) {
return 0;
}
FOREACH_INOTIFY_EVENT(e, buffer, l) {
Unit *u;
if (e->wd < 0)
/* Queue overflow has no watch descriptor */
continue;
if (e->mask & IN_IGNORED)
/* The watch was just removed */
continue;
if (!u) /* Not that inotify might deliver
* events for a watch even after it
* was removed, because it was queued
* before the removal. Let's ignore
* this here safely. */
continue;
(void) unit_notify_cgroup_empty(u);
}
}
}
int manager_setup_cgroup(Manager *m) {
int r, unified;
char *e;
assert(m);
/* 1. Determine hierarchy */
if (r < 0)
return log_error_errno(r, "Cannot determine cgroup we are running in: %m");
/* Chop off the init scope, if we are already located in it */
/* LEGACY: Also chop off the system slice if we are in
* it. This is to support live upgrades from older systemd
* versions where PID 1 was moved there. Also see
* cg_get_root_path(). */
if (!e && m->running_as == MANAGER_SYSTEM) {
if (!e)
}
if (e)
*e = 0;
/* And make sure to store away the root value without trailing
* slash, even for the root dir, so that we can easily prepend
* it everywhere. */
*e = 0;
/* 2. Show data */
if (r < 0)
return log_error_errno(r, "Cannot find cgroup mount point: %m");
unified = cg_unified();
if (unified < 0)
return log_error_errno(r, "Couldn't determine if we are running in the unified hierarchy: %m");
if (unified > 0)
else
log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path);
if (!m->test_run) {
const char *scope_path;
/* 3. Install agent */
if (unified) {
/* In the unified hierarchy we can can get
* cgroup empty notifications via inotify. */
if (m->cgroup_inotify_fd < 0)
r = sd_event_add_io(m->event, &m->cgroup_inotify_event_source, m->cgroup_inotify_fd, EPOLLIN, on_cgroup_inotify_event, m);
if (r < 0)
return log_error_errno(r, "Failed to watch control group inotify object: %m");
if (r < 0)
return log_error_errno(r, "Failed to set priority of inotify event source: %m");
} else if (m->running_as == MANAGER_SYSTEM) {
/* On the legacy hierarchy we only get
* notifications via cgroup agents. (Which
* isn't really reliable, since it does not
* generate events when control groups with
* children run empty. */
if (r < 0)
log_warning_errno(r, "Failed to install release agent, ignoring: %m");
else if (r > 0)
log_debug("Installed release agent.");
else if (r == 0)
log_debug("Release agent already installed.");
}
/* 4. Make sure we are in the special "init.scope" unit in the root slice. */
if (r < 0)
/* also, move all other userspace processes remaining
* in the root cgroup into that scope. */
r = cg_migrate(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, SYSTEMD_CGROUP_CONTROLLER, scope_path, false);
if (r < 0)
log_warning_errno(r, "Couldn't move remaining userspace processes, ignoring: %m");
/* 5. And pin it, so that it cannot be unmounted */
if (m->pin_cgroupfs_fd < 0)
/* 6. Always enable hierarchical support if it exists... */
if (!unified)
}
/* 7. Figure out which controllers are supported */
r = cg_mask_supported(&m->cgroup_supported);
if (r < 0)
return log_error_errno(r, "Failed to determine supported controllers: %m");
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++)
log_debug("Controller '%s' supported: %s", cgroup_controller_to_string(c), yes_no(m->cgroup_supported & c));
return 0;
}
assert(m);
/* We can't really delete the group, since we are in it. But
* let's trim it. */
if (delete && m->cgroup_root)
}
char *p;
Unit *u;
assert(m);
if (u)
return u;
for (;;) {
char *e;
e = strrchr(p, '/');
if (!e || e == p)
*e = 0;
u = hashmap_get(m->cgroup_unit, p);
if (u)
return u;
}
}
int r;
assert(m);
if (pid <= 0)
return NULL;
if (r < 0)
return NULL;
return manager_get_unit_by_cgroup(m, cgroup);
}
Unit *u;
assert(m);
if (pid <= 0)
return NULL;
if (pid == 1)
if (u)
return u;
if (u)
return u;
return manager_get_unit_by_pid_cgroup(m, pid);
}
Unit *u;
assert(m);
u = manager_get_unit_by_cgroup(m, cgroup);
if (!u)
return 0;
return unit_notify_cgroup_empty(u);
}
_cleanup_free_ char *v = NULL;
int r;
assert(u);
if (!u->cgroup_path)
return -ENODATA;
if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0)
return -ENODATA;
if (cg_unified() <= 0)
else
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
return safe_atou64(v, ret);
}
_cleanup_free_ char *v = NULL;
int r;
assert(u);
if (!u->cgroup_path)
return -ENODATA;
if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0)
return -ENODATA;
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
return safe_atou64(v, ret);
}
_cleanup_free_ char *v = NULL;
int r;
assert(u);
if (!u->cgroup_path)
return -ENODATA;
if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0)
return -ENODATA;
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
r = safe_atou64(v, &ns);
if (r < 0)
return r;
return 0;
}
int r;
r = unit_get_cpu_usage_raw(u, &ns);
if (r < 0)
return r;
if (ns > u->cpuacct_usage_base)
ns -= u->cpuacct_usage_base;
else
ns = 0;
return 0;
}
int unit_reset_cpu_usage(Unit *u) {
int r;
assert(u);
r = unit_get_cpu_usage_raw(u, &ns);
if (r < 0) {
u->cpuacct_usage_base = 0;
return r;
}
u->cpuacct_usage_base = ns;
return 0;
}
bool unit_cgroup_delegate(Unit *u) {
CGroupContext *c;
assert(u);
c = unit_get_cgroup_context(u);
if (!c)
return false;
return c->delegate;
}
assert(u);
if (!UNIT_HAS_CGROUP_CONTEXT(u))
return;
if (m == 0)
return;
if ((u->cgroup_realized_mask & m) == 0)
return;
u->cgroup_realized_mask &= ~m;
}
void manager_invalidate_startup_units(Manager *m) {
Iterator i;
Unit *u;
assert(m);
SET_FOREACH(u, m->startup_units, i)
}
static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
[CGROUP_AUTO] = "auto",
[CGROUP_CLOSED] = "closed",
[CGROUP_STRICT] = "strict",
};