Commit 26d04f86 authored by Lennart Poettering's avatar Lennart Poettering
Browse files

unit: rework resource management API

This introduces a new static list of known attributes and their special
semantics. This means that cgroup attribute values can now be
automatically translated from user to kernel notation for command line
set settings, too.

This also adds proper support for multi-line attributes.
parent 416389f7
......@@ -852,6 +852,8 @@ libsystemd_core_la_SOURCES = \
src/core/tcpwrap.h \
src/core/cgroup-attr.c \
src/core/cgroup-attr.h \
src/core/cgroup-semantics.c \
src/core/cgroup-semantics.h \
src/core/securebits.h \
src/core/initreq.h \
src/core/special.h \
......
......@@ -25,8 +25,8 @@
#include "fileio.h"
int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
int r;
_cleanup_free_ char *path = NULL, *v = NULL;
int r;
assert(a);
......@@ -34,8 +34,8 @@ int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
if (!b)
return 0;
if (a->map_callback) {
r = a->map_callback(a->controller, a->name, a->value, &v);
if (a->semantics && a->semantics->map_write) {
r = a->semantics->map_write(a->semantics, a->value, &v);
if (r < 0)
return r;
}
......@@ -66,6 +66,29 @@ int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) {
return r;
}
bool cgroup_attribute_matches(CGroupAttribute *a, const char *controller, const char *name) {
assert(a);
if (controller) {
if (streq(a->controller, controller) && (!name || streq(a->name, name)))
return true;
} else if (!name)
return true;
else if (streq(a->name, name)) {
size_t x, y;
x = strlen(a->controller);
y = strlen(name);
if (y > x &&
memcmp(a->controller, name, x) == 0 &&
name[x] == '.')
return true;
}
return false;
}
CGroupAttribute *cgroup_attribute_find_list(
CGroupAttribute *first,
const char *controller,
......@@ -74,24 +97,9 @@ CGroupAttribute *cgroup_attribute_find_list(
assert(name);
LIST_FOREACH(by_unit, a, first) {
if (controller) {
if (streq(a->controller, controller) && streq(a->name, name))
return a;
} else if (streq(a->name, name)) {
size_t x, y;
x = strlen(a->controller);
y = strlen(name);
if (y > x &&
memcmp(a->controller, name, x) == 0 &&
name[x] == '.')
return a;
}
}
LIST_FOREACH(by_unit, a, first)
if (cgroup_attribute_matches(a, controller, name))
return a;
return NULL;
}
......@@ -114,3 +122,11 @@ void cgroup_attribute_free_list(CGroupAttribute *first) {
LIST_FOREACH_SAFE(by_unit, a, n, first)
cgroup_attribute_free(a);
}
void cgroup_attribute_free_some(CGroupAttribute *first, const char *controller, const char *name) {
CGroupAttribute *a, *n;
LIST_FOREACH_SAFE(by_unit, a, n, first)
if (cgroup_attribute_matches(a, controller, name))
cgroup_attribute_free(a);
}
......@@ -23,10 +23,9 @@
typedef struct CGroupAttribute CGroupAttribute;
typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret);
#include "unit.h"
#include "cgroup.h"
#include "cgroup-semantics.h"
struct CGroupAttribute {
char *controller;
......@@ -35,7 +34,7 @@ struct CGroupAttribute {
Unit *unit;
CGroupAttributeMapCallback map_callback;
const CGroupSemantics *semantics;
LIST_FIELDS(CGroupAttribute, by_unit);
};
......@@ -43,7 +42,9 @@ struct CGroupAttribute {
int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b);
int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b);
bool cgroup_attribute_matches(CGroupAttribute *a, const char *controller, const char *name);
CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name);
void cgroup_attribute_free(CGroupAttribute *a);
void cgroup_attribute_free_list(CGroupAttribute *first);
void cgroup_attribute_free_some(CGroupAttribute *first, const char *controller, const char *name);
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
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 "util.h"
#include "strv.h"
#include "path-util.h"
#include "cgroup-util.h"
#include "cgroup-semantics.h"
static int parse_cpu_shares(const CGroupSemantics *s, const char *value, char **ret) {
unsigned long ul;
assert(s);
assert(value);
assert(ret);
if (safe_atolu(value, &ul) < 0 || ul < 1)
return -EINVAL;
if (asprintf(ret, "%lu", ul) < 0)
return -ENOMEM;
return 1;
}
static int parse_memory_limit(const CGroupSemantics *s, const char *value, char **ret) {
off_t sz;
assert(s);
assert(value);
assert(ret);
if (parse_bytes(value, &sz) < 0 || sz <= 0)
return -EINVAL;
if (asprintf(ret, "%llu", (unsigned long long) sz) < 0)
return -ENOMEM;
return 1;
}
static int parse_device(const CGroupSemantics *s, const char *value, char **ret) {
_cleanup_strv_free_ char **l = NULL;
char *x;
unsigned k;
assert(s);
assert(value);
assert(ret);
l = strv_split_quoted(value);
if (!l)
return -ENOMEM;
k = strv_length(l);
if (k < 1 || k > 2)
return -EINVAL;
if (!streq(l[0], "*") && !path_startswith(l[0], "/dev"))
return -EINVAL;
if (!isempty(l[1]) && !in_charset(l[1], "rwm"))
return -EINVAL;
x = strdup(value);
if (!x)
return -ENOMEM;
*ret = x;
return 1;
}
static int parse_blkio_weight(const CGroupSemantics *s, const char *value, char **ret) {
_cleanup_strv_free_ char **l = NULL;
unsigned long ul;
assert(s);
assert(value);
assert(ret);
l = strv_split_quoted(value);
if (!l)
return -ENOMEM;
if (strv_length(l) != 1)
return 0; /* Returning 0 will cause parse_blkio_weight_device() be tried instead */
if (safe_atolu(l[0], &ul) < 0 || ul < 10 || ul > 1000)
return -EINVAL;
if (asprintf(ret, "%lu", ul) < 0)
return -ENOMEM;
return 1;
}
static int parse_blkio_weight_device(const CGroupSemantics *s, const char *value, char **ret) {
_cleanup_strv_free_ char **l = NULL;
unsigned long ul;
assert(s);
assert(value);
assert(ret);
l = strv_split_quoted(value);
if (!l)
return -ENOMEM;
if (strv_length(l) != 2)
return -EINVAL;
if (!path_startswith(l[0], "/dev"))
return -EINVAL;
if (safe_atolu(l[1], &ul) < 0 || ul < 10 || ul > 1000)
return -EINVAL;
if (asprintf(ret, "%s %lu", l[0], ul) < 0)
return -ENOMEM;
return 1;
}
static int parse_blkio_bandwidth(const CGroupSemantics *s, const char *value, char **ret) {
_cleanup_strv_free_ char **l = NULL;
off_t bytes;
assert(s);
assert(value);
assert(ret);
l = strv_split_quoted(value);
if (!l)
return -ENOMEM;
if (strv_length(l) != 2)
return -EINVAL;
if (!path_startswith(l[0], "/dev")) {
return -EINVAL;
}
if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0)
return -EINVAL;
if (asprintf(ret, "%s %llu", l[0], (unsigned long long) bytes) < 0)
return -ENOMEM;
return 0;
}
static int map_device(const CGroupSemantics *s, const char *value, char **ret) {
_cleanup_strv_free_ char **l = NULL;
unsigned k;
assert(s);
assert(value);
assert(ret);
l = strv_split_quoted(value);
if (!l)
return -ENOMEM;
k = strv_length(l);
if (k < 1 || k > 2)
return -EINVAL;
if (streq(l[0], "*")) {
if (asprintf(ret, "a *:*%s%s",
isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
return -ENOMEM;
} else {
struct stat st;
if (stat(l[0], &st) < 0) {
log_warning("Couldn't stat device %s", l[0]);
return -errno;
}
if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
log_warning("%s is not a device.", l[0]);
return -ENODEV;
}
if (asprintf(ret, "%c %u:%u%s%s",
S_ISCHR(st.st_mode) ? 'c' : 'b',
major(st.st_rdev), minor(st.st_rdev),
isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
return -ENOMEM;
}
return 0;
}
static int map_blkio(const CGroupSemantics *s, const char *value, char **ret) {
_cleanup_strv_free_ char **l = NULL;
struct stat st;
dev_t d;
assert(s);
assert(value);
assert(ret);
l = strv_split_quoted(value);
if (!l)
return log_oom();
if (strv_length(l) != 2)
return -EINVAL;
if (stat(l[0], &st) < 0) {
log_warning("Couldn't stat device %s", l[0]);
return -errno;
}
if (S_ISBLK(st.st_mode))
d = st.st_rdev;
else if (major(st.st_dev) != 0) {
/* If this is not a device node then find the block
* device this file is stored on */
d = st.st_dev;
/* If this is a partition, try to get the originating
* block device */
block_get_whole_disk(d, &d);
} else {
log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]);
return -ENODEV;
}
if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0)
return -ENOMEM;
return 0;
}
static const CGroupSemantics semantics[] = {
{ "cpu", "cpu.shares", "CPUShare", false, parse_cpu_shares, NULL, NULL },
{ "memory", "memory.soft_limit_in_bytes", "MemorySoftLimit", false, parse_memory_limit, NULL, NULL },
{ "memory", "memory.limit_in_bytes", "MemoryLimit", false, parse_memory_limit, NULL, NULL },
{ "devices", "devices.allow", "DeviceAllow", true, parse_device, map_device, NULL },
{ "devices", "devices.deny", "DeviceDeny", true, parse_device, map_device, NULL },
{ "blkio", "blkio.weight", "BlockIOWeight", false, parse_blkio_weight, NULL, NULL },
{ "blkio", "blkio.weight_device", "BlockIOWeight", true, parse_blkio_weight_device, map_blkio, NULL },
{ "blkio", "blkio.read_bps_device", "BlockIOReadBandwidth", true, parse_blkio_bandwidth, map_blkio, NULL },
{ "blkio", "blkio.write_bps_device", "BlockIOWriteBandwidth", true, parse_blkio_bandwidth, map_blkio, NULL }
};
int cgroup_semantics_find(
const char *controller,
const char *name,
const char *value,
char **ret,
const CGroupSemantics **_s) {
_cleanup_free_ char *c = NULL;
unsigned i;
int r;
assert(name);
assert(_s);
assert(!value == !ret);
if (!controller) {
r = cg_controller_from_attr(name, &c);
if (r < 0)
return r;
controller = c;
}
for (i = 0; i < ELEMENTSOF(semantics); i++) {
const CGroupSemantics *s = semantics + i;
bool matches_name, matches_pretty;
if (controller && s->controller && !streq(s->controller, controller))
continue;
matches_name = s->name && streq(s->name, name);
matches_pretty = s->pretty && streq(s->pretty, name);
if (!matches_name && !matches_pretty)
continue;
if (value) {
if (matches_pretty && s->map_pretty) {
r = s->map_pretty(s, value, ret);
if (r < 0)
return r;
if (r == 0)
continue;
} else {
char *x;
x = strdup(value);
if (!x)
return -ENOMEM;
*ret = x;
}
}
*_s = s;
return 1;
}
*ret = NULL;
*_s = NULL;
return 0;
}
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2011 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
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/>.
***/
typedef struct CGroupSemantics CGroupSemantics;
struct CGroupSemantics {
const char *controller;
const char *name;
const char *pretty;
bool multiple;
/* This call is used for parsing the pretty value to the actual attribute value */
int (*map_pretty)(const CGroupSemantics *semantics, const char *value, char **ret);
/* Right before writing this attribute the attribute value is converted to a low-level value */
int (*map_write)(const CGroupSemantics *semantics, const char *value, char **ret);
/* If this attribute takes a list, this call can be used to reset the list to empty */
int (*reset)(const CGroupSemantics *semantics, const char *group);
};
int cgroup_semantics_find(const char *controller, const char *name, const char *value, char **ret, const CGroupSemantics **semantics);
......@@ -103,30 +103,31 @@
" <method name=\"ResetFailedUnit\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"GetUnitControlGroupAttributes\">\n" \
" <method name=\"SetUnitControlGroup\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n" \
" <arg name=\"values\" type=\"as\" direction=\"out\"/>\n" \
" <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetUnitControlGroupAttributes\">\n" \
" <method name=\"UnsetUnitControlGroup\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"attributes\" type=\"a(sss)\" direction=\"in\"/>\n" \
" <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>" \
" </method>\n" \
" <method name=\"UnsetUnitControlGroupAttributes\">\n" \
" <method name=\"GetUnitControlGroupAttribute\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"attributes\" type=\"a(ss)\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"values\" type=\"as\" direction=\"out\"/>\n" \
" </method>\n" \
" <method name=\"SetUnitControlGroups\">\n" \
" <method name=\"SetUnitControlGroupAttribute\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"values\" type=\"as\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>" \
" </method>\n" \
" <method name=\"UnsetUnitControlGroups\">\n" \
" <method name=\"UnsetUnitControlGroupAttributes\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>" \
" <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"GetJob\">\n" \
" <arg name=\"id\" type=\"u\" direction=\"in\"/>\n" \
......@@ -874,7 +875,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
if (!reply)
goto oom;
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroups")) {
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroup")) {
const char *name;
Unit *u;
DBusMessageIter iter;
......@@ -902,7 +903,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
if (!reply)
goto oom;
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroups")) {
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroup")) {
const char *name;
Unit *u;
DBusMessageIter iter;
......@@ -930,7 +931,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
if (!reply)
goto oom;
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttributes")) {
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttribute")) {
const char *name;
Unit *u;
DBusMessageIter iter;
......@@ -949,6 +950,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
}
SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
r = bus_unit_cgroup_attribute_set(u, &iter);
if (r < 0)
return bus_send_error_reply(connection, message, NULL, r);
......@@ -957,7 +959,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
if (!reply)
goto oom;
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttributes")) {
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttribute")) {
const char *name;
Unit *u;
DBusMessageIter iter;
......@@ -985,7 +987,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
if (!reply)
goto oom;
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttributes")) {
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttribute")) {
const char *name;
Unit *u;
DBusMessageIter iter;
......@@ -1005,6 +1007,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
}
SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
r = bus_unit_cgroup_attribute_get(u, &iter, &list);
if (r < 0)
return bus_send_error_reply(connection, message, NULL, r);
......
......@@ -344,8 +344,8 @@ static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property
char _cleanup_free_ *v = NULL;
bool success;
if (a-&g