cgroup.c revision 75399049653f2d5e22032da70cf96f20d7b4d9a6
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
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 "path-util.h"
#include "special.h"
#include "cgroup-util.h"
#include "cgroup.h"
void cgroup_context_init(CGroupContext *c) {
assert(c);
/* Initialize everything to the kernel defaults, assuming the
* structure is preinitialized to 0 */
c->cpu_shares = (unsigned long) -1;
c->startup_cpu_shares = (unsigned long) -1;
c->blockio_weight = (unsigned long) -1;
c->startup_blockio_weight = (unsigned long) -1;
}
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"
"%sCPUShares=%lu\n"
"%sStartupCPUShares=%lu\n"
"%sCPUQuotaPerSecSec=%s\n"
"%sBlockIOWeight=%lu\n"
"%sStartupBlockIOWeight=%lu\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 %lu",
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, CGroupControllerMask mask, const char *path, ManagerState state) {
bool is_root;
int r;
assert(c);
if (mask == 0)
return;
/* Some cgroup attributes are not support 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 != (unsigned long) -1 ? 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_BLKIO) {
if (!is_root) {
sprintf(buf, "%lu\n", IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_blockio_weight != (unsigned long) -1 ? c->startup_blockio_weight :
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);
}
}
} else
if (r < 0)
"Failed to set memory.limit_in_bytes 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
}
}
}
CGroupControllerMask mask = 0;
/* Figure out which controllers we need */
if (c->cpu_accounting ||
c->cpu_shares != (unsigned long) -1 ||
c->startup_cpu_shares != (unsigned long) -1 ||
if (c->blockio_accounting ||
c->blockio_weight != (unsigned long) -1 ||
c->startup_blockio_weight != (unsigned long) -1 ||
c->blockio_device_weights ||
mask |= CGROUP_BLKIO;
if (c->memory_accounting ||
mask |= CGROUP_MEMORY;
if (c->device_allow ||
c->device_policy != CGROUP_AUTO)
mask |= CGROUP_DEVICE;
return mask;
}
CGroupContext *c;
c = unit_get_cgroup_context(u);
if (!c)
return 0;
/* If delegation is turned on, then turn on all cgroups,
* unless the process we fork into it is known to drop
* privileges anyway, and shouldn't get access to the
* controllers anyway. */
if (c->delegate) {
ExecContext *e;
e = unit_get_exec_context(u);
if (!e || exec_context_maintains_privileges(e))
return _CGROUP_CONTROLLER_MASK_ALL;
}
return cgroup_context_get_mask(c);
}
assert(u);
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);
if (UNIT_ISSET(u->slice))
return unit_get_cgroup_mask(u) | 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) {
bool more;
assert(u);
/* Calculate subtree mask */
m = unit_get_cgroup_mask(u) | unit_get_members_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;
}
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();
if (r < 0) {
log_error(r == -EEXIST ? "cgroup %s exists already: %s" : "hashmap_put failed for %s: %s", path, strerror(-r));
return r;
}
if (r > 0) {
u->cgroup_path = path;
}
}
/* First, create our own group */
if (r < 0)
/* Keep track that this is now realized */
u->cgroup_realized = true;
u->cgroup_realized_mask = mask;
/* 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;
}
mask = unit_get_target_mask(u);
if (unit_has_mask_realized(u, mask))
return 0;
/* First, realize parents */
if (UNIT_ISSET(u->slice)) {
if (r < 0)
return r;
}
/* And then do the real work */
r = unit_create_cgroups(u, mask);
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) {
CGroupContext *c;
assert(u);
c = unit_get_cgroup_context(u);
if (!c)
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_destroy_cgroup_if_empty(Unit *u) {
int r;
assert(u);
if (!u->cgroup_path)
return;
r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !unit_has_name(u, SPECIAL_ROOT_SLICE));
if (r < 0) {
return;
}
free(u->cgroup_path);
u->cgroup_path = NULL;
u->cgroup_realized = false;
u->cgroup_realized_mask = 0;
}
assert(u);
if (!u->cgroup_path)
return 0;
return 0;
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. */
pid = 0;
break;
}
}
return pid;
}
int manager_setup_cgroup(Manager *m) {
int r;
assert(m);
/* 1. Determine hierarchy */
free(m->cgroup_root);
m->cgroup_root = NULL;
if (r < 0)
return log_error_errno(r, "Cannot determine cgroup we are running in: %m");
/* LEGACY: Already in /system.slice? If so, let's cut this
* off. This is to support live upgrades from older systemd
* versions where PID 1 was moved there. */
if (m->running_as == SYSTEMD_SYSTEM) {
char *e;
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. */
m->cgroup_root[0] = 0;
/* 2. Show data */
if (r < 0)
return log_error_errno(r, "Cannot find cgroup mount point: %m");
log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path);
if (!m->test_run) {
/* 3. Install agent */
if (m->running_as == SYSTEMD_SYSTEM) {
if (r < 0)
log_warning_errno(r, "Failed to install release agent, ignoring: %m");
else if (r > 0)
log_debug("Installed release agent.");
else
log_debug("Release agent already installed.");
}
/* 4. Make sure we are in the root cgroup */
if (r < 0)
return log_error_errno(r, "Failed to create root cgroup hierarchy: %m");
/* 5. And pin it, so that it cannot be unmounted */
if (m->pin_cgroupfs_fd < 0)
/* 6. Always enable hierarchial support if it exists... */
}
/* 7. Figure out which controllers are supported */
m->cgroup_supported = cg_mask_supported();
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)
free(m->cgroup_root);
m->cgroup_root = NULL;
}
char *p;
Unit *u;
assert(m);
if (u)
return u;
for (;;) {
char *e;
e = strrchr(p, '/');
if (e == p || !e)
return NULL;
*e = 0;
u = hashmap_get(m->cgroup_unit, p);
if (u)
return u;
}
}
int r;
assert(m);
if (pid <= 1)
return NULL;
if (r < 0)
return NULL;
return manager_get_unit_by_cgroup(m, cgroup);
}
Unit *u;
int r;
assert(m);
u = manager_get_unit_by_cgroup(m, cgroup);
if (u) {
if (r > 0) {
if (UNIT_VTABLE(u)->notify_cgroup_empty)
UNIT_VTABLE(u)->notify_cgroup_empty(u);
}
}
return 0;
}
static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
[CGROUP_AUTO] = "auto",
[CGROUP_CLOSED] = "closed",
[CGROUP_STRICT] = "strict",
};