2014-05-08 16:37:00 +04:00
|
|
|
#define LOG_PREFIX "cg: "
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mount.h>
|
|
|
|
#include <sys/stat.h>
|
2014-07-10 17:00:28 +04:00
|
|
|
#include <ftw.h>
|
|
|
|
#include <libgen.h>
|
|
|
|
#include "list.h"
|
2014-05-08 16:37:00 +04:00
|
|
|
#include "xmalloc.h"
|
|
|
|
#include "cgroup.h"
|
2014-08-06 22:23:00 +04:00
|
|
|
#include "cr_options.h"
|
2014-05-08 16:37:00 +04:00
|
|
|
#include "pstree.h"
|
|
|
|
#include "proc_parse.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "fdset.h"
|
2014-07-16 17:46:00 +04:00
|
|
|
#include "util-pie.h"
|
2014-05-08 16:37:00 +04:00
|
|
|
#include "protobuf.h"
|
|
|
|
#include "protobuf/core.pb-c.h"
|
|
|
|
#include "protobuf/cgroup.pb-c.h"
|
2014-05-08 16:42:40 +04:00
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
/*
|
|
|
|
* These string arrays have the names of all the properties that will be
|
|
|
|
* restored. To add a property for a cgroup type, add it to the
|
|
|
|
* corresponding char array above the NULL terminator. If you are adding
|
|
|
|
* a new cgroup family all together, you must also edit get_known_properties()
|
|
|
|
* Currently the code only supports properties with 1 value
|
|
|
|
*/
|
|
|
|
|
|
|
|
static const char *cpu_props[] = {
|
|
|
|
"cpu.shares",
|
2014-08-07 15:26:14 -07:00
|
|
|
"cpu.cfs_period_us",
|
|
|
|
"cpu.cfs_quota_us",
|
|
|
|
"cpu.rt_period_us",
|
|
|
|
"cpu.rt_runtime_us",
|
|
|
|
"notify_on_release",
|
2014-08-07 15:26:13 -07:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *memory_props[] = {
|
2014-08-07 15:26:14 -07:00
|
|
|
/* limit_in_bytes and memsw.limit_in_bytes must be set in this order */
|
2014-08-07 15:26:13 -07:00
|
|
|
"memory.limit_in_bytes",
|
2014-08-07 15:26:14 -07:00
|
|
|
"memory.memsw.limit_in_bytes",
|
|
|
|
"memory.use_hierarchy",
|
|
|
|
"notify_on_release",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *cpuset_props[] = {
|
|
|
|
/*
|
|
|
|
* cpuset.cpus and cpuset.mems must be set before the process moves
|
2014-08-22 14:22:32 +00:00
|
|
|
* into its cgroup; they are "initialized" below to whatever the root
|
|
|
|
* values are in copy_special_cg_props so as not to cause ENOSPC when
|
|
|
|
* values are restored via this code.
|
2014-08-07 15:26:14 -07:00
|
|
|
*/
|
2014-08-22 14:22:32 +00:00
|
|
|
"cpuset.cpus",
|
|
|
|
"cpuset.mems",
|
2014-08-07 15:26:14 -07:00
|
|
|
"cpuset.memory_migrate",
|
|
|
|
"cpuset.cpu_exclusive",
|
|
|
|
"cpuset.mem_exclusive",
|
|
|
|
"cpuset.mem_hardwall",
|
|
|
|
"cpuset.memory_spread_page",
|
|
|
|
"cpuset.memory_spread_slab",
|
|
|
|
"cpuset.sched_load_balance",
|
|
|
|
"cpuset.sched_relax_domain_level",
|
|
|
|
"notify_on_release",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *blkio_props[] = {
|
|
|
|
"blkio.weight",
|
|
|
|
"notify_on_release",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *freezer_props[] = {
|
|
|
|
"notify_on_release",
|
2014-08-07 15:26:13 -07:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2014-05-08 16:42:40 +04:00
|
|
|
/*
|
|
|
|
* This structure describes set of controller groups
|
|
|
|
* a task lives in. The cg_ctl entries are stored in
|
2014-07-10 17:00:28 +04:00
|
|
|
* the @ctls list sorted by the .name field and then
|
|
|
|
* by the .path field.
|
2014-05-08 16:42:40 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
struct cg_set {
|
|
|
|
u32 id;
|
|
|
|
struct list_head l;
|
|
|
|
unsigned int n_ctls;
|
|
|
|
struct list_head ctls;
|
|
|
|
};
|
|
|
|
|
|
|
|
static LIST_HEAD(cg_sets);
|
|
|
|
static unsigned int n_sets;
|
2014-05-08 16:55:53 +04:00
|
|
|
static CgSetEntry **rst_sets;
|
2014-07-10 17:00:28 +04:00
|
|
|
static unsigned int n_controllers;
|
|
|
|
static CgControllerEntry **controllers;
|
2014-05-08 16:55:53 +04:00
|
|
|
static char *cg_yard;
|
2014-05-08 16:42:40 +04:00
|
|
|
static struct cg_set *root_cgset; /* Set root item lives in */
|
|
|
|
static struct cg_set *criu_cgset; /* Set criu process lives in */
|
|
|
|
static u32 cg_set_ids = 1;
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
static LIST_HEAD(cgroups);
|
|
|
|
static unsigned int n_cgroups;
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
static CgSetEntry *find_rst_set_by_id(u32 id)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < n_sets; i++)
|
|
|
|
if (rst_sets[i]->id == id)
|
|
|
|
return rst_sets[i];
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-05-08 16:42:40 +04:00
|
|
|
#define CGCMP_MATCH 1 /* check for exact match */
|
|
|
|
#define CGCMP_ISSUB 2 /* check set is subset of ctls */
|
|
|
|
|
|
|
|
static bool cg_set_compare(struct cg_set *set, struct list_head *ctls, int what)
|
|
|
|
{
|
|
|
|
struct list_head *l1 = &set->ctls, *l2 = ctls;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
struct cg_ctl *c1 = NULL, *c2 = NULL;
|
|
|
|
|
|
|
|
if (l1->next != &set->ctls)
|
|
|
|
c1 = list_first_entry(l1, struct cg_ctl, l);
|
|
|
|
if (l2->next != ctls)
|
|
|
|
c2 = list_first_entry(l2, struct cg_ctl, l);
|
|
|
|
|
|
|
|
if (!c1 || !c2) /* Nowhere to move next */
|
|
|
|
return !c1 && !c2; /* Both lists scanned -- match */
|
|
|
|
|
|
|
|
if (strcmp(c1->name, c2->name))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (what) {
|
|
|
|
case CGCMP_MATCH:
|
|
|
|
if (strcmp(c1->path, c2->path))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
case CGCMP_ISSUB:
|
|
|
|
if (!strstartswith(c1->path, c2->path))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
l1 = l1->next;
|
|
|
|
l2 = l2->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct cg_set *get_cg_set(struct list_head *ctls, unsigned int n_ctls)
|
|
|
|
{
|
|
|
|
struct cg_set *cs;
|
|
|
|
|
|
|
|
list_for_each_entry(cs, &cg_sets, l)
|
|
|
|
if (cg_set_compare(cs, ctls, CGCMP_MATCH)) {
|
|
|
|
pr_debug(" `- Existing css %d found\n", cs->id);
|
|
|
|
put_ctls(ctls);
|
|
|
|
return cs;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug(" `- New css ID %d\n", cg_set_ids);
|
|
|
|
cs = xmalloc(sizeof(*cs));
|
|
|
|
if (cs) {
|
|
|
|
cs->id = cg_set_ids++;
|
|
|
|
INIT_LIST_HEAD(&cs->ctls);
|
|
|
|
list_splice(ctls, &cs->ctls);
|
|
|
|
cs->n_ctls = n_ctls;
|
|
|
|
list_add_tail(&cs->l, &cg_sets);
|
|
|
|
n_sets++;
|
|
|
|
|
2014-09-02 23:27:20 +04:00
|
|
|
if (!pr_quelled(LOG_DEBUG)) {
|
2014-05-08 16:42:40 +04:00
|
|
|
struct cg_ctl *ctl;
|
|
|
|
|
|
|
|
list_for_each_entry(ctl, &cs->ctls, l)
|
|
|
|
pr_debug(" `- [%s] -> [%s]\n", ctl->name, ctl->path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cs;
|
|
|
|
}
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
struct cg_controller *new_controller(const char *name, int heirarchy)
|
|
|
|
{
|
|
|
|
struct cg_controller *nc = xmalloc(sizeof(*nc));
|
|
|
|
if (!nc)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
nc->controllers = xmalloc(sizeof(char *));
|
|
|
|
if (!nc->controllers) {
|
|
|
|
xfree(nc);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
nc->controllers[0] = xstrdup(name);
|
|
|
|
if (!nc->controllers[0]) {
|
|
|
|
xfree(nc->controllers);
|
|
|
|
xfree(nc);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
nc->n_controllers = 1;
|
|
|
|
nc->heirarchy = heirarchy;
|
|
|
|
|
|
|
|
nc->n_heads = 0;
|
|
|
|
INIT_LIST_HEAD(&nc->heads);
|
|
|
|
|
|
|
|
return nc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int parse_cg_info(void)
|
|
|
|
{
|
|
|
|
if (parse_cgroups(&cgroups, &n_cgroups) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check that co-mounted controllers from /proc/cgroups (e.g. cpu and cpuacct)
|
2014-07-11 20:11:00 +04:00
|
|
|
* are contained in a comma separated string (e.g. from /proc/self/cgroup or
|
|
|
|
* mount options). */
|
2014-07-14 17:39:42 +04:00
|
|
|
static bool cgroup_contains(char **controllers, unsigned int n_controllers, char *name)
|
2014-07-10 17:00:28 +04:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
bool all_match = true;
|
|
|
|
for (i = 0; i < n_controllers; i++) {
|
|
|
|
bool found = false;
|
|
|
|
const char *loc = name;
|
|
|
|
do {
|
|
|
|
loc = strstr(loc, controllers[i]);
|
|
|
|
if (loc) {
|
|
|
|
loc += strlen(controllers[i]);
|
|
|
|
switch (*loc) {
|
|
|
|
case '\0':
|
|
|
|
case ',':
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (loc);
|
|
|
|
all_match &= found;
|
|
|
|
}
|
|
|
|
|
|
|
|
return all_match && n_controllers > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is for use in add_cgroup() as additional arguments for the ftw()
|
|
|
|
* callback */
|
|
|
|
static struct cg_controller *current_controller;
|
2014-07-16 19:48:44 +04:00
|
|
|
static unsigned int path_pref_len;
|
2014-07-10 17:00:28 +04:00
|
|
|
|
|
|
|
#define EXACT_MATCH 0
|
|
|
|
#define PARENT_MATCH 1
|
|
|
|
#define NO_MATCH 2
|
|
|
|
|
|
|
|
static int find_dir(const char *path, struct list_head *dirs, struct cgroup_dir **rdir)
|
|
|
|
{
|
|
|
|
struct cgroup_dir *d;
|
|
|
|
list_for_each_entry(d, dirs, siblings) {
|
|
|
|
if (strcmp(d->path, path) == 0) {
|
|
|
|
*rdir = d;
|
|
|
|
return EXACT_MATCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strstartswith(path, d->path)) {
|
|
|
|
int ret = find_dir(path, &d->children, rdir);
|
|
|
|
if (ret == NO_MATCH) {
|
|
|
|
*rdir = d;
|
|
|
|
return PARENT_MATCH;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NO_MATCH;
|
|
|
|
}
|
|
|
|
|
2014-08-12 17:19:01 +04:00
|
|
|
/*
|
|
|
|
* Strips trailing '\n' from the string
|
|
|
|
*/
|
|
|
|
static inline char *strip(char *str)
|
|
|
|
{
|
|
|
|
char *e;
|
|
|
|
|
|
|
|
e = strchr(str, '\0');
|
|
|
|
if (e != str && *(e - 1) == '\n')
|
|
|
|
*(e - 1) = '\0';
|
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
/*
|
2014-08-22 14:22:31 +00:00
|
|
|
* Currently this function only supports properties that have a string value
|
|
|
|
* under 1024 chars.
|
2014-08-07 15:26:13 -07:00
|
|
|
*/
|
2014-08-14 22:39:00 +04:00
|
|
|
static int read_cgroup_prop(struct cgroup_prop *property, const char *fullpath)
|
2014-08-07 15:26:13 -07:00
|
|
|
{
|
2014-08-22 14:22:31 +00:00
|
|
|
char buf[1024];
|
2014-08-07 15:26:13 -07:00
|
|
|
FILE *f;
|
2014-08-13 22:59:00 +04:00
|
|
|
char *endptr;
|
2014-08-07 15:26:13 -07:00
|
|
|
|
2014-08-14 22:39:00 +04:00
|
|
|
f = fopen(fullpath, "r");
|
2014-08-07 15:26:13 -07:00
|
|
|
if (!f) {
|
|
|
|
property->value = NULL;
|
2014-08-20 14:52:00 +04:00
|
|
|
pr_perror("Failed opening %s", fullpath);
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
if (fread(buf, sizeof(buf), 1, f) != 1) {
|
|
|
|
if (!feof(f)) {
|
2014-08-14 22:39:00 +04:00
|
|
|
pr_err("Failed scanning %s\n", fullpath);
|
2014-08-07 15:26:13 -07:00
|
|
|
fclose(f);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fclose(f) != 0) {
|
2014-08-14 22:39:00 +04:00
|
|
|
pr_err("Failed closing %s\n", fullpath);
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-13 22:59:00 +04:00
|
|
|
if (strtoll(buf, &endptr, 10) == LLONG_MAX)
|
|
|
|
strcpy(buf, "-1");
|
|
|
|
|
2014-08-12 17:19:01 +04:00
|
|
|
property->value = xstrdup(strip(buf));
|
2014-08-07 15:26:13 -07:00
|
|
|
if (!property->value)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct cgroup_prop *create_cgroup_prop(const char *name)
|
|
|
|
{
|
|
|
|
struct cgroup_prop *property;
|
|
|
|
|
|
|
|
property = xmalloc(sizeof(*property));
|
|
|
|
if (!property)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
property->name = xstrdup(name);
|
|
|
|
if (!property->name) {
|
|
|
|
xfree(property);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
property->value = NULL;
|
|
|
|
return property;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_cgroup_prop(struct cgroup_prop *prop)
|
|
|
|
{
|
|
|
|
xfree(prop->name);
|
|
|
|
xfree(prop->value);
|
|
|
|
xfree(prop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_all_cgroup_props(struct cgroup_dir *ncd)
|
|
|
|
{
|
|
|
|
struct cgroup_prop *prop;
|
|
|
|
|
|
|
|
list_for_each_entry(prop, &ncd->properties, list) {
|
|
|
|
free_cgroup_prop(prop);
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&ncd->properties);
|
|
|
|
ncd->n_properties = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char **get_known_properties(char *controller)
|
|
|
|
{
|
|
|
|
const char **prop_arr = NULL;
|
|
|
|
|
|
|
|
if (!strcmp(controller, "cpu"))
|
|
|
|
prop_arr = cpu_props;
|
|
|
|
else if (!strcmp(controller, "memory"))
|
|
|
|
prop_arr = memory_props;
|
2014-08-07 15:26:14 -07:00
|
|
|
else if (!strcmp(controller, "cpuset"))
|
|
|
|
prop_arr = cpuset_props;
|
|
|
|
else if (!strcmp(controller, "blkio"))
|
|
|
|
prop_arr = blkio_props;
|
|
|
|
else if (!strcmp(controller, "freezer"))
|
|
|
|
prop_arr = freezer_props;
|
2014-08-07 15:26:13 -07:00
|
|
|
|
|
|
|
return prop_arr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_cgroup_properties(const char *fpath, struct cgroup_dir *ncd,
|
|
|
|
struct cg_controller *controller)
|
|
|
|
{
|
|
|
|
int i, j;
|
2014-08-14 22:39:00 +04:00
|
|
|
char buf[PATH_MAX];
|
2014-08-07 15:26:13 -07:00
|
|
|
struct cgroup_prop *prop;
|
|
|
|
|
|
|
|
for (i = 0; i < controller->n_controllers; ++i) {
|
|
|
|
|
|
|
|
const char **prop_arr = get_known_properties(controller->controllers[i]);
|
|
|
|
|
|
|
|
for (j = 0; prop_arr != NULL && prop_arr[j] != NULL; ++j) {
|
2014-08-14 22:39:00 +04:00
|
|
|
if (snprintf(buf, PATH_MAX, "%s/%s", fpath, prop_arr[j]) >= PATH_MAX) {
|
|
|
|
pr_err("snprintf output was truncated");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (access(buf, F_OK) < 0 && errno == ENOENT) {
|
|
|
|
pr_info("Couldn't open %s. This cgroup property may not exist on this kernel\n", buf);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
prop = create_cgroup_prop(prop_arr[j]);
|
|
|
|
if (!prop) {
|
|
|
|
free_all_cgroup_props(ncd);
|
|
|
|
return -1;
|
|
|
|
}
|
2014-08-14 22:39:00 +04:00
|
|
|
|
|
|
|
if (read_cgroup_prop(prop, buf) < 0) {
|
2014-08-07 15:26:13 -07:00
|
|
|
free_cgroup_prop(prop);
|
|
|
|
free_all_cgroup_props(ncd);
|
|
|
|
return -1;
|
|
|
|
}
|
2014-08-14 22:39:00 +04:00
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
pr_info("Dumping value %s from %s/%s\n", prop->value, fpath, prop->name);
|
|
|
|
list_add_tail(&prop->list, &ncd->properties);
|
|
|
|
ncd->n_properties++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
static int add_cgroup(const char *fpath, const struct stat *sb, int typeflag)
|
|
|
|
{
|
|
|
|
struct cgroup_dir *ncd = NULL, *match;
|
2014-08-07 15:26:13 -07:00
|
|
|
int ret = 0;
|
2014-07-10 17:00:28 +04:00
|
|
|
|
|
|
|
if (typeflag == FTW_D) {
|
|
|
|
int mtype;
|
|
|
|
|
|
|
|
pr_info("adding cgroup %s\n", fpath);
|
|
|
|
|
|
|
|
ncd = xmalloc(sizeof(*ncd));
|
2014-07-21 14:47:04 +04:00
|
|
|
if (!ncd)
|
2014-07-10 17:00:28 +04:00
|
|
|
goto out;
|
|
|
|
|
2014-07-16 19:48:44 +04:00
|
|
|
/* chop off the first "/proc/self/fd/N" str */
|
2014-07-16 19:49:28 +04:00
|
|
|
if (fpath[path_pref_len] == '\0')
|
|
|
|
ncd->path = xstrdup("/");
|
|
|
|
else
|
|
|
|
ncd->path = xstrdup(fpath + path_pref_len);
|
|
|
|
|
2014-07-21 14:47:04 +04:00
|
|
|
if (!ncd->path)
|
2014-07-16 17:46:00 +04:00
|
|
|
goto out;
|
2014-07-10 17:00:28 +04:00
|
|
|
|
|
|
|
mtype = find_dir(ncd->path, ¤t_controller->heads, &match);
|
|
|
|
|
|
|
|
switch (mtype) {
|
|
|
|
/* ignore co-mounted cgroups */
|
|
|
|
case EXACT_MATCH:
|
|
|
|
goto out;
|
|
|
|
case PARENT_MATCH:
|
|
|
|
list_add_tail(&ncd->siblings, &match->children);
|
|
|
|
match->n_children++;
|
|
|
|
break;
|
|
|
|
case NO_MATCH:
|
|
|
|
list_add_tail(&ncd->siblings, ¤t_controller->heads);
|
|
|
|
current_controller->n_heads++;
|
|
|
|
break;
|
2014-08-06 18:14:00 +04:00
|
|
|
default:
|
|
|
|
BUG();
|
2014-07-10 17:00:28 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&ncd->children);
|
|
|
|
ncd->n_children = 0;
|
2014-08-07 15:26:13 -07:00
|
|
|
|
|
|
|
INIT_LIST_HEAD(&ncd->properties);
|
|
|
|
ncd->n_properties = 0;
|
|
|
|
if (add_cgroup_properties(fpath, ncd, current_controller) < 0) {
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
return 0;
|
2014-07-21 14:47:04 +04:00
|
|
|
} else
|
|
|
|
return 0;
|
2014-07-10 17:00:28 +04:00
|
|
|
|
|
|
|
out:
|
2014-07-21 14:47:04 +04:00
|
|
|
if (ncd)
|
|
|
|
xfree(ncd->path);
|
|
|
|
xfree(ncd);
|
2014-08-07 15:26:13 -07:00
|
|
|
return ret;
|
2014-07-10 17:00:28 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int collect_cgroups(struct list_head *ctls)
|
|
|
|
{
|
|
|
|
struct cg_ctl *cc;
|
|
|
|
int ret = 0;
|
2014-07-16 17:46:00 +04:00
|
|
|
int fd = -1;
|
2014-07-10 17:00:28 +04:00
|
|
|
|
|
|
|
list_for_each_entry(cc, ctls, l) {
|
2014-08-13 16:24:00 +04:00
|
|
|
char path[PATH_MAX], mopts[1024];
|
2014-07-14 17:40:13 +04:00
|
|
|
char *name, prefix[] = ".criu.cgmounts.XXXXXX";
|
2014-07-10 17:00:28 +04:00
|
|
|
struct cg_controller *cg;
|
|
|
|
|
|
|
|
current_controller = NULL;
|
|
|
|
|
|
|
|
/* We should get all the "real" (i.e. not name=systemd type)
|
|
|
|
* controller from parse_cgroups(), so find that controller if
|
|
|
|
* it exists. */
|
|
|
|
list_for_each_entry(cg, &cgroups, l) {
|
|
|
|
if (cgroup_contains(cg->controllers, cg->n_controllers, cc->name)) {
|
|
|
|
current_controller = cg;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!current_controller) {
|
|
|
|
/* only allow "fake" controllers to be created this way */
|
|
|
|
if (!strstartswith(cc->name, "name=")) {
|
|
|
|
pr_err("controller %s not found\n", cc->name);
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
} else {
|
|
|
|
struct cg_controller *nc = new_controller(cc->name, -1);
|
|
|
|
list_add_tail(&nc->l, &cg->l);
|
|
|
|
n_cgroups++;
|
|
|
|
current_controller = nc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 16:24:00 +04:00
|
|
|
if (!opts.manage_cgroups)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (strstartswith(cc->name, "name=")) {
|
|
|
|
name = cc->name + 5;
|
|
|
|
snprintf(mopts, sizeof(mopts), "none,%s", cc->name);
|
|
|
|
} else {
|
|
|
|
name = cc->name;
|
|
|
|
snprintf(mopts, sizeof(mopts), "%s", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mkdtemp(prefix) == NULL) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("can't make dir for cg mounts");
|
2014-08-13 16:24:00 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mount("none", prefix, "cgroup", 0, mopts) < 0) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("couldn't mount %s", mopts);
|
2014-08-13 16:24:00 +04:00
|
|
|
rmdir(prefix);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fd = open_detach_mount(prefix);
|
|
|
|
if (fd < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
path_pref_len = snprintf(path, PATH_MAX, "/proc/self/fd/%d", fd);
|
|
|
|
snprintf(path + path_pref_len, PATH_MAX - path_pref_len, "%s", cc->path);
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
ret = ftw(path, add_cgroup, 4);
|
|
|
|
if (ret < 0) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("failed walking %s for empty cgroups", path);
|
2014-07-10 17:00:28 +04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2014-07-16 17:46:00 +04:00
|
|
|
close_safe(&fd);
|
2014-07-10 17:00:28 +04:00
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-08 16:42:40 +04:00
|
|
|
int dump_task_cgroup(struct pstree_item *item, u32 *cg_id)
|
|
|
|
{
|
|
|
|
int pid;
|
|
|
|
LIST_HEAD(ctls);
|
|
|
|
unsigned int n_ctls = 0;
|
|
|
|
struct cg_set *cs;
|
|
|
|
|
|
|
|
if (item)
|
|
|
|
pid = item->pid.real;
|
|
|
|
else
|
|
|
|
pid = getpid();
|
|
|
|
|
|
|
|
pr_info("Dumping cgroups for %d\n", pid);
|
|
|
|
if (parse_task_cgroup(pid, &ctls, &n_ctls))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
cs = get_cg_set(&ctls, n_ctls);
|
|
|
|
if (!cs)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!item) {
|
|
|
|
BUG_ON(criu_cgset);
|
|
|
|
criu_cgset = cs;
|
|
|
|
pr_info("Set %d is criu one\n", cs->id);
|
|
|
|
} else if (item == root_item) {
|
|
|
|
BUG_ON(root_cgset);
|
|
|
|
root_cgset = cs;
|
|
|
|
pr_info("Set %d is root one\n", cs->id);
|
2014-07-15 17:57:00 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The on-stack ctls is moved into cs inside
|
|
|
|
* the get_cg_set routine.
|
|
|
|
*/
|
2014-08-13 16:24:00 +04:00
|
|
|
if (cs != criu_cgset && collect_cgroups(&cs->ctls))
|
2014-07-15 17:57:00 +04:00
|
|
|
return -1;
|
2014-05-08 16:42:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
*cg_id = cs->id;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
static int dump_cg_dir_props(struct list_head *props, size_t n_props,
|
|
|
|
CgroupPropEntry ***ents)
|
|
|
|
{
|
|
|
|
struct cgroup_prop *prop_cur;
|
|
|
|
CgroupPropEntry *cpe;
|
|
|
|
void *m;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
m = xmalloc(n_props * (sizeof(CgroupPropEntry *) + sizeof(CgroupPropEntry)));
|
|
|
|
*ents = m;
|
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
cpe = m + n_props * sizeof(CgroupPropEntry *);
|
|
|
|
|
|
|
|
list_for_each_entry(prop_cur, props, list) {
|
|
|
|
cgroup_prop_entry__init(cpe);
|
|
|
|
cpe->name = xstrdup(prop_cur->name);
|
|
|
|
cpe->value = xstrdup(prop_cur->value);
|
|
|
|
if (!cpe->name || !cpe->value)
|
|
|
|
goto error;
|
|
|
|
(*ents)[i++] = cpe++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
while (i >= 0) {
|
|
|
|
xfree(cpe->name);
|
|
|
|
xfree(cpe->value);
|
|
|
|
--cpe;
|
|
|
|
--i;
|
|
|
|
}
|
|
|
|
|
|
|
|
xfree(*ents);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-13 13:34:00 +04:00
|
|
|
static int dump_cg_dirs(struct list_head *dirs, size_t n_dirs, CgroupDirEntry ***ents, int poff)
|
2014-07-10 17:00:28 +04:00
|
|
|
{
|
|
|
|
struct cgroup_dir *cur;
|
|
|
|
CgroupDirEntry *cde;
|
|
|
|
void *m;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
m = xmalloc(n_dirs * (sizeof(CgroupDirEntry *) + sizeof(CgroupDirEntry)));
|
|
|
|
*ents = m;
|
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
cde = m + n_dirs * sizeof(CgroupDirEntry *);
|
|
|
|
|
|
|
|
list_for_each_entry(cur, dirs, siblings) {
|
|
|
|
cgroup_dir_entry__init(cde);
|
2014-08-25 18:05:00 +04:00
|
|
|
cde->dir_name = cur->path + poff;
|
|
|
|
if (poff != 1) /* parent isn't "/" */
|
|
|
|
cde->dir_name++; /* leading / */
|
2014-07-10 17:00:28 +04:00
|
|
|
cde->n_children = cur->n_children;
|
|
|
|
if (cur->n_children > 0)
|
2014-08-13 13:34:00 +04:00
|
|
|
if (dump_cg_dirs(&cur->children, cur->n_children, &cde->children, strlen(cur->path)) < 0) {
|
2014-07-10 17:00:28 +04:00
|
|
|
xfree(*ents);
|
|
|
|
return -1;
|
|
|
|
}
|
2014-08-07 15:26:13 -07:00
|
|
|
|
|
|
|
cde->n_properties = cur->n_properties;
|
|
|
|
if (cde->n_properties > 0) {
|
|
|
|
if (dump_cg_dir_props(&cur->properties,
|
|
|
|
cde->n_properties, &cde->properties) < 0) {
|
|
|
|
xfree(*ents);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
(*ents)[i++] = cde++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dump_controllers(CgroupEntry *cg)
|
|
|
|
{
|
|
|
|
struct cg_controller *cur;
|
|
|
|
CgControllerEntry *ce;
|
|
|
|
void *m;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
cg->n_controllers = n_cgroups;
|
|
|
|
m = xmalloc(n_cgroups * (sizeof(CgControllerEntry *) + sizeof(CgControllerEntry)));
|
|
|
|
cg->controllers = m;
|
|
|
|
ce = m + cg->n_controllers * sizeof(CgControllerEntry *);
|
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
list_for_each_entry(cur, &cgroups, l) {
|
|
|
|
cg_controller_entry__init(ce);
|
|
|
|
|
2014-07-11 15:00:51 +04:00
|
|
|
ce->cnames = cur->controllers;
|
|
|
|
ce->n_cnames = cur->n_controllers;
|
2014-07-10 17:00:28 +04:00
|
|
|
ce->n_dirs = cur->n_heads;
|
|
|
|
if (ce->n_dirs > 0)
|
2014-08-13 13:34:00 +04:00
|
|
|
if (dump_cg_dirs(&cur->heads, cur->n_heads, &ce->dirs, 0) < 0) {
|
2014-07-10 17:00:28 +04:00
|
|
|
xfree(cg->controllers);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
cg->controllers[i++] = ce++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-08 16:42:40 +04:00
|
|
|
static int dump_sets(CgroupEntry *cg)
|
|
|
|
{
|
|
|
|
struct cg_set *set;
|
|
|
|
struct cg_ctl *ctl;
|
|
|
|
int s, c;
|
|
|
|
void *m;
|
|
|
|
CgSetEntry *se;
|
2014-07-03 17:03:00 +04:00
|
|
|
CgMemberEntry *ce;
|
2014-05-08 16:42:40 +04:00
|
|
|
|
|
|
|
pr_info("Dumping %d sets\n", n_sets - 1);
|
|
|
|
|
|
|
|
cg->n_sets = n_sets - 1;
|
|
|
|
m = xmalloc(cg->n_sets * (sizeof(CgSetEntry *) + sizeof(CgSetEntry)));
|
|
|
|
cg->sets = m;
|
|
|
|
se = m + cg->n_sets * sizeof(CgSetEntry *);
|
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
s = 0;
|
|
|
|
list_for_each_entry(set, &cg_sets, l) {
|
|
|
|
if (set == criu_cgset)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that all sets we've found that tasks live in are
|
|
|
|
* subsets of the one root task lives in
|
|
|
|
*/
|
|
|
|
|
|
|
|
pr_info(" `- Dumping %d set (%d ctls)\n", set->id, set->n_ctls);
|
|
|
|
if (!cg_set_compare(set, &root_cgset->ctls, CGCMP_ISSUB)) {
|
|
|
|
pr_err("Set %d is not subset of %d\n",
|
|
|
|
set->id, root_cgset->id);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now encode them onto the image entry
|
|
|
|
*/
|
|
|
|
|
|
|
|
cg_set_entry__init(se);
|
|
|
|
se->id = set->id;
|
|
|
|
|
|
|
|
se->n_ctls = set->n_ctls;
|
2014-07-03 17:03:00 +04:00
|
|
|
m = xmalloc(se->n_ctls * (sizeof(CgMemberEntry *) + sizeof(CgMemberEntry)));
|
2014-05-08 16:42:40 +04:00
|
|
|
se->ctls = m;
|
2014-07-03 17:03:00 +04:00
|
|
|
ce = m + se->n_ctls * sizeof(CgMemberEntry *);
|
2014-05-08 16:42:40 +04:00
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
c = 0;
|
|
|
|
list_for_each_entry(ctl, &set->ctls, l) {
|
|
|
|
pr_info(" `- Dumping %s of %s\n", ctl->name, ctl->path);
|
2014-07-03 17:03:00 +04:00
|
|
|
cg_member_entry__init(ce);
|
2014-05-08 16:42:40 +04:00
|
|
|
ce->name = ctl->name;
|
|
|
|
ce->path = ctl->path;
|
|
|
|
se->ctls[c++] = ce++;
|
|
|
|
}
|
|
|
|
|
|
|
|
cg->sets[s++] = se++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dump_cgroups(void)
|
|
|
|
{
|
|
|
|
CgroupEntry cg = CGROUP_ENTRY__INIT;
|
|
|
|
|
|
|
|
BUG_ON(!criu_cgset || !root_cgset);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check whether root task lives in its own set as compared
|
|
|
|
* to criu. If yes, we should not dump anything, but make
|
|
|
|
* sure no other sets exist. The latter case can be supported,
|
|
|
|
* but requires some trickery and is hardly needed at the
|
|
|
|
* moment.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (root_cgset == criu_cgset) {
|
|
|
|
if (!list_is_singular(&cg_sets)) {
|
|
|
|
pr_err("Non supported sub-cgroups found\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_info("All tasks in criu's cgroups. Nothing to dump.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dump_sets(&cg))
|
|
|
|
return -1;
|
2014-07-10 17:00:28 +04:00
|
|
|
if (dump_controllers(&cg))
|
|
|
|
return -1;
|
2014-05-08 16:42:40 +04:00
|
|
|
|
|
|
|
pr_info("Writing CG image\n");
|
|
|
|
return pb_write_one(fdset_fd(glob_fdset, CR_FD_CGROUP), &cg, PB_CGROUP);
|
|
|
|
}
|
2014-05-08 16:55:53 +04:00
|
|
|
|
2014-08-12 17:19:32 +04:00
|
|
|
static int ctrl_dir_and_opt(CgControllerEntry *ctl, char *dir, int ds,
|
|
|
|
char *opt, int os)
|
|
|
|
{
|
|
|
|
int i, doff = 0, ooff = 0;
|
|
|
|
bool none_opt = false;
|
|
|
|
|
|
|
|
for (i = 0; i < ctl->n_cnames; i++) {
|
|
|
|
char *n;
|
|
|
|
|
|
|
|
n = ctl->cnames[i];
|
|
|
|
if (strstartswith(n, "name=")) {
|
|
|
|
n += 5;
|
|
|
|
if (opt && !none_opt) {
|
|
|
|
ooff += snprintf(opt + ooff, os - ooff, "none,");
|
|
|
|
none_opt = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
doff += snprintf(dir + doff, ds - doff, "%s,", n);
|
|
|
|
if (opt)
|
|
|
|
ooff += snprintf(opt + ooff, os - ooff, "%s,", ctl->cnames[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Chop the trailing ','-s */
|
|
|
|
dir[--doff] = '\0';
|
|
|
|
if (opt)
|
|
|
|
opt[ooff - 1] = '\0';
|
|
|
|
|
|
|
|
return doff;
|
|
|
|
}
|
|
|
|
|
2014-08-22 14:22:32 +00:00
|
|
|
static const char *special_cpuset_props[] = {
|
|
|
|
"cpuset.cpus",
|
|
|
|
"cpuset.mems",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int copy_special_cg_props(const char *controller, const char *path)
|
|
|
|
{
|
|
|
|
int cg = get_service_fd(CGROUP_YARD);
|
|
|
|
|
|
|
|
pr_info("copying special cg props for %s\n", controller);
|
|
|
|
|
|
|
|
if (strstr(controller, "cpuset")) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; special_cpuset_props[i]; i++) {
|
|
|
|
char fpath[PATH_MAX], buf[1024];
|
|
|
|
const char *prop = special_cpuset_props[i];
|
|
|
|
int ret;
|
|
|
|
FILE *f;
|
|
|
|
|
|
|
|
snprintf(fpath, PATH_MAX, "%s/%s", controller, prop);
|
|
|
|
f = fopenat(cg, fpath, "r");
|
|
|
|
if (!f)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
ret = fscanf(f, "%1024s", buf);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
if (ret <= 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_info("copying %s for %s/%s\n", buf, controller, prop);
|
|
|
|
|
|
|
|
snprintf(fpath, PATH_MAX, "%s/%s/%s", controller, path, prop);
|
|
|
|
f = fopenat(cg, fpath, "w");
|
|
|
|
if (!f)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
fprintf(f, "%s", buf);
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
static int move_in_cgroup(CgSetEntry *se)
|
|
|
|
{
|
|
|
|
int cg, i;
|
|
|
|
|
|
|
|
pr_info("Move into %d\n", se->id);
|
|
|
|
cg = get_service_fd(CGROUP_YARD);
|
|
|
|
for (i = 0; i < se->n_ctls; i++) {
|
2014-08-14 18:39:00 +04:00
|
|
|
char aux[PATH_MAX];
|
2014-07-10 17:00:28 +04:00
|
|
|
int fd, err, j, aux_off;
|
2014-07-03 17:03:00 +04:00
|
|
|
CgMemberEntry *ce = se->ctls[i];
|
2014-07-10 17:00:28 +04:00
|
|
|
CgControllerEntry *ctrl = NULL;
|
|
|
|
|
|
|
|
for (j = 0; j < n_controllers; j++) {
|
|
|
|
CgControllerEntry *cur = controllers[j];
|
2014-07-11 15:00:51 +04:00
|
|
|
if (cgroup_contains(cur->cnames, cur->n_cnames, ce->name)) {
|
2014-07-10 17:00:28 +04:00
|
|
|
ctrl = cur;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ctrl) {
|
|
|
|
pr_err("No cg_controller_entry found for %s/%s\n", ce->name, ce->path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-12 17:19:32 +04:00
|
|
|
aux_off = ctrl_dir_and_opt(ctrl, aux, sizeof(aux), NULL, 0);
|
2014-08-22 14:22:32 +00:00
|
|
|
|
|
|
|
if (copy_special_cg_props(aux, ce->path) < 0) {
|
|
|
|
pr_perror("Couldn't copy special cgroup properties\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-07-21 14:47:06 +04:00
|
|
|
snprintf(aux + aux_off, sizeof(aux) - aux_off, "/%s/tasks", ce->path);
|
2014-05-08 16:55:53 +04:00
|
|
|
pr_debug(" `-> %s\n", aux);
|
|
|
|
err = fd = openat(cg, aux, O_WRONLY);
|
|
|
|
if (fd >= 0) {
|
|
|
|
/*
|
|
|
|
* Writing zero into this file moves current
|
|
|
|
* task w/o any permissions checks :)
|
|
|
|
*/
|
|
|
|
err = write(fd, "0", 1);
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err < 0) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("Can't move into %s (%d/%d)", aux, err, fd);
|
2014-05-08 16:55:53 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int prepare_task_cgroup(struct pstree_item *me)
|
|
|
|
{
|
|
|
|
CgSetEntry *se;
|
|
|
|
u32 current_cgset;
|
|
|
|
|
|
|
|
if (!me->rst->cg_set)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (me->parent)
|
|
|
|
current_cgset = me->parent->rst->cg_set;
|
|
|
|
else
|
|
|
|
current_cgset = root_cg_set;
|
|
|
|
|
|
|
|
if (me->rst->cg_set == current_cgset) {
|
|
|
|
pr_info("Cgroups %d inherited from parent\n", current_cgset);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
se = find_rst_set_by_id(me->rst->cg_set);
|
|
|
|
if (!se) {
|
|
|
|
pr_err("No set %d found\n", me->rst->cg_set);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return move_in_cgroup(se);
|
|
|
|
}
|
|
|
|
|
|
|
|
void fini_cgroup(void)
|
|
|
|
{
|
|
|
|
if (!cg_yard)
|
|
|
|
return;
|
|
|
|
|
|
|
|
close_service_fd(CGROUP_YARD);
|
|
|
|
umount2(cg_yard, MNT_DETACH);
|
|
|
|
rmdir(cg_yard);
|
|
|
|
xfree(cg_yard);
|
2014-09-04 16:21:00 +04:00
|
|
|
cg_yard = NULL;
|
2014-05-08 16:55:53 +04:00
|
|
|
}
|
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
static int restore_cgroup_prop(const CgroupPropEntry * cg_prop_entry_p,
|
2014-08-13 13:34:00 +04:00
|
|
|
char *path, int off)
|
2014-08-07 15:26:13 -07:00
|
|
|
{
|
|
|
|
FILE *f;
|
2014-08-12 18:17:48 +04:00
|
|
|
int cg;
|
2014-08-07 15:26:13 -07:00
|
|
|
|
|
|
|
if (!cg_prop_entry_p->value) {
|
|
|
|
pr_err("cg_prop_entry->value was empty when should have had a value");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-13 13:34:00 +04:00
|
|
|
if (snprintf(path + off, PATH_MAX - off, "/%s", cg_prop_entry_p->name) >= PATH_MAX) {
|
2014-08-07 15:26:13 -07:00
|
|
|
pr_err("snprintf output was truncated for %s\n", cg_prop_entry_p->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-12 18:17:48 +04:00
|
|
|
cg = get_service_fd(CGROUP_YARD);
|
2014-08-07 15:26:13 -07:00
|
|
|
f = fopenat(cg, path, "w+");
|
|
|
|
if (!f) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("Failed opening %s for writing", path);
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fprintf(f, "%s", cg_prop_entry_p->value) < 0) {
|
|
|
|
fclose(f);
|
|
|
|
pr_err("Failed writing %s to %s\n", cg_prop_entry_p->value, path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fclose(f) != 0) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("Failed closing %s", path);
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_info("Restored cgroup property value %s to %s\n", cg_prop_entry_p->value, path);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-12 18:18:04 +04:00
|
|
|
static int prepare_cgroup_dir_properties(char *path, int off, CgroupDirEntry **ents,
|
2014-08-07 15:26:13 -07:00
|
|
|
unsigned int n_ents)
|
|
|
|
{
|
|
|
|
unsigned int i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < n_ents; i++) {
|
|
|
|
CgroupDirEntry *e = ents[i];
|
2014-08-15 16:24:24 +04:00
|
|
|
size_t off2 = off;
|
2014-08-07 15:26:13 -07:00
|
|
|
|
2014-08-15 16:24:24 +04:00
|
|
|
off2 += sprintf(path + off, "/%s", e->dir_name);
|
2014-08-07 15:26:13 -07:00
|
|
|
/*
|
|
|
|
* Check to see if we made e->properties NULL during restore
|
|
|
|
* because directory already existed and as such we don't want to
|
|
|
|
* change its properties
|
|
|
|
*/
|
|
|
|
if (e->properties) {
|
|
|
|
for (j = 0; j < e->n_properties; ++j) {
|
2014-08-21 21:00:21 +00:00
|
|
|
if (restore_cgroup_prop(e->properties[j], path, off2) < 0)
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-15 16:24:24 +04:00
|
|
|
if (prepare_cgroup_dir_properties(path, off2, e->children, e->n_children) < 0)
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int prepare_cgroup_properties(void)
|
|
|
|
{
|
|
|
|
char cname_path[PATH_MAX];
|
2014-08-12 18:18:04 +04:00
|
|
|
unsigned int i, off;
|
2014-08-07 15:26:13 -07:00
|
|
|
|
|
|
|
for (i = 0; i < n_controllers; i++) {
|
|
|
|
CgControllerEntry *c = controllers[i];
|
|
|
|
|
|
|
|
if (c->n_cnames < 1) {
|
|
|
|
pr_err("Each CgControllerEntry should have at least 1 cname\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-12 18:18:04 +04:00
|
|
|
off = ctrl_dir_and_opt(c, cname_path, sizeof(cname_path), NULL, 0);
|
|
|
|
if (prepare_cgroup_dir_properties(cname_path, off, c->dirs, c->n_dirs) < 0)
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
static int prepare_cgroup_dirs(char *paux, size_t off, CgroupDirEntry **ents, size_t n_ents)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
CgroupDirEntry *e;
|
|
|
|
|
|
|
|
for (i = 0; i < n_ents; i++) {
|
2014-08-15 16:24:24 +04:00
|
|
|
size_t off2 = off;
|
2014-07-10 17:00:28 +04:00
|
|
|
e = ents[i];
|
|
|
|
|
2014-08-15 16:24:24 +04:00
|
|
|
off2 += sprintf(paux + off, "/%s", e->dir_name);
|
2014-07-10 17:00:28 +04:00
|
|
|
|
2014-08-07 15:26:13 -07:00
|
|
|
/*
|
|
|
|
* Checking to see if file already exists. If not, create it. If
|
|
|
|
* it does exist, prevent us from overwriting the properties
|
|
|
|
* later by setting the CgroupDirEntry's properties pointer to NULL
|
|
|
|
*/
|
|
|
|
if (access(paux, F_OK) < 0) {
|
|
|
|
if (errno != ENOENT) {
|
2014-08-15 14:30:00 +04:00
|
|
|
pr_perror("Failed accessing file %s", paux);
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (mkdirp(paux)) {
|
|
|
|
pr_perror("Can't make cgroup dir %s", paux);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pr_info("Created dir %s\n", paux);
|
|
|
|
} else {
|
|
|
|
e->properties = NULL;
|
|
|
|
pr_info("Determined dir %s already existed\n", paux);
|
2014-07-10 17:00:28 +04:00
|
|
|
}
|
|
|
|
|
2014-08-15 16:24:24 +04:00
|
|
|
if (prepare_cgroup_dirs(paux, off2, e->children, e->n_children) < 0)
|
2014-08-07 15:26:13 -07:00
|
|
|
return -1;
|
2014-07-10 17:00:28 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
/*
|
|
|
|
* Prepare the CGROUP_YARD service descriptor. This guy is
|
|
|
|
* tmpfs mount with the set of ctl->name directories each
|
|
|
|
* one having the respective cgroup mounted.
|
|
|
|
*
|
|
|
|
* It's required for two reasons.
|
|
|
|
*
|
|
|
|
* First, if we move more than one task into cgroups it's
|
|
|
|
* faster to have cgroup tree visible by them all in sime
|
|
|
|
* single place. Searching for this thing existing in the
|
|
|
|
* criu's space is not nice, as parsing /proc/mounts is not
|
|
|
|
* very fast, other than this not all cgroups may be mounted.
|
|
|
|
*
|
|
|
|
* Second, when we have user-namespaces support we will
|
|
|
|
* loose the ability to mount cgroups on-demand, so prepare
|
|
|
|
* them in advance.
|
|
|
|
*/
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
static int prepare_cgroup_sfd(CgroupEntry *ce)
|
2014-05-08 16:55:53 +04:00
|
|
|
{
|
|
|
|
int off, i;
|
2014-07-10 17:00:28 +04:00
|
|
|
char paux[PATH_MAX];
|
2014-05-08 16:55:53 +04:00
|
|
|
|
|
|
|
pr_info("Preparing cgroups yard\n");
|
|
|
|
|
|
|
|
off = sprintf(paux, ".criu.cgyard.XXXXXX");
|
|
|
|
if (mkdtemp(paux) == NULL) {
|
|
|
|
pr_perror("Can't make temp cgyard dir");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cg_yard = xstrdup(paux);
|
|
|
|
if (!cg_yard) {
|
|
|
|
rmdir(paux);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mount("none", cg_yard, "tmpfs", 0, NULL)) {
|
|
|
|
pr_perror("Can't mount tmpfs in cgyard");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-06-20 20:34:00 +04:00
|
|
|
if (mount("none", cg_yard, NULL, MS_PRIVATE, NULL)) {
|
|
|
|
pr_perror("Can't make cgyard private");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-08-12 17:19:32 +04:00
|
|
|
paux[off++] = '/';
|
|
|
|
|
2014-07-10 17:00:28 +04:00
|
|
|
for (i = 0; i < ce->n_controllers; i++) {
|
2014-08-12 17:19:32 +04:00
|
|
|
int ctl_off = off;
|
|
|
|
char opt[128];
|
2014-07-10 17:00:28 +04:00
|
|
|
CgControllerEntry *ctrl = ce->controllers[i];
|
|
|
|
|
2014-07-11 15:00:51 +04:00
|
|
|
if (ctrl->n_cnames < 1) {
|
2014-07-10 17:00:28 +04:00
|
|
|
pr_err("Each cg_controller_entry must have at least 1 controller");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-08-12 17:19:32 +04:00
|
|
|
ctl_off += ctrl_dir_and_opt(ctrl,
|
|
|
|
paux + ctl_off, sizeof(paux) - ctl_off,
|
|
|
|
opt, sizeof(opt));
|
2014-05-08 16:55:53 +04:00
|
|
|
|
2014-08-12 17:19:32 +04:00
|
|
|
pr_debug("\tMaking subdir %s (%s)\n", paux, opt);
|
2014-05-08 16:55:53 +04:00
|
|
|
if (mkdir(paux, 0700)) {
|
2014-07-10 17:00:28 +04:00
|
|
|
pr_perror("Can't make cgyard subdir %s", paux);
|
2014-05-08 16:55:53 +04:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mount("none", paux, "cgroup", 0, opt) < 0) {
|
2014-07-10 17:00:28 +04:00
|
|
|
pr_perror("Can't mount %s cgyard", paux);
|
2014-05-08 16:55:53 +04:00
|
|
|
goto err;
|
|
|
|
}
|
2014-07-10 17:00:28 +04:00
|
|
|
|
2014-08-06 22:23:00 +04:00
|
|
|
if (opts.manage_cgroups &&
|
2014-08-12 17:19:32 +04:00
|
|
|
prepare_cgroup_dirs(paux, ctl_off, ctrl->dirs, ctrl->n_dirs))
|
2014-07-10 17:00:28 +04:00
|
|
|
goto err;
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("Opening %s as cg yard\n", cg_yard);
|
|
|
|
i = open(cg_yard, O_DIRECTORY);
|
|
|
|
if (i < 0) {
|
|
|
|
pr_perror("Can't open cgyard");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
off = install_service_fd(CGROUP_YARD, i);
|
|
|
|
close(i);
|
|
|
|
if (off < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
fini_cgroup();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-08-15 17:02:21 -05:00
|
|
|
static int rewrite_cgsets(CgroupEntry *cge, char **controllers, int n_controllers,
|
|
|
|
char *from, char *to)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < cge->n_sets; i++) {
|
|
|
|
CgSetEntry *set = cge->sets[i];
|
|
|
|
for (j = 0; j < set->n_ctls; j++) {
|
|
|
|
CgMemberEntry *cg = set->ctls[j];
|
|
|
|
if (cgroup_contains(controllers, n_controllers, cg->name) &&
|
|
|
|
/* +1 to get rid of leading / */
|
|
|
|
strstartswith(cg->path + 1, from)) {
|
|
|
|
|
|
|
|
/* +1 to get rid of leading /, again */
|
|
|
|
int off = strlen(from) + 1;
|
|
|
|
|
|
|
|
/* +1 for trailing NULL */
|
|
|
|
int newlen = strlen(to) + strlen(cg->path + off) + 1;
|
|
|
|
char *m = malloc(newlen * sizeof(char*));
|
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
sprintf(m, "%s%s", to, cg->path + off);
|
|
|
|
free(cg->path);
|
|
|
|
cg->path = m;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rewrite_cgroup_roots(CgroupEntry *cge)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
struct cg_root_opt *o;
|
|
|
|
char *newroot = NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < cge->n_controllers; i++) {
|
|
|
|
CgControllerEntry *ctrl = cge->controllers[i];
|
|
|
|
newroot = opts.new_global_cg_root;
|
|
|
|
|
|
|
|
list_for_each_entry(o, &opts.new_cgroup_roots, node) {
|
|
|
|
if (cgroup_contains(ctrl->cnames, ctrl->n_cnames, o->controller)) {
|
|
|
|
newroot = o->newroot;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newroot) {
|
|
|
|
for (j = 0; j < ctrl->n_dirs; j++) {
|
|
|
|
CgroupDirEntry *cgde = ctrl->dirs[j];
|
|
|
|
char *m;
|
|
|
|
|
|
|
|
pr_info("rewriting %s to %s\n", cgde->dir_name, newroot);
|
|
|
|
if (rewrite_cgsets(cge, ctrl->cnames, ctrl->n_cnames, cgde->dir_name, newroot))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
m = xstrdup(newroot);
|
|
|
|
if (!m)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
free(cgde->dir_name);
|
|
|
|
cgde->dir_name = m;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
int prepare_cgroup(void)
|
|
|
|
{
|
|
|
|
int fd, ret;
|
|
|
|
CgroupEntry *ce;
|
|
|
|
|
|
|
|
fd = open_image(CR_FD_CGROUP, O_RSTR | O_OPT);
|
|
|
|
if (fd < 0) {
|
|
|
|
if (errno == ENOENT) /* backward compatibility */
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pb_read_one_eof(fd, &ce, PB_CGROUP);
|
|
|
|
close(fd);
|
|
|
|
if (ret <= 0) /* Zero is OK -- no sets there. */
|
|
|
|
return ret;
|
|
|
|
|
2014-08-15 17:02:21 -05:00
|
|
|
if (rewrite_cgroup_roots(ce))
|
|
|
|
return -1;
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
n_sets = ce->n_sets;
|
|
|
|
rst_sets = ce->sets;
|
2014-07-10 17:00:28 +04:00
|
|
|
n_controllers = ce->n_controllers;
|
|
|
|
controllers = ce->controllers;
|
|
|
|
|
2014-05-08 16:55:53 +04:00
|
|
|
if (n_sets)
|
|
|
|
/*
|
|
|
|
* We rely on the fact that all sets contain the same
|
|
|
|
* set of controllers. This is checked during dump
|
|
|
|
* with cg_set_compare(CGCMP_ISSUB) call.
|
|
|
|
*/
|
2014-07-10 17:00:28 +04:00
|
|
|
ret = prepare_cgroup_sfd(ce);
|
2014-05-08 16:55:53 +04:00
|
|
|
else
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2014-08-15 17:02:21 -05:00
|
|
|
|
|
|
|
int new_cg_root_add(char *controller, char *newroot)
|
|
|
|
{
|
|
|
|
struct cg_root_opt *o;
|
|
|
|
|
2014-08-22 16:10:27 +04:00
|
|
|
if (!controller) {
|
|
|
|
opts.new_global_cg_root = newroot;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-15 17:02:21 -05:00
|
|
|
o = xmalloc(sizeof(*o));
|
|
|
|
if (!o)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
o->controller = controller;
|
|
|
|
o->newroot = newroot;
|
|
|
|
list_add(&o->node, &opts.new_cgroup_roots);
|
|
|
|
return 0;
|
|
|
|
}
|