diff options
Diffstat (limited to 'src')
34 files changed, 2051 insertions, 769 deletions
diff --git a/src/activate/activate.c b/src/activate/activate.c new file mode 100644 index 0000000000..7fcb0a97c4 --- /dev/null +++ b/src/activate/activate.c @@ -0,0 +1,410 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 <unistd.h> +#include <fcntl.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <getopt.h> + +#include <systemd/sd-daemon.h> + +#include "socket-util.h" +#include "build.h" +#include "log.h" +#include "strv.h" +#include "macro.h" + +static char** arg_listen = NULL; +static bool arg_accept = false; +static char** arg_args = NULL; + +static int add_epoll(int epoll_fd, int fd) { + int r; + struct epoll_event ev = {EPOLLIN}; + ev.data.fd = fd; + + assert(epoll_fd >= 0); + assert(fd >= 0); + + r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (r < 0) + log_error("Failed to add event on epoll fd:%d for fd:%d: %s", + epoll_fd, fd, strerror(-r)); + return r; +} + +static int set_nocloexec(int fd) { + int flags; + + flags = fcntl(fd, F_GETFD); + if (flags < 0) { + log_error("Querying flags for fd:%d: %m", fd); + return -errno; + } + + if (!(flags & FD_CLOEXEC)) + return 0; + + if (fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) { + log_error("Settings flags for fd:%d: %m", fd); + return -errno; + } + + return 0; +} + +static int print_socket(const char* desc, int fd) { + int r; + SocketAddress addr = { + .size = sizeof(union sockaddr_union), + .type = SOCK_STREAM, + }; + int family; + + r = getsockname(fd, &addr.sockaddr.sa, &addr.size); + if (r < 0) { + log_warning("Failed to query socket on fd:%d: %m", fd); + return 0; + } + + family = socket_address_family(&addr); + switch(family) { + case AF_INET: + case AF_INET6: { + char* _cleanup_free_ a = NULL; + r = socket_address_print(&addr, &a); + if (r < 0) + log_warning("socket_address_print(): %s", strerror(-r)); + else + log_info("%s %s address %s", + desc, + family == AF_INET ? "IP" : "IPv6", + a); + break; + } + default: + log_warning("Connection with unknown family %d", family); + } + + return 0; +} + +static int open_sockets(int *epoll_fd, bool accept) { + int n, fd; + int count = 0; + char **address; + + n = sd_listen_fds(true); + if (n < 0) { + log_error("Failed to read listening file descriptors from environment: %s", + strerror(-n)); + return n; + } + log_info("Received %d descriptors", n); + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + log_debug("Received descriptor fd:%d", fd); + print_socket("Listening on", fd); + + if (!arg_accept) { + int r = set_nocloexec(fd); + if (r < 0) + return r; + } + + count ++; + } + + STRV_FOREACH(address, arg_listen) { + log_info("Opening address %s", *address); + + fd = make_socket_fd(*address, SOCK_STREAM | (arg_accept*SOCK_CLOEXEC)); + if (fd < 0) { + log_error("Failed to open '%s': %s", *address, strerror(-fd)); + return fd; + } + + count ++; + } + + *epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (*epoll_fd < 0) { + log_error("Failed to create epoll object: %m"); + return -errno; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) { + int r = add_epoll(*epoll_fd, fd); + if (r < 0) + return r; + } + + return count; +} + +static int launch(char* name, char **argv, char **environ, int fds) { + unsigned n_env = 0; + char* envp[7] = {NULL}; /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID */ + static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="}; + char _cleanup_free_ *tmp = NULL; + unsigned i; + + for (i = 0; i < ELEMENTSOF(tocopy); i++) { + envp[n_env] = strv_find_prefix(environ, tocopy[i]); + if (envp[n_env]) + n_env ++; + } + + if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) || + (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0)) + return log_oom(); + + tmp = strv_join(argv, " "); + if (!tmp) + return log_oom(); + + log_info("Execing %s (%s)", name, tmp); + execvpe(name, argv, envp); + log_error("Failed to execp %s (%s): %m", name, tmp); + return -errno; +} + +static int launch1(const char* child, char** argv, char **environ, int fd) { + pid_t parent_pid, child_pid; + int r; + + char _cleanup_free_ *tmp = NULL; + tmp = strv_join(argv, " "); + if (!tmp) + return log_oom(); + + parent_pid = getpid(); + + child_pid = fork(); + if (child_pid < 0) { + log_error("Failed to fork: %m"); + return -errno; + } + + /* In the child */ + if (child_pid == 0) { + r = dup2(fd, STDIN_FILENO); + if (r < 0) { + log_error("Failed to dup connection to stdin: %m"); + _exit(EXIT_FAILURE); + } + + r = dup2(fd, STDOUT_FILENO); + if (r < 0) { + log_error("Failed to dup connection to stdout: %m"); + _exit(EXIT_FAILURE); + } + + r = close(fd); + if (r < 0) { + log_error("Failed to close dupped connection: %m"); + _exit(EXIT_FAILURE); + } + + /* Make sure the child goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + execvp(child, argv); + log_error("Failed to exec child %s: %m", child); + _exit(EXIT_FAILURE); + } + + log_info("Spawned %s (%s) as PID %d", child, tmp, child_pid); + + return 0; +} + +static int do_accept(const char* name, char **argv, char **envp, int fd) { + SocketAddress addr = { + .size = sizeof(union sockaddr_union), + .type = SOCK_STREAM, + }; + int fd2, r; + + fd2 = accept(fd, &addr.sockaddr.sa, &addr.size); + if (fd2 < 0) { + log_error("Failed to accept connection on fd:%d: %m", fd); + return fd2; + } + + print_socket("Connection from", fd2); + + r = launch1(name, argv, envp, fd2); + return r; +} + +/* SIGCHLD handler. */ +static void sigchld_hdl(int sig, siginfo_t *t, void *data) +{ + log_info("Child %d died with code %d", t->si_pid, t->si_status); + /* Wait for a dead child. */ + waitpid(t->si_pid, NULL, 0); +} + +static int install_chld_handler(void) { + int r; + struct sigaction act; + zero(act); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = sigchld_hdl; + + r = sigaction(SIGCHLD, &act, 0); + if (r < 0) + log_error("Failed to install SIGCHLD handler: %m"); + return r; +} + +static int help(void) { + printf("%s [OPTIONS...]\n\n" + "Listen on sockets and launch child on connection.\n\n" + "Options:\n" + " -l --listen=ADDR Listen for raw connections at ADDR\n" + " -a --accept Spawn separate child for each connection\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + "\n" + "Note: file descriptors from sd_listen_fds() will be passed through.\n" + , program_invocation_short_name + ); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "listen", required_argument, NULL, 'l' }, + { "accept", no_argument, NULL, 'a' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hl:sa", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0 /* done */; + + case 'l': { + int r = strv_extend(&arg_listen, optarg); + if (r < 0) + return r; + + break; + } + + case 'a': + arg_accept = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + + if (optind == argc) { + log_error("Usage: %s [OPTION...] PROGRAM [OPTION...]", + program_invocation_short_name); + return -EINVAL; + } + + arg_args = argv + optind; + + return 1 /* work to do */; +} + +int main(int argc, char **argv, char **envp) { + int r, n; + int epoll_fd = -1; + + log_set_max_level(LOG_DEBUG); + log_show_color(true); + log_parse_environment(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + + r = install_chld_handler(); + if (r < 0) + return EXIT_FAILURE; + + n = open_sockets(&epoll_fd, arg_accept); + if (n < 0) + return EXIT_FAILURE; + + while (true) { + struct epoll_event event; + + r = epoll_wait(epoll_fd, &event, 1, -1); + if (r < 0) { + if (errno == EINTR) + continue; + + log_error("epoll_wait() failed: %m"); + return EXIT_FAILURE; + } + + log_info("Communication attempt on fd:%d", event.data.fd); + if (arg_accept) { + r = do_accept(argv[optind], argv + optind, envp, + event.data.fd); + if (r < 0) + return EXIT_FAILURE; + } else + break; + } + + launch(argv[optind], argv + optind, envp, n); + + return EXIT_SUCCESS; +} 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/execute.c b/src/core/execute.c index b28962a3c3..92cf174641 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -165,6 +165,14 @@ void exec_context_tty_reset(const ExecContext *context) { vt_disallocate(context->tty_path); } +static bool is_terminal_output(ExecOutput o) { + return + o == EXEC_OUTPUT_TTY || + o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || + o == EXEC_OUTPUT_KMSG_AND_CONSOLE || + o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE; +} + static int open_null_as(int flags, int nfd) { int fd, r; @@ -224,7 +232,7 @@ static int connect_logger_as(const ExecContext *context, ExecOutput output, cons !!context->syslog_level_prefix, output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE, - output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE); + is_terminal_output(output)); if (fd != nfd) { r = dup2(fd, nfd) < 0 ? -errno : nfd; @@ -1710,6 +1718,37 @@ int exec_context_load_environment(const ExecContext *c, char ***l) { return 0; } +static bool tty_may_match_dev_console(const char *tty) { + char *active = NULL, *console; + bool b; + + if (startswith(tty, "/dev/")) + tty += 5; + + /* trivial identity? */ + if (streq(tty, "console")) + return true; + + console = resolve_dev_console(&active); + /* if we could not resolve, assume it may */ + if (!console) + return true; + + /* "tty0" means the active VC, so it may be the same sometimes */ + b = streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); + free(active); + + return b; +} + +bool exec_context_may_touch_console(ExecContext *ec) { + return (ec->tty_reset || ec->tty_vhangup || ec->tty_vt_disallocate || + is_terminal_input(ec->std_input) || + is_terminal_output(ec->std_output) || + is_terminal_output(ec->std_error)) && + tty_may_match_dev_console(tty_path(ec)); +} + static void strv_fprintf(FILE *f, char **l) { char **g; diff --git a/src/core/execute.h b/src/core/execute.h index 2bcd2e1e6c..001eb0e7cc 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -198,6 +198,8 @@ void exec_context_tty_reset(const ExecContext *context); int exec_context_load_environment(const ExecContext *c, char ***l); +bool exec_context_may_touch_console(ExecContext *c); + void exec_status_start(ExecStatus *s, pid_t pid); void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); diff --git a/src/core/job.c b/src/core/job.c index 990607f990..90de550016 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -206,6 +206,7 @@ Job* job_install(Job *j) { "Merged into running job, re-running: %s/%s as %u", uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); uj->state = JOB_WAITING; + uj->manager->n_running_jobs--; return uj; } } @@ -483,7 +484,7 @@ static void job_change_type(Job *j, JobType newtype) { int job_run_and_invalidate(Job *j) { int r; uint32_t id; - Manager *m; + Manager *m = j->manager; assert(j); assert(j->installed); @@ -500,6 +501,7 @@ int job_run_and_invalidate(Job *j) { return -EAGAIN; j->state = JOB_RUNNING; + m->n_running_jobs++; job_add_to_dbus_queue(j); /* While we execute this operation the job might go away (for @@ -508,7 +510,6 @@ int job_run_and_invalidate(Job *j) { * store the id here, so that we can verify the job is still * valid. */ id = j->id; - m = j->manager; switch (j->type) { @@ -558,9 +559,10 @@ int job_run_and_invalidate(Job *j) { r = job_finish_and_invalidate(j, JOB_DONE, true); else if (r == -ENOEXEC) r = job_finish_and_invalidate(j, JOB_SKIPPED, true); - else if (r == -EAGAIN) + else if (r == -EAGAIN) { j->state = JOB_WAITING; - else if (r < 0) + m->n_running_jobs--; + } else if (r < 0) r = job_finish_and_invalidate(j, JOB_FAILED, true); } @@ -642,20 +644,20 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) { case JOB_DONE: if (u->condition_result) - unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format, unit_description(u)); + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format); break; case JOB_FAILED: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format, unit_description(u)); - unit_status_printf(u, NULL, "See 'systemctl status %s' for details.", u->id); + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format); + manager_status_printf(u->manager, false, NULL, "See 'systemctl status %s' for details.", u->id); break; case JOB_DEPENDENCY: - unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, format, unit_description(u)); + unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, format); break; case JOB_TIMEOUT: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format, unit_description(u)); + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format); break; default: @@ -671,12 +673,12 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) { switch (result) { case JOB_TIMEOUT: - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format, unit_description(u)); + unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format); break; case JOB_DONE: case JOB_FAILED: - unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format, unit_description(u)); + unit_status_printf(u, ANSI_HIGHLIGHT_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format); break; default: @@ -689,7 +691,7 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) { * Most likely a DEPEND warning from a requisiting unit will * occur next and it's nice to see what was requisited. */ if (result == JOB_SKIPPED) - unit_status_printf(u, ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, "%s is not active.", unit_description(u)); + unit_status_printf(u, ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, "%s is not active."); } } @@ -760,6 +762,9 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) { j->result = result; + if (j->state == JOB_RUNNING) + j->manager->n_running_jobs--; + log_debug_unit(u->id, "Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result)); 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/manager.c b/src/core/manager.c index a578813617..ec12a75371 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -79,6 +79,11 @@ /* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */ #define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC) +/* Initial delay and the interval for printing status messages about running jobs */ +#define JOBS_IN_PROGRESS_WAIT_SEC 5 +#define JOBS_IN_PROGRESS_PERIOD_SEC 1 +#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3 + /* Where clients shall send notification messages to */ #define NOTIFY_SOCKET "@/org/freedesktop/systemd1/notify" @@ -140,6 +145,146 @@ static int manager_setup_notify(Manager *m) { return 0; } +static int manager_jobs_in_progress_mod_timer(Manager *m) { + struct itimerspec its; + + zero(its); + + its.it_value.tv_sec = JOBS_IN_PROGRESS_WAIT_SEC; + its.it_interval.tv_sec = JOBS_IN_PROGRESS_PERIOD_SEC; + + if (timerfd_settime(m->jobs_in_progress_watch.fd, 0, &its, NULL) < 0) + return -errno; + + return 0; +} + +static int manager_watch_jobs_in_progress(Manager *m) { + struct epoll_event ev; + int r; + + assert(m); + + if (m->jobs_in_progress_watch.type != WATCH_INVALID) + return 0; + + m->jobs_in_progress_watch.type = WATCH_JOBS_IN_PROGRESS; + m->jobs_in_progress_watch.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (m->jobs_in_progress_watch.fd < 0) { + log_error("Failed to create timerfd: %m"); + r = -errno; + goto err; + } + + r = manager_jobs_in_progress_mod_timer(m); + if (r < 0) { + log_error("Failed to set up timer for jobs progress watch: %s", strerror(-r)); + goto err; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.ptr = &m->jobs_in_progress_watch; + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->jobs_in_progress_watch.fd, &ev) < 0) { + log_error("Failed to add jobs progress timer fd to epoll: %m"); + r = -errno; + goto err; + } + + log_debug("Set up jobs progress timerfd."); + + return 0; + +err: + if (m->jobs_in_progress_watch.fd >= 0) + close_nointr_nofail(m->jobs_in_progress_watch.fd); + watch_init(&m->jobs_in_progress_watch); + return r; +} + +static void manager_unwatch_jobs_in_progress(Manager *m) { + if (m->jobs_in_progress_watch.type != WATCH_JOBS_IN_PROGRESS) + return; + + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, m->jobs_in_progress_watch.fd, NULL) >= 0); + close_nointr_nofail(m->jobs_in_progress_watch.fd); + watch_init(&m->jobs_in_progress_watch); + m->jobs_in_progress_iteration = 0; + + log_debug("Closed jobs progress timerfd."); +} + +#define CYLON_BUFFER_EXTRA (2*strlen(ANSI_RED_ON) + strlen(ANSI_HIGHLIGHT_RED_ON) + 2*strlen(ANSI_HIGHLIGHT_OFF)) +static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { + char *p = buffer; + + assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); + assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ + + if (pos > 1) { + if (pos > 2) { + memset(p, ' ', pos-2); + p += pos-2; + } + memcpy(p, ANSI_RED_ON, strlen(ANSI_RED_ON)); + p += strlen(ANSI_RED_ON); + *p++ = '*'; + } + + if (pos > 0 && pos <= width) { + memcpy(p, ANSI_HIGHLIGHT_RED_ON, strlen(ANSI_HIGHLIGHT_RED_ON)); + p += strlen(ANSI_HIGHLIGHT_RED_ON); + *p++ = '*'; + } + + memcpy(p, ANSI_HIGHLIGHT_OFF, strlen(ANSI_HIGHLIGHT_OFF)); + p += strlen(ANSI_HIGHLIGHT_OFF); + + if (pos < width) { + memcpy(p, ANSI_RED_ON, strlen(ANSI_RED_ON)); + p += strlen(ANSI_RED_ON); + *p++ = '*'; + if (pos < width-1) { + memset(p, ' ', width-1-pos); + p += width-1-pos; + } + memcpy(p, ANSI_HIGHLIGHT_OFF, strlen(ANSI_HIGHLIGHT_OFF)); + p += strlen(ANSI_HIGHLIGHT_OFF); + } + *p = 0; +} + +static void manager_print_jobs_in_progress(Manager *m) { + Iterator i; + Job *j; + char *job_of_n = NULL; + unsigned counter = 0, print_nr; + char cylon[6 + CYLON_BUFFER_EXTRA + 1]; + unsigned cylon_pos; + + print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs; + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->state == JOB_RUNNING && counter++ == print_nr) + break; + + cylon_pos = m->jobs_in_progress_iteration % 14; + if (cylon_pos >= 8) + cylon_pos = 14 - cylon_pos; + draw_cylon(cylon, sizeof(cylon), 6, cylon_pos); + + if (m->n_running_jobs > 1) + if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0) + job_of_n = NULL; + + manager_status_printf(m, true, cylon, "%sA %s job is running for %s", + strempty(job_of_n), job_type_to_string(j->type), unit_description(j->unit)); + free(job_of_n); + + m->jobs_in_progress_iteration++; +} + static int manager_setup_time_change(Manager *m) { struct epoll_event ev; struct itimerspec its; @@ -324,6 +469,7 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { watch_init(&m->swap_watch); watch_init(&m->udev_watch); watch_init(&m->time_change_watch); + watch_init(&m->jobs_in_progress_watch); m->epoll_fd = m->dev_autofs_fd = -1; m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ @@ -564,6 +710,8 @@ void manager_free(Manager *m) { close_nointr_nofail(m->notify_watch.fd); if (m->time_change_watch.fd >= 0) close_nointr_nofail(m->time_change_watch.fd); + if (m->jobs_in_progress_watch.fd >= 0) + close_nointr_nofail(m->jobs_in_progress_watch.fd); free(m->notify_socket); @@ -988,6 +1136,10 @@ unsigned manager_dispatch_run_queue(Manager *m) { } m->dispatching_run_queue = false; + + if (hashmap_size(m->jobs) > 0) + manager_watch_jobs_in_progress(m); + return n; } @@ -1527,6 +1679,16 @@ static int process_event(Manager *m, struct epoll_event *ev) { break; } + case WATCH_JOBS_IN_PROGRESS: { + uint64_t v; + + /* not interested in the data */ + read(w->fd, &v, sizeof(v)); + + manager_print_jobs_in_progress(m); + break; + } + default: log_error("event type=%i", w->type); assert_not_reached("Unknown epoll event type."); @@ -2152,7 +2314,7 @@ finish: return r; } -bool manager_is_booting_or_shutting_down(Manager *m) { +static bool manager_is_booting_or_shutting_down(Manager *m) { Unit *u; assert(m); @@ -2199,8 +2361,10 @@ void manager_check_finished(Manager *m) { assert(m); - if (hashmap_size(m->jobs) > 0) + if (hashmap_size(m->jobs) > 0) { + manager_jobs_in_progress_mod_timer(m); return; + } /* Notify Type=idle units that we are done now */ close_pipe(m->idle_pipe); @@ -2208,6 +2372,8 @@ void manager_check_finished(Manager *m) { /* Turn off confirm spawn now */ m->confirm_spawn = false; + manager_unwatch_jobs_in_progress(m); + if (dual_timestamp_is_set(&m->finish_timestamp)) return; @@ -2479,7 +2645,7 @@ void manager_set_show_status(Manager *m, bool b) { unlink("/run/systemd/show-status"); } -bool manager_get_show_status(Manager *m) { +static bool manager_get_show_status(Manager *m) { assert(m); if (m->running_as != SYSTEMD_SYSTEM) @@ -2494,6 +2660,25 @@ bool manager_get_show_status(Manager *m) { return plymouth_running(); } +void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...) { + va_list ap; + + if (!manager_get_show_status(m)) + return; + + /* XXX We should totally drop the check for ephemeral here + * and thus effectively make 'Type=idle' pointless. */ + if (ephemeral && m->n_on_console > 0) + return; + + if (!manager_is_booting_or_shutting_down(m)) + return; + + va_start(ap, format); + status_vprintf(status, true, ephemeral, format, ap); + va_end(ap); +} + void watch_init(Watch *w) { assert(w); diff --git a/src/core/manager.h b/src/core/manager.h index cc4edf8f1e..c486a16887 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -61,7 +61,8 @@ enum WatchType { WATCH_UDEV, WATCH_DBUS_WATCH, WATCH_DBUS_TIMEOUT, - WATCH_TIME_CHANGE + WATCH_TIME_CHANGE, + WATCH_JOBS_IN_PROGRESS }; struct Watch { @@ -127,6 +128,7 @@ struct Manager { Watch notify_watch; Watch signal_watch; Watch time_change_watch; + Watch jobs_in_progress_watch; int epoll_fd; @@ -225,6 +227,11 @@ struct Manager { unsigned n_installed_jobs; unsigned n_failed_jobs; + /* Jobs in progress watching */ + unsigned n_running_jobs; + unsigned n_on_console; + unsigned jobs_in_progress_iteration; + /* Type=idle pipes */ int idle_pipe[2]; @@ -276,8 +283,6 @@ int manager_distribute_fds(Manager *m, FDSet *fds); int manager_reload(Manager *m); -bool manager_is_booting_or_shutting_down(Manager *m); - void manager_reset_failed(Manager *m); void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); @@ -293,6 +298,6 @@ void manager_undo_generators(Manager *m); void manager_recheck_journal(Manager *m); void manager_set_show_status(Manager *m, bool b); -bool manager_get_show_status(Manager *m); +void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...); void watch_init(Watch *w); diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index e7e2736615..dab3601467 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -77,10 +77,6 @@ static const MountPoint mount_table[] = { NULL, MNT_FATAL|MNT_IN_CONTAINER }, { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL, MNT_NONE }, -#ifdef ENABLE_EFI - { "efivarfs", "/sys/firmware/efi/efivars", "efivarfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - is_efi_boot, MNT_NONE }, -#endif { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, NULL, MNT_FATAL|MNT_IN_CONTAINER }, { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, @@ -91,6 +87,12 @@ static const MountPoint mount_table[] = { NULL, MNT_IN_CONTAINER }, { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL, MNT_IN_CONTAINER }, + { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_NONE }, +#ifdef ENABLE_EFI + { "efivarfs", "/sys/firmware/efi/efivars", "efivarfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + is_efi_boot, MNT_NONE }, +#endif }; /* These are API file systems that might be mounted by other software, diff --git a/src/core/transaction.c b/src/core/transaction.c index 0329366350..4a8d90e6e5 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -396,8 +396,8 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi "Job %s/%s deleted to break ordering cycle starting with %s/%s", delete->unit->id, job_type_to_string(delete->type), j->unit->id, job_type_to_string(j->type)); - status_printf(ANSI_HIGHLIGHT_RED_ON " SKIP " ANSI_HIGHLIGHT_OFF, true, - "Ordering cycle found, skipping %s", unit_description(delete->unit)); + unit_status_printf(delete->unit, ANSI_HIGHLIGHT_RED_ON " SKIP " ANSI_HIGHLIGHT_OFF, + "Ordering cycle found, skipping %s"); transaction_delete_unit(tr, delete->unit); return -EAGAIN; } diff --git a/src/core/unit.c b/src/core/unit.c index 3a88996eb7..e2c06ae8b6 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) @@ -996,7 +994,7 @@ static void unit_status_print_starting_stopping(Unit *u, JobType t) { if (!format) return; - unit_status_printf(u, "", format, unit_description(u)); + unit_status_printf(u, "", format); } #pragma GCC diagnostic push @@ -1322,6 +1320,7 @@ void unit_trigger_on_failure(Unit *u) { } void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { + Manager *m; bool unexpected; assert(u); @@ -1334,7 +1333,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su * behavior here. For example: if a mount point is remounted * this function will be called too! */ - if (u->manager->n_reloading <= 0) { + m = u->manager; + + if (m->n_reloading <= 0) { dual_timestamp ts; dual_timestamp_get(&ts); @@ -1356,6 +1357,21 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su if (UNIT_IS_INACTIVE_OR_FAILED(ns)) cgroup_bonding_trim_list(u->cgroup_bondings, true); + if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { + ExecContext *ec = unit_get_exec_context(u); + if (ec && exec_context_may_touch_console(ec)) { + /* XXX The counter may get out of sync if the admin edits + * TTY-related unit file properties and issues a daemon-reload + * while the unit is active. No big deal though, because + * it influences only the printing of boot/shutdown + * status messages. */ + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + m->n_on_console--; + else + m->n_on_console++; + } + } + if (u->job) { unexpected = false; @@ -1422,7 +1438,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su } else unexpected = true; - if (u->manager->n_reloading <= 0) { + if (m->n_reloading <= 0) { /* If this state change happened without being * requested by a job, then let's retroactively start @@ -1458,18 +1474,18 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su /* The bus just might have become available, * hence try to connect to it, if we aren't * yet connected. */ - bus_init(u->manager, true); + bus_init(m, true); if (u->type == UNIT_SERVICE && !UNIT_IS_ACTIVE_OR_RELOADING(os) && - u->manager->n_reloading <= 0) { + m->n_reloading <= 0) { /* Write audit record if we have just finished starting up */ - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true); + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); u->in_audit = true; } if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) - manager_send_unit_plymouth(u->manager, u); + manager_send_unit_plymouth(m, u); } else { @@ -1479,25 +1495,25 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su if (u->type == UNIT_SERVICE && UNIT_IS_INACTIVE_OR_FAILED(ns) && !UNIT_IS_INACTIVE_OR_FAILED(os) && - u->manager->n_reloading <= 0) { + m->n_reloading <= 0) { /* Hmm, if there was no start record written * write it now, so that we always have a nice * pair */ if (!u->in_audit) { - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); if (ns == UNIT_INACTIVE) - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true); + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); } else /* Write audit record if we have just finished shutting down */ - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); u->in_audit = false; } } - manager_recheck_journal(u->manager); + manager_recheck_journal(m); /* Maybe we finished startup and are now ready for being * stopped because unneeded? */ @@ -1900,30 +1916,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 +2107,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 +2151,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 +2162,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 */ - if (ret) - *ret = a; + v = strdup(value); + if (!v) + return -ENOMEM; + + free(a->value); + a->value = v; - return 1; + if (ret) + *ret = a; + return 1; + } } a = new0(CGroupAttribute, 1); @@ -2226,11 +2243,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); @@ -2537,21 +2553,8 @@ int unit_coldplug(Unit *u) { return 0; } -void unit_status_printf(Unit *u, const char *status, const char *format, ...) { - va_list ap; - - assert(u); - assert(format); - - if (!manager_get_show_status(u->manager)) - return; - - if (!manager_is_booting_or_shutting_down(u->manager)) - return; - - va_start(ap, format); - status_vprintf(status, true, format, ap); - va_end(ap); +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { + manager_status_printf(u->manager, false, status, unit_status_msg_format, unit_description(u)); } bool unit_need_daemon_reload(Unit *u) { @@ -2763,52 +2766,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..9029d6225b 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); @@ -519,7 +520,7 @@ int unit_add_node_link(Unit *u, const char *what, bool wants); int unit_coldplug(Unit *u); -void unit_status_printf(Unit *u, const char *status, const char *format, ...); +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format); bool unit_need_daemon_reload(Unit *u); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 0de159b89a..9084509704 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -433,7 +433,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_follow && !arg_no_tail && arg_lines < 0) arg_lines = 10; - if (arg_since_set && arg_until_set && arg_since_set > arg_until_set) { + if (arg_since_set && arg_until_set && arg_since > arg_until) { log_error("--since= must be before --until=."); return -EINVAL; } @@ -1079,6 +1079,8 @@ int main(int argc, char *argv[]) { log_error("Failed to determine timestamp: %s", strerror(-r)); goto finish; } + if (usec > arg_until) + goto finish; } if (!arg_merge) { diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 8904560094..19123544b3 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -1321,10 +1321,12 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) assert(prefix); assert(dirname); + log_debug("Considering %s/%s.", prefix, dirname); + if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && (sd_id128_from_string(dirname, &id) < 0 || sd_id128_get_machine(&mid) < 0 || - !sd_id128_equal(id, mid))) + !(sd_id128_equal(id, mid) || path_startswith(prefix, "/run")))) return 0; path = strjoin(prefix, "/", dirname, NULL); diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install new file mode 100644 index 0000000000..16c06e0842 --- /dev/null +++ b/src/kernel-install/kernel-install @@ -0,0 +1,152 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +# +# This file is part of systemd. +# +# Copyright 2013 Harald Hoyer +# +# 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 +# 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/>. + +export LC_COLLATE=C + +COMMAND="$1" +KERNEL_VERSION="$2" +KERNEL_IMAGE="$3" + +[[ -f /etc/os-release ]] && . /etc/os-release +if ! [[ $ID ]]; then + echo "Can't determine the name of your distribution. Please create /etc/os-release." >&2 + echo "See man:os-release(5)" >&2 + exit 1 +fi + +[[ -f /etc/machine-id ]] && read MACHINE_ID < /etc/machine-id +if ! [[ $MACHINE_ID ]]; then + echo "Can't determine your machine id. Please create /etc/machine-id!" >&2 + echo "See man:machine-id(5)" >&2 + exit 1 +fi + +if [[ -f /etc/kernel/cmdline ]]; then + readarray -t BOOT_OPTIONS < /etc/kernel/cmdline +fi + +if ! [[ "${BOOT_OPTIONS[@]}" ]]; then + readarray -t BOOT_OPTIONS < /proc/cmdline +fi + +if ! [[ $BOOT_OPTIONS ]]; then + echo "Can't determine the kernel command line parameters." >&2 + echo "Please specify the kernel command line in /etc/kernel/cmdline!" >&2 + exit 1 +fi + +usage() +{ + { + echo "Usage:" + echo " $0 add <kernel-version> <kernel-image>" + echo " $0 remove <kernel-version> <kernel-image>" + } >&2 +} + +if ! ( [[ $COMMAND ]] && [[ $KERNEL_VERSION ]] && [[ $KERNEL_IMAGE ]] ); then + usage + exit 1 +fi + +BOOT_DIR="/${MACHINE_ID}/${KERNEL_VERSION}" +BOOT_DIR_ABS="/boot${BOOT_DIR}" +LOADER_ENTRY="/boot/loader/entries/${MACHINE_ID}-${KERNEL_VERSION}.conf" +ret=0 + +dropindirs_sort() +{ + suffix=$1; shift + readarray -t files< <( + for d in "$@"; do + for i in "${d}/"*${suffix}; do + [[ -e $i ]] && echo ${i##*/} + done + done | sort -Vu + ) + + for f in "${files[@]}"; do + for d in "$@"; do + if [[ -e "$d/$f" ]]; then + echo "$d/$f" + continue 2 + fi + done + done +} + +readarray -t PLUGINS < <( + dropindirs_sort ".install" \ + "/etc/kernel/install.d" \ + "/usr/lib/kernel/install.d" +) + +case "$COMMAND" in + add) + mkdir -p "$BOOT_DIR_ABS" || exit 1 + + for f in "${PLUGINS[@]}"; do + [[ -x $f ]] && "$f" add "$KERNEL_VERSION" "$BOOT_DIR_ABS" + ((ret+=$?)) + done + + if ! cp --preserve "$KERNEL_IMAGE" "$BOOT_DIR_ABS"/linux; then + echo "Can't copy '$KERNEL_IMAGE to '$BOOT_DIR_ABS/linux'!" >&2 + fi + + [[ -d /boot/loader/entries ]] || mkdir -p /boot/loader/entries + + { + echo "title $PRETTY_NAME" + echo "version $KERNEL_VERSION" + echo "machine-id $MACHINE_ID" + echo "options ${BOOT_OPTIONS[@]}" + echo "linux $BOOT_DIR/linux" + [[ -f "${BOOT_DIR_ABS}"/initrd ]] && \ + echo "initrd $BOOT_DIR/initrd" + : + } > $LOADER_ENTRY + + ((ret+=$?)) + + if ! [[ -f $LOADER_ENTRY ]]; then + echo "Could not create '$LOADER_ENTRY'!" >&2 + fi + ;; + + remove) + for f in "${PLUGINS[@]}"; do + [[ -x $f ]] && "$f" remove "$KERNEL_VERSION" "$BOOT_DIR_ABS" + ((ret+=$?)) + done + + rm -fr "$BOOT_DIR_ABS" + rm -f "$LOADER_ENTRY" + ;; + + *) + usage + ret=1;; +esac + +((ret+=$?)) + +exit $ret diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b8962e9894..8b57421431 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -101,23 +101,30 @@ static uint64_t arg_retain = (1ULL << CAP_SYS_BOOT) | (1ULL << CAP_AUDIT_WRITE) | (1ULL << CAP_AUDIT_CONTROL); +static char **arg_bind = NULL; +static char **arg_bind_ro = NULL; static int help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" "Spawn a minimal namespace container for debugging, testing and building.\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -D --directory=NAME Root directory for the container\n" - " -b --boot Boot up full system (i.e. invoke init)\n" - " -u --user=USER Run the command under specified user or uid\n" - " -C --controllers=LIST Put the container in specified comma-separated cgroup hierarchies\n" - " --uuid=UUID Set a specific machine UUID for the container\n" - " --private-network Disable network in container\n" - " --read-only Mount the root directory read-only\n" - " --capability=CAP In addition to the default, retain specified capability\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, host\n" - " -j Equivalent to --link-journal=host\n", + " -h --help Show this help\n" + " --version Print version string\n" + " -D --directory=NAME Root directory for the container\n" + " -b --boot Boot up full system (i.e. invoke init)\n" + " -u --user=USER Run the command under specified user or uid\n" + " -C --controllers=LIST Put the container in specified comma-separated\n" + " cgroup hierarchies\n" + " --uuid=UUID Set a specific machine UUID for the container\n" + " --private-network Disable network in container\n" + " --read-only Mount the root directory read-only\n" + " --capability=CAP In addition to the default, retain specified\n" + " capability\n" + " --link-journal=MODE Link up guest journal, one of no, auto, guest, host\n" + " -j Equivalent to --link-journal=host\n" + " --bind=PATH[:PATH] Bind mount a file or directory from the host into\n" + " the container\n" + " --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n", program_invocation_short_name); return 0; @@ -131,7 +138,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_UUID, ARG_READ_ONLY, ARG_CAPABILITY, - ARG_LINK_JOURNAL + ARG_LINK_JOURNAL, + ARG_BIND, + ARG_BIND_RO }; static const struct option options[] = { @@ -146,6 +155,8 @@ static int parse_argv(int argc, char *argv[]) { { "read-only", no_argument, NULL, ARG_READ_ONLY }, { "capability", required_argument, NULL, ARG_CAPABILITY }, { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, + { "bind", required_argument, NULL, ARG_BIND }, + { "bind-ro", required_argument, NULL, ARG_BIND_RO }, { NULL, 0, NULL, 0 } }; @@ -258,6 +269,43 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_BIND: + case ARG_BIND_RO: { + _cleanup_free_ char *a = NULL, *b = NULL; + char *e; + char ***x; + int r; + + x = c == ARG_BIND ? &arg_bind : &arg_bind_ro; + + e = strchr(optarg, ':'); + if (e) { + a = strndup(optarg, e - optarg); + b = strdup(e + 1); + } else { + a = strdup(optarg); + b = strdup(optarg); + } + + if (!a || !b) + return log_oom(); + + if (!path_is_absolute(a) || !path_is_absolute(b)) { + log_error("Invalid bind mount specification: %s", optarg); + return -EINVAL; + } + + r = strv_extend(x, a); + if (r < 0) + return r; + + r = strv_extend(x, b); + if (r < 0) + return r; + + break; + } + case '?': return -EINVAL; @@ -303,14 +351,9 @@ static int mount_all(const char *dest) { char _cleanup_free_ *where = NULL; int t; - if (asprintf(&where, "%s/%s", dest, mount_table[k].where) < 0) { - log_oom(); - - if (r == 0) - r = -ENOMEM; - - break; - } + where = strjoin(dest, "/", mount_table[k].where, NULL); + if (!where) + return log_oom(); t = path_is_mount_point(where, true); if (t < 0) { @@ -326,7 +369,7 @@ static int mount_all(const char *dest) { if (mount_table[k].what && t > 0) continue; - mkdir_p_label(where, 0755); + mkdir_p(where, 0755); if (mount(mount_table[k].what, where, @@ -345,6 +388,32 @@ static int mount_all(const char *dest) { return r; } +static int mount_binds(const char *dest, char **l, unsigned long flags) { + char **x, **y; + + STRV_FOREACH_PAIR(x, y, l) { + _cleanup_free_ char *where = NULL; + + where = strjoin(dest, "/", *y, NULL); + if (!where) + return log_oom(); + + mkdir_p_label(where, 0755); + + if (mount(*x, where, "bind", MS_BIND, NULL) < 0) { + log_error("mount(%s) failed: %m", where); + return -errno; + } + + if (flags && mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL) < 0) { + log_error("mount(%s) failed: %m", where); + return -errno; + } + } + + return 0; +} + static int setup_timezone(const char *dest) { _cleanup_free_ char *where = NULL, *p = NULL, *q = NULL, *check = NULL, *what = NULL; char *z, *y; @@ -1227,7 +1296,7 @@ int main(int argc, char *argv[]) { const char *home = NULL; uid_t uid = (uid_t) -1; gid_t gid = (gid_t) -1; - unsigned n_env = 0; + unsigned n_env = 2; const char *envp[] = { "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */ @@ -1241,8 +1310,9 @@ int main(int argc, char *argv[]) { NULL }; - envp[2] = strv_find_prefix(environ, "TERM="); - n_env = 3; + envp[n_env] = strv_find_prefix(environ, "TERM="); + if (envp[n_env]) + n_env ++; close_nointr_nofail(pipefd[1]); fd_wait_for_event(pipefd[0], POLLHUP, -1); @@ -1346,6 +1416,12 @@ int main(int argc, char *argv[]) { if (setup_journal(arg_directory) < 0) goto child_fail; + if (mount_binds(arg_directory, arg_bind, 0) < 0) + goto child_fail; + + if (mount_binds(arg_directory, arg_bind_ro, MS_RDONLY) < 0) + goto child_fail; + if (chdir(arg_directory) < 0) { log_error("chdir(%s) failed: %m", arg_directory); goto child_fail; diff --git a/src/shared/log.c b/src/shared/log.c index ff2dd45350..293c261f9e 100644 --- a/src/shared/log.c +++ b/src/shared/log.c @@ -541,11 +541,11 @@ static int log_dispatch( k = write_to_journal(level, file, line, func, object_name, object, buffer); - if (k <= 0) { - if (k < 0 && k != -EAGAIN) + if (k < 0) { + if (k != -EAGAIN) log_close_journal(); log_open_kmsg(); - } else + } else if (k > 0) r++; } @@ -554,11 +554,11 @@ static int log_dispatch( k = write_to_syslog(level, file, line, func, object_name, object, buffer); - if (k <= 0) { - if (k < 0 && k != -EAGAIN) + if (k < 0) { + if (k != -EAGAIN) log_close_syslog(); log_open_kmsg(); - } else + } else if (k > 0) r++; } @@ -571,11 +571,10 @@ static int log_dispatch( k = write_to_kmsg(level, file, line, func, object_name, object, buffer); - if (k <= 0) { - if (k < 0 && k != -EAGAIN) - log_close_kmsg(); + if (k < 0) { + log_close_kmsg(); log_open_console(); - } else + } else if (k > 0) r++; } 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/socket-util.c b/src/shared/socket-util.c index 6c94d69486..f6ddea3183 100644 --- a/src/shared/socket-util.c +++ b/src/shared/socket-util.c @@ -565,6 +565,45 @@ bool socket_address_matches_fd(const SocketAddress *a, int fd) { return false; } +int make_socket_fd(const char* address, int flags) { + SocketAddress a; + int fd, r; + char _cleanup_free_ *p = NULL; + + r = socket_address_parse(&a, address); + if (r < 0) { + log_error("failed to parse socket: %s", strerror(-r)); + return r; + } + + fd = socket(socket_address_family(&a), flags, 0); + if (fd < 0) { + log_error("socket(): %m"); + return -errno; + } + + r = socket_address_print(&a, &p); + if (r < 0) { + log_error("socket_address_print(): %s", strerror(-r)); + return r; + } + log_info("Listening on %s", p); + + r = bind(fd, &a.sockaddr.sa, a.size); + if (r < 0) { + log_error("bind to %s: %m", address); + return -errno; + } + + r = listen(fd, SOMAXCONN); + if (r < 0) { + log_error("listen on %s: %m", address); + return -errno; + } + + return fd; +} + static const char* const netlink_family_table[] = { [NETLINK_ROUTE] = "route", [NETLINK_FIREWALL] = "firewall", diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h index 771765d323..33838345ed 100644 --- a/src/shared/socket-util.h +++ b/src/shared/socket-util.h @@ -88,6 +88,8 @@ bool socket_address_is_netlink(const SocketAddress *a, const char *s); bool socket_address_matches_fd(const SocketAddress *a, int fd); +int make_socket_fd(const char* address, int flags); + bool socket_address_equal(const SocketAddress *a, const SocketAddress *b); bool socket_address_needs_mount(const SocketAddress *a, const char *prefix); 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/shared/util.c b/src/shared/util.c index f5adedc531..e643cd367c 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -2859,12 +2859,13 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) { } } -int status_vprintf(const char *status, bool ellipse, const char *format, va_list ap) { +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { static const char status_indent[] = " "; /* "[" STATUS "] " */ _cleanup_free_ char *s = NULL; _cleanup_close_ int fd = -1; - struct iovec iovec[5]; + struct iovec iovec[6]; int n = 0; + static bool prev_ephemeral; assert(format); @@ -2902,6 +2903,10 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list zero(iovec); + if (prev_ephemeral) + IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); + prev_ephemeral = ephemeral; + if (status) { if (!isempty(status)) { IOVEC_SET_STRING(iovec[n++], "["); @@ -2912,7 +2917,8 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list } IOVEC_SET_STRING(iovec[n++], s); - IOVEC_SET_STRING(iovec[n++], "\n"); + if (!ephemeral) + IOVEC_SET_STRING(iovec[n++], "\n"); if (writev(fd, iovec, n) < 0) return -errno; @@ -2920,14 +2926,14 @@ int status_vprintf(const char *status, bool ellipse, const char *format, va_list return 0; } -int status_printf(const char *status, bool ellipse, const char *format, ...) { +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { va_list ap; int r; assert(format); va_start(ap, format); - r = status_vprintf(status, ellipse, format, ap); + r = status_vprintf(status, ellipse, ephemeral, format, ap); va_end(ap); return r; @@ -3503,6 +3509,29 @@ int vtnr_from_tty(const char *tty) { return i; } +char *resolve_dev_console(char **active) { + char *tty; + + /* Resolve where /dev/console is pointing to, if /sys is actually ours + * (i.e. not read-only-mounted which is a sign for container setups) */ + + if (path_is_read_only_fs("/sys") > 0) + return NULL; + + if (read_one_line_file("/sys/class/tty/console/active", active) < 0) + return NULL; + + /* If multiple log outputs are configured the last one is what + * /dev/console points to */ + tty = strrchr(*active, ' '); + if (tty) + tty++; + else + tty = *active; + + return tty; +} + bool tty_is_vc_resolve(const char *tty) { char *active = NULL; bool b; @@ -3512,19 +3541,11 @@ bool tty_is_vc_resolve(const char *tty) { if (startswith(tty, "/dev/")) tty += 5; - /* Resolve where /dev/console is pointing to, if /sys is - * actually ours (i.e. not read-only-mounted which is a sign - * for container setups) */ - if (streq(tty, "console") && path_is_read_only_fs("/sys") <= 0) - if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { - /* If multiple log outputs are configured the - * last one is what /dev/console points to */ - tty = strrchr(active, ' '); - if (tty) - tty++; - else - tty = active; - } + if (streq(tty, "console")) { + tty = resolve_dev_console(&active); + if (!tty) + return false; + } b = tty_is_vc(tty); free(active); diff --git a/src/shared/util.h b/src/shared/util.h index 19cc36af84..b5ad1ff3b5 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -55,10 +55,12 @@ union dirent_storage { #define FORMAT_BYTES_MAX 8 #define ANSI_HIGHLIGHT_ON "\x1B[1;39m" +#define ANSI_RED_ON "\x1B[31m" #define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m" #define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m" #define ANSI_HIGHLIGHT_YELLOW_ON "\x1B[1;33m" #define ANSI_HIGHLIGHT_OFF "\x1B[0m" +#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" size_t page_size(void); #define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) @@ -354,8 +356,8 @@ int pipe_eof(int fd); cpu_set_t* cpu_set_malloc(unsigned *ncpus); -int status_vprintf(const char *status, bool ellipse, const char *format, va_list ap); -int status_printf(const char *status, bool ellipse, const char *format, ...); +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap); +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...); int status_welcome(void); int fd_columns(int fd); @@ -388,6 +390,7 @@ DIR *xopendirat(int dirfd, const char *name, int flags); char *fstab_node_to_udev_node(const char *p); +char *resolve_dev_console(char **active); bool tty_is_vc(const char *tty); bool tty_is_vc_resolve(const char *tty); bool tty_is_console(const char *tty); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 8174376257..c67c6c972a 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1045,7 +1045,7 @@ static bool need_daemon_reload(DBusConnection *bus, const char *unit) { if (!n) return log_oom(); - r = bus_method_call_with_reply ( + r = bus_method_call_with_reply( bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -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(); + STRV_FOREACH(argument, args + 2) { - runtime = arg_runtime ? "runtime" : "persistent"; - 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); - 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(); + STRV_FOREACH(argument, args + 2) { + _cleanup_strv_free_ char **list = NULL; + DBusMessageIter iter; + char **a; - 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(); - - 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; @@ -3985,16 +3956,15 @@ static int enable_unit(DBusConnection *bus, char **args) { } if (carries_install_info == 0) - log_warning( -"The unit files have no [Install] section. They are not meant to be enabled\n" -"using systemctl.\n" -"Possible reasons for having this kind of units are:\n" -"1) A unit may be statically enabled by being symlinked from another unit's\n" -" .wants/ or .requires/ directory.\n" -"2) A unit's purpose may be to act as a helper for some other unit which has\n" -" a requirement dependency on it.\n" -"3) A unit may be started when needed via activation (socket, path, timer,\n" -" D-Bus, udev, scripted systemctl call, ...).\n"); + log_warning("The unit files have no [Install] section. They are not meant to be enabled\n" + "using systemctl.\n" + "Possible reasons for having this kind of units are:\n" + "1) A unit may be statically enabled by being symlinked from another unit's\n" + " .wants/ or .requires/ directory.\n" + "2) A unit's purpose may be to act as a helper for some other unit which has\n" + " a requirement dependency on it.\n" + "3) A unit may be started when needed via activation (socket, path, timer,\n" + " D-Bus, udev, scripted systemctl call, ...).\n"); finish: unit_file_changes_free(changes, n_changes); @@ -5124,11 +5094,11 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "condreload", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ { "condrestart", MORE, 2, start_unit }, /* For compatibility with RH */ { "isolate", EQUAL, 2, start_unit }, - { "set-cgroup", MORE, 2, set_cgroup }, - { "unset-cgroup", MORE, 2, set_cgroup }, - { "get-cgroup-attr", MORE, 2, get_cgroup_attr }, - { "set-cgroup-attr", MORE, 2, set_cgroup_attr }, - { "unset-cgroup-attr", MORE, 2, set_cgroup }, + { "set-cgroup", MORE, 3, set_cgroup }, + { "unset-cgroup", MORE, 3, set_cgroup }, + { "get-cgroup-attr", MORE, 3, get_cgroup_attr }, + { "set-cgroup-attr", MORE, 4, set_cgroup_attr }, + { "unset-cgroup-attr", MORE, 3, set_cgroup }, { "kill", MORE, 2, kill_unit }, { "is-active", MORE, 2, check_unit_active }, { "check", MORE, 2, check_unit_active }, diff --git a/src/test/test-util.c b/src/test/test-util.c index e4dea09f85..cb0af4f6a1 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -129,17 +129,107 @@ static void test_safe_atod(void) { assert_se(r == -EINVAL); } +static void test_strstrip(void) { + char *r; + char input[] = " hello, waldo. "; + + r = strstrip(input); + assert_se(streq(r, "hello, waldo.")); + +} + +static void test_delete_chars(void) { + char *r; + char input[] = " hello, waldo. abc"; + + r = delete_chars(input, WHITESPACE); + assert_se(streq(r, "hello,waldo.abc")); +} + +static void test_in_charset(void) { + assert_se(in_charset("dddaaabbbcccc", "abcd")); + assert_se(!in_charset("dddaaabbbcccc", "abc f")); +} + +static void test_hexchar(void) { + assert_se(hexchar(0xa) == 'a'); + assert_se(hexchar(0x0) == '0'); +} + +static void test_unhexchar(void) { + assert_se(unhexchar('a') == 0xA); + assert_se(unhexchar('A') == 0xA); + assert_se(unhexchar('0') == 0x0); +} + +static void test_octchar(void) { + assert_se(octchar(00) == '0'); + assert_se(octchar(07) == '7'); +} + +static void test_unoctchar(void) { + assert_se(unoctchar('0') == 00); + assert_se(unoctchar('7') == 07); +} + +static void test_decchar(void) { + assert_se(decchar(0) == '0'); + assert_se(decchar(9) == '9'); +} + +static void test_undecchar(void) { + assert_se(undecchar('0') == 0); + assert_se(undecchar('9') == 9); +} + +static void test_foreach_word(void) { + char *w, *state; + size_t l; + int i = 0; + const char test[] = "test abc d\te f "; + const char * const expected[] = { + "test", + "abc", + "d", + "e", + "f", + "", + NULL + }; + + FOREACH_WORD(w, l, test, state) { + assert_se(strneq(expected[i++], w, l)); + } +} + static void test_foreach_word_quoted(void) { char *w, *state; size_t l; - const char test[] = "test a b c 'd' e '' '' hhh '' ''"; + int i = 0; + const char test[] = "test a b c 'd' e '' '' hhh '' '' \"a b c\""; + const char * const expected[] = { + "test", + "a", + "b", + "c", + "d", + "e", + "", + "", + "hhh", + "", + "", + "a b c", + NULL + }; + printf("<%s>\n", test); FOREACH_WORD_QUOTED(w, l, test, state) { - char *t; + _cleanup_free_ char *t = NULL; assert_se(t = strndup(w, l)); + assert_se(strneq(expected[i++], w, l)); printf("<%s>\n", t); - free(t); } } @@ -175,12 +265,22 @@ int main(int argc, char *argv[]) { test_streq_ptr(); test_first_word(); test_parse_boolean(); - test_default_term_for_tty(); test_parse_pid(); test_parse_uid(); test_safe_atolli(); test_safe_atod(); + test_strstrip(); + test_delete_chars(); + test_in_charset(); + test_hexchar(); + test_unhexchar(); + test_octchar(); + test_unoctchar(); + test_decchar(); + test_undecchar(); + test_foreach_word(); test_foreach_word_quoted(); + test_default_term_for_tty(); test_memdup_multiply(); return 0; |