diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/cgroup-attr.c | 58 | ||||
-rw-r--r-- | src/core/cgroup-attr.h | 7 | ||||
-rw-r--r-- | src/core/cgroup-semantics.c | 333 | ||||
-rw-r--r-- | src/core/cgroup-semantics.h | 43 | ||||
-rw-r--r-- | src/core/dbus-manager.c | 41 | ||||
-rw-r--r-- | src/core/dbus-unit.c | 329 | ||||
-rw-r--r-- | src/core/dbus-unit.h | 25 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.m4 | 16 | ||||
-rw-r--r-- | src/core/load-fragment.c | 300 | ||||
-rw-r--r-- | src/core/load-fragment.h | 6 | ||||
-rw-r--r-- | src/core/unit.c | 159 | ||||
-rw-r--r-- | src/core/unit.h | 3 | ||||
-rw-r--r-- | src/shared/path-lookup.c | 9 | ||||
-rw-r--r-- | src/shared/strv.c | 25 | ||||
-rw-r--r-- | src/shared/strv.h | 1 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 155 |
16 files changed, 872 insertions, 638 deletions
diff --git a/src/core/cgroup-attr.c b/src/core/cgroup-attr.c index 1373684bdb..2ab4d4623e 100644 --- a/src/core/cgroup-attr.c +++ b/src/core/cgroup-attr.c @@ -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); +} diff --git a/src/core/cgroup-attr.h b/src/core/cgroup-attr.h index 0f5b854898..0b542981e8 100644 --- a/src/core/cgroup-attr.h +++ b/src/core/cgroup-attr.h @@ -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); diff --git a/src/core/cgroup-semantics.c b/src/core/cgroup-semantics.c new file mode 100644 index 0000000000..82b02bbd78 --- /dev/null +++ b/src/core/cgroup-semantics.c @@ -0,0 +1,333 @@ +/*-*- 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; +} diff --git a/src/core/cgroup-semantics.h b/src/core/cgroup-semantics.h new file mode 100644 index 0000000000..4f848f4bb7 --- /dev/null +++ b/src/core/cgroup-semantics.h @@ -0,0 +1,43 @@ +/*-*- 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); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index de23369397..8f4bbc59b7 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -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); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 4f968c20f2..7c23e1e616 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -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->map_callback) - a->map_callback(a->controller, a->name, a->value, &v); + if (a->semantics && a->semantics->map_write) + a->semantics->map_write(a->semantics, a->value, &v); success = dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && @@ -472,7 +472,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn if (!reply) goto oom; - } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroups")) { + } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroup")) { DBusMessageIter iter; SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); @@ -488,7 +488,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn if (!reply) goto oom; - } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroups")) { + } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroup")) { DBusMessageIter iter; SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop"); @@ -496,14 +496,14 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn if (!dbus_message_iter_init(message, &iter)) goto oom; - r = bus_unit_cgroup_set(u, &iter); + r = bus_unit_cgroup_unset(u, &iter); if (r < 0) return bus_send_error_reply(connection, message, NULL, r); reply = dbus_message_new_method_return(message); if (!reply) goto oom; - } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttributes")) { + } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttribute")) { DBusMessageIter iter; _cleanup_strv_free_ char **list = NULL; @@ -524,7 +524,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn if (bus_append_strv_iter(&iter, list) < 0) goto oom; - } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttributes")) { + } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttribute")) { DBusMessageIter iter; SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); @@ -540,7 +540,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn if (!reply) goto oom; - } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttributes")) { + } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttribute")) { DBusMessageIter iter; SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop"); @@ -897,17 +897,17 @@ oom: return DBUS_HANDLER_RESULT_NEED_MEMORY; } -static int next_and_parse_mode(DBusMessageIter *iter, bool *runtime) { +static int parse_mode(DBusMessageIter *iter, bool *runtime, bool next) { const char *mode; + int r; assert(iter); assert(runtime); - dbus_message_iter_next(iter); - if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) - return -EINVAL; + r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &mode, next); + if (r < 0) + return r; - dbus_message_iter_get_basic(iter, &mode); if (streq(mode, "runtime")) *runtime = true; else if (streq(mode, "persistent")) @@ -919,10 +919,11 @@ static int next_and_parse_mode(DBusMessageIter *iter, bool *runtime) { } int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) { - int r; - _cleanup_strv_free_ char **a = NULL; - char **name; + _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL; + const char *name; + CGroupBonding *b; bool runtime; + int r; assert(u); assert(iter); @@ -930,60 +931,74 @@ int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) { if (!unit_get_exec_context(u)) return -EINVAL; - r = bus_parse_strv_iter(iter, &a); + r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); if (r < 0) return r; - r = next_and_parse_mode(iter, &runtime); + r = parse_mode(iter, &runtime, false); if (r < 0) return r; - STRV_FOREACH(name, a) { - _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL; - CGroupBonding *b; - - r = cg_split_spec(*name, &controller, &new_path); - if (r < 0) - return r; + r = cg_split_spec(name, &controller, &new_path); + if (r < 0) + return r; - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (b) { - old_path = strdup(b->path); - if (!old_path) - return -ENOMEM; - } + if (!new_path) { + new_path = unit_default_cgroup_path(u); + if (!new_path) + return -ENOMEM; + } - r = unit_add_cgroup_from_text(u, *name, true, &b); - if (r < 0) - return r; + if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + return -EINVAL; - if (r > 0) { - /* Try to move things to the new place, and clean up the old place */ - cgroup_bonding_realize(b); - cgroup_bonding_migrate(b, u->cgroup_bondings); + b = cgroup_bonding_find_list(u->cgroup_bondings, controller); + if (b) { + if (streq(b->path, new_path)) + return 0; - if (old_path) - cg_trim(controller, old_path, true); - } + if (b->essential) + return -EINVAL; - contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n" - "ControlGroup=", *name, "\n", NULL); - if (!contents) + old_path = strdup(b->path); + if (!old_path) return -ENOMEM; + } - r = unit_write_drop_in(u, runtime, *name, contents); - if (r < 0) - return r; + r = unit_add_cgroup_from_text(u, name, true, &b); + if (r < 0) + return r; + if (r > 0) { + CGroupAttribute *a; + + /* Try to move things to the new place, and clean up the old place */ + cgroup_bonding_realize(b); + cgroup_bonding_migrate(b, u->cgroup_bondings); + + if (old_path) + cg_trim(controller, old_path, true); + + /* Apply the attributes to the new group */ + LIST_FOREACH(by_unit, a, u->cgroup_attributes) + if (streq(a->controller, controller)) + cgroup_attribute_apply(a, b); } - return 0; + contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n" + "ControlGroup=", name, "\n", NULL); + if (!contents) + return -ENOMEM; + + return unit_write_drop_in(u, runtime, controller, contents); } int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) { - _cleanup_strv_free_ char **a = NULL; - char **name; - int r; + _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL; + const char *name; + CGroupAttribute *a, *n; + CGroupBonding *b; bool runtime; + int r; assert(u); assert(iter); @@ -991,50 +1006,57 @@ int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) { if (!unit_get_exec_context(u)) return -EINVAL; - r = bus_parse_strv_iter(iter, &a); + r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); if (r < 0) return r; - r = next_and_parse_mode(iter, &runtime); + r = parse_mode(iter, &runtime, false); if (r < 0) return r; - STRV_FOREACH(name, a) { - _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL; - CGroupBonding *b; + r = cg_split_spec(name, &controller, &path); + if (r < 0) + return r; - r = cg_split_spec(*name, &controller, &path); - if (r < 0) - return r; + if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + return -EINVAL; - if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - return -EINVAL; + b = cgroup_bonding_find_list(u->cgroup_bondings, controller); + if (!b) + return -ENOENT; - unit_remove_drop_in(u, runtime, *name); + if (path && !path_equal(path, b->path)) + return -ENOENT; - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (!b) - continue; + if (b->essential) + return -EINVAL; - if (path && !path_equal(path, b->path)) - continue; + unit_remove_drop_in(u, runtime, controller); - if (b->essential) - return -EINVAL; + /* Try to migrate the old group away */ + if (cg_get_by_pid(controller, 0, &target) >= 0) + cgroup_bonding_migrate_to(u->cgroup_bondings, target, false); + + cgroup_bonding_free(b, true); - /* Try to migrate the old group away */ - if (cg_get_by_pid(controller, 0, &target) >= 0) - cgroup_bonding_migrate_to(u->cgroup_bondings, target, false); + /* Drop all attributes of this controller */ + LIST_FOREACH_SAFE(by_unit, a, n, u->cgroup_attributes) { + if (!streq(a->controller, controller)) + continue; - cgroup_bonding_free(b, true); + unit_remove_drop_in(u, runtime, a->name); + cgroup_attribute_free(a); } return 0; } int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result) { - _cleanup_strv_free_ char **l = NULL, **result = NULL; - char **name; + _cleanup_free_ char *controller = NULL; + CGroupAttribute *a; + CGroupBonding *b; + const char *name; + char **l = NULL; int r; assert(u); @@ -1044,63 +1066,99 @@ int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_resul if (!unit_get_exec_context(u)) return -EINVAL; - r = bus_parse_strv_iter(iter, &l); + r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, false); if (r < 0) return r; - STRV_FOREACH(name, l) { - _cleanup_free_ char *controller = NULL; - CGroupAttribute *a; - CGroupBonding *b; + r = cg_controller_from_attr(name, &controller); + if (r < 0) + return r; + + /* First attempt, read the value from the kernel */ + b = cgroup_bonding_find_list(u->cgroup_bondings, controller); + if (b) { + _cleanup_free_ char *p = NULL, *v = NULL; - r = cg_controller_from_attr(*name, &controller); + r = cg_get_path(b->controller, b->path, name, &p); if (r < 0) return r; - /* First attempt, read the value from the kernel */ - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (b) { - _cleanup_free_ char *p = NULL, *v = NULL; + r = read_full_file(p, &v, NULL); + if (r >= 0) { + /* Split on new lines */ + l = strv_split_newlines(v); + if (!l) + return -ENOMEM; - r = cg_get_path(b->controller, b->path, *name, &p); - if (r < 0) - return r; + *_result = l; + return 0; - r = read_full_file(p, &v, NULL); - if (r >= 0) { - r = strv_extend(&result, v); - if (r < 0) - return r; - - continue; - } else if (r != -ENOENT) - return r; } + } - /* If that didn't work, read our cached value */ - a = cgroup_attribute_find_list(u->cgroup_attributes, NULL, *name); - if (a) { - r = strv_extend(&result, a->value); - if (r < 0) - return r; + /* If that didn't work, read our cached value */ + LIST_FOREACH(by_unit, a, u->cgroup_attributes) { + if (!cgroup_attribute_matches(a, controller, name)) continue; - } - return -ENOENT; + r = strv_extend(&l, a->value); + if (r < 0) { + strv_free(l); + return r; + } } - *_result = result; - result = NULL; + if (!l) + return -ENOENT; + *_result = l; return 0; } +static int update_attribute_drop_in(Unit *u, bool runtime, const char *name) { + _cleanup_free_ char *buf = NULL; + CGroupAttribute *a; + + assert(u); + assert(name); + + LIST_FOREACH(by_unit, a, u->cgroup_attributes) { + if (!cgroup_attribute_matches(a, NULL, name)) + continue; + + if (!buf) { + buf = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n" + "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL); + + if (!buf) + return -ENOMEM; + } else { + char *b; + + b = strjoin(buf, + "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL); + + if (!b) + return -ENOMEM; + + free(buf); + buf = b; + } + } + + if (buf) + return unit_write_drop_in(u, runtime, name, buf); + else + return unit_remove_drop_in(u, runtime, name); +} + int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) { _cleanup_strv_free_ char **l = NULL; int r; bool runtime = false; - char **name, **value; + char **value; + const char *name; assert(u); assert(iter); @@ -1108,19 +1166,34 @@ int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) { if (!unit_get_exec_context(u)) return -EINVAL; - r = bus_parse_strv_pairs_iter(iter, &l); + r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); + if (r < 0) + return r; + + r = bus_parse_strv_iter(iter, &l); if (r < 0) return r; - r = next_and_parse_mode(iter, &runtime); + if (!dbus_message_iter_next(iter)) + return -EINVAL; + + r = parse_mode(iter, &runtime, false); if (r < 0) return r; - STRV_FOREACH_PAIR(name, value, l) { - _cleanup_free_ char *contents = NULL; + STRV_FOREACH(value, l) { + _cleanup_free_ char *v = NULL; CGroupAttribute *a; + const CGroupSemantics *s; + + r = cgroup_semantics_find(NULL, name, *value, &v, &s); + if (r < 0) + return r; + + if (s && !s->multiple && l[1]) + return -EINVAL; - r = unit_add_cgroup_attribute(u, NULL, *name, *value, NULL, &a); + r = unit_add_cgroup_attribute(u, s, NULL, name, v ? v : *value, &a); if (r < 0) return r; @@ -1144,22 +1217,17 @@ int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) { cgroup_attribute_apply(a, u->cgroup_bondings); } - contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n" - "ControlGroupAttribute=", *name, " ", *value, "\n", NULL); - if (!contents) - return -ENOMEM; - - r = unit_write_drop_in(u, runtime, *name, contents); - if (r < 0) - return r; } + r = update_attribute_drop_in(u, runtime, name); + if (r < 0) + return r; + return 0; } int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) { - _cleanup_strv_free_ char **l = NULL; - char **name; + const char *name; bool runtime; int r; @@ -1169,23 +1237,16 @@ int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) { if (!unit_get_exec_context(u)) return -EINVAL; - r = bus_parse_strv_iter(iter, &l); + r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); if (r < 0) return r; - r = next_and_parse_mode(iter, &runtime); + r = parse_mode(iter, &runtime, false); if (r < 0) return r; - STRV_FOREACH(name, l) { - CGroupAttribute *a; - - a = cgroup_attribute_find_list(u->cgroup_attributes, NULL, *name); - if (a) - cgroup_attribute_free(a); - - unit_remove_drop_in(u, runtime, *name); - } + cgroup_attribute_free_some(u->cgroup_attributes, NULL, name); + update_attribute_drop_in(u, runtime, name); return 0; } diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h index c8903f8e5e..34980b046e 100644 --- a/src/core/dbus-unit.h +++ b/src/core/dbus-unit.h @@ -127,24 +127,25 @@ " <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>\n" \ " <property name=\"ControlGroupAttributes\" type=\"a(sss)\" access=\"read\"/>\n" \ - " <method name=\"GetControlGroupAttributes\">\n" \ - " <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n" \ - " <arg name=\"values\" type=\"as\" direction=\"out\"/>\n" \ - " </method>\n" \ - " <method name=\"SetControlGroupAttributes\">\n" \ - " <arg name=\"attributes\" type=\"a(ss)\" direction=\"in\"/>\n" \ + " <method name=\"SetControlGroup\">\n" \ + " <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ " </method>\n" \ - " <method name=\"UnsetControlGroupAttributes\">\n" \ - " <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n" \ + " <method name=\"UnsetControlGroup\">\n" \ + " <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ " </method>\n" \ - " <method name=\"SetControlGroups\">\n" \ - " <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n" \ + " <method name=\"GetControlGroupAttribute\">\n" \ + " <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"values\" type=\"as\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"SetControlGroupAttribute\">\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=\"UnsetControlGroups\">\n" \ - " <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n" \ + " <method name=\"UnsetControlGroupAttribute\">\n" \ + " <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ " </method>\n" diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 0b6a5cc659..22c1761dc2 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -68,14 +68,14 @@ $1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPR $1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) $1.ControlGroup, config_parse_unit_cgroup, 0, 0 $1.ControlGroupAttribute, config_parse_unit_cgroup_attr, 0, 0 -$1.CPUShares, config_parse_unit_cpu_shares, 0, 0 -$1.MemoryLimit, config_parse_unit_memory_limit, 0, 0 -$1.MemorySoftLimit, config_parse_unit_memory_limit, 0, 0 -$1.DeviceAllow, config_parse_unit_device_allow, 0, 0 -$1.DeviceDeny, config_parse_unit_device_allow, 0, 0 -$1.BlockIOWeight, config_parse_unit_blkio_weight, 0, 0 -$1.BlockIOReadBandwidth, config_parse_unit_blkio_bandwidth, 0, 0 -$1.BlockIOWriteBandwidth, config_parse_unit_blkio_bandwidth, 0, 0 +$1.CPUShares, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.MemoryLimit, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.MemorySoftLimit, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.DeviceAllow, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.DeviceDeny, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.BlockIOWeight, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.BlockIOReadBandwidth, config_parse_unit_cgroup_attr_pretty, 0, 0 +$1.BlockIOWriteBandwidth, config_parse_unit_cgroup_attr_pretty, 0, 0 $1.ReadWriteDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.read_write_dirs) $1.ReadOnlyDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.read_only_dirs) $1.InaccessibleDirectories, config_parse_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 6e333aaf15..d79e1d936c 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1769,7 +1769,9 @@ int config_parse_unit_cgroup_attr( void *userdata) { Unit *u = data; - _cleanup_strv_free_ char **l = NULL; + size_t a, b; + _cleanup_free_ char *n = NULL, *v = NULL; + const CGroupSemantics *s; int r; assert(filename); @@ -1784,160 +1786,24 @@ int config_parse_unit_cgroup_attr( return 0; } - l = strv_split_quoted(rvalue); - if (!l) - return log_oom(); - - if (strv_length(l) != 2) { + a = strcspn(rvalue, WHITESPACE); + b = strspn(rvalue + a, WHITESPACE); + if (a <= 0 || b <= 0) { log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue); return 0; } - r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL, NULL); - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - unsigned long ul; - _cleanup_free_ char *t = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (safe_atolu(rvalue, &ul) < 0 || ul < 1) { - log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (asprintf(&t, "%lu", ul) < 0) - return log_oom(); - - r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL, NULL); - if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - off_t sz; - _cleanup_free_ char *t = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) { - log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (asprintf(&t, "%llu", (unsigned long long) sz) < 0) + n = strndup(rvalue, a); + if (!n) return log_oom(); - r = unit_add_cgroup_attribute(u, - "memory", - streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes", - t, NULL, NULL); + r = cgroup_semantics_find(NULL, n, rvalue + a + b, &v, &s); if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -static int device_map(const char *controller, const char *name, const char *value, char **ret) { - _cleanup_strv_free_ char **l = NULL; - - assert(controller); - assert(name); - assert(value); - assert(ret); - - l = strv_split_quoted(value); - if (!l) - return -ENOMEM; - - assert(strv_length(l) >= 1); - - 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; -} - -int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - _cleanup_strv_free_ char **l = NULL; - int r; - unsigned k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - l = strv_split_quoted(rvalue); - if (!l) - return log_oom(); - - k = strv_length(l); - if (k < 1 || k > 2) { - log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) { - log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (!isempty(l[1]) && !in_charset(l[1], "rwm")) { - log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue); + log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue); return 0; } - r = unit_add_cgroup_attribute(u, "devices", - streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny", - rvalue, device_map, NULL); - + r = unit_add_cgroup_attribute(u, s, NULL, n, v ? v : rvalue + a + b, NULL); if (r < 0) { log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); return 0; @@ -1946,148 +1812,36 @@ int config_parse_unit_device_allow(const char *filename, unsigned line, const ch return 0; } -static int blkio_map(const char *controller, const char *name, const char *value, char **ret) { - struct stat st; - _cleanup_strv_free_ char **l = NULL; - dev_t d; - - assert(controller); - assert(name); - assert(value); - assert(ret); - - l = strv_split_quoted(value); - if (!l) - return log_oom(); - - assert(strv_length(l) == 2); - - 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; -} +int config_parse_unit_cgroup_attr_pretty( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { -int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { Unit *u = data; + _cleanup_free_ char *v = NULL; + const CGroupSemantics *s; int r; - unsigned long ul; - const char *device = NULL, *weight; - unsigned k; - _cleanup_free_ char *t = NULL; - _cleanup_strv_free_ char **l = NULL; assert(filename); assert(lvalue); assert(rvalue); assert(data); - l = strv_split_quoted(rvalue); - if (!l) - return log_oom(); - - k = strv_length(l); - if (k < 1 || k > 2) { - log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (k == 1) - weight = l[0]; - else { - device = l[0]; - weight = l[1]; - } - - if (device && !path_is_absolute(device)) { - log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) { - log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue); - return 0; - } - - if (device) - r = asprintf(&t, "%s %lu", device, ul); - else - r = asprintf(&t, "%lu", ul); - if (r < 0) - return log_oom(); - - if (device) - r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map, NULL); - else - r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL, NULL); + r = cgroup_semantics_find(NULL, lvalue, rvalue, &v, &s); if (r < 0) { - log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { - Unit *u = data; - int r; - off_t bytes; - unsigned k; - _cleanup_free_ char *t = NULL; - _cleanup_strv_free_ char **l = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - l = strv_split_quoted(rvalue); - if (!l) - return log_oom(); - - k = strv_length(l); - if (k != 2) { - log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue); + log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue); return 0; - } - - if (!path_is_absolute(l[0])) { - log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue); + } else if (r == 0) { + log_error("[%s:%u] Unknown or unsupported cgroup attribute %s, ignoring: %s", filename, line, lvalue, rvalue); return 0; } - if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) { - log_error("[%s:%u] Failed to parse block IO bandwidth value, ignoring: %s", filename, line, rvalue); - return 0; - } - - r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes); - if (r < 0) - return log_oom(); - - r = unit_add_cgroup_attribute(u, "blkio", - streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device", - t, blkio_map, NULL); + r = unit_add_cgroup_attribute(u, s, NULL, NULL, v, NULL); if (r < 0) { log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue); return 0; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 421b4c33ec..dfb2ef0c3b 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -75,11 +75,7 @@ int config_parse_kill_mode(const char *filename, unsigned line, const char *sect int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_cgroup_attr_pretty(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_unit_requires_mounts_for(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_syscall_filter(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_environ(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/core/unit.c b/src/core/unit.c index 3a88996eb7..370ad67e59 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -749,15 +749,13 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, b->controller, b->path); LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - char *v = NULL; + _cleanup_free_ char *v = NULL; - if (a->map_callback) - a->map_callback(a->controller, a->name, a->value, &v); + if (a->semantics && a->semantics->map_write) + a->semantics->map_write(a->semantics, a->value, &v); fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n", prefix, a->controller, a->name, v ? v : a->value); - - free(v); } if (UNIT_VTABLE(u)->dump) @@ -1900,30 +1898,12 @@ finish: } int set_unit_path(const char *p) { - char *cwd, *c; - int r; + _cleanup_free_ char *c = NULL; /* This is mostly for debug purposes */ - - if (path_is_absolute(p)) { - if (!(c = strdup(p))) - return -ENOMEM; - } else { - if (!(cwd = get_current_dir_name())) - return -errno; - - r = asprintf(&c, "%s/%s", cwd, p); - free(cwd); - - if (r < 0) - return -ENOMEM; - } - - if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) { - r = -errno; - free(c); - return r; - } + c = path_make_absolute_cwd(p); + if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) + return -errno; return 0; } @@ -2109,7 +2089,7 @@ static int unit_add_one_default_cgroup(Unit *u, const char *controller) { if (r < 0) goto fail; - return 0; + return 1; fail: free(b->path); @@ -2153,10 +2133,10 @@ CGroupBonding* unit_get_default_cgroup(Unit *u) { int unit_add_cgroup_attribute( Unit *u, + const CGroupSemantics *semantics, const char *controller, const char *name, const char *value, - CGroupAttributeMapCallback map_callback, CGroupAttribute **ret) { _cleanup_free_ char *c = NULL; @@ -2164,48 +2144,67 @@ int unit_add_cgroup_attribute( int r; assert(u); - assert(name); assert(value); + if (semantics) { + /* Semantics always take precedence */ + if (semantics->name) + name = semantics->name; + + if (semantics->controller) + controller = semantics->controller; + } + + if (!name) + return -EINVAL; + if (!controller) { r = cg_controller_from_attr(name, &c); if (r < 0) return -EINVAL; controller = c; - } else { - if (!filename_is_safe(name)) - return -EINVAL; - - if (!filename_is_safe(controller)) - return -EINVAL; } if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) return -EINVAL; + if (!filename_is_safe(name)) + return -EINVAL; + + if (!filename_is_safe(controller)) + return -EINVAL; + + /* Check if this attribute already exists. Note that we will + * explicitly check for the value here too, as there are + * attributes which accept multiple values. */ a = cgroup_attribute_find_list(u->cgroup_attributes, controller, name); if (a) { - char *v; - if (streq(value, a->value)) { + /* Exactly the same value is always OK, let's ignore this */ if (ret) *ret = a; return 0; } - v = strdup(value); - if (!v) - return -ENOMEM; + if (semantics && !semantics->multiple) { + char *v; - free(a->value); - a->value = v; + /* If this is a single-item entry, we can + * simply patch the existing attribute */ + + v = strdup(value); + if (!v) + return -ENOMEM; - if (ret) - *ret = a; + free(a->value); + a->value = v; - return 1; + if (ret) + *ret = a; + return 1; + } } a = new0(CGroupAttribute, 1); @@ -2226,11 +2225,10 @@ int unit_add_cgroup_attribute( free(a->name); free(a->value); free(a); - return -ENOMEM; } - a->map_callback = map_callback; + a->semantics = semantics; a->unit = u; LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a); @@ -2763,52 +2761,77 @@ ExecContext *unit_get_exec_context(Unit *u) { return (ExecContext*) ((uint8_t*) u + offset); } -int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) { - _cleanup_free_ char *p = NULL, *q = NULL; +static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char **_q) { + char *p, *q; + int r; + assert(u); + assert(name); + assert(_p); + assert(_q); - if (u->manager->running_as != SYSTEMD_SYSTEM) + if (u->manager->running_as == SYSTEMD_USER && runtime) return -ENOTSUP; if (!filename_is_safe(name)) return -EINVAL; - p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL); + if (u->manager->running_as == SYSTEMD_USER) { + _cleanup_free_ char *c = NULL; + + r = user_config_home(&c); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + p = strjoin(c, "/", u->id, ".d", NULL); + } else if (runtime) + p = strjoin("/run/systemd/system/", u->id, ".d", NULL); + else + p = strjoin("/etc/systemd/system/", u->id, ".d", NULL); if (!p) return -ENOMEM; q = strjoin(p, "/50-", name, ".conf", NULL); - if (!q) + if (!q) { + free(p); return -ENOMEM; + } - mkdir_p(p, 0755); - return write_one_line_file_atomic_label(q, data); + *_p = p; + *_q = q; + return 0; } -int unit_remove_drop_in(Unit *u, bool runtime, const char *name) { +int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) { _cleanup_free_ char *p = NULL, *q = NULL; + int r; assert(u); - if (u->manager->running_as != SYSTEMD_SYSTEM) - return -ENOTSUP; + r = drop_in_file(u, runtime, name, &p, &q); + if (r < 0) + return r; - if (!filename_is_safe(name)) - return -EINVAL; + mkdir_p(p, 0755); + return write_one_line_file_atomic_label(q, data); +} - p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL); - if (!p) - return -ENOMEM; +int unit_remove_drop_in(Unit *u, bool runtime, const char *name) { + _cleanup_free_ char *p = NULL, *q = NULL; + int r; - q = strjoin(p, "/50-", name, ".conf", NULL); - if (!q) - return -ENOMEM; + assert(u); + r = drop_in_file(u, runtime, name, &p, &q); if (unlink(q) < 0) - return -errno; + r = -errno; + else + r = 0; rmdir(p); - return 0; + return r; } int unit_kill_context( diff --git a/src/core/unit.h b/src/core/unit.h index c90210302d..17a5a5f0d9 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -40,6 +40,7 @@ typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; #include "condition.h" #include "install.h" #include "unit-name.h" +#include "cgroup-semantics.h" enum UnitActiveState { UNIT_ACTIVE, @@ -445,7 +446,7 @@ int unit_add_cgroup(Unit *u, CGroupBonding *b); int unit_add_cgroup_from_text(Unit *u, const char *name, bool overwrite, CGroupBonding **ret); int unit_add_default_cgroups(Unit *u); CGroupBonding* unit_get_default_cgroup(Unit *u); -int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback, CGroupAttribute **ret); +int unit_add_cgroup_attribute(Unit *u, const CGroupSemantics *semantics, const char *controller, const char *name, const char *value, CGroupAttribute **ret); int unit_choose_id(Unit *u, const char *name); int unit_set_description(Unit *u, const char *description); diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c index fa4995ceea..ffdc5367cb 100644 --- a/src/shared/path-lookup.c +++ b/src/shared/path-lookup.c @@ -41,21 +41,26 @@ DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs); int user_config_home(char **config_home) { const char *e; + char *r; e = getenv("XDG_CONFIG_HOME"); if (e) { - if (asprintf(config_home, "%s/systemd/user", e) < 0) + r = strappend(e, "/systemd/user"); + if (!r) return -ENOMEM; + *config_home = r; return 1; } else { const char *home; home = getenv("HOME"); if (home) { - if (asprintf(config_home, "%s/.config/systemd/user", home) < 0) + r = strappend(home, "/.config/systemd/user"); + if (!r) return -ENOMEM; + *config_home = r; return 1; } } diff --git a/src/shared/strv.c b/src/shared/strv.c index ec25755289..60c4762572 100644 --- a/src/shared/strv.c +++ b/src/shared/strv.c @@ -305,6 +305,31 @@ char **strv_split_quoted(const char *s) { return r; } +char **strv_split_newlines(const char *s) { + char **l; + unsigned n; + + assert(s); + + /* Special version of strv_split() that splits on newlines and + * suppresses an empty string at the end */ + + l = strv_split(s, NEWLINE); + if (!l) + return NULL; + + n = strv_length(l); + if (n <= 0) + return l; + + if (isempty(l[n-1])) { + free(l[n-1]); + l[n-1] = NULL; + } + + return l; +} + char *strv_join(char **l, const char *separator) { char *r, *e; char **s; diff --git a/src/shared/strv.h b/src/shared/strv.h index b3802a7a3f..623f10216d 100644 --- a/src/shared/strv.h +++ b/src/shared/strv.h @@ -58,6 +58,7 @@ static inline bool strv_isempty(char **l) { char **strv_split(const char *s, const char *separator) _malloc_; char **strv_split_quoted(const char *s) _malloc_; +char **strv_split_newlines(const char *s) _malloc_; char *strv_join(char **l, const char *separator) _malloc_; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 65c5eca559..a2dbbe82cd 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1963,53 +1963,41 @@ static int kill_unit(DBusConnection *bus, char **args) { } static int set_cgroup(DBusConnection *bus, char **args) { - _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; - DBusError error; - const char *method; - DBusMessageIter iter; - int r; _cleanup_free_ char *n = NULL; - const char *runtime; + const char *method, *runtime; + char **argument; + int r; assert(bus); assert(args); - dbus_error_init(&error); - method = - streq(args[0], "set-cgroup") ? "SetUnitControlGroups" : - streq(args[0], "unset-group") ? "UnsetUnitControlGroups" - : "UnsetUnitControlGroupAttributes"; + streq(args[0], "set-cgroup") ? "SetUnitControlGroup" : + streq(args[0], "unset-cgroup") ? "UnsetUnitControlGroup" + : "UnsetUnitControlGroupAttribute"; + + runtime = arg_runtime ? "runtime" : "persistent"; n = unit_name_mangle(args[1]); if (!n) return log_oom(); - m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method); - if (!m) - return log_oom(); - - dbus_message_iter_init_append(m, &iter); - if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n)) - return log_oom(); - - r = bus_append_strv_iter(&iter, args + 2); - if (r < 0) - return log_oom(); - - runtime = arg_runtime ? "runtime" : "persistent"; - if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime)) - return log_oom(); + STRV_FOREACH(argument, args + 2) { - reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); - if (!reply) { - log_error("Failed to issue method call: %s", bus_error_message(&error)); - dbus_error_free(&error); - return -EIO; + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method, + NULL, + NULL, + DBUS_TYPE_STRING, &n, + DBUS_TYPE_STRING, argument, + DBUS_TYPE_STRING, &runtime, + DBUS_TYPE_INVALID); + if (r < 0) + return r; } return 0; @@ -2018,20 +2006,17 @@ static int set_cgroup(DBusConnection *bus, char **args) { static int set_cgroup_attr(DBusConnection *bus, char **args) { _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; DBusError error; - DBusMessageIter iter, sub, sub2; - char **x, **y; + DBusMessageIter iter; _cleanup_free_ char *n = NULL; const char *runtime; + int r; assert(bus); assert(args); dbus_error_init(&error); - if (strv_length(args) % 2 != 0) { - log_error("Expecting an uneven number of arguments!"); - return -EINVAL; - } + runtime = arg_runtime ? "runtime" : "persistent"; n = unit_name_mangle(args[1]); if (!n) @@ -2041,26 +2026,20 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) { "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "SetUnitControlGroupAttributes"); + "SetUnitControlGroupAttribute"); if (!m) return log_oom(); dbus_message_iter_init_append(m, &iter); if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n) || - !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub)) + !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &args[2])) return log_oom(); - STRV_FOREACH_PAIR(x, y, args + 2) { - if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, x) || - !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, y) || - !dbus_message_iter_close_container(&sub, &sub2)) - return log_oom(); - } + r = bus_append_strv_iter(&iter, args + 3); + if (r < 0) + return log_oom(); - runtime = arg_runtime ? "runtime" : "persistent"; - if (!dbus_message_iter_close_container(&iter, &sub) || - !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime)) + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime)) return log_oom(); reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); @@ -2075,57 +2054,49 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) { static int get_cgroup_attr(DBusConnection *bus, char **args) { _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; - DBusError error; - DBusMessageIter iter; - int r; _cleanup_free_ char *n = NULL; - _cleanup_strv_free_ char **list = NULL; - char **a; + char **argument; + int r; assert(bus); assert(args); - dbus_error_init(&error); - n = unit_name_mangle(args[1]); if (!n) return log_oom(); - m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitControlGroupAttributes"); - if (!m) - return log_oom(); - - dbus_message_iter_init_append(m, &iter); - if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n)) - return log_oom(); - - r = bus_append_strv_iter(&iter, args + 2); - if (r < 0) - return log_oom(); + STRV_FOREACH(argument, args + 2) { + _cleanup_strv_free_ char **list = NULL; + DBusMessageIter iter; + char **a; - reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); - if (!reply) { - log_error("Failed to issue method call: %s", bus_error_message(&error)); - dbus_error_free(&error); - return -EIO; - } + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitControlGroupAttribute", + &reply, + NULL, + DBUS_TYPE_STRING, &n, + DBUS_TYPE_STRING, argument, + DBUS_TYPE_INVALID); + if (r < 0) + return r; - dbus_message_iter_init(reply, &iter); - r = bus_parse_strv_iter(&iter, &list); - if (r < 0) { - log_error("Failed to parse value list."); - return r; - } + dbus_message_iter_init(reply, &iter); + r = bus_parse_strv_iter(&iter, &list); + if (r < 0) { + log_error("Failed to parse value list."); + return r; + } - STRV_FOREACH(a, list) { - if (endswith(*a, "\n")) - fputs(*a, stdout); - else - puts(*a); + STRV_FOREACH(a, list) { + if (endswith(*a, "\n")) + fputs(*a, stdout); + else + puts(*a); + } } return 0; |