blob: b44f805bea9229bc09673615814279a924eb5622 [file] [log] [blame]
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#define _GNU_SOURCE /* For asprintf */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "container_cgroup.h"
static const char *cgroup_names[NUM_CGROUP_TYPES] = {
"cpu", "cpuacct", "devices", "freezer",
};
static int open_cgroup_file(const char *cgroup_path, const char *name)
{
int fd;
char *path = NULL;
if (asprintf(&path, "%s/%s", cgroup_path, name) < 0)
return -errno;
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd == -1)
fd = -errno;
free(path);
return fd;
}
static int write_cgroup_file(const char *cgroup_path, const char *name,
const char *str)
{
int fd;
int rc = 0;
fd = open_cgroup_file(cgroup_path, name);
if (fd < 0)
return fd;
const char *buffer = str;
size_t len = strlen(str);
if (write(fd, buffer, len) != len)
rc = -errno;
close(fd);
return rc;
}
static int write_cgroup_file_int(const char *cgroup_path, const char *name,
const int value)
{
char *str = NULL;
int rc;
if (asprintf(&str, "%d", value) < 0)
return -errno;
rc = write_cgroup_file(cgroup_path, name, str);
free(str);
return rc;
}
static int freeze(const struct container_cgroup *cg)
{
return write_cgroup_file(cg->cgroup_paths[CGROUP_FREEZER],
"freezer.state", "FROZEN\n");
}
static int thaw(const struct container_cgroup *cg)
{
return write_cgroup_file(cg->cgroup_paths[CGROUP_FREEZER],
"freezer.state", "THAWED\n");
}
static int deny_all_devices(const struct container_cgroup *cg)
{
return write_cgroup_file(cg->cgroup_paths[CGROUP_DEVICES],
"devices.deny", "a\n");
}
static int add_device(const struct container_cgroup *cg, int major, int minor,
int read, int write, int modify, char type)
{
char *perm_string = NULL;
int rc;
if (type != 'b' && type != 'c')
return -EINVAL;
if (!read && !write)
return -EINVAL;
if (minor >= 0) {
if (asprintf(&perm_string, "%c %d:%d %s%s%s\n",
type, major, minor,
read ? "r" : "", write ? "w" : "",
modify ? "m" : "") < 0)
return -errno;
} else {
/* Set perms for all devices with this major number. */
if (asprintf(&perm_string, "%c %d:* %s%s%s\n",
type, major,
read ? "r" : "", write ? "w" : "",
modify ? "m" : "") < 0)
return -errno;
}
rc = write_cgroup_file(cg->cgroup_paths[CGROUP_DEVICES],
"devices.allow", perm_string);
free(perm_string);
return rc;
}
static int set_cpu_shares(const struct container_cgroup *cg, int shares)
{
return write_cgroup_file_int(cg->cgroup_paths[CGROUP_CPU],
"cpu.shares", shares);
}
static int set_cpu_quota(const struct container_cgroup *cg, int quota)
{
return write_cgroup_file_int(cg->cgroup_paths[CGROUP_CPU],
"cpu.cfs_quota_us", quota);
}
static int set_cpu_period(const struct container_cgroup *cg, int period)
{
return write_cgroup_file_int(cg->cgroup_paths[CGROUP_CPU],
"cpu.cfs_period_us", period);
}
static int set_cpu_rt_runtime(const struct container_cgroup *cg, int rt_runtime)
{
return write_cgroup_file_int(cg->cgroup_paths[CGROUP_CPU],
"cpu.rt_runtime_us", rt_runtime);
}
static int set_cpu_rt_period(const struct container_cgroup *cg, int rt_period)
{
return write_cgroup_file_int(cg->cgroup_paths[CGROUP_CPU],
"cpu.rt_period_us", rt_period);
}
static const struct cgroup_ops cgroup_ops = {
.freeze = freeze,
.thaw = thaw,
.deny_all_devices = deny_all_devices,
.add_device = add_device,
.set_cpu_shares = set_cpu_shares,
.set_cpu_quota = set_cpu_quota,
.set_cpu_period = set_cpu_period,
.set_cpu_rt_runtime = set_cpu_rt_runtime,
.set_cpu_rt_period = set_cpu_rt_period,
};
static int create_cgroup_as_owner(const char *cgroup_path, uid_t cgroup_owner)
{
int mkdir_rc;
/*
* If running as root and the cgroup owner is a user, create the cgroup
* as that user.
*/
if (getuid() == 0 && cgroup_owner != 0) {
if (seteuid(cgroup_owner))
return -errno;
/*
* CAUTION: Make sure that no return path forgets to set the
* user back to root from here.
*/
mkdir_rc = mkdir(cgroup_path, S_IRWXU | S_IRWXG);
if (seteuid(0))
return -errno;
} else {
mkdir_rc = mkdir(cgroup_path, S_IRWXU | S_IRWXG);
}
if (mkdir_rc < 0 && errno != EEXIST)
return -errno;
return 0;
}
struct container_cgroup *container_cgroup_new(const char *name,
const char *cgroup_root,
const char *cgroup_parent,
uid_t cgroup_owner)
{
int i;
struct container_cgroup *cg;
cg = calloc(1, sizeof(*cg));
if (!cg)
return NULL;
for (i = 0; i < NUM_CGROUP_TYPES; ++i) {
if (cgroup_parent) {
if (asprintf(&cg->cgroup_paths[i], "%s/%s/%s/%s",
cgroup_root, cgroup_names[i],
cgroup_parent, name) < 0)
goto error_free_cg;
} else {
if (asprintf(&cg->cgroup_paths[i], "%s/%s/%s",
cgroup_root, cgroup_names[i], name) < 0)
goto error_free_cg;
}
if (create_cgroup_as_owner(cg->cgroup_paths[i], cgroup_owner))
goto error_free_cg;
if (asprintf(&cg->cgroup_tasks_paths[i], "%s/tasks",
cg->cgroup_paths[i]) < 0)
goto error_free_cg;
}
cg->name = strdup(name);
if (!cg->name)
goto error_free_cg;
cg->ops = &cgroup_ops;
return cg;
error_free_cg:
container_cgroup_destroy(cg);
return NULL;
}
void container_cgroup_destroy(struct container_cgroup *cg)
{
int i;
free(cg->name);
for (i = 0; i < NUM_CGROUP_TYPES; ++i) {
if (!cg->cgroup_paths[i])
continue;
rmdir(cg->cgroup_paths[i]);
free(cg->cgroup_paths[i]);
free(cg->cgroup_tasks_paths[i]);
}
free(cg);
}