diff options
Diffstat (limited to 'src')
353 files changed, 25345 insertions, 10645 deletions
diff --git a/src/.gitignore b/src/.gitignore index afabb6a5d2..e6ac2d7b8a 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -4,3 +4,5 @@ load-fragment-gperf.gperf org.freedesktop.systemd1.policy.in org.freedesktop.systemd1.policy 99-systemd.rules +*.gcno +*.gcda diff --git a/src/activate/activate.c b/src/activate/activate.c index 87526d47cc..83d25b13af 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -290,16 +290,15 @@ static int do_accept(const char* name, char **argv, char **envp, int fd) { } /* SIGCHLD handler. */ -static void sigchld_hdl(int sig, siginfo_t *t, void *data) -{ +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); + /* Wait for a dead child. */ + waitpid(t->si_pid, NULL, 0); } static int install_chld_handler(void) { int r; - struct sigaction act; + struct sigaction act; zero(act); act.sa_flags = SA_SIGINFO; act.sa_sigaction = sigchld_hdl; diff --git a/src/analyze/systemd-analyze.c b/src/analyze/systemd-analyze.c index bb86ec7da8..27d063c548 100644 --- a/src/analyze/systemd-analyze.c +++ b/src/analyze/systemd-analyze.c @@ -38,6 +38,7 @@ #include "unit-name.h" #include "special.h" #include "hashmap.h" +#include "pager.h" #define SCALE_X (0.1 / 1000.0) /* pixels per us */ #define SCALE_Y 20.0 @@ -67,8 +68,8 @@ static enum dot { } arg_dot = DEP_ALL; static char** arg_dot_from_patterns = NULL; static char** arg_dot_to_patterns = NULL; - -usec_t arg_fuzz = 0; +static usec_t arg_fuzz = 0; +static bool arg_no_pager = false; struct boot_times { usec_t firmware_time; @@ -78,7 +79,12 @@ struct boot_times { usec_t initrd_time; usec_t userspace_time; usec_t finish_time; + usec_t generators_start_time; + usec_t generators_finish_time; + usec_t unitsload_start_time; + usec_t unitsload_finish_time; }; + struct unit_times { char *name; usec_t ixt; @@ -88,6 +94,14 @@ struct unit_times { usec_t time; }; +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; DBusMessageIter iter, sub; @@ -303,7 +317,27 @@ static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) { "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "FinishTimestampMonotonic", - ×.finish_time) < 0) + ×.finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsStartTimestampMonotonic", + ×.generators_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsFinishTimestampMonotonic", + ×.generators_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadStartTimestampMonotonic", + ×.unitsload_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadFinishTimestampMonotonic", + ×.unitsload_finish_time) < 0) return -EIO; if (times.finish_time <= 0) { @@ -459,7 +493,8 @@ static int analyze_plot(DBusConnection *bus) { svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" " "xmlns=\"http://www.w3.org/2000/svg\">\n\n", - 80.0 + width, 150.0 + (m * SCALE_Y)); + 80.0 + width, 150.0 + (m * SCALE_Y) + + 5 * SCALE_Y /* legend */); /* write some basic info as a comment, including some help */ svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n" @@ -480,23 +515,23 @@ static int analyze_plot(DBusConnection *bus) { " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n" " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n" " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n" + " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n" + " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n" " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n" " line { stroke: rgb(64,64,64); stroke-width: 1; }\n" "// line.sec1 { }\n" " line.sec5 { stroke-width: 2; }\n" " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n" - " text { font-family: Verdana, Helvetica; font-size: 10; }\n" - " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n" - " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n" - " text.sec { font-size: 8; }\n" + " text { font-family: Verdana, Helvetica; font-size: 14px; }\n" + " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n" + " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n" + " text.sec { font-size: 10px; }\n" " ]]>\n </style>\n</defs>\n\n"); svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times); svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>", isempty(osname) ? "Linux" : osname, name.nodename, name.release, name.version, name.machine); - svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>", - 120.0 + (m *SCALE_Y)); svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time)); svg_graph_box(m, -boot->firmware_time, boot->finish_time); @@ -521,8 +556,10 @@ static int analyze_plot(DBusConnection *bus) { svg_text(true, boot->initrd_time, y, "initrd"); y++; } - svg_bar("userspace", boot->userspace_time, boot->finish_time, y); - svg_text("left", boot->userspace_time, y, "userspace"); + svg_bar("active", boot->userspace_time, boot->finish_time, y); + svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); + svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); + svg_text("left", boot->userspace_time, y, "systemd"); y++; for (u = times; u < times + n; u++) { @@ -544,6 +581,25 @@ static int analyze_plot(DBusConnection *bus) { svg_text(b, u->ixt, y, "%s", u->name); y++; } + + /* Legend */ + y++; + svg_bar("activating", 0, 300000, y); + svg_text("right", 400000, y, "Activating"); + y++; + svg_bar("active", 0, 300000, y); + svg_text("right", 400000, y, "Active"); + y++; + svg_bar("deactivating", 0, 300000, y); + svg_text("right", 400000, y, "Deactivating"); + y++; + svg_bar("generators", 0, 300000, y); + svg_text("right", 400000, y, "Generators"); + y++; + svg_bar("unitsload", 0, 300000, y); + svg_text("right", 400000, y, "Loading unit files"); + y++; + svg("</g>\n\n"); svg("</svg>"); @@ -553,7 +609,6 @@ static int analyze_plot(DBusConnection *bus) { return 0; } - static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, bool last, struct unit_times *times, struct boot_times *boot) { unsigned int i; @@ -779,7 +834,7 @@ static int list_dependencies_one(DBusConnection *bus, const char *name, unsigned return 0; } -static int list_dependencies(DBusConnection *bus) { +static int list_dependencies(DBusConnection *bus, const char *name) { _cleanup_strv_free_ char **units = NULL; char ts[FORMAT_TIMESPAN_MAX]; struct unit_times *times; @@ -794,7 +849,7 @@ static int list_dependencies(DBusConnection *bus) { assert(bus); - path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET); + path = unit_dbus_path_from_name(name); if (path == NULL) return -EINVAL; @@ -843,10 +898,10 @@ static int list_dependencies(DBusConnection *bus) { printf("%s\n", id); } - return list_dependencies_one(bus, SPECIAL_DEFAULT_TARGET, 0, &units, 0); + return list_dependencies_one(bus, name, 0, &units, 0); } -static int analyze_critical_chain(DBusConnection *bus) { +static int analyze_critical_chain(DBusConnection *bus, char *names[]) { struct unit_times *times; int n, r; unsigned int i; @@ -867,10 +922,17 @@ static int analyze_critical_chain(DBusConnection *bus) { } unit_times_hashmap = h; + pager_open_if_enabled(); + puts("The time after the unit is active or started is printed after the \"@\" character.\n" "The time the unit takes to start is printed after the \"+\" character.\n"); - list_dependencies(bus); + if (!strv_isempty(names)) { + char **name; + STRV_FOREACH(name, names) + list_dependencies(bus, *name); + } else + list_dependencies(bus, SPECIAL_DEFAULT_TARGET); hashmap_free(h); free_unit_times(times, (unsigned) n); @@ -888,6 +950,8 @@ static int analyze_blame(DBusConnection *bus) { qsort(times, n, sizeof(struct unit_times), compare_unit_time); + pager_open_if_enabled(); + for (i = 0; i < (unsigned) n; i++) { char ts[FORMAT_TIMESPAN_MAX]; @@ -1112,8 +1176,97 @@ static int dot(DBusConnection *bus, char* patterns[]) { return 0; } -static void analyze_help(void) -{ +static int dump(DBusConnection *bus, char **args) { + _cleanup_free_ DBusMessage *reply = NULL; + DBusError error; + int r; + const char *text; + + dbus_error_init(&error); + + if (!strv_isempty(args)) { + log_error("Too many arguments."); + return -E2BIG; + } + + pager_open_if_enabled(); + + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Dump", + &reply, + NULL, + DBUS_TYPE_INVALID); + if (r < 0) + return r; + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + dbus_error_free(&error); + return -EIO; + } + + fputs(text, stdout); + return 0; +} + +static int set_log_level(DBusConnection *bus, char **args) { + _cleanup_dbus_error_free_ DBusError error; + _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; + DBusMessageIter iter, sub; + const char* property = "LogLevel"; + const char* interface = "org.freedesktop.systemd1.Manager"; + const char* value; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + value = args[0]; + dbus_error_init(&error); + + m = dbus_message_new_method_call("org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.DBus.Properties", + "Set"); + if (!m) + return log_oom(); + + dbus_message_iter_init_append(m, &iter); + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) || + !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &property) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &sub)) + return log_oom(); + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &value)) + return log_oom(); + + if (!dbus_message_iter_close_container(&iter, &sub)) + 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)); + return -EIO; + } + + return 0; +} + +static void analyze_help(void) { + + pager_open_if_enabled(); + printf("%s [OPTIONS...] {COMMAND} ...\n\n" "Process systemd profiling information\n\n" " -h --help Show this help\n" @@ -1128,13 +1281,16 @@ static void analyze_help(void) " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n" " services, which finished TIMESPAN earlier, than the\n" " latest in the branch. The unit of TIMESPAN is seconds\n" - " unless specified with a different unit, i.e. 50ms\n\n" + " unless specified with a different unit, i.e. 50ms\n" + " --no-pager Do not pipe output into a pager\n\n" "Commands:\n" " time Print time spent in the kernel before reaching userspace\n" " blame Print list of running units ordered by time to init\n" " critical-chain Print a tree of the time critical chain of units\n" " plot Output SVG graphic showing service initialization\n" - " dot Dump dependency graph (in dot(1) format)\n\n", + " dot Output dependency graph in dot(1) format\n" + " set-log-level LEVEL Set logging threshold for systemd\n" + " dump Output state serialization of service manager\n", program_invocation_short_name); /* When updating this list, including descriptions, apply @@ -1142,8 +1298,7 @@ static void analyze_help(void) * shell-completion/systemd-zsh-completion.zsh too. */ } -static int parse_argv(int argc, char *argv[]) -{ +static int parse_argv(int argc, char *argv[]) { int r; enum { @@ -1154,20 +1309,22 @@ static int parse_argv(int argc, char *argv[]) ARG_SYSTEM, ARG_DOT_FROM_PATTERN, ARG_DOT_TO_PATTERN, - ARG_FUZZ + ARG_FUZZ, + ARG_NO_PAGER }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN}, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { NULL, 0, NULL, 0 } + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "order", no_argument, NULL, ARG_ORDER }, + { "require", no_argument, NULL, ARG_REQUIRE }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, + { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, + { "fuzz", required_argument, NULL, ARG_FUZZ }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { NULL, 0, NULL, 0 } }; assert(argc >= 0); @@ -1218,6 +1375,10 @@ static int parse_argv(int argc, char *argv[]) return r; break; + case ARG_NO_PAGER: + arg_no_pager = true; + break; + case -1: return 1; @@ -1240,31 +1401,39 @@ int main(int argc, char *argv[]) { log_open(); r = parse_argv(argc, argv); - if (r < 0) - return EXIT_FAILURE; - else if (r <= 0) - return EXIT_SUCCESS; + if (r <= 0) + goto finish; bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL); - if (!bus) - return EXIT_FAILURE; + if (!bus) { + r = -EIO; + goto finish; + } if (!argv[optind] || streq(argv[optind], "time")) r = analyze_time(bus); else if (streq(argv[optind], "blame")) r = analyze_blame(bus); else if (streq(argv[optind], "critical-chain")) - r = analyze_critical_chain(bus); + r = analyze_critical_chain(bus, argv+optind+1); else if (streq(argv[optind], "plot")) r = analyze_plot(bus); else if (streq(argv[optind], "dot")) r = dot(bus, argv+optind+1); + else if (streq(argv[optind], "dump")) + r = dump(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-level")) + r = set_log_level(bus, argv+optind+1); else log_error("Unknown operation '%s'.", argv[optind]); + dbus_connection_unref(bus); + +finish: + pager_close(); + strv_free(arg_dot_from_patterns); strv_free(arg_dot_to_patterns); - dbus_connection_unref(bus); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/timestamp/Makefile b/src/backlight/Makefile index d0b0e8e008..d0b0e8e008 120000 --- a/src/timestamp/Makefile +++ b/src/backlight/Makefile diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c new file mode 100644 index 0000000000..9b2eada397 --- /dev/null +++ b/src/backlight/backlight.c @@ -0,0 +1,132 @@ +/*-*- 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 <libudev.h> + +#include "util.h" +#include "mkdir.h" +#include "fileio.h" + +int main(int argc, char *argv[]) { + struct udev *udev = NULL; + struct udev_device *device = NULL; + _cleanup_free_ char *saved = NULL; + int r; + + if (argc != 3) { + log_error("This program requires two arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = mkdir_p("/var/lib/systemd/backlight", 0755); + if (r < 0) { + log_error("Failed to create backlight directory: %s", strerror(-r)); + goto finish; + } + + udev = udev_new(); + if (!udev) { + r = log_oom(); + goto finish; + } + + errno = 0; + device = udev_device_new_from_subsystem_sysname(udev, "backlight", argv[2]); + if (!device) { + if (errno != 0) { + log_error("Failed to get backlight device: %m"); + r = -errno; + } else + r = log_oom(); + + goto finish; + } + + if (!streq_ptr(udev_device_get_subsystem(device), "backlight")) { + log_error("Not a backlight device: %s", argv[2]); + r = -ENODEV; + goto finish; + } + + saved = strappend("/var/lib/systemd/backlight/", udev_device_get_sysname(device)); + if (!saved) { + r = log_oom(); + goto finish; + } + + if (streq(argv[1], "load")) { + _cleanup_free_ char *value = NULL; + + r = read_one_line_file(saved, &value); + if (r < 0) { + + if (r == -ENOENT) { + r = 0; + goto finish; + } + + log_error("Failed to read %s: %s", saved, strerror(-r)); + goto finish; + } + + r = udev_device_set_sysattr_value(device, "brightness", value); + if (r < 0) { + log_error("Failed to write system attribute: %s", strerror(-r)); + goto finish; + } + + } else if (streq(argv[1], "save")) { + const char *value; + + value = udev_device_get_sysattr_value(device, "brightness"); + if (!value) { + log_error("Failed to read system attribute: %s", strerror(-r)); + goto finish; + } + + r = write_string_file(saved, value); + if (r < 0) { + log_error("Failed to write %s: %s", saved, strerror(-r)); + goto finish; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + r = -EINVAL; + goto finish; + } + +finish: + if (device) + udev_device_unref(device); + + if (udev) + udev_unref(udev); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + +} diff --git a/src/boot/boot-efi.c b/src/boot/boot-efi.c index 9960c4d742..33840b6864 100644 --- a/src/boot/boot-efi.c +++ b/src/boot/boot-efi.c @@ -174,7 +174,7 @@ int boot_info_query(struct boot_info *info) { efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo", &info->fw_info); efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier", &info->loader_image_path); tilt_slashes(info->loader_image_path); - efi_get_loader_device_part_uuid(&info->loader_part_uuid); + efi_loader_get_device_part_uuid(&info->loader_part_uuid); boot_loader_read_entries(info); efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntrySelected", &loader_active); diff --git a/src/bootchart/bootchart.c b/src/bootchart/bootchart.c index 8be5a27afa..14ccd3efe5 100644 --- a/src/bootchart/bootchart.c +++ b/src/bootchart/bootchart.c @@ -3,7 +3,7 @@ /*** This file is part of systemd. - Copyright (C) 2009-2013 Intel Coproration + Copyright (C) 2009-2013 Intel Corporation Authors: Auke Kok <auke-jan.h.kok@intel.com> diff --git a/src/bootchart/bootchart.h b/src/bootchart/bootchart.h index d0273421de..968c38da26 100644 --- a/src/bootchart/bootchart.h +++ b/src/bootchart/bootchart.h @@ -5,7 +5,7 @@ /*** This file is part of systemd. - Copyright (C) 2009-2013 Intel Coproration + Copyright (C) 2009-2013 Intel Corporation Authors: Auke Kok <auke-jan.h.kok@intel.com> diff --git a/src/bootchart/store.c b/src/bootchart/store.c index b2afb8d13b..f8c97c2324 100755..100644 --- a/src/bootchart/store.c +++ b/src/bootchart/store.c @@ -3,7 +3,7 @@ /*** This file is part of systemd. - Copyright (C) 2009-2013 Intel Coproration + Copyright (C) 2009-2013 Intel Corporation Authors: Auke Kok <auke-jan.h.kok@intel.com> diff --git a/src/bootchart/store.h b/src/bootchart/store.h index 7c8ad284da..f211b6f53b 100644 --- a/src/bootchart/store.h +++ b/src/bootchart/store.h @@ -5,7 +5,7 @@ /*** This file is part of systemd. - Copyright (C) 2009-2013 Intel Coproration + Copyright (C) 2009-2013 Intel Corporation Authors: Auke Kok <auke-jan.h.kok@intel.com> diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c index 859cf81c22..5eee2d1987 100644 --- a/src/bootchart/svg.c +++ b/src/bootchart/svg.c @@ -3,7 +3,7 @@ /*** This file is part of systemd. - Copyright (C) 2009-2013 Intel Coproration + Copyright (C) 2009-2013 Intel Corporation Authors: Auke Kok <auke-jan.h.kok@intel.com> diff --git a/src/bootchart/svg.h b/src/bootchart/svg.h index e7369f5111..df3a7bf8ef 100644 --- a/src/bootchart/svg.h +++ b/src/bootchart/svg.h @@ -5,7 +5,7 @@ /*** This file is part of systemd. - Copyright (C) 2009-2013 Intel Coproration + Copyright (C) 2009-2013 Intel Corporation Authors: Auke Kok <auke-jan.h.kok@intel.com> diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index ef3e5672ab..c689b5c471 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -34,6 +34,7 @@ #include "pager.h" #include "build.h" #include "output-mode.h" +#include "fileio.h" static bool arg_no_pager = false; static bool arg_kernel_threads = false; @@ -49,7 +50,7 @@ static void help(void) { " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " -a --all Show all groups, including empty\n" - " --full Do not ellipsize output\n" + " -l --full Do not ellipsize output\n" " -k Include kernel threads in output\n" " -M --machine Show container\n", program_invocation_short_name); @@ -60,7 +61,6 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_NO_PAGER = 0x100, ARG_VERSION, - ARG_FULL, }; static const struct option options[] = { @@ -68,7 +68,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, ARG_FULL }, + { "full", no_argument, NULL, 'l' }, { "machine", required_argument, NULL, 'M' }, { NULL, 0, NULL, 0 } }; @@ -78,7 +78,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 1); assert(argv); - while ((c = getopt_long(argc, argv, "hkaM:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hkalM:", options, NULL)) >= 0) { switch (c) { @@ -99,7 +99,7 @@ static int parse_argv(int argc, char *argv[]) { arg_all = true; break; - case ARG_FULL: + case 'l': arg_full = true; break; @@ -156,7 +156,9 @@ int main(int argc, char *argv[]) { for (i = optind; i < argc; i++) { int q; - printf("%s:\n", argv[i]); + + fprintf(stdout, "%s:\n", argv[i]); + fflush(stdout); if (arg_machine) root = strjoin("machine/", arg_machine, "/", argv[i], NULL); @@ -185,9 +187,11 @@ int main(int argc, char *argv[]) { r = show_cgroup_by_path(p, NULL, 0, arg_kernel_threads, output_flags); } else { - if (arg_machine) - r = cg_get_machine_path(arg_machine, &root); - else + if (arg_machine) { + char *m; + m = strappenda("/run/systemd/machines/", arg_machine); + r = parse_env_file(m, NEWLINE, "CGROUP", &root, NULL); + } else r = cg_get_root_path(&root); if (r < 0) { log_error("Failed to get %s path: %s", diff --git a/src/cgroups-agent/cgroups-agent.c b/src/cgroups-agent/cgroups-agent.c index 7a6173e2a2..a47949a180 100644 --- a/src/cgroups-agent/cgroups-agent.c +++ b/src/cgroups-agent/cgroups-agent.c @@ -48,26 +48,19 @@ int main(int argc, char *argv[]) { * this to avoid an activation loop when we start dbus when we * are called when the dbus service is shut down. */ - if (!(bus = dbus_connection_open_private("unix:path=/run/systemd/private", &error))) { -#ifndef LEGACY - dbus_error_free(&error); - - /* Retry with the pre v21 socket name, to ease upgrades */ - if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", &error))) { -#endif - log_error("Failed to get D-Bus connection: %s", bus_error_message(&error)); - goto finish; - } -#ifndef LEGACY + bus = dbus_connection_open_private("unix:path=/run/systemd/private", &error); + if (!bus) { + log_warning("Failed to get D-Bus connection: %s", bus_error_message(&error)); + goto finish; } -#endif if (bus_check_peercred(bus) < 0) { log_error("Bus owner not root."); goto finish; } - if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1/agent", "org.freedesktop.systemd1.Agent", "Released"))) { + m = dbus_message_new_signal("/org/freedesktop/systemd1/agent", "org.freedesktop.systemd1.Agent", "Released"); + if (!m) { log_error("Could not allocate signal message."); goto finish; } diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 1e21b0074d..cacf705a0a 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -134,7 +134,7 @@ static int process(const char *controller, const char *path, Hashmap *a, Hashmap /* Regardless which controller, let's find the maximum number * of processes in any of it */ - r = cg_enumerate_tasks(controller, path, &f); + r = cg_enumerate_processes(controller, path, &f); if (r < 0) return r; @@ -824,9 +824,9 @@ int main(int argc, char *argv[]) { case '?': case 'h': fprintf(stdout, - "\t<" ON "P" OFF "> By path; <" ON "T" OFF "> By tasks; <" ON "C" OFF "> By CPU; <" ON "M" OFF "> By memory; <" ON "I" OFF "> By I/O\n" + "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n" "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n" - "\t<" ON "Q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh"); + "\t<" ON "q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh"); fflush(stdout); sleep(3); break; diff --git a/src/core/sync.c b/src/core/async.c index 7e74b63071..af527bea4e 100644 --- a/src/core/sync.c +++ b/src/core/async.c @@ -22,14 +22,10 @@ #include <pthread.h> #include <unistd.h> -#include "sync.h" +#include "async.h" +#include "log.h" -static void *sync_thread(void *p) { - sync(); - return NULL; -} - -int asynchronous_sync(void) { +int asynchronous_job(void* (*func)(void *p), void *arg) { pthread_attr_t a; pthread_t t; int r; @@ -53,7 +49,7 @@ int asynchronous_sync(void) { goto finish; } - r = pthread_create(&t, &a, sync_thread, NULL); + r = pthread_create(&t, &a, func, arg); if (r != 0) { r = -r; goto finish; @@ -63,3 +59,14 @@ finish: pthread_attr_destroy(&a); return r; } + +static void *sync_thread(void *p) { + sync(); + return NULL; +} + +int asynchronous_sync(void) { + log_debug("Spawning new thread for sync"); + + return asynchronous_job(sync_thread, NULL); +} diff --git a/src/core/sync.h b/src/core/async.h index eb26c88deb..6601b4dc4b 100644 --- a/src/core/sync.h +++ b/src/core/async.h @@ -21,4 +21,5 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +int asynchronous_job(void* (*func)(void *p), void *arg); int asynchronous_sync(void); diff --git a/src/core/automount.c b/src/core/automount.c index a20d5340f2..d1379e0913 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -66,7 +66,7 @@ static void automount_init(Unit *u) { UNIT(a)->ignore_on_isolate = true; } -static void repeat_unmout(const char *path) { +static void repeat_unmount(const char *path) { assert(path); for (;;) { @@ -100,7 +100,7 @@ static void unmount_autofs(Automount *a) { if (a->where && (UNIT(a)->manager->exit_code != MANAGER_RELOAD && UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) - repeat_unmout(a->where); + repeat_unmount(a->where); } static void automount_done(Unit *u) { @@ -117,42 +117,17 @@ static void automount_done(Unit *u) { a->tokens = NULL; } -int automount_add_one_mount_link(Automount *a, Mount *m) { +static int automount_add_mount_links(Automount *a) { + _cleanup_free_ char *parent = NULL; int r; assert(a); - assert(m); - - if (UNIT(a)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!path_startswith(a->where, m->where)) - return 0; - if (path_equal(a->where, m->where)) - return 0; - - r = unit_add_two_dependencies(UNIT(a), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); + r = path_get_parent(a->where, &parent); if (r < 0) return r; - return 0; -} - -static int automount_add_mount_links(Automount *a) { - Unit *other; - int r; - - assert(a); - - LIST_FOREACH(units_by_type, other, UNIT(a)->manager->units_by_type[UNIT_MOUNT]) { - r = automount_add_one_mount_link(a, MOUNT(other)); - if (r < 0) - return r; - } - - return 0; + return unit_require_mounts_for(UNIT(a), parent); } static int automount_add_default_dependencies(Automount *a) { @@ -575,7 +550,7 @@ fail: close_nointr_nofail(ioctl_fd); if (mounted) - repeat_unmout(a->where); + repeat_unmount(a->where); log_error_unit(UNIT(a)->id, "Failed to initialize automounter: %s", strerror(-r)); diff --git a/src/core/automount.h b/src/core/automount.h index 0c6b8a72e9..a7a25d34e0 100644 --- a/src/core/automount.h +++ b/src/core/automount.h @@ -62,8 +62,6 @@ extern const UnitVTable automount_vtable; int automount_send_ready(Automount *a, int status); -int automount_add_one_mount_link(Automount *a, Mount *m); - const char* automount_state_to_string(AutomountState i) _const_; AutomountState automount_state_from_string(const char *s) _pure_; diff --git a/src/core/bus-errors.h b/src/core/bus-errors.h index 7a4084ea15..9368d68e80 100644 --- a/src/core/bus-errors.h +++ b/src/core/bus-errors.h @@ -40,3 +40,4 @@ #define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" #define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" #define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" +#define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed" diff --git a/src/core/cgroup-attr.c b/src/core/cgroup-attr.c deleted file mode 100644 index 7e3e08eabb..0000000000 --- a/src/core/cgroup-attr.c +++ /dev/null @@ -1,132 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - 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/>. -***/ - -#include "cgroup-attr.h" -#include "cgroup-util.h" -#include "list.h" -#include "fileio.h" - -int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) { - _cleanup_free_ char *path = NULL, *v = NULL; - int r; - - assert(a); - - b = cgroup_bonding_find_list(b, a->controller); - if (!b) - return 0; - - if (a->semantics && a->semantics->map_write) { - r = a->semantics->map_write(a->semantics, a->value, &v); - if (r < 0) - return r; - } - - r = cg_get_path(a->controller, b->path, a->name, &path); - if (r < 0) - return r; - - r = write_string_file(path, v ? v : a->value); - if (r < 0) - log_warning("Failed to write '%s' to %s: %s", v ? v : a->value, path, strerror(-r)); - - return r; -} - -int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) { - CGroupAttribute *a; - int r = 0; - - LIST_FOREACH(by_unit, a, first) { - int k; - - k = cgroup_attribute_apply(a, b); - if (r == 0) - r = k; - } - - 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, - const char *name) { - CGroupAttribute *a; - - assert(name); - - LIST_FOREACH(by_unit, a, first) - if (cgroup_attribute_matches(a, controller, name)) - return a; - - return NULL; -} - -void cgroup_attribute_free(CGroupAttribute *a) { - assert(a); - - if (a->unit) - LIST_REMOVE(CGroupAttribute, by_unit, a->unit->cgroup_attributes, a); - - free(a->controller); - free(a->name); - free(a->value); - free(a); -} - -void cgroup_attribute_free_list(CGroupAttribute *first) { - CGroupAttribute *a, *n; - - 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 deleted file mode 100644 index 3a13b7c92d..0000000000 --- a/src/core/cgroup-attr.h +++ /dev/null @@ -1,50 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -typedef struct CGroupAttribute CGroupAttribute; - -#include "unit.h" -#include "cgroup.h" -#include "cgroup-semantics.h" - -struct CGroupAttribute { - char *controller; - char *name; - char *value; - - Unit *unit; - - const CGroupSemantics *semantics; - - LIST_FIELDS(CGroupAttribute, by_unit); -}; - -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) _pure_; -CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name) _pure_; - -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 deleted file mode 100644 index 82b02bbd78..0000000000 --- a/src/core/cgroup-semantics.c +++ /dev/null @@ -1,333 +0,0 @@ -/*-*- 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 deleted file mode 100644 index 4f848f4bb7..0000000000 --- a/src/core/cgroup-semantics.h +++ /dev/null @@ -1,43 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -typedef struct CGroupSemantics CGroupSemantics; - -struct CGroupSemantics { - const char *controller; - const char *name; - const char *pretty; - - bool multiple; - - /* This call is used for parsing the pretty value to the actual attribute value */ - int (*map_pretty)(const CGroupSemantics *semantics, const char *value, char **ret); - - /* Right before writing this attribute the attribute value is converted to a low-level value */ - int (*map_write)(const CGroupSemantics *semantics, const char *value, char **ret); - - /* If this attribute takes a list, this call can be used to reset the list to empty */ - int (*reset)(const CGroupSemantics *semantics, const char *group); -}; - -int cgroup_semantics_find(const char *controller, const char *name, const char *value, char **ret, const CGroupSemantics **semantics); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 83df0f3c9a..8bf4d896de 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -3,7 +3,7 @@ /*** This file is part of systemd. - Copyright 2010 Lennart Poettering + 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 @@ -19,310 +19,578 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <errno.h> -#include <assert.h> -#include <unistd.h> -#include <sys/types.h> -#include <signal.h> -#include <sys/mount.h> #include <fcntl.h> -#include "cgroup.h" -#include "cgroup-util.h" -#include "log.h" -#include "strv.h" #include "path-util.h" +#include "special.h" +#include "cgroup-util.h" +#include "cgroup.h" -int cgroup_bonding_realize(CGroupBonding *b) { - int r; +void cgroup_context_init(CGroupContext *c) { + assert(c); - assert(b); - assert(b->path); - assert(b->controller); + /* Initialize everything to the kernel defaults, assuming the + * structure is preinitialized to 0 */ - r = cg_create(b->controller, b->path, NULL); - if (r < 0) { - log_warning("Failed to create cgroup %s:%s: %s", b->controller, b->path, strerror(-r)); - return r; - } + c->cpu_shares = 1024; + c->memory_limit = (uint64_t) -1; + c->blockio_weight = 1000; +} - b->realized = true; +void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { + assert(c); + assert(a); - return 0; + LIST_REMOVE(CGroupDeviceAllow, device_allow, c->device_allow, a); + free(a->path); + free(a); } -int cgroup_bonding_realize_list(CGroupBonding *first) { - CGroupBonding *b; - int r; - - LIST_FOREACH(by_unit, b, first) - if ((r = cgroup_bonding_realize(b)) < 0 && b->essential) - return r; +void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w) { + assert(c); + assert(w); - return 0; + LIST_REMOVE(CGroupBlockIODeviceWeight, device_weights, c->blockio_device_weights, w); + free(w->path); + free(w); } -void cgroup_bonding_free(CGroupBonding *b, bool trim) { +void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) { + assert(c); assert(b); - if (b->unit) { - CGroupBonding *f; + LIST_REMOVE(CGroupBlockIODeviceBandwidth, device_bandwidths, c->blockio_device_bandwidths, b); + free(b->path); + free(b); +} - LIST_REMOVE(CGroupBonding, by_unit, b->unit->cgroup_bondings, b); +void cgroup_context_done(CGroupContext *c) { + assert(c); - if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { - assert_se(f = hashmap_get(b->unit->manager->cgroup_bondings, b->path)); - LIST_REMOVE(CGroupBonding, by_path, f, b); + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - if (f) - hashmap_replace(b->unit->manager->cgroup_bondings, b->path, f); - else - hashmap_remove(b->unit->manager->cgroup_bondings, b->path); - } - } + while (c->blockio_device_bandwidths) + cgroup_context_free_blockio_device_bandwidth(c, c->blockio_device_bandwidths); - if (b->realized && b->ours && trim) - cg_trim(b->controller, b->path, false); + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); +} - free(b->controller); - free(b->path); - free(b); +void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + CGroupBlockIODeviceBandwidth *b; + CGroupBlockIODeviceWeight *w; + CGroupDeviceAllow *a; + + assert(c); + assert(f); + + prefix = strempty(prefix); + + fprintf(f, + "%sCPUAccounting=%s\n" + "%sBlockIOAccounting=%s\n" + "%sMemoryAccounting=%s\n" + "%sCPUShares=%lu\n" + "%sBlockIOWeight=%lu\n" + "%sMemoryLimit=%" PRIu64 "\n" + "%sDevicePolicy=%s\n", + prefix, yes_no(c->cpu_accounting), + prefix, yes_no(c->blockio_accounting), + prefix, yes_no(c->memory_accounting), + prefix, c->cpu_shares, + prefix, c->blockio_weight, + prefix, c->memory_limit, + prefix, cgroup_device_policy_to_string(c->device_policy)); + + LIST_FOREACH(device_allow, a, c->device_allow) + fprintf(f, + "%sDeviceAllow=%s %s%s%s\n", + prefix, + a->path, + a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); + + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + fprintf(f, + "%sBlockIODeviceWeight=%s %lu", + prefix, + w->path, + w->weight); + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + char buf[FORMAT_BYTES_MAX]; + + fprintf(f, + "%s%s=%s %s\n", + prefix, + b->read ? "BlockIOReadBandwidth" : "BlockIOWriteBandwidth", + b->path, + format_bytes(buf, sizeof(buf), b->bandwidth)); + } } -void cgroup_bonding_free_list(CGroupBonding *first, bool remove_or_trim) { - CGroupBonding *b, *n; +static int lookup_blkio_device(const char *p, dev_t *dev) { + struct stat st; + int r; - LIST_FOREACH_SAFE(by_unit, b, n, first) - cgroup_bonding_free(b, remove_or_trim); -} + assert(p); + assert(dev); -void cgroup_bonding_trim(CGroupBonding *b, bool delete_root) { - assert(b); + r = stat(p, &st); + if (r < 0) { + log_warning("Couldn't stat device %s: %m", p); + return -errno; + } - if (b->realized && b->ours) - cg_trim(b->controller, b->path, delete_root); -} + if (S_ISBLK(st.st_mode)) + *dev = 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 */ + *dev = st.st_dev; -void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root) { - CGroupBonding *b; + /* If this is a partition, try to get the originating + * block device */ + block_get_whole_disk(*dev, dev); + } else { + log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p); + return -ENODEV; + } - LIST_FOREACH(by_unit, b, first) - cgroup_bonding_trim(b, delete_root); + return 0; } -int cgroup_bonding_install(CGroupBonding *b, pid_t pid, const char *cgroup_suffix) { - _cleanup_free_ char *p = NULL; - const char *path; +static int whitelist_device(const char *path, const char *node, const char *acc) { + char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4]; + struct stat st; int r; - assert(b); - assert(pid >= 0); + assert(path); + assert(acc); - if (cgroup_suffix) { - p = strjoin(b->path, "/", cgroup_suffix, NULL); - if (!p) - return -ENOMEM; + if (stat(node, &st) < 0) { + log_warning("Couldn't stat device %s", node); + return -errno; + } - path = p; - } else - path = b->path; + if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_warning("%s is not a device.", node); + return -ENODEV; + } + + sprintf(buf, + "%c %u:%u %s", + S_ISCHR(st.st_mode) ? 'c' : 'b', + major(st.st_rdev), minor(st.st_rdev), + acc); - r = cg_create_and_attach(b->controller, path, pid); + r = cg_set_attribute("devices", path, "devices.allow", buf); if (r < 0) - return r; + log_warning("Failed to set devices.allow on %s: %s", path, strerror(-r)); - b->realized = true; - return 0; + return r; } -int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid, const char *cgroup_suffix) { - CGroupBonding *b; +void cgroup_context_apply(CGroupContext *c, CGroupControllerMask mask, const char *path) { int r; - LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_install(b, pid, cgroup_suffix); - if (r < 0 && b->essential) - return r; + assert(c); + assert(path); + + if (mask == 0) + return; + + if (mask & CGROUP_CPU) { + char buf[DECIMAL_STR_MAX(unsigned long) + 1]; + + sprintf(buf, "%lu\n", c->cpu_shares); + r = cg_set_attribute("cpu", path, "cpu.shares", buf); + if (r < 0) + log_warning("Failed to set cpu.shares on %s: %s", path, strerror(-r)); } - return 0; -} + if (mask & CGROUP_BLKIO) { + char buf[MAX3(DECIMAL_STR_MAX(unsigned long)+1, + DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(unsigned long)*1, + DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1)]; + CGroupBlockIODeviceWeight *w; + CGroupBlockIODeviceBandwidth *b; -int cgroup_bonding_migrate(CGroupBonding *b, CGroupBonding *list) { - CGroupBonding *q; - int ret = 0; + sprintf(buf, "%lu\n", c->blockio_weight); + r = cg_set_attribute("blkio", path, "blkio.weight", buf); + if (r < 0) + log_warning("Failed to set blkio.weight on %s: %s", path, strerror(-r)); - LIST_FOREACH(by_unit, q, list) { - int r; + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) { + dev_t dev; - if (q == b) - continue; + r = lookup_blkio_device(w->path, &dev); + if (r < 0) + continue; - if (!q->ours) - continue; + sprintf(buf, "%u:%u %lu", major(dev), minor(dev), w->weight); + r = cg_set_attribute("blkio", path, "blkio.weight_device", buf); + if (r < 0) + log_error("Failed to set blkio.weight_device on %s: %s", path, strerror(-r)); + } + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + const char *a; + dev_t dev; + + r = lookup_blkio_device(b->path, &dev); + if (r < 0) + continue; + + a = b->read ? "blkio.throttle.read_bps_device" : "blkio.throttle.write_bps_device"; + + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), b->bandwidth); + r = cg_set_attribute("blkio", path, a, buf); + if (r < 0) + log_error("Failed to set %s on %s: %s", a, path, strerror(-r)); + } + } + + if (mask & CGROUP_MEMORY) { + if (c->memory_limit != (uint64_t) -1) { + char buf[DECIMAL_STR_MAX(uint64_t) + 1]; + + sprintf(buf, "%" PRIu64 "\n", c->memory_limit); + r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf); + } else + r = cg_set_attribute("memory", path, "memory.limit_in_bytes", "-1"); - r = cg_migrate_recursive(q->controller, q->path, b->controller, b->path, true, false); - if (r < 0 && ret == 0) - ret = r; + if (r < 0) + log_error("Failed to set memory.limit_in_bytes on %s: %s", path, strerror(-r)); } - return ret; + if (mask & CGROUP_DEVICE) { + CGroupDeviceAllow *a; + + if (c->device_allow || c->device_policy != CGROUP_AUTO) + r = cg_set_attribute("devices", path, "devices.deny", "a"); + else + r = cg_set_attribute("devices", path, "devices.allow", "a"); + if (r < 0) + log_error("Failed to reset devices.list on %s: %s", path, strerror(-r)); + + if (c->device_policy == CGROUP_CLOSED || + (c->device_policy == CGROUP_AUTO && c->device_allow)) { + static const char auto_devices[] = + "/dev/null\0" "rw\0" + "/dev/zero\0" "rw\0" + "/dev/full\0" "rw\0" + "/dev/random\0" "rw\0" + "/dev/urandom\0" "rw\0"; + + const char *x, *y; + + NULSTR_FOREACH_PAIR(x, y, auto_devices) + whitelist_device(path, x, y); + } + + LIST_FOREACH(device_allow, a, c->device_allow) { + char acc[4]; + unsigned k = 0; + + if (a->r) + acc[k++] = 'r'; + if (a->w) + acc[k++] = 'w'; + if (a->m) + acc[k++] = 'm'; + + if (k == 0) + continue; + + acc[k++] = 0; + whitelist_device(path, a->path, acc); + } + } } -int cgroup_bonding_migrate_to(CGroupBonding *b, const char *target, bool rem) { - assert(b); - assert(target); +CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) { + CGroupControllerMask mask = 0; + + /* Figure out which controllers we need */ + + if (c->cpu_accounting || c->cpu_shares != 1024) + mask |= CGROUP_CPUACCT | CGROUP_CPU; + + if (c->blockio_accounting || + c->blockio_weight != 1000 || + c->blockio_device_weights || + c->blockio_device_bandwidths) + mask |= CGROUP_BLKIO; + + if (c->memory_accounting || + c->memory_limit != (uint64_t) -1) + mask |= CGROUP_MEMORY; + + if (c->device_allow || c->device_policy != CGROUP_AUTO) + mask |= CGROUP_DEVICE; - return cg_migrate_recursive(b->controller, b->path, b->controller, target, true, rem); + return mask; } -int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid) { - assert(b); +static CGroupControllerMask unit_get_cgroup_mask(Unit *u) { + CGroupContext *c; - if (!b->realized) - return -EINVAL; + c = unit_get_cgroup_context(u); + if (!c) + return 0; - return cg_set_group_access(b->controller, b->path, mode, uid, gid); + return cgroup_context_get_mask(c); } -int cgroup_bonding_set_group_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid) { - CGroupBonding *b; - int r; +static CGroupControllerMask unit_get_members_mask(Unit *u) { + CGroupControllerMask mask = 0; + Unit *m; + Iterator i; - LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_set_group_access(b, mode, uid, gid); - if (r < 0) - return r; + assert(u); + + SET_FOREACH(m, u->dependencies[UNIT_BEFORE], i) { + + if (UNIT_DEREF(m->slice) != u) + continue; + + mask |= unit_get_cgroup_mask(m) | unit_get_members_mask(m); } - return 0; + return mask; } -int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky) { - assert(b); +static CGroupControllerMask unit_get_siblings_mask(Unit *u) { + assert(u); - if (!b->realized) - return -EINVAL; + if (!UNIT_ISSET(u->slice)) + return 0; - return cg_set_task_access(b->controller, b->path, mode, uid, gid, sticky); + /* Sibling propagation is only relevant for weight-based + * controllers, so let's mask out everything else */ + return unit_get_members_mask(UNIT_DEREF(u->slice)) & + (CGROUP_CPU|CGROUP_BLKIO|CGROUP_CPUACCT); } -int cgroup_bonding_set_task_access_list(CGroupBonding *first, mode_t mode, uid_t uid, gid_t gid, int sticky) { - CGroupBonding *b; +static int unit_create_cgroups(Unit *u, CGroupControllerMask mask) { + char *path = NULL; int r; + bool is_in_hash = false; + + assert(u); + + path = unit_default_cgroup_path(u); + if (!path) + return -ENOMEM; + + r = hashmap_put(u->manager->cgroup_unit, path, u); + if (r == 0) + is_in_hash = true; + + if (r < 0) { + log_error("cgroup %s exists already: %s", path, strerror(-r)); + free(path); + return r; + } + + /* First, create our own group */ + r = cg_create_everywhere(u->manager->cgroup_supported, mask, path); + if (r < 0) + log_error("Failed to create cgroup %s: %s", path, strerror(-r)); - LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_set_task_access(b, mode, uid, gid, sticky); + /* Then, possibly move things over */ + if (u->cgroup_path) { + r = cg_migrate_everywhere(u->manager->cgroup_supported, u->cgroup_path, path); if (r < 0) - return r; + log_error("Failed to migrate cgroup %s: %s", path, strerror(-r)); } + if (!is_in_hash) { + /* And remember the new data */ + free(u->cgroup_path); + u->cgroup_path = path; + } + + u->cgroup_realized = true; + u->cgroup_mask = mask; + return 0; } -int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, bool rem, Set *s, const char *cgroup_suffix) { - char *p = NULL; - const char *path; - int r; +static int unit_realize_cgroup_now(Unit *u) { + CGroupControllerMask mask; - assert(b); - assert(sig >= 0); + assert(u); + + if (u->in_cgroup_queue) { + LIST_REMOVE(Unit, cgroup_queue, u->manager->cgroup_queue, u); + u->in_cgroup_queue = false; + } + + mask = unit_get_cgroup_mask(u) | unit_get_members_mask(u) | unit_get_siblings_mask(u); + mask &= u->manager->cgroup_supported; - /* Don't kill cgroups that aren't ours */ - if (!b->ours) + if (u->cgroup_realized && + u->cgroup_mask == mask) return 0; - if (cgroup_suffix) { - p = strjoin(b->path, "/", cgroup_suffix, NULL); - if (!p) - return -ENOMEM; + /* First, realize parents */ + if (UNIT_ISSET(u->slice)) + unit_realize_cgroup_now(UNIT_DEREF(u->slice)); - path = p; - } else - path = b->path; + /* And then do the real work */ + return unit_create_cgroups(u, mask); +} - r = cg_kill_recursive(b->controller, path, sig, sigcont, true, rem, s); - free(p); +static void unit_add_to_cgroup_queue(Unit *u) { - return r; + if (u->in_cgroup_queue) + return; + + LIST_PREPEND(Unit, cgroup_queue, u->manager->cgroup_queue, u); + u->in_cgroup_queue = true; } -int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, bool rem, Set *s, const char *cgroup_suffix) { - CGroupBonding *b; - Set *allocated_set = NULL; - int ret = -EAGAIN, r; +unsigned manager_dispatch_cgroup_queue(Manager *m) { + Unit *i; + unsigned n = 0; - if (!first) - return 0; + while ((i = m->cgroup_queue)) { + assert(i->in_cgroup_queue); + + if (unit_realize_cgroup_now(i) >= 0) + cgroup_context_apply(unit_get_cgroup_context(i), i->cgroup_mask, i->cgroup_path); + + n++; + } + + return n; +} + +static void unit_queue_siblings(Unit *u) { + Unit *slice; + + /* This adds the siblings of the specified unit and the + * siblings of all parent units to the cgroup queue. (But + * neither the specified unit itself nor the parents.) */ - if (!s) - if (!(s = allocated_set = set_new(trivial_hash_func, trivial_compare_func))) - return -ENOMEM; + while ((slice = UNIT_DEREF(u->slice))) { + Iterator i; + Unit *m; - LIST_FOREACH(by_unit, b, first) { - r = cgroup_bonding_kill(b, sig, sigcont, rem, s, cgroup_suffix); - if (r < 0) { - if (r == -EAGAIN || r == -ESRCH) + SET_FOREACH(m, slice->dependencies[UNIT_BEFORE], i) { + if (m == u) continue; - ret = r; - goto finish; + if (UNIT_DEREF(m->slice) != slice) + continue; + + unit_add_to_cgroup_queue(m); } - if (ret < 0 || r > 0) - ret = r; + u = slice; } +} + +int unit_realize_cgroup(Unit *u) { + CGroupContext *c; + int r; + + assert(u); + + c = unit_get_cgroup_context(u); + if (!c) + return 0; -finish: - if (allocated_set) - set_free(allocated_set); + /* So, here's the deal: when realizing the cgroups for this + * unit, we need to first create all parents, but there's more + * actually: for the weight-based controllers we also need to + * make sure that all our siblings (i.e. units that are in the + * same slice as we are) have cgroup too. Otherwise things + * would become very uneven as each of their processes would + * get as much resources as all our group together. This call + * will synchronously create the parent cgroups, but will + * defer work on the siblings to the next event loop + * iteration. */ - return ret; + /* Add all sibling slices to the cgroup queue. */ + unit_queue_siblings(u); + + /* And realize this one now */ + r = unit_realize_cgroup_now(u); + + /* And apply the values */ + if (r >= 0) + cgroup_context_apply(c, u->cgroup_mask, u->cgroup_path); + + return r; } -/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we - * cannot know */ -int cgroup_bonding_is_empty(CGroupBonding *b) { +void unit_destroy_cgroup(Unit *u) { int r; - assert(b); + assert(u); - if ((r = cg_is_empty_recursive(b->controller, b->path, true)) < 0) - return r; + if (!u->cgroup_path) + return; + + r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !unit_has_name(u, SPECIAL_ROOT_SLICE)); + if (r < 0) + log_debug("Failed to destroy cgroup %s: %s", u->cgroup_path, strerror(-r)); + + hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); - /* If it is empty it is empty */ - if (r > 0) - return 1; + free(u->cgroup_path); + u->cgroup_path = NULL; + u->cgroup_realized = false; + u->cgroup_mask = 0; - /* It's not only us using this cgroup, so we just don't know */ - return b->ours ? 0 : -EAGAIN; } -int cgroup_bonding_is_empty_list(CGroupBonding *first) { - CGroupBonding *b; +pid_t unit_search_main_pid(Unit *u) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0, npid, mypid; - LIST_FOREACH(by_unit, b, first) { - int r; + assert(u); - if ((r = cgroup_bonding_is_empty(b)) < 0) { - /* If this returned -EAGAIN, then we don't know if the - * group is empty, so let's see if another group can - * tell us */ + if (!u->cgroup_path) + return 0; - if (r != -EAGAIN) - return r; - } else - return r; + if (cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f) < 0) + return 0; + + mypid = getpid(); + while (cg_read_pid(f, &npid) > 0) { + pid_t ppid; + + if (npid == pid) + continue; + + /* Ignore processes that aren't our kids */ + if (get_parent_of_pid(npid, &ppid) >= 0 && ppid != mypid) + continue; + + if (pid != 0) { + /* Dang, there's more than one daemonized PID + in this group, so we don't know what process + is the main process. */ + pid = 0; + break; + } + + pid = npid; } - return -EAGAIN; + return pid; } int manager_setup_cgroup(Manager *m) { - _cleanup_free_ char *current = NULL, *path = NULL; - char suffix_buffer[sizeof("/systemd-") + DECIMAL_STR_MAX(pid_t)]; - const char *suffix; + _cleanup_free_ char *path = NULL; int r; + char *e, *a; assert(m); @@ -333,37 +601,30 @@ int manager_setup_cgroup(Manager *m) { } /* 1. Determine hierarchy */ - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, ¤t); + free(m->cgroup_root); + m->cgroup_root = NULL; + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &m->cgroup_root); if (r < 0) { log_error("Cannot determine cgroup we are running in: %s", strerror(-r)); return r; } - if (m->running_as == SYSTEMD_SYSTEM) - suffix = "/system"; - else { - sprintf(suffix_buffer, "/systemd-%lu", (unsigned long) getpid()); - suffix = suffix_buffer; + /* Already in /system.slice? If so, let's cut this off again */ + if (m->running_as == SYSTEMD_SYSTEM) { + e = endswith(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE); + if (e) + *e = 0; } - free(m->cgroup_hierarchy); - if (endswith(current, suffix)) { - /* We probably got reexecuted and can continue to use our root cgroup */ - m->cgroup_hierarchy = current; - current = NULL; - } else { - /* We need a new root cgroup */ - if (streq(current, "/")) - m->cgroup_hierarchy = strdup(suffix); - else - m->cgroup_hierarchy = strappend(current, suffix); - - if (!m->cgroup_hierarchy) - return log_oom(); - } + /* And make sure to store away the root value without trailing + * slash, even for the root dir, so that we can easily prepend + * it everywhere. */ + if (streq(m->cgroup_root, "/")) + m->cgroup_root[0] = 0; /* 2. Show data */ - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, NULL, &path); + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path); if (r < 0) { log_error("Cannot find cgroup mount point: %s", strerror(-r)); return r; @@ -382,8 +643,12 @@ int manager_setup_cgroup(Manager *m) { log_debug("Release agent already installed."); } - /* 4. Realize the group */ - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, 0); + /* 4. Realize the system slice and put us in there */ + if (m->running_as == SYSTEMD_SYSTEM) { + a = strappenda(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, a, 0); + } else + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, 0); if (r < 0) { log_error("Failed to create root cgroup hierarchy: %s", strerror(-r)); return r; @@ -399,16 +664,11 @@ int manager_setup_cgroup(Manager *m) { return -errno; } - /* 6. Remove non-existing controllers from the default controllers list */ - cg_shorten_controllers(m->default_controllers); + /* 6. Figure out which controllers are supported */ + m->cgroup_supported = cg_mask_supported(); - /* 7. Let's create the user and machine hierarchies - * right-away, so that people can inotify on them, if they - * wish, without this being racy. */ - if (m->running_as == SYSTEMD_SYSTEM) { - cg_create(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, "../user"); - cg_create(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy, "../machine"); - } + /* 7. Always enable hierarchial support if it exists... */ + cg_set_attribute("memory", "/", "memory.use_hierarchy", "1"); return 0; } @@ -416,213 +676,88 @@ int manager_setup_cgroup(Manager *m) { void manager_shutdown_cgroup(Manager *m, bool delete) { assert(m); - if (delete && m->cgroup_hierarchy) - cg_delete(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_hierarchy); + /* We can't really delete the group, since we are in it. But + * let's trim it. */ + if (delete && m->cgroup_root) + cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, false); if (m->pin_cgroupfs_fd >= 0) { close_nointr_nofail(m->pin_cgroupfs_fd); m->pin_cgroupfs_fd = -1; } - free(m->cgroup_hierarchy); - m->cgroup_hierarchy = NULL; + free(m->cgroup_root); + m->cgroup_root = NULL; } -int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding) { - CGroupBonding *b; +Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) { char *p; + Unit *u; assert(m); assert(cgroup); - assert(bonding); - b = hashmap_get(m->cgroup_bondings, cgroup); - if (b) { - *bonding = b; - return 1; - } + u = hashmap_get(m->cgroup_unit, cgroup); + if (u) + return u; p = strdupa(cgroup); - if (!p) - return -ENOMEM; - for (;;) { char *e; e = strrchr(p, '/'); - if (e == p || !e) { - *bonding = NULL; - return 0; - } + if (e == p || !e) + return NULL; *e = 0; - b = hashmap_get(m->cgroup_bondings, p); - if (b) { - *bonding = b; - return 1; - } + u = hashmap_get(m->cgroup_unit, p); + if (u) + return u; } } -int cgroup_notify_empty(Manager *m, const char *group) { - CGroupBonding *l, *b; +Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) { + _cleanup_free_ char *cgroup = NULL; int r; assert(m); - assert(group); - - r = cgroup_bonding_get(m, group, &l); - if (r <= 0) - return r; - - LIST_FOREACH(by_path, b, l) { - int t; - - if (!b->unit) - continue; - - t = cgroup_bonding_is_empty_list(b); - if (t < 0) { - - /* If we don't know, we don't know */ - if (t != -EAGAIN) - log_warning("Failed to check whether cgroup is empty: %s", strerror(errno)); - - continue; - } - - if (t > 0) { - /* If it is empty, let's delete it */ - cgroup_bonding_trim_list(b->unit->cgroup_bondings, true); - - if (UNIT_VTABLE(b->unit)->cgroup_notify_empty) - UNIT_VTABLE(b->unit)->cgroup_notify_empty(b->unit); - } - } - - return 0; -} - -Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) { - CGroupBonding *l, *b; - char *group = NULL; - - assert(m); if (pid <= 1) return NULL; - if (cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &group) < 0) - return NULL; - - l = hashmap_get(m->cgroup_bondings, group); - - if (!l) { - char *slash; - - while ((slash = strrchr(group, '/'))) { - if (slash == group) - break; - - *slash = 0; - - if ((l = hashmap_get(m->cgroup_bondings, group))) - break; - } - } - - free(group); - - LIST_FOREACH(by_path, b, l) { - - if (!b->unit) - continue; - - if (b->ours) - return b->unit; - } - - return NULL; -} - -CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) { - CGroupBonding *b; - - if (!controller) - controller = SYSTEMD_CGROUP_CONTROLLER; - - LIST_FOREACH(by_unit, b, first) - if (streq(b->controller, controller)) - return b; - - return NULL; -} - -char *cgroup_bonding_to_string(CGroupBonding *b) { - char *r; - - assert(b); - - if (asprintf(&r, "%s:%s", b->controller, b->path) < 0) + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); + if (r < 0) return NULL; - return r; + return manager_get_unit_by_cgroup(m, cgroup); } -pid_t cgroup_bonding_search_main_pid(CGroupBonding *b) { - FILE *f; - pid_t pid = 0, npid, mypid; - - assert(b); - - if (!b->ours) - return 0; - - if (cg_enumerate_processes(b->controller, b->path, &f) < 0) - return 0; - - mypid = getpid(); - - while (cg_read_pid(f, &npid) > 0) { - pid_t ppid; +int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { + Unit *u; + int r; - if (npid == pid) - continue; + assert(m); + assert(cgroup); - /* Ignore processes that aren't our kids */ - if (get_parent_of_pid(npid, &ppid) >= 0 && ppid != mypid) - continue; + u = manager_get_unit_by_cgroup(m, cgroup); + if (u) { + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true); + if (r > 0) { + if (UNIT_VTABLE(u)->notify_cgroup_empty) + UNIT_VTABLE(u)->notify_cgroup_empty(u); - if (pid != 0) { - /* Dang, there's more than one daemonized PID - in this group, so we don't know what process - is the main process. */ - pid = 0; - break; + unit_add_to_gc_queue(u); } - - pid = npid; } - fclose(f); - - return pid; + return 0; } -pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *first) { - CGroupBonding *b; - pid_t pid; - - /* Try to find a main pid from this cgroup, but checking if - * there's only one PID in the cgroup and returning it. Later - * on we might want to add additional, smarter heuristics - * here. */ +static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { + [CGROUP_AUTO] = "auto", + [CGROUP_CLOSED] = "closed", + [CGROUP_STRICT] = "strict", +}; - LIST_FOREACH(by_unit, b, first) - if ((pid = cgroup_bonding_search_main_pid(b)) != 0) - return pid; - - return 0; - -} +DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 6555d89e37..0a079e909d 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -5,7 +5,7 @@ /*** This file is part of systemd. - Copyright 2010 Lennart Poettering + 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 @@ -21,74 +21,95 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -typedef struct CGroupBonding CGroupBonding; +#include "list.h" -#include "unit.h" +typedef struct CGroupContext CGroupContext; +typedef struct CGroupDeviceAllow CGroupDeviceAllow; +typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; +typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; -/* Binds a cgroup to a name */ -struct CGroupBonding { - char *controller; - char *path; +typedef enum CGroupDevicePolicy { - Unit *unit; + /* When devices listed, will allow those, plus built-in ones, + if none are listed will allow everything. */ + CGROUP_AUTO, - /* For the Unit::cgroup_bondings list */ - LIST_FIELDS(CGroupBonding, by_unit); + /* Everything forbidden, except built-in ones and listed ones. */ + CGROUP_CLOSED, - /* For the Manager::cgroup_bondings hashmap */ - LIST_FIELDS(CGroupBonding, by_path); + /* Everythings forbidden, except for the listed devices */ + CGROUP_STRICT, - /* When shutting down, remove cgroup? Are our own tasks the - * only ones in this group?*/ - bool ours:1; + _CGROUP_DEVICE_POLICY_MAX, + _CGROUP_DEVICE_POLICY_INVALID = -1 +} CGroupDevicePolicy; - /* If we cannot create this group, or add a process to it, is this fatal? */ - bool essential:1; +struct CGroupDeviceAllow { + LIST_FIELDS(CGroupDeviceAllow, device_allow); + char *path; + bool r:1; + bool w:1; + bool m:1; +}; - /* This cgroup is realized */ - bool realized:1; +struct CGroupBlockIODeviceWeight { + LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); + char *path; + unsigned long weight; }; -int cgroup_bonding_realize(CGroupBonding *b); -int cgroup_bonding_realize_list(CGroupBonding *first); +struct CGroupBlockIODeviceBandwidth { + LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); + char *path; + uint64_t bandwidth; + bool read; +}; -void cgroup_bonding_free(CGroupBonding *b, bool trim); -void cgroup_bonding_free_list(CGroupBonding *first, bool trim); +struct CGroupContext { + bool cpu_accounting; + bool blockio_accounting; + bool memory_accounting; -int cgroup_bonding_install(CGroupBonding *b, pid_t pid, const char *suffix); -int cgroup_bonding_install_list(CGroupBonding *first, pid_t pid, const char *suffix); + unsigned long cpu_shares; -int cgroup_bonding_migrate(CGroupBonding *b, CGroupBonding *list); -int cgroup_bonding_migrate_to(CGroupBonding *b, const char *target, bool rem); + unsigned long blockio_weight; + LIST_HEAD(CGroupBlockIODeviceWeight, blockio_device_weights); + LIST_HEAD(CGroupBlockIODeviceBandwidth, blockio_device_bandwidths); -int cgroup_bonding_set_group_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); -int cgroup_bonding_set_group_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid); + uint64_t memory_limit; -int cgroup_bonding_set_task_access(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); -int cgroup_bonding_set_task_access_list(CGroupBonding *b, mode_t mode, uid_t uid, gid_t gid, int sticky); + CGroupDevicePolicy device_policy; + LIST_HEAD(CGroupDeviceAllow, device_allow); +}; -int cgroup_bonding_kill(CGroupBonding *b, int sig, bool sigcont, bool rem, Set *s, const char *suffix); -int cgroup_bonding_kill_list(CGroupBonding *first, int sig, bool sigcont, bool rem, Set *s, const char *suffix); +#include "unit.h" +#include "manager.h" +#include "cgroup-util.h" -void cgroup_bonding_trim(CGroupBonding *first, bool delete_root); -void cgroup_bonding_trim_list(CGroupBonding *first, bool delete_root); +void cgroup_context_init(CGroupContext *c); +void cgroup_context_done(CGroupContext *c); +void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix); +void cgroup_context_apply(CGroupContext *c, CGroupControllerMask mask, const char *path); +CGroupControllerMask cgroup_context_get_mask(CGroupContext *c); -int cgroup_bonding_is_empty(CGroupBonding *b); -int cgroup_bonding_is_empty_list(CGroupBonding *first); +void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); +void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); +void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); -CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) _pure_; +int unit_realize_cgroup(Unit *u); +void unit_destroy_cgroup(Unit *u); -char *cgroup_bonding_to_string(CGroupBonding *b); +int manager_setup_cgroup(Manager *m); +void manager_shutdown_cgroup(Manager *m, bool delete); -pid_t cgroup_bonding_search_main_pid(CGroupBonding *b); -pid_t cgroup_bonding_search_main_pid_list(CGroupBonding *b); +unsigned manager_dispatch_cgroup_queue(Manager *m); -#include "manager.h" +Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); +Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); -int manager_setup_cgroup(Manager *m); -void manager_shutdown_cgroup(Manager *m, bool delete); +pid_t unit_search_main_pid(Unit *u); -int cgroup_bonding_get(Manager *m, const char *cgroup, CGroupBonding **bonding); -int cgroup_notify_empty(Manager *m, const char *group); +int manager_notify_cgroup_empty(Manager *m, const char *group); -Unit* cgroup_unit_by_pid(Manager *m, pid_t pid); +const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_; +CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_; diff --git a/src/core/condition.c b/src/core/condition.c index 16cae6d23b..6c387450af 100644 --- a/src/core/condition.c +++ b/src/core/condition.c @@ -37,6 +37,7 @@ #include "virt.h" #include "path-util.h" #include "fileio.h" +#include "unit.h" Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { Condition *c; @@ -157,15 +158,28 @@ static bool test_virtualization(const char *parameter) { return v > 0 && streq(parameter, id); } +static bool test_apparmor_enabled(void) { + int r; + _cleanup_free_ char *p = NULL; + + r = read_one_line_file("/sys/module/apparmor/parameters/enabled", &p); + if (r < 0) + return false; + + return parse_boolean(p) > 0; +} + static bool test_security(const char *parameter) { #ifdef HAVE_SELINUX if (streq(parameter, "selinux")) return is_selinux_enabled() > 0; #endif - if (streq(parameter, "apparmor")) - return access("/sys/kernel/security/apparmor/", F_OK) == 0; - if (streq(parameter, "smack")) - return access("/sys/fs/smackfs", F_OK) == 0; + if (streq(parameter, "apparmor")) + return test_apparmor_enabled(); + if (streq(parameter, "ima")) + return access("/sys/kernel/security/ima/", F_OK) == 0; + if (streq(parameter, "smack")) + return access("/sys/fs/smackfs", F_OK) == 0; return false; } @@ -236,7 +250,7 @@ static bool test_ac_power(const char *parameter) { return (on_ac_power() != 0) == !!r; } -bool condition_test(Condition *c) { +static bool condition_test(Condition *c) { assert(c); switch(c->type) { @@ -320,7 +334,7 @@ bool condition_test(Condition *c) { } } -bool condition_test_list(Condition *first) { +bool condition_test_list(const char *unit, Condition *first) { Condition *c; int triggered = -1; @@ -335,6 +349,16 @@ bool condition_test_list(Condition *first) { bool b; b = condition_test(c); + if (unit) + log_debug_unit(unit, + "%s=%s%s%s %s for %s.", + condition_type_to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + b ? "succeeded" : "failed", + unit); + c->state = b ? 1 : -1; if (!c->trigger && !b) return false; @@ -354,12 +378,13 @@ void condition_dump(Condition *c, FILE *f, const char *prefix) { prefix = ""; fprintf(f, - "%s\t%s: %s%s%s\n", + "%s\t%s: %s%s%s %s\n", prefix, condition_type_to_string(c->type), c->trigger ? "|" : "", c->negate ? "!" : "", - c->parameter); + c->parameter, + c->state < 0 ? "failed" : c->state > 0 ? "succeeded" : "untested"); } void condition_dump_list(Condition *first, FILE *f, const char *prefix) { @@ -378,9 +403,11 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", + [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", [CONDITION_SECURITY] = "ConditionSecurity", + [CONDITION_CAPABILITY] = "ConditionCapability", [CONDITION_HOST] = "ConditionHost", [CONDITION_AC_POWER] = "ConditionACPower", [CONDITION_NULL] = "ConditionNull" diff --git a/src/core/condition.h b/src/core/condition.h index 50ed955af9..1813b735a5 100644 --- a/src/core/condition.h +++ b/src/core/condition.h @@ -48,11 +48,14 @@ typedef enum ConditionType { typedef struct Condition { ConditionType type; - char *parameter; bool trigger:1; bool negate:1; + char *parameter; + + int state; + LIST_FIELDS(struct Condition, conditions); } Condition; @@ -60,8 +63,7 @@ Condition* condition_new(ConditionType type, const char *parameter, bool trigger void condition_free(Condition *c); void condition_free_list(Condition *c); -bool condition_test(Condition *c); -bool condition_test_list(Condition *c); +bool condition_test_list(const char *unit, Condition *c); void condition_dump(Condition *c, FILE *f, const char *prefix); void condition_dump_list(Condition *c, FILE *f, const char *prefix); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c new file mode 100644 index 0000000000..9ebcad9da6 --- /dev/null +++ b/src/core/dbus-cgroup.c @@ -0,0 +1,554 @@ +/*-*- 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 <dbus/dbus.h> + +#include "path-util.h" +#include "dbus-cgroup.h" + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_cgroup_append_device_policy, cgroup_device_policy, CGroupDevicePolicy); + +static int bus_cgroup_append_device_weights(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub, sub2; + CGroupContext *c = data; + CGroupBlockIODeviceWeight *w; + + assert(i); + assert(property); + assert(c); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(st)", &sub)) + return -ENOMEM; + + LIST_FOREACH(device_weights, w, c->blockio_device_weights) { + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &w->path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &w->weight) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_cgroup_append_device_bandwidths(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub, sub2; + CGroupContext *c = data; + CGroupBlockIODeviceBandwidth *b; + + assert(i); + assert(property); + assert(c); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(st)", &sub)) + return -ENOMEM; + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + + if (streq(property, "BlockIOReadBandwidth") != b->read) + continue; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &b->path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT64, &b->bandwidth) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_cgroup_append_device_allow(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub, sub2; + CGroupContext *c = data; + CGroupDeviceAllow *a; + + assert(i); + assert(property); + assert(c); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(ss)", &sub)) + return -ENOMEM; + + LIST_FOREACH(device_allow, a, c->device_allow) { + const char *rwm; + char buf[4]; + unsigned k = 0; + + if (a->r) + buf[k++] = 'r'; + if (a->w) + buf[k++] = 'w'; + if (a->m) + buf[k++] = 'm'; + + buf[k] = 0; + rwm = buf; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->path) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &rwm) || + !dbus_message_iter_close_container(&sub, &sub2)) + return -ENOMEM; + } + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +const BusProperty bus_cgroup_context_properties[] = { + { "CPUAccounting", bus_property_append_bool, "b", offsetof(CGroupContext, cpu_accounting) }, + { "CPUShares", bus_property_append_ul, "t", offsetof(CGroupContext, cpu_shares) }, + { "BlockIOAccounting", bus_property_append_bool, "b", offsetof(CGroupContext, blockio_accounting) }, + { "BlockIOWeight", bus_property_append_ul, "t", offsetof(CGroupContext, blockio_weight) }, + { "BlockIODeviceWeight", bus_cgroup_append_device_weights, "a(st)", 0 }, + { "BlockIOReadBandwidth", bus_cgroup_append_device_bandwidths, "a(st)", 0 }, + { "BlockIOWriteBandwidth", bus_cgroup_append_device_bandwidths, "a(st)", 0 }, + { "MemoryAccounting", bus_property_append_bool, "b", offsetof(CGroupContext, memory_accounting) }, + { "MemoryLimit", bus_property_append_uint64, "t", offsetof(CGroupContext, memory_limit) }, + { "DevicePolicy", bus_cgroup_append_device_policy, "s", offsetof(CGroupContext, device_policy) }, + { "DeviceAllow", bus_cgroup_append_device_allow, "a(ss)", 0 }, + {} +}; + +int bus_cgroup_set_property( + Unit *u, + CGroupContext *c, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + assert(name); + assert(u); + assert(c); + assert(i); + + if (streq(name, "CPUAccounting")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + if (mode != UNIT_CHECK) { + dbus_bool_t b; + dbus_message_iter_get_basic(i, &b); + + c->cpu_accounting = b; + unit_write_drop_in_private(u, mode, name, b ? "CPUAccounting=yes" : "CPUAccounting=no"); + } + + return 1; + + } else if (streq(name, "CPUShares")) { + uint64_t u64; + unsigned long ul; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_UINT64) + return -EINVAL; + + dbus_message_iter_get_basic(i, &u64); + ul = (unsigned long) u64; + + if (u64 <= 0 || u64 != (uint64_t) ul) + return -EINVAL; + + if (mode != UNIT_CHECK) { + c->cpu_shares = ul; + unit_write_drop_in_private_format(u, mode, name, "CPUShares=%lu", ul); + } + + return 1; + + } else if (streq(name, "BlockIOAccounting")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + if (mode != UNIT_CHECK) { + dbus_bool_t b; + dbus_message_iter_get_basic(i, &b); + + c->blockio_accounting = b; + unit_write_drop_in_private(u, mode, name, b ? "BlockIOAccounting=yes" : "BlockIOAccounting=no"); + } + + return 1; + + } else if (streq(name, "BlockIOWeight")) { + uint64_t u64; + unsigned long ul; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_UINT64) + return -EINVAL; + + dbus_message_iter_get_basic(i, &u64); + ul = (unsigned long) u64; + + if (u64 < 10 || u64 > 1000) + return -EINVAL; + + if (mode != UNIT_CHECK) { + c->blockio_weight = ul; + unit_write_drop_in_private_format(u, mode, name, "BlockIOWeight=%lu", ul); + } + + return 1; + + } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) { + DBusMessageIter sub; + unsigned n = 0; + bool read = true; + + if (streq(name, "BlockIOWriteBandwidth")) + read = false; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + DBusMessageIter sub2; + const char *path; + uint64_t u64; + CGroupBlockIODeviceBandwidth *a; + + dbus_message_iter_recurse(&sub, &sub2); + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &u64, false) < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceBandwidth *b; + bool exist = false; + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + if (path_equal(path, b->path) && read == b->read) { + a = b; + exist = true; + break; + } + } + + if (!exist) { + a = new0(CGroupBlockIODeviceBandwidth, 1); + if (!a) + return -ENOMEM; + + a->read = read; + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + } + + a->bandwidth = u64; + + if (!exist) + LIST_PREPEND(CGroupBlockIODeviceBandwidth, device_bandwidths, + c->blockio_device_bandwidths, a); + } + + n++; + dbus_message_iter_next(&sub); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupBlockIODeviceBandwidth *a; + CGroupBlockIODeviceBandwidth *next; + size_t size = 0; + + if (n == 0) { + LIST_FOREACH_SAFE(device_bandwidths, a, next, c->blockio_device_bandwidths) + if (a->read == read) + cgroup_context_free_blockio_device_bandwidth(c, a); + } + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + if (read) { + fputs("BlockIOReadBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (a->read) + fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->bandwidth); + } else { + fputs("BlockIOWriteBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (!a->read) + fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->bandwidth); + } + + fflush(f); + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "BlockIODeviceWeight")) { + DBusMessageIter sub; + unsigned n = 0; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + DBusMessageIter sub2; + const char *path; + uint64_t u64; + unsigned long ul; + CGroupBlockIODeviceWeight *a; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &u64, false) < 0) + return -EINVAL; + + ul = (unsigned long) u64; + if (ul < 10 || ul > 1000) + return -EINVAL; + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceWeight *b; + bool exist = false; + + LIST_FOREACH(device_weights, b, c->blockio_device_weights) { + if (path_equal(b->path, path)) { + a = b; + exist = true; + break; + } + } + + if (!exist) { + a = new0(CGroupBlockIODeviceWeight, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + } + + a->weight = ul; + + if (!exist) + LIST_PREPEND(CGroupBlockIODeviceWeight, device_weights, + c->blockio_device_weights, a); + } + + n++; + dbus_message_iter_next(&sub); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupBlockIODeviceWeight *a; + size_t size = 0; + + if (n == 0) { + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + } + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("BlockIODeviceWeight=\n", f); + LIST_FOREACH(device_weights, a, c->blockio_device_weights) + fprintf(f, "BlockIODeviceWeight=%s %lu\n", a->path, a->weight); + + fflush(f); + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "MemoryAccounting")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + if (mode != UNIT_CHECK) { + dbus_bool_t b; + dbus_message_iter_get_basic(i, &b); + + c->memory_accounting = b; + unit_write_drop_in_private(u, mode, name, b ? "MemoryAccounting=yes" : "MemoryAccounting=no"); + } + + return 1; + + } else if (streq(name, "MemoryLimit")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_UINT64) + return -EINVAL; + + if (mode != UNIT_CHECK) { + uint64_t limit; + dbus_message_iter_get_basic(i, &limit); + + c->memory_limit = limit; + unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, limit); + } + + return 1; + + } else if (streq(name, "DevicePolicy")) { + const char *policy; + CGroupDevicePolicy p; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(i, &policy); + p = cgroup_device_policy_from_string(policy); + if (p < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + char *buf; + + c->device_policy = p; + + buf = strappenda("DevicePolicy=", policy); + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "DeviceAllow")) { + DBusMessageIter sub; + unsigned n = 0; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + DBusMessageIter sub2; + const char *path, *rwm; + CGroupDeviceAllow *a; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &rwm, false) < 0) + return -EINVAL; + + if (!path_startswith(path, "/dev")) { + dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "DeviceAllow= requires device node"); + return -EINVAL; + } + + if (isempty(rwm)) + rwm = "rwm"; + + if (!in_charset(rwm, "rwm")) { + dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "DeviceAllow= requires combination of rwm flags"); + return -EINVAL; + } + + if (mode != UNIT_CHECK) { + CGroupDeviceAllow *b; + bool exist = false; + + LIST_FOREACH(device_allow, b, c->device_allow) { + if (path_equal(b->path, path)) { + a = b; + exist = true; + break; + } + } + + if (!exist) { + a = new0(CGroupDeviceAllow, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + } + + a->r = !!strchr(rwm, 'r'); + a->w = !!strchr(rwm, 'w'); + a->m = !!strchr(rwm, 'm'); + + if (!exist) + LIST_PREPEND(CGroupDeviceAllow, device_allow, c->device_allow, a); + } + + n++; + dbus_message_iter_next(&sub); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupDeviceAllow *a; + size_t size = 0; + + if (n == 0) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + } + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("DeviceAllow=\n", f); + LIST_FOREACH(device_allow, a, c->device_allow) + fprintf(f, "DeviceAllow=%s %s%s%s\n", a->path, a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); + + fflush(f); + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + } + + return 0; +} diff --git a/src/core/dbus-cgroup.h b/src/core/dbus-cgroup.h new file mode 100644 index 0000000000..e5ac4c3af7 --- /dev/null +++ b/src/core/dbus-cgroup.h @@ -0,0 +1,45 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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 <dbus/dbus.h> + +#include "manager.h" +#include "dbus-common.h" +#include "cgroup.h" + +#define BUS_CGROUP_CONTEXT_INTERFACE \ + " <property name=\"CPUAccounting\" type=\"b\" access=\"read\"/>\n" \ + " <property name=\"CPUShares\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"BlockIOAccounting\" type=\"b\" access=\"read\"/>\n" \ + " <property name=\"BlockIOWeight\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"BlockIODeviceWeight\" type=\"a(st)\" access=\"read\"/>\n" \ + " <property name=\"BlockIOReadBandwidth=\" type=\"a(st)\" access=\"read\"/>\n" \ + " <property name=\"BlockIOWriteBandwidth=\" type=\"a(st)\" access=\"read\"/>\n" \ + " <property name=\"MemoryAccounting\" type=\"b\" access=\"read\"/>\n" \ + " <property name=\"MemoryLimit\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"DevicePolicy\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"DeviceAllow\" type=\"a(ss)\" access=\"read\"/>\n" + +extern const BusProperty bus_cgroup_context_properties[]; + +int bus_cgroup_set_property(Unit *u, CGroupContext *c, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 2a8a0e1ac5..2402e8c34d 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -31,10 +31,10 @@ #include "syscall-list.h" #include "fileio.h" -DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_input, exec_input, ExecInput); -DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_output, exec_output, ExecOutput); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_input, exec_input, ExecInput); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_execute_append_output, exec_output, ExecOutput); -int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data) { char **env_files = data, **j; DBusMessageIter sub, sub2; @@ -66,7 +66,7 @@ int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void return 0; } -int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; int32_t n; @@ -77,12 +77,11 @@ int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property if (c->oom_score_adjust_set) n = c->oom_score_adjust; else { - char *t; + _cleanup_free_ char *t = NULL; n = 0; if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) { safe_atoi(t, &n); - free(t); } } @@ -92,7 +91,7 @@ int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property return 0; } -int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; int32_t n; @@ -111,7 +110,7 @@ int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data return 0; } -int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; int32_t n; @@ -130,7 +129,7 @@ int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *da return 0; } -int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; int32_t n; @@ -149,7 +148,7 @@ int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property return 0; } -int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; int32_t n; @@ -174,7 +173,7 @@ int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *proper return 0; } -int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; dbus_bool_t b; DBusMessageIter sub; @@ -200,7 +199,7 @@ int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void * return 0; } -int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; uint64_t u; @@ -219,7 +218,7 @@ int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property return 0; } -int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; uint64_t normal, inverted; @@ -236,7 +235,7 @@ int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, v return bus_property_append_uint64(i, property, &inverted); } -int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; char *t = NULL; const char *s; @@ -265,7 +264,7 @@ int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, vo return 0; } -int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; int r; uint64_t u; @@ -347,7 +346,7 @@ int bus_execute_append_command(DBusMessageIter *i, const char *property, void *d return 0; } -int bus_execute_append_syscall_filter(DBusMessageIter *i, const char *property, void *data) { +static int bus_execute_append_syscall_filter(DBusMessageIter *i, const char *property, void *data) { ExecContext *c = data; dbus_bool_t b; DBusMessageIter sub; @@ -430,10 +429,8 @@ const BusProperty bus_exec_context_properties[] = { { "PrivateNetwork", bus_property_append_bool, "b", offsetof(ExecContext, private_network) }, { "SameProcessGroup", bus_property_append_bool, "b", offsetof(ExecContext, same_pgrp) }, { "UtmpIdentifier", bus_property_append_string, "s", offsetof(ExecContext, utmp_id), true }, - { "ControlGroupModify", bus_property_append_bool, "b", offsetof(ExecContext, control_group_modify) }, - { "ControlGroupPersistent", bus_property_append_tristate_false, "b", offsetof(ExecContext, control_group_persistent) }, { "IgnoreSIGPIPE", bus_property_append_bool, "b", offsetof(ExecContext, ignore_sigpipe) }, { "NoNewPrivileges", bus_property_append_bool, "b", offsetof(ExecContext, no_new_privileges) }, { "SystemCallFilter", bus_execute_append_syscall_filter, "au", 0 }, - { NULL, } + {} }; diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h index 91d70e535f..79bf30838a 100644 --- a/src/core/dbus-execute.h +++ b/src/core/dbus-execute.h @@ -63,7 +63,7 @@ " <property name=\"CPUSchedulingPolicy\" type=\"i\" access=\"read\"/>\n" \ " <property name=\"CPUSchedulingPriority\" type=\"i\" access=\"read\"/>\n" \ " <property name=\"CPUAffinity\" type=\"ay\" access=\"read\"/>\n" \ - " <property name=\"TimerSlackNS\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"TimerSlackNSec\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"CPUSchedulingResetOnFork\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"NonBlocking\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"StandardInput\" type=\"s\" access=\"read\"/>\n" \ @@ -92,8 +92,6 @@ " <property name=\"PrivateNetwork\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"SameProcessGroup\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"UtmpIdentifier\" type=\"s\" access=\"read\"/>\n" \ - " <property name=\"ControlGroupModify\" type=\"b\" access=\"read\"/>\n" \ - " <property name=\"ControlGroupPersistent\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"IgnoreSIGPIPE\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"NoNewPrivileges\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"SystemCallFilter\" type=\"au\" access=\"read\"/>\n" @@ -106,18 +104,4 @@ extern const BusProperty bus_exec_context_properties[]; #define BUS_EXEC_COMMAND_PROPERTY(name, command, indirect) \ { name, bus_execute_append_command, "a(sasbttttuii)", (command), (indirect), NULL } -int bus_execute_append_output(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_input(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_oom_score_adjust(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_nice(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_ioprio(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_cpu_sched_policy(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_cpu_sched_priority(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_affinity(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_timer_slack_nsec(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_capabilities(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_capability_bs(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_rlimits(DBusMessageIter *i, const char *property, void *data); int bus_execute_append_command(DBusMessageIter *u, const char *property, void *data); -int bus_execute_append_env_files(DBusMessageIter *i, const char *property, void *data); -int bus_execute_append_syscall_filter(DBusMessageIter *i, const char *property, void *data); diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c index 98ccfa62ec..4ab88d06c3 100644 --- a/src/core/dbus-job.c +++ b/src/core/dbus-job.c @@ -60,7 +60,7 @@ static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType); static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) { Job *j = data; DBusMessageIter sub; - char *p; + _cleanup_free_ char *p = NULL; assert(i); assert(property); @@ -75,12 +75,9 @@ static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *d if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) || !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) { - free(p); return -ENOMEM; } - free(p); - if (!dbus_message_iter_close_container(i, &sub)) return -ENOMEM; @@ -136,7 +133,7 @@ static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBu /* Be nice to gdbus and return introspection data for our mid-level paths */ if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { - char *introspection = NULL; + _cleanup_free_ char *introspection = NULL; FILE *f; Iterator i; size_t size; @@ -169,7 +166,6 @@ static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBu if (ferror(f)) { fclose(f); - free(introspection); goto oom; } @@ -179,12 +175,9 @@ static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBu goto oom; if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { - free(introspection); goto oom; } - free(introspection); - if (!bus_maybe_send_reply(connection, message, reply)) goto oom; @@ -261,55 +254,51 @@ static int job_send_message(Job *j, DBusMessage* (*new_message)(Job *j)) { } static DBusMessage* new_change_signal_message(Job *j) { - DBusMessage *m = NULL; - char *p = NULL; + _cleanup_free_ char *p = NULL; + DBusMessage *m; p = job_dbus_path(j); if (!p) - goto oom; + return NULL; if (j->sent_dbus_new_signal) { /* Send a properties changed signal */ m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES); if (!m) - goto oom; + return NULL; } else { /* Send a new signal */ m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew"); if (!m) - goto oom; + return NULL; if (!dbus_message_append_args(m, DBUS_TYPE_UINT32, &j->id, DBUS_TYPE_OBJECT_PATH, &p, DBUS_TYPE_STRING, &j->unit->id, - DBUS_TYPE_INVALID)) - goto oom; + DBUS_TYPE_INVALID)) { + dbus_message_unref(m); + return NULL; + } } return m; - -oom: - if (m) - dbus_message_unref(m); - free(p); - return NULL; } static DBusMessage* new_removed_signal_message(Job *j) { - DBusMessage *m = NULL; - char *p = NULL; + _cleanup_free_ char *p = NULL; + DBusMessage *m; const char *r; p = job_dbus_path(j); if (!p) - goto oom; + return NULL; m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved"); if (!m) - goto oom; + return NULL; r = job_result_to_string(j->result); @@ -318,16 +307,12 @@ static DBusMessage* new_removed_signal_message(Job *j) { DBUS_TYPE_OBJECT_PATH, &p, DBUS_TYPE_STRING, &j->unit->id, DBUS_TYPE_STRING, &r, - DBUS_TYPE_INVALID)) - goto oom; + DBUS_TYPE_INVALID)) { + dbus_message_unref(m); + return NULL; + } return m; - -oom: - if (m) - dbus_message_unref(m); - free(p); - return NULL; } void bus_job_send_change_signal(Job *j) { diff --git a/src/core/dbus-kill.c b/src/core/dbus-kill.c index 165f63074b..811adb1b5a 100644 --- a/src/core/dbus-kill.c +++ b/src/core/dbus-kill.c @@ -25,11 +25,83 @@ #include "dbus-kill.h" #include "dbus-common.h" -DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_kill_append_mode, kill_mode, KillMode); +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_kill_append_mode, kill_mode, KillMode); const BusProperty bus_kill_context_properties[] = { { "KillMode", bus_kill_append_mode, "s", offsetof(KillContext, kill_mode) }, { "KillSignal", bus_property_append_int, "i", offsetof(KillContext, kill_signal) }, { "SendSIGKILL", bus_property_append_bool, "b", offsetof(KillContext, send_sigkill) }, - { NULL, } + { "SendSIGHUP", bus_property_append_bool, "b", offsetof(KillContext, send_sighup) }, + {} }; + +int bus_kill_context_set_transient_property( + Unit *u, + KillContext *c, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + assert(u); + assert(c); + assert(name); + assert(i); + + if (streq(name, "KillMode")) { + const char *m; + KillMode k; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(i, &m); + + k = kill_mode_from_string(m); + if (k < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + c->kill_mode = k; + + unit_write_drop_in_private_format(u, mode, name, "KillMode=%s\n", kill_mode_to_string(k)); + } + + return 1; + + } else if (streq(name, "SendSIGHUP")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + if (mode != UNIT_CHECK) { + dbus_bool_t b; + + dbus_message_iter_get_basic(i, &b); + c->send_sighup = b; + + unit_write_drop_in_private_format(u, mode, name, "SendSIGHUP=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "SendSIGKILL")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + if (mode != UNIT_CHECK) { + dbus_bool_t b; + + dbus_message_iter_get_basic(i, &b); + c->send_sigkill = b; + + unit_write_drop_in_private_format(u, mode, name, "SendSIGKILL=%s\n", yes_no(b)); + } + + return 1; + + } + + return 0; +} diff --git a/src/core/dbus-kill.h b/src/core/dbus-kill.h index 238fbd36d6..7676d98e91 100644 --- a/src/core/dbus-kill.h +++ b/src/core/dbus-kill.h @@ -29,11 +29,9 @@ #define BUS_KILL_CONTEXT_INTERFACE \ " <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"KillSignal\" type=\"i\" access=\"read\"/>\n" \ - " <property name=\"SendSIGKILL\" type=\"b\" access=\"read\"/>\n" - -#define BUS_KILL_COMMAND_INTERFACE(name) \ - " <property name=\"" name "\" type=\"a(sasbttuii)\" access=\"read\"/>\n" + " <property name=\"SendSIGKILL\" type=\"b\" access=\"read\"/>\n" \ + " <property name=\"SendSIGHUP\" type=\"b\" access=\"read\"/>\n" extern const BusProperty bus_kill_context_properties[]; -int bus_kill_append_mode(DBusMessageIter *i, const char *property, void *data); +int bus_kill_context_set_transient_property(Unit *u, KillContext *c, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 56b02a1cf5..676a07ffa5 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -103,32 +103,6 @@ " <method name=\"ResetFailedUnit\">\n" \ " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ " </method>\n" \ - " <method name=\"SetUnitControlGroup\">\n" \ - " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ - " </method>\n" \ - " <method name=\"UnsetUnitControlGroup\">\n" \ - " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>" \ - " </method>\n" \ - " <method name=\"GetUnitControlGroupAttribute\">\n" \ - " <arg name=\"name\" 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=\"SetUnitControlGroupAttribute\">\n" \ - " <arg name=\"name\" 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=\"UnsetUnitControlGroupAttributes\">\n" \ - " <arg name=\"name\" 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" \ " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \ @@ -178,8 +152,8 @@ " <arg name=\"unset\" type=\"as\" direction=\"in\"/>\n" \ " <arg name=\"set\" type=\"as\" direction=\"in\"/>\n" \ " </method>\n" \ - " <method name=\"ListUnitFiles\">\n" \ - " <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \ + " <method name=\"ListUnitFiles\">\n" \ + " <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \ " </method>\n" \ " <method name=\"GetUnitFileState\">\n" \ " <arg name=\"file\" type=\"s\" direction=\"in\"/>\n" \ @@ -227,6 +201,25 @@ " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \ " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \ " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"SetDefaultTarget\">\n" \ + " <arg name=\"files\" type=\"as\" direction=\"in\"/>\n" \ + " <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"GetDefaultTarget\">\n" \ + " <arg name=\"name\" type=\"s\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"SetUnitProperties\">\n" \ + " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \ + " <arg name=\"properties\" type=\"a(sv)\" direction=\"in\"/>\n" \ + " </method>\n" \ + " <method name=\"StartTransientUnit\">\n" \ + " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"properties\" type=\"a(sv)\" direction=\"in\"/>\n" \ + " <arg name=\"aux\" type=\"a(sa(sv))\" direction=\"in\"/>\n" \ + " <arg name=\"job\" type=\"o\" direction=\"out\"/>\n" \ " </method>\n" #define BUS_MANAGER_INTERFACE_SIGNALS \ @@ -257,7 +250,10 @@ " <arg name=\"userspace\" type=\"t\"/>\n" \ " <arg name=\"total\" type=\"t\"/>\n" \ " </signal>" \ - " <signal name=\"UnitFilesChanged\"/>\n" + " <signal name=\"UnitFilesChanged\"/>\n" \ + " <signal name=\"Reloading\">\n" \ + " <arg name=\"active\" type=\"b\"/>\n" \ + " </signal>" #define BUS_MANAGER_INTERFACE_PROPERTIES_GENERAL \ " <property name=\"Version\" type=\"s\" access=\"read\"/>\n" \ @@ -275,6 +271,14 @@ " <property name=\"UserspaceTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"FinishTimestamp\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"FinishTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"GeneratorsStartTimestamp\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"GeneratorsStartTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"GeneratorsFinishTimestamp\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"GeneratorsFinishTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"UnitsLoadStartTimestamp\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"UnitsLoadStartTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"UnitsLoadFinishTimestamp\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"UnitsLoadFinishTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"LogLevel\" type=\"s\" access=\"readwrite\"/>\n" \ " <property name=\"LogTarget\" type=\"s\" access=\"readwrite\"/>\n" \ " <property name=\"NNames\" type=\"u\" access=\"read\"/>\n" \ @@ -286,8 +290,6 @@ " <property name=\"ConfirmSpawn\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"ShowStatus\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"UnitPath\" type=\"as\" access=\"read\"/>\n" \ - " <property name=\"ControlGroupHierarchy\" type=\"s\" access=\"read\"/>\n" \ - " <property name=\"DefaultControllers\" type=\"as\" access=\"read\"/>\n" \ " <property name=\"DefaultStandardOutput\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"DefaultStandardError\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"RuntimeWatchdogUSec\" type=\"t\" access=\"readwrite\"/>\n" \ @@ -384,7 +386,7 @@ static int bus_manager_set_log_target(DBusMessageIter *i, const char *property, } static int bus_manager_append_log_level(DBusMessageIter *i, const char *property, void *data) { - char *t; + _cleanup_free_ char *t = NULL; int r; assert(i); @@ -397,7 +399,6 @@ static int bus_manager_append_log_level(DBusMessageIter *i, const char *property if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) r = -ENOMEM; - free(t); return r; } @@ -580,6 +581,14 @@ static const BusProperty bus_manager_properties[] = { { "UserspaceTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, userspace_timestamp.monotonic) }, { "FinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.realtime) }, { "FinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, finish_timestamp.monotonic) }, + { "GeneratorsStartTimestamp", bus_property_append_uint64, "t", offsetof(Manager, generators_start_timestamp.realtime) }, + { "GeneratorsStartTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, generators_start_timestamp.monotonic) }, + { "GeneratorsFinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, generators_finish_timestamp.realtime) }, + { "GeneratorsFinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, generators_finish_timestamp.monotonic) }, + { "UnitsLoadStartTimestamp", bus_property_append_uint64, "t", offsetof(Manager, unitsload_start_timestamp.realtime) }, + { "UnitsLoadStartTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, unitsload_start_timestamp.monotonic) }, + { "UnitsLoadFinishTimestamp", bus_property_append_uint64, "t", offsetof(Manager, unitsload_finish_timestamp.realtime) }, + { "UnitsLoadFinishTimestampMonotonic", bus_property_append_uint64, "t", offsetof(Manager, unitsload_finish_timestamp.monotonic) }, { "LogLevel", bus_manager_append_log_level, "s", 0, false, bus_manager_set_log_level }, { "LogTarget", bus_manager_append_log_target, "s", 0, false, bus_manager_set_log_target }, { "NNames", bus_manager_append_n_names, "u", 0 }, @@ -591,8 +600,6 @@ static const BusProperty bus_manager_properties[] = { { "ConfirmSpawn", bus_property_append_bool, "b", offsetof(Manager, confirm_spawn) }, { "ShowStatus", bus_property_append_bool, "b", offsetof(Manager, show_status) }, { "UnitPath", bus_property_append_strv, "as", offsetof(Manager, lookup_paths.unit_path), true }, - { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_hierarchy), true }, - { "DefaultControllers", bus_property_append_strv, "as", offsetof(Manager, default_controllers), true }, { "DefaultStandardOutput", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_output) }, { "DefaultStandardError", bus_manager_append_exec_output, "s", offsetof(Manager, default_std_error) }, { "RuntimeWatchdogUSec", bus_property_append_usec, "t", offsetof(Manager, runtime_watchdog), false, bus_manager_set_runtime_watchdog_usec }, @@ -662,7 +669,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, DBUS_TYPE_INVALID)) return bus_send_error_reply(connection, message, &error, -EINVAL); - u = cgroup_unit_by_pid(m, (pid_t) pid); + u = manager_get_unit_by_pid(m, (pid_t) pid); if (!u) { dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "No unit for PID %lu is loaded.", (unsigned long) pid); return bus_send_error_reply(connection, message, &error, -ENOENT); @@ -875,151 +882,6 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, if (!reply) goto oom; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroup")) { - const char *name; - Unit *u; - DBusMessageIter iter; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); - - u = manager_get_unit(m, name); - if (!u) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); - - r = bus_unit_cgroup_set(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 (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroup")) { - const char *name; - Unit *u; - DBusMessageIter iter; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); - - u = manager_get_unit(m, name); - if (!u) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop"); - - 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 (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttribute")) { - const char *name; - Unit *u; - DBusMessageIter iter; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); - - u = manager_get_unit(m, name); - if (!u) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - 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); - - reply = dbus_message_new_method_return(message); - if (!reply) - goto oom; - - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttribute")) { - const char *name; - Unit *u; - DBusMessageIter iter; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); - - u = manager_get_unit(m, name); - if (!u) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop"); - - r = bus_unit_cgroup_attribute_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 (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttribute")) { - const char *name; - Unit *u; - DBusMessageIter iter; - _cleanup_strv_free_ char **list = NULL; - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); - - u = manager_get_unit(m, name); - if (!u) { - dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - return bus_send_error_reply(connection, message, &error, -ENOENT); - } - - 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); - - reply = dbus_message_new_method_return(message); - if (!reply) - goto oom; - - dbus_message_iter_init_append(reply, &iter); - if (bus_append_strv_iter(&iter, list) < 0) - goto oom; - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnits")) { DBusMessageIter iter, sub; Iterator i; @@ -1170,17 +1032,9 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, SELINUX_ACCESS_CHECK(connection, message, "status"); - s = BUS_CONNECTION_SUBSCRIBED(m, connection); - if (!s) { - s = set_new(string_hash_func, string_compare_func); - if (!s) - goto oom; - - if (!dbus_connection_set_data(connection, m->subscribed_data_slot, s, NULL)) { - set_free(s); - goto oom; - } - } + s = bus_acquire_subscribed(m, connection); + if (!s) + goto oom; client = strdup(bus_message_get_sender_with_fallback(message)); if (!client) @@ -1309,7 +1163,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, goto oom; } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { - char *introspection = NULL; + _cleanup_free_ char *introspection = NULL; FILE *f; Iterator i; Unit *u; @@ -1335,7 +1189,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, fputs(INTROSPECTION_BEGIN, f); HASHMAP_FOREACH_KEY(u, k, m->units, i) { - char *p; + _cleanup_free_ char *p = NULL; if (k != u->id) continue; @@ -1343,12 +1197,10 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, p = bus_path_escape(k); if (!p) { fclose(f); - free(introspection); goto oom; } fprintf(f, "<node name=\"unit/%s\"/>", p); - free(p); } HASHMAP_FOREACH(j, m->jobs, i) @@ -1358,7 +1210,6 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, if (ferror(f)) { fclose(f); - free(introspection); goto oom; } @@ -1368,12 +1219,8 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, goto oom; if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { - free(introspection); goto oom; } - - free(introspection); - } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Reload")) { SELINUX_ACCESS_CHECK(connection, message, "reload"); @@ -1728,7 +1575,8 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ReenableUnitFiles") || dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "LinkUnitFiles") || dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PresetUnitFiles") || - dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "MaskUnitFiles")) { + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "MaskUnitFiles") || + dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetDefaultTarget")) { char **l = NULL; DBusMessageIter iter; @@ -1771,6 +1619,8 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, carries_install_info = r; } else if (streq(member, "MaskUnitFiles")) r = unit_file_mask(scope, runtime, NULL, l, force, &changes, &n_changes); + else if (streq(member, "SetDefaultTarget")) + r = unit_file_set_default(scope, NULL, l[0], &changes, &n_changes); else assert_not_reached("Uh? Wrong method"); @@ -1838,6 +1688,111 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, if (!reply) goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetDefaultTarget")) { + UnitFileScope scope = m->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + _cleanup_free_ char *default_target = NULL; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + r = unit_file_get_default(scope, NULL, &default_target); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &default_target, DBUS_TYPE_INVALID)) { + goto oom; + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitProperties")) { + DBusMessageIter iter; + dbus_bool_t runtime; + const char *name; + Unit *u; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + if (bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true) < 0 || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + u = manager_get_unit(m, name); + if (!u) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); + + r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "StartTransientUnit")) { + const char *name, *smode; + DBusMessageIter iter; + JobMode mode; + UnitType t; + Unit *u; + + if (!dbus_message_iter_init(message, &iter)) + goto oom; + + if (bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true) < 0 || + bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &smode, true) < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + + t = unit_name_to_type(name); + if (t < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); + if (!unit_vtable[t]->can_transient) { + dbus_set_error(&error, DBUS_ERROR_INVALID_ARGS, "Unit type %s does not support transient units.", unit_type_to_string(t)); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + mode = job_mode_from_string(smode); + if (mode < 0) { + dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode); + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + r = manager_load_unit(m, name, NULL, NULL, &u); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); + + if (u->load_state != UNIT_NOT_FOUND || set_size(u->dependencies[UNIT_REFERENCED_BY]) > 0) { + dbus_set_error(&error, BUS_ERROR_UNIT_EXISTS, "Unit %s already exists.", name); + return bus_send_error_reply(connection, message, &error, -EEXIST); + } + + /* OK, the unit failed to load and is unreferenced, + * now let's fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + /* Set our properties */ + r = bus_unit_set_properties(u, &iter, UNIT_RUNTIME, false, &error); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + /* And load this stub fully */ + r = unit_load(u); + if (r < 0) + return bus_send_error_reply(connection, message, &error, r); + + manager_dispatch_load_queue(m); + + /* Finally, start it */ + return bus_unit_queue_job(connection, message, u, JOB_START, mode, false); + } else { const BusBoundProperties bps[] = { { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string }, diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c index 0fcceb500d..72e187063c 100644 --- a/src/core/dbus-mount.c +++ b/src/core/dbus-mount.c @@ -22,11 +22,12 @@ #include <errno.h> #include "dbus-unit.h" -#include "dbus-mount.h" -#include "dbus-kill.h" #include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-cgroup.h" #include "dbus-common.h" #include "selinux-access.h" +#include "dbus-mount.h" #define BUS_MOUNT_INTERFACE \ " <interface name=\"org.freedesktop.systemd1.Mount\">\n" \ @@ -35,12 +36,13 @@ " <property name=\"Options\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Type\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \ + BUS_UNIT_CGROUP_INTERFACE \ BUS_EXEC_COMMAND_INTERFACE("ExecMount") \ BUS_EXEC_COMMAND_INTERFACE("ExecUnmount") \ BUS_EXEC_COMMAND_INTERFACE("ExecRemount") \ BUS_EXEC_CONTEXT_INTERFACE \ BUS_KILL_CONTEXT_INTERFACE \ - BUS_UNIT_CGROUP_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \ @@ -156,11 +158,12 @@ DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMess Mount *m = MOUNT(u); const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Mount", bus_mount_properties, m }, - { "org.freedesktop.systemd1.Mount", bus_exec_context_properties, &m->exec_context }, - { "org.freedesktop.systemd1.Mount", bus_kill_context_properties, &m->kill_context }, - { "org.freedesktop.systemd1.Mount", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Mount", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Mount", bus_mount_properties, m }, + { "org.freedesktop.systemd1.Mount", bus_exec_context_properties, &m->exec_context }, + { "org.freedesktop.systemd1.Mount", bus_kill_context_properties, &m->kill_context }, + { "org.freedesktop.systemd1.Mount", bus_cgroup_context_properties, &m->cgroup_context }, { NULL, } }; @@ -168,3 +171,31 @@ DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMess return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps ); } + +int bus_mount_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Mount *m = MOUNT(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &m->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + return 0; +} + +int bus_mount_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-mount.h b/src/core/dbus-mount.h index 8597394373..f4ec8b1625 100644 --- a/src/core/dbus-mount.h +++ b/src/core/dbus-mount.h @@ -27,5 +27,8 @@ DBusHandlerResult bus_mount_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); +int bus_mount_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_mount_commit_properties(Unit *u); + extern const char bus_mount_interface[]; extern const char bus_mount_invalidating_properties[]; diff --git a/src/core/dbus-scope.c b/src/core/dbus-scope.c new file mode 100644 index 0000000000..783a969fb3 --- /dev/null +++ b/src/core/dbus-scope.c @@ -0,0 +1,189 @@ +/*-*- 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 <errno.h> + +#include "dbus-unit.h" +#include "dbus-common.h" +#include "dbus-cgroup.h" +#include "dbus-kill.h" +#include "selinux-access.h" +#include "dbus-scope.h" + +#define BUS_SCOPE_INTERFACE \ + " <interface name=\"org.freedesktop.systemd1.Scope\">\n" \ + BUS_UNIT_CGROUP_INTERFACE \ + " <property name=\"TimeoutStopUSec\" type=\"t\" access=\"read\"/>\n" \ + BUS_KILL_CONTEXT_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ + " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \ + " </interface>\n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>\n" \ + BUS_UNIT_INTERFACE \ + BUS_SCOPE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "</node>\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Scope\0" + +const char bus_scope_interface[] _introspect_("Scope") = BUS_SCOPE_INTERFACE; + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_scope_append_scope_result, scope_result, ScopeResult); + +static const BusProperty bus_scope_properties[] = { + { "TimeoutStopUSec", bus_property_append_usec, "t", offsetof(Scope, timeout_stop_usec) }, + { "Result", bus_scope_append_scope_result, "s", offsetof(Scope, result) }, + {} +}; + +DBusHandlerResult bus_scope_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Scope *s = SCOPE(u); + + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Scope", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Scope", bus_scope_properties, s }, + { "org.freedesktop.systemd1.Scope", bus_cgroup_context_properties, &s->cgroup_context }, + { "org.freedesktop.systemd1.Scope", bus_kill_context_properties, &s->kill_context }, + {} + }; + + SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status"); + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} + +static int bus_scope_set_transient_property( + Scope *s, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + int r; + + assert(name); + assert(s); + assert(i); + + if (streq(name, "PIDs")) { + DBusMessageIter sub; + unsigned n = 0; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_UINT32) + return -EINVAL; + + r = set_ensure_allocated(&s->pids, trivial_hash_func, trivial_compare_func); + if (r < 0) + return r; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) { + uint32_t pid; + + dbus_message_iter_get_basic(&sub, &pid); + + if (pid <= 1) + return -EINVAL; + + if (mode != UNIT_CHECK) { + r = set_put(s->pids, LONG_TO_PTR(pid)); + if (r < 0 && r != -EEXIST) + return r; + } + + dbus_message_iter_next(&sub); + n++; + } + + if (n <= 0) + return -EINVAL; + + return 1; + + } else if (streq(name, "TimeoutStopUSec")) { + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_UINT64) + return -EINVAL; + + if (mode != UNIT_CHECK) { + uint64_t t; + + dbus_message_iter_get_basic(i, &t); + + s->timeout_stop_usec = t; + + unit_write_drop_in_format(UNIT(s), mode, name, "[Scope]\nTimeoutStopSec=%lluus\n", (unsigned long long) t); + } + + return 1; + } + + return 0; +} + +int bus_scope_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Scope *s = SCOPE(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + if (u->load_state == UNIT_STUB) { + /* While we are created we still accept PIDs */ + + r = bus_scope_set_transient_property(s, name, i, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &s->kill_context, name, i, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_scope_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-scope.h b/src/core/dbus-scope.h new file mode 100644 index 0000000000..e6836f13f0 --- /dev/null +++ b/src/core/dbus-scope.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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 <dbus/dbus.h> + +#include "unit.h" + +DBusHandlerResult bus_scope_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +int bus_scope_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_scope_commit_properties(Unit *u); + +extern const char bus_scope_interface[]; diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index e06a5dce97..696c4462fe 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -21,12 +21,15 @@ #include <errno.h> +#include "strv.h" +#include "path-util.h" #include "dbus-unit.h" #include "dbus-execute.h" #include "dbus-kill.h" -#include "dbus-service.h" +#include "dbus-cgroup.h" #include "dbus-common.h" #include "selinux-access.h" +#include "dbus-service.h" #define BUS_SERVICE_INTERFACE \ " <interface name=\"org.freedesktop.systemd1.Service\">\n" \ @@ -35,13 +38,15 @@ " <property name=\"PIDFile\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"NotifyAccess\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"RestartUSec\" type=\"t\" access=\"read\"/>\n" \ - " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"TimeoutStartUSec\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"TimeoutStopUSec\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"WatchdogUSec\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"WatchdogTimestamp\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"WatchdogTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"StartLimitInterval\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"StartLimitBurst\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"StartLimitAction\" type=\"s\" access=\"readwrite\"/>\n" \ + BUS_UNIT_CGROUP_INTERFACE \ BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ BUS_EXEC_COMMAND_INTERFACE("ExecStart") \ BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ @@ -50,7 +55,7 @@ BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ BUS_EXEC_CONTEXT_INTERFACE \ BUS_KILL_CONTEXT_INTERFACE \ - BUS_UNIT_CGROUP_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ " <property name=\"PermissionsStartOnly\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"RootDirectoryStartOnly\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"RemainAfterExit\" type=\"b\" access=\"read\"/>\n" \ @@ -103,12 +108,12 @@ static DEFINE_BUS_PROPERTY_SET_ENUM(bus_service_set_start_limit_action, start_li static const BusProperty bus_exec_main_status_properties[] = { { "ExecMainStartTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, { "ExecMainStartTimestampMonotonic",bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, - { "ExecMainExitTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.realtime) }, - { "ExecMainExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(ExecStatus, start_timestamp.monotonic) }, + { "ExecMainExitTimestamp", bus_property_append_usec, "t", offsetof(ExecStatus, exit_timestamp.realtime) }, + { "ExecMainExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(ExecStatus, exit_timestamp.monotonic) }, { "ExecMainPID", bus_property_append_pid, "u", offsetof(ExecStatus, pid) }, { "ExecMainCode", bus_property_append_int, "i", offsetof(ExecStatus, code) }, { "ExecMainStatus", bus_property_append_int, "i", offsetof(ExecStatus, status) }, - { NULL, } + {} }; static const BusProperty bus_service_properties[] = { @@ -117,7 +122,6 @@ static const BusProperty bus_service_properties[] = { { "PIDFile", bus_property_append_string, "s", offsetof(Service, pid_file), true }, { "NotifyAccess", bus_service_append_notify_access, "s", offsetof(Service, notify_access) }, { "RestartUSec", bus_property_append_usec, "t", offsetof(Service, restart_usec) }, - { "TimeoutUSec", bus_property_append_usec, "t", offsetof(Service, timeout_start_usec) }, { "TimeoutStartUSec", bus_property_append_usec, "t", offsetof(Service, timeout_start_usec) }, { "TimeoutStopUSec", bus_property_append_usec, "t", offsetof(Service, timeout_stop_usec) }, { "WatchdogUSec", bus_property_append_usec, "t", offsetof(Service, watchdog_usec) }, @@ -141,7 +145,7 @@ static const BusProperty bus_service_properties[] = { { "BusName", bus_property_append_string, "s", offsetof(Service, bus_name), true }, { "StatusText", bus_property_append_string, "s", offsetof(Service, status_text), true }, { "Result", bus_service_append_service_result,"s", offsetof(Service, result) }, - { NULL, } + {} }; DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connection, DBusMessage *message) { @@ -149,15 +153,184 @@ DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *connectio const BusBoundProperties bps[] = { { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Service", bus_unit_cgroup_properties, u }, { "org.freedesktop.systemd1.Service", bus_service_properties, s }, { "org.freedesktop.systemd1.Service", bus_exec_context_properties, &s->exec_context }, { "org.freedesktop.systemd1.Service", bus_kill_context_properties, &s->kill_context }, + { "org.freedesktop.systemd1.Service", bus_cgroup_context_properties, &s->cgroup_context }, { "org.freedesktop.systemd1.Service", bus_exec_main_status_properties, &s->main_exec_status }, - { "org.freedesktop.systemd1.Service", bus_unit_cgroup_properties, u }, - { NULL, } + {} }; SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status"); return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); } + +static int bus_service_set_transient_property( + Service *s, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + int r; + + assert(name); + assert(s); + assert(i); + + if (streq(name, "RemainAfterExit")) { + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + if (mode != UNIT_CHECK) { + dbus_bool_t b; + + dbus_message_iter_get_basic(i, &b); + + s->remain_after_exit = b; + unit_write_drop_in_private_format(UNIT(s), mode, name, "RemainAfterExit=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "ExecStart")) { + DBusMessageIter sub; + unsigned n = 0; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + _cleanup_strv_free_ char **argv = NULL; + DBusMessageIter sub2; + dbus_bool_t ignore; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) < 0) + return -EINVAL; + + if (!path_is_absolute(path)) { + dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); + return -EINVAL; + } + + r = bus_parse_strv_iter(&sub2, &argv); + if (r < 0) + return r; + + dbus_message_iter_next(&sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, false) < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + ExecCommand *c; + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + c->path = strdup(path); + if (!c->path) { + free(c); + return -ENOMEM; + } + + c->argv = argv; + argv = NULL; + + c->ignore = ignore; + + path_kill_slashes(c->path); + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], c); + } + + n++; + dbus_message_iter_next(&sub); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + ExecCommand *c; + size_t size = 0; + + if (n == 0) { + exec_command_free_list(s->exec_command[SERVICE_EXEC_START]); + s->exec_command[SERVICE_EXEC_START] = NULL; + } + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("ExecStart=\n", f); + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + _cleanup_free_ char *a; + + a = strv_join_quoted(c->argv); + if (!a) + return -ENOMEM; + + fprintf(f, "ExecStart=%s@%s %s\n", + c->ignore ? "-" : "", + c->path, + a); + } + + fflush(f); + unit_write_drop_in_private(UNIT(s), mode, name, buf); + } + + return 1; + } + + return 0; +} + +int bus_service_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Service *s = SERVICE(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + if (u->transient && u->load_state == UNIT_STUB) { + /* This is a transient unit, let's load a little more */ + + r = bus_service_set_transient_property(s, name, i, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &s->kill_context, name, i, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_service_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h index 143aed7ae5..9b9f13701c 100644 --- a/src/core/dbus-service.h +++ b/src/core/dbus-service.h @@ -27,5 +27,8 @@ DBusHandlerResult bus_service_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); +int bus_service_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_service_commit_properties(Unit *u); + extern const char bus_service_interface[]; extern const char bus_service_invalidating_properties[]; diff --git a/src/core/dbus-slice.c b/src/core/dbus-slice.c new file mode 100644 index 0000000000..dac9fbdf5f --- /dev/null +++ b/src/core/dbus-slice.c @@ -0,0 +1,93 @@ +/*-*- 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 <errno.h> + +#include "dbus-unit.h" +#include "dbus-common.h" +#include "dbus-cgroup.h" +#include "selinux-access.h" +#include "dbus-slice.h" + +#define BUS_SLICE_INTERFACE \ + " <interface name=\"org.freedesktop.systemd1.Slice\">\n" \ + BUS_UNIT_CGROUP_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ + " </interface>\n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>\n" \ + BUS_UNIT_INTERFACE \ + BUS_SLICE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "</node>\n" + +#define INTERFACES_LIST \ + BUS_UNIT_INTERFACES_LIST \ + "org.freedesktop.systemd1.Slice\0" + +const char bus_slice_interface[] _introspect_("Slice") = BUS_SLICE_INTERFACE; + +DBusHandlerResult bus_slice_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { + Slice *s = SLICE(u); + + const BusBoundProperties bps[] = { + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Slice", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Slice", bus_cgroup_context_properties, &s->cgroup_context }, + {} + }; + + SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status"); + + return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); +} + +int bus_slice_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Slice *s = SLICE(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + return 0; +} + +int bus_slice_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-slice.h b/src/core/dbus-slice.h new file mode 100644 index 0000000000..c5ac473763 --- /dev/null +++ b/src/core/dbus-slice.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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 <dbus/dbus.h> + +#include "unit.h" + +DBusHandlerResult bus_slice_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); + +int bus_slice_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_slice_commit_properties(Unit *u); + +extern const char bus_slice_interface[]; diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index 77d98ea0fd..30c4b6302c 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -22,24 +22,26 @@ #include <errno.h> #include "dbus-unit.h" -#include "dbus-socket.h" #include "dbus-execute.h" #include "dbus-kill.h" +#include "dbus-cgroup.h" #include "dbus-common.h" #include "selinux-access.h" +#include "dbus-socket.h" #define BUS_SOCKET_INTERFACE \ " <interface name=\"org.freedesktop.systemd1.Socket\">\n" \ " <property name=\"BindIPv6Only\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"Backlog\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \ + BUS_UNIT_CGROUP_INTERFACE \ BUS_EXEC_COMMAND_INTERFACE("ExecStartPre") \ BUS_EXEC_COMMAND_INTERFACE("ExecStartPost") \ BUS_EXEC_COMMAND_INTERFACE("ExecStopPre") \ BUS_EXEC_COMMAND_INTERFACE("ExecStopPost") \ BUS_EXEC_CONTEXT_INTERFACE \ BUS_KILL_CONTEXT_INTERFACE \ - BUS_UNIT_CGROUP_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"BindToDevice\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \ @@ -65,6 +67,7 @@ " <property name=\"MessageQueueMessageSize\" type=\"x\" access=\"read\"/>\n" \ " <property name=\"Listen\" type=\"a(ss)\" access=\"read\"/>\n" \ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"ReusePort\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"SmackLabel\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"SmackLabelIPIn\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"SmackLabelIPOut\" type=\"s\" access=\"read\"/>\n" \ @@ -192,24 +195,54 @@ static const BusProperty bus_socket_properties[] = { { "MessageQueueMaxMessages", bus_property_append_long, "x", offsetof(Socket, mq_maxmsg) }, { "MessageQueueMessageSize", bus_property_append_long, "x", offsetof(Socket, mq_msgsize) }, { "Result", bus_socket_append_socket_result, "s", offsetof(Socket, result) }, + { "ReusePort", bus_property_append_bool, "b", offsetof(Socket, reuseport) }, { "SmackLabel", bus_property_append_string, "s", offsetof(Socket, smack), true }, { "SmackLabelIPIn", bus_property_append_string, "s", offsetof(Socket, smack_ip_in), true }, { "SmackLabelIPOut",bus_property_append_string, "s", offsetof(Socket, smack_ip_out), true }, - { NULL, } + {} }; DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { Socket *s = SOCKET(u); const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Socket", bus_socket_properties, s }, - { "org.freedesktop.systemd1.Socket", bus_exec_context_properties, &s->exec_context }, - { "org.freedesktop.systemd1.Socket", bus_kill_context_properties, &s->kill_context }, - { "org.freedesktop.systemd1.Socket", bus_unit_properties, u }, - { NULL, } + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Socket", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Socket", bus_socket_properties, s }, + { "org.freedesktop.systemd1.Socket", bus_exec_context_properties, &s->exec_context }, + { "org.freedesktop.systemd1.Socket", bus_kill_context_properties, &s->kill_context }, + { "org.freedesktop.systemd1.Socket", bus_cgroup_context_properties, &s->cgroup_context }, + {} }; SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status"); return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); } + +int bus_socket_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Socket *s = SOCKET(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + return 0; +} + +int bus_socket_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-socket.h b/src/core/dbus-socket.h index 5369b22e5e..eb035c1a94 100644 --- a/src/core/dbus-socket.h +++ b/src/core/dbus-socket.h @@ -27,5 +27,8 @@ DBusHandlerResult bus_socket_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); +int bus_socket_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_socket_commit_properties(Unit *u); + extern const char bus_socket_interface[]; extern const char bus_socket_invalidating_properties[]; diff --git a/src/core/dbus-swap.c b/src/core/dbus-swap.c index 2e99fba7db..06edfdcde4 100644 --- a/src/core/dbus-swap.c +++ b/src/core/dbus-swap.c @@ -23,22 +23,24 @@ #include <errno.h> #include "dbus-unit.h" -#include "dbus-swap.h" #include "dbus-execute.h" #include "dbus-kill.h" +#include "dbus-cgroup.h" #include "dbus-common.h" #include "selinux-access.h" +#include "dbus-swap.h" #define BUS_SWAP_INTERFACE \ " <interface name=\"org.freedesktop.systemd1.Swap\">\n" \ " <property name=\"What\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Priority\" type=\"i\" access=\"read\"/>\n" \ " <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \ + BUS_UNIT_CGROUP_INTERFACE \ BUS_EXEC_COMMAND_INTERFACE("ExecActivate") \ BUS_EXEC_COMMAND_INTERFACE("ExecDeactivate") \ BUS_EXEC_CONTEXT_INTERFACE \ BUS_KILL_CONTEXT_INTERFACE \ - BUS_UNIT_CGROUP_INTERFACE \ + BUS_CGROUP_CONTEXT_INTERFACE \ " <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"Result\" type=\"s\" access=\"read\"/>\n" \ " </interface>\n" @@ -93,6 +95,7 @@ static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_swap_append_swap_result, swap_result, static const BusProperty bus_swap_properties[] = { { "What", bus_property_append_string, "s", offsetof(Swap, what), true }, { "Priority", bus_swap_append_priority, "i", 0 }, + { "TimeoutUSec",bus_property_append_usec, "t", offsetof(Swap, timeout_usec)}, BUS_EXEC_COMMAND_PROPERTY("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), false), BUS_EXEC_COMMAND_PROPERTY("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), false), { "ControlPID", bus_property_append_pid, "u", offsetof(Swap, control_pid) }, @@ -103,11 +106,12 @@ static const BusProperty bus_swap_properties[] = { DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message) { Swap *s = SWAP(u); const BusBoundProperties bps[] = { - { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, - { "org.freedesktop.systemd1.Swap", bus_swap_properties, s }, - { "org.freedesktop.systemd1.Swap", bus_exec_context_properties, &s->exec_context }, - { "org.freedesktop.systemd1.Swap", bus_kill_context_properties, &s->kill_context }, - { "org.freedesktop.systemd1.Swap", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Unit", bus_unit_properties, u }, + { "org.freedesktop.systemd1.Swap", bus_unit_cgroup_properties, u }, + { "org.freedesktop.systemd1.Swap", bus_swap_properties, s }, + { "org.freedesktop.systemd1.Swap", bus_exec_context_properties, &s->exec_context }, + { "org.freedesktop.systemd1.Swap", bus_kill_context_properties, &s->kill_context }, + { "org.freedesktop.systemd1.Swap", bus_cgroup_context_properties, &s->cgroup_context }, { NULL, } }; @@ -115,3 +119,31 @@ DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessa return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps); } + +int bus_swap_set_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { + + Swap *s = SWAP(u); + int r; + + assert(name); + assert(u); + assert(i); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error); + if (r != 0) + return r; + + return 0; +} + +int bus_swap_commit_properties(Unit *u) { + assert(u); + + unit_realize_cgroup(u); + return 0; +} diff --git a/src/core/dbus-swap.h b/src/core/dbus-swap.h index 41fe4447ff..9b586a1ad2 100644 --- a/src/core/dbus-swap.h +++ b/src/core/dbus-swap.h @@ -28,5 +28,8 @@ DBusHandlerResult bus_swap_message_handler(Unit *u, DBusConnection *c, DBusMessage *message); +int bus_swap_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); +int bus_swap_commit_properties(Unit *u); + extern const char bus_swap_interface[]; extern const char bus_swap_invalidating_properties[]; diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 575f8eb36a..2ea59b2913 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -81,6 +81,22 @@ static int bus_unit_append_following(DBusMessageIter *i, const char *property, v return 0; } +static int bus_unit_append_slice(DBusMessageIter *i, const char *property, void *data) { + Unit *u = data; + const char *d; + + assert(i); + assert(property); + assert(u); + + d = strempty(unit_slice_name(u)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d)) + return -ENOMEM; + + return 0; +} + static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) { Unit *u; Iterator j; @@ -279,101 +295,69 @@ static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *d return 0; } -static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) { +static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) { Unit *u = data; - char *t; - CGroupBonding *cgb; - bool success; + dbus_bool_t b; assert(i); assert(property); assert(u); - cgb = unit_get_default_cgroup(u); - if (cgb) { - t = cgroup_bonding_to_string(cgb); - if (!t) - return -ENOMEM; - } else - t = (char*) ""; - - success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t); - - if (cgb) - free(t); - - return success ? 0 : -ENOMEM; -} - -static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - CGroupBonding *cgb; - DBusMessageIter sub; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) - return -ENOMEM; - - LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) { - _cleanup_free_ char *t = NULL; - bool success; - - t = cgroup_bonding_to_string(cgb); - if (!t) - return -ENOMEM; - - success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t); - if (!success) - return -ENOMEM; - } + b = unit_need_daemon_reload(u); - if (!dbus_message_iter_close_container(i, &sub)) + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) return -ENOMEM; return 0; } -static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - CGroupAttribute *a; - DBusMessageIter sub, sub2; - - if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub)) - return -ENOMEM; +static int bus_property_append_condition(DBusMessageIter *i, const char *property, void *data) { + Condition **cp = data; + Condition *c; + const char *name, *param; + dbus_bool_t trigger, negate; + dbus_int32_t state; + DBusMessageIter sub; - LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - _cleanup_free_ char *v = NULL; - bool success; + assert(i); + assert(property); + assert(cp); - if (a->semantics && a->semantics->map_write) - a->semantics->map_write(a->semantics, a->value, &v); + c = *cp; + assert(c); - success = - dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) && - dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) && - dbus_message_iter_close_container(&sub, &sub2); - if (!success) - return -ENOMEM; - } + name = condition_type_to_string(c->type); + param = c->parameter; + trigger = c->trigger; + negate = c->negate; + state = c->state; - if (!dbus_message_iter_close_container(i, &sub)) + if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &trigger) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &negate) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, ¶m) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_INT32, &state) || + !dbus_message_iter_close_container(i, &sub)) return -ENOMEM; return 0; } -static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) { - Unit *u = data; - dbus_bool_t b; +static int bus_property_append_condition_list(DBusMessageIter *i, const char *property, void *data) { + Condition **first = data, *c; + DBusMessageIter sub; assert(i); - assert(property); - assert(u); + assert(data); - b = unit_need_daemon_reload(u); + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sbbsi)", &sub)) + return -ENOMEM; - if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b)) + LIST_FOREACH(conditions, c, *first) + bus_property_append_condition(&sub, property, &c); + + if (!dbus_message_iter_close_container(i, &sub)) return -ENOMEM; return 0; @@ -471,86 +455,21 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn reply = dbus_message_new_method_return(message); if (!reply) goto oom; - - } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroup")) { + } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "SetProperties")) { DBusMessageIter iter; - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); + dbus_bool_t runtime; if (!dbus_message_iter_init(message, &iter)) goto oom; - r = bus_unit_cgroup_set(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), "UnsetControlGroup")) { - DBusMessageIter iter; - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop"); - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - 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), "GetControlGroupAttribute")) { - DBusMessageIter iter; - _cleanup_strv_free_ char **list = NULL; - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status"); - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_unit_cgroup_attribute_get(u, &iter, &list); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); - - reply = dbus_message_new_method_return(message); - if (!reply) - goto oom; - - dbus_message_iter_init_append(reply, &iter); - if (bus_append_strv_iter(&iter, list) < 0) - goto oom; - - } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttribute")) { - DBusMessageIter iter; + if (bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0) + return bus_send_error_reply(connection, message, NULL, -EINVAL); SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start"); - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_unit_cgroup_attribute_set(u, &iter); + r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, &error); 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), "UnsetControlGroupAttribute")) { - DBusMessageIter iter; - - SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop"); - - if (!dbus_message_iter_init(message, &iter)) - goto oom; - - r = bus_unit_cgroup_attribute_unset(u, &iter); - if (r < 0) - return bus_send_error_reply(connection, message, NULL, r); + return bus_send_error_reply(connection, message, &error, r); reply = dbus_message_new_method_return(message); if (!reply) @@ -701,8 +620,9 @@ const DBusObjectPathVTable bus_unit_vtable = { }; void bus_unit_send_change_signal(Unit *u) { - _cleanup_free_ char *p = NULL; _cleanup_dbus_message_unref_ DBusMessage *m = NULL; + _cleanup_free_ char *p = NULL; + int r; assert(u); @@ -720,8 +640,10 @@ void bus_unit_send_change_signal(Unit *u) { } p = unit_dbus_path(u); - if (!p) - goto oom; + if (!p) { + log_oom(); + return; + } if (u->sent_dbus_new_signal) { /* Send a properties changed signal. First for the @@ -734,19 +656,26 @@ void bus_unit_send_change_signal(Unit *u) { m = bus_properties_changed_new(p, UNIT_VTABLE(u)->bus_interface, UNIT_VTABLE(u)->bus_invalidating_properties); - if (!m) - goto oom; + if (!m) { + log_oom(); + return; + } - if (bus_broadcast(u->manager, m) < 0) - goto oom; + r = bus_broadcast(u->manager, m); + if (r < 0) { + log_error("Failed to broadcast change message: %s", strerror(-r)); + return; + } dbus_message_unref(m); } m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES); - if (!m) - goto oom; + if (!m) { + log_oom(); + return; + } } else { /* Send a new signal */ @@ -754,25 +683,27 @@ void bus_unit_send_change_signal(Unit *u) { m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew"); - if (!m) - goto oom; + if (!m) { + log_oom(); + return; + } if (!dbus_message_append_args(m, DBUS_TYPE_STRING, &u->id, DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_INVALID)) - goto oom; + DBUS_TYPE_INVALID)) { + log_oom(); + return; + } } - if (bus_broadcast(u->manager, m) < 0) - goto oom; + r = bus_broadcast(u->manager, m); + if (r < 0) { + log_error("Failed to broadcast UnitNew/PropertiesChanged message."); + return; + } u->sent_dbus_new_signal = true; - - return; - -oom: - log_oom(); } void bus_unit_send_removed_signal(Unit *u) { @@ -849,7 +780,7 @@ DBusHandlerResult bus_unit_queue_job( (type == JOB_START || type == JOB_RESTART || type == JOB_TRY_RESTART) ? "start" : type == JOB_STOP ? "stop" : "reload"); - if (type == JOB_STOP && u->load_state == UNIT_ERROR && unit_active_state(u) == UNIT_INACTIVE) { + if (type == JOB_STOP && (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && unit_active_state(u) == UNIT_INACTIVE) { dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id); return bus_send_error_reply(connection, message, &error, -EPERM); } @@ -897,428 +828,273 @@ oom: return DBUS_HANDLER_RESULT_NEED_MEMORY; } -static int parse_mode(DBusMessageIter *iter, bool *runtime, bool next) { - const char *mode; - int r; - - assert(iter); - assert(runtime); - - r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &mode, next); - if (r < 0) - return r; - - if (streq(mode, "runtime")) - *runtime = true; - else if (streq(mode, "persistent")) - *runtime = false; - else - return -EINVAL; - - return 0; -} +static int bus_unit_set_transient_property( + Unit *u, + const char *name, + DBusMessageIter *i, + UnitSetPropertiesMode mode, + DBusError *error) { -int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) { - _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); + assert(name); + assert(i); - if (!unit_get_exec_context(u)) - return -EINVAL; + if (streq(name, "Description")) { + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) + return -EINVAL; - r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return r; + if (mode != UNIT_CHECK) { + const char *description; - r = parse_mode(iter, &runtime, false); - if (r < 0) - return r; + dbus_message_iter_get_basic(i, &description); - r = cg_split_spec(name, &controller, &new_path); - if (r < 0) - return r; + r = unit_set_description(u, description); + if (r < 0) + return r; - if (!new_path) { - new_path = unit_default_cgroup_path(u); - if (!new_path) - return -ENOMEM; - } + unit_write_drop_in_format(u, mode, name, "[Unit]\nDescription=%s\n", description); + } - if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - return -EINVAL; + return 1; - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (b) { - if (streq(b->path, new_path)) - return 0; + } else if (streq(name, "Slice") && unit_get_cgroup_context(u)) { + const char *s; - if (b->essential) + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) return -EINVAL; - old_path = strdup(b->path); - if (!old_path) - return -ENOMEM; - } - - 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); - } - - 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); -} + dbus_message_iter_get_basic(i, &s); -int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) { - _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL; - const char *name; - CGroupAttribute *a, *n; - CGroupBonding *b; - bool runtime; - int r; + if (isempty(s)) { + if (mode != UNIT_CHECK) { + unit_ref_unset(&u->slice); + unit_remove_drop_in(u, mode, name); + } + } else { + Unit *slice; - assert(u); - assert(iter); + r = manager_load_unit(u->manager, s, NULL, error, &slice); + if (r < 0) + return r; - if (!unit_get_exec_context(u)) - return -EINVAL; + if (slice->type != UNIT_SLICE) + return -EINVAL; - r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return r; + if (mode != UNIT_CHECK) { + unit_ref_set(&u->slice, slice); + unit_write_drop_in_private_format(u, mode, name, "Slice=%s\n", s); + } + } - r = parse_mode(iter, &runtime, false); - if (r < 0) - return r; + return 1; + + } else if (streq(name, "Requires") || + streq(name, "RequiresOverridable") || + streq(name, "Requisite") || + streq(name, "RequisiteOverridable") || + streq(name, "Wants") || + streq(name, "BindsTo") || + streq(name, "Conflicts") || + streq(name, "Before") || + streq(name, "After") || + streq(name, "OnFailure") || + streq(name, "PropagatesReloadTo") || + streq(name, "ReloadPropagatedFrom") || + streq(name, "PartOf")) { + + UnitDependency d; + DBusMessageIter sub; + + d = unit_dependency_from_string(name); + if (d < 0) + return -EINVAL; - r = cg_split_spec(name, &controller, &path); - if (r < 0) - return r; + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(i) != DBUS_TYPE_STRING) + return -EINVAL; - if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - return -EINVAL; + dbus_message_iter_recurse(i, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + const char *other; - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (!b) - return -ENOENT; + dbus_message_iter_get_basic(&sub, &other); - if (path && !path_equal(path, b->path)) - return -ENOENT; + if (!unit_name_is_valid(other, false)) + return -EINVAL; - if (b->essential) - return -EINVAL; + if (mode != UNIT_CHECK) { + _cleanup_free_ char *label = NULL; - unit_remove_drop_in(u, runtime, controller); + r = unit_add_dependency_by_name(u, d, other, NULL, true); + if (r < 0) + return r; - /* Try to migrate the old group away */ - if (cg_pid_get_path(controller, 0, &target) >= 0) - cgroup_bonding_migrate_to(u->cgroup_bondings, target, false); + label = strjoin(name, "-", other, NULL); + if (!label) + return -ENOMEM; - cgroup_bonding_free(b, true); + unit_write_drop_in_format(u, mode, label, "[Unit]\n%s=%s\n", name, other); + } - /* Drop all attributes of this controller */ - LIST_FOREACH_SAFE(by_unit, a, n, u->cgroup_attributes) { - if (!streq(a->controller, controller)) - continue; + dbus_message_iter_next(&sub); + } - unit_remove_drop_in(u, runtime, a->name); - cgroup_attribute_free(a); + return 1; } return 0; } -int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result) { - _cleanup_free_ char *controller = NULL; - CGroupAttribute *a; - CGroupBonding *b; - const char *name; - char **l = NULL; +int bus_unit_set_properties( + Unit *u, + DBusMessageIter *iter, + UnitSetPropertiesMode mode, + bool commit, + DBusError *error) { + + bool for_real = false; + DBusMessageIter sub; + unsigned n = 0; int r; assert(u); assert(iter); - assert(_result); - if (!unit_get_exec_context(u)) - return -EINVAL; + if (u->transient) + mode &= UNIT_RUNTIME; - r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, false); - if (r < 0) - return r; + /* We iterate through the array twice. First run we just check + * if all passed data is valid, second run actually applies + * it. This is to implement transaction-like behaviour without + * actually providing full transactions. */ - 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_get_path(b->controller, b->path, name, &p); - if (r < 0) - return r; + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRUCT) + return -EINVAL; - r = read_full_file(p, &v, NULL); - if (r >= 0) { - /* Split on new lines */ - l = strv_split_newlines(v); - if (!l) - return -ENOMEM; + dbus_message_iter_recurse(iter, &sub); + for (;;) { + DBusMessageIter sub2, sub3; + const char *name; - *_result = l; - return 0; + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_INVALID) { - } - } + if (for_real || mode == UNIT_CHECK) + break; - /* If that didn't work, read our cached value */ - LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - - if (!cgroup_attribute_matches(a, controller, name)) + /* Reached EOF. Let's try again, and this time for realz... */ + dbus_message_iter_recurse(iter, &sub); + for_real = true; continue; - - r = strv_extend(&l, a->value); - if (r < 0) { - strv_free(l); - return r; } - } - - 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; + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) + return -EINVAL; - b = strjoin(buf, - "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL); + dbus_message_iter_recurse(&sub, &sub2); - if (!b) - return -ENOMEM; + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 || + dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) + return -EINVAL; - free(buf); - buf = b; + if (!UNIT_VTABLE(u)->bus_set_property) { + dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "Objects of this type do not support setting properties."); + return -ENOENT; } - } - - 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 **value; - const char *name; - - assert(u); - assert(iter); - - if (!unit_get_exec_context(u)) - return -EINVAL; - - 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; - - if (!dbus_message_iter_next(iter)) - return -EINVAL; - r = parse_mode(iter, &runtime, false); - if (r < 0) - return r; - - STRV_FOREACH(value, l) { - _cleanup_free_ char *v = NULL; - CGroupAttribute *a; - const CGroupSemantics *s; - - r = cgroup_semantics_find(NULL, name, *value, &v, &s); + dbus_message_iter_recurse(&sub2, &sub3); + r = UNIT_VTABLE(u)->bus_set_property(u, name, &sub3, for_real ? mode : UNIT_CHECK, error); + if (r == 0 && u->transient && u->load_state == UNIT_STUB) + r = bus_unit_set_transient_property(u, name, &sub3, for_real ? mode : UNIT_CHECK, error); if (r < 0) return r; - - if (s && !s->multiple && l[1]) - return -EINVAL; - - r = unit_add_cgroup_attribute(u, s, NULL, name, v ? v : *value, &a); - if (r < 0) - return r; - - if (r > 0) { - CGroupBonding *b; - - b = cgroup_bonding_find_list(u->cgroup_bondings, a->controller); - if (!b) { - /* Doesn't exist yet? Then let's add it */ - r = unit_add_cgroup_from_text(u, a->controller, false, &b); - if (r < 0) - return r; - - if (r > 0) { - cgroup_bonding_realize(b); - cgroup_bonding_migrate(b, u->cgroup_bondings); - } - } - - /* Make it count */ - cgroup_attribute_apply(a, u->cgroup_bondings); + if (r == 0) { + dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "Cannot set property %s, or unknown property.", name); + return -ENOENT; } - } - - r = update_attribute_drop_in(u, runtime, name); - if (r < 0) - return r; - - return 0; -} + dbus_message_iter_next(&sub); -int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) { - const char *name; - bool runtime; - int r; - - assert(u); - assert(iter); - - if (!unit_get_exec_context(u)) - return -EINVAL; - - r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true); - if (r < 0) - return r; - - r = parse_mode(iter, &runtime, false); - if (r < 0) - return r; + n += for_real; + } - cgroup_attribute_free_some(u->cgroup_attributes, NULL, name); - update_attribute_drop_in(u, runtime, name); + if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties) + UNIT_VTABLE(u)->bus_commit_properties(u); - return 0; + return n; } const BusProperty bus_unit_properties[] = { - { "Id", bus_property_append_string, "s", offsetof(Unit, id), true }, - { "Names", bus_unit_append_names, "as", 0 }, - { "Following", bus_unit_append_following, "s", 0 }, - { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true }, - { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true }, - { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true }, - { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true }, - { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true }, - { "BindsTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BINDS_TO]), true }, - { "PartOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PART_OF]), true }, - { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true }, - { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true }, - { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true }, - { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true }, - { "ConsistsOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), true }, - { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true }, - { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true }, - { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true }, - { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true }, - { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true }, - { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true }, - { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true }, - { "PropagatesReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), true }, - { "ReloadPropagatedFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), true }, - { "RequiresMountsFor", bus_property_append_strv, "as", offsetof(Unit, requires_mounts_for), true }, - { "Documentation", bus_property_append_strv, "as", offsetof(Unit, documentation), true }, - { "Description", bus_unit_append_description, "s", 0 }, - { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) }, - { "ActiveState", bus_unit_append_active_state, "s", 0 }, - { "SubState", bus_unit_append_sub_state, "s", 0 }, - { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true }, - { "SourcePath", bus_property_append_string, "s", offsetof(Unit, source_path), true }, - { "DropInPaths", bus_property_append_strv, "as", offsetof(Unit, dropin_paths), true }, - { "UnitFileState", bus_unit_append_file_state, "s", 0 }, - { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) }, - { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) }, - { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) }, - { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) }, - { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) }, - { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) }, - { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) }, - { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) }, - { "CanStart", bus_unit_append_can_start, "b", 0 }, - { "CanStop", bus_unit_append_can_stop, "b", 0 }, - { "CanReload", bus_unit_append_can_reload, "b", 0 }, - { "CanIsolate", bus_unit_append_can_isolate, "b", 0 }, - { "Job", bus_unit_append_job, "(uo)", 0 }, - { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) }, - { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) }, - { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) }, - { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) }, - { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) }, - { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) }, - { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) }, - { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) }, - { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 }, - { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) }, - { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) }, - { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) }, - { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) }, - { "LoadError", bus_unit_append_load_error, "(ss)", 0 }, - { NULL, } + { "Id", bus_property_append_string, "s", offsetof(Unit, id), true }, + { "Names", bus_unit_append_names, "as", 0 }, + { "Following", bus_unit_append_following, "s", 0 }, + { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true }, + { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true }, + { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true }, + { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true }, + { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true }, + { "BindsTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BINDS_TO]), true }, + { "PartOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PART_OF]), true }, + { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true }, + { "RequiredByOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true }, + { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true }, + { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true }, + { "ConsistsOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), true }, + { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true }, + { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true }, + { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true }, + { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true }, + { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true }, + { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true }, + { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true }, + { "PropagatesReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), true }, + { "ReloadPropagatedFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), true }, + { "RequiresMountsFor", bus_property_append_strv, "as", offsetof(Unit, requires_mounts_for), true }, + { "Documentation", bus_property_append_strv, "as", offsetof(Unit, documentation), true }, + { "Description", bus_unit_append_description, "s", 0 }, + { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) }, + { "ActiveState", bus_unit_append_active_state, "s", 0 }, + { "SubState", bus_unit_append_sub_state, "s", 0 }, + { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true }, + { "SourcePath", bus_property_append_string, "s", offsetof(Unit, source_path), true }, + { "DropInPaths", bus_property_append_strv, "as", offsetof(Unit, dropin_paths), true }, + { "UnitFileState", bus_unit_append_file_state, "s", 0 }, + { "InactiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) }, + { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) }, + { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) }, + { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) }, + { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) }, + { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) }, + { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) }, + { "InactiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) }, + { "CanStart", bus_unit_append_can_start, "b", 0 }, + { "CanStop", bus_unit_append_can_stop, "b", 0 }, + { "CanReload", bus_unit_append_can_reload, "b", 0 }, + { "CanIsolate", bus_unit_append_can_isolate, "b", 0 }, + { "Job", bus_unit_append_job, "(uo)", 0 }, + { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) }, + { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) }, + { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) }, + { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) }, + { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) }, + { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) }, + { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) }, + { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) }, + { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 }, + { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) }, + { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) }, + { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) }, + { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) }, + { "Conditions", bus_property_append_condition_list, "a(sbbsi)", offsetof(Unit, conditions) }, + { "LoadError", bus_unit_append_load_error, "(ss)", 0 }, + { "Transient", bus_property_append_bool, "b", offsetof(Unit, transient) }, + {} }; const BusProperty bus_unit_cgroup_properties[] = { - { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 }, - { "ControlGroups", bus_unit_append_cgroups, "as", 0 }, - { "ControlGroupAttributes", bus_unit_append_cgroup_attrs, "a(sss)", 0 }, - { NULL, } + { "Slice", bus_unit_append_slice, "s", 0 }, + { "ControlGroup", bus_property_append_string, "s", offsetof(Unit, cgroup_path), true }, + {} }; diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h index acd1ddbe78..3064cd552a 100644 --- a/src/core/dbus-unit.h +++ b/src/core/dbus-unit.h @@ -61,6 +61,10 @@ " <arg name=\"signal\" type=\"i\" direction=\"in\"/>\n" \ " </method>\n" \ " <method name=\"ResetFailed\"/>\n" \ + " <method name=\"SetProperties\">\n" \ + " <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n" \ + " <arg name=\"properties\" type=\"a(sv)\" direction=\"in\"/>\n" \ + " </method>\n" \ " <property name=\"Id\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Names\" type=\"as\" access=\"read\"/>\n" \ " <property name=\"Following\" type=\"s\" access=\"read\"/>\n" \ @@ -121,34 +125,14 @@ " <property name=\"ConditionTimestamp\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"ConditionTimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"ConditionResult\" type=\"b\" access=\"read\"/>\n" \ + " <property name=\"Conditions\" type=\"a(sbbsi)\" access=\"read\"/>\n" \ " <property name=\"LoadError\" type=\"(ss)\" access=\"read\"/>\n" \ + " <property name=\"Transient\" type=\"b\" access=\"read\"/>\n" \ " </interface>\n" #define BUS_UNIT_CGROUP_INTERFACE \ - " <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=\"SetControlGroup\">\n" \ - " <arg name=\"group\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ - " </method>\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=\"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=\"UnsetControlGroupAttribute\">\n" \ - " <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n" \ - " </method>\n" + " <property name=\"Slice\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"ControlGroup\" type=\"s\" access=\"read\"/>\n" #define BUS_UNIT_INTERFACES_LIST \ BUS_GENERIC_INTERFACES_LIST \ @@ -160,19 +144,9 @@ extern const BusProperty bus_unit_cgroup_properties[]; void bus_unit_send_change_signal(Unit *u); void bus_unit_send_removed_signal(Unit *u); -DBusHandlerResult bus_unit_queue_job( - DBusConnection *connection, - DBusMessage *message, - Unit *u, - JobType type, - JobMode mode, - bool reload_if_possible); - -int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter); -int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter); -int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result); -int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter); -int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter); +DBusHandlerResult bus_unit_queue_job(DBusConnection *connection, DBusMessage *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible); + +int bus_unit_set_properties(Unit *u, DBusMessageIter *i, UnitSetPropertiesMode mode, bool commit, DBusError *error); extern const DBusObjectPathVTable bus_unit_vtable; diff --git a/src/core/dbus.c b/src/core/dbus.c index 1272c938cf..aa3d93bf06 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -28,7 +28,6 @@ #include "dbus.h" #include "log.h" #include "strv.h" -#include "cgroup.h" #include "mkdir.h" #include "missing.h" #include "dbus-unit.h" @@ -453,7 +452,7 @@ static DBusHandlerResult system_bus_message_filter(DBusConnection *connection, D DBUS_TYPE_INVALID)) log_error("Failed to parse Released message: %s", bus_error_message(&error)); else - cgroup_notify_empty(m, cgroup); + manager_notify_cgroup_empty(m, cgroup); } dbus_error_free(&error); @@ -489,7 +488,7 @@ static DBusHandlerResult private_bus_message_filter(DBusConnection *connection, DBUS_TYPE_INVALID)) log_error("Failed to parse Released message: %s", bus_error_message(&error)); else - cgroup_notify_empty(m, cgroup); + manager_notify_cgroup_empty(m, cgroup); /* Forward the message to the system bus, so that user * instances are notified as well */ @@ -1136,19 +1135,19 @@ int bus_init(Manager *m, bool try_bus_connect) { if (set_ensure_allocated(&m->bus_connections, trivial_hash_func, trivial_compare_func) < 0 || set_ensure_allocated(&m->bus_connections_for_dispatch, trivial_hash_func, trivial_compare_func) < 0) - goto oom; + return log_oom(); if (m->name_data_slot < 0) if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot)) - goto oom; + return log_oom(); if (m->conn_data_slot < 0) if (!dbus_pending_call_allocate_data_slot(&m->conn_data_slot)) - goto oom; + return log_oom(); if (m->subscribed_data_slot < 0) if (!dbus_connection_allocate_data_slot(&m->subscribed_data_slot)) - goto oom; + return log_oom(); if (try_bus_connect) { if ((r = bus_init_system(m)) < 0 || @@ -1156,16 +1155,14 @@ int bus_init(Manager *m, bool try_bus_connect) { return r; } - if ((r = bus_init_private(m)) < 0) + r = bus_init_private(m); + if (r < 0) return r; return 0; -oom: - return log_oom(); } static void shutdown_connection(Manager *m, DBusConnection *c) { - Set *s; Job *j; Iterator i; @@ -1181,15 +1178,7 @@ static void shutdown_connection(Manager *m, DBusConnection *c) { set_remove(m->bus_connections, c); set_remove(m->bus_connections_for_dispatch, c); - - if ((s = BUS_CONNECTION_SUBSCRIBED(m, c))) { - char *t; - - while ((t = set_steal_first(s))) - free(t); - - set_free(s); - } + set_free_free(BUS_CONNECTION_SUBSCRIBED(m, c)); if (m->queued_message_connection == c) { m->queued_message_connection = NULL; @@ -1260,10 +1249,10 @@ void bus_done(Manager *m) { set_free(m->bus_connections_for_dispatch); if (m->name_data_slot >= 0) - dbus_pending_call_free_data_slot(&m->name_data_slot); + dbus_pending_call_free_data_slot(&m->name_data_slot); if (m->conn_data_slot >= 0) - dbus_pending_call_free_data_slot(&m->conn_data_slot); + dbus_pending_call_free_data_slot(&m->conn_data_slot); if (m->subscribed_data_slot >= 0) dbus_connection_free_data_slot(&m->subscribed_data_slot); @@ -1390,6 +1379,12 @@ bool bus_has_subscriber(Manager *m) { assert(m); + /* If we are reloading then we might not have deserialized the + subscribers yet, hence let's assume that there are some */ + + if (m->n_reloading > 0) + return true; + SET_FOREACH(c, m->bus_connections_for_dispatch, i) if (bus_connection_has_subscriber(m, c)) return true; @@ -1456,7 +1451,7 @@ void bus_broadcast_finished( usec_t userspace_usec, usec_t total_usec) { - DBusMessage *message; + _cleanup_dbus_message_unref_ DBusMessage *message = NULL; assert(m); @@ -1476,16 +1471,106 @@ void bus_broadcast_finished( DBUS_TYPE_UINT64, &total_usec, DBUS_TYPE_INVALID)) { log_oom(); - goto finish; + return; } if (bus_broadcast(m, message) < 0) { log_oom(); - goto finish; + return; } +} -finish: - if (message) - dbus_message_unref(message); +void bus_broadcast_reloading(Manager *m, bool active) { + + _cleanup_dbus_message_unref_ DBusMessage *message = NULL; + dbus_bool_t b = active; + + assert(m); + + message = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Reloading"); + if (!message) { + log_oom(); + return; + } + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + if (!dbus_message_append_args(message, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_INVALID)) { + log_oom(); + return; + } + + + if (bus_broadcast(m, message) < 0) { + log_oom(); + return; + } +} + +Set *bus_acquire_subscribed(Manager *m, DBusConnection *c) { + Set *s; + + assert(m); + assert(c); + + s = BUS_CONNECTION_SUBSCRIBED(m, c); + if (s) + return s; + + s = set_new(string_hash_func, string_compare_func); + if (!s) + return NULL; + + if (!dbus_connection_set_data(c, m->subscribed_data_slot, s, NULL)) { + set_free(s); + return NULL; + } + + return s; +} + +void bus_serialize(Manager *m, FILE *f) { + char *client; + Iterator i; + Set *s; + + assert(m); + assert(f); + + if (!m->api_bus) + return; + + s = BUS_CONNECTION_SUBSCRIBED(m, m->api_bus); + SET_FOREACH(client, s, i) + fprintf(f, "subscribed=%s\n", client); +} + +int bus_deserialize_item(Manager *m, const char *line) { + const char *e; + char *b; + Set *s; + + assert(m); + assert(line); + + if (!m->api_bus) + return 0; + + e = startswith(line, "subscribed="); + if (!e) + return 0; + + s = bus_acquire_subscribed(m, m->api_bus); + if (!s) + return -ENOMEM; + + b = strdup(e); + if (!b) + return -ENOMEM; + + set_consume(s, b); + + return 1; } diff --git a/src/core/dbus.h b/src/core/dbus.h index c7a058e198..6500cd7455 100644 --- a/src/core/dbus.h +++ b/src/core/dbus.h @@ -43,6 +43,12 @@ bool bus_connection_has_subscriber(Manager *m, DBusConnection *c); int bus_fdset_add_all(Manager *m, FDSet *fds); void bus_broadcast_finished(Manager *m, usec_t firmware_usec, usec_t loader_usec, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); +void bus_broadcast_reloading(Manager *m, bool active); + +Set *bus_acquire_subscribed(Manager *m, DBusConnection *c); + +void bus_serialize(Manager *m, FILE *f); +int bus_deserialize_item(Manager *m, const char *line); #define BUS_CONNECTION_SUBSCRIBED(m, c) dbus_connection_get_data((c), (m)->subscribed_data_slot) #define BUS_PENDING_CALL_NAME(m, p) dbus_pending_call_get_data((p), (m)->name_data_slot) diff --git a/src/core/execute.c b/src/core/execute.c index 3959ef9623..a53ef48ef8 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -55,7 +55,6 @@ #include "sd-messages.h" #include "ioprio.h" #include "securebits.h" -#include "cgroup.h" #include "namespace.h" #include "tcpwrap.h" #include "exit-status.h" @@ -67,8 +66,11 @@ #include "syscall-list.h" #include "env-util.h" #include "fileio.h" +#include "unit.h" +#include "async.h" #define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) +#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) /* This assumes there is a 'tty' group */ #define TTY_MODE 0620 @@ -758,24 +760,30 @@ static int setup_pam( * daemon. We do things this way to ensure that the main PID * of the daemon is the one we initially fork()ed. */ - if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) { + pam_code = pam_start(name, user, &conv, &handle); + if (pam_code != PAM_SUCCESS) { handle = NULL; goto fail; } - if (tty) - if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS) + if (tty) { + pam_code = pam_set_item(handle, PAM_TTY, tty); + if (pam_code != PAM_SUCCESS) goto fail; + } - if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS) + pam_code = pam_acct_mgmt(handle, PAM_SILENT); + if (pam_code != PAM_SUCCESS) goto fail; - if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS) + pam_code = pam_open_session(handle, PAM_SILENT); + if (pam_code != PAM_SUCCESS) goto fail; close_session = true; - if ((!(e = pam_getenvlist(handle)))) { + e = pam_getenvlist(handle); + if (!e) { pam_code = PAM_BUF_ERR; goto fail; } @@ -789,7 +797,8 @@ static int setup_pam( parent_pid = getpid(); - if ((pam_pid = fork()) < 0) + pam_pid = fork(); + if (pam_pid < 0) goto fail; if (pam_pid == 0) { @@ -840,9 +849,11 @@ static int setup_pam( } /* If our parent died we'll end the session */ - if (getppid() != parent_pid) - if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS) + if (getppid() != parent_pid) { + pam_code = pam_close_session(handle, PAM_DATA_SILENT); + if (pam_code != PAM_SUCCESS) goto child_finish; + } r = 0; @@ -977,6 +988,35 @@ static int apply_seccomp(uint32_t *syscall_filter) { return 0; } +static void do_idle_pipe_dance(int idle_pipe[4]) { + assert(idle_pipe); + + if (idle_pipe[1] >= 0) + close_nointr_nofail(idle_pipe[1]); + if (idle_pipe[2] >= 0) + close_nointr_nofail(idle_pipe[2]); + + if (idle_pipe[0] >= 0) { + int r; + + r = fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); + + if (idle_pipe[3] >= 0 && r == 0 /* timeout */) { + /* Signal systemd that we are bored and want to continue. */ + write(idle_pipe[3], "x", 1); + + /* Wait for systemd to react to the signal above. */ + fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC); + } + + close_nointr_nofail(idle_pipe[0]); + + } + + if (idle_pipe[3] >= 0) + close_nointr_nofail(idle_pipe[3]); +} + int exec_spawn(ExecCommand *command, char **argv, ExecContext *context, @@ -986,18 +1026,17 @@ int exec_spawn(ExecCommand *command, bool apply_chroot, bool apply_tty_stdin, bool confirm_spawn, - CGroupBonding *cgroup_bondings, - CGroupAttribute *cgroup_attributes, - const char *cgroup_suffix, + CGroupControllerMask cgroup_supported, + const char *cgroup_path, const char *unit_id, - int idle_pipe[2], + int idle_pipe[4], pid_t *ret) { + _cleanup_strv_free_ char **files_env = NULL; + int socket_fd; + char *line; pid_t pid; int r; - char *line; - int socket_fd; - _cleanup_strv_free_ char **files_env = NULL; assert(command); assert(context); @@ -1042,17 +1081,6 @@ int exec_spawn(ExecCommand *command, NULL); free(line); - r = cgroup_bonding_realize_list(cgroup_bondings); - if (r < 0) - return r; - - /* We must initialize the attributes in the parent, before we - fork, because we really need them initialized before making - the process a member of the group (which we do in both the - child and the parent), and we cannot really apply them twice - (due to 'append' style attributes) */ - cgroup_attribute_apply_list(cgroup_attributes, cgroup_bondings); - if (context->private_tmp && !context->tmp_dir && !context->var_tmp_dir) { r = setup_tmpdirs(&context->tmp_dir, &context->var_tmp_dir); if (r < 0) @@ -1072,7 +1100,6 @@ int exec_spawn(ExecCommand *command, _cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; unsigned n_env = 0; - bool set_access = false; /* child */ @@ -1096,14 +1123,8 @@ int exec_spawn(ExecCommand *command, goto fail_child; } - if (idle_pipe) { - if (idle_pipe[1] >= 0) - close_nointr_nofail(idle_pipe[1]); - if (idle_pipe[0] >= 0) { - fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); - close_nointr_nofail(idle_pipe[0]); - } - } + if (idle_pipe) + do_idle_pipe_dance(idle_pipe); /* Close sockets very early to make sure we don't * block init reexecution because it cannot bind its @@ -1185,8 +1206,8 @@ int exec_spawn(ExecCommand *command, goto fail_child; } - if (cgroup_bondings) { - err = cgroup_bonding_install_list(cgroup_bondings, 0, cgroup_suffix); + if (cgroup_path) { + err = cg_attach_everywhere(cgroup_supported, cgroup_path, 0); if (err < 0) { r = EXIT_CGROUP; goto fail_child; @@ -1269,37 +1290,24 @@ int exec_spawn(ExecCommand *command, goto fail_child; } } + } - if (cgroup_bondings && context->control_group_modify) { - err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid); - if (err >= 0) - err = cgroup_bonding_set_task_access_list( - cgroup_bondings, - 0644, - uid, - gid, - context->control_group_persistent); - if (err < 0) { - r = EXIT_CGROUP; - goto fail_child; - } - - set_access = true; +#ifdef HAVE_PAM + if (cgroup_path && context->user && context->pam_name) { + err = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, cgroup_path, 0644, uid, gid); + if (err < 0) { + r = EXIT_CGROUP; + goto fail_child; } - } - if (cgroup_bondings && !set_access && context->control_group_persistent >= 0) { - err = cgroup_bonding_set_task_access_list( - cgroup_bondings, - (mode_t) -1, - (uid_t) -1, - (uid_t) -1, - context->control_group_persistent); + + err = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, cgroup_path, 0755, uid, gid); if (err < 0) { r = EXIT_CGROUP; goto fail_child; } } +#endif if (apply_permissions) { err = enforce_groups(context, username, gid); @@ -1562,7 +1570,8 @@ int exec_spawn(ExecCommand *command, * outside of the cgroup) and in the parent (so that we can be * sure that when we kill the cgroup the process will be * killed too). */ - cgroup_bonding_install_list(cgroup_bondings, pid, cgroup_suffix); + if (cgroup_path) + cg_attach(SYSTEMD_CGROUP_CONTROLLER, cgroup_path, pid); exec_status_start(&command->exec_status, pid); @@ -1578,11 +1587,32 @@ void exec_context_init(ExecContext *c) { c->cpu_sched_policy = SCHED_OTHER; c->syslog_priority = LOG_DAEMON|LOG_INFO; c->syslog_level_prefix = true; - c->control_group_persistent = -1; c->ignore_sigpipe = true; c->timer_slack_nsec = (nsec_t) -1; } +static void *remove_tmpdir_thread(void *p) { + int r; + _cleanup_free_ char *dirp = p; + char *dir; + + assert(dirp); + + r = rm_rf_dangerous(dirp, false, true, false); + dir = dirname(dirp); + if (r < 0) + log_warning("Failed to remove content of temporary directory %s: %s", + dir, strerror(-r)); + else { + r = rmdir(dir); + if (r < 0) + log_warning("Failed to remove temporary directory %s: %s", + dir, strerror(-r)); + } + + return NULL; +} + void exec_context_tmp_dirs_done(ExecContext *c) { char* dirs[] = {c->tmp_dir ? c->tmp_dir : c->var_tmp_dir, c->tmp_dir ? c->var_tmp_dir : NULL, @@ -1590,22 +1620,8 @@ void exec_context_tmp_dirs_done(ExecContext *c) { char **dirp; for(dirp = dirs; *dirp; dirp++) { - char *dir; - int r; - - r = rm_rf_dangerous(*dirp, false, true, false); - dir = dirname(*dirp); - if (r < 0) - log_warning("Failed to remove content of temporary directory %s: %s", - dir, strerror(-r)); - else { - r = rmdir(dir); - if (r < 0) - log_warning("Failed to remove temporary directory %s: %s", - dir, strerror(-r)); - } - - free(*dirp); + log_debug("Spawning thread to nuke %s", *dirp); + asynchronous_job(remove_tmpdir_thread, *dirp); } c->tmp_dir = c->var_tmp_dir = NULL; @@ -1770,10 +1786,10 @@ int exec_context_load_environment(const ExecContext *c, char ***l) { strv_free(r); return k; - } + } /* Log invalid environment variables with filename */ - if (p) - p = strv_env_clean_log(p, pglob.gl_pathv[n]); + if (p) + p = strv_env_clean_log(p, pglob.gl_pathv[n]); if (r == NULL) r = p; @@ -1837,14 +1853,13 @@ static void strv_fprintf(FILE *f, char **l) { } void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { - char ** e; + char **e; unsigned i; assert(c); assert(f); - if (!prefix) - prefix = ""; + prefix = strempty(prefix); fprintf(f, "%sUMask: %04o\n" @@ -1852,8 +1867,6 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { "%sRootDirectory: %s\n" "%sNonBlocking: %s\n" "%sPrivateTmp: %s\n" - "%sControlGroupModify: %s\n" - "%sControlGroupPersistent: %s\n" "%sPrivateNetwork: %s\n" "%sIgnoreSIGPIPE: %s\n", prefix, c->umask, @@ -1861,8 +1874,6 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { prefix, c->root_directory ? c->root_directory : "/", prefix, yes_no(c->non_blocking), prefix, yes_no(c->private_tmp), - prefix, yes_no(c->control_group_modify), - prefix, yes_no(c->control_group_persistent), prefix, yes_no(c->private_network), prefix, yes_no(c->ignore_sigpipe)); diff --git a/src/core/execute.h b/src/core/execute.h index 15574dc97e..c1e9717dc8 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -33,14 +33,11 @@ typedef struct ExecContext ExecContext; #include <stdio.h> #include <sched.h> -struct CGroupBonding; -struct CGroupAttribute; - -typedef struct Unit Unit; - #include "list.h" #include "util.h" +typedef struct Unit Unit; + typedef enum ExecInput { EXEC_INPUT_NULL, EXEC_INPUT_TTY, @@ -148,9 +145,6 @@ struct ExecContext { bool no_new_privileges; - bool control_group_modify; - int control_group_persistent; - /* This is not exposed to the user but available * internally. We need it to make sure that whenever we spawn * /bin/mount it is run in the same process group as us so @@ -166,6 +160,8 @@ struct ExecContext { bool cpu_sched_set:1; }; +#include "cgroup.h" + int exec_spawn(ExecCommand *command, char **argv, ExecContext *context, @@ -175,9 +171,8 @@ int exec_spawn(ExecCommand *command, bool apply_chroot, bool apply_tty_stdin, bool confirm_spawn, - struct CGroupBonding *cgroup_bondings, - struct CGroupAttribute *cgroup_attributes, - const char *cgroup_suffix, + CGroupControllerMask cgroup_mask, + const char *cgroup_path, const char *unit_id, int pipe_fd[2], pid_t *ret); diff --git a/src/core/job.c b/src/core/job.c index d304a16d06..bf1d956908 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -35,7 +35,7 @@ #include "log.h" #include "dbus-job.h" #include "special.h" -#include "sync.h" +#include "async.h" #include "virt.h" JobBusClient* job_bus_client_new(DBusConnection *connection, const char *name) { @@ -1088,10 +1088,13 @@ void job_shutdown_magic(Job *j) { * asynchronous sync() would cause their exit to be * delayed. */ - if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) + if (j->type != JOB_START) return; - if (j->type != JOB_START) + if (j->unit->manager->running_as != SYSTEMD_SYSTEM) + return; + + if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) return; if (detect_container(NULL) > 0) diff --git a/src/core/kill.c b/src/core/kill.c index 0775653f73..ea947c23ae 100644 --- a/src/core/kill.c +++ b/src/core/kill.c @@ -29,6 +29,7 @@ void kill_context_init(KillContext *c) { c->kill_signal = SIGTERM; c->send_sigkill = true; + c->send_sighup = false; } void kill_context_dump(KillContext *c, FILE *f, const char *prefix) { @@ -40,10 +41,12 @@ void kill_context_dump(KillContext *c, FILE *f, const char *prefix) { fprintf(f, "%sKillMode: %s\n" "%sKillSignal: SIG%s\n" - "%sSendSIGKILL: %s\n", + "%sSendSIGKILL: %s\n" + "%sSendSIGHUP: %s\n", prefix, kill_mode_to_string(c->kill_mode), prefix, signal_to_string(c->kill_signal), - prefix, yes_no(c->send_sigkill)); + prefix, yes_no(c->send_sigkill), + prefix, yes_no(c->send_sighup)); } static const char* const kill_mode_table[_KILL_MODE_MAX] = { diff --git a/src/core/kill.h b/src/core/kill.h index 71a0513e84..41773f07ae 100644 --- a/src/core/kill.h +++ b/src/core/kill.h @@ -41,6 +41,7 @@ struct KillContext { KillMode kill_mode; int kill_signal; bool send_sigkill; + bool send_sighup; }; typedef enum KillWho { diff --git a/src/core/killall.c b/src/core/killall.c index a0f57455fb..e395050107 100644 --- a/src/core/killall.c +++ b/src/core/killall.c @@ -33,7 +33,7 @@ static bool ignore_proc(pid_t pid) { _cleanup_fclose_ FILE *f = NULL; - char c; + char c, *p; size_t count; uid_t uid; int r; @@ -50,7 +50,8 @@ static bool ignore_proc(pid_t pid) { if (uid != 0) return false; - f = fopen(procfs_file_alloca(pid, "cmdline"), "re"); + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); if (!f) return true; /* not really, but has the desired effect */ diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 4e1454ee6c..31fb7bcd3f 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -66,16 +66,6 @@ $1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQ $1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) $1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) $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_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) @@ -85,15 +75,28 @@ $1.MountFlags, config_parse_exec_mount_flags, 0, $1.TCPWrapName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.tcpwrap_name) $1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name) $1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe) -$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) -$1.ControlGroupModify, config_parse_bool, 0, offsetof($1, exec_context.control_group_modify) -$1.ControlGroupPersistent, config_parse_tristate, 0, offsetof($1, exec_context.control_group_persistent)' +$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id)' )m4_dnl m4_define(`KILL_CONTEXT_CONFIG_ITEMS', `$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill) +$1.SendSIGHUP, config_parse_bool, 0, offsetof($1, kill_context.send_sighup) $1.KillMode, config_parse_kill_mode, 0, offsetof($1, kill_context.kill_mode) $1.KillSignal, config_parse_kill_signal, 0, offsetof($1, kill_context.kill_signal)' )m4_dnl +m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', +`$1.Slice, config_parse_unit_slice, 0, 0 +$1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) +$1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context) +$1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) +$1.MemoryLimit, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +$1.DeviceAllow, config_parse_device_allow, 0, offsetof($1, cgroup_context) +$1.DevicePolicy, config_parse_device_policy, 0, offsetof($1, cgroup_context.device_policy) +$1.BlockIOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.blockio_accounting) +$1.BlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context) +$1.BlockIODeviceWeight, config_parse_blockio_device_weight, 0, offsetof($1, cgroup_context) +$1.BlockIOReadBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) +$1.BlockIOWriteBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context)' +)m4_dnl Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation) Unit.SourcePath, config_parse_path, 0, offsetof(Unit, source_path) @@ -113,7 +116,7 @@ Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAG Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0 -Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, offsetof(Unit, requires_mounts_for) +Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0 Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) @@ -172,6 +175,7 @@ Service.NotifyAccess, config_parse_notify_access, 0, Service.Sockets, config_parse_service_sockets, 0, 0 Service.FsckPassNo, config_parse_fsck_passno, 0, offsetof(Service, fsck_passno) EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Service)m4_dnl KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl m4_dnl Socket.ListenStream, config_parse_socket_listen, SOCKET_SOCKET, 0 @@ -214,6 +218,7 @@ Socket.SmackLabel, config_parse_string, 0, Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in) Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out) EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl KILL_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl m4_dnl Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what) @@ -224,6 +229,7 @@ Mount.FsckPassNo, config_parse_fsck_passno, 0, Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec) Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl m4_dnl Automount.Where, config_parse_path, 0, offsetof(Automount, where) @@ -233,6 +239,7 @@ Swap.What, config_parse_path, 0, Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) Swap.TimeoutSec, config_parse_sec, 0, offsetof(Swap, timeout_usec) EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl KILL_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl m4_dnl Timer.OnCalendar, config_parse_timer, 0, 0 @@ -251,6 +258,12 @@ Path.DirectoryNotEmpty, config_parse_path_spec, 0, Path.Unit, config_parse_trigger_unit, 0, 0 Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory) Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) +m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Slice)m4_dnl +m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl +Scope.TimeoutStopSec, config_parse_sec, 0, offsetof(Scope, timeout_stop_usec) m4_dnl The [Install] section is ignored here. Install.Alias, NULL, 0, 0 Install.WantedBy, NULL, 0, 0 diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index e2015ed58f..44920d6449 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -51,6 +51,7 @@ #include "path-util.h" #include "syscall-list.h" #include "env-util.h" +#include "cgroup.h" #ifndef HAVE_SYSV_COMPAT int config_parse_warn_compat(const char *unit, @@ -98,9 +99,12 @@ int config_parse_unit_deps(const char* unit, if (!t) return log_oom(); - k = unit_name_printf(u, t); - if (!k) - return log_oom(); + r = unit_name_printf(u, t, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", strerror(-r)); + continue; + } r = unit_add_dependency_by_name(u, d, k, NULL, true); if (r < 0) @@ -123,16 +127,17 @@ int config_parse_unit_string_printf(const char *unit, Unit *u = userdata; _cleanup_free_ char *k = NULL; + int r; assert(filename); assert(lvalue); assert(rvalue); assert(u); - k = unit_full_printf(u, rvalue); - if (!k) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + r = unit_full_printf(u, rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s, ignoring: %s", rvalue, strerror(-r)); return config_parse_string(unit, filename, line, section, lvalue, ltype, k ? k : rvalue, data, userdata); @@ -150,16 +155,17 @@ int config_parse_unit_strv_printf(const char *unit, Unit *u = userdata; _cleanup_free_ char *k = NULL; + int r; assert(filename); assert(lvalue); assert(rvalue); assert(u); - k = unit_full_printf(u, rvalue); - if (!k) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + r = unit_full_printf(u, rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s, ignoring: %s", rvalue, strerror(-r)); return config_parse_strv(unit, filename, line, section, lvalue, ltype, k ? k : rvalue, data, userdata); @@ -177,16 +183,17 @@ int config_parse_unit_path_printf(const char *unit, Unit *u = userdata; _cleanup_free_ char *k = NULL; + int r; assert(filename); assert(lvalue); assert(rvalue); assert(u); - k = unit_full_printf(u, rvalue); - if (!k) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + r = unit_full_printf(u, rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s, ignoring: %s", rvalue, strerror(-r)); return config_parse_path(unit, filename, line, section, lvalue, ltype, k ? k : rvalue, data, userdata); @@ -204,6 +211,7 @@ int config_parse_socket_listen(const char *unit, SocketPort *p, *tail; Socket *s; + int r; assert(filename); assert(lvalue); @@ -225,32 +233,31 @@ int config_parse_socket_listen(const char *unit, if (ltype != SOCKET_SOCKET) { p->type = ltype; - p->path = unit_full_printf(UNIT(s), rvalue); - if (!p->path) { + r = unit_full_printf(UNIT(s), rvalue, &p->path); + if (r < 0) { p->path = strdup(rvalue); if (!p->path) { free(p); return log_oom(); } else - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s, ignoring: %s", rvalue, strerror(-r)); } path_kill_slashes(p->path); } else if (streq(lvalue, "ListenNetlink")) { _cleanup_free_ char *k = NULL; - int r; p->type = SOCKET_SOCKET; - k = unit_full_printf(UNIT(s), rvalue); - if (!k) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + r = unit_full_printf(UNIT(s), rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s, ignoring: %s", rvalue, strerror(-r)); r = socket_address_parse_netlink(&p->address, k ? k : rvalue); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, + log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to parse address value, ignoring: %s", rvalue); free(p); return 0; @@ -258,17 +265,16 @@ int config_parse_socket_listen(const char *unit, } else { _cleanup_free_ char *k = NULL; - int r; p->type = SOCKET_SOCKET; - k = unit_full_printf(UNIT(s), rvalue); - if (!k) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + r = unit_full_printf(UNIT(s), rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s, ignoring: %s", rvalue, strerror(-r)); r = socket_address_parse(&p->address, k ? k : rvalue); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, + log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to parse address value, ignoring: %s", rvalue); free(p); return 0; @@ -996,58 +1002,6 @@ int config_parse_limit(const char *unit, return 0; } -int config_parse_unit_cgroup(const char *unit, - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - char *w; - size_t l; - char *state; - - if (isempty(rvalue)) { - /* An empty assignment resets the list */ - cgroup_bonding_free_list(u->cgroup_bondings, false); - u->cgroup_bondings = NULL; - return 0; - } - - FOREACH_WORD_QUOTED(w, l, rvalue, state) { - _cleanup_free_ char *t = NULL, *k = NULL, *ku = NULL; - int r; - - t = strndup(w, l); - if (!t) - return log_oom(); - - k = unit_full_printf(u, t); - if (!k) - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to resolve unit specifiers on %s. Ignoring.", - t); - - ku = cunescape(k ? k : t); - if (!ku) - return log_oom(); - - r = unit_add_cgroup_from_text(u, ku, true, NULL); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, - "Failed to parse cgroup value %s, ignoring: %s", - k, rvalue); - return 0; - } - } - - return 0; -} - #ifdef HAVE_SYSV_COMPAT int config_parse_sysv_priority(const char *unit, const char *filename, @@ -1281,11 +1235,12 @@ int config_parse_trigger_unit( return 0; } - p = unit_name_printf(u, rvalue); - if (!p) - return log_oom(); + r = unit_name_printf(u, rvalue, &p); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", strerror(-r)); - type = unit_name_to_type(p); + type = unit_name_to_type(p ?: rvalue); if (type < 0) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Unit type not valid, ignoring: %s", rvalue); @@ -1298,10 +1253,10 @@ int config_parse_trigger_unit( return 0; } - r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_TRIGGERS, p, NULL, true); + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_TRIGGERS, p ?: rvalue, NULL, true); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, -r, - "Failed to add trigger on %s, ignoring: %s", p, strerror(-r)); + "Failed to add trigger on %s, ignoring: %s", p ?: rvalue, strerror(-r)); return 0; } @@ -1322,6 +1277,7 @@ int config_parse_path_spec(const char *unit, PathSpec *s; PathType b; _cleanup_free_ char *k = NULL; + int r; assert(filename); assert(lvalue); @@ -1341,13 +1297,13 @@ int config_parse_path_spec(const char *unit, return 0; } - k = unit_full_printf(UNIT(p), rvalue); - if (!k) { + r = unit_full_printf(UNIT(p), rvalue, &k); + if (r < 0) { k = strdup(rvalue); if (!k) return log_oom(); else - log_syntax(unit, LOG_ERR, filename, line, EINVAL, + log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); } @@ -1395,19 +1351,20 @@ int config_parse_socket_service(const char *unit, dbus_error_init(&error); - p = unit_name_printf(UNIT(s), rvalue); - if (!p) - return log_oom(); + r = unit_name_printf(UNIT(s), rvalue, &p); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", rvalue); - if (!endswith(p, ".service")) { + if (!endswith(p ?: rvalue, ".service")) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Unit must be of type service, ignoring: %s", rvalue); return 0; } - r = manager_load_unit(UNIT(s)->manager, p, NULL, &error, &x); + r = manager_load_unit(UNIT(s)->manager, p ?: rvalue, NULL, &error, &x); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error(&error, r)); dbus_error_free(&error); @@ -1446,23 +1403,24 @@ int config_parse_service_sockets(const char *unit, if (!t) return log_oom(); - k = unit_name_printf(UNIT(s), t); - if (!k) - return log_oom(); + r = unit_name_printf(UNIT(s), t, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", strerror(-r)); - if (!endswith(k, ".socket")) { + if (!endswith(k ?: t, ".socket")) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Unit must be of type socket, ignoring: %s", k); + "Unit must be of type socket, ignoring: %s", k ?: t); continue; } - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k ?: t, NULL, true); if (r < 0) log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to add dependency on %s, ignoring: %s", - k, strerror(-r)); + k ?: t, strerror(-r)); - r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); + r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k ?: t, NULL, true); if (r < 0) return r; } @@ -1514,7 +1472,8 @@ int config_parse_unit_env_file(const char *unit, char ***env = data; Unit *u = userdata; - _cleanup_free_ char *s = NULL; + _cleanup_free_ char *n = NULL; + const char *s; int r; assert(filename); @@ -1529,10 +1488,12 @@ int config_parse_unit_env_file(const char *unit, return 0; } - s = unit_full_printf(u, rvalue); - if (!s) - return log_oom(); + r = unit_full_printf(u, rvalue, &n); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to resolve specifiers, ignoring: %s", rvalue); + s = n ?: rvalue; if (!path_is_absolute(s[0] == '-' ? s + 1 : s)) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Path '%s' is not absolute, ignoring.", s); @@ -1560,11 +1521,12 @@ int config_parse_environ(const char *unit, char*** env = data, *w, *state; size_t l; _cleanup_free_ char *k = NULL; + int r; assert(filename); assert(lvalue); assert(rvalue); - assert(u); + assert(data); if (isempty(rvalue)) { /* Empty assignment resets the list */ @@ -1573,7 +1535,15 @@ int config_parse_environ(const char *unit, return 0; } - k = unit_full_printf(u, rvalue); + if (u) { + r = unit_full_printf(u, rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", rvalue); + } + + if (!k) + k = strdup(rvalue); if (!k) return log_oom(); @@ -1645,6 +1615,7 @@ int config_parse_unit_condition_path(const char *unit, bool trigger, negate; Condition *c; _cleanup_free_ char *p = NULL; + int r; assert(filename); assert(lvalue); @@ -1666,9 +1637,15 @@ int config_parse_unit_condition_path(const char *unit, if (negate) rvalue++; - p = unit_full_printf(u, rvalue); - if (!p) - return log_oom(); + r = unit_full_printf(u, rvalue, &p); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", rvalue); + if (!p) { + p = strdup(rvalue); + if (!p) + return log_oom(); + } if (!path_is_absolute(p)) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, @@ -1699,6 +1676,7 @@ int config_parse_unit_condition_string(const char *unit, bool trigger, negate; Condition *c; _cleanup_free_ char *s = NULL; + int r; assert(filename); assert(lvalue); @@ -1720,9 +1698,15 @@ int config_parse_unit_condition_string(const char *unit, if (negate) rvalue++; - s = unit_full_printf(u, rvalue); - if (!s) - return log_oom(); + r = unit_full_printf(u, rvalue, &s); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve specifiers, ignoring: %s", rvalue); + if (!s) { + s = strdup(rvalue); + if (!s) + return log_oom(); + } c = condition_new(cond, s, trigger, negate); if (!c) @@ -1789,139 +1773,52 @@ int config_parse_unit_condition_null(const char *unit, DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); DEFINE_CONFIG_PARSE_ENUM(config_parse_start_limit_action, start_limit_action, StartLimitAction, "Failed to parse start limit action specifier"); -int config_parse_unit_cgroup_attr(const char *unit, - 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 *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { - Unit *u = data; - size_t a, b; - _cleanup_free_ char *n = NULL, *v = NULL; - const CGroupSemantics *s; - int r; + Unit *u = userdata; + char *state; + size_t l; + char *w; assert(filename); assert(lvalue); assert(rvalue); assert(data); - if (isempty(rvalue)) { - /* Empty assignment clears the list */ - cgroup_attribute_free_list(u->cgroup_attributes); - u->cgroup_attributes = NULL; - return 0; - } - - a = strcspn(rvalue, WHITESPACE); - b = strspn(rvalue + a, WHITESPACE); - if (a <= 0 || b <= 0) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Failed to parse cgroup attribute value, ignoring: %s", - rvalue); - return 0; - } - - n = strndup(rvalue, a); - if (!n) - return log_oom(); - - r = cgroup_semantics_find(NULL, n, rvalue + a + b, &v, &s); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, - "Failed to parse cgroup attribute value, ignoring: %s", - rvalue); - return 0; - } - - r = unit_add_cgroup_attribute(u, s, NULL, n, v ? v : rvalue + a + b, NULL); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, - "Failed to add cgroup attribute value, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -int config_parse_unit_cgroup_attr_pretty(const char *unit, - 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; + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + int r; + _cleanup_free_ char *n; - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); + n = strndup(w, l); + if (!n) + return log_oom(); - r = cgroup_semantics_find(NULL, lvalue, rvalue, &v, &s); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, - "Failed to parse cgroup attribute value, ignoring: %s", - rvalue); - return 0; - } else if (r == 0) { - log_syntax(unit, LOG_ERR, filename, line, ENOTSUP, - "Unknown or unsupported cgroup attribute %s, ignoring: %s", - lvalue, rvalue); - return 0; - } + if (!utf8_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Path is not UTF-8 clean, ignoring assignment: %s", rvalue); + continue; + } - r = unit_add_cgroup_attribute(u, s, NULL, NULL, v, NULL); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, -r, - "Failed to add cgroup attribute value, ignoring: %s", rvalue); - return 0; + r = unit_require_mounts_for(u, n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to add required mount for, ignoring: %s", rvalue); + continue; + } } return 0; } -int config_parse_unit_requires_mounts_for(const char *unit, - const char *filename, - unsigned line, - const char *section, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Unit *u = userdata; - int r; - bool empty_before; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - empty_before = !u->requires_mounts_for; - - r = config_parse_path_strv(unit, filename, line, section, lvalue, ltype, - rvalue, data, userdata); - - /* Make it easy to find units with requires_mounts set */ - if (empty_before && u->requires_mounts_for) - LIST_PREPEND(Unit, has_requires_mounts_for, u->manager->has_requires_mounts_for, u); - - return r; -} - int config_parse_documentation(const char *unit, const char *filename, unsigned line, @@ -2058,6 +1955,365 @@ int config_parse_syscall_filter(const char *unit, return 0; } +int config_parse_unit_slice( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata, *slice; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + if (!k) { + k = strdup(rvalue); + if (!k) + return log_oom(); + } + + r = manager_load_unit(u->manager, k, NULL, NULL, &slice); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to load slice unit %s. Ignoring.", k); + return 0; + } + + if (slice->type != UNIT_SLICE) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Slice unit %s is not a slice. Ignoring.", k); + return 0; + } + + unit_ref_set(&u->slice, slice); + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); + +int config_parse_cpu_shares( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + CGroupContext *c = data; + unsigned long lu; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + c->cpu_shares = 1024; + return 0; + } + + r = safe_atolu(rvalue, &lu); + if (r < 0 || lu <= 0) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "CPU shares '%s' invalid. Ignoring.", rvalue); + return 0; + } + + c->cpu_shares = lu; + return 0; +} + +int config_parse_memory_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + CGroupContext *c = data; + off_t bytes; + int r; + + if (isempty(rvalue)) { + c->memory_limit = (uint64_t) -1; + return 0; + } + + assert_cc(sizeof(uint64_t) == sizeof(off_t)); + + r = parse_bytes(rvalue, &bytes); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Memory limit '%s' invalid. Ignoring.", rvalue); + return 0; + } + + c->memory_limit = (uint64_t) bytes; + return 0; +} + +int config_parse_device_allow( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *path = NULL; + CGroupContext *c = data; + CGroupDeviceAllow *a; + const char *m; + size_t n; + + if (isempty(rvalue)) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + m = rvalue + n + strspn(rvalue + n, WHITESPACE); + if (isempty(m)) + m = "rwm"; + + if (!in_charset(m, "rwm")) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid device rights '%s'. Ignoring.", m); + return 0; + } + + a = new0(CGroupDeviceAllow, 1); + if (!a) + return log_oom(); + + a->path = path; + path = NULL; + a->r = !!strchr(m, 'r'); + a->w = !!strchr(m, 'w'); + a->m = !!strchr(m, 'm'); + + LIST_PREPEND(CGroupDeviceAllow, device_allow, c->device_allow, a); + return 0; +} + +int config_parse_blockio_weight( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + CGroupContext *c = data; + unsigned long lu; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + c->blockio_weight = 1000; + return 0; + } + + r = safe_atolu(rvalue, &lu); + if (r < 0 || lu < 10 || lu > 1000) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Block IO weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + c->blockio_weight = lu; + + return 0; +} + +int config_parse_blockio_device_weight( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *path = NULL; + CGroupBlockIODeviceWeight *w; + CGroupContext *c = data; + unsigned long lu; + const char *weight; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + weight = rvalue + n; + if (!*weight) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Expected block device and device weight. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + weight += strspn(weight, WHITESPACE); + r = safe_atolu(weight, &lu); + if (r < 0 || lu < 10 || lu > 1000) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Block IO weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + + w = new0(CGroupBlockIODeviceWeight, 1); + if (!w) + return log_oom(); + + w->path = path; + path = NULL; + + w->weight = lu; + + LIST_PREPEND(CGroupBlockIODeviceWeight, device_weights, c->blockio_device_weights, w); + return 0; +} + +int config_parse_blockio_bandwidth( + const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *path = NULL; + CGroupBlockIODeviceBandwidth *b; + CGroupContext *c = data; + const char *bandwidth; + off_t bytes; + bool read; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + read = streq("BlockIOReadBandwidth", lvalue); + + if (isempty(rvalue)) { + CGroupBlockIODeviceBandwidth *next; + + LIST_FOREACH_SAFE (device_bandwidths, b, next, c->blockio_device_bandwidths) + if (b->read == read) + cgroup_context_free_blockio_device_bandwidth(c, b); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + bandwidth = rvalue + n; + bandwidth += strspn(bandwidth, WHITESPACE); + + if (!*bandwidth) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Expected space separated pair of device node and bandwidth. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + r = parse_bytes(bandwidth, &bytes); + if (r < 0 || bytes <= 0) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Block IO Bandwidth '%s' invalid. Ignoring.", rvalue); + return 0; + } + + b = new0(CGroupBlockIODeviceBandwidth, 1); + if (!b) + return log_oom(); + + b->path = path; + path = NULL; + b->bandwidth = (uint64_t) bytes; + b->read = read; + + LIST_PREPEND(CGroupBlockIODeviceBandwidth, device_bandwidths, c->blockio_device_bandwidths, b); + + return 0; +} + #define FOLLOW_MAX 8 static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { @@ -2267,14 +2523,14 @@ static int load_from_path(Unit *u, const char *path) { if (null_or_empty(&st)) u->load_state = UNIT_MASKED; else { + u->load_state = UNIT_LOADED; + /* Now, parse the file contents */ r = config_parse(u->id, filename, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, (void*) load_fragment_gperf_lookup, false, true, u); if (r < 0) goto finish; - - u->load_state = UNIT_LOADED; } free(u->fragment_path); @@ -2417,7 +2673,6 @@ void unit_dump_config_items(FILE *f) { { config_parse_exec_secure_bits, "SECUREBITS" }, { config_parse_bounding_set, "BOUNDINGSET" }, { config_parse_limit, "LIMIT" }, - { config_parse_unit_cgroup, "CGROUP [...]" }, { config_parse_unit_deps, "UNIT [...]" }, { config_parse_exec, "PATH [ARGUMENT [...]]" }, { config_parse_service_type, "SERVICETYPE" }, @@ -2446,6 +2701,24 @@ void unit_dump_config_items(FILE *f) { { config_parse_unit_condition_path, "CONDITION" }, { config_parse_unit_condition_string, "CONDITION" }, { config_parse_unit_condition_null, "CONDITION" }, + { config_parse_unit_slice, "SLICE" }, + { config_parse_documentation, "URL" }, + { config_parse_service_timeout, "SECONDS" }, + { config_parse_start_limit_action, "ACTION" }, + { config_parse_set_status, "STATUS" }, + { config_parse_service_sockets, "SOCKETS" }, + { config_parse_fsck_passno, "PASSNO" }, + { config_parse_environ, "ENVIRON" }, + { config_parse_syscall_filter, "SYSCALL" }, + { config_parse_cpu_shares, "SHARES" }, + { config_parse_memory_limit, "LIMIT" }, + { config_parse_device_allow, "DEVICE" }, + { config_parse_device_policy, "POLICY" }, + { config_parse_blockio_bandwidth, "BANDWIDTH" }, + { config_parse_blockio_weight, "WEIGHT" }, + { config_parse_blockio_device_weight, "DEVICEWEIGHT" }, + { config_parse_long, "LONG" }, + { config_parse_socket_service, "SERVICE" }, }; const char *prev = NULL; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index ff7f22a6f0..90e5e3a5c9 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -55,7 +55,6 @@ int config_parse_exec_capabilities(const char *unit, const char *filename, unsig int config_parse_exec_secure_bits(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_bounding_set(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_limit(const char *unit, 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(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_sysv_priority(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_fsck_passno(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_kill_signal(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); @@ -73,11 +72,17 @@ int config_parse_unit_condition_null(const char *unit, const char *filename, uns int config_parse_kill_mode(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_notify_access(const char *unit, 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 *unit, 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 *unit, 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 *unit, 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 *unit, 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 *unit, 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 *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unit_slice(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_device_policy(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_device_allow(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_blockio_weight(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_blockio_device_weight(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_blockio_bandwidth(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c index d7113b9795..276deb9dc1 100644 --- a/src/core/locale-setup.c +++ b/src/core/locale-setup.c @@ -28,6 +28,8 @@ #include "macro.h" #include "virt.h" #include "fileio.h" +#include "strv.h" +#include "env-util.h" enum { /* We don't list LC_ALL here on purpose. People should be @@ -67,7 +69,8 @@ static const char * const variable_names[_VARIABLE_MAX] = { [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" }; -int locale_setup(void) { +int locale_setup(char ***environment) { + char **add; char *variables[_VARIABLE_MAX] = {}; int r = 0, i; @@ -117,27 +120,44 @@ int locale_setup(void) { log_warning("Failed to read /etc/locale.conf: %s", strerror(-r)); } - if (!variables[VARIABLE_LANG]) { - variables[VARIABLE_LANG] = strdup("C"); - if (!variables[VARIABLE_LANG]) { + add = NULL; + for (i = 0; i < _VARIABLE_MAX; i++) { + char *s; + + if (!variables[i]) + continue; + + s = strjoin(variable_names[i], "=", variables[i], NULL); + if (!s) { + r = -ENOMEM; + goto finish; + } + + if (strv_push(&add, s) < 0) { + free(s); r = -ENOMEM; goto finish; } } - for (i = 0; i < _VARIABLE_MAX; i++) { - if (variables[i]) { - if (setenv(variable_names[i], variables[i], 1) < 0) { - r = -errno; - goto finish; - } - } else - unsetenv(variable_names[i]); + if (!strv_isempty(add)) { + char **e; + + e = strv_env_merge(2, *environment, add); + if (!e) { + r = -ENOMEM; + goto finish; + } + + strv_free(*environment); + *environment = e; } r = 0; finish: + strv_free(add); + for (i = 0; i < _VARIABLE_MAX; i++) free(variables[i]); diff --git a/src/core/locale-setup.h b/src/core/locale-setup.h index 5a0f2f7888..62c654c37c 100644 --- a/src/core/locale-setup.h +++ b/src/core/locale-setup.h @@ -21,4 +21,4 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -int locale_setup(void); +int locale_setup(char ***environment); diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in index f77082c2db..89b48259ad 100644 --- a/src/core/macros.systemd.in +++ b/src/core/macros.systemd.in @@ -20,6 +20,7 @@ # RPM macros for packages installing systemd unit files %_unitdir @systemunitdir@ +%_userunitdir @userunitdir@ %_presetdir @systempresetdir@ %_udevhwdbdir @udevhwdbdir@ %_udevrulesdir @udevrulesdir@ @@ -71,3 +72,7 @@ fi \ %journal_catalog_update() \ @rootbindir@/journalctl --update-catalog >/dev/null 2>&1 || : \ %{nil} + +%tmpfiles_create() \ +@rootbindir@/systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ +%{nil} diff --git a/src/core/main.c b/src/core/main.c index 7fc06bea05..fe291f8410 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -64,12 +64,10 @@ #endif #include "hostname-setup.h" #include "machine-id-setup.h" -#include "locale-setup.h" #include "selinux-setup.h" #include "ima-setup.h" #include "fileio.h" #include "smack-setup.h" -#include "efivars.h" static enum { ACTION_RUN, @@ -89,12 +87,12 @@ static int arg_crash_chvt = -1; static bool arg_confirm_spawn = false; static bool arg_show_status = true; static bool arg_switched_root = false; -static char **arg_default_controllers = NULL; static char ***arg_join_controllers = NULL; static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; static usec_t arg_runtime_watchdog = 0; static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; +static char **arg_default_environment = NULL; static struct rlimit *arg_default_rlimit[RLIMIT_NLIMITS] = {}; static uint64_t arg_capability_bounding_set_drop = 0; static nsec_t arg_timer_slack_nsec = (nsec_t) -1; @@ -106,7 +104,10 @@ static void nop_handler(int sig) { _noreturn_ static void crash(int sig) { - if (!arg_dump_core) + if (getpid() != 1) + /* Pass this on immediately, if this is not PID 1 */ + raise(sig); + else if (!arg_dump_core) log_error("Caught <%s>, not dumping core.", signal_to_string(sig)); else { struct sigaction sa = { @@ -116,7 +117,7 @@ _noreturn_ static void crash(int sig) { pid_t pid; /* We want to wait for the core process, hence let's enable SIGCHLD */ - assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + sigaction(SIGCHLD, &sa, NULL); pid = fork(); if (pid < 0) @@ -128,7 +129,7 @@ _noreturn_ static void crash(int sig) { /* Enable default signal handler for core dump */ zero(sa); sa.sa_handler = SIG_DFL; - assert_se(sigaction(sig, &sa, NULL) == 0); + sigaction(sig, &sa, NULL); /* Don't limit the core dump size */ rl.rlim_cur = RLIM_INFINITY; @@ -136,7 +137,7 @@ _noreturn_ static void crash(int sig) { setrlimit(RLIMIT_CORE, &rl); /* Just to be sure... */ - assert_se(chdir("/") == 0); + chdir("/"); /* Raise the signal again */ raise(sig); @@ -347,32 +348,21 @@ static int parse_proc_cmdline_word(const char *word) { arg_default_std_error = r; } else if (startswith(word, "systemd.setenv=")) { _cleanup_free_ char *cenv = NULL; - char *eq; - int r; cenv = strdup(word + 15); if (!cenv) return -ENOMEM; - eq = strchr(cenv, '='); - if (!eq) { - if (!env_name_is_valid(cenv)) - log_warning("Environment variable name '%s' is not valid. Ignoring.", cenv); - else { - r = unsetenv(cenv); - if (r < 0) - log_warning("Unsetting environment variable '%s' failed, ignoring: %m", cenv); - } - } else { - if (!env_assignment_is_valid(cenv)) - log_warning("Environment variable assignment '%s' is not valid. Ignoring.", cenv); - else { - *eq = 0; - r = setenv(cenv, eq + 1, 1); - if (r < 0) - log_warning("Setting environment variable '%s=%s' failed, ignoring: %m", cenv, eq + 1); - } - } + if (env_assignment_is_valid(cenv)) { + char **env; + + env = strv_env_set(arg_default_environment, cenv); + if (env) + arg_default_environment = env; + else + log_warning("Setting environment variable '%s' failed, ignoring: %m", cenv); + } else + log_warning("Environment variable name '%s' is not valid. Ignoring.", cenv); } else if (startswith(word, "systemd.") || (in_initrd() && startswith(word, "rd.systemd."))) { @@ -411,7 +401,14 @@ static int parse_proc_cmdline_word(const char *word) { } else if (streq(word, "quiet")) arg_show_status = false; - else if (!in_initrd()) { + else if (streq(word, "debug")) { + /* Log to kmsg, the journal socket will fill up before the + * journal is started and tools running during that time + * will block with every log message for for 60 seconds, + * before they give up. */ + log_set_max_level(LOG_DEBUG); + log_set_target(LOG_TARGET_KMSG); + } else if (!in_initrd()) { unsigned i; /* SysV compatibility */ @@ -637,7 +634,6 @@ static int parse_config_file(void) { { "Manager", "ShowStatus", config_parse_bool, 0, &arg_show_status }, { "Manager", "CrashChVT", config_parse_int, 0, &arg_crash_chvt }, { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, - { "Manager", "DefaultControllers", config_parse_strv, 0, &arg_default_controllers }, { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, @@ -645,6 +641,7 @@ static int parse_config_file(void) { { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, { "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop }, { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, { "Manager", "DefaultLimitCPU", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_CPU]}, { "Manager", "DefaultLimitFSIZE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_FSIZE]}, { "Manager", "DefaultLimitDATA", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_DATA]}, @@ -1051,15 +1048,16 @@ static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching assert(_f); assert(_fds); - /* Make sure nothing is really destructed when we shut down */ - m->n_reloading ++; - r = manager_open_serialization(m, &f); if (r < 0) { log_error("Failed to create serialization file: %s", strerror(-r)); goto fail; } + /* Make sure nothing is really destructed when we shut down */ + m->n_reloading ++; + bus_broadcast_reloading(m, true); + fds = fdset_new(); if (!fds) { r = -ENOMEM; @@ -1140,25 +1138,6 @@ static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { return 0; } -static struct dual_timestamp* parse_initrd_timestamp(struct dual_timestamp *t) { - const char *e; - unsigned long long a, b; - - assert(t); - - e = getenv("RD_TIMESTAMP"); - if (!e) - return NULL; - - if (sscanf(e, "%llu %llu", &a, &b) != 2) - return NULL; - - t->realtime = (usec_t) a; - t->monotonic = (usec_t) b; - - return t; -} - static void test_mtab(void) { char *p; @@ -1239,8 +1218,6 @@ int main(int argc, char *argv[]) { dual_timestamp initrd_timestamp = { 0ULL, 0ULL }; dual_timestamp userspace_timestamp = { 0ULL, 0ULL }; dual_timestamp kernel_timestamp = { 0ULL, 0ULL }; - dual_timestamp firmware_timestamp = { 0ULL, 0ULL }; - dual_timestamp loader_timestamp = { 0ULL, 0ULL }; static char systemd[] = "systemd"; bool skip_setup = false; int j; @@ -1288,28 +1265,20 @@ int main(int argc, char *argv[]) { log_show_color(isatty(STDERR_FILENO) > 0); + /* Disable the umask logic */ + if (getpid() == 1) + umask(0); + if (getpid() == 1 && detect_container(NULL) <= 0) { -#ifdef ENABLE_EFI - efi_get_boot_timestamps(&userspace_timestamp, &firmware_timestamp, &loader_timestamp); -#endif + /* Running outside of a container as PID 1 */ arg_running_as = SYSTEMD_SYSTEM; make_null_stdio(); log_set_target(LOG_TARGET_KMSG); log_open(); - if (in_initrd()) { - char *rd_timestamp = NULL; - + if (in_initrd()) initrd_timestamp = userspace_timestamp; - asprintf(&rd_timestamp, "%llu %llu", - (unsigned long long) initrd_timestamp.realtime, - (unsigned long long) initrd_timestamp.monotonic); - if (rd_timestamp) { - setenv("RD_TIMESTAMP", rd_timestamp, 1); - free(rd_timestamp); - } - } if (!skip_setup) { mount_setup_early(); @@ -1345,10 +1314,10 @@ int main(int argc, char *argv[]) { */ hwclock_reset_timezone(); - /* Tell the kernel our time zone */ + /* Tell the kernel our timezone */ r = hwclock_set_timezone(NULL); if (r < 0) - log_error("Failed to set the kernel's time zone, ignoring: %s", strerror(-r)); + log_error("Failed to set the kernel's timezone, ignoring: %s", strerror(-r)); } } @@ -1408,7 +1377,6 @@ int main(int argc, char *argv[]) { /* Reset all signal handlers. */ assert_se(reset_all_signal_handlers() == 0); - /* If we are init, we can block sigkill. Yay. */ ignore_signals(SIGNALS_IGNORE, -1); if (parse_config_file() < 0) @@ -1474,59 +1442,12 @@ int main(int argc, char *argv[]) { if (serialization) assert_se(fdset_remove(fds, fileno(serialization)) >= 0); - /* Set up PATH unless it is already set */ - setenv("PATH", -#ifdef HAVE_SPLIT_USR - "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", -#else - "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", -#endif - arg_running_as == SYSTEMD_SYSTEM); - - if (arg_running_as == SYSTEMD_SYSTEM) { - /* Parse the data passed to us. We leave this - * variables set, but the manager later on will not - * pass them on to our children. */ - if (!in_initrd()) - parse_initrd_timestamp(&initrd_timestamp); - - /* Unset some environment variables passed in from the - * kernel that don't really make sense for us. */ - unsetenv("HOME"); - unsetenv("TERM"); - - /* When we are invoked by a shell, these might be set, - * but make little sense to pass on */ - unsetenv("PWD"); - unsetenv("SHLVL"); - unsetenv("_"); - - /* When we are invoked by a chroot-like tool such as - * nspawn, these might be set, but make little sense - * to pass on */ - unsetenv("USER"); - unsetenv("LOGNAME"); - - /* We suppress the socket activation env vars, as - * we'll try to match *any* open fd to units if - * possible. */ - unsetenv("LISTEN_FDS"); - unsetenv("LISTEN_PID"); - - /* All other variables are left as is, so that clients - * can still read them via /proc/1/environ */ - } - - /* Move out of the way, so that we won't block unmounts */ - assert_se(chdir("/") == 0); - - if (arg_running_as == SYSTEMD_SYSTEM) { + if (arg_running_as == SYSTEMD_SYSTEM) /* Become a session leader if we aren't one yet. */ setsid(); - /* Disable the umask logic */ - umask(0); - } + /* Move out of the way, so that we won't block unmounts */ + assert_se(chdir("/") == 0); /* Make sure D-Bus doesn't fiddle with the SIGPIPE handlers */ dbus_connection_set_change_sigpipe(FALSE); @@ -1565,8 +1486,6 @@ int main(int argc, char *argv[]) { log_debug(PACKAGE_STRING " running in user mode. (" SYSTEMD_FEATURES ")"); if (arg_running_as == SYSTEMD_SYSTEM && !skip_setup) { - locale_setup(); - if (arg_show_status || plymouth_running()) status_welcome(); @@ -1590,14 +1509,14 @@ int main(int argc, char *argv[]) { log_error("Failed to adjust timer slack: %m"); if (arg_capability_bounding_set_drop) { - r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true); + r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop); if (r < 0) { - log_error("Failed to drop capability bounding set: %s", strerror(-r)); + log_error("Failed to drop capability bounding set of usermode helpers: %s", strerror(-r)); goto finish; } - r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop); + r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true); if (r < 0) { - log_error("Failed to drop capability bounding set of usermode helpers: %s", strerror(-r)); + log_error("Failed to drop capability bounding set: %s", strerror(-r)); goto finish; } } @@ -1614,7 +1533,7 @@ int main(int argc, char *argv[]) { if (arg_running_as == SYSTEMD_SYSTEM) bump_rlimit_nofile(&saved_rlimit_nofile); - r = manager_new(arg_running_as, &m); + r = manager_new(arg_running_as, !!serialization, &m); if (r < 0) { log_error("Failed to allocate manager object: %s", strerror(-r)); goto finish; @@ -1627,14 +1546,12 @@ int main(int argc, char *argv[]) { m->shutdown_watchdog = arg_shutdown_watchdog; m->userspace_timestamp = userspace_timestamp; m->kernel_timestamp = kernel_timestamp; - m->firmware_timestamp = firmware_timestamp; - m->loader_timestamp = loader_timestamp; m->initrd_timestamp = initrd_timestamp; manager_set_default_rlimits(m, arg_default_rlimit); - if (arg_default_controllers) - manager_set_default_controllers(m, arg_default_controllers); + if (arg_default_environment) + manager_environment_add(m, arg_default_environment); manager_set_show_status(m, arg_show_status); @@ -1650,6 +1567,7 @@ int main(int argc, char *argv[]) { /* This will close all file descriptors that were opened, but * not claimed by any unit. */ fdset_free(fds); + fds = NULL; if (serialization) { fclose(serialization); @@ -1669,7 +1587,7 @@ int main(int argc, char *argv[]) { if (r < 0) { log_error("Failed to load default target: %s", bus_error(&error, r)); dbus_error_free(&error); - } else if (target->load_state == UNIT_ERROR) + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) log_error("Failed to load default target: %s", strerror(-target->load_error)); else if (target->load_state == UNIT_MASKED) log_error("Default target masked."); @@ -1682,7 +1600,7 @@ int main(int argc, char *argv[]) { log_error("Failed to load rescue target: %s", bus_error(&error, r)); dbus_error_free(&error); goto finish; - } else if (target->load_state == UNIT_ERROR) { + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { log_error("Failed to load rescue target: %s", strerror(-target->load_error)); goto finish; } else if (target->load_state == UNIT_MASKED) { @@ -1805,7 +1723,6 @@ finish: free(arg_default_rlimit[j]); free(arg_default_unit); - strv_free(arg_default_controllers); free_join_controllers(); dbus_shutdown(); @@ -1865,6 +1782,10 @@ finish: args[i++] = sfd; args[i++] = NULL; + /* do not pass along the environment we inherit from the kernel or initrd */ + if (switch_root_dir) + clearenv(); + assert(i <= args_size); execv(args[0], (char* const*) args); } @@ -1946,6 +1867,12 @@ finish: watchdog_close(true); } + /* Avoid the creation of new processes forked by the + * kernel; at this point, we will not listen to the + * signals anyway */ + if (detect_container(NULL) <= 0) + cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); + execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); free(env_block); log_error("Failed to execute shutdown binary, freezing: %m"); diff --git a/src/core/manager.c b/src/core/manager.c index c7f8f20a70..58dacdc8b5 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -55,7 +55,7 @@ #include "util.h" #include "mkdir.h" #include "ratelimit.h" -#include "cgroup.h" +#include "locale-setup.h" #include "mount-setup.h" #include "unit-name.h" #include "dbus-unit.h" @@ -70,11 +70,9 @@ #include "cgroup-util.h" #include "path-util.h" #include "audit-fd.h" +#include "boot-timestamps.h" #include "env-util.h" -/* As soon as 16 units are in our GC queue, make sure to run a gc sweep */ -#define GC_QUEUE_ENTRIES_MAX 16 - /* 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) @@ -276,6 +274,54 @@ static void manager_print_jobs_in_progress(Manager *m) { m->jobs_in_progress_iteration++; } +static int manager_watch_idle_pipe(Manager *m) { + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = &m->idle_pipe_watch, + }; + int r; + + if (m->idle_pipe_watch.type != WATCH_INVALID) + return 0; + + if (m->idle_pipe[2] < 0) + return 0; + + m->idle_pipe_watch.type = WATCH_IDLE_PIPE; + m->idle_pipe_watch.fd = m->idle_pipe[2]; + if (m->idle_pipe_watch.fd < 0) { + log_error("Failed to create timerfd: %m"); + r = -errno; + goto err; + } + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->idle_pipe_watch.fd, &ev) < 0) { + log_error("Failed to add idle_pipe fd to epoll: %m"); + r = -errno; + goto err; + } + + log_debug("Set up idle_pipe watch."); + + return 0; + +err: + if (m->idle_pipe_watch.fd >= 0) + close_nointr_nofail(m->idle_pipe_watch.fd); + watch_init(&m->idle_pipe_watch); + return r; +} + +static void manager_unwatch_idle_pipe(Manager *m) { + if (m->idle_pipe_watch.type != WATCH_IDLE_PIPE) + return; + + assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, m->idle_pipe_watch.fd, NULL) >= 0); + watch_init(&m->idle_pipe_watch); + + log_debug("Closed idle_pipe watch."); +} + static int manager_setup_time_change(Manager *m) { struct epoll_event ev = { .events = EPOLLIN, @@ -409,25 +455,34 @@ static int manager_setup_signals(Manager *m) { return 0; } -static void manager_strip_environment(Manager *m) { +static int manager_default_environment(Manager *m) { assert(m); - /* Remove variables from the inherited set that are part of - * the container interface: - * http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface */ - strv_remove_prefix(m->environment, "container="); - strv_remove_prefix(m->environment, "container_"); + if (m->running_as == SYSTEMD_SYSTEM) { + /* The system manager always starts with a clean + * environment for its children. It does not import + * the kernel or the parents exported variables. + * + * The initial passed environ is untouched to keep + * /proc/self/environ valid; it is used for tagging + * the init process inside containers. */ + m->environment = strv_new("PATH=" DEFAULT_PATH, + NULL); + + /* Import locale variables LC_*= from configuration */ + locale_setup(&m->environment); + } else + /* The user manager passes its own environment + * along to its children. */ + m->environment = strv_copy(environ); - /* Remove variables from the inherited set that are part of - * the initrd interface: - * http://www.freedesktop.org/wiki/Software/systemd/InitrdInterface */ - strv_remove_prefix(m->environment, "RD_"); + if (!m->environment) + return -ENOMEM; - /* Drop invalid entries */ - strv_env_clean(m->environment); + return 0; } -int manager_new(SystemdRunningAs running_as, Manager **_m) { +int manager_new(SystemdRunningAs running_as, bool reexecuting, Manager **_m) { Manager *m; int r = -ENOMEM; @@ -439,11 +494,16 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { if (!m) return -ENOMEM; +#ifdef ENABLE_EFI + if (detect_container(NULL) <= 0) + boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); +#endif + m->running_as = running_as; m->name_data_slot = m->conn_data_slot = m->subscribed_data_slot = -1; m->exit_code = _MANAGER_EXIT_CODE_INVALID; m->pin_cgroupfs_fd = -1; - m->idle_pipe[0] = m->idle_pipe[1] = -1; + m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1; watch_init(&m->signal_watch); watch_init(&m->mount_watch); @@ -455,18 +515,10 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { 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 */ - m->environment = strv_copy(environ); - if (!m->environment) + r = manager_default_environment(m); + if (r < 0) goto fail; - manager_strip_environment(m); - - if (running_as == SYSTEMD_SYSTEM) { - m->default_controllers = strv_new("cpu", NULL); - if (!m->default_controllers) - goto fail; - } - if (!(m->units = hashmap_new(string_hash_func, string_compare_func))) goto fail; @@ -476,10 +528,12 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func))) goto fail; - if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func))) + m->cgroup_unit = hashmap_new(string_hash_func, string_compare_func); + if (!m->cgroup_unit) goto fail; - if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func))) + m->watch_bus = hashmap_new(string_hash_func, string_compare_func); + if (!m->watch_bus) goto fail; m->epoll_fd = epoll_create1(EPOLL_CLOEXEC); @@ -503,9 +557,13 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) { goto fail; /* Try to connect to the busses, if possible. */ - r = bus_init(m, running_as != SYSTEMD_SYSTEM); - if (r < 0) - goto fail; + if ((running_as == SYSTEMD_USER && getenv("DBUS_SESSION_BUS_ADDRESS")) || + running_as == SYSTEMD_SYSTEM) { + r = bus_init(m, reexecuting || running_as != SYSTEMD_SYSTEM); + if (r < 0) + goto fail; + } else + log_debug("Skipping DBus session bus connection attempt - no DBUS_SESSION_BUS_ADDRESS set..."); m->taint_usr = dir_is_empty("/usr") > 0; @@ -600,12 +658,7 @@ static unsigned manager_dispatch_gc_queue(Manager *m) { assert(m); - if ((m->n_in_gc_queue < GC_QUEUE_ENTRIES_MAX) && - (m->gc_queue_timestamp <= 0 || - (m->gc_queue_timestamp + GC_QUEUE_USEC_MAX) > now(CLOCK_MONOTONIC))) - return 0; - - log_debug("Running GC..."); + /* log_debug("Running GC..."); */ m->gc_marker += _GC_OFFSET_MAX; if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX) @@ -632,7 +685,6 @@ static unsigned manager_dispatch_gc_queue(Manager *m) { } m->n_in_gc_queue = 0; - m->gc_queue_timestamp = 0; return n; } @@ -661,6 +713,11 @@ static void manager_clear_jobs_and_units(Manager *m) { m->n_running_jobs = 0; } +static void close_idle_pipe(Manager *m) { + close_pipe(m->idle_pipe); + close_pipe(m->idle_pipe + 2); +} + void manager_free(Manager *m) { UnitType c; int i; @@ -702,12 +759,10 @@ void manager_free(Manager *m) { lookup_paths_free(&m->lookup_paths); strv_free(m->environment); - strv_free(m->default_controllers); - - hashmap_free(m->cgroup_bondings); + hashmap_free(m->cgroup_unit); set_free_free(m->unit_path_cache); - close_pipe(m->idle_pipe); + close_idle_pipe(m); free(m->switch_root); free(m->switch_root_init); @@ -715,6 +770,9 @@ void manager_free(Manager *m) { for (i = 0; i < RLIMIT_NLIMITS; i++) free(m->rlimit[i]); + assert(hashmap_isempty(m->units_requiring_mounts_for)); + hashmap_free(m->units_requiring_mounts_for); + free(m); } @@ -727,9 +785,11 @@ int manager_enumerate(Manager *m) { /* Let's ask every type to load all units from disk/kernel * that it might know */ for (c = 0; c < _UNIT_TYPE_MAX; c++) - if (unit_vtable[c]->enumerate) - if ((q = unit_vtable[c]->enumerate(m)) < 0) + if (unit_vtable[c]->enumerate) { + q = unit_vtable[c]->enumerate(m); + if (q < 0) r = q; + } manager_dispatch_load_queue(m); return r; @@ -820,7 +880,9 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { assert(m); + dual_timestamp_get(&m->generators_start_timestamp); manager_run_generators(m); + dual_timestamp_get(&m->generators_finish_timestamp); r = lookup_paths_init( &m->lookup_paths, m->running_as, true, @@ -839,7 +901,9 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { m->n_reloading ++; /* First, enumerate what we can from all config files */ + dual_timestamp_get(&m->unitsload_start_timestamp); r = manager_enumerate(m); + dual_timestamp_get(&m->unitsload_finish_timestamp); /* Second, deserialize if there is something to deserialize */ if (serialization) { @@ -866,6 +930,11 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { if (serialization) { assert(m->n_reloading > 0); m->n_reloading --; + + /* Let's wait for the UnitNew/JobNew messages being + * sent, before we notify that the reload is + * finished */ + m->send_reloading_done = true; } return r; @@ -987,7 +1056,13 @@ unsigned manager_dispatch_load_queue(Manager *m) { return n; } -int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) { +int manager_load_unit_prepare( + Manager *m, + const char *name, + const char *path, + DBusError *e, + Unit **_ret) { + Unit *ret; UnitType t; int r; @@ -1031,7 +1106,8 @@ int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DB } } - if ((r = unit_add_name(ret, name)) < 0) { + r = unit_add_name(ret, name); + if (r < 0) { unit_free(ret); return r; } @@ -1046,7 +1122,13 @@ int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DB return 0; } -int manager_load_unit(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret) { +int manager_load_unit( + Manager *m, + const char *name, + const char *path, + DBusError *e, + Unit **_ret) { + int r; assert(m); @@ -1122,6 +1204,9 @@ unsigned manager_dispatch_run_queue(Manager *m) { if (m->n_running_jobs > 0) manager_watch_jobs_in_progress(m); + if (m->n_on_console > 0) + manager_watch_idle_pipe(m); + return n; } @@ -1152,6 +1237,13 @@ unsigned manager_dispatch_dbus_queue(Manager *m) { } m->dispatching_dbus_queue = false; + + if (m->send_reloading_done) { + m->send_reloading_done = false; + + bus_broadcast_reloading(m, false); + } + return n; } @@ -1205,7 +1297,7 @@ static int manager_process_notify_fd(Manager *m) { u = hashmap_get(m->watch_pids, LONG_TO_PTR(ucred->pid)); if (!u) { - u = cgroup_unit_by_pid(m, ucred->pid); + u = manager_get_unit_by_pid(m, ucred->pid); if (!u) { log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid); continue; @@ -1270,7 +1362,7 @@ static int manager_dispatch_sigchld(Manager *m) { /* And now figure out the unit this belongs to */ u = hashmap_get(m->watch_pids, LONG_TO_PTR(si.si_pid)); if (!u) - u = cgroup_unit_by_pid(m, si.si_pid); + u = manager_get_unit_by_pid(m, si.si_pid); /* And now, we actually reap the zombie. */ if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { @@ -1372,7 +1464,7 @@ static int manager_process_signal_fd(Manager *m) { case SIGINT: if (m->running_as == SYSTEMD_SYSTEM) { - manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE); + manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); break; } @@ -1668,6 +1760,14 @@ static int process_event(Manager *m, struct epoll_event *ev) { break; } + case WATCH_IDLE_PIPE: { + m->no_console_output = true; + + manager_unwatch_idle_pipe(m); + close_idle_pipe(m); + break; + } + default: log_error("event type=%i", w->type); assert_not_reached("Unknown epoll event type."); @@ -1714,16 +1814,19 @@ int manager_loop(Manager *m) { if (manager_dispatch_load_queue(m) > 0) continue; - if (manager_dispatch_run_queue(m) > 0) + if (manager_dispatch_gc_queue(m) > 0) continue; - if (bus_dispatch(m) > 0) + if (manager_dispatch_cleanup_queue(m) > 0) continue; - if (manager_dispatch_cleanup_queue(m) > 0) + if (manager_dispatch_cgroup_queue(m) > 0) continue; - if (manager_dispatch_gc_queue(m) > 0) + if (manager_dispatch_run_queue(m) > 0) + continue; + + if (bus_dispatch(m) > 0) continue; if (manager_dispatch_dbus_queue(m) > 0) @@ -1761,7 +1864,7 @@ int manager_loop(Manager *m) { } int manager_load_unit_from_dbus_path(Manager *m, const char *s, DBusError *e, Unit **_u) { - char *n; + _cleanup_free_ char *n = NULL; Unit *u; int r; @@ -1769,16 +1872,11 @@ int manager_load_unit_from_dbus_path(Manager *m, const char *s, DBusError *e, Un assert(s); assert(_u); - if (!startswith(s, "/org/freedesktop/systemd1/unit/")) - return -EINVAL; - - n = bus_path_unescape(s+31); - if (!n) - return -ENOMEM; + r = unit_name_from_dbus_path(s, &n); + if (r < 0) + return r; r = manager_load_unit(m, n, NULL, e, &u); - free(n); - if (r < 0) return r; @@ -2033,6 +2131,8 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { } } + bus_serialize(m, f); + fputc('\n', f); HASHMAP_FOREACH_KEY(u, t, m->units, i) { @@ -2046,7 +2146,8 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { fputs(u->id, f); fputc('\n', f); - if ((r = unit_serialize(u, f, fds, !switching_root)) < 0) { + r = unit_serialize(u, f, fds, !switching_root); + if (r < 0) { m->n_reloading --; return r; } @@ -2151,7 +2252,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { strv_free(m->environment); m->environment = e; - } else + } else if (bus_deserialize_item(m, l) == 0) log_debug("Unknown serialization item '%s'", l); } @@ -2226,6 +2327,7 @@ int manager_reload(Manager *m) { return r; m->n_reloading ++; + bus_broadcast_reloading(m, true); fds = fdset_new(); if (!fds) { @@ -2285,6 +2387,8 @@ int manager_reload(Manager *m) { assert(m->n_reloading > 0); m->n_reloading--; + m->send_reloading_done = true; + finish: if (f) fclose(f); @@ -2357,7 +2461,8 @@ void manager_check_finished(Manager *m) { } /* Notify Type=idle units that we are done now */ - close_pipe(m->idle_pipe); + manager_unwatch_idle_pipe(m); + close_idle_pipe(m); /* Turn off confirm spawn now */ m->confirm_spawn = false; @@ -2559,19 +2664,16 @@ void manager_undo_generators(Manager *m) { remove_generator_dir(m, &m->generator_unit_path_late); } -int manager_set_default_controllers(Manager *m, char **controllers) { - char **l; - +int manager_environment_add(Manager *m, char **environment) { + char **e = NULL; assert(m); - l = strv_copy(controllers); - if (!l) + e = strv_env_merge(2, m->environment, environment); + if (!e) return -ENOMEM; - strv_free(m->default_controllers); - m->default_controllers = l; - - cg_shorten_controllers(m->default_controllers); + strv_free(m->environment); + m->environment = e; return 0; } @@ -2638,6 +2740,9 @@ static bool manager_get_show_status(Manager *m) { if (m->running_as != SYSTEMD_SYSTEM) return false; + if (m->no_console_output) + return false; + if (m->show_status) return true; @@ -2666,6 +2771,41 @@ void manager_status_printf(Manager *m, bool ephemeral, const char *status, const va_end(ap); } +int manager_get_unit_by_path(Manager *m, const char *path, const char *suffix, Unit **_found) { + _cleanup_free_ char *p = NULL; + Unit *found; + + assert(m); + assert(path); + assert(suffix); + assert(_found); + + p = unit_name_from_path(path, suffix); + if (!p) + return -ENOMEM; + + found = manager_get_unit(m, p); + if (!found) { + *_found = NULL; + return 0; + } + + *_found = found; + return 1; +} + +Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) { + char p[strlen(path)+1]; + + assert(m); + assert(path); + + strcpy(p, path); + path_kill_slashes(p); + + return hashmap_get(m->units_requiring_mounts_for, streq(p, "/") ? "" : p); +} + void watch_init(Watch *w) { assert(w); diff --git a/src/core/manager.h b/src/core/manager.h index bf833540ae..a3049b5e5b 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -27,6 +27,7 @@ #include <dbus/dbus.h> #include "fdset.h" +#include "cgroup-util.h" /* Enforce upper limit how many names we allow */ #define MANAGER_MAX_NAMES 131072 /* 128K */ @@ -62,7 +63,8 @@ enum WatchType { WATCH_DBUS_WATCH, WATCH_DBUS_TIMEOUT, WATCH_TIME_CHANGE, - WATCH_JOBS_IN_PROGRESS + WATCH_JOBS_IN_PROGRESS, + WATCH_IDLE_PIPE, }; struct Watch { @@ -86,6 +88,7 @@ struct Watch { #include "dbus.h" #include "path-lookup.h" #include "execute.h" +#include "unit-name.h" struct Manager { /* Note that the set of units we know of is allowed to be @@ -100,9 +103,6 @@ struct Manager { * type we maintain a per type linked list */ LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]); - /* To optimize iteration of units that have requires_mounts_for set */ - LIST_HEAD(Unit, has_requires_mounts_for); - /* Units that need to be loaded */ LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */ @@ -122,6 +122,9 @@ struct Manager { /* Units to check when doing GC */ LIST_HEAD(Unit, gc_queue); + /* Units that should be realized */ + LIST_HEAD(Unit, cgroup_queue); + Hashmap *watch_pids; /* pid => Unit object n:1 */ char *notify_socket; @@ -130,6 +133,7 @@ struct Manager { Watch signal_watch; Watch time_change_watch; Watch jobs_in_progress_watch; + Watch idle_pipe_watch; int epoll_fd; @@ -139,7 +143,6 @@ struct Manager { Set *unit_path_cache; char **environment; - char **default_controllers; usec_t runtime_watchdog; usec_t shutdown_watchdog; @@ -150,6 +153,10 @@ struct Manager { dual_timestamp initrd_timestamp; dual_timestamp userspace_timestamp; dual_timestamp finish_timestamp; + dual_timestamp generators_start_timestamp; + dual_timestamp generators_finish_timestamp; + dual_timestamp unitsload_start_timestamp; + dual_timestamp unitsload_finish_timestamp; char *generator_unit_path; char *generator_unit_path_early; @@ -187,6 +194,8 @@ struct Manager { int32_t conn_data_slot; int32_t subscribed_data_slot; + bool send_reloading_done; + uint32_t current_job_id; uint32_t default_unit_job_id; @@ -194,10 +203,10 @@ struct Manager { int dev_autofs_fd; /* Data specific to the cgroup subsystem */ - Hashmap *cgroup_bondings; /* path string => CGroupBonding object 1:n */ - char *cgroup_hierarchy; + Hashmap *cgroup_unit; + CGroupControllerMask cgroup_supported; + char *cgroup_root; - usec_t gc_queue_timestamp; int gc_marker; unsigned n_in_gc_queue; @@ -217,6 +226,7 @@ struct Manager { bool show_status; bool confirm_spawn; + bool no_console_output; ExecOutput default_std_output, default_std_error; @@ -234,13 +244,18 @@ struct Manager { unsigned jobs_in_progress_iteration; /* Type=idle pipes */ - int idle_pipe[2]; + int idle_pipe[4]; char *switch_root; char *switch_root_init; + + /* This maps all possible path prefixes to the units needing + * them. It's a hashmap with a path string as key and a Set as + * value where Unit objects are contained. */ + Hashmap *units_requiring_mounts_for; }; -int manager_new(SystemdRunningAs running_as, Manager **m); +int manager_new(SystemdRunningAs running_as, bool reexecuting, Manager **m); void manager_free(Manager *m); int manager_enumerate(Manager *m); @@ -250,6 +265,8 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds); Job *manager_get_job(Manager *m, uint32_t id); Unit *manager_get_unit(Manager *m, const char *name); +int manager_get_unit_by_path(Manager *m, const char *path, const char *suffix, Unit **_found); + int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DBusError *e, Unit **_ret); @@ -268,7 +285,7 @@ unsigned manager_dispatch_load_queue(Manager *m); unsigned manager_dispatch_run_queue(Manager *m); unsigned manager_dispatch_dbus_queue(Manager *m); -int manager_set_default_controllers(Manager *m, char **controllers); +int manager_environment_add(Manager *m, char **environment); int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit); int manager_loop(Manager *m); @@ -303,4 +320,6 @@ void manager_recheck_journal(Manager *m); void manager_set_show_status(Manager *m, bool b); void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...) _printf_attr_(4,5); +Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path); + void watch_init(Watch *w); diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 4629808a7a..4359f59908 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -350,14 +350,8 @@ static int nftw_cb( }; int mount_setup(bool loaded_policy) { - - static const char relabel[] = - "/run/initramfs/root-fsck\0" - "/run/initramfs/shutdown\0"; - int r; unsigned i; - const char *j; for (i = 0; i < ELEMENTSOF(mount_table); i ++) { r = mount_one(mount_table + i, true); @@ -379,10 +373,6 @@ int mount_setup(bool loaded_policy) { nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); - /* Explicitly relabel these */ - NULSTR_FOREACH(j, relabel) - label_fix(j, true, false); - after_relabel = now(CLOCK_MONOTONIC); log_info("Relabelled /dev and /run in %s.", diff --git a/src/core/mount.c b/src/core/mount.c index 10073b50be..3d46557fb1 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -82,6 +82,7 @@ static void mount_init(Unit *u) { } kill_context_init(&m->kill_context); + cgroup_context_init(&m->cgroup_context); /* We need to make sure that /bin/mount is always called in * the same process group as us, so that the autofs kernel @@ -127,6 +128,7 @@ static void mount_done(Unit *u) { mount_parameters_done(&m->parameters_proc_self_mountinfo); mount_parameters_done(&m->parameters_fragment); + cgroup_context_done(&m->cgroup_context); exec_context_done(&m->exec_context, manager_is_reloading_or_reexecuting(u->manager)); exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); m->control_command = NULL; @@ -155,138 +157,58 @@ _pure_ static MountParameters* get_mount_parameters(Mount *m) { } static int mount_add_mount_links(Mount *m) { - Unit *other; - int r; + _cleanup_free_ char *parent = NULL; MountParameters *pm; - - assert(m); - - pm = get_mount_parameters_fragment(m); - - /* Adds in links to other mount points that might lie below or - * above us in the hierarchy */ - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_MOUNT]) { - Mount *n = MOUNT(other); - MountParameters *pn; - - if (n == m) - continue; - - if (UNIT(n)->load_state != UNIT_LOADED) - continue; - - pn = get_mount_parameters_fragment(n); - - if (path_startswith(m->where, n->where)) { - - if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) - return r; - - if (pn) - if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) - return r; - - } else if (path_startswith(n->where, m->where)) { - - if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) - return r; - - if (pm) - if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - - } else if (pm && pm->what && path_startswith(pm->what, n->where)) { - - if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(n), true)) < 0) - return r; - - if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(n), true)) < 0) - return r; - - } else if (pn && pn->what && path_startswith(pn->what, m->where)) { - - if ((r = unit_add_dependency(UNIT(n), UNIT_AFTER, UNIT(m), true)) < 0) - return r; - - if ((r = unit_add_dependency(UNIT(n), UNIT_REQUIRES, UNIT(m), true)) < 0) - return r; - } - } - - return 0; -} - -static int mount_add_swap_links(Mount *m) { Unit *other; + Iterator i; + Set *s; int r; assert(m); - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SWAP]) { - r = swap_add_one_mount_link(SWAP(other), m); + if (!path_equal(m->where, "/")) { + /* Adds in links to other mount points that might lie further + * up in the hierarchy */ + r = path_get_parent(m->where, &parent); if (r < 0) return r; - } - return 0; -} - -static int mount_add_path_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_PATH]) { - r = path_add_one_mount_link(PATH(other), m); + r = unit_require_mounts_for(UNIT(m), parent); if (r < 0) return r; } - return 0; -} - -static int mount_add_automount_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_AUTOMOUNT]) { - r = automount_add_one_mount_link(AUTOMOUNT(other), m); + /* Adds in links to other mount points that might be needed + * for the source path (if this is a bind mount) to be + * available. */ + pm = get_mount_parameters_fragment(m); + if (pm && path_is_absolute(pm->what)) { + r = unit_require_mounts_for(UNIT(m), pm->what); if (r < 0) return r; } - return 0; -} + /* Adds in links to other units that use this path or paths + * further down in the hierarchy */ + s = manager_get_units_requiring_mounts_for(UNIT(m)->manager, m->where); + SET_FOREACH(other, s, i) { -static int mount_add_socket_links(Mount *m) { - Unit *other; - int r; + if (other->load_state != UNIT_LOADED) + continue; - assert(m); + if (other == UNIT(m)) + continue; - LIST_FOREACH(units_by_type, other, UNIT(m)->manager->units_by_type[UNIT_SOCKET]) { - r = socket_add_one_mount_link(SOCKET(other), m); + r = unit_add_dependency(other, UNIT_AFTER, UNIT(m), true); if (r < 0) return r; - } - - return 0; -} -static int mount_add_requires_mounts_links(Mount *m) { - Unit *other; - int r; - - assert(m); - - LIST_FOREACH(has_requires_mounts_for, other, UNIT(m)->manager->has_requires_mounts_for) { - r = unit_add_one_mount_link(other, m); - if (r < 0) - return r; + if (UNIT(m)->fragment_path) { + /* If we have fragment configuration, then make this dependency required */ + r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true); + if (r < 0) + return r; + } } return 0; @@ -336,6 +258,12 @@ static bool mount_is_bind(MountParameters *p) { return false; } +static bool mount_is_auto(MountParameters *p) { + assert(p); + + return !mount_test_option(p->options, "noauto"); +} + static bool needs_quota(MountParameters *p) { assert(p); @@ -354,6 +282,7 @@ static bool needs_quota(MountParameters *p) { static int mount_add_device_links(Mount *m) { MountParameters *p; + bool device_wants_mount = false; int r; assert(m); @@ -374,7 +303,10 @@ static int mount_add_device_links(Mount *m) { if (path_equal(m->where, "/")) return 0; - r = unit_add_node_link(UNIT(m), p->what, false); + if (mount_is_auto(p) && UNIT(m)->manager->running_as == SYSTEMD_SYSTEM) + device_wants_mount = true; + + r = unit_add_node_link(UNIT(m), p->what, device_wants_mount); if (r < 0) return r; @@ -435,6 +367,21 @@ static int mount_add_quota_links(Mount *m) { return 0; } +static bool should_umount(Mount *m) { + MountParameters *p; + + if (path_equal(m->where, "/") || + path_equal(m->where, "/usr")) + return false; + + p = get_mount_parameters(m); + if (p && mount_test_option(p->options, "x-initrd.mount") && + !in_initrd()) + return false; + + return true; +} + static int mount_add_default_dependencies(Mount *m) { const char *after, *after2, *online; MountParameters *p; @@ -479,9 +426,11 @@ static int mount_add_default_dependencies(Mount *m) { return r; } - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - return r; + if (should_umount(m)) { + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + return r; + } return 0; } @@ -538,8 +487,9 @@ static int mount_fix_timeouts(Mount *m) { } static int mount_verify(Mount *m) { + _cleanup_free_ char *e = NULL; bool b; - char *e; + assert(m); if (UNIT(m)->load_state != UNIT_LOADED) @@ -548,12 +498,11 @@ static int mount_verify(Mount *m) { if (!m->from_fragment && !m->from_proc_self_mountinfo) return -ENOENT; - if (!(e = unit_name_from_path(m->where, ".mount"))) + e = unit_name_from_path(m->where, ".mount"); + if (!e) return -ENOMEM; b = unit_has_name(UNIT(m), e); - free(e); - if (!b) { log_error_unit(UNIT(m)->id, "%s's Where setting doesn't match unit name. Refusing.", @@ -617,26 +566,6 @@ static int mount_add_extras(Mount *m) { if (r < 0) return r; - r = mount_add_socket_links(m); - if (r < 0) - return r; - - r = mount_add_swap_links(m); - if (r < 0) - return r; - - r = mount_add_path_links(m); - if (r < 0) - return r; - - r = mount_add_requires_mounts_links(m); - if (r < 0) - return r; - - r = mount_add_automount_links(m); - if (r < 0) - return r; - r = mount_add_quota_links(m); if (r < 0) return r; @@ -647,7 +576,7 @@ static int mount_add_extras(Mount *m) { return r; } - r = unit_add_default_cgroups(u); + r = unit_add_default_slice(u); if (r < 0) return r; @@ -820,9 +749,9 @@ static void mount_dump(Unit *u, FILE *f, const char *prefix) { prefix, mount_state_to_string(m->state), prefix, mount_result_to_string(m->result), prefix, m->where, - prefix, strna(p->what), - prefix, strna(p->fstype), - prefix, strna(p->options), + prefix, p ? strna(p->what) : "n/a", + prefix, p ? strna(p->fstype) : "n/a", + prefix, p ? strna(p->options) : "n/a", prefix, yes_no(m->from_proc_self_mountinfo), prefix, yes_no(m->from_fragment), prefix, m->directory_mode); @@ -844,28 +773,31 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { assert(c); assert(_pid); + unit_realize_cgroup(UNIT(m)); + r = unit_watch_timer(UNIT(m), CLOCK_MONOTONIC, true, m->timeout_usec, &m->timer_watch); if (r < 0) goto fail; - if ((r = exec_spawn(c, - NULL, - &m->exec_context, - NULL, 0, - UNIT(m)->manager->environment, - true, - true, - true, - UNIT(m)->manager->confirm_spawn, - UNIT(m)->cgroup_bondings, - UNIT(m)->cgroup_attributes, - NULL, - UNIT(m)->id, - NULL, - &pid)) < 0) + r = exec_spawn(c, + NULL, + &m->exec_context, + NULL, 0, + UNIT(m)->manager->environment, + true, + true, + true, + UNIT(m)->manager->confirm_spawn, + UNIT(m)->manager->cgroup_supported, + UNIT(m)->cgroup_path, + UNIT(m)->id, + NULL, + &pid); + if (r < 0) goto fail; - if ((r = unit_watch_pid(UNIT(m), pid)) < 0) + r = unit_watch_pid(UNIT(m), pid); + if (r < 0) /* FIXME: we need to do something here */ goto fail; @@ -1538,9 +1470,11 @@ static int mount_add_one( if (r < 0) goto fail; - r = unit_add_dependency_by_name(u, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - goto fail; + if (should_umount(MOUNT(u))) { + r = unit_add_dependency_by_name(u, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + goto fail; + } unit_add_to_load_queue(u); } else { @@ -1555,7 +1489,7 @@ static int mount_add_one( } } - if (u->load_state == UNIT_ERROR) { + if (u->load_state == UNIT_NOT_FOUND) { u->load_state = UNIT_LOADED; u->load_error = 0; @@ -1616,79 +1550,56 @@ fail: static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { int r = 0; unsigned i; - char *device, *path, *options, *options2, *fstype, *d, *p, *o; assert(m); rewind(m->proc_self_mountinfo); for (i = 1;; i++) { + _cleanup_free_ char *device = NULL, *path = NULL, *options = NULL, *options2 = NULL, *fstype = NULL, *d = NULL, *p = NULL, *o = NULL; int k; - device = path = options = options2 = fstype = d = p = o = NULL; - - if ((k = fscanf(m->proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%ms" /* (6) mount options */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%ms " /* (9) file system type */ - "%ms" /* (10) mount source */ - "%ms" /* (11) mount options 2 */ - "%*[^\n]", /* some rubbish at the end */ - &path, - &options, - &fstype, - &device, - &options2)) != 5) { - - if (k == EOF) - break; - + k = fscanf(m->proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%ms" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%ms " /* (9) file system type */ + "%ms" /* (10) mount source */ + "%ms" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &path, + &options, + &fstype, + &device, + &options2); + + if (k == EOF) + break; + + if (k != 5) { log_warning("Failed to parse /proc/self/mountinfo:%u.", i); - goto clean_up; + continue; } o = strjoin(options, ",", options2, NULL); - if (!o) { - r = -ENOMEM; - goto finish; - } + if (!o) + return log_oom(); - if (!(d = cunescape(device)) || - !(p = cunescape(path))) { - r = -ENOMEM; - goto finish; - } + d = cunescape(device); + p = cunescape(path); + if (!d || !p) + return log_oom(); - if ((k = mount_add_one(m, d, p, o, fstype, 0, set_flags)) < 0) + k = mount_add_one(m, d, p, o, fstype, 0, set_flags); + if (k < 0) r = k; - -clean_up: - free(device); - free(path); - free(options); - free(options2); - free(fstype); - free(d); - free(p); - free(o); } -finish: - free(device); - free(path); - free(options); - free(options2); - free(fstype); - free(d); - free(p); - free(o); - return r; } @@ -1872,8 +1783,9 @@ const UnitVTable mount_vtable = { "Mount\0" "Install\0", + .private_section = "Mount", .exec_context_offset = offsetof(Mount, exec_context), - .exec_section = "Mount", + .cgroup_context_offset = offsetof(Mount, cgroup_context), .no_alias = true, .no_instances = true, @@ -1908,6 +1820,8 @@ const UnitVTable mount_vtable = { .bus_interface = "org.freedesktop.systemd1.Mount", .bus_message_handler = bus_mount_message_handler, .bus_invalidating_properties = bus_mount_invalidating_properties, + .bus_set_property = bus_mount_set_property, + .bus_commit_properties = bus_mount_commit_properties, .enumerate = mount_enumerate, .shutdown = mount_shutdown, diff --git a/src/core/mount.h b/src/core/mount.h index bcc10ee0d4..7cd4320d94 100644 --- a/src/core/mount.h +++ b/src/core/mount.h @@ -25,6 +25,8 @@ typedef struct Mount Mount; #include "unit.h" #include "kill.h" +#include "execute.h" +#include "cgroup.h" typedef enum MountState { MOUNT_DEAD, @@ -95,8 +97,10 @@ struct Mount { usec_t timeout_usec; ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; + ExecContext exec_context; KillContext kill_context; + CGroupContext cgroup_context; MountState state, deserialized_state; diff --git a/src/core/namespace.c b/src/core/namespace.c index 7e33d84156..16b132ba56 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -51,6 +51,7 @@ typedef struct BindMount { const char *path; MountMode mode; bool done; + bool ignore; } BindMount; static int append_mounts(BindMount **p, char **strv, MountMode mode) { @@ -58,6 +59,13 @@ static int append_mounts(BindMount **p, char **strv, MountMode mode) { STRV_FOREACH(i, strv) { + (*p)->ignore = false; + + if ((mode == INACCESSIBLE || mode == READONLY) && (*i)[0] == '-') { + (*p)->ignore = true; + (*i)++; + } + if (!path_is_absolute(*i)) return -EINVAL; @@ -155,6 +163,8 @@ static int apply_mount( r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL); if (r >= 0) log_debug("Successfully mounted %s to %s", what, m->path); + else if (m->ignore && errno == ENOENT) + r = 0; return r; } @@ -168,7 +178,7 @@ static int make_read_only(BindMount *m) { return 0; r = mount(NULL, m->path, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL); - if (r < 0) + if (r < 0 && !(m->ignore && errno == ENOENT)) return -errno; return 0; diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index a07a8e1ce3..a375dce0b0 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -86,6 +86,10 @@ send_interface="org.freedesktop.systemd1.Manager" send_member="Dump"/> + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" + send_member="GetDefaultTarget"/> + <allow receive_sender="org.freedesktop.systemd1"/> </policy> diff --git a/src/core/path.c b/src/core/path.c index 8a09deb4ff..99e2fedf29 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -241,10 +241,6 @@ static bool path_spec_check_good(PathSpec *s, bool initial) { return good; } -static bool path_spec_startswith(PathSpec *s, const char *what) { - return path_startswith(s->path, what); -} - static void path_spec_mkdir(PathSpec *s, mode_t mode) { int r; @@ -301,38 +297,14 @@ static void path_done(Unit *u) { path_free_specs(p); } -int path_add_one_mount_link(Path *p, Mount *m) { +static int path_add_mount_links(Path *p) { PathSpec *s; int r; assert(p); - assert(m); - - if (UNIT(p)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; LIST_FOREACH(spec, s, p->specs) { - if (!path_spec_startswith(s, m->where)) - continue; - - r = unit_add_two_dependencies(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, - UNIT(m), true); - if (r < 0) - return r; - } - - return 0; -} - -static int path_add_mount_links(Path *p) { - Unit *other; - int r; - - assert(p); - - LIST_FOREACH(units_by_type, other, UNIT(p)->manager->units_by_type[UNIT_MOUNT]) { - r = path_add_one_mount_link(p, MOUNT(other)); + r = unit_require_mounts_for(UNIT(p), s->path); if (r < 0) return r; } diff --git a/src/core/path.h b/src/core/path.h index 6adab5897d..dec3df7035 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -90,10 +90,6 @@ struct Path { PathResult result; }; -/* Called from the mount code figure out if a mount is a dependency of - * any of the paths of this path object */ -int path_add_one_mount_link(Path *p, Mount *m); - void path_free_specs(Path *p); extern const UnitVTable path_vtable; diff --git a/src/core/scope.c b/src/core/scope.c new file mode 100644 index 0000000000..50e5dbacb4 --- /dev/null +++ b/src/core/scope.c @@ -0,0 +1,482 @@ +/*-*- 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 <errno.h> +#include <signal.h> +#include <unistd.h> + +#include "unit.h" +#include "scope.h" +#include "load-fragment.h" +#include "log.h" +#include "dbus-scope.h" +#include "special.h" +#include "unit-name.h" +#include "load-dropin.h" + +static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = UNIT_INACTIVE, + [SCOPE_RUNNING] = UNIT_ACTIVE, + [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SCOPE_FAILED] = UNIT_FAILED +}; + +static void scope_init(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_stop_usec = DEFAULT_TIMEOUT_USEC; + + watch_init(&s->timer_watch); + + cgroup_context_init(&s->cgroup_context); + kill_context_init(&s->kill_context); + + UNIT(s)->ignore_on_isolate = true; + UNIT(s)->ignore_on_snapshot = true; +} + +static void scope_done(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + + cgroup_context_done(&s->cgroup_context); + + set_free(s->pids); + s->pids = NULL; + + unit_unwatch_timer(u, &s->timer_watch); +} + +static void scope_set_state(Scope *s, ScopeState state) { + ScopeState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SCOPE_STOP_SIGTERM && + state != SCOPE_STOP_SIGKILL) + unit_unwatch_timer(UNIT(s), &s->timer_watch); + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(s)->id, + scope_state_to_string(old_state), + scope_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int scope_add_default_dependencies(Scope *s) { + int r; + + assert(s); + + /* Make sure scopes are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int scope_verify(Scope *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (set_size(s->pids) <= 0 && UNIT(s)->manager->n_reloading <= 0) { + log_error_unit(UNIT(s)->id, "Scope %s has no PIDs. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + return 0; +} + +static int scope_load(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(u->load_state == UNIT_STUB); + + if (!u->transient && UNIT(s)->manager->n_reloading <= 0) + return -ENOENT; + + u->load_state = UNIT_LOADED; + + r = unit_load_dropin(u); + if (r < 0) + return r; + + r = unit_add_default_slice(u); + if (r < 0) + return r; + + if (u->default_dependencies) { + r = scope_add_default_dependencies(s); + if (r < 0) + return r; + } + + return scope_verify(s); +} + +static int scope_coldplug(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(s->state == SCOPE_DEAD); + + if (s->deserialized_state != s->state) { + + if ((s->deserialized_state == SCOPE_STOP_SIGKILL || s->deserialized_state == SCOPE_STOP_SIGTERM) + && s->timeout_stop_usec > 0) { + r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_stop_usec, &s->timer_watch); + if (r < 0) + + return r; + } + + scope_set_state(s, s->deserialized_state); + } + + return 0; +} + +static void scope_dump(Unit *u, FILE *f, const char *prefix) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + + fprintf(f, + "%sScope State: %s\n" + "%sResult: %s\n", + prefix, scope_state_to_string(s->state), + prefix, scope_result_to_string(s->result)); + + cgroup_context_dump(&s->cgroup_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); +} + +static void scope_enter_dead(Scope *s, ScopeResult f) { + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); +} + +static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { + int r; + + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + state != SCOPE_STOP_SIGTERM, + -1, -1, false); + if (r < 0) + goto fail; + + if (r > 0) { + if (s->timeout_stop_usec > 0) { + r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_stop_usec, &s->timer_watch); + if (r < 0) + goto fail; + } + + scope_set_state(s, state); + } else + scope_enter_dead(s, SCOPE_SUCCESS); + + return; + +fail: + log_warning_unit(UNIT(s)->id, + "%s failed to kill processes: %s", UNIT(s)->id, strerror(-r)); + + scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); +} + +static int scope_start(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + + if (s->state == SCOPE_FAILED) + return -EPERM; + + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return -EAGAIN; + + assert(s->state == SCOPE_DEAD); + + if (!u->transient && UNIT(s)->manager->n_reloading <= 0) + return -ENOENT; + + r = unit_realize_cgroup(u); + if (r < 0) { + log_error("Failed to realize cgroup: %s", strerror(-r)); + return r; + } + + r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, s->pids); + if (r < 0) + return r; + + set_free(s->pids); + s->pids = NULL; + + s->result = SCOPE_SUCCESS; + + scope_set_state(s, SCOPE_RUNNING); + return 0; +} + +static int scope_stop(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + assert(s->state == SCOPE_RUNNING); + + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return 0; + + assert(s->state == SCOPE_RUNNING); + + scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); + return 0; +} + +static void scope_reset_failed(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + + if (s->state == SCOPE_FAILED) + scope_set_state(s, SCOPE_DEAD); + + s->result = SCOPE_SUCCESS; +} + +static int scope_kill(Unit *u, KillWho who, int signo, DBusError *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); + return 0; +} + +static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ScopeState state; + + state = scope_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +static bool scope_check_gc(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + + /* Never clean up scopes that still have a process around, + * even if the scope is formally dead. */ + + if (UNIT(s)->cgroup_path) { + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path, true); + if (r <= 0) + return true; + } + + return false; +} + +static void scope_timer_event(Unit *u, uint64_t elapsed, Watch*w) { + Scope *s = SCOPE(u); + + assert(s); + assert(elapsed == 1); + assert(w == &s->timer_watch); + + switch (s->state) { + + case SCOPE_STOP_SIGTERM: + if (s->kill_context.send_sigkill) { + log_warning_unit(u->id, "%s stopping timed out. Killing.", u->id); + scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); + } else { + log_warning_unit(u->id, "%s stopping timed out. Skipping SIGKILL.", u->id); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + } + + break; + + case SCOPE_STOP_SIGKILL: + log_warning_unit(u->id, "%s still around after SIGKILL. Ignoring.", u->id); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } +} + +static void scope_notify_cgroup_empty_event(Unit *u) { + Scope *s = SCOPE(u); + assert(u); + + log_debug_unit(u->id, "%s: cgroup is empty", u->id); + + switch (s->state) { + + case SCOPE_RUNNING: + case SCOPE_STOP_SIGTERM: + case SCOPE_STOP_SIGKILL: + scope_enter_dead(s, SCOPE_SUCCESS); + + break; + + default: + ; + } +} + +_pure_ static UnitActiveState scope_active_state(Unit *u) { + assert(u); + + return state_translation_table[SCOPE(u)->state]; +} + +_pure_ static const char *scope_sub_state_to_string(Unit *u) { + assert(u); + + return scope_state_to_string(SCOPE(u)->state); +} + +static const char* const scope_state_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = "dead", + [SCOPE_RUNNING] = "running", + [SCOPE_STOP_SIGTERM] = "stop-sigterm", + [SCOPE_STOP_SIGKILL] = "stop-sigkill", + [SCOPE_FAILED] = "failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); + +static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { + [SCOPE_SUCCESS] = "success", + [SCOPE_FAILURE_RESOURCES] = "resources", + [SCOPE_FAILURE_TIMEOUT] = "timeout", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); + +const UnitVTable scope_vtable = { + .object_size = sizeof(Scope), + .sections = + "Unit\0" + "Scope\0" + "Install\0", + + .private_section = "Scope", + .cgroup_context_offset = offsetof(Scope, cgroup_context), + + .no_alias = true, + .no_instances = true, + + .init = scope_init, + .load = scope_load, + .done = scope_done, + + .coldplug = scope_coldplug, + + .dump = scope_dump, + + .start = scope_start, + .stop = scope_stop, + + .kill = scope_kill, + + .serialize = scope_serialize, + .deserialize_item = scope_deserialize_item, + + .active_state = scope_active_state, + .sub_state_to_string = scope_sub_state_to_string, + + .check_gc = scope_check_gc, + + .timer_event = scope_timer_event, + + .reset_failed = scope_reset_failed, + + .notify_cgroup_empty = scope_notify_cgroup_empty_event, + + .bus_interface = "org.freedesktop.systemd1.Scope", + .bus_message_handler = bus_scope_message_handler, + .bus_set_property = bus_scope_set_property, + .bus_commit_properties = bus_scope_commit_properties, + + .can_transient = true +}; diff --git a/src/core/scope.h b/src/core/scope.h new file mode 100644 index 0000000000..2a3dcb73d7 --- /dev/null +++ b/src/core/scope.h @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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/>. +***/ + +typedef struct Scope Scope; + +#include "unit.h" +#include "kill.h" + +typedef enum ScopeState { + SCOPE_DEAD, + SCOPE_RUNNING, + SCOPE_STOP_SIGTERM, + SCOPE_STOP_SIGKILL, + SCOPE_FAILED, + _SCOPE_STATE_MAX, + _SCOPE_STATE_INVALID = -1 +} ScopeState; + +typedef enum ScopeResult { + SCOPE_SUCCESS, + SCOPE_FAILURE_RESOURCES, + SCOPE_FAILURE_TIMEOUT, + _SCOPE_RESULT_MAX, + _SCOPE_RESULT_INVALID = -1 +} ScopeResult; + +struct Scope { + Unit meta; + + CGroupContext cgroup_context; + KillContext kill_context; + + ScopeState state, deserialized_state; + ScopeResult result; + + usec_t timeout_stop_usec; + + Set *pids; + + Watch timer_watch; +}; + +extern const UnitVTable scope_vtable; + +const char* scope_state_to_string(ScopeState i) _const_; +ScopeState scope_state_from_string(const char *s) _pure_; + +const char* scope_result_to_string(ScopeResult i) _const_; +ScopeResult scope_result_from_string(const char *s) _pure_; diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c index 426aed07d2..0a3ee18bb9 100644 --- a/src/core/selinux-access.c +++ b/src/core/selinux-access.c @@ -6,16 +6,16 @@ Copyright 2012 Dan Walsh systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + 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. + Lesser General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h index 9183cbc9a6..2d7ac64c8f 100644 --- a/src/core/selinux-access.h +++ b/src/core/selinux-access.h @@ -8,16 +8,16 @@ Copyright 2012 Dan Walsh systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + 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. + Lesser General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ diff --git a/src/core/service.c b/src/core/service.c index 3617c24711..67920248d3 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -141,6 +141,7 @@ static void service_init(Unit *u) { exec_context_init(&s->exec_context); kill_context_init(&s->kill_context); + cgroup_context_init(&s->cgroup_context); RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5); @@ -190,6 +191,14 @@ static int service_set_main_pid(Service *s, pid_t pid) { if (pid == getpid()) return -EINVAL; + if (s->main_pid == pid && s->main_pid_known) + return 0; + + if (s->main_pid != pid) { + service_unwatch_main_pid(s); + exec_status_start(&s->main_exec_status, pid); + } + s->main_pid = pid; s->main_pid_known = true; @@ -202,8 +211,6 @@ static int service_set_main_pid(Service *s, pid_t pid) { } else s->main_pid_alien = false; - exec_status_start(&s->main_exec_status, pid); - return 0; } @@ -220,7 +227,7 @@ static void service_close_socket_fd(Service *s) { static void service_connection_unref(Service *s) { assert(s); - if (!UNIT_DEREF(s->accept_socket)) + if (!UNIT_ISSET(s->accept_socket)) return; socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); @@ -235,7 +242,7 @@ static void service_stop_watchdog(Service *s) { s->watchdog_timestamp.monotonic = 0; } -static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart); +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); static void service_handle_watchdog(Service *s) { usec_t offset; @@ -249,7 +256,7 @@ static void service_handle_watchdog(Service *s) { offset = now(CLOCK_MONOTONIC) - s->watchdog_timestamp.monotonic; if (offset >= s->watchdog_usec) { log_error_unit(UNIT(s)->id, "%s watchdog timeout!", UNIT(s)->id); - service_enter_dead(s, SERVICE_FAILURE_WATCHDOG, true); + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_WATCHDOG); return; } @@ -283,6 +290,7 @@ static void service_done(Unit *u) { free(s->status_text); s->status_text = NULL; + cgroup_context_done(&s->cgroup_context); exec_context_done(&s->exec_context, manager_is_reloading_or_reexecuting(u->manager)); exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); s->control_command = NULL; @@ -981,7 +989,7 @@ static int service_load_sysv_name(Service *s, const char *name) { assert(s); assert(name); - /* For SysV services we strip the *.sh suffixes. */ + /* For SysV services we strip the *.sh suffixes. */ if (endswith(name, ".sh.service")) return -ENOENT; @@ -1109,6 +1117,12 @@ static int service_verify(Service *s) { return -EINVAL; } + if (s->type == SERVICE_ONESHOT && s->restart != SERVICE_RESTART_NO) { + log_error_unit(UNIT(s)->id, + "%s has Restart setting other than no, which isn't allowed for Type=oneshot services. Refusing.", UNIT(s)->id); + return -EINVAL; + } + if (s->type == SERVICE_DBUS && !s->bus_name) { log_error_unit(UNIT(s)->id, "%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->id); @@ -1191,27 +1205,32 @@ static int service_load(Unit *u) { assert(s); /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) + r = unit_load_fragment(u); + if (r < 0) return r; #ifdef HAVE_SYSV_COMPAT /* Load a classic init script as a fallback, if we couldn't find anything */ - if (u->load_state == UNIT_STUB) - if ((r = service_load_sysv(s)) < 0) + if (u->load_state == UNIT_STUB) { + r = service_load_sysv(s); + if (r < 0) return r; + } #endif /* Still nothing found? Then let's give up */ if (u->load_state == UNIT_STUB) return -ENOENT; - /* We were able to load something, then let's add in the - * dropin directories. */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) - return r; - /* This is a new unit? Then let's add in some extras */ if (u->load_state == UNIT_LOADED) { + + /* We were able to load something, then let's add in + * the dropin directories. */ + r = unit_load_dropin(u); + if (r < 0) + return r; + if (s->type == _SERVICE_TYPE_INVALID) s->type = s->bus_name ? SERVICE_DBUS : SERVICE_SIMPLE; @@ -1225,7 +1244,7 @@ static int service_load(Unit *u) { if (r < 0) return r; - r = unit_add_default_cgroups(u); + r = unit_add_default_slice(u); if (r < 0) return r; @@ -1453,7 +1472,7 @@ static int service_search_main_pid(Service *s) { assert(s->main_pid <= 0); - pid = cgroup_bonding_search_main_pid_list(UNIT(s)->cgroup_bondings); + pid = unit_search_main_pid(UNIT(s)); if (pid <= 0) return -ENOENT; @@ -1474,24 +1493,6 @@ static int service_search_main_pid(Service *s) { return 0; } -static void service_notify_sockets_dead(Service *s, bool failed_permanent) { - Iterator i; - Unit *u; - - assert(s); - - /* Notifies all our sockets when we die */ - - if (s->socket_fd >= 0) - return; - - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) - if (u->type == UNIT_SOCKET) - socket_notify_service_dead(SOCKET(u), failed_permanent); - - return; -} - static void service_set_state(Service *s, ServiceState state) { ServiceState old_state; const UnitActiveState *table; @@ -1543,19 +1544,6 @@ static void service_set_state(Service *s, ServiceState state) { s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; } - if (state == SERVICE_FAILED) - service_notify_sockets_dead(s, s->result == SERVICE_FAILURE_START_LIMIT); - - if (state == SERVICE_DEAD || - state == SERVICE_STOP || - state == SERVICE_STOP_SIGTERM || - state == SERVICE_STOP_SIGKILL || - state == SERVICE_STOP_POST || - state == SERVICE_FINAL_SIGTERM || - state == SERVICE_FINAL_SIGKILL || - state == SERVICE_AUTO_RESTART) - service_notify_sockets_dead(s, false); - if (state != SERVICE_START_PRE && state != SERVICE_START && state != SERVICE_START_POST && @@ -1578,7 +1566,7 @@ static void service_set_state(Service *s, ServiceState state) { /* For the inactive states unit_notify() will trim the cgroup, * but for exit we have to do that ourselves... */ if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0) - cgroup_bonding_trim_list(UNIT(s)->cgroup_bondings, true); + unit_destroy_cgroup(UNIT(s)); if (old_state != state) log_debug_unit(UNIT(s)->id, @@ -1610,6 +1598,7 @@ static int service_coldplug(Unit *u) { s->deserialized_state == SERVICE_FINAL_SIGTERM || s->deserialized_state == SERVICE_FINAL_SIGKILL || s->deserialized_state == SERVICE_AUTO_RESTART) { + if (s->deserialized_state == SERVICE_AUTO_RESTART || s->timeout_start_usec > 0) { usec_t k; @@ -1747,11 +1736,14 @@ static int service_spawn( unsigned n_fds = 0, n_env = 0; _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL; + const char *path; assert(s); assert(c); assert(_pid); + unit_realize_cgroup(UNIT(s)); + if (pass_fds || s->exec_context.std_input == EXEC_INPUT_SOCKET || s->exec_context.std_output == EXEC_OUTPUT_SOCKET || @@ -1777,11 +1769,9 @@ static int service_spawn( } else unit_unwatch_timer(UNIT(s), &s->timer_watch); - argv = unit_full_printf_strv(UNIT(s), c->argv); - if (!argv) { - r = -ENOMEM; + r = unit_full_printf_strv(UNIT(s), c->argv, &argv); + if (r < 0) goto fail; - } our_env = new0(char*, 5); if (!our_env) { @@ -1807,7 +1797,7 @@ static int service_spawn( goto fail; } - if (s->meta.manager->running_as != SYSTEMD_SYSTEM) + if (UNIT(s)->manager->running_as != SYSTEMD_SYSTEM) if (asprintf(our_env + n_env++, "MANAGERPID=%lu", (unsigned long) getpid()) < 0) { r = -ENOMEM; goto fail; @@ -1819,6 +1809,12 @@ static int service_spawn( goto fail; } + if (is_control && UNIT(s)->cgroup_path) { + path = strappenda(UNIT(s)->cgroup_path, "/control"); + cg_create(SYSTEMD_CGROUP_CONTROLLER, path); + } else + path = UNIT(s)->cgroup_path; + r = exec_spawn(c, argv, &s->exec_context, @@ -1828,9 +1824,8 @@ static int service_spawn( apply_chroot, apply_tty_stdin, UNIT(s)->manager->confirm_spawn, - UNIT(s)->cgroup_bondings, - UNIT(s)->cgroup_attributes, - is_control ? "control" : NULL, + UNIT(s)->manager->cgroup_supported, + path, UNIT(s)->id, s->type == SERVICE_IDLE ? UNIT(s)->manager->idle_pipe : NULL, &pid); @@ -1865,7 +1860,7 @@ static int main_pid_good(Service *s) { /* If it's an alien child let's check if it is still * alive ... */ - if (s->main_pid_alien) + if (s->main_pid_alien && s->main_pid > 0) return kill(s->main_pid, 0) >= 0 || errno != ESRCH; /* .. otherwise assume we'll get a SIGCHLD for it, @@ -1889,7 +1884,10 @@ static int cgroup_good(Service *s) { assert(s); - r = cgroup_bonding_is_empty_list(UNIT(s)->cgroup_bondings); + if (!UNIT(s)->cgroup_path) + return 0; + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path, true); if (r < 0) return r; @@ -1910,6 +1908,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) (s->restart == SERVICE_RESTART_ALWAYS || (s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) || (s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) || + (s->restart == SERVICE_RESTART_ON_WATCHDOG && s->result == SERVICE_FAILURE_WATCHDOG) || (s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL || s->result == SERVICE_FAILURE_CORE_DUMP))) && (s->result != SERVICE_FAILURE_EXIT_CODE || @@ -1930,6 +1929,12 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) /* we want fresh tmpdirs in case service is started again immediately */ exec_context_tmp_dirs_done(&s->exec_context); + /* Try to delete the pid file. At this point it will be + * out-of-date, and some software might be confused by it, so + * let's remove it. */ + if (s->pid_file) + unlink_noerrno(s->pid_file); + return; fail: @@ -1939,8 +1944,6 @@ fail: service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); } -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); - static void service_enter_stop_post(Service *s, ServiceResult f) { int r; assert(s); @@ -1970,7 +1973,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) { service_set_state(s, SERVICE_STOP_POST); } else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); + service_enter_dead(s, SERVICE_SUCCESS, true); return; @@ -2121,25 +2124,33 @@ fail: service_enter_stop(s, SERVICE_FAILURE_RESOURCES); } +static void service_kill_control_processes(Service *s) { + char *p; + + if (!UNIT(s)->cgroup_path) + return; + + p = strappenda(UNIT(s)->cgroup_path, "/control"); + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL); +} + static void service_enter_start(Service *s) { + ExecCommand *c; pid_t pid; int r; - ExecCommand *c; assert(s); assert(s->exec_command[SERVICE_EXEC_START]); assert(!s->exec_command[SERVICE_EXEC_START]->command_next || s->type == SERVICE_ONESHOT); - if (s->type == SERVICE_FORKING) - service_unwatch_control_pid(s); - else - service_unwatch_main_pid(s); + service_unwatch_control_pid(s); + service_unwatch_main_pid(s); /* We want to ensure that nobody leaks processes from * START_PRE here, so let's go on a killing spree, People * should not spawn long running processes from START_PRE. */ - cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control"); + service_kill_control_processes(s); if (s->type == SERVICE_FORKING) { s->control_command_id = SERVICE_EXEC_START; @@ -2215,11 +2226,9 @@ static void service_enter_start_pre(Service *s) { s->control_command = s->exec_command[SERVICE_EXEC_START_PRE]; if (s->control_command) { - /* Before we start anything, let's clear up what might * be left from previous runs. */ - cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, - true,true, NULL, "control"); + service_kill_control_processes(s); s->control_command_id = SERVICE_EXEC_START_PRE; @@ -2691,8 +2700,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, if (parse_pid(value, &pid) < 0) log_debug_unit(u->id, "Failed to parse main-pid value %s", value); - else - service_set_main_pid(s, (pid_t) pid); + else { + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + } } else if (streq(key, "main-pid-known")) { int b; @@ -3043,7 +3054,6 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { } } else if (s->control_pid == pid) { - s->control_pid = 0; if (s->control_command) { @@ -3064,8 +3074,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { /* Immediately get rid of the cgroup, so that the * kernel doesn't delay the cgroup empty messages for * the service cgroup any longer than necessary */ - cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, - true, true, NULL, "control"); + service_kill_control_processes(s); if (s->control_command && s->control_command->command_next && @@ -3294,13 +3303,12 @@ static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) { } } -static void service_cgroup_notify_event(Unit *u) { +static void service_notify_cgroup_empty_event(Unit *u) { Service *s = SERVICE(u); assert(u); - log_debug_unit(u->id, - "%s: cgroup is empty", u->id); + log_debug_unit(u->id, "%s: cgroup is empty", u->id); switch (s->state) { @@ -3387,6 +3395,7 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags) { log_debug_unit(u->id, "%s: got %s", u->id, e); service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); } } @@ -3683,8 +3692,10 @@ static void service_bus_query_pid_done( (s->state == SERVICE_START || s->state == SERVICE_START_POST || s->state == SERVICE_RUNNING || - s->state == SERVICE_RELOAD)) + s->state == SERVICE_RELOAD)){ service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + } } int service_set_socket_fd(Service *s, int fd, Socket *sock) { @@ -3729,6 +3740,7 @@ static void service_reset_failed(Unit *u) { static int service_kill(Unit *u, KillWho who, int signo, DBusError *error) { Service *s = SERVICE(u); + return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error); } @@ -3756,6 +3768,7 @@ static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { [SERVICE_RESTART_NO] = "no", [SERVICE_RESTART_ON_SUCCESS] = "on-success", [SERVICE_RESTART_ON_FAILURE] = "on-failure", + [SERVICE_RESTART_ON_WATCHDOG] = "on-watchdog", [SERVICE_RESTART_ON_ABORT] = "on-abort", [SERVICE_RESTART_ALWAYS] = "always" }; @@ -3821,8 +3834,9 @@ const UnitVTable service_vtable = { "Service\0" "Install\0", + .private_section = "Service", .exec_context_offset = offsetof(Service, exec_context), - .exec_section = "Service", + .cgroup_context_offset = offsetof(Service, cgroup_context), .init = service_init, .done = service_done, @@ -3855,7 +3869,7 @@ const UnitVTable service_vtable = { .reset_failed = service_reset_failed, - .cgroup_notify_empty = service_cgroup_notify_event, + .notify_cgroup_empty = service_notify_cgroup_empty_event, .notify_message = service_notify_message, .bus_name_owner_change = service_bus_name_owner_change, @@ -3864,6 +3878,10 @@ const UnitVTable service_vtable = { .bus_interface = "org.freedesktop.systemd1.Service", .bus_message_handler = bus_service_message_handler, .bus_invalidating_properties = bus_service_invalidating_properties, + .bus_set_property = bus_service_set_property, + .bus_commit_properties = bus_service_commit_properties, + + .can_transient = true, #ifdef HAVE_SYSV_COMPAT .enumerate = service_enumerate, diff --git a/src/core/service.h b/src/core/service.h index 703d3faa45..ce5b5e04ab 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -54,6 +54,7 @@ typedef enum ServiceRestart { SERVICE_RESTART_NO, SERVICE_RESTART_ON_SUCCESS, SERVICE_RESTART_ON_FAILURE, + SERVICE_RESTART_ON_WATCHDOG, SERVICE_RESTART_ON_ABORT, SERVICE_RESTART_ALWAYS, _SERVICE_RESTART_MAX, @@ -135,6 +136,7 @@ struct Service { ExecContext exec_context; KillContext kill_context; + CGroupContext cgroup_context; ServiceState state, deserialized_state; diff --git a/src/core/shutdown.c b/src/core/shutdown.c index 2db761de36..4709746de4 100644 --- a/src/core/shutdown.c +++ b/src/core/shutdown.c @@ -39,6 +39,7 @@ #include "missing.h" #include "log.h" +#include "fileio.h" #include "umount.h" #include "util.h" #include "mkdir.h" @@ -130,12 +131,27 @@ static int pivot_to_new_root(void) { } int main(int argc, char *argv[]) { + _cleanup_free_ char *line = NULL; int cmd, r; unsigned retries; bool need_umount = true, need_swapoff = true, need_loop_detach = true, need_dm_detach = true; bool in_container, use_watchdog = false; char *arguments[3]; + /* suppress shutdown status output if 'quiet' is used */ + r = read_one_line_file("/proc/cmdline", &line); + if (r >= 0) { + char *w, *state; + size_t l; + + FOREACH_WORD_QUOTED(w, l, line, state) { + if (l == 5 && memcmp(w, "quiet", 5) == 0) { + log_set_max_level(LOG_WARNING); + break; + } + } + } + log_parse_environment(); log_set_target(LOG_TARGET_CONSOLE); /* syslog will die if not gone yet */ log_open(); @@ -302,7 +318,7 @@ int main(int argc, char *argv[]) { log_warning("kexec failed. Falling back to normal reboot."); } else { /* Child */ - const char *args[3] = { "/sbin/kexec", "-e", NULL }; + const char *args[3] = { KEXEC, "-e", NULL }; execv(args[0], (char * const *) args); return EXIT_FAILURE; } diff --git a/src/core/slice.c b/src/core/slice.c new file mode 100644 index 0000000000..40d416e35e --- /dev/null +++ b/src/core/slice.c @@ -0,0 +1,322 @@ +/*-*- 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 <errno.h> +#include <signal.h> +#include <unistd.h> + +#include "unit.h" +#include "slice.h" +#include "load-fragment.h" +#include "log.h" +#include "dbus-slice.h" +#include "special.h" +#include "unit-name.h" + +static const UnitActiveState state_translation_table[_SLICE_STATE_MAX] = { + [SLICE_DEAD] = UNIT_INACTIVE, + [SLICE_ACTIVE] = UNIT_ACTIVE +}; + +static void slice_init(Unit *u) { + Slice *s = SLICE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + cgroup_context_init(&s->cgroup_context); +} + +static void slice_done(Unit *u) { + Slice *s = SLICE(u); + + assert(u); + + cgroup_context_done(&s->cgroup_context); +} + +static void slice_set_state(Slice *t, SliceState state) { + SliceState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + slice_state_to_string(old_state), + slice_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static int slice_add_parent_slice(Slice *s) { + char *a, *dash; + Unit *parent; + int r; + + assert(s); + + if (UNIT_ISSET(UNIT(s)->slice)) + return 0; + + if (unit_has_name(UNIT(s), SPECIAL_ROOT_SLICE)) + return 0; + + a = strdupa(UNIT(s)->id); + dash = strrchr(a, '-'); + if (dash) + strcpy(dash, ".slice"); + else + a = (char*) SPECIAL_ROOT_SLICE; + + r = manager_load_unit(UNIT(s)->manager, a, NULL, NULL, &parent); + if (r < 0) + return r; + + unit_ref_set(&UNIT(s)->slice, parent); + return 0; +} + +static int slice_add_default_dependencies(Slice *s) { + int r; + + assert(s); + + /* Make sure slices are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int slice_verify(Slice *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (UNIT_DEREF(UNIT(s)->slice)) { + char *a, *dash; + + a = strdupa(UNIT(s)->id); + dash = strrchr(a, '-'); + if (dash) + strcpy(dash, ".slice"); + else + a = (char*) SPECIAL_ROOT_SLICE; + + if (!unit_has_name(UNIT_DEREF(UNIT(s)->slice), a)) { + log_error_unit(UNIT(s)->id, + "%s located outside its parent slice. Refusing.", UNIT(s)->id); + return -EINVAL; + } + } + + return 0; +} + +static int slice_load(Unit *u) { + Slice *s = SLICE(u); + int r; + + assert(s); + + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + r = slice_add_parent_slice(s); + if (r < 0) + return r; + + if (u->default_dependencies) { + r = slice_add_default_dependencies(s); + if (r < 0) + return r; + } + } + + return slice_verify(s); +} + +static int slice_coldplug(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_DEAD); + + if (t->deserialized_state != t->state) + slice_set_state(t, t->deserialized_state); + + return 0; +} + +static void slice_dump(Unit *u, FILE *f, const char *prefix) { + Slice *t = SLICE(u); + + assert(t); + assert(f); + + fprintf(f, + "%sSlice State: %s\n", + prefix, slice_state_to_string(t->state)); + + cgroup_context_dump(&t->cgroup_context, f, prefix); +} + +static int slice_start(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_DEAD); + + unit_realize_cgroup(u); + + slice_set_state(t, SLICE_ACTIVE); + return 0; +} + +static int slice_stop(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_ACTIVE); + + /* We do not need to destroy the cgroup explicitly, + * unit_notify() will do that for us anyway. */ + + slice_set_state(t, SLICE_DEAD); + return 0; +} + +static int slice_kill(Unit *u, KillWho who, int signo, DBusError *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { + Slice *s = SLICE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", slice_state_to_string(s->state)); + return 0; +} + +static int slice_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Slice *s = SLICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + SliceState state; + + state = slice_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +_pure_ static UnitActiveState slice_active_state(Unit *u) { + assert(u); + + return state_translation_table[SLICE(u)->state]; +} + +_pure_ static const char *slice_sub_state_to_string(Unit *u) { + assert(u); + + return slice_state_to_string(SLICE(u)->state); +} + +static const char* const slice_state_table[_SLICE_STATE_MAX] = { + [SLICE_DEAD] = "dead", + [SLICE_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); + +const UnitVTable slice_vtable = { + .object_size = sizeof(Slice), + .sections = + "Unit\0" + "Slice\0" + "Install\0", + + .private_section = "Slice", + .cgroup_context_offset = offsetof(Slice, cgroup_context), + + .no_alias = true, + .no_instances = true, + + .init = slice_init, + .load = slice_load, + .done = slice_done, + + .coldplug = slice_coldplug, + + .dump = slice_dump, + + .start = slice_start, + .stop = slice_stop, + + .kill = slice_kill, + + .serialize = slice_serialize, + .deserialize_item = slice_deserialize_item, + + .active_state = slice_active_state, + .sub_state_to_string = slice_sub_state_to_string, + + .bus_interface = "org.freedesktop.systemd1.Slice", + .bus_message_handler = bus_slice_message_handler, + .bus_set_property = bus_slice_set_property, + .bus_commit_properties = bus_slice_commit_properties, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Created slice %s.", + [JOB_DEPENDENCY] = "Dependency failed for %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Removed slice %s.", + }, + }, +}; diff --git a/src/core/slice.h b/src/core/slice.h new file mode 100644 index 0000000000..ad0c63902b --- /dev/null +++ b/src/core/slice.h @@ -0,0 +1,46 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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/>. +***/ + +typedef struct Slice Slice; + +#include "unit.h" + +typedef enum SliceState { + SLICE_DEAD, + SLICE_ACTIVE, + _SLICE_STATE_MAX, + _SLICE_STATE_INVALID = -1 +} SliceState; + +struct Slice { + Unit meta; + + SliceState state, deserialized_state; + + CGroupContext cgroup_context; +}; + +extern const UnitVTable slice_vtable; + +const char* slice_state_to_string(SliceState i) _const_; +SliceState slice_state_from_string(const char *s) _pure_; diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c index 73eeb04190..1434dea7c1 100644 --- a/src/core/smack-setup.c +++ b/src/core/smack-setup.c @@ -40,7 +40,9 @@ #include "label.h" #define SMACK_CONFIG "/etc/smack/accesses.d/" -#define CIPSO_CONFIG "/etc/smack/cipso/" +#define CIPSO_CONFIG "/etc/smack/cipso.d/" + +#ifdef HAVE_SMACK static int write_rules(const char* dstpath, const char* srcdir) { _cleanup_fclose_ FILE *dst = NULL; @@ -111,8 +113,12 @@ static int write_rules(const char* dstpath, const char* srcdir) { return r; } +#endif int smack_setup(void) { + +#ifdef HAVE_SMACK + int r; r = write_rules("/sys/fs/smackfs/load2", SMACK_CONFIG); @@ -148,4 +154,8 @@ int smack_setup(void) { strerror(abs(r))); return 0; } + +#endif + + return 0; } diff --git a/src/core/snapshot.c b/src/core/snapshot.c index a63eccd8de..d11239dff3 100644 --- a/src/core/snapshot.c +++ b/src/core/snapshot.c @@ -40,6 +40,7 @@ static void snapshot_init(Unit *u) { UNIT(s)->ignore_on_isolate = true; UNIT(s)->ignore_on_snapshot = true; + UNIT(s)->allow_isolate = true; } static void snapshot_set_state(Snapshot *s, SnapshotState state) { @@ -66,7 +67,7 @@ static int snapshot_load(Unit *u) { /* Make sure that only snapshots created via snapshot_create() * can be loaded */ - if (!s->by_snapshot_create && UNIT(s)->manager->n_reloading <= 0) + if (!u->transient && UNIT(s)->manager->n_reloading <= 0) return -ENOENT; u->load_state = UNIT_LOADED; @@ -151,21 +152,24 @@ static int snapshot_deserialize_item(Unit *u, const char *key, const char *value if (streq(key, "state")) { SnapshotState state; - if ((state = snapshot_state_from_string(value)) < 0) + state = snapshot_state_from_string(value); + if (state < 0) log_debug("Failed to parse state value %s", value); else s->deserialized_state = state; } else if (streq(key, "cleanup")) { - if ((r = parse_boolean(value)) < 0) + r = parse_boolean(value); + if (r < 0) log_debug("Failed to parse cleanup value %s", value); else s->cleanup = r; } else if (streq(key, "wants")) { - if ((r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, value, NULL, true)) < 0) + r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, value, NULL, true); + if (r < 0) return r; } else log_debug("Unknown serialization key '%s'", key); @@ -186,9 +190,9 @@ _pure_ static const char *snapshot_sub_state_to_string(Unit *u) { } int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Snapshot **_s) { - Iterator i; + _cleanup_free_ char *n = NULL; Unit *other, *u = NULL; - char *n = NULL; + Iterator i; int r; const char *k; @@ -217,28 +221,28 @@ int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Sn if (asprintf(&n, "snapshot-%u.snapshot", ++ m->n_snapshots) < 0) return -ENOMEM; - if (!manager_get_unit(m, n)) + if (!manager_get_unit(m, n)) { + name = n; break; + } free(n); + n = NULL; } - - name = n; } r = manager_load_unit_prepare(m, name, NULL, e, &u); - free(n); - if (r < 0) goto fail; - SNAPSHOT(u)->by_snapshot_create = true; + u->transient = true; manager_dispatch_load_queue(m); assert(u->load_state == UNIT_LOADED); HASHMAP_FOREACH_KEY(other, k, m->units, i) { - if (other->ignore_on_snapshot) + if (other->ignore_on_snapshot || + other->transient) continue; if (k != other->id) @@ -251,12 +255,12 @@ int snapshot_create(Manager *m, const char *name, bool cleanup, DBusError *e, Sn if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) continue; - if ((r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, other, true)) < 0) + r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, other, true); + if (r < 0) goto fail; } SNAPSHOT(u)->cleanup = cleanup; - u->allow_isolate = true; *_s = SNAPSHOT(u); return 0; diff --git a/src/core/snapshot.h b/src/core/snapshot.h index 56f87cff4d..2675b1b242 100644 --- a/src/core/snapshot.h +++ b/src/core/snapshot.h @@ -38,7 +38,6 @@ struct Snapshot { SnapshotState state, deserialized_state; bool cleanup; - bool by_snapshot_create:1; }; extern const UnitVTable snapshot_vtable; diff --git a/src/core/socket.c b/src/core/socket.c index 1b08f0a5fd..6c0ac1a898 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -88,6 +88,7 @@ static void socket_init(Unit *u) { s->exec_context.std_output = u->manager->default_std_output; s->exec_context.std_error = u->manager->default_std_error; kill_context_init(&s->kill_context); + cgroup_context_init(&s->cgroup_context); s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; } @@ -128,6 +129,8 @@ static void socket_done(Unit *u) { socket_free_ports(s); exec_context_done(&s->exec_context, manager_is_reloading_or_reexecuting(u->manager)); + cgroup_context_init(&s->cgroup_context); + exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); s->control_command = NULL; @@ -255,53 +258,24 @@ static int socket_verify(Socket *s) { return 0; } -static bool socket_needs_mount(Socket *s, const char *prefix) { +static int socket_add_mount_links(Socket *s) { SocketPort *p; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - - if (p->type == SOCKET_SOCKET) { - if (socket_address_needs_mount(&p->address, prefix)) - return true; - } else if (p->type == SOCKET_FIFO || p->type == SOCKET_SPECIAL) { - if (path_startswith(p->path, prefix)) - return true; - } - } - - return false; -} - -int socket_add_one_mount_link(Socket *s, Mount *m) { int r; assert(s); - assert(m); - if (UNIT(s)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!socket_needs_mount(s, m->where)) - return 0; - - r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); - if (r < 0) - return r; - - return 0; -} + LIST_FOREACH(port, p, s->ports) { + const char *path = NULL; -static int socket_add_mount_links(Socket *s) { - Unit *other; - int r; + if (p->type == SOCKET_SOCKET) + path = socket_address_get_path(&p->address); + else if (p->type == SOCKET_FIFO || p->type == SOCKET_SPECIAL) + path = p->path; - assert(s); + if (!path) + continue; - LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) { - r = socket_add_one_mount_link(s, MOUNT(other)); + r = unit_require_mounts_for(UNIT(s), path); if (r < 0) return r; } @@ -395,7 +369,8 @@ static int socket_load(Unit *u) { if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0) return r; - if ((r = unit_add_default_cgroups(u)) < 0) + r = unit_add_default_slice(u); + if (r < 0) return r; if (UNIT(s)->default_dependencies) @@ -532,6 +507,11 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { "%sMessageQueueMessageSize: %li\n", prefix, s->mq_msgsize); + if (s->reuseport) + fprintf(f, + "%sReusePort: %s\n", + prefix, yes_no(s->reuseport)); + if (s->smack) fprintf(f, "%sSmackLabel: %s\n", @@ -788,7 +768,13 @@ static void socket_apply_socket_options(Socket *s, int fd) { if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) log_warning_unit(UNIT(s)->id, "TCP_CONGESTION failed: %m"); -#ifdef HAVE_XATTR + if (s->reuseport) { + int b = s->reuseport; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &b, sizeof(b))) + log_warning_unit(UNIT(s)->id, "SO_REUSEPORT failed: %m"); + } + +#ifdef HAVE_SMACK if (s->smack_ip_in) if (fsetxattr(fd, "security.SMACK64IPIN", s->smack_ip_in, strlen(s->smack_ip_in), 0) < 0) log_error_unit(UNIT(s)->id, @@ -810,7 +796,7 @@ static void socket_apply_fifo_options(Socket *s, int fd) { log_warning_unit(UNIT(s)->id, "F_SETPIPE_SZ: %m"); -#ifdef HAVE_XATTR +#ifdef HAVE_SMACK if (s->smack) if (fsetxattr(fd, "security.SMACK64", s->smack, strlen(s->smack), 0) < 0) log_error_unit(UNIT(s)->id, @@ -1000,7 +986,7 @@ static int socket_open_fds(Socket *s) { if ((r = socket_instantiate_service(s)) < 0) return r; - if (UNIT_DEREF(s->service) && + if (UNIT_ISSET(s->service) && SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) { r = label_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label); @@ -1205,15 +1191,15 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { assert(c); assert(_pid); + unit_realize_cgroup(UNIT(s)); + r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_usec, &s->timer_watch); if (r < 0) goto fail; - argv = unit_full_printf_strv(UNIT(s), c->argv); - if (!argv) { - r = -ENOMEM; + r = unit_full_printf_strv(UNIT(s), c->argv, &argv); + if (r < 0) goto fail; - } r = exec_spawn(c, argv, @@ -1224,9 +1210,8 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { true, true, UNIT(s)->manager->confirm_spawn, - UNIT(s)->cgroup_bondings, - UNIT(s)->cgroup_attributes, - NULL, + UNIT(s)->manager->cgroup_supported, + UNIT(s)->cgroup_path, UNIT(s)->id, NULL, &pid); @@ -1628,7 +1613,7 @@ static int socket_start(Unit *u) { return 0; /* Cannot run this without the service being around */ - if (UNIT_DEREF(s->service)) { + if (UNIT_ISSET(s->service)) { Service *service; service = SERVICE(UNIT_DEREF(s->service)); @@ -2261,7 +2246,7 @@ int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds) { return 0; } -void socket_notify_service_dead(Socket *s, bool failed_permanent) { +static void socket_notify_service_dead(Socket *s, bool failed_permanent) { assert(s); /* The service is dead. Dang! @@ -2306,6 +2291,41 @@ static void socket_reset_failed(Unit *u) { s->result = SOCKET_SUCCESS; } +static void socket_trigger_notify(Unit *u, Unit *other) { + Socket *s = SOCKET(u); + Service *se = SERVICE(other); + + assert(u); + assert(other); + + /* Don't propagate state changes from the service if we are + already down or accepting connections */ + if ((s->state != SOCKET_RUNNING && + s->state != SOCKET_LISTENING) || + s->accept) + return; + + if (other->load_state != UNIT_LOADED || + other->type != UNIT_SERVICE) + return; + + if (se->state == SERVICE_FAILED) + socket_notify_service_dead(s, se->result == SERVICE_FAILURE_START_LIMIT); + + if (se->state == SERVICE_DEAD || + se->state == SERVICE_STOP || + se->state == SERVICE_STOP_SIGTERM || + se->state == SERVICE_STOP_SIGKILL || + se->state == SERVICE_STOP_POST || + se->state == SERVICE_FINAL_SIGTERM || + se->state == SERVICE_FINAL_SIGKILL || + se->state == SERVICE_AUTO_RESTART) + socket_notify_service_dead(s, false); + + if (se->state == SERVICE_RUNNING) + socket_set_state(s, SOCKET_RUNNING); +} + static int socket_kill(Unit *u, KillWho who, int signo, DBusError *error) { return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error); } @@ -2356,8 +2376,9 @@ const UnitVTable socket_vtable = { "Socket\0" "Install\0", + .private_section = "Socket", .exec_context_offset = offsetof(Socket, exec_context), - .exec_section = "Socket", + .cgroup_context_offset = offsetof(Socket, cgroup_context), .init = socket_init, .done = socket_done, @@ -2385,11 +2406,15 @@ const UnitVTable socket_vtable = { .sigchld_event = socket_sigchld_event, .timer_event = socket_timer_event, + .trigger_notify = socket_trigger_notify, + .reset_failed = socket_reset_failed, .bus_interface = "org.freedesktop.systemd1.Socket", .bus_message_handler = bus_socket_message_handler, .bus_invalidating_properties = bus_socket_invalidating_properties, + .bus_set_property = bus_socket_set_property, + .bus_commit_properties = bus_socket_commit_properties, .status_message_formats = { /*.starting_stopping = { diff --git a/src/core/socket.h b/src/core/socket.h index 9d48cde0a6..3d7eadc9fe 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -102,6 +102,7 @@ struct Socket { ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; ExecContext exec_context; KillContext kill_context; + CGroupContext cgroup_context; /* For Accept=no sockets refers to the one service we'll activate. For Accept=yes sockets is either NULL, or filled @@ -143,6 +144,7 @@ struct Socket { size_t pipe_size; char *bind_to_device; char *tcp_congestion; + bool reuseport; long mq_maxmsg; long mq_msgsize; @@ -154,13 +156,6 @@ struct Socket { /* Called from the service code when collecting fds */ int socket_collect_fds(Socket *s, int **fds, unsigned *n_fds); -/* Called from the service when it shut down */ -void socket_notify_service_dead(Socket *s, bool failed_permanent); - -/* Called from the mount code figure out if a mount is a dependency of - * any of the sockets of this socket */ -int socket_add_one_mount_link(Socket *s, Mount *m); - /* Called from the service code when a per-connection service ended */ void socket_connection_unref(Socket *s); diff --git a/src/core/special.h b/src/core/special.h index a9b50bce05..6d252e7baa 100644 --- a/src/core/special.h +++ b/src/core/special.h @@ -113,3 +113,9 @@ #define SPECIAL_RUNLEVEL3_TARGET "runlevel3.target" #define SPECIAL_RUNLEVEL4_TARGET "runlevel4.target" #define SPECIAL_RUNLEVEL5_TARGET "runlevel5.target" + +/* Where we add all our system units, users and machines by default */ +#define SPECIAL_SYSTEM_SLICE "system.slice" +#define SPECIAL_USER_SLICE "user.slice" +#define SPECIAL_MACHINE_SLICE "machine.slice" +#define SPECIAL_ROOT_SLICE "-.slice" diff --git a/src/core/swap.c b/src/core/swap.c index d503fe20df..a68ab7cdf8 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -92,6 +92,7 @@ static void swap_init(Unit *u) { s->exec_context.std_output = u->manager->default_std_output; s->exec_context.std_error = u->manager->default_std_error; kill_context_init(&s->kill_context); + cgroup_context_init(&s->cgroup_context); s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; @@ -129,47 +130,13 @@ static void swap_done(Unit *u) { exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); s->control_command = NULL; + cgroup_context_done(&s->cgroup_context); + swap_unwatch_control_pid(s); unit_unwatch_timer(u, &s->timer_watch); } -int swap_add_one_mount_link(Swap *s, Mount *m) { - int r; - - assert(s); - assert(m); - - if (UNIT(s)->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (is_device_path(s->what)) - return 0; - - if (!path_startswith(s->what, m->where)) - return 0; - - r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); - if (r < 0) - return r; - - return 0; -} - -static int swap_add_mount_links(Swap *s) { - Unit *other; - int r; - - assert(s); - - LIST_FOREACH(units_by_type, other, UNIT(s)->manager->units_by_type[UNIT_MOUNT]) - if ((r = swap_add_one_mount_link(s, MOUNT(other))) < 0) - return r; - - return 0; -} - static int swap_add_device_links(Swap *s) { SwapParameters *p; @@ -184,8 +151,7 @@ static int swap_add_device_links(Swap *s) { return 0; if (is_device_path(s->what)) - return unit_add_node_link(UNIT(s), s->what, - !p->noauto && p->nofail && + return unit_add_node_link(UNIT(s), s->what, !p->noauto && UNIT(s)->manager->running_as == SYSTEMD_SYSTEM); else /* File based swap devices need to be ordered after @@ -195,6 +161,7 @@ static int swap_add_device_links(Swap *s) { } static int swap_add_default_dependencies(Swap *s) { + bool nofail = false, noauto = false; int r; assert(s); @@ -209,6 +176,24 @@ static int swap_add_default_dependencies(Swap *s) { if (r < 0) return r; + if (s->from_fragment) { + SwapParameters *p = &s->parameters_fragment; + + nofail = p->nofail; + noauto = p->noauto; + } + + if (!noauto) { + if (nofail) + r = unit_add_dependency_by_name_inverse(UNIT(s), + UNIT_WANTS, SPECIAL_SWAP_TARGET, NULL, true); + else + r = unit_add_two_dependencies_by_name_inverse(UNIT(s), + UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SWAP_TARGET, NULL, true); + if (r < 0) + return r; + } + return 0; } @@ -279,15 +264,15 @@ static int swap_load(Unit *u) { if ((r = unit_set_description(u, s->what)) < 0) return r; - r = swap_add_device_links(s); + r = unit_require_mounts_for(UNIT(s), s->what); if (r < 0) return r; - r = swap_add_mount_links(s); + r = swap_add_device_links(s); if (r < 0) return r; - r = unit_add_default_cgroups(u); + r = unit_add_default_slice(u); if (r < 0) return r; @@ -589,6 +574,8 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { assert(c); assert(_pid); + unit_realize_cgroup(UNIT(s)); + r = unit_watch_timer(UNIT(s), CLOCK_MONOTONIC, true, s->timeout_usec, &s->timer_watch); if (r < 0) goto fail; @@ -602,9 +589,8 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { true, true, UNIT(s)->manager->confirm_spawn, - UNIT(s)->cgroup_bondings, - UNIT(s)->cgroup_attributes, - NULL, + UNIT(s)->manager->cgroup_supported, + UNIT(s)->cgroup_path, UNIT(s)->id, NULL, &pid); @@ -1052,7 +1038,7 @@ static int swap_load_proc_swaps(Manager *m, bool set_flags) { (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n"); for (i = 1;; i++) { - char *dev = NULL, *d; + _cleanup_free_ char *dev = NULL, *d = NULL; int prio = 0, k; k = fscanf(m->proc_swaps, @@ -1067,19 +1053,14 @@ static int swap_load_proc_swaps(Manager *m, bool set_flags) { break; log_warning("Failed to parse /proc/swaps:%u", i); - free(dev); continue; } d = cunescape(dev); - free(dev); - if (!d) return -ENOMEM; k = swap_process_new_swap(m, d, prio, set_flags); - free(d); - if (k < 0) r = k; } @@ -1323,8 +1304,9 @@ const UnitVTable swap_vtable = { "Swap\0" "Install\0", + .private_section = "Swap", .exec_context_offset = offsetof(Swap, exec_context), - .exec_section = "Swap", + .cgroup_context_offset = offsetof(Swap, cgroup_context), .no_alias = true, .no_instances = true, @@ -1358,6 +1340,8 @@ const UnitVTable swap_vtable = { .bus_interface = "org.freedesktop.systemd1.Swap", .bus_message_handler = bus_swap_message_handler, .bus_invalidating_properties = bus_swap_invalidating_properties, + .bus_set_property = bus_swap_set_property, + .bus_commit_properties = bus_swap_commit_properties, .following = swap_following, .following_set = swap_following_set, diff --git a/src/core/swap.h b/src/core/swap.h index 121889d1d5..dd89535895 100644 --- a/src/core/swap.h +++ b/src/core/swap.h @@ -88,6 +88,7 @@ struct Swap { ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; ExecContext exec_context; KillContext kill_context; + CGroupContext cgroup_context; SwapState state, deserialized_state; @@ -106,8 +107,6 @@ struct Swap { extern const UnitVTable swap_vtable; -int swap_add_one_mount_link(Swap *s, Mount *m); - int swap_dispatch_reload(Manager *m); int swap_fd_event(Manager *m, int events); diff --git a/src/core/system.conf b/src/core/system.conf index 508e0f5fa4..7b03c8782b 100644 --- a/src/core/system.conf +++ b/src/core/system.conf @@ -17,14 +17,14 @@ #ShowStatus=yes #CrashChVT=1 #CPUAffinity=1 2 -#DefaultControllers=cpu #DefaultStandardOutput=journal #DefaultStandardError=inherit -#JoinControllers=cpu,cpuacct,cpuset net_cls,net_prio +#JoinControllers=cpu,cpuacct net_cls,net_prio #RuntimeWatchdogSec=0 #ShutdownWatchdogSec=10min #CapabilityBoundingSet= #TimerSlackNSec= +#DefaultEnvironment= #DefaultLimitCPU= #DefaultLimitFSIZE= #DefaultLimitDATA= diff --git a/src/core/systemd.pc.in b/src/core/systemd.pc.in index 2f49d5df52..de0f6494e9 100644 --- a/src/core/systemd.pc.in +++ b/src/core/systemd.pc.in @@ -16,6 +16,9 @@ systemdsystemconfdir=@pkgsysconfdir@/system systemduserconfdir=@pkgsysconfdir@/user systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user +systemdsystemgeneratordir=@systemgeneratordir@ +systemdusergeneratordir=@usergeneratordir@ +catalogdir=@catalogdir@ Name: systemd Description: systemd System and Service Manager diff --git a/src/core/transaction.c b/src/core/transaction.c index fa97b69755..203070fa26 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -344,7 +344,7 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi assert(!j->transaction_prev); /* Does a recursive sweep through the ordering graph, looking - * for a cycle. If we find cycle we try to break it. */ + * for a cycle. If we find a cycle we try to break it. */ /* Have we seen this before? */ if (j->generation == generation) { @@ -371,7 +371,7 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi /* logging for j not k here here to provide consistent narrative */ log_info_unit(j->unit->id, - "Walked on cycle path to %s/%s", + "Found dependency on %s/%s", k->unit->id, job_type_to_string(k->type)); if (!delete && @@ -733,8 +733,11 @@ int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e * feature for cosmetics, not actually useful for * anything beyond that. */ - if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0) + if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0 && + m->idle_pipe[2] < 0 && m->idle_pipe[3] < 0) { pipe2(m->idle_pipe, O_NONBLOCK|O_CLOEXEC); + pipe2(m->idle_pipe + 2, O_NONBLOCK|O_CLOEXEC); + } } return 0; @@ -851,6 +854,7 @@ int transaction_add_job_and_dependencies( if (unit->load_state != UNIT_LOADED && unit->load_state != UNIT_ERROR && + unit->load_state != UNIT_NOT_FOUND && unit->load_state != UNIT_MASKED) { dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); return -EINVAL; @@ -866,6 +870,14 @@ int transaction_add_job_and_dependencies( return -EINVAL; } + if (type != JOB_STOP && unit->load_state == UNIT_NOT_FOUND) { + dbus_set_error(e, BUS_ERROR_LOAD_FAILED, + "Unit %s failed to load: %s.", + unit->id, + strerror(-unit->load_error)); + return -EINVAL; + } + if (type != JOB_STOP && unit->load_state == UNIT_MASKED) { dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id); return -EADDRNOTAVAIL; diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c index 85a05b872a..1a29a986e9 100644 --- a/src/core/unit-printf.c +++ b/src/core/unit-printf.c @@ -27,99 +27,161 @@ #include "unit-name.h" #include "unit-printf.h" #include "macro.h" +#include "cgroup-util.h" +#include "special.h" -static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) { +static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; + char *n; + assert(u); - return unit_name_to_prefix_and_instance(u->id); + n = unit_name_to_prefix_and_instance(u->id); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -static char *specifier_prefix(char specifier, void *data, void *userdata) { +static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; + char *n; + assert(u); - return unit_name_to_prefix(u->id); + n = unit_name_to_prefix(u->id); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -static char *specifier_prefix_unescaped(char specifier, void *data, void *userdata) { +static int specifier_prefix_unescaped(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; - char *p, *r; + _cleanup_free_ char *p = NULL; + char *n; assert(u); p = unit_name_to_prefix(u->id); if (!p) - return NULL; + return -ENOMEM; - r = unit_name_unescape(p); - free(p); + n = unit_name_unescape(p); + if (!n) + return -ENOMEM; - return r; + *ret = n; + return 0; } -static char *specifier_instance_unescaped(char specifier, void *data, void *userdata) { +static int specifier_instance_unescaped(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; + char *n; + assert(u); if (u->instance) - return unit_name_unescape(u->instance); + n = unit_name_unescape(u->instance); + else + n = strdup(""); + + if (!n) + return -ENOMEM; - return strdup(""); + *ret = n; + return 0; } -static char *specifier_filename(char specifier, void *data, void *userdata) { +static int specifier_filename(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; + char *n; + assert(u); if (u->instance) - return unit_name_path_unescape(u->instance); + n = unit_name_path_unescape(u->instance); + else + n = unit_name_to_path(u->id); - return unit_name_to_path(u->id); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -static char *specifier_cgroup(char specifier, void *data, void *userdata) { +static int specifier_cgroup(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; + char *n; + assert(u); - return unit_default_cgroup_path(u); + n = unit_default_cgroup_path(u); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -static char *specifier_cgroup_root(char specifier, void *data, void *userdata) { +static int specifier_cgroup_root(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; - char *p; + const char *slice; + char *n; + int r; + assert(u); - if (specifier == 'r') - return strdup(u->manager->cgroup_hierarchy); + slice = unit_slice_name(u); + if (specifier == 'R' || !slice) + n = strdup(u->manager->cgroup_root); + else { + _cleanup_free_ char *p = NULL; - if (path_get_parent(u->manager->cgroup_hierarchy, &p) < 0) - return strdup(""); + r = cg_slice_to_path(slice, &p); + if (r < 0) + return r; - if (streq(p, "/")) { - free(p); - return strdup(""); + n = strjoin(u->manager->cgroup_root, "/", p, NULL); + if (!n) + return -ENOMEM; } - return p; + *ret = n; + return 0; } -static char *specifier_runtime(char specifier, void *data, void *userdata) { +static int specifier_runtime(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; + char *n = NULL; + assert(u); if (u->manager->running_as == SYSTEMD_USER) { const char *e; e = getenv("XDG_RUNTIME_DIR"); - if (e) - return strdup(e); + if (e) { + n = strdup(e); + if (!n) + return -ENOMEM; + } + } + + if (!n) { + n = strdup("/run"); + if (!n) + return -ENOMEM; } - return strdup("/run"); + *ret = n; + return 0; } -static char *specifier_user_name(char specifier, void *data, void *userdata) { +static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; ExecContext *c; int r; @@ -141,26 +203,31 @@ static char *specifier_user_name(char specifier, void *data, void *userdata) { /* fish username from passwd */ r = get_user_creds(&username, &uid, NULL, NULL, NULL); if (r < 0) - return NULL; + return r; switch (specifier) { case 'U': if (asprintf(&printed, "%d", uid) < 0) - return NULL; + return -ENOMEM; break; case 'u': printed = strdup(username); break; } - return printed; + if (!printed) + return -ENOMEM; + + *ret = printed; + return 0; } -static char *specifier_user_home(char specifier, void *data, void *userdata) { +static int specifier_user_home(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; ExecContext *c; int r; const char *username, *home; + char *n; assert(u); @@ -172,25 +239,31 @@ static char *specifier_user_home(char specifier, void *data, void *userdata) { r = get_home_dir(&h); if (r < 0) - return NULL; + return r; - return h; + *ret = h; + return 0; } username = c->user; r = get_user_creds(&username, NULL, NULL, &home, NULL); if (r < 0) - return NULL; + return r; + + n = strdup(home); + if (!n) + return -ENOMEM; - return strdup(home); + *ret = n; + return 0; } -static char *specifier_user_shell(char specifier, void *data, void *userdata) { +static int specifier_user_shell(char specifier, void *data, void *userdata, char **ret) { Unit *u = userdata; ExecContext *c; int r; const char *username, *shell; - char *ret; + char *n; assert(u); @@ -203,27 +276,18 @@ static char *specifier_user_shell(char specifier, void *data, void *userdata) { /* return /bin/sh for root, otherwise the value from passwd */ r = get_user_creds(&username, NULL, NULL, NULL, &shell); - if (r < 0) { - log_warning_unit(u->id, - "Failed to determine shell: %s", - strerror(-r)); - return NULL; - } - - if (!path_is_absolute(shell)) { - log_warning_unit(u->id, - "Shell %s is not absolute, ignoring.", - shell); - } + if (r < 0) + return r; - ret = strdup(shell); - if (!ret) - log_oom(); + n = strdup(shell); + if (!n) + return -ENOMEM; - return ret; + *ret = n; + return 0; } -char *unit_name_printf(Unit *u, const char* format) { +int unit_name_printf(Unit *u, const char* format, char **ret) { /* * This will use the passed string as format string and @@ -245,19 +309,20 @@ char *unit_name_printf(Unit *u, const char* format) { assert(u); assert(format); + assert(ret); - return specifier_printf(format, table, u); + return specifier_printf(format, table, u, ret); } -char *unit_full_printf(Unit *u, const char *format) { +int unit_full_printf(Unit *u, const char *format, char **ret) { /* This is similar to unit_name_printf() but also supports * unescaping. Also, adds a couple of additional codes: * * %f the the instance if set, otherwise the id * %c cgroup path of unit - * %r root cgroup path of this systemd instance (e.g. "/user/lennart/shared/systemd-4711") - * %R parent of root cgroup path (e.g. "/usr/lennart/shared") + * %r where units in this slice are place in the cgroup tree + * %R the root of this systemd's instance tree * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) * %U the UID of the configured user or running user * %u the username of the configured user or running user @@ -266,6 +331,7 @@ char *unit_full_printf(Unit *u, const char *format) { * %m the machine ID of the running system * %H the host name of the running system * %b the boot ID of the running system + * %v `uname -r` of the running system */ const Specifier table[] = { @@ -289,17 +355,21 @@ char *unit_full_printf(Unit *u, const char *format) { { 'm', specifier_machine_id, NULL }, { 'H', specifier_host_name, NULL }, { 'b', specifier_boot_id, NULL }, - { 0, NULL, NULL } + { 'v', specifier_kernel_release, NULL }, + {} }; + assert(u); assert(format); + assert(ret); - return specifier_printf(format, table, u); + return specifier_printf(format, table, u, ret); } -char **unit_full_printf_strv(Unit *u, char **l) { +int unit_full_printf_strv(Unit *u, char **l, char ***ret) { size_t n; char **r, **i, **j; + int q; /* Applies unit_full_printf to every entry in l */ @@ -308,22 +378,22 @@ char **unit_full_printf_strv(Unit *u, char **l) { n = strv_length(l); r = new(char*, n+1); if (!r) - return NULL; + return -ENOMEM; for (i = l, j = r; *i; i++, j++) { - *j = unit_full_printf(u, *i); - if (!*j) + q = unit_full_printf(u, *i, j); + if (q < 0) goto fail; } *j = NULL; - return r; + *ret = r; + return 0; fail: for (j--; j >= r; j--) free(*j); free(r); - - return NULL; + return q; } diff --git a/src/core/unit-printf.h b/src/core/unit-printf.h index d2f4ccd178..51acad63e9 100644 --- a/src/core/unit-printf.h +++ b/src/core/unit-printf.h @@ -23,6 +23,6 @@ #include "unit.h" -char *unit_name_printf(Unit *u, const char* text); -char *unit_full_printf(Unit *u, const char *text); -char **unit_full_printf_strv(Unit *u, char **l); +int unit_name_printf(Unit *u, const char* text, char **ret); +int unit_full_printf(Unit *u, const char *text, char **ret); +int unit_full_printf_strv(Unit *u, char **l, char ***ret); diff --git a/src/core/unit.c b/src/core/unit.c index 9b36b225fa..4b9771076a 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -44,7 +44,6 @@ #include "special.h" #include "cgroup-util.h" #include "missing.h" -#include "cgroup-attr.h" #include "mkdir.h" #include "label.h" #include "fileio-label.h" @@ -60,7 +59,9 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_AUTOMOUNT] = &automount_vtable, [UNIT_SNAPSHOT] = &snapshot_vtable, [UNIT_SWAP] = &swap_vtable, - [UNIT_PATH] = &path_vtable + [UNIT_PATH] = &path_vtable, + [UNIT_SLICE] = &slice_vtable, + [UNIT_SCOPE] = &scope_vtable }; Unit *unit_new(Manager *m, size_t size) { @@ -188,7 +189,8 @@ fail: } int unit_choose_id(Unit *u, const char *name) { - char *s, *t = NULL, *i; + char *s, *i; + _cleanup_free_ char *t = NULL; int r; assert(u); @@ -207,7 +209,6 @@ int unit_choose_id(Unit *u, const char *name) { /* Selects one of the names of this unit as the id */ s = set_get(u->names, (char*) name); - free(t); if (!s) return -ENOENT; @@ -230,8 +231,13 @@ int unit_set_description(Unit *u, const char *description) { assert(u); - if (!(s = strdup(description))) - return -ENOMEM; + if (isempty(description)) + s = NULL; + else { + s = strdup(description); + if (!s) + return -ENOMEM; + } free(u->description); u->description = s; @@ -305,9 +311,6 @@ void unit_add_to_gc_queue(Unit *u) { u->in_gc_queue = true; u->manager->n_in_gc_queue ++; - - if (u->manager->gc_queue_timestamp <= 0) - u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC); } void unit_add_to_dbus_queue(Unit *u) { @@ -348,6 +351,57 @@ static void bidi_set_free(Unit *u, Set *s) { set_free(s); } +static void unit_remove_transient(Unit *u) { + char **i; + + assert(u); + + if (!u->transient) + return; + + if (u->fragment_path) + unlink(u->fragment_path); + + STRV_FOREACH(i, u->dropin_paths) { + _cleanup_free_ char *p = NULL; + int r; + + unlink(*i); + + r = path_get_parent(*i, &p); + if (r >= 0) + rmdir(p); + } +} + +static void unit_free_requires_mounts_for(Unit *u) { + char **j; + + STRV_FOREACH(j, u->requires_mounts_for) { + char s[strlen(*j) + 1]; + + PATH_FOREACH_PREFIX_MORE(s, *j) { + char *y; + Set *x; + + x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + if (!x) + continue; + + set_remove(x, u); + + if (set_isempty(x)) { + hashmap_remove(u->manager->units_requiring_mounts_for, y); + free(y); + set_free(x); + } + } + } + + strv_free(u->requires_mounts_for); + u->requires_mounts_for = NULL; +} + void unit_free(Unit *u) { UnitDependency d; Iterator i; @@ -355,12 +409,17 @@ void unit_free(Unit *u) { assert(u); + if (u->manager->n_reloading <= 0) + unit_remove_transient(u); + bus_unit_send_removed_signal(u); if (u->load_state != UNIT_STUB) if (UNIT_VTABLE(u)->done) UNIT_VTABLE(u)->done(u); + unit_free_requires_mounts_for(u); + SET_FOREACH(t, u->names, i) hashmap_remove_value(u->manager->units, t, u); @@ -379,11 +438,6 @@ void unit_free(Unit *u) { for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) bidi_set_free(u, u->dependencies[d]); - if (u->requires_mounts_for) { - LIST_REMOVE(Unit, has_requires_mounts_for, u->manager->has_requires_mounts_for, u); - strv_free(u->requires_mounts_for); - } - if (u->type != _UNIT_TYPE_INVALID) LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u); @@ -401,8 +455,13 @@ void unit_free(Unit *u) { u->manager->n_in_gc_queue--; } - cgroup_bonding_free_list(u->cgroup_bondings, u->manager->n_reloading <= 0); - cgroup_attribute_free_list(u->cgroup_attributes); + if (u->in_cgroup_queue) + LIST_REMOVE(Unit, cgroup_queue, u->manager->cgroup_queue, u); + + if (u->cgroup_path) { + hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); + free(u->cgroup_path); + } free(u->description); strv_free(u->documentation); @@ -415,6 +474,8 @@ void unit_free(Unit *u) { condition_free_list(u->conditions); + unit_ref_unset(&u->slice); + while (u->refs) unit_ref_unset(u->refs); @@ -521,7 +582,7 @@ int unit_merge(Unit *u, Unit *other) { return -EINVAL; if (other->load_state != UNIT_STUB && - other->load_state != UNIT_ERROR) + other->load_state != UNIT_NOT_FOUND) return -EEXIST; if (other->job) @@ -562,7 +623,7 @@ int unit_merge(Unit *u, Unit *other) { int unit_merge_by_name(Unit *u, const char *name) { Unit *other; int r; - char *s = NULL; + _cleanup_free_ char *s = NULL; assert(u); assert(name); @@ -577,12 +638,12 @@ int unit_merge_by_name(Unit *u, const char *name) { name = s; } - if (!(other = manager_get_unit(u->manager, name))) + other = manager_get_unit(u->manager, name); + if (!other) r = unit_add_name(u, name); else r = unit_merge(u, other); - free(s); return r; } @@ -640,7 +701,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { char *t, **j; UnitDependency d; Iterator i; - char *p2; + _cleanup_free_ char *p2 = NULL; const char *prefix2; char timestamp1[FORMAT_TIMESTAMP_MAX], @@ -669,7 +730,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tActive Exit Timestamp: %s\n" "%s\tInactive Enter Timestamp: %s\n" "%s\tGC Check Good: %s\n" - "%s\tNeed Daemon Reload: %s\n", + "%s\tNeed Daemon Reload: %s\n" + "%s\tTransient: %s\n" + "%s\tSlice: %s\n" + "%s\tCGroup: %s\n" + "%s\tCGroup realized: %s\n" + "%s\tCGroup mask: 0x%x\n", prefix, u->id, prefix, unit_description(u), prefix, strna(u->instance), @@ -680,7 +746,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), prefix, yes_no(unit_check_gc(u)), - prefix, yes_no(unit_need_daemon_reload(u))); + prefix, yes_no(unit_need_daemon_reload(u)), + prefix, yes_no(u->transient), + prefix, strna(unit_slice_name(u)), + prefix, strna(u->cgroup_path), + prefix, yes_no(u->cgroup_realized), + prefix, u->cgroup_mask); SET_FOREACH(t, u->names, i) fprintf(f, "%s\tName: %s\n", prefix, t); @@ -730,8 +801,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { } if (u->load_state == UNIT_LOADED) { - CGroupBonding *b; - CGroupAttribute *a; fprintf(f, "%s\tStopWhenUnneeded: %s\n" @@ -749,20 +818,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(u->ignore_on_isolate), prefix, yes_no(u->ignore_on_snapshot)); - LIST_FOREACH(by_unit, b, u->cgroup_bondings) - fprintf(f, "%s\tControlGroup: %s:%s\n", - prefix, b->controller, b->path); - - LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - _cleanup_free_ char *v = NULL; - - 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); - } - if (UNIT_VTABLE(u)->dump) UNIT_VTABLE(u)->dump(u, f, prefix2); @@ -780,7 +835,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->nop_job) job_dump(u->nop_job, f, prefix2); - free(p2); } /* Common implementation for multiple backends */ @@ -790,14 +844,16 @@ int unit_load_fragment_and_dropin(Unit *u) { assert(u); /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) + r = unit_load_fragment(u); + if (r < 0) return r; if (u->load_state == UNIT_STUB) return -ENOENT; /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) return r; return 0; @@ -813,14 +869,16 @@ int unit_load_fragment_and_dropin_optional(Unit *u) { * something can be loaded or not doesn't matter. */ /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) + r = unit_load_fragment(u); + if (r < 0) return r; if (u->load_state == UNIT_STUB) u->load_state = UNIT_LOADED; /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) return r; return 0; @@ -853,6 +911,7 @@ int unit_add_default_target_dependency(Unit *u, Unit *target) { } static int unit_add_default_dependencies(Unit *u) { + static const UnitDependency deps[] = { UNIT_REQUIRED_BY, UNIT_REQUIRED_BY_OVERRIDABLE, @@ -868,9 +927,21 @@ static int unit_add_default_dependencies(Unit *u) { assert(u); for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(target, u->dependencies[deps[k]], i) - if ((r = unit_add_default_target_dependency(u, target)) < 0) + SET_FOREACH(target, u->dependencies[deps[k]], i) { + r = unit_add_default_target_dependency(u, target); + if (r < 0) return r; + } + + if (u->default_dependencies && unit_get_cgroup_context(u)) { + if (UNIT_ISSET(u->slice)) + r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, UNIT_DEREF(u->slice), true); + else + r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, SPECIAL_ROOT_SLICE, NULL, true); + + if (r < 0) + return r; + } return 0; } @@ -891,34 +962,38 @@ int unit_load(Unit *u) { if (u->load_state != UNIT_STUB) return 0; - if (UNIT_VTABLE(u)->load) - if ((r = UNIT_VTABLE(u)->load(u)) < 0) + if (UNIT_VTABLE(u)->load) { + r = UNIT_VTABLE(u)->load(u); + if (r < 0) goto fail; + } if (u->load_state == UNIT_STUB) { r = -ENOENT; goto fail; } - if (u->load_state == UNIT_LOADED && - u->default_dependencies) - if ((r = unit_add_default_dependencies(u)) < 0) - goto fail; - if (u->load_state == UNIT_LOADED) { + + if (u->default_dependencies) { + r = unit_add_default_dependencies(u); + if (r < 0) + goto fail; + } + r = unit_add_mount_links(u); if (r < 0) - return r; - } + goto fail; - if (u->on_failure_isolate && - set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + if (u->on_failure_isolate && + set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - log_error_unit(u->id, - "More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", u->id); + log_error_unit(u->id, + "More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", u->id); - r = -EINVAL; - goto fail; + r = -EINVAL; + goto fail; + } } assert((u->load_state != UNIT_MERGED) == !u->merged_into); @@ -929,7 +1004,7 @@ int unit_load(Unit *u) { return 0; fail: - u->load_state = UNIT_ERROR; + u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; u->load_error = r; unit_add_to_dbus_queue(u); unit_add_to_gc_queue(u); @@ -944,7 +1019,7 @@ bool unit_condition_test(Unit *u) { assert(u); dual_timestamp_get(&u->condition_timestamp); - u->condition_result = condition_test_list(u->conditions); + u->condition_result = condition_test_list(u->id, u->conditions); return u->condition_result; } @@ -1073,7 +1148,8 @@ int unit_start(Unit *u) { } /* Forward to the main object, if we aren't it. */ - if ((following = unit_following(u))) { + following = unit_following(u); + if (following) { log_debug_unit(u->id, "Redirecting start request from %s to %s.", u->id, following->id); return unit_start(following); @@ -1368,15 +1444,19 @@ 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); + unit_destroy_cgroup(u); 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)) { - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - m->n_on_console--; - else - m->n_on_console++; + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { + m->n_on_console --; + + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = 0; + } else + m->n_on_console ++; } } @@ -1857,7 +1937,7 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, con int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { Unit *other; int r; - char *s; + _cleanup_free_ char *s = NULL; assert(u); assert(name || path); @@ -1866,19 +1946,17 @@ int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency return -ENOMEM; if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; + return r; r = unit_add_two_dependencies(u, d, e, other, add_reference); -finish: - free(s); return r; } int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { Unit *other; int r; - char *s; + _cleanup_free_ char *s = NULL; assert(u); assert(name || path); @@ -1887,19 +1965,17 @@ int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *n return -ENOMEM; if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; + return r; r = unit_add_dependency(other, d, u, add_reference); -finish: - free(s); return r; } int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { Unit *other; int r; - char *s; + _cleanup_free_ char *s = NULL; assert(u); assert(name || path); @@ -1908,13 +1984,11 @@ int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDep return -ENOMEM; if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; + return r; if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0) - goto finish; + return r; -finish: - free(s); return r; } @@ -1938,351 +2012,91 @@ char *unit_dbus_path(Unit *u) { return unit_dbus_path_from_name(u->id); } -static int unit_add_cgroup(Unit *u, CGroupBonding *b) { +char *unit_default_cgroup_path(Unit *u) { + _cleanup_free_ char *escaped = NULL, *slice = NULL; int r; assert(u); - assert(b); - - assert(b->path); - if (!b->controller) { - b->controller = strdup(SYSTEMD_CGROUP_CONTROLLER); - if (!b->controller) - return log_oom(); - - b->ours = true; - } + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return strdup(u->manager->cgroup_root); - /* Ensure this hasn't been added yet */ - assert(!b->unit); - - if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { - CGroupBonding *l; - - l = hashmap_get(u->manager->cgroup_bondings, b->path); - LIST_PREPEND(CGroupBonding, by_path, l, b); - - r = hashmap_replace(u->manager->cgroup_bondings, b->path, l); - if (r < 0) { - LIST_REMOVE(CGroupBonding, by_path, l, b); - return r; - } + if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) { + r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice); + if (r < 0) + return NULL; } - LIST_PREPEND(CGroupBonding, by_unit, u->cgroup_bondings, b); - b->unit = u; - - return 0; -} - -char *unit_default_cgroup_path(Unit *u) { - _cleanup_free_ char *escaped_instance = NULL; - - assert(u); - - escaped_instance = cg_escape(u->id); - if (!escaped_instance) + escaped = cg_escape(u->id); + if (!escaped) return NULL; - if (u->instance) { - _cleanup_free_ char *t = NULL, *escaped_template = NULL; - - t = unit_name_template(u->id); - if (!t) - return NULL; - - escaped_template = cg_escape(t); - if (!escaped_template) - return NULL; - - return strjoin(u->manager->cgroup_hierarchy, "/", escaped_template, "/", escaped_instance, NULL); - } else - return strjoin(u->manager->cgroup_hierarchy, "/", escaped_instance, NULL); + if (slice) + return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); + else + return strjoin(u->manager->cgroup_root, "/", escaped, NULL); } -int unit_add_cgroup_from_text(Unit *u, const char *name, bool overwrite, CGroupBonding **ret) { - char *controller = NULL, *path = NULL; - CGroupBonding *b = NULL; - bool ours = false; +int unit_add_default_slice(Unit *u) { + _cleanup_free_ char *b = NULL; + const char *slice_name; + Unit *slice; int r; assert(u); - assert(name); - - r = cg_split_spec(name, &controller, &path); - if (r < 0) - return r; - - if (!path) { - path = unit_default_cgroup_path(u); - ours = true; - } - - if (!controller) { - controller = strdup("systemd"); - ours = true; - } - - if (!path || !controller) { - free(path); - free(controller); - return log_oom(); - } - if (streq(controller, "systemd")) { - /* Within the systemd unit hierarchy we do not allow changes. */ - if (path_startswith(path, "/system")) { - log_warning_unit(u->id, "Manipulating the systemd:/system cgroup hierarchy is not permitted."); - free(path); - free(controller); - return -EPERM; - } - } - - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (b) { - if (streq(path, b->path)) { - free(path); - free(controller); - - if (ret) - *ret = b; - return 0; - } - - if (overwrite && !b->essential) { - free(controller); - - free(b->path); - b->path = path; - - b->ours = ours; - b->realized = false; - - if (ret) - *ret = b; - - return 1; - } - - r = -EEXIST; - b = NULL; - goto fail; - } - - b = new0(CGroupBonding, 1); - if (!b) { - r = -ENOMEM; - goto fail; - } - - b->controller = controller; - b->path = path; - b->ours = ours; - b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); - - r = unit_add_cgroup(u, b); - if (r < 0) - goto fail; - - if (ret) - *ret = b; - - return 1; - -fail: - free(path); - free(controller); - free(b); - - return r; -} - -static int unit_add_one_default_cgroup(Unit *u, const char *controller) { - CGroupBonding *b = NULL; - int r = -ENOMEM; - - assert(u); - - if (controller && !cg_controller_is_valid(controller, true)) - return -EINVAL; - - if (!controller) - controller = SYSTEMD_CGROUP_CONTROLLER; - - if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) + if (UNIT_ISSET(u->slice)) return 0; - b = new0(CGroupBonding, 1); - if (!b) - return -ENOMEM; - - b->controller = strdup(controller); - if (!b->controller) - goto fail; - - b->path = unit_default_cgroup_path(u); - if (!b->path) - goto fail; - - b->ours = true; - b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); - - r = unit_add_cgroup(u, b); - if (r < 0) - goto fail; + if (!unit_get_cgroup_context(u)) + return 0; - return 1; + if (u->instance) { + _cleanup_free_ char *prefix = NULL, *escaped = NULL; -fail: - free(b->path); - free(b->controller); - free(b); + /* Implicitly place all instantiated units in their + * own per-template slice */ - return r; -} - -int unit_add_default_cgroups(Unit *u) { - CGroupAttribute *a; - char **c; - int r; + prefix = unit_name_to_prefix(u->id); + if (!prefix) + return -ENOMEM; - assert(u); + /* The prefix is already escaped, but it might include + * "-" which has a special meaning for slice units, + * hence escape it here extra. */ + escaped = strreplace(prefix, "-", "\\x2d"); + if (!escaped) + return -ENOMEM; - /* Adds in the default cgroups, if they weren't specified - * otherwise. */ + if (u->manager->running_as == SYSTEMD_SYSTEM) + b = strjoin("system-", escaped, ".slice", NULL); + else + b = strappend(escaped, ".slice"); + if (!b) + return -ENOMEM; - if (!u->manager->cgroup_hierarchy) - return 0; + slice_name = b; + } else + slice_name = + u->manager->running_as == SYSTEMD_SYSTEM + ? SPECIAL_SYSTEM_SLICE + : SPECIAL_ROOT_SLICE; - r = unit_add_one_default_cgroup(u, NULL); + r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); if (r < 0) return r; - STRV_FOREACH(c, u->manager->default_controllers) - unit_add_one_default_cgroup(u, *c); - - LIST_FOREACH(by_unit, a, u->cgroup_attributes) - unit_add_one_default_cgroup(u, a->controller); - + unit_ref_set(&u->slice, slice); return 0; } -CGroupBonding* unit_get_default_cgroup(Unit *u) { - assert(u); - - return cgroup_bonding_find_list(u->cgroup_bondings, NULL); -} - -int unit_add_cgroup_attribute( - Unit *u, - const CGroupSemantics *semantics, - const char *controller, - const char *name, - const char *value, - CGroupAttribute **ret) { - - _cleanup_free_ char *c = NULL; - CGroupAttribute *a; - int r; - +const char *unit_slice_name(Unit *u) { assert(u); - 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; - } - - if (!controller || - streq(controller, SYSTEMD_CGROUP_CONTROLLER) || - streq(controller, "systemd")) - return -EINVAL; - - if (!filename_is_safe(name)) - return -EINVAL; - - if (!cg_controller_is_valid(controller, false)) - 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) { - if (streq(value, a->value)) { - /* Exactly the same value is always OK, let's ignore this */ - if (ret) - *ret = a; - - return 0; - } - - if (semantics && !semantics->multiple) { - char *v; - - /* If this is a single-item entry, we can - * simply patch the existing attribute */ - - v = strdup(value); - if (!v) - return -ENOMEM; - - free(a->value); - a->value = v; - - if (ret) - *ret = a; - return 1; - } - } - a = new0(CGroupAttribute, 1); - if (!a) - return -ENOMEM; - - if (c) { - a->controller = c; - c = NULL; - } else - a->controller = strdup(controller); - - a->name = strdup(name); - a->value = strdup(value); - - if (!a->controller || !a->name || !a->value) { - free(a->controller); - free(a->name); - free(a->value); - free(a); - return -ENOMEM; - } - - a->semantics = semantics; - a->unit = u; - - LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a); - - if (ret) - *ret = a; + if (!UNIT_ISSET(u->slice)) + return NULL; - return 1; + return UNIT_DEREF(u->slice)->id; } int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { @@ -2359,7 +2173,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { if (!unit_can_serialize(u)) return 0; - if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0) + r = UNIT_VTABLE(u)->serialize(u, f, fds); + if (r < 0) return r; @@ -2384,6 +2199,11 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { if (dual_timestamp_is_set(&u->condition_timestamp)) unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + unit_serialize_item(u, f, "transient", yes_no(u->transient)); + + if (u->cgroup_path) + unit_serialize_item(u, f, "cgroup", u->cgroup_path); + /* End marker */ fputc('\n', f); return 0; @@ -2506,22 +2326,47 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { } else if (streq(l, "condition-result")) { int b; - if ((b = parse_boolean(v)) < 0) + b = parse_boolean(v); + if (b < 0) log_debug("Failed to parse condition result value %s", v); else u->condition_result = b; continue; + + } else if (streq(l, "transient")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse transient bool %s", v); + else + u->transient = b; + + continue; + } else if (streq(l, "cgroup")) { + char *s; + + s = strdup(v); + if (!s) + return -ENOMEM; + + free(u->cgroup_path); + u->cgroup_path = s; + + assert(hashmap_put(u->manager->cgroup_unit, s, u) == 1); + continue; } - if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) + r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); + if (r < 0) return r; } } int unit_add_node_link(Unit *u, const char *what, bool wants) { Unit *device; - char *e; + _cleanup_free_ char *e = NULL; int r; assert(u); @@ -2539,7 +2384,7 @@ int unit_add_node_link(Unit *u, const char *what, bool wants) { return -ENOMEM; r = manager_load_unit(u->manager, e, NULL, NULL, &device); - free(e); + if (r < 0) return r; @@ -2714,6 +2559,34 @@ int unit_kill(Unit *u, KillWho w, int signo, DBusError *error) { return UNIT_VTABLE(u)->kill(u, w, signo, error); } +static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { + Set *pid_set; + int r; + + pid_set = set_new(trivial_hash_func, trivial_compare_func); + if (!pid_set) + return NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + if (main_pid > 0) { + r = set_put(pid_set, LONG_TO_PTR(main_pid)); + if (r < 0) + goto fail; + } + + if (control_pid > 0) { + r = set_put(pid_set, LONG_TO_PTR(control_pid)); + if (r < 0) + goto fail; + } + + return pid_set; + +fail: + set_free(pid_set); + return NULL; +} + int unit_kill_common( Unit *u, KillWho who, @@ -2750,28 +2623,16 @@ int unit_kill_common( if (kill(main_pid, signo) < 0) r = -errno; - if (who == KILL_ALL) { + if (who == KILL_ALL && u->cgroup_path) { _cleanup_set_free_ Set *pid_set = NULL; int q; - pid_set = set_new(trivial_hash_func, trivial_compare_func); + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); if (!pid_set) return -ENOMEM; - /* Exclude the control/main pid from being killed via the cgroup */ - if (control_pid > 0) { - q = set_put(pid_set, LONG_TO_PTR(control_pid)); - if (q < 0) - return q; - } - - if (main_pid > 0) { - q = set_put(pid_set, LONG_TO_PTR(main_pid)); - if (q < 0) - return q; - } - - q = cgroup_bonding_kill_list(u->cgroup_bondings, signo, false, false, pid_set, NULL); + q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set); if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) r = q; } @@ -2823,40 +2684,39 @@ void unit_ref_unset(UnitRef *ref) { ref->unit = NULL; } -int unit_add_one_mount_link(Unit *u, Mount *m) { +int unit_add_mount_links(Unit *u) { char **i; + int r; assert(u); - assert(m); - - if (u->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; STRV_FOREACH(i, u->requires_mounts_for) { + char prefix[strlen(*i) + 1]; - if (UNIT(m) == u) - continue; + PATH_FOREACH_PREFIX_MORE(prefix, *i) { + Unit *m; - if (!path_startswith(*i, m->where)) - continue; - - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); - } + r = manager_get_unit_by_path(u->manager, prefix, ".mount", &m); + if (r < 0) + return r; + if (r == 0) + continue; + if (m == u) + continue; - return 0; -} + if (m->load_state != UNIT_LOADED) + continue; -int unit_add_mount_links(Unit *u) { - Unit *other; - int r; - - assert(u); + r = unit_add_dependency(u, UNIT_AFTER, m, true); + if (r < 0) + return r; - LIST_FOREACH(units_by_type, other, u->manager->units_by_type[UNIT_MOUNT]) { - r = unit_add_one_mount_link(u, MOUNT(other)); - if (r < 0) - return r; + if (m->fragment_path) { + r = unit_add_dependency(u, UNIT_REQUIRES, m, true); + if (r < 0) + return r; + } + } } return 0; @@ -2870,7 +2730,6 @@ int unit_exec_context_defaults(Unit *u, ExecContext *c) { assert(c); /* This only copies in the ones that need memory */ - for (i = 0; i < RLIMIT_NLIMITS; i++) if (u->manager->rlimit[i] && !c->rlimit[i]) { c->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); @@ -2900,7 +2759,18 @@ ExecContext *unit_get_exec_context(Unit *u) { return (ExecContext*) ((uint8_t*) u + offset); } -static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char **_q) { +CGroupContext *unit_get_cgroup_context(Unit *u) { + size_t offset; + + offset = UNIT_VTABLE(u)->cgroup_context_offset; + if (offset <= 0) + return NULL; + + return (CGroupContext*) ((uint8_t*) u + offset); +} + +static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, char **_p, char **_q) { + _cleanup_free_ char *b = NULL; char *p, *q; int r; @@ -2908,11 +2778,13 @@ static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char assert(name); assert(_p); assert(_q); + assert(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)); - if (u->manager->running_as == SYSTEMD_USER && runtime) - return -ENOTSUP; + b = xescape(name, "/."); + if (!b) + return -ENOMEM; - if (!filename_is_safe(name)) + if (!filename_is_safe(b)) return -EINVAL; if (u->manager->running_as == SYSTEMD_USER) { @@ -2925,14 +2797,14 @@ static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char return -ENOENT; p = strjoin(c, "/", u->id, ".d", NULL); - } else if (runtime) - p = strjoin("/run/systemd/system/", u->id, ".d", NULL); - else + } else if (mode & UNIT_PERSISTENT) p = strjoin("/etc/systemd/system/", u->id, ".d", NULL); + else + p = strjoin("/run/systemd/system/", u->id, ".d", NULL); if (!p) return -ENOMEM; - q = strjoin(p, "/50-", name, ".conf", NULL); + q = strjoin(p, "/90-", b, ".conf", NULL); if (!q) { free(p); return -ENOMEM; @@ -2943,13 +2815,18 @@ static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char return 0; } -int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) { +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { _cleanup_free_ char *p = NULL, *q = NULL; int r; assert(u); + assert(name); + assert(data); + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; - r = drop_in_file(u, runtime, name, &p, &q); + r = drop_in_file(u, mode, name, &p, &q); if (r < 0) return r; @@ -2957,22 +2834,126 @@ int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data return write_string_file_atomic_label(q, data); } -int unit_remove_drop_in(Unit *u, bool runtime, const char *name) { +int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, p); +} + +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + _cleanup_free_ char *ndata = NULL; + + assert(u); + assert(name); + assert(data); + + if (!UNIT_VTABLE(u)->private_section) + return -EINVAL; + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); + if (!ndata) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, ndata); +} + +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in_private(u, mode, name, p); +} + +int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name) { _cleanup_free_ char *p = NULL, *q = NULL; int r; assert(u); - r = drop_in_file(u, runtime, name, &p, &q); + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + r = drop_in_file(u, mode, name, &p, &q); if (unlink(q) < 0) - r = -errno; + r = errno == ENOENT ? 0 : -errno; else - r = 0; + r = 1; rmdir(p); return r; } +int unit_make_transient(Unit *u) { + int r; + + assert(u); + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + free(u->fragment_path); + u->fragment_path = 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; + + u->fragment_path = strjoin(c, "/", u->id, NULL); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p(c, 0755); + } else { + u->fragment_path = strappend("/run/systemd/system/", u->id); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p("/run/systemd/system", 0755); + } + + return write_string_file_atomic_label(u->fragment_path, "# Transient stub"); +} + int unit_kill_context( Unit *u, KillContext *c, @@ -3000,8 +2981,12 @@ int unit_kill_context( log_warning_unit(u->id, "Failed to kill main process %li (%s): %s", (long) main_pid, strna(comm), strerror(-r)); - } else + } else { wait_for_exit = !main_pid_alien; + + if (c->send_sighup) + kill(main_pid, SIGHUP); + } } if (control_pid > 0) { @@ -3014,41 +2999,123 @@ int unit_kill_context( log_warning_unit(u->id, "Failed to kill control process %li (%s): %s", (long) control_pid, strna(comm), strerror(-r)); - } else + } else { wait_for_exit = true; + + if (c->send_sighup) + kill(control_pid, SIGHUP); + } } - if (c->kill_mode == KILL_CONTROL_GROUP) { + if (c->kill_mode == KILL_CONTROL_GROUP && u->cgroup_path) { _cleanup_set_free_ Set *pid_set = NULL; - pid_set = set_new(trivial_hash_func, trivial_compare_func); + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); if (!pid_set) return -ENOMEM; - /* Exclude the main/control pids from being killed via the cgroup */ - if (main_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(main_pid)); - if (r < 0) - return r; - } - - if (control_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(control_pid)); - if (r < 0) - return r; - } - - r = cgroup_bonding_kill_list(u->cgroup_bondings, sig, true, false, pid_set, NULL); + r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, true, false, pid_set); if (r < 0) { if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) log_warning_unit(u->id, "Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) + } else if (r > 0) { wait_for_exit = true; + if (c->send_sighup) { + set_free(pid_set); + + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, SIGHUP, true, true, false, pid_set); + } + } } return wait_for_exit; } +int unit_require_mounts_for(Unit *u, const char *path) { + char prefix[strlen(path) + 1], *p; + int r; + + assert(u); + assert(path); + + /* Registers a unit for requiring a certain path and all its + * prefixes. We keep a simple array of these paths in the + * unit, since its usually short. However, we build a prefix + * table for all possible prefixes so that new appearing mount + * units can easily determine which units to make themselves a + * dependency of. */ + + p = strdup(path); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (!path_is_absolute(p)) { + free(p); + return -EINVAL; + } + + if (!path_is_safe(p)) { + free(p); + return -EPERM; + } + + if (strv_contains(u->requires_mounts_for, p)) { + free(p); + return 0; + } + + r = strv_push(&u->requires_mounts_for, p); + if (r < 0) { + free(p); + return r; + } + + PATH_FOREACH_PREFIX_MORE(prefix, p) { + Set *x; + + x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); + if (!x) { + char *q; + + if (!u->manager->units_requiring_mounts_for) { + u->manager->units_requiring_mounts_for = hashmap_new(string_hash_func, string_compare_func); + if (!u->manager->units_requiring_mounts_for) + return -ENOMEM; + } + + q = strdup(prefix); + if (!q) + return -ENOMEM; + + x = set_new(NULL, NULL); + if (!x) { + free(q); + return -ENOMEM; + } + + r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); + if (r < 0) { + free(q); + set_free(x); + return r; + } + } + + r = set_put(x, u); + if (r < 0) + return r; + } + + return 0; +} + static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { [UNIT_ACTIVE] = "active", [UNIT_RELOADING] = "reloading", diff --git a/src/core/unit.h b/src/core/unit.h index b04475e4fb..6dd750f8c2 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -37,10 +37,10 @@ typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; #include "list.h" #include "socket-util.h" #include "execute.h" +#include "cgroup.h" #include "condition.h" #include "install.h" #include "unit-name.h" -#include "cgroup-semantics.h" enum UnitActiveState { UNIT_ACTIVE, @@ -115,8 +115,15 @@ enum UnitDependency { #include "manager.h" #include "job.h" -#include "cgroup.h" -#include "cgroup-attr.h" + +struct UnitRef { + /* Keeps tracks of references to a unit. This is useful so + * that we can merge two units if necessary and correct all + * references to them */ + + Unit* unit; + LIST_FIELDS(UnitRef, refs); +}; struct Unit { Manager *manager; @@ -165,8 +172,10 @@ struct Unit { dual_timestamp inactive_enter_timestamp; /* Counterparts in the cgroup filesystem */ - CGroupBonding *cgroup_bondings; - CGroupAttribute *cgroup_attributes; + char *cgroup_path; + CGroupControllerMask cgroup_mask; + + UnitRef slice; /* Per type list */ LIST_FIELDS(Unit, units_by_type); @@ -186,6 +195,9 @@ struct Unit { /* GC queue */ LIST_FIELDS(Unit, gc_queue); + /* CGroup realize members queue */ + LIST_FIELDS(Unit, cgroup_queue); + /* Used during GC sweeps */ unsigned gc_marker; @@ -228,25 +240,22 @@ struct Unit { /* Did the last condition check succeed? */ bool condition_result; + /* Is this a transient unit? */ + bool transient; + bool in_load_queue:1; bool in_dbus_queue:1; bool in_cleanup_queue:1; bool in_gc_queue:1; + bool in_cgroup_queue:1; bool sent_dbus_new_signal:1; bool no_gc:1; bool in_audit:1; -}; -struct UnitRef { - /* Keeps tracks of references to a unit. This is useful so - * that we can merge two units if necessary and correct all - * references to them */ - - Unit* unit; - LIST_FIELDS(UnitRef, refs); + bool cgroup_realized:1; }; struct UnitStatusMessageFormats { @@ -255,6 +264,12 @@ struct UnitStatusMessageFormats { const char *finished_stop_job[_JOB_RESULT_MAX]; }; +typedef enum UnitSetPropertiesMode { + UNIT_CHECK = 0, + UNIT_RUNTIME = 1, + UNIT_PERSISTENT = 2, +} UnitSetPropertiesMode; + #include "service.h" #include "timer.h" #include "socket.h" @@ -265,6 +280,8 @@ struct UnitStatusMessageFormats { #include "snapshot.h" #include "swap.h" #include "path.h" +#include "slice.h" +#include "scope.h" struct UnitVTable { /* How much memory does an object of this unit type need */ @@ -274,8 +291,12 @@ struct UnitVTable { * ExecContext is found, if the unit type has that */ size_t exec_context_offset; - /* The name of the section with the exec settings of ExecContext */ - const char *exec_section; + /* If greater than 0, the offset into the object where + * CGroupContext is found, if the unit type has that */ + size_t cgroup_context_offset; + + /* The name of the configuration file section with the private settings of this unit*/ + const char *private_section; /* Config file sections this unit type understands, separated * by NUL chars */ @@ -347,7 +368,7 @@ struct UnitVTable { /* Called whenever any of the cgroups this unit watches for * ran empty */ - void (*cgroup_notify_empty)(Unit *u); + void (*notify_cgroup_empty)(Unit *u); /* Called whenever a process of this unit sends us a message */ void (*notify_message)(Unit *u, pid_t pid, char **tags); @@ -362,6 +383,12 @@ struct UnitVTable { /* Called for each message received on the bus */ DBusHandlerResult (*bus_message_handler)(Unit *u, DBusConnection *c, DBusMessage *message); + /* Called for each property that is being set */ + int (*bus_set_property)(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error); + + /* Called after at least one property got changed to apply the necessary change */ + int (*bus_commit_properties)(Unit *u); + /* Return the unit this unit is following */ Unit *(*following)(Unit *u); @@ -403,6 +430,9 @@ struct UnitVTable { /* Exclude from automatic gc */ bool no_gc:1; + + /* True if transient units of this type are OK */ + bool can_transient:1; }; extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; @@ -433,6 +463,8 @@ DEFINE_CAST(AUTOMOUNT, Automount); DEFINE_CAST(SNAPSHOT, Snapshot); DEFINE_CAST(SWAP, Swap); DEFINE_CAST(PATH, Path); +DEFINE_CAST(SLICE, Slice); +DEFINE_CAST(SCOPE, Scope); Unit *unit_new(Manager *m, size_t size); void unit_free(Unit *u); @@ -450,11 +482,6 @@ int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDep int unit_add_exec_dependencies(Unit *u, ExecContext *c); -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 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); @@ -474,6 +501,8 @@ int unit_load_fragment_and_dropin(Unit *u); int unit_load_fragment_and_dropin_optional(Unit *u); int unit_load(Unit *unit); +int unit_add_default_slice(Unit *u); + const char *unit_description(Unit *u) _pure_; bool unit_has_name(Unit *u, const char *name); @@ -536,6 +565,8 @@ void unit_reset_failed(Unit *u); Unit *unit_following(Unit *u); +const char *unit_slice_name(Unit *u); + bool unit_stop_pending(Unit *u) _pure_; bool unit_inactive_or_pending(Unit *u) _pure_; bool unit_active_or_pending(Unit *u); @@ -557,19 +588,29 @@ Unit* unit_ref_set(UnitRef *ref, Unit *u); void unit_ref_unset(UnitRef *ref); #define UNIT_DEREF(ref) ((ref).unit) +#define UNIT_ISSET(ref) (!!(ref).unit) -int unit_add_one_mount_link(Unit *u, Mount *m); int unit_add_mount_links(Unit *u); int unit_exec_context_defaults(Unit *u, ExecContext *c); ExecContext *unit_get_exec_context(Unit *u) _pure_; +CGroupContext *unit_get_cgroup_context(Unit *u) _pure_; -int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data); -int unit_remove_drop_in(Unit *u, bool runtime, const char *name); +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); +int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_attr_(4,5); + +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_attr_(4,5); + +int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name); int unit_kill_context(Unit *u, KillContext *c, bool sigkill, pid_t main_pid, pid_t control_pid, bool main_pid_alien); +int unit_make_transient(Unit *u); + +int unit_require_mounts_for(Unit *u, const char *path); + const char *unit_active_state_to_string(UnitActiveState i) _const_; UnitActiveState unit_active_state_from_string(const char *s) _pure_; diff --git a/src/core/user.conf b/src/core/user.conf index 4252451eb7..4a0129a984 100644 --- a/src/core/user.conf +++ b/src/core/user.conf @@ -12,6 +12,5 @@ #LogTarget=console #LogColor=yes #LogLocation=no -#DefaultControllers=cpu #DefaultStandardOutput=inherit #DefaultStandardError=inherit diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 81b770890a..e1798a3e82 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -72,13 +72,20 @@ static int create_disk( _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL; _cleanup_fclose_ FILE *f = NULL; - bool noauto, nofail; + bool noauto, nofail, tmp, swap; assert(name); assert(device); noauto = has_option(options, "noauto"); nofail = has_option(options, "nofail"); + tmp = has_option(options, "tmp"); + swap = has_option(options, "swap"); + + if (tmp && swap) { + log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name); + return -EINVAL; + } n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service"); if (!n) @@ -111,6 +118,7 @@ static int create_disk( "Conflicts=umount.target\n" "DefaultDependencies=no\n" "BindsTo=dev-mapper-%i.device\n" + "IgnoreOnIsolate=true\n" "After=systemd-readahead-collect.service systemd-readahead-replay.service\n", f); @@ -122,7 +130,7 @@ static int create_disk( if (streq(password, "/dev/urandom") || streq(password, "/dev/random") || streq(password, "/dev/hw_random")) - fputs("After=systemd-random-seed-load.service\n", f); + fputs("After=systemd-random-seed.service\n", f); else if (!streq(password, "-") && !streq(password, "none")) fprintf(f, @@ -151,12 +159,12 @@ static int create_disk( name, u, strempty(password), strempty(options), name); - if (has_option(options, "tmp")) + if (tmp) fprintf(f, "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n", name); - if (has_option(options, "swap")) + if (swap) fprintf(f, "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n", name); @@ -233,7 +241,7 @@ static int create_disk( return 0; } -static int parse_proc_cmdline(char ***arg_proc_cmdline_disks, char **arg_proc_cmdline_keyfile) { +static int parse_proc_cmdline(char ***arg_proc_cmdline_disks, char ***arg_proc_cmdline_options, char **arg_proc_cmdline_keyfile) { _cleanup_free_ char *line = NULL; char *w = NULL, *state = NULL; int r; @@ -300,7 +308,20 @@ static int parse_proc_cmdline(char ***arg_proc_cmdline_disks, char **arg_proc_cm return log_oom(); } + } else if (startswith(word, "luks.options=")) { + if (strv_extend(arg_proc_cmdline_options, word + 13) < 0) + return log_oom(); + + } else if (startswith(word, "rd.luks.options=")) { + + if (in_initrd()) { + if (strv_extend(arg_proc_cmdline_options, word + 16) < 0) + return log_oom(); + } + } else if (startswith(word, "luks.key=")) { + if (*arg_proc_cmdline_keyfile) + free(*arg_proc_cmdline_keyfile); *arg_proc_cmdline_keyfile = strdup(word + 9); if (!*arg_proc_cmdline_keyfile) return log_oom(); @@ -330,6 +351,7 @@ static int parse_proc_cmdline(char ***arg_proc_cmdline_disks, char **arg_proc_cm int main(int argc, char *argv[]) { _cleanup_strv_free_ char **arg_proc_cmdline_disks_done = NULL; _cleanup_strv_free_ char **arg_proc_cmdline_disks = NULL; + _cleanup_strv_free_ char **arg_proc_cmdline_options = NULL; _cleanup_free_ char *arg_proc_cmdline_keyfile = NULL; _cleanup_fclose_ FILE *f = NULL; unsigned n = 0; @@ -350,7 +372,7 @@ int main(int argc, char *argv[]) { umask(0022); - if (parse_proc_cmdline(&arg_proc_cmdline_disks, &arg_proc_cmdline_keyfile) < 0) + if (parse_proc_cmdline(&arg_proc_cmdline_disks, &arg_proc_cmdline_options, &arg_proc_cmdline_keyfile) < 0) return EXIT_FAILURE; if (!arg_enabled) @@ -405,6 +427,26 @@ int main(int argc, char *argv[]) { continue; } + if (arg_proc_cmdline_options) { + /* + If options are specified on the kernel commandline, let them override + the ones from crypttab. + */ + STRV_FOREACH(i, arg_proc_cmdline_options) { + _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL; + const char *p = *i; + + k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options); + if (k == 2 && streq(proc_uuid, device + 5)) { + if (options) + free(options); + options = strdup(p); + if (!proc_options) + return log_oom(); + } + } + } + if (arg_proc_cmdline_disks) { /* If luks UUIDs are specified on the kernel command line, use them as a filter @@ -445,7 +487,7 @@ next: on the kernel command line and not yet written. */ - _cleanup_free_ char *name = NULL, *device = NULL; + _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL; const char *p = *i; if (startswith(p, "luks-")) @@ -460,7 +502,44 @@ next: if (!name || !device) return log_oom(); - if (create_disk(name, device, arg_proc_cmdline_keyfile, "timeout=0") < 0) + if (arg_proc_cmdline_options) { + /* + If options are specified on the kernel commandline, use them. + */ + char **j; + + STRV_FOREACH(j, arg_proc_cmdline_options) { + _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL; + const char *s = *j; + int k; + + k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options); + if (k == 2) { + if (streq(proc_uuid, device + 5)) { + if (options) + free(options); + options = strdup(proc_options); + if (!options) + return log_oom(); + } + } else if (!options) { + /* + Fall back to options without a specified UUID + */ + options = strdup(s); + if (!options) + return log_oom(); + } + } + } + + if (!options) { + options = strdup("timeout=0"); + if (!options) + return log_oom(); + } + + if (create_disk(name, device, arg_proc_cmdline_keyfile, options) < 0) r = EXIT_FAILURE; } diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 347394db8e..22b5eead72 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -27,6 +27,7 @@ #include <libcryptsetup.h> #include <libudev.h> +#include "fileio.h" #include "log.h" #include "util.h" #include "path-util.h" @@ -34,16 +35,19 @@ #include "ask-password-api.h" #include "def.h" -static const char *opt_type = NULL; /* LUKS1 or PLAIN */ +static const char *opt_type = NULL; /* CRYPT_LUKS1, CRYPT_TCRYPT or CRYPT_PLAIN */ static char *opt_cipher = NULL; static unsigned opt_key_size = 0; static unsigned opt_keyfile_size = 0; static unsigned opt_keyfile_offset = 0; static char *opt_hash = NULL; -static unsigned opt_tries = 0; +static unsigned opt_tries = 3; static bool opt_readonly = false; static bool opt_verify = false; static bool opt_discards = false; +static bool opt_tcrypt_hidden = false; +static bool opt_tcrypt_system = false; +static char **opt_tcrypt_keyfiles = NULL; static usec_t opt_timeout = 0; /* Options Debian's crypttab knows we don't: @@ -82,6 +86,14 @@ static int parse_one_option(const char *option) { return 0; } + } else if (startswith(option, "tcrypt-keyfile=")) { + + opt_type = CRYPT_TCRYPT; + if (path_is_absolute(option+15)) + opt_tcrypt_keyfiles = strv_append(opt_tcrypt_keyfiles, strdup(option+15)); + else + log_error("Key file path '%s' is not absolute. Ignoring.", option+15); + } else if (startswith(option, "keyfile-size=")) { if (safe_atou(option+13, &opt_keyfile_size) < 0) { @@ -117,11 +129,19 @@ static int parse_one_option(const char *option) { opt_readonly = true; else if (streq(option, "verify")) opt_verify = true; - else if (streq(option, "allow-discards")) + else if (streq(option, "allow-discards") || streq(option, "discard")) opt_discards = true; else if (streq(option, "luks")) opt_type = CRYPT_LUKS1; - else if (streq(option, "plain") || + else if (streq(option, "tcrypt")) + opt_type = CRYPT_TCRYPT; + else if (streq(option, "tcrypt-hidden")) { + opt_type = CRYPT_TCRYPT; + opt_tcrypt_hidden = true; + } else if (streq(option, "tcrypt-system")) { + opt_type = CRYPT_TCRYPT; + opt_tcrypt_system = true; + } else if (streq(option, "plain") || streq(option, "swap") || streq(option, "tmp")) opt_type = CRYPT_PLAIN; @@ -215,7 +235,8 @@ finish: } static char *disk_mount_point(const char *label) { - char *mp = NULL, *device = NULL; + char *mp = NULL; + _cleanup_free_ char *device = NULL; FILE *f = NULL; struct mntent *m; @@ -238,11 +259,211 @@ finish: if (f) endmntent(f); - free(device); - return mp; } +static int get_password(const char *name, usec_t until, bool accept_cached, char ***passwords) { + int r; + char **p; + _cleanup_free_ char *text = NULL; + + assert(name); + assert(passwords); + + if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) + return log_oom(); + + r = ask_password_auto(text, "drive-harddisk", until, accept_cached, passwords); + if (r < 0) { + log_error("Failed to query password: %s", strerror(-r)); + return r; + } + + if (opt_verify) { + _cleanup_strv_free_ char **passwords2 = NULL; + + assert(strv_length(*passwords) == 1); + + if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) + return log_oom(); + + r = ask_password_auto(text, "drive-harddisk", until, false, &passwords2); + if (r < 0) { + log_error("Failed to query verification password: %s", strerror(-r)); + return r; + } + + assert(strv_length(passwords2) == 1); + + if (!streq(*passwords[0], passwords2[0])) { + log_warning("Passwords did not match, retrying."); + return -EAGAIN; + } + } + + strv_uniq(*passwords); + + STRV_FOREACH(p, *passwords) { + char *c; + + if (strlen(*p)+1 >= opt_key_size) + continue; + + /* Pad password if necessary */ + if (!(c = new(char, opt_key_size))) + return log_oom(); + + strncpy(c, *p, opt_key_size); + free(*p); + *p = c; + } + + return 0; +} + +static int attach_tcrypt(struct crypt_device *cd, + const char *name, + const char *key_file, + char **passwords, + uint32_t flags) { + int r = 0; + _cleanup_free_ char *passphrase = NULL; + struct crypt_params_tcrypt params = { + .flags = CRYPT_TCRYPT_LEGACY_MODES, + .keyfiles = (const char **)opt_tcrypt_keyfiles, + .keyfiles_count = strv_length(opt_tcrypt_keyfiles) + }; + + assert(cd); + assert(name); + assert(key_file || passwords); + + if (opt_tcrypt_hidden) + params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; + + if (opt_tcrypt_system) + params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER; + + if (key_file) { + r = read_one_line_file(key_file, &passphrase); + if (r < 0) { + log_error("Failed to read password file '%s': %s", key_file, strerror(-r)); + return -EAGAIN; + } + + params.passphrase = passphrase; + } else + params.passphrase = passwords[0]; + params.passphrase_size = strlen(params.passphrase); + + r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + if (r < 0) { + if (key_file && r == -EPERM) { + log_error("Failed to activate using password file '%s'.", key_file); + return -EAGAIN; + } + return r; + } + + return crypt_activate_by_volume_key(cd, name, NULL, 0, flags);; +} + +static int attach_luks_or_plain(struct crypt_device *cd, + const char *name, + const char *key_file, + char **passwords, + uint32_t flags) { + int r = 0; + bool pass_volume_key = false; + + assert(cd); + assert(name); + assert(key_file || passwords); + + if (!opt_type || streq(opt_type, CRYPT_LUKS1)) + r = crypt_load(cd, CRYPT_LUKS1, NULL); + + if ((!opt_type && r < 0) || streq_ptr(opt_type, CRYPT_PLAIN)) { + struct crypt_params_plain params = {}; + const char *cipher, *cipher_mode; + _cleanup_free_ char *truncated_cipher = NULL; + + if (opt_hash) { + /* plain isn't a real hash type. it just means "use no hash" */ + if (!streq(opt_hash, "plain")) + params.hash = opt_hash; + } else + params.hash = "ripemd160"; + + if (opt_cipher) { + size_t l; + + l = strcspn(opt_cipher, "-"); + truncated_cipher = strndup(opt_cipher, l); + if (!truncated_cipher) + return log_oom(); + + cipher = truncated_cipher; + cipher_mode = opt_cipher[l] ? opt_cipher+l+1 : "plain"; + } else { + cipher = "aes"; + cipher_mode = "cbc-essiv:sha256"; + } + + /* for CRYPT_PLAIN limit reads + * from keyfile to key length, and + * ignore keyfile-size */ + opt_keyfile_size = opt_key_size / 8; + + /* In contrast to what the name + * crypt_setup() might suggest this + * doesn't actually format anything, + * it just configures encryption + * parameters when used for plain + * mode. */ + r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, + NULL, NULL, opt_keyfile_size, ¶ms); + + /* hash == NULL implies the user passed "plain" */ + pass_volume_key = (params.hash == NULL); + } + + if (r < 0) { + log_error("Loading of cryptographic parameters failed: %s", strerror(-r)); + return r; + } + + log_info("Set cipher %s, mode %s, key size %i bits for device %s.", + crypt_get_cipher(cd), + crypt_get_cipher_mode(cd), + crypt_get_volume_key_size(cd)*8, + crypt_get_device_name(cd)); + + if (key_file) { + r = crypt_activate_by_keyfile_offset(cd, name, CRYPT_ANY_SLOT, + key_file, opt_keyfile_size, + opt_keyfile_offset, flags); + if (r < 0) { + log_error("Failed to activate with key file '%s': %s", key_file, strerror(-r)); + return -EAGAIN; + } + } else { + char **p; + + STRV_FOREACH(p, passwords) { + if (pass_volume_key) + r = crypt_activate_by_volume_key(cd, name, *p, opt_key_size, flags); + else + r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, *p, strlen(*p), flags); + + if (r >= 0) + break; + } + } + + return r; +} + static int help(void) { printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n" @@ -257,9 +478,6 @@ static int help(void) { int main(int argc, char *argv[]) { int r = EXIT_FAILURE; struct crypt_device *cd = NULL; - char **passwords = NULL, *truncated_cipher = NULL; - const char *cipher = NULL, *cipher_mode = NULL, *hash = NULL, *name = NULL; - char *description = NULL, *name_buffer = NULL, *mount_point = NULL; if (argc <= 1) { help(); @@ -280,10 +498,11 @@ int main(int argc, char *argv[]) { if (streq(argv[1], "attach")) { uint32_t flags = 0; int k; - unsigned try; - const char *key_file = NULL; + unsigned tries; usec_t until; crypt_status_info status; + const char *key_file = NULL, *name = NULL; + _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL; /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ @@ -298,7 +517,7 @@ int main(int argc, char *argv[]) { !streq(argv[4], "none")) { if (!path_is_absolute(argv[4])) - log_error("Password file path %s is not absolute. Ignoring.", argv[4]); + log_error("Password file path '%s' is not absolute. Ignoring.", argv[4]); else key_file = argv[4]; } @@ -357,183 +576,38 @@ int main(int argc, char *argv[]) { else until = 0; - opt_tries = opt_tries > 0 ? opt_tries : 3; opt_key_size = (opt_key_size > 0 ? opt_key_size : 256); - if (opt_hash) { - /* plain isn't a real hash type. it just means "use no hash" */ - if (!streq(opt_hash, "plain")) - hash = opt_hash; - } else - hash = "ripemd160"; - - if (opt_cipher) { - size_t l; - l = strcspn(opt_cipher, "-"); - truncated_cipher = strndup(opt_cipher, l); - - if (!truncated_cipher) { - log_oom(); - goto finish; - } + if (key_file) { + struct stat st; - cipher = truncated_cipher; - cipher_mode = opt_cipher[l] ? opt_cipher+l+1 : "plain"; - } else { - cipher = "aes"; - cipher_mode = "cbc-essiv:sha256"; + /* Ideally we'd do this on the open fd, but since this is just a + * warning it's OK to do this in two steps. */ + if (stat(key_file, &st) >= 0 && (st.st_mode & 0005)) + log_warning("Key file %s is world-readable. This is not a good idea!", key_file); } - for (try = 0; try < opt_tries; try++) { - bool pass_volume_key = false; - - strv_free(passwords); - passwords = NULL; + for (tries = 0; opt_tries == 0 || tries < opt_tries; tries++) { + _cleanup_strv_free_ char **passwords = NULL; if (!key_file) { - char *text, **p; - - if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) { - log_oom(); - goto finish; - } - - k = ask_password_auto(text, "drive-harddisk", until, try == 0 && !opt_verify, &passwords); - free(text); - - if (k < 0) { - log_error("Failed to query password: %s", strerror(-k)); - goto finish; - } - - if (opt_verify) { - char **passwords2 = NULL; - - assert(strv_length(passwords) == 1); - - if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) { - log_oom(); - goto finish; - } - - k = ask_password_auto(text, "drive-harddisk", until, false, &passwords2); - free(text); - - if (k < 0) { - log_error("Failed to query verification password: %s", strerror(-k)); - goto finish; - } - - assert(strv_length(passwords2) == 1); - - if (!streq(passwords[0], passwords2[0])) { - log_warning("Passwords did not match, retrying."); - strv_free(passwords2); - continue; - } - - strv_free(passwords2); - } - - strv_uniq(passwords); - - STRV_FOREACH(p, passwords) { - char *c; - - if (strlen(*p)+1 >= opt_key_size) - continue; - - /* Pad password if necessary */ - if (!(c = new(char, opt_key_size))) { - log_oom(); - goto finish; - } - - strncpy(c, *p, opt_key_size); - free(*p); - *p = c; - } - } - - k = 0; - - if (!opt_type || streq(opt_type, CRYPT_LUKS1)) - k = crypt_load(cd, CRYPT_LUKS1, NULL); - - if ((!opt_type && k < 0) || streq_ptr(opt_type, CRYPT_PLAIN)) { - struct crypt_params_plain params = { .hash = hash }; - - /* for CRYPT_PLAIN limit reads - * from keyfile to key length, and - * ignore keyfile-size */ - opt_keyfile_size = opt_key_size / 8; - - /* In contrast to what the name - * crypt_setup() might suggest this - * doesn't actually format anything, - * it just configures encryption - * parameters when used for plain - * mode. */ - k = crypt_format(cd, CRYPT_PLAIN, - cipher, - cipher_mode, - NULL, - NULL, - opt_keyfile_size, - ¶ms); - - /* hash == NULL implies the user passed "plain" */ - pass_volume_key = (hash == NULL); - } - - if (k < 0) { - log_error("Loading of cryptographic parameters failed: %s", strerror(-k)); - goto finish; - } - - log_info("Set cipher %s, mode %s, key size %i bits for device %s.", - crypt_get_cipher(cd), - crypt_get_cipher_mode(cd), - crypt_get_volume_key_size(cd)*8, - argv[3]); - - if (key_file) { - struct stat st; - - /* Ideally we'd do this on the open - * fd, but since this is just a - * warning it's OK to do this in two - * steps */ - if (stat(key_file, &st) >= 0 && (st.st_mode & 0005)) - log_warning("Key file %s is world-readable. That's certainly not a good idea.", key_file); - - k = crypt_activate_by_keyfile_offset( - cd, argv[2], CRYPT_ANY_SLOT, key_file, opt_keyfile_size, - opt_keyfile_offset, flags); - if (k < 0) { - log_error("Failed to activate with key file '%s': %s", key_file, strerror(-k)); - key_file = NULL; + k = get_password(name, until, tries == 0 && !opt_verify, &passwords); + if (k == -EAGAIN) continue; - } - } else { - char **p; - - STRV_FOREACH(p, passwords) { - - if (pass_volume_key) - k = crypt_activate_by_volume_key(cd, argv[2], *p, opt_key_size, flags); - else - k = crypt_activate_by_passphrase(cd, argv[2], CRYPT_ANY_SLOT, *p, strlen(*p), flags); - - if (k >= 0) - break; - } + else if (k < 0) + goto finish; } + if (streq_ptr(opt_type, CRYPT_TCRYPT)) + k = attach_tcrypt(cd, argv[2], key_file, passwords, flags); + else + k = attach_luks_or_plain(cd, argv[2], key_file, passwords, flags); if (k >= 0) break; - - if (k != -EPERM) { + else if (k == -EAGAIN) { + key_file = NULL; + continue; + } else if (k != -EPERM) { log_error("Failed to activate: %s", strerror(-k)); goto finish; } @@ -541,8 +615,8 @@ int main(int argc, char *argv[]) { log_warning("Invalid passphrase."); } - if (try >= opt_tries) { - log_error("Too many attempts."); + if (opt_tries != 0 && tries >= opt_tries) { + log_error("Too many attempts; giving up."); r = EXIT_FAILURE; goto finish; } @@ -578,14 +652,7 @@ finish: free(opt_cipher); free(opt_hash); - - free(truncated_cipher); - - strv_free(passwords); - - free(description); - free(mount_point); - free(name_buffer); + strv_free(opt_tcrypt_keyfiles); return r; } diff --git a/src/delta/delta.c b/src/delta/delta.c index aec3dc8995..b3272d916e 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -31,6 +31,7 @@ #include "log.h" #include "pager.h" #include "build.h" +#include "strv.h" static bool arg_no_pager = false; static int arg_diff = -1; @@ -41,9 +42,10 @@ static enum { SHOW_REDIRECTED = 1 << 2, SHOW_OVERRIDDEN = 1 << 3, SHOW_UNCHANGED = 1 << 4, + SHOW_EXTENDED = 1 << 5, SHOW_DEFAULTS = - (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN) + (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED) } arg_flags = 0; static int equivalent(const char *a, const char *b) { @@ -64,7 +66,8 @@ static int notify_override_masked(const char *top, const char *bottom) { if (!(arg_flags & SHOW_MASKED)) return 0; - printf(ANSI_HIGHLIGHT_RED_ON "[MASKED]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom); + printf("%s%s%s %s → %s\n", + ansi_highlight_red(), "[MASKED]", ansi_highlight_off(), top, bottom); return 1; } @@ -72,7 +75,8 @@ static int notify_override_equivalent(const char *top, const char *bottom) { if (!(arg_flags & SHOW_EQUIVALENT)) return 0; - printf(ANSI_HIGHLIGHT_GREEN_ON "[EQUIVALENT]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom); + printf("%s%s%s %s → %s\n", + ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight(), top, bottom); return 1; } @@ -80,7 +84,8 @@ static int notify_override_redirected(const char *top, const char *bottom) { if (!(arg_flags & SHOW_REDIRECTED)) return 0; - printf(ANSI_HIGHLIGHT_ON "[REDIRECTED]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom); + printf("%s%s%s %s → %s\n", + ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(), top, bottom); return 1; } @@ -88,7 +93,17 @@ static int notify_override_overridden(const char *top, const char *bottom) { if (!(arg_flags & SHOW_OVERRIDDEN)) return 0; - printf(ANSI_HIGHLIGHT_ON "[OVERRIDDEN]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom); + printf("%s%s%s %s → %s\n", + ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(), top, bottom); + return 1; +} + +static int notify_override_extended(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_EXTENDED)) + return 0; + + printf("%s%s%s %s → %s\n", + ansi_highlight(), "[EXTENDED]", ansi_highlight_off(), top, bottom); return 1; } @@ -108,24 +123,20 @@ static int found_override(const char *top, const char *bottom) { assert(top); assert(bottom); - if (null_or_empty_path(top) > 0) { - notify_override_masked(top, bottom); - return 0; - } + if (null_or_empty_path(top) > 0) + return notify_override_masked(top, bottom); k = readlink_malloc(top, &dest); if (k >= 0) { if (equivalent(dest, bottom) > 0) - notify_override_equivalent(top, bottom); + return notify_override_equivalent(top, bottom); else - notify_override_redirected(top, bottom); - - return 0; + return notify_override_redirected(top, bottom); } - notify_override_overridden(top, bottom); + k = notify_override_overridden(top, bottom); if (!arg_diff) - return 0; + return k; putchar('\n'); @@ -145,14 +156,117 @@ static int found_override(const char *top, const char *bottom) { putchar('\n'); + return k; +} + +static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) { + _cleanup_free_ char *conf = NULL; + _cleanup_free_ char *path = NULL; + _cleanup_strv_free_ char **list = NULL; + char **file; + char *c; + int r; + + path = strjoin(toppath, "/", drop, NULL); + if (!path) + return -ENOMEM; + + path_kill_slashes(path); + + conf = strdup(drop); + if (!conf) + return -ENOMEM; + + c = strrchr(conf, '.'); + if (!c) + return -EINVAL; + *c = 0; + + r = get_files_in_directory(path, &list); + if (r < 0){ + log_error("Failed to enumerate %s: %s", path, strerror(-r)); + return r; + } + + STRV_FOREACH(file, list) { + Hashmap *h; + int k; + char *p; + char *d; + + if (!endswith(*file, ".conf")) + continue; + + p = strjoin(path, "/", *file, NULL); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + d = strrchr(p, '/'); + if (!d || d == p) { + free(p); + return -EINVAL; + } + d--; + d = strrchr(p, '/'); + + if (!d || d == p) { + free(p); + return -EINVAL; + } + + k = hashmap_put(top, d, p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + d = strrchr(p, '/'); + d--; + d = strrchr(p, '/'); + } else if (k != -EEXIST) { + free(p); + return k; + } + + free(hashmap_remove(bottom, d)); + k = hashmap_put(bottom, d, p); + if (k < 0) { + free(p); + return k; + } + + h = hashmap_get(drops, conf); + if (!h) { + h = hashmap_new(string_hash_func, string_compare_func); + if (!h) + return -ENOMEM; + hashmap_put(drops, conf, h); + conf = strdup(conf); + if (!conf) + return -ENOMEM; + } + + p = strdup(p); + if (!p) + return -ENOMEM; + + k = hashmap_put(h, path_get_file_name(p), p); + if (k < 0) { + free(p); + if (k != -EEXIST) + return k; + } + } return 0; } -static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) { +static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) { _cleanup_closedir_ DIR *d; assert(top); assert(bottom); + assert(drops); assert(path); d = opendir(path); @@ -177,6 +291,9 @@ static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) { if (!de) break; + if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) + enumerate_dir_d(top, bottom, drops, path, de->d_name); + if (!dirent_is_file(de)) continue; @@ -207,12 +324,14 @@ static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) { return 0; } -static int process_suffix(const char *prefixes, const char *suffix) { +static int process_suffix(const char *prefixes, const char *suffix, bool dropins) { const char *p; char *f; - Hashmap *top, *bottom=NULL; + Hashmap *top, *bottom=NULL, *drops=NULL; + Hashmap *h; + char *key; int r = 0, k; - Iterator i; + Iterator i, j; int n_found = 0; assert(prefixes); @@ -230,6 +349,12 @@ static int process_suffix(const char *prefixes, const char *suffix) { goto finish; } + drops = hashmap_new(string_hash_func, string_compare_func); + if (!drops) { + r = -ENOMEM; + goto finish; + } + NULSTR_FOREACH(p, prefixes) { _cleanup_free_ char *t = NULL; @@ -239,29 +364,33 @@ static int process_suffix(const char *prefixes, const char *suffix) { goto finish; } - k = enumerate_dir(top, bottom, t); + k = enumerate_dir(top, bottom, drops, t, dropins); if (k < 0) r = k; log_debug("Looking at %s", t); } - HASHMAP_FOREACH(f, top, i) { + HASHMAP_FOREACH_KEY(f, key, top, i) { char *o; - o = hashmap_get(bottom, path_get_file_name(f)); + o = hashmap_get(bottom, key); assert(o); - if (path_equal(o, f)) { + if (path_equal(o, f)) notify_override_unchanged(f); - continue; + else { + k = found_override(f, o); + if (k < 0) + r = k; + else + n_found += k; } - k = found_override(f, o); - if (k < 0) - r = k; - - n_found ++; + h = hashmap_get(drops, key); + if (h) + HASHMAP_FOREACH(o, h, j) + n_found += notify_override_extended(f, o); } finish: @@ -269,25 +398,32 @@ finish: hashmap_free_free(top); if (bottom) hashmap_free_free(bottom); - + if (drops) { + HASHMAP_FOREACH_KEY(h, key, drops, i){ + hashmap_free_free(hashmap_remove(drops, key)); + hashmap_remove(drops, key); + free(key); + } + hashmap_free(drops); + } return r < 0 ? r : n_found; } -static int process_suffix_chop(const char *prefixes, const char *suffix) { +static int process_suffix_chop(const char *prefixes, const char *suffix, const char *have_dropins) { const char *p; assert(prefixes); assert(suffix); if (!path_is_absolute(suffix)) - return process_suffix(prefixes, suffix); + return process_suffix(prefixes, suffix, nulstr_contains(have_dropins, suffix)); /* Strip prefix from the suffix */ NULSTR_FOREACH(p, prefixes) { if (startswith(suffix, p)) { suffix += strlen(p); suffix += strspn(suffix, "/"); - return process_suffix(prefixes, suffix); + return process_suffix(prefixes, suffix, nulstr_contains(have_dropins, suffix)); } } @@ -322,6 +458,8 @@ static int parse_flags(const char *flag_str, int flags) { flags |= SHOW_OVERRIDDEN; else if (strneq("unchanged", w, l)) flags |= SHOW_UNCHANGED; + else if (strneq("extended", w, l)) + flags |= SHOW_EXTENDED; else if (strneq("default", w, l)) flags |= SHOW_DEFAULTS; else @@ -435,6 +573,10 @@ int main(int argc, char *argv[]) { "udev/rules.d\0" "modprobe.d\0"; + const char have_dropins[] = + "systemd/system\0" + "systemd/user\0"; + int r = 0, k; int n_found = 0; @@ -460,7 +602,7 @@ int main(int argc, char *argv[]) { int i; for (i = optind; i < argc; i++) { - k = process_suffix_chop(prefixes, argv[i]); + k = process_suffix_chop(prefixes, argv[i], have_dropins); if (k < 0) r = k; else @@ -471,7 +613,7 @@ int main(int argc, char *argv[]) { const char *n; NULSTR_FOREACH(n, suffixes) { - k = process_suffix(prefixes, n); + k = process_suffix(prefixes, n, nulstr_contains(have_dropins, n)); if (k < 0) r = k; else @@ -480,7 +622,8 @@ int main(int argc, char *argv[]) { } if (r >= 0) - printf("\n%i overridden configuration files found.\n", n_found); + printf("%s%i overridden configuration files found.\n", + n_found ? "\n" : "", n_found); finish: pager_close(); diff --git a/src/efi-boot-generator/efi-boot-generator.c b/src/efi-boot-generator/efi-boot-generator.c index 4367c536b0..05b95ed455 100644 --- a/src/efi-boot-generator/efi-boot-generator.c +++ b/src/efi-boot-generator/efi-boot-generator.c @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) { if (dir_is_empty("/boot") <= 0) return EXIT_SUCCESS; - r = efi_get_loader_device_part_uuid(&id); + r = efi_loader_get_device_part_uuid(&id); if (r == -ENOENT) return EXIT_SUCCESS; if (r < 0) { diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index c17299f267..9efccb983d 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -38,22 +38,6 @@ static const char *arg_dest = "/tmp"; static bool arg_enabled = true; -static int device_name(const char *path, char **unit) { - char *p; - - assert(path); - - if (!is_device_path(path)) - return 0; - - p = unit_name_from_path(path, ".device"); - if (!p) - return log_oom(); - - *unit = p; - return 1; -} - static int mount_find_pri(struct mntent *me, int *ret) { char *end, *pri; unsigned long r; @@ -80,9 +64,9 @@ static int mount_find_pri(struct mntent *me, int *ret) { } static int add_swap(const char *what, struct mntent *me) { - _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL; + _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; _cleanup_fclose_ FILE *f = NULL; - bool noauto, nofail; + bool noauto; int r, pri = -1; assert(what); @@ -95,7 +79,6 @@ static int add_swap(const char *what, struct mntent *me) { } noauto = !!hasmntopt(me, "noauto"); - nofail = !!hasmntopt(me, "nofail"); name = unit_name_from_path(what, ".swap"); if (!name) @@ -114,18 +97,10 @@ static int add_swap(const char *what, struct mntent *me) { return -errno; } - fputs("# Automatically generated by systemd-fstab-generator\n\n" - "[Unit]\n" - "SourcePath=/etc/fstab\n" - "DefaultDependencies=no\n" - "Conflicts=" SPECIAL_UMOUNT_TARGET "\n" - "Before=" SPECIAL_UMOUNT_TARGET "\n", f); - - if (!noauto && !nofail) - fputs("Before=" SPECIAL_SWAP_TARGET "\n", f); - fprintf(f, - "\n" + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=/etc/fstab\n\n" "[Swap]\n" "What=%s\n", what); @@ -151,38 +126,11 @@ static int add_swap(const char *what, struct mntent *me) { log_error("Failed to create symlink %s: %m", lnk); return -errno; } - - r = device_name(what, &device); - if (r < 0) - return r; - - if (r > 0) { - free(lnk); - lnk = strjoin(arg_dest, "/", device, ".wants/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) { - log_error("Failed to create symlink %s: %m", lnk); - return -errno; - } - } } return 0; } -static bool mount_is_bind(struct mntent *me) { - assert(me); - - return - hasmntopt(me, "bind") || - streq(me->mnt_type, "bind") || - hasmntopt(me, "rbind") || - streq(me->mnt_type, "rbind"); -} - static bool mount_is_network(struct mntent *me) { assert(me); @@ -208,17 +156,12 @@ static int add_mount( bool noauto, bool nofail, bool automount, - bool isbind, - const char *pre, - const char *pre2, - const char *online, const char *post, const char *source) { _cleanup_free_ char - *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL, + *name = NULL, *unit = NULL, *lnk = NULL, *automount_name = NULL, *automount_unit = NULL; _cleanup_fclose_ FILE *f = NULL; - int r; assert(what); assert(where); @@ -258,33 +201,9 @@ static int add_mount( fprintf(f, "# Automatically generated by systemd-fstab-generator\n\n" "[Unit]\n" - "SourcePath=%s\n" - "DefaultDependencies=no\n", + "SourcePath=%s\n", source); - if (!path_equal(where, "/")) { - if (pre) - fprintf(f, - "After=%s\n", - pre); - - if (pre2) - fprintf(f, - "After=%s\n", - pre2); - - if (online) - fprintf(f, - "After=%s\n" - "Wants=%s\n", - online, - online); - - fprintf(f, - "Conflicts=" SPECIAL_UMOUNT_TARGET "\n" - "Before=" SPECIAL_UMOUNT_TARGET "\n"); - } - if (post && !noauto && !nofail && !automount) fprintf(f, "Before=%s\n", @@ -326,32 +245,11 @@ static int add_mount( return -errno; } } - - if (!isbind && - !path_equal(where, "/")) { - - r = device_name(what, &device); - if (r < 0) - return r; - - if (r > 0) { - free(lnk); - lnk = strjoin(arg_dest, "/", device, ".wants/", name, NULL); - if (!lnk) - return log_oom(); - - mkdir_parents_label(lnk, 0755); - if (symlink(unit, lnk) < 0) { - log_error("Failed to create symlink %s: %m", lnk); - return -errno; - } - } - } } if (automount && !path_equal(where, "/")) { automount_name = unit_name_from_path(where, ".automount"); - if (!name) + if (!automount_name) return log_oom(); automount_unit = strjoin(arg_dest, "/", automount_name, NULL); @@ -368,10 +266,7 @@ static int add_mount( fprintf(f, "# Automatically generated by systemd-fstab-generator\n\n" "[Unit]\n" - "SourcePath=%s\n" - "DefaultDependencies=no\n" - "Conflicts=" SPECIAL_UMOUNT_TARGET "\n" - "Before=" SPECIAL_UMOUNT_TARGET "\n", + "SourcePath=%s\n", source); if (post) @@ -446,36 +341,28 @@ static int parse_fstab(const char *prefix, bool initrd) { if (streq(me->mnt_type, "swap")) k = add_swap(what, me); else { - bool noauto, nofail, automount, isbind; - const char *pre, *pre2, *post, *online; + bool noauto, nofail, automount; + const char *post; noauto = !!hasmntopt(me, "noauto"); nofail = !!hasmntopt(me, "nofail"); automount = hasmntopt(me, "comment=systemd.automount") || hasmntopt(me, "x-systemd.automount"); - isbind = mount_is_bind(me); if (initrd) { - pre = pre2 = online = NULL; post = SPECIAL_INITRD_FS_TARGET; } else if (mount_in_initrd(me)) { - pre = pre2 = online = NULL; post = SPECIAL_INITRD_ROOT_FS_TARGET; } else if (mount_is_network(me)) { - pre = SPECIAL_REMOTE_FS_PRE_TARGET; - pre2 = SPECIAL_NETWORK_TARGET; - online = SPECIAL_NETWORK_ONLINE_TARGET; post = SPECIAL_REMOTE_FS_TARGET; } else { - pre = SPECIAL_LOCAL_FS_PRE_TARGET; - pre2 = online = NULL; post = SPECIAL_LOCAL_FS_TARGET; } k = add_mount(what, where, me->mnt_type, me->mnt_opts, me->mnt_passno, noauto, nofail, automount, - isbind, pre, pre2, online, post, fstab_path); + post, fstab_path); } if (k < 0) @@ -492,6 +379,7 @@ static int parse_new_root_from_proc_cmdline(void) { char *w, *state; int r; size_t l; + bool noauto, nofail; r = read_one_line_file("/proc/cmdline", &line); if (r < 0) { @@ -547,6 +435,9 @@ static int parse_new_root_from_proc_cmdline(void) { } } + noauto = !!strstr(opts, "noauto"); + nofail = !!strstr(opts, "nofail"); + if (!what) { log_debug("Could not find a root= entry on the kernel commandline."); return 0; @@ -558,8 +449,8 @@ static int parse_new_root_from_proc_cmdline(void) { } log_debug("Found entry what=%s where=/sysroot type=%s", what, type); - r = add_mount(what, "/sysroot", type, opts, 0, false, false, false, - false, NULL, NULL, NULL, SPECIAL_INITRD_ROOT_FS_TARGET, "/proc/cmdline"); + r = add_mount(what, "/sysroot", type, opts, 0, noauto, nofail, false, + SPECIAL_INITRD_ROOT_FS_TARGET, "/proc/cmdline"); return (r < 0) ? r : 0; } @@ -596,9 +487,9 @@ static int parse_proc_cmdline(void) { } else if (startswith(word, "rd.fstab=")) { if (in_initrd()) { - r = parse_boolean(word + 6); + r = parse_boolean(word + 9); if (r < 0) - log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6); + log_warning("Failed to parse fstab switch %s. Ignoring.", word + 9); else arg_enabled = r; } diff --git a/src/getty-generator/getty-generator.c b/src/getty-generator/getty-generator.c index 4b7a60a4ec..6c938062de 100644 --- a/src/getty-generator/getty-generator.c +++ b/src/getty-generator/getty-generator.c @@ -122,33 +122,42 @@ int main(int argc, char *argv[]) { } if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) { - const char *tty; - - tty = strrchr(active, ' '); - if (tty) - tty ++; - else - tty = active; - - /* Automatically add in a serial getty on the kernel - * console */ - if (isempty(tty) || tty_is_vc(tty)) - free(active); - else { + char *w, *state; + size_t l; + + /* Automatically add in a serial getty on all active + * kernel consoles */ + FOREACH_WORD(w, l, active, state) { + char *tty; int k; + tty = strndup(w, l); + if (!tty) { + log_oom(); + free(active); + r = EXIT_FAILURE; + goto finish; + } + + if (isempty(tty) || tty_is_vc(tty)) { + free(tty); + continue; + } + /* We assume that gettys on virtual terminals are * started via manual configuration and do this magic * only for non-VC terminals. */ k = add_serial_getty(tty); - free(active); if (k < 0) { + free(tty); + free(active); r = EXIT_FAILURE; goto finish; } } + free(active); } /* Automatically add in a serial getty on the first diff --git a/src/gpt-auto-generator/Makefile b/src/gpt-auto-generator/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/gpt-auto-generator/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c new file mode 100644 index 0000000000..ca54925da4 --- /dev/null +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -0,0 +1,527 @@ +/*-*- 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 <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/statfs.h> +#include <blkid.h> + +#ifdef HAVE_LINUX_BTRFS_H +#include <linux/btrfs.h> +#endif + +#include "path-util.h" +#include "util.h" +#include "mkdir.h" +#include "missing.h" +#include "sd-id128.h" +#include "libudev.h" +#include "special.h" +#include "unit-name.h" + +/* TODO: + * + * - Properly handle cryptsetup partitions + * - Define new partition type for encrypted swap + * - Make /home automount rather than mount + * + */ + +static const char *arg_dest = "/tmp"; + +static inline void blkid_free_probep(blkid_probe *b) { + if (*b) + blkid_free_probe(*b); +} +#define _cleanup_blkid_freep_probe_ _cleanup_(blkid_free_probep) + +static int verify_gpt_partition(const char *node, sd_id128_t *type, unsigned *nr, char **fstype) { + _cleanup_blkid_freep_probe_ blkid_probe b = NULL; + const char *v; + int r; + + errno = 0; + b = blkid_new_probe_from_filename(node); + if (!b) + return errno != 0 ? -errno : -ENOMEM; + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2) + return -ENODEV; + else if (r == 1) + return -ENODEV; + else if (r != 0) + return errno ? -errno : -EIO; + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); + if (r != 0) + /* return 0 if we're not on GPT */ + return errno ? -errno : 0; + + if (strcmp(v, "gpt") != 0) + return 0; + + if (type) { + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); + if (r != 0) + return errno ? -errno : -EIO; + + r = sd_id128_from_string(v, type); + if (r < 0) + return r; + } + + if (nr) { + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); + if (r != 0) + return errno ? -errno : -EIO; + + r = safe_atou(v, nr); + if (r < 0) + return r; + } + + + if (fstype) { + char *fst; + + errno = 0; + r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); + if (r != 0) + *fstype = NULL; + else { + fst = strdup(v); + if (!fst) + return -ENOMEM; + + *fstype = fst; + } + } + + return 1; +} + +static int add_swap(const char *path, const char *fstype) { + _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + + log_debug("Adding swap: %s %s", path, fstype); + + name = unit_name_from_path(path, ".swap"); + if (!name) + return log_oom(); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) { + log_error("Failed to create unit file %s: %m", unit); + return -errno; + } + + fprintf(f, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "DefaultDependencies=no\n" + "Conflicts=" SPECIAL_UMOUNT_TARGET "\n" + "Before=" SPECIAL_UMOUNT_TARGET " " SPECIAL_SWAP_TARGET "\n\n" + "[Swap]\n" + "What=%s\n", + path); + + fflush(f); + if (ferror(f)) { + log_error("Failed to write unit file %s: %m", unit); + return -errno; + } + + lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET ".wants/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) { + log_error("Failed to create symlink %s: %m", lnk); + return -errno; + } + + return 0; +} + +static int add_home(const char *path, const char *fstype) { + _cleanup_free_ char *unit = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + + if (dir_is_empty("/home") <= 0) + return 0; + + log_debug("Adding home: %s %s", path, fstype); + + unit = strappend(arg_dest, "/home.mount"); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) { + log_error("Failed to create unit file %s: %m", unit); + return -errno; + } + + fprintf(f, + "# Automatically generated by systemd-gpt-auto-generator\n\n" + "[Unit]\n" + "DefaultDependencies=no\n" + "After=" SPECIAL_LOCAL_FS_PRE_TARGET "\n" + "Conflicts=" SPECIAL_UMOUNT_TARGET "\n" + "Before=" SPECIAL_UMOUNT_TARGET " " SPECIAL_LOCAL_FS_TARGET "\n\n" + "[Mount]\n" + "What=%s\n" + "Where=/home\n" + "Type=%s\n" + "FsckPassNo=2\n", + path, fstype); + + fflush(f); + if (ferror(f)) { + log_error("Failed to write unit file %s: %m", unit); + return -errno; + } + + lnk = strjoin(arg_dest, "/" SPECIAL_LOCAL_FS_TARGET ".requires/home.mount", NULL); + if (!lnk) + return log_oom(); + + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) { + log_error("Failed to create symlink %s: %m", lnk); + return -errno; + } + + return 0; +} + +static int enumerate_partitions(struct udev *udev, dev_t dev) { + struct udev_enumerate *e = NULL; + struct udev_device *parent = NULL, *d = NULL; + struct udev_list_entry *first, *item; + unsigned home_nr = (unsigned) -1; + _cleanup_free_ char *home = NULL, *home_fstype = NULL; + int r; + + e = udev_enumerate_new(udev); + if (!e) { + r = log_oom(); + goto finish; + } + + d = udev_device_new_from_devnum(udev, 'b', dev); + if (!d) { + r = log_oom(); + goto finish; + } + + parent = udev_device_get_parent(d); + if (!parent) { + r = log_oom(); + goto finish; + } + + r = udev_enumerate_add_match_parent(e, parent); + if (r < 0) { + r = log_oom(); + goto finish; + } + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) { + r = log_oom(); + goto finish; + } + + r = udev_enumerate_scan_devices(e); + if (r < 0) { + log_error("Failed to enumerate partitions on /dev/block/%u:%u: %s", + major(dev), minor(dev), strerror(-r)); + goto finish; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_free_ char *fstype = NULL; + const char *node = NULL; + struct udev_device *q; + sd_id128_t type_id; + unsigned nr; + + q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!q) { + r = log_oom(); + goto finish; + } + + if (udev_device_get_devnum(q) == udev_device_get_devnum(d)) + goto skip; + + if (udev_device_get_devnum(q) == udev_device_get_devnum(parent)) + goto skip; + + node = udev_device_get_devnode(q); + if (!node) { + r = log_oom(); + goto finish; + } + + r = verify_gpt_partition(node, &type_id, &nr, &fstype); + if (r < 0) { + log_error("Failed to verify GPT partition %s: %s", + node, strerror(-r)); + udev_device_unref(q); + goto finish; + } + if (r == 0) + goto skip; + + if (sd_id128_equal(type_id, SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f))) + add_swap(node, fstype); + else if (sd_id128_equal(type_id, SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15))) { + + if (!home || nr < home_nr) { + free(home); + home = strdup(node); + if (!home) { + r = log_oom(); + goto finish; + } + + home_nr = nr; + + free(home_fstype); + home_fstype = fstype; + fstype = NULL; + } + } + + skip: + udev_device_unref(q); + } + + if (home && home_fstype) + add_home(home, home_fstype); + +finish: + if (d) + udev_device_unref(d); + + if (e) + udev_enumerate_unref(e); + + + return r; +} + +static int get_btrfs_block_device(const char *path, dev_t *dev) { + struct btrfs_ioctl_fs_info_args fsi; + _cleanup_close_ int fd = -1; + uint64_t id; + + assert(path); + assert(dev); + + fd = open(path, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) + return -errno; + + zero(fsi); + if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) + return -errno; + + /* We won't do this for btrfs RAID */ + if (fsi.num_devices != 1) + return 0; + + for (id = 1; id <= fsi.max_id; id++) { + struct btrfs_ioctl_dev_info_args di; + struct stat st; + + zero(di); + di.devid = id; + + if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { + if (errno == ENODEV) + continue; + + return -errno; + } + + if (stat((char*) di.path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -ENODEV; + + if (major(st.st_rdev) == 0) + return -ENODEV; + + *dev = st.st_rdev; + return 1; + } + + return -ENODEV; +} + +static int get_block_device(const char *path, dev_t *dev) { + struct stat st; + struct statfs sfs; + + assert(path); + assert(dev); + + if (lstat("/", &st)) + return -errno; + + if (major(st.st_dev) != 0) { + *dev = st.st_dev; + return 1; + } + + if (statfs("/", &sfs) < 0) + return -errno; + + if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) + return get_btrfs_block_device(path, dev); + + return 0; +} + +static int devno_to_devnode(struct udev *udev, dev_t devno, char **ret) { + struct udev_device *d = NULL; + const char *t; + char *n; + int r; + + d = udev_device_new_from_devnum(udev, 'b', devno); + if (!d) { + r = log_oom(); + goto finish; + } + + t = udev_device_get_devnode(d); + if (!t) { + r = -ENODEV; + goto finish; + } + + n = strdup(t); + if (!n) { + r = -ENOMEM; + goto finish; + } + + *ret = n; + r = 0; + +finish: + if (d) + udev_device_unref(d); + + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_free_ char *node = NULL; + struct udev *udev = NULL; + dev_t devno; + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + r = -EINVAL; + goto finish; + } + + if (argc > 1) + arg_dest = argv[3]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + if (in_initrd()) { + r = 0; + goto finish; + } + + r = get_block_device("/", &devno); + if (r < 0) { + log_error("Failed to determine block device of root file system: %s", strerror(-r)); + goto finish; + } + if (r == 0) { + log_debug("Root file system not on a (single) block device."); + goto finish; + } + + udev = udev_new(); + if (!udev) { + r = log_oom(); + goto finish; + } + + r = devno_to_devnode(udev, devno, &node); + if (r < 0) { + log_error("Failed to determine block device node from major/minor: %s", strerror(-r)); + goto finish; + } + + log_debug("Root device %s.", node); + + r = verify_gpt_partition(node, NULL, NULL, NULL); + if (r < 0) { + log_error("Failed to verify GPT partition %s: %s", node, strerror(-r)); + goto finish; + } + if (r == 0) + goto finish; + + r = enumerate_partitions(udev, devno); + +finish: + if (udev) + udev_unref(udev); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 064581a31c..66015c2f4c 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -44,10 +44,11 @@ static enum transport { TRANSPORT_POLKIT } arg_transport = TRANSPORT_NORMAL; static bool arg_ask_password = true; -static const char *arg_host = NULL; -static bool arg_set_transient = false; -static bool arg_set_pretty = false; -static bool arg_set_static = false; +static char *arg_host = NULL; +static char *arg_user = NULL; +static bool arg_transient = false; +static bool arg_pretty = false; +static bool arg_static = false; static void polkit_agent_open_if_enabled(void) { @@ -151,15 +152,52 @@ static int status_property(const char *name, DBusMessageIter *iter, StatusInfo * return 0; } -static int show_status(DBusConnection *bus, char **args, unsigned n) { +static int show_one_name(DBusConnection *bus, const char* attr) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *interface = "org.freedesktop.hostname1", *s; + DBusMessageIter iter, sub; + int r; + + r = bus_method_call_with_reply( + bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.DBus.Properties", + "Get", + &reply, + NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &attr, + DBUS_TYPE_INVALID); + if (r < 0) + return r; + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_get_basic(&sub, &s); + printf("%s\n", s); + + return 0; +} + +static int show_all_names(DBusConnection *bus) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; const char *interface = ""; int r; DBusMessageIter iter, sub, sub2, sub3; StatusInfo info = {}; - assert(args); - r = bus_method_call_with_reply( bus, "org.freedesktop.hostname1", @@ -217,9 +255,28 @@ static int show_status(DBusConnection *bus, char **args, unsigned n) { return 0; } +static int show_status(DBusConnection *bus, char **args, unsigned n) { + assert(args); + + if (arg_pretty || arg_static || arg_transient) { + const char *attr; + + if (!!arg_static + !!arg_pretty + !!arg_transient > 1) { + log_error("Cannot query more than one name type at a time"); + return -EINVAL; + } + + attr = arg_pretty ? "PrettyHostname" : + arg_static ? "StaticHostname" : "Hostname"; + + return show_one_name(bus, attr); + } else + return show_all_names(bus); +} + static int set_hostname(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true; + dbus_bool_t interactive = arg_ask_password; _cleanup_free_ char *h = NULL; const char *hostname = args[1]; int r; @@ -229,7 +286,10 @@ static int set_hostname(DBusConnection *bus, char **args, unsigned n) { polkit_agent_open_if_enabled(); - if (arg_set_pretty) { + if (!arg_pretty && !arg_static && !arg_transient) + arg_pretty = arg_static = arg_transient = true; + + if (arg_pretty) { const char *p; /* If the passed hostname is already valid, then @@ -244,7 +304,7 @@ static int set_hostname(DBusConnection *bus, char **args, unsigned n) { hostname_cleanup(h, true); - if (arg_set_static && streq(h, hostname)) + if (arg_static && streq(h, hostname)) p = ""; else { p = hostname; @@ -269,7 +329,7 @@ static int set_hostname(DBusConnection *bus, char **args, unsigned n) { reply = NULL; } - if (arg_set_static) { + if (arg_static) { r = bus_method_call_with_reply( bus, "org.freedesktop.hostname1", @@ -289,7 +349,7 @@ static int set_hostname(DBusConnection *bus, char **args, unsigned n) { reply = NULL; } - if (arg_set_transient) { + if (arg_transient) { r = bus_method_call_with_reply( bus, "org.freedesktop.hostname1", @@ -311,7 +371,7 @@ static int set_hostname(DBusConnection *bus, char **args, unsigned n) { static int set_icon_name(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true; + dbus_bool_t interactive = arg_ask_password; assert(args); assert(n == 2); @@ -333,7 +393,7 @@ static int set_icon_name(DBusConnection *bus, char **args, unsigned n) { static int set_chassis(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true; + dbus_bool_t interactive = arg_ask_password; assert(args); assert(n == 2); @@ -362,6 +422,7 @@ static int help(void) { " --transient Only set transient hostname\n" " --static Only set static hostname\n" " --pretty Only set pretty hostname\n" + " -P --privileged Acquire privileges before execution\n" " --no-ask-password Do not prompt for password\n" " -H --host=[USER@]HOST Operate on remote host\n\n" "Commands:\n" @@ -379,17 +440,17 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_ASK_PASSWORD, - ARG_SET_TRANSIENT, - ARG_SET_STATIC, - ARG_SET_PRETTY + ARG_TRANSIENT, + ARG_STATIC, + ARG_PRETTY }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, - { "transient", no_argument, NULL, ARG_SET_TRANSIENT }, - { "static", no_argument, NULL, ARG_SET_STATIC }, - { "pretty", no_argument, NULL, ARG_SET_PRETTY }, + { "transient", no_argument, NULL, ARG_TRANSIENT }, + { "static", no_argument, NULL, ARG_STATIC }, + { "pretty", no_argument, NULL, ARG_PRETTY }, { "host", required_argument, NULL, 'H' }, { "privileged", no_argument, NULL, 'P' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, @@ -420,19 +481,19 @@ static int parse_argv(int argc, char *argv[]) { case 'H': arg_transport = TRANSPORT_SSH; - arg_host = optarg; + parse_user_at_host(optarg, &arg_user, &arg_host); break; - case ARG_SET_TRANSIENT: - arg_set_transient = true; + case ARG_TRANSIENT: + arg_transient = true; break; - case ARG_SET_PRETTY: - arg_set_pretty = true; + case ARG_PRETTY: + arg_pretty = true; break; - case ARG_SET_STATIC: - arg_set_static = true; + case ARG_STATIC: + arg_static = true; break; case ARG_NO_ASK_PASSWORD: @@ -448,9 +509,6 @@ static int parse_argv(int argc, char *argv[]) { } } - if (!arg_set_transient && !arg_set_pretty && !arg_set_static) - arg_set_transient = arg_set_pretty = arg_set_static = true; - return 1; } diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 0437e33a66..6a43aeb840 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -553,7 +553,8 @@ static DBusHandlerResult hostname_message_handler( * safe than sorry */ if (k == PROP_ICON_NAME && !filename_is_safe(name)) return bus_send_error_reply(connection, message, NULL, -EINVAL); - if (k == PROP_PRETTY_HOSTNAME && string_has_cc(name)) + if (k == PROP_PRETTY_HOSTNAME && + (string_has_cc(name) || chars_intersect(name, "\t"))) return bus_send_error_reply(connection, message, NULL, -EINVAL); if (k == PROP_CHASSIS && !valid_chassis(name)) return bus_send_error_reply(connection, message, NULL, -EINVAL); diff --git a/src/initctl/initctl.c b/src/initctl/initctl.c index 735f1e1450..ec33040509 100644 --- a/src/initctl/initctl.c +++ b/src/initctl/initctl.c @@ -122,7 +122,7 @@ static void change_runlevel(Server *s, int runlevel) { if (isolate) mode = "isolate"; else - mode = "replace"; + mode = "replace-irreversibly"; log_debug("Running request %s/start/%s", target, mode); @@ -223,8 +223,10 @@ static int fifo_process(Fifo *f) { assert(f); errno = EIO; - if ((l = read(f->fd, ((uint8_t*) &f->buffer) + f->bytes_read, sizeof(f->buffer) - f->bytes_read)) <= 0) { - + l = read(f->fd, + ((uint8_t*) &f->buffer) + f->bytes_read, + sizeof(f->buffer) - f->bytes_read); + if (l <= 0) { if (errno == EAGAIN) return 0; @@ -372,8 +374,8 @@ static int process_event(Server *s, struct epoll_event *ev) { } f = (Fifo*) ev->data.ptr; - - if ((r = fifo_process(f)) < 0) { + r = fifo_process(f); + if (r < 0) { log_info("Got error on fifo: %s", strerror(-r)); fifo_free(f); return r; diff --git a/src/journal/coredump.c b/src/journal/coredump.c index fd03e389bb..68c353fe83 100644 --- a/src/journal/coredump.c +++ b/src/journal/coredump.c @@ -41,7 +41,7 @@ #define COREDUMP_MIN_START (3*1024*1024) /* Make sure to not make this larger than the maximum journal entry * size. See ENTRY_SIZE_MAX in journald-native.c. */ -#define COREDUMP_MAX (768*1024*1024) +#define COREDUMP_MAX (767*1024*1024) enum { ARG_PID = 1, @@ -241,7 +241,7 @@ int main(int argc, char* argv[]) { coredump_data = malloc(coredump_bufsize); if (!coredump_data) { r = log_oom(); - goto finish; + goto finalize; } memcpy(coredump_data, "COREDUMP=", 9); @@ -258,9 +258,15 @@ int main(int argc, char* argv[]) { break; coredump_size += n; + + if(coredump_size > COREDUMP_MAX) { + log_error("Coredump too large, ignoring"); + goto finalize; + } + if (!GREEDY_REALLOC(coredump_data, coredump_bufsize, coredump_size + 1)) { r = log_oom(); - goto finish; + goto finalize; } } @@ -268,6 +274,7 @@ int main(int argc, char* argv[]) { iovec[j].iov_len = coredump_size; j++; +finalize: r = sd_journal_sendv(iovec, j); if (r < 0) log_error("Failed to send coredump: %s", strerror(-r)); diff --git a/src/journal/coredumpctl.c b/src/journal/coredumpctl.c index 5652c2f91a..75c96cc081 100644 --- a/src/journal/coredumpctl.c +++ b/src/journal/coredumpctl.c @@ -84,6 +84,7 @@ static int help(void) { "Flags:\n" " -o --output=FILE Write output to FILE\n" " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not print the column headers.\n\n" "Commands:\n" " -h --help Show this help\n" @@ -341,7 +342,7 @@ static int dump_list(sd_journal *j) { assert(j); /* The coredumps are likely to compressed, and for just - * listing them we don#t need to decompress them, so let's + * listing them we don't need to decompress them, so let's * pick a fairly low data threshold here */ sd_journal_set_data_threshold(j, 4096); @@ -556,6 +557,13 @@ int main(int argc, char *argv[]) { } } + if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) { + _cleanup_free_ char *filter; + + filter = journal_make_match_string(j); + log_debug("Journal filter: %s", filter); + } + switch(arg_action) { case ACTION_LIST: diff --git a/src/journal/fsprg.c b/src/journal/fsprg.c index 6817a629c8..dd9a242561 100644 --- a/src/journal/fsprg.c +++ b/src/journal/fsprg.c @@ -19,7 +19,13 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA + */ + +/* + * See "Practical Secure Logging: Seekable Sequential Key Generators" + * by G. A. Marson, B. Poettering for details: * + * http://eprint.iacr.org/2013/397 */ #include <gcrypt.h> diff --git a/src/journal/journal-authenticate.c b/src/journal/journal-authenticate.c index 64bf96874e..bd7100a8d5 100644 --- a/src/journal/journal-authenticate.c +++ b/src/journal/journal-authenticate.c @@ -60,9 +60,9 @@ int journal_file_append_tag(JournalFile *f) { o->tag.seqnum = htole64(journal_file_tag_seqnum(f)); o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state)); - log_debug("Writing tag %llu for epoch %llu\n", - (unsigned long long) le64toh(o->tag.seqnum), - (unsigned long long) FSPRG_GetEpoch(f->fsprg_state)); + log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"\n", + le64toh(o->tag.seqnum), + FSPRG_GetEpoch(f->fsprg_state)); /* Add the tag object itself, so that we can protect its * header. This will exclude the actual hash value in it */ @@ -152,7 +152,7 @@ int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { epoch = FSPRG_GetEpoch(f->fsprg_state); if (epoch < goal) - log_debug("Evolving FSPRG key from epoch %llu to %llu.", (unsigned long long) epoch, (unsigned long long) goal); + log_debug("Evolving FSPRG key from epoch %"PRIu64" to %"PRIu64".", epoch, goal); for (;;) { if (epoch > goal) @@ -195,7 +195,7 @@ int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { return -ENOMEM; } - log_debug("Seeking FSPRG key to %llu.", (unsigned long long) goal); + log_debug("Seeking FSPRG key to %"PRIu64".", goal); msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 38499a6881..12364030d9 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -549,7 +549,7 @@ static int journal_file_setup_data_hash_table(JournalFile *f) { if (s < DEFAULT_DATA_HASH_TABLE_SIZE) s = DEFAULT_DATA_HASH_TABLE_SIZE; - log_debug("Reserving %llu entries in hash table.", (unsigned long long) (s / sizeof(HashItem))); + log_debug("Reserving %"PRIu64" entries in hash table.", s / sizeof(HashItem)); r = journal_file_append_object(f, OBJECT_DATA_HASH_TABLE, @@ -985,7 +985,7 @@ static int journal_file_append_data( o->object.size = htole64(offsetof(Object, data.payload) + rsize); o->object.flags |= OBJECT_COMPRESSED; - log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize); + log_debug("Compressed data object %"PRIu64" -> %"PRIu64, size, rsize); } } #endif @@ -1206,7 +1206,7 @@ static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { if (r < 0) return r; - /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */ + /* log_debug("=> %s seqnr=%"PRIu64" n_entries=%"PRIu64, f->path, o->entry.seqnum, f->header->n_entries); */ if (f->header->head_entry_realtime == 0) f->header->head_entry_realtime = o->entry.realtime; @@ -2227,10 +2227,10 @@ void journal_file_dump(JournalFile *f) { break; case OBJECT_ENTRY: - printf("Type: OBJECT_ENTRY seqnum=%llu monotonic=%llu realtime=%llu\n", - (unsigned long long) le64toh(o->entry.seqnum), - (unsigned long long) le64toh(o->entry.monotonic), - (unsigned long long) le64toh(o->entry.realtime)); + printf("Type: OBJECT_ENTRY seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", + le64toh(o->entry.seqnum), + le64toh(o->entry.monotonic), + le64toh(o->entry.realtime)); break; case OBJECT_FIELD_HASH_TABLE: @@ -2246,9 +2246,9 @@ void journal_file_dump(JournalFile *f) { break; case OBJECT_TAG: - printf("Type: OBJECT_TAG seqnum=%llu epoch=%llu\n", - (unsigned long long) le64toh(o->tag.seqnum), - (unsigned long long) le64toh(o->tag.epoch)); + printf("Type: OBJECT_TAG seqnum=%"PRIu64" epoch=%"PRIu64"\n", + le64toh(o->tag.seqnum), + le64toh(o->tag.epoch)); break; default: @@ -2270,9 +2270,18 @@ fail: log_error("File corrupt"); } +static const char* format_timestamp_safe(char *buf, size_t l, usec_t t) { + const char *x; + + x = format_timestamp(buf, l, t); + if (x) + return x; + return " --- "; +} + void journal_file_print_header(JournalFile *f) { - char a[33], b[33], c[33]; - char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX]; + char a[33], b[33], c[33], d[33]; + char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX], z[FORMAT_TIMESTAMP_MAX]; struct stat st; char bytes[FORMAT_BYTES_MAX]; @@ -2286,22 +2295,23 @@ void journal_file_print_header(JournalFile *f) { "State: %s\n" "Compatible Flags:%s%s\n" "Incompatible Flags:%s%s\n" - "Header size: %llu\n" - "Arena size: %llu\n" - "Data Hash Table Size: %llu\n" - "Field Hash Table Size: %llu\n" + "Header size: %"PRIu64"\n" + "Arena size: %"PRIu64"\n" + "Data Hash Table Size: %"PRIu64"\n" + "Field Hash Table Size: %"PRIu64"\n" "Rotate Suggested: %s\n" - "Head Sequential Number: %llu\n" - "Tail Sequential Number: %llu\n" + "Head Sequential Number: %"PRIu64"\n" + "Tail Sequential Number: %"PRIu64"\n" "Head Realtime Timestamp: %s\n" "Tail Realtime Timestamp: %s\n" - "Objects: %llu\n" - "Entry Objects: %llu\n", + "Tail Monotonic Timestamp: %s\n" + "Objects: %"PRIu64"\n" + "Entry Objects: %"PRIu64"\n", f->path, sd_id128_to_string(f->header->file_id, a), sd_id128_to_string(f->header->machine_id, b), sd_id128_to_string(f->header->boot_id, c), - sd_id128_to_string(f->header->seqnum_id, c), + sd_id128_to_string(f->header->seqnum_id, d), f->header->state == STATE_OFFLINE ? "OFFLINE" : f->header->state == STATE_ONLINE ? "ONLINE" : f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN", @@ -2309,36 +2319,37 @@ void journal_file_print_header(JournalFile *f) { (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) ? " ???" : "", JOURNAL_HEADER_COMPRESSED(f->header) ? " COMPRESSED" : "", (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "", - (unsigned long long) le64toh(f->header->header_size), - (unsigned long long) le64toh(f->header->arena_size), - (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem), - (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem), + le64toh(f->header->header_size), + le64toh(f->header->arena_size), + le64toh(f->header->data_hash_table_size) / sizeof(HashItem), + le64toh(f->header->field_hash_table_size) / sizeof(HashItem), yes_no(journal_file_rotate_suggested(f, 0)), - (unsigned long long) le64toh(f->header->head_entry_seqnum), - (unsigned long long) le64toh(f->header->tail_entry_seqnum), - format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)), - format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), - (unsigned long long) le64toh(f->header->n_objects), - (unsigned long long) le64toh(f->header->n_entries)); + le64toh(f->header->head_entry_seqnum), + le64toh(f->header->tail_entry_seqnum), + format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)), + format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), + format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC), + le64toh(f->header->n_objects), + le64toh(f->header->n_entries)); if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) - printf("Data Objects: %llu\n" + printf("Data Objects: %"PRIu64"\n" "Data Hash Table Fill: %.1f%%\n", - (unsigned long long) le64toh(f->header->n_data), + le64toh(f->header->n_data), 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)))); if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) - printf("Field Objects: %llu\n" + printf("Field Objects: %"PRIu64"\n" "Field Hash Table Fill: %.1f%%\n", - (unsigned long long) le64toh(f->header->n_fields), + le64toh(f->header->n_fields), 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)))); if (JOURNAL_HEADER_CONTAINS(f->header, n_tags)) - printf("Tag Objects: %llu\n", - (unsigned long long) le64toh(f->header->n_tags)); + printf("Tag Objects: %"PRIu64"\n", + le64toh(f->header->n_tags)); if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) - printf("Entry Array Objects: %llu\n", - (unsigned long long) le64toh(f->header->n_entry_arrays)); + printf("Entry Array Objects: %"PRIu64"\n", + le64toh(f->header->n_entry_arrays)); if (fstat(f->fd, &st) >= 0) printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (off_t) st.st_blocks * 512ULL)); @@ -2564,9 +2575,9 @@ int journal_file_rotate(JournalFile **f, bool compress, bool seal) { p[l-8] = '@'; sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1); snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1, - "-%016llx-%016llx.journal", - (unsigned long long) le64toh((*f)->header->head_entry_seqnum), - (unsigned long long) le64toh((*f)->header->head_entry_realtime)); + "-%016"PRIx64"-%016"PRIx64".journal", + le64toh((*f)->header->head_entry_seqnum), + le64toh((*f)->header->head_entry_realtime)); r = rename(old_file->path, p); free(p); @@ -2596,7 +2607,7 @@ int journal_file_open_reliably( int r; size_t l; - char *p; + _cleanup_free_ char *p = NULL; r = journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, template, ret); @@ -2627,7 +2638,6 @@ int journal_file_open_reliably( return -ENOMEM; r = rename(fname, p); - free(p); if (r < 0) return -errno; @@ -2873,23 +2883,23 @@ bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) { if (JOURNAL_HEADER_CONTAINS(f->header, n_data)) if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) { - log_debug("Data hash table of %s has a fill level at %.1f (%llu of %llu items, %llu file size, %llu bytes per hash table item), suggesting rotation.", + log_debug("Data hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items, %llu file size, %"PRIu64" bytes per hash table item), suggesting rotation.", f->path, 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))), - (unsigned long long) le64toh(f->header->n_data), - (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)), - (unsigned long long) (f->last_stat.st_size), - (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data))); + le64toh(f->header->n_data), + le64toh(f->header->data_hash_table_size) / sizeof(HashItem), + (unsigned long long) f->last_stat.st_size, + f->last_stat.st_size / le64toh(f->header->n_data)); return true; } if (JOURNAL_HEADER_CONTAINS(f->header, n_fields)) if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) { - log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.", + log_debug("Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.", f->path, 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))), - (unsigned long long) le64toh(f->header->n_fields), - (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))); + le64toh(f->header->n_fields), + le64toh(f->header->field_hash_table_size) / sizeof(HashItem)); return true; } diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 7b1cd42854..5cc2c2d28d 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -42,10 +42,14 @@ typedef struct JournalMetrics { uint64_t keep_free; } JournalMetrics; +typedef enum direction { + DIRECTION_UP, + DIRECTION_DOWN +} direction_t; + typedef struct JournalFile { int fd; - char *path; - struct stat last_stat; + mode_t mode; int flags; @@ -56,6 +60,11 @@ typedef struct JournalFile { bool tail_entry_monotonic_valid; + direction_t last_direction; + + char *path; + struct stat last_stat; + Header *header; HashItem *data_hash_table; HashItem *field_hash_table; @@ -90,11 +99,6 @@ typedef struct JournalFile { #endif } JournalFile; -typedef enum direction { - DIRECTION_UP, - DIRECTION_DOWN -} direction_t; - int journal_file_open( const char *fname, int flags, diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c index 745f45f932..06a236df6b 100644 --- a/src/journal/journal-gatewayd.c +++ b/src/journal/journal-gatewayd.c @@ -109,7 +109,7 @@ static int open_journal(RequestMeta *m) { if (m->journal) return 0; - return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY); + return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM); } static int respond_oom_internal(struct MHD_Connection *connection) { @@ -248,7 +248,7 @@ static ssize_t request_reader_entries( } } - r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH); + r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL); if (r < 0) { log_error("Failed to serialize item: %s", strerror(-r)); return MHD_CONTENT_READER_END_WITH_ERROR; @@ -834,17 +834,17 @@ static int request_handler_machine( "\"hostname\" : \"%s\"," "\"os_pretty_name\" : \"%s\"," "\"virtualization\" : \"%s\"," - "\"usage\" : \"%llu\"," - "\"cutoff_from_realtime\" : \"%llu\"," - "\"cutoff_to_realtime\" : \"%llu\" }\n", + "\"usage\" : \"%"PRIu64"\"," + "\"cutoff_from_realtime\" : \"%"PRIu64"\"," + "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n", SD_ID128_FORMAT_VAL(mid), SD_ID128_FORMAT_VAL(bid), hostname_cleanup(hostname, false), os_name ? os_name : "Linux", v ? v : "bare", - (unsigned long long) usage, - (unsigned long long) cutoff_from, - (unsigned long long) cutoff_to); + usage, + cutoff_from, + cutoff_to); if (r < 0) return respond_oom(connection); diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h index c7e585d810..5bc653537c 100644 --- a/src/journal/journal-internal.h +++ b/src/journal/journal-internal.h @@ -97,8 +97,6 @@ struct Directory { }; struct sd_journal { - int flags; - char *path; Hashmap *files; @@ -109,26 +107,29 @@ struct sd_journal { JournalFile *current_file; uint64_t current_field; - Hashmap *directories_by_path; - Hashmap *directories_by_wd; - - int inotify_fd; - Match *level0, *level1, *level2; + pid_t original_pid; + + int inotify_fd; unsigned current_invalidate_counter, last_invalidate_counter; + usec_t last_process_usec; char *unique_field; JournalFile *unique_file; uint64_t unique_offset; + int flags; + bool on_network; + bool no_new_files; size_t data_threshold; - Set *errors; + Hashmap *directories_by_path; + Hashmap *directories_by_wd; - usec_t last_process_usec; + Set *errors; }; char *journal_make_match_string(sd_journal *j); @@ -139,3 +140,6 @@ static inline void journal_closep(sd_journal **j) { } #define _cleanup_journal_close_ _cleanup_(journal_closep) + +#define JOURNAL_FOREACH_DATA_RETVAL(j, data, l, retval) \ + for (sd_journal_restart_data(j); ((retval) = sd_journal_enumerate_data((j), &(data), &(l))) > 0; ) diff --git a/src/journal/journal-qrcode.c b/src/journal/journal-qrcode.c index 10a14e4def..1db66e89c6 100644 --- a/src/journal/journal-qrcode.c +++ b/src/journal/journal-qrcode.c @@ -76,9 +76,9 @@ int print_qr_code( fprintf(f, "%02x", ((uint8_t*) seed)[i]); } - fprintf(f, "/%llx-%llx?machine=" SD_ID128_FORMAT_STR, - (unsigned long long) start, - (unsigned long long) interval, + fprintf(f, "/%"PRIx64"-%"PRIx64"?machine=" SD_ID128_FORMAT_STR, + start, + interval, SD_ID128_FORMAT_VAL(machine)); if (hn) diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c index 14c437da78..d00e26f1eb 100644 --- a/src/journal/journal-send.c +++ b/src/journal/journal-send.c @@ -204,8 +204,14 @@ _public_ int sd_journal_sendv(const struct iovec *iov, int n) { struct iovec *w; uint64_t *l; int i, j = 0; - struct msghdr mh; - struct sockaddr_un sa; + struct sockaddr_un sa = { + .sun_family = AF_UNIX, + .sun_path = "/run/systemd/journal/socket", + }; + struct msghdr mh = { + .msg_name = &sa, + .msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path), + }; ssize_t k; union { struct cmsghdr cmsghdr; @@ -239,7 +245,7 @@ _public_ int sd_journal_sendv(const struct iovec *iov, int n) { have_syslog_identifier = have_syslog_identifier || (c == (char *) iov[i].iov_base + 17 && - memcmp(iov[i].iov_base, "SYSLOG_IDENTIFIER", 17) == 0); + startswith(iov[i].iov_base, "SYSLOG_IDENTIFIER")); nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len); if (nl) { @@ -292,13 +298,6 @@ _public_ int sd_journal_sendv(const struct iovec *iov, int n) { if (_unlikely_(fd < 0)) return fd; - zero(sa); - sa.sun_family = AF_UNIX; - strncpy(sa.sun_path, "/run/systemd/journal/socket", sizeof(sa.sun_path)); - - zero(mh); - mh.msg_name = &sa; - mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path); mh.msg_iov = w; mh.msg_iovlen = j; @@ -402,7 +401,10 @@ _public_ int sd_journal_perror(const char *message) { } _public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { - union sockaddr_union sa; + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/stdout", + }; int fd; char *header; size_t l; @@ -415,10 +417,6 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve if (fd < 0) return -errno; - zero(sa); - sa.un.sun_family = AF_UNIX; - strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); - r = connect(fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)); if (r < 0) { close_nointr_nofail(fd); diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c index 4a3a5a9e63..c73ad8f393 100644 --- a/src/journal/journal-vacuum.c +++ b/src/journal/journal-vacuum.c @@ -128,6 +128,25 @@ static void patch_realtime( #endif } +static int journal_file_empty(int dir_fd, const char *name) { + int r; + le64_t n_entries; + _cleanup_close_ int fd; + + fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); + if (fd < 0) + return -errno; + + if (lseek(fd, offsetof(Header, n_entries), SEEK_SET) < 0) + return -errno; + + r = read(fd, &n_entries, sizeof(n_entries)); + if (r != sizeof(n_entries)) + return r == 0 ? -EINVAL : -errno; + + return le64toh(n_entries) == 0; +} + int journal_directory_vacuum( const char *directory, uint64_t max_use, @@ -135,11 +154,12 @@ int journal_directory_vacuum( usec_t max_retention_usec, usec_t *oldest_usec) { - DIR *d; + _cleanup_closedir_ DIR *d = NULL; int r = 0; struct vacuum_info *list = NULL; - unsigned n_list = 0, n_allocated = 0, i; - uint64_t sum = 0; + unsigned n_list = 0, i; + size_t n_allocated = 0; + uint64_t sum = 0, freed = 0; usec_t retention_limit = 0; assert(directory); @@ -246,22 +266,25 @@ int journal_directory_vacuum( /* We do not vacuum active files or unknown files! */ continue; - patch_realtime(directory, de->d_name, &st, &realtime); + if (journal_file_empty(dirfd(d), p)) { + /* Always vacuum empty non-online files. */ - if (n_list >= n_allocated) { - struct vacuum_info *j; + uint64_t size = 512UL * (uint64_t) st.st_blocks; - n_allocated = MAX(n_allocated * 2U, 8U); - j = realloc(list, n_allocated * sizeof(struct vacuum_info)); - if (!j) { - free(p); - r = -ENOMEM; - goto finish; - } + if (unlinkat(dirfd(d), p, 0) >= 0) { + log_info("Deleted empty journal %s/%s (%"PRIu64" bytes).", + directory, p, size); + freed += size; + } else if (errno != ENOENT) + log_warning("Failed to delete %s/%s: %m", directory, p); - list = j; + continue; } + patch_realtime(directory, p, &st, &realtime); + + GREEDY_REALLOC(list, n_allocated, n_list + 1); + list[n_list].filename = p; list[n_list].usage = 512UL * (uint64_t) st.st_blocks; list[n_list].seqnum = seqnum; @@ -291,7 +314,9 @@ int journal_directory_vacuum( break; if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) { - log_debug("Deleted archived journal %s/%s.", directory, list[i].filename); + log_debug("Deleted archived journal %s/%s (%"PRIu64" bytes).", + directory, list[i].filename, list[i].usage); + freed += list[i].usage; if (list[i].usage < sum) sum -= list[i].usage; @@ -308,11 +333,9 @@ int journal_directory_vacuum( finish: for (i = 0; i < n_list; i++) free(list[i].filename); - free(list); - if (d) - closedir(d); + log_info("Vacuuming done, freed %"PRIu64" bytes", freed); return r; } diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c index ed28b45737..3405811534 100644 --- a/src/journal/journal-verify.c +++ b/src/journal/journal-verify.c @@ -34,10 +34,15 @@ #include "compress.h" #include "fsprg.h" -static int journal_file_object_verify(JournalFile *f, Object *o) { +/* Use six characters to cover the offsets common in smallish journal + * files without adding to many zeros. */ +#define OFSfmt "%06"PRIx64 + +static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) { uint64_t i; assert(f); + assert(offset); assert(o); /* This does various superficial tests about the length an @@ -53,12 +58,21 @@ static int journal_file_object_verify(JournalFile *f, Object *o) { case OBJECT_DATA: { uint64_t h1, h2; - if (le64toh(o->data.entry_offset) <= 0 || - le64toh(o->data.n_entries) <= 0) + if (le64toh(o->data.entry_offset) == 0) + log_warning(OFSfmt": unused data (entry_offset==0)", offset); + + if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { + log_error(OFSfmt": bad n_entries: %"PRIu64, offset, o->data.n_entries); return -EBADMSG; + } - if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) + if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) { + log_error(OFSfmt": bad object size (<= %zu): %"PRIu64, + offset, + offsetof(DataObject, payload), + le64toh(o->object.size)); return -EBADMSG; + } h1 = le64toh(o->data.hash); @@ -69,104 +83,197 @@ static int journal_file_object_verify(JournalFile *f, Object *o) { if (!uncompress_blob(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload), - &b, &alloc, &b_size, 0)) + &b, &alloc, &b_size, 0)) { + log_error(OFSfmt": uncompression failed", offset); return -EBADMSG; + } h2 = hash64(b, b_size); free(b); #else + log_error("Compression is not supported"); return -EPROTONOSUPPORT; #endif } else h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload)); - if (h1 != h2) + if (h1 != h2) { + log_error(OFSfmt": invalid hash (%08"PRIx64" vs. %08"PRIx64, offset, h1, h2); return -EBADMSG; + } if (!VALID64(o->data.next_hash_offset) || !VALID64(o->data.next_field_offset) || !VALID64(o->data.entry_offset) || - !VALID64(o->data.entry_array_offset)) + !VALID64(o->data.entry_array_offset)) { + log_error(OFSfmt": invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt, + offset, + o->data.next_hash_offset, + o->data.next_field_offset, + o->data.entry_offset, + o->data.entry_array_offset); return -EBADMSG; + } break; } case OBJECT_FIELD: - if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) + if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) { + log_error(OFSfmt": bad field size (<= %zu): %"PRIu64, + offset, + offsetof(FieldObject, payload), + le64toh(o->object.size)); return -EBADMSG; + } if (!VALID64(o->field.next_hash_offset) || - !VALID64(o->field.head_data_offset)) + !VALID64(o->field.head_data_offset)) { + log_error(OFSfmt": invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt, + offset, + o->field.next_hash_offset, + o->field.head_data_offset); return -EBADMSG; + } break; case OBJECT_ENTRY: - if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) + if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) { + log_error(OFSfmt": bad entry size (<= %zu): %"PRIu64, + offset, + offsetof(EntryObject, items), + le64toh(o->object.size)); return -EBADMSG; + } - if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) + if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) { + log_error(OFSfmt": invalid number items in entry: %"PRIu64, + offset, + (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem)); return -EBADMSG; + } + + if (le64toh(o->entry.seqnum) <= 0) { + log_error(OFSfmt": invalid entry seqnum: %"PRIx64, + offset, + le64toh(o->entry.seqnum)); + return -EBADMSG; + } - if (le64toh(o->entry.seqnum) <= 0 || - !VALID_REALTIME(le64toh(o->entry.realtime)) || - !VALID_MONOTONIC(le64toh(o->entry.monotonic))) + if (!VALID_REALTIME(le64toh(o->entry.realtime))) { + log_error(OFSfmt": invalid entry realtime timestamp: %"PRIu64, + offset, + le64toh(o->entry.realtime)); return -EBADMSG; + } + + if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) { + log_error(OFSfmt": invalid entry monotonic timestamp: %"PRIu64, + offset, + le64toh(o->entry.monotonic)); + return -EBADMSG; + } for (i = 0; i < journal_file_entry_n_items(o); i++) { if (o->entry.items[i].object_offset == 0 || - !VALID64(o->entry.items[i].object_offset)) + !VALID64(o->entry.items[i].object_offset)) { + log_error(OFSfmt": invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt, + offset, + i, journal_file_entry_n_items(o), + o->entry.items[i].object_offset); return -EBADMSG; + } } break; case OBJECT_DATA_HASH_TABLE: case OBJECT_FIELD_HASH_TABLE: - if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0) - return -EBADMSG; - - if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) + if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 || + (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) { + log_error(OFSfmt": invalid %s hash table size: %"PRIu64, + offset, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + le64toh(o->object.size)); return -EBADMSG; + } for (i = 0; i < journal_file_hash_table_n_items(o); i++) { if (o->hash_table.items[i].head_hash_offset != 0 && - !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) + !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) { + log_error(OFSfmt": invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt, + offset, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + i, journal_file_hash_table_n_items(o), + le64toh(o->hash_table.items[i].head_hash_offset)); return -EBADMSG; + } if (o->hash_table.items[i].tail_hash_offset != 0 && - !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) + !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) { + log_error(OFSfmt": invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt, + offset, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + i, journal_file_hash_table_n_items(o), + le64toh(o->hash_table.items[i].tail_hash_offset)); return -EBADMSG; + } if ((o->hash_table.items[i].head_hash_offset != 0) != - (o->hash_table.items[i].tail_hash_offset != 0)) + (o->hash_table.items[i].tail_hash_offset != 0)) { + log_error(OFSfmt": invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt, + offset, + o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", + i, journal_file_hash_table_n_items(o), + le64toh(o->hash_table.items[i].head_hash_offset), + le64toh(o->hash_table.items[i].tail_hash_offset)); return -EBADMSG; + } } break; case OBJECT_ENTRY_ARRAY: - if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0) - return -EBADMSG; - - if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) + if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 || + (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) { + log_error(OFSfmt": invalid object entry array size: %"PRIu64, + offset, + le64toh(o->object.size)); return -EBADMSG; + } - if (!VALID64(o->entry_array.next_entry_array_offset)) + if (!VALID64(o->entry_array.next_entry_array_offset)) { + log_error(OFSfmt": invalid object entry array next_entry_array_offset: "OFSfmt, + offset, + o->entry_array.next_entry_array_offset); return -EBADMSG; + } for (i = 0; i < journal_file_entry_array_n_items(o); i++) if (o->entry_array.items[i] != 0 && - !VALID64(o->entry_array.items[i])) + !VALID64(o->entry_array.items[i])) { + log_error(OFSfmt": invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, + offset, + i, journal_file_entry_array_n_items(o), + o->entry_array.items[i]); return -EBADMSG; + } break; case OBJECT_TAG: - if (le64toh(o->object.size) != sizeof(TagObject)) + if (le64toh(o->object.size) != sizeof(TagObject)) { + log_error(OFSfmt": invalid object tag size: %"PRIu64, + offset, + le64toh(o->object.size)); return -EBADMSG; + } - if (!VALID_EPOCH(o->tag.epoch)) + if (!VALID_EPOCH(o->tag.epoch)) { + log_error(OFSfmt": invalid object tag epoch: %"PRIu64, + offset, + o->tag.epoch); return -EBADMSG; + } break; } @@ -203,7 +310,7 @@ static void draw_progress(uint64_t p, usec_t *last_usec) { for (i = 0; i < k; i++) fputs("\xe2\x96\x91", stdout); - printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU); + printf(" %3"PRIu64"%%", 100U * p / 65535U); fputs("\r\x1B[?25h", stdout); fflush(stdout); @@ -288,7 +395,7 @@ static int entry_points_to_data( assert(entry_fd >= 0); if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) { - log_error("Data object references invalid entry at %llu", (unsigned long long) data_p); + log_error("Data object references invalid entry at %"PRIu64, data_p); return -EBADMSG; } @@ -304,7 +411,7 @@ static int entry_points_to_data( } if (!found) { - log_error("Data object not referenced by linked entry at %llu", (unsigned long long) data_p); + log_error("Data object not referenced by linked entry at %"PRIu64, data_p); return -EBADMSG; } @@ -347,7 +454,7 @@ static int entry_points_to_data( x = z; } - log_error("Entry object doesn't exist in main entry array at %llu", (unsigned long long) entry_p); + log_error("Entry object doesn't exist in main entry array at %"PRIu64, entry_p); return -EBADMSG; } @@ -375,8 +482,18 @@ static int verify_data( n = le64toh(o->data.n_entries); a = le64toh(o->data.entry_array_offset); - /* We already checked this earlier */ - assert(n > 0); + /* Entry array means at least two objects */ + if (a && n < 2) { + log_error("Entry array present (entry_array_offset=%"PRIu64", but n_entries=%"PRIu64, + a, n); + return -EBADMSG; + } + + if (n == 0) + return 0; + + /* We already checked that earlier */ + assert(o->data.entry_offset); last = q = le64toh(o->data.entry_offset); r = entry_points_to_data(f, entry_fd, n_entries, q, p); @@ -388,12 +505,12 @@ static int verify_data( uint64_t next, m, j; if (a == 0) { - log_error("Array chain too short at %llu", (unsigned long long) p); + log_error("Array chain too short at %"PRIu64, p); return -EBADMSG; } if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { - log_error("Invalid array at %llu", (unsigned long long) p); + log_error("Invalid array at %"PRIu64, p); return -EBADMSG; } @@ -403,7 +520,7 @@ static int verify_data( next = le64toh(o->entry_array.next_entry_array_offset); if (next != 0 && next <= a) { - log_error("Array chain has cycle at %llu", (unsigned long long) p); + log_error("Array chain has cycle at %"PRIu64, p); return -EBADMSG; } @@ -412,7 +529,7 @@ static int verify_data( q = le64toh(o->entry_array.items[j]); if (q <= last) { - log_error("Data object's entry array not sorted at %llu", (unsigned long long) p); + log_error("Data object's entry array not sorted at %"PRIu64, p); return -EBADMSG; } last = q; @@ -463,8 +580,8 @@ static int verify_hash_table( uint64_t next; if (!contains_uint64(f->mmap, data_fd, n_data, p)) { - log_error("Invalid data object at hash entry %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Invalid data object at hash entry %"PRIu64" of %"PRIu64, + i, n); return -EBADMSG; } @@ -474,14 +591,14 @@ static int verify_hash_table( next = le64toh(o->data.next_hash_offset); if (next != 0 && next <= p) { - log_error("Hash chain has a cycle in hash entry %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, + i, n); return -EBADMSG; } if (le64toh(o->data.hash) % n != i) { - log_error("Hash value mismatch in hash entry %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, + i, n); return -EBADMSG; } @@ -548,8 +665,7 @@ static int verify_entry( h = le64toh(o->entry.items[i].hash); if (!contains_uint64(f->mmap, data_fd, n_data, q)) { - log_error("Invalid data object at entry %llu", - (unsigned long long) p); + log_error("Invalid data object at entry %"PRIu64, p); return -EBADMSG; } @@ -558,8 +674,7 @@ static int verify_entry( return r; if (le64toh(u->data.hash) != h) { - log_error("Hash mismatch for data object at entry %llu", - (unsigned long long) p); + log_error("Hash mismatch for data object at entry %"PRIu64, p); return -EBADMSG; } @@ -567,8 +682,7 @@ static int verify_entry( if (r < 0) return r; if (r == 0) { - log_error("Data object missing from hash at entry %llu", - (unsigned long long) p); + log_error("Data object missing from hash at entry %"PRIu64, p); return -EBADMSG; } } @@ -603,14 +717,12 @@ static int verify_entry_array( draw_progress(0x8000 + (0x3FFF * i / n), last_usec); if (a == 0) { - log_error("Array chain too short at %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Array chain too short at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { - log_error("Invalid array at %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Invalid array at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } @@ -620,8 +732,7 @@ static int verify_entry_array( next = le64toh(o->entry_array.next_entry_array_offset); if (next != 0 && next <= a) { - log_error("Array chain has cycle at %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Array chain has cycle at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } @@ -631,15 +742,15 @@ static int verify_entry_array( p = le64toh(o->entry_array.items[j]); if (p <= last) { - log_error("Entry array not sorted at %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Entry array not sorted at %"PRIu64" of %"PRIu64, + i, n); return -EBADMSG; } last = p; if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) { - log_error("Invalid array entry at %llu of %llu", - (unsigned long long) i, (unsigned long long) n); + log_error("Invalid array entry at %"PRIu64" of %"PRIu64, + i, n); return -EBADMSG; } @@ -753,7 +864,7 @@ int journal_file_verify( r = journal_file_move_to_object(f, -1, p, &o); if (r < 0) { - log_error("Invalid object at %llu", (unsigned long long) p); + log_error("Invalid object at "OFSfmt, p); goto fail; } @@ -768,14 +879,14 @@ int journal_file_verify( n_objects ++; - r = journal_file_object_verify(f, o); + r = journal_file_object_verify(f, p, o); if (r < 0) { - log_error("Invalid object contents at %llu", (unsigned long long) p); + log_error("Invalid object contents at "OFSfmt": %s", p, strerror(-r)); goto fail; } if ((o->object.flags & OBJECT_COMPRESSED) && !JOURNAL_HEADER_COMPRESSED(f->header)) { - log_error("Compressed object in file without compression at %llu", (unsigned long long) p); + log_error("Compressed object in file without compression at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -796,7 +907,7 @@ int journal_file_verify( case OBJECT_ENTRY: if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) { - log_error("First entry before first tag at %llu", (unsigned long long) p); + log_error("First entry before first tag at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -806,21 +917,21 @@ int journal_file_verify( goto fail; if (le64toh(o->entry.realtime) < last_tag_realtime) { - log_error("Older entry after newer tag at %llu", (unsigned long long) p); + log_error("Older entry after newer tag at "OFSfmt, p); r = -EBADMSG; goto fail; } if (!entry_seqnum_set && le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) { - log_error("Head entry sequence number incorrect at %llu", (unsigned long long) p); + log_error("Head entry sequence number incorrect at "OFSfmt, p); r = -EBADMSG; goto fail; } if (entry_seqnum_set && entry_seqnum >= le64toh(o->entry.seqnum)) { - log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p); + log_error("Entry sequence number out of synchronization at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -831,7 +942,7 @@ int journal_file_verify( if (entry_monotonic_set && sd_id128_equal(entry_boot_id, o->entry.boot_id) && entry_monotonic > le64toh(o->entry.monotonic)) { - log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p); + log_error("Entry timestamp out of synchronization at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -855,7 +966,7 @@ int journal_file_verify( case OBJECT_DATA_HASH_TABLE: if (n_data_hash_tables > 1) { - log_error("More than one data hash table at %llu", (unsigned long long) p); + log_error("More than one data hash table at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -872,7 +983,7 @@ int journal_file_verify( case OBJECT_FIELD_HASH_TABLE: if (n_field_hash_tables > 1) { - log_error("More than one field hash table at %llu", (unsigned long long) p); + log_error("More than one field hash table at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -894,7 +1005,7 @@ int journal_file_verify( if (p == le64toh(f->header->entry_array_offset)) { if (found_main_entry_array) { - log_error("More than one main entry array at %llu", (unsigned long long) p); + log_error("More than one main entry array at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -907,19 +1018,19 @@ int journal_file_verify( case OBJECT_TAG: if (!JOURNAL_HEADER_SEALED(f->header)) { - log_error("Tag object in file without sealing at %llu", (unsigned long long) p); + log_error("Tag object in file without sealing at "OFSfmt, p); r = -EBADMSG; goto fail; } if (le64toh(o->tag.seqnum) != n_tags + 1) { - log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p); + log_error("Tag sequence number out of synchronization at "OFSfmt, p); r = -EBADMSG; goto fail; } if (le64toh(o->tag.epoch) < last_epoch) { - log_error("Epoch sequence out of synchronization at %llu", (unsigned long long) p); + log_error("Epoch sequence out of synchronization at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -928,11 +1039,11 @@ int journal_file_verify( if (f->seal) { uint64_t q, rt; - log_debug("Checking tag %llu..", (unsigned long long) le64toh(o->tag.seqnum)); + log_debug("Checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec; if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) { - log_error("Tag/entry realtime timestamp out of synchronization at %llu", (unsigned long long) p); + log_error("Tag/entry realtime timestamp out of synchronization at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -975,7 +1086,7 @@ int journal_file_verify( goto fail; if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { - log_error("Tag failed verification at %llu", (unsigned long long) p); + log_error("Tag failed verification at "OFSfmt, p); r = -EBADMSG; goto fail; } @@ -1138,11 +1249,11 @@ fail: if (show_progress) flush_progress(); - log_error("File corruption detected at %s:%llu (of %llu, %llu%%).", + log_error("File corruption detected at %s:"OFSfmt" (of %llu bytes, %"PRIu64"%%).", f->path, - (unsigned long long) p, + p, (unsigned long long) f->last_stat.st_size, - (unsigned long long) (100 * p / f->last_stat.st_size)); + 100 * p / f->last_stat.st_size); if (data_fd >= 0) { mmap_cache_close_fd(f->mmap, data_fd); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 409f082276..9a2d255361 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -45,6 +45,7 @@ #include "logs-show.h" #include "util.h" #include "path-util.h" +#include "fileio.h" #include "build.h" #include "pager.h" #include "logs-show.h" @@ -70,13 +71,19 @@ static int arg_lines = -1; static bool arg_no_tail = false; static bool arg_quiet = false; static bool arg_merge = false; -static bool arg_this_boot = false; +static bool arg_boot = false; +static char *arg_boot_descriptor = NULL; +static bool arg_dmesg = false; static const char *arg_cursor = NULL; +static const char *arg_after_cursor = NULL; +static bool arg_show_cursor = false; static const char *arg_directory = NULL; +static char **arg_file = NULL; static int arg_priorities = 0xFF; static const char *arg_verify_key = NULL; #ifdef HAVE_GCRYPT static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; +static bool arg_force = false; #endif static usec_t arg_since, arg_until; static bool arg_since_set = false, arg_until_set = false; @@ -85,6 +92,7 @@ static char **arg_user_units = NULL; static const char *arg_field = NULL; static bool arg_catalog = false; static bool arg_reverse = false; +static int arg_journal_type = 0; static const char *arg_root = NULL; static enum { @@ -99,50 +107,63 @@ static enum { ACTION_UPDATE_CATALOG } arg_action = ACTION_SHOW; +typedef struct boot_id_t { + sd_id128_t id; + uint64_t timestamp; +} boot_id_t; + static int help(void) { printf("%s [OPTIONS...] [MATCHES...]\n\n" "Query the journal.\n\n" "Flags:\n" - " --since=DATE Start showing entries newer or of the specified date\n" - " --until=DATE Stop showing entries older or of the specified date\n" - " -c --cursor=CURSOR Start showing entries from specified cursor\n" - " -b --this-boot Show data only from current boot\n" - " -u --unit=UNIT Show data only from the specified unit\n" - " --user-unit=UNIT Show data only from the specified user session unit\n" - " -p --priority=RANGE Show only messages within the specified priority range\n" - " -e --pager-end Immediately jump to end of the journal in the pager\n" - " -f --follow Follow journal\n" - " -n --lines[=INTEGER] Number of journal entries to show\n" - " --no-tail Show all lines, even in follow mode\n" - " -r --reverse Show the newest entries first\n" - " -o --output=STRING Change journal output mode (short, short-monotonic,\n" - " verbose, export, json, json-pretty, json-sse, cat)\n" - " -x --catalog Add message explanations where available\n" - " --full Do not ellipsize fields\n" - " -a --all Show all fields, including long and unprintable\n" - " -q --quiet Don't show privilege warning\n" - " --no-pager Do not pipe output into a pager\n" - " -m --merge Show entries from all available journals\n" - " -D --directory=PATH Show journal files from directory\n" - " --root=ROOT Operate on catalog files underneath the root ROOT\n" + " --system Show only the system journal\n" + " --user Show only the user journal for current user\n" + " --since=DATE Start showing entries newer or of the specified date\n" + " --until=DATE Stop showing entries older or of the specified date\n" + " -c --cursor=CURSOR Start showing entries from specified cursor\n" + " --after-cursor=CURSOR Start showing entries from specified cursor\n" + " --show-cursor Print the cursor after all the entries\n" + " -b --boot[=ID] Show data only from ID or current boot if unspecified\n" + " -k --dmesg Show kernel message log from current boot\n" + " -u --unit=UNIT Show data only from the specified unit\n" + " --user-unit=UNIT Show data only from the specified user session unit\n" + " -p --priority=RANGE Show only messages within the specified priority range\n" + " -e --pager-end Immediately jump to end of the journal in the pager\n" + " -f --follow Follow journal\n" + " -n --lines[=INTEGER] Number of journal entries to show\n" + " --no-tail Show all lines, even in follow mode\n" + " -r --reverse Show the newest entries first\n" + " -o --output=STRING Change journal output mode (short, short-iso,\n" + " short-precise, short-monotonic, verbose,\n" + " export, json, json-pretty, json-sse, cat)\n" + " -x --catalog Add message explanations where available\n" + " -l --full Do not ellipsize fields\n" + " -a --all Show all fields, including long and unprintable\n" + " -q --quiet Don't show privilege warning\n" + " --no-pager Do not pipe output into a pager\n" + " -m --merge Show entries from all available journals\n" + " -D --directory=PATH Show journal files from directory\n" + " --file=PATH Show journal file\n" + " --root=ROOT Operate on catalog files underneath the root ROOT\n" #ifdef HAVE_GCRYPT - " --interval=TIME Time interval for changing the FSS sealing key\n" - " --verify-key=KEY Specify FSS verification key\n" + " --interval=TIME Time interval for changing the FSS sealing key\n" + " --verify-key=KEY Specify FSS verification key\n" + " --force Force overriding new FSS key pair with --setup-keys\n" #endif "\nCommands:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --new-id128 Generate a new 128 Bit ID\n" - " --header Show journal header information\n" - " --disk-usage Show total disk usage\n" - " -F --field=FIELD List all values a certain field takes\n" - " --list-catalog Show message IDs of all entries in the message catalog\n" - " --dump-catalog Show entries in the message catalog\n" - " --update-catalog Update the message catalog database\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --new-id128 Generate a new 128 Bit ID\n" + " --header Show journal header information\n" + " --disk-usage Show total disk usage\n" + " -F --field=FIELD List all values a certain field takes\n" + " --list-catalog Show message IDs of all entries in the message catalog\n" + " --dump-catalog Show entries in the message catalog\n" + " --update-catalog Update the message catalog database\n" #ifdef HAVE_GCRYPT - " --setup-keys Generate new FSS key pair\n" - " --verify Verify journal file consistency\n" + " --setup-keys Generate new FSS key pair\n" + " --verify Verify journal file consistency\n" #endif , program_invocation_short_name); @@ -156,58 +177,71 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_PAGER, ARG_NO_TAIL, ARG_NEW_ID128, + ARG_USER, + ARG_SYSTEM, ARG_ROOT, ARG_HEADER, - ARG_FULL, ARG_SETUP_KEYS, + ARG_FILE, ARG_INTERVAL, ARG_VERIFY, ARG_VERIFY_KEY, ARG_DISK_USAGE, ARG_SINCE, ARG_UNTIL, + ARG_AFTER_CURSOR, + ARG_SHOW_CURSOR, ARG_USER_UNIT, ARG_LIST_CATALOG, ARG_DUMP_CATALOG, - ARG_UPDATE_CATALOG + ARG_UPDATE_CATALOG, + ARG_FORCE, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "pager-end", no_argument, NULL, 'e' }, - { "follow", no_argument, NULL, 'f' }, - { "output", required_argument, NULL, 'o' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, ARG_FULL }, - { "lines", optional_argument, NULL, 'n' }, - { "no-tail", no_argument, NULL, ARG_NO_TAIL }, - { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, - { "quiet", no_argument, NULL, 'q' }, - { "merge", no_argument, NULL, 'm' }, - { "this-boot", no_argument, NULL, 'b' }, - { "directory", required_argument, NULL, 'D' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "header", no_argument, NULL, ARG_HEADER }, - { "priority", required_argument, NULL, 'p' }, - { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, - { "interval", required_argument, NULL, ARG_INTERVAL }, - { "verify", no_argument, NULL, ARG_VERIFY }, - { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, - { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, - { "cursor", required_argument, NULL, 'c' }, - { "since", required_argument, NULL, ARG_SINCE }, - { "until", required_argument, NULL, ARG_UNTIL }, - { "unit", required_argument, NULL, 'u' }, - { "user-unit", required_argument, NULL, ARG_USER_UNIT }, - { "field", required_argument, NULL, 'F' }, - { "catalog", no_argument, NULL, 'x' }, - { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, - { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, - { "update-catalog",no_argument, NULL, ARG_UPDATE_CATALOG }, - { "reverse", no_argument, NULL, 'r' }, - { NULL, 0, NULL, 0 } + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "pager-end", no_argument, NULL, 'e' }, + { "follow", no_argument, NULL, 'f' }, + { "force", no_argument, NULL, ARG_FORCE }, + { "output", required_argument, NULL, 'o' }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "lines", optional_argument, NULL, 'n' }, + { "no-tail", no_argument, NULL, ARG_NO_TAIL }, + { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, + { "quiet", no_argument, NULL, 'q' }, + { "merge", no_argument, NULL, 'm' }, + { "boot", optional_argument, NULL, 'b' }, + { "this-boot", optional_argument, NULL, 'b' }, /* deprecated */ + { "dmesg", no_argument, NULL, 'k' }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "directory", required_argument, NULL, 'D' }, + { "file", required_argument, NULL, ARG_FILE }, + { "root", required_argument, NULL, ARG_ROOT }, + { "header", no_argument, NULL, ARG_HEADER }, + { "priority", required_argument, NULL, 'p' }, + { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, + { "interval", required_argument, NULL, ARG_INTERVAL }, + { "verify", no_argument, NULL, ARG_VERIFY }, + { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, + { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, + { "cursor", required_argument, NULL, 'c' }, + { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, + { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR }, + { "since", required_argument, NULL, ARG_SINCE }, + { "until", required_argument, NULL, ARG_UNTIL }, + { "unit", required_argument, NULL, 'u' }, + { "user-unit", required_argument, NULL, ARG_USER_UNIT }, + { "field", required_argument, NULL, 'F' }, + { "catalog", no_argument, NULL, 'x' }, + { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, + { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, + { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG }, + { "reverse", no_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } }; int c, r; @@ -215,7 +249,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hefo:an::qmbD:p:c:u:F:xr", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:u:F:xr", options, NULL)) >= 0) { switch (c) { @@ -260,7 +294,7 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_FULL: + case 'l': arg_full = true; break; @@ -314,13 +348,46 @@ static int parse_argv(int argc, char *argv[]) { break; case 'b': - arg_this_boot = true; + arg_boot = true; + + if (optarg) + arg_boot_descriptor = optarg; + else if (optind < argc) { + int boot; + + if (argv[optind][0] != '-' || + safe_atoi(argv[optind], &boot) >= 0) { + arg_boot_descriptor = argv[optind]; + optind++; + } + } + + break; + + case 'k': + arg_boot = arg_dmesg = true; + break; + + case ARG_SYSTEM: + arg_journal_type |= SD_JOURNAL_SYSTEM; + break; + + case ARG_USER: + arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; case 'D': arg_directory = optarg; break; + case ARG_FILE: + r = glob_extend(&arg_file, optarg); + if (r < 0) { + log_error("Failed to add paths: %s", strerror(-r)); + return r; + }; + break; + case ARG_ROOT: arg_root = optarg; break; @@ -329,6 +396,14 @@ static int parse_argv(int argc, char *argv[]) { arg_cursor = optarg; break; + case ARG_AFTER_CURSOR: + arg_after_cursor = optarg; + break; + + case ARG_SHOW_CURSOR: + arg_show_cursor = true; + break; + case ARG_HEADER: arg_action = ACTION_PRINT_HEADER; break; @@ -342,6 +417,10 @@ static int parse_argv(int argc, char *argv[]) { break; #ifdef HAVE_GCRYPT + case ARG_FORCE: + arg_force = true; + break; + case ARG_SETUP_KEYS: arg_action = ACTION_SETUP_KEYS; break; @@ -364,6 +443,7 @@ static int parse_argv(int argc, char *argv[]) { case ARG_SETUP_KEYS: case ARG_VERIFY_KEY: case ARG_INTERVAL: + case ARG_FORCE: log_error("Forward-secure sealing not available."); return -ENOTSUP; #endif @@ -484,13 +564,18 @@ static int parse_argv(int argc, char *argv[]) { if (arg_follow && !arg_no_tail && arg_lines < 0) arg_lines = 10; + if (arg_directory && arg_file) { + log_error("Please specify either -D/--directory= or --file=, not both."); + return -EINVAL; + } + if (arg_since_set && arg_until_set && arg_since > arg_until) { log_error("--since= must be before --until=."); return -EINVAL; } - if (arg_cursor && arg_since_set) { - log_error("Please specify either --since= or --cursor=, not both."); + if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1) { + log_error("Please specify only one of --since=, --cursor=, and --after-cursor."); return -EINVAL; } @@ -544,8 +629,9 @@ static int add_matches(sd_journal *j, char **args) { if (streq(*i, "+")) r = sd_journal_add_disjunction(j); else if (path_is_absolute(*i)) { - _cleanup_free_ char *p, *t = NULL; + _cleanup_free_ char *p, *t = NULL, *t2 = NULL; const char *path; + _cleanup_free_ char *interpreter = NULL; struct stat st; p = canonicalize_file_name(*i); @@ -556,9 +642,27 @@ static int add_matches(sd_journal *j, char **args) { return -errno; } - if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) - t = strappend("_EXE=", path); - else if (S_ISCHR(st.st_mode)) + if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) { + if (executable_is_script(path, &interpreter) > 0) { + _cleanup_free_ char *comm; + + comm = strndup(path_get_file_name(path), 15); + if (!comm) + return log_oom(); + + t = strappend("_COMM=", comm); + + /* Append _EXE only if the interpreter is not a link. + Otherwise it might be outdated often. */ + if (lstat(interpreter, &st) == 0 && + !S_ISLNK(st.st_mode)) { + t2 = strappend("_EXE=", interpreter); + if (!t2) + return log_oom(); + } + } else + t = strappend("_EXE=", path); + } else if (S_ISCHR(st.st_mode)) asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev)); else if (S_ISBLK(st.st_mode)) asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev)); @@ -571,6 +675,8 @@ static int add_matches(sd_journal *j, char **args) { return log_oom(); r = sd_journal_add_match(j, t, 0); + if (t2) + r = sd_journal_add_match(j, t2, 0); } else r = sd_journal_add_match(j, *i, 0); @@ -583,24 +689,169 @@ static int add_matches(sd_journal *j, char **args) { return 0; } -static int add_this_boot(sd_journal *j) { - char match[9+32+1] = "_BOOT_ID="; - sd_id128_t boot_id; +static int boot_id_cmp(const void *a, const void *b) { + uint64_t _a, _b; + + _a = ((const boot_id_t *)a)->timestamp; + _b = ((const boot_id_t *)b)->timestamp; + + return _a < _b ? -1 : (_a > _b ? 1 : 0); +} + +static int get_relative_boot_id(sd_journal *j, sd_id128_t *boot_id, int relative) { int r; + const void *data; + unsigned int count = 0; + size_t length, allocated = 0; + boot_id_t ref_boot_id = {SD_ID128_NULL}, *id; + _cleanup_free_ boot_id_t *all_ids = NULL; + + assert(j); + assert(boot_id); + + if (relative == 0 && !sd_id128_equal(*boot_id, SD_ID128_NULL)) + return 0; + + r = sd_journal_query_unique(j, "_BOOT_ID"); + if (r < 0) + return r; + + SD_JOURNAL_FOREACH_UNIQUE(j, data, length) { + if (length < strlen("_BOOT_ID=")) + continue; + + if (!GREEDY_REALLOC(all_ids, allocated, count + 1)) + return log_oom(); + + id = &all_ids[count]; + + r = sd_id128_from_string(((const char *)data) + strlen("_BOOT_ID="), &id->id); + if (r < 0) + continue; + + r = sd_journal_add_match(j, data, length); + if (r < 0) + return r; + + r = sd_journal_seek_head(j); + if (r < 0) + return r; + + r = sd_journal_next(j); + if (r < 0) + return r; + else if (r == 0) + goto flush; + + r = sd_journal_get_realtime_usec(j, &id->timestamp); + if (r < 0) + return r; + + if (sd_id128_equal(id->id, *boot_id)) + ref_boot_id = *id; + + count++; + flush: + sd_journal_flush_matches(j); + } + + qsort(all_ids, count, sizeof(boot_id_t), boot_id_cmp); + + if (sd_id128_equal(*boot_id, SD_ID128_NULL)) { + if (relative > (int) count || relative <= -(int)count) + return -EADDRNOTAVAIL; + + *boot_id = all_ids[(relative <= 0)*count + relative - 1].id; + } else { + id = bsearch(&ref_boot_id, all_ids, count, sizeof(boot_id_t), boot_id_cmp); + + if (!id || + relative <= 0 ? (id - all_ids) + relative < 0 : + (id - all_ids) + relative >= (int) count) + return -EADDRNOTAVAIL; + + *boot_id = (id + relative)->id; + } + + return 0; +} + +static int add_boot(sd_journal *j) { + char match[9+32+1] = "_BOOT_ID="; + char *offset; + sd_id128_t boot_id = SD_ID128_NULL; + int r, relative = 0; assert(j); - if (!arg_this_boot) + if (!arg_boot) return 0; - r = sd_id128_get_boot(&boot_id); + if (!arg_boot_descriptor) + return add_match_this_boot(j); + + if (strlen(arg_boot_descriptor) >= 32) { + char tmp = arg_boot_descriptor[32]; + arg_boot_descriptor[32] = '\0'; + r = sd_id128_from_string(arg_boot_descriptor, &boot_id); + arg_boot_descriptor[32] = tmp; + + if (r < 0) { + log_error("Failed to parse boot ID '%.32s': %s", + arg_boot_descriptor, strerror(-r)); + return r; + } + + offset = arg_boot_descriptor + 32; + + if (*offset && *offset != '-' && *offset != '+') { + log_error("Relative boot ID offset must start with a '+' or a '-', found '%s' ", offset); + return -EINVAL; + } + } else + offset = arg_boot_descriptor; + + if (*offset) { + r = safe_atoi(offset, &relative); + if (r < 0) { + log_error("Failed to parse relative boot ID number '%s'", offset); + return -EINVAL; + } + } + + r = get_relative_boot_id(j, &boot_id, relative); if (r < 0) { - log_error("Failed to get boot id: %s", strerror(-r)); + if (sd_id128_equal(boot_id, SD_ID128_NULL)) + log_error("Failed to look up boot %+d: %s", relative, strerror(-r)); + else + log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+d: %s", + SD_ID128_FORMAT_VAL(boot_id), relative, strerror(-r)); return r; } sd_id128_to_string(boot_id, match + 9); - r = sd_journal_add_match(j, match, strlen(match)); + + r = sd_journal_add_match(j, match, sizeof(match) - 1); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + return r; + } + + r = sd_journal_add_conjunction(j); + if (r < 0) + return r; + + return 0; +} + +static int add_dmesg(sd_journal *j) { + int r; + assert(j); + + if (!arg_dmesg) + return 0; + + r = sd_journal_add_match(j, "_TRANSPORT=kernel", strlen("_TRANSPORT=kernel")); if (r < 0) { log_error("Failed to add match: %s", strerror(-r)); return r; @@ -690,6 +941,19 @@ static int setup_keys(void) { char *p = NULL, *k = NULL; struct FSSHeader h; uint64_t n; + struct stat st; + + r = stat("/var/log/journal", &st); + if (r < 0 && errno != ENOENT && errno != ENOTDIR) { + log_error("stat(\"%s\") failed: %m", "/var/log/journal"); + return -errno; + } + + if (r < 0 || !S_ISDIR(st.st_mode)) { + log_error("%s is not a directory, must be using persistent logging for FSS.", + "/var/log/journal"); + return r < 0 ? -errno : -ENOTDIR; + } r = sd_id128_get_machine(&machine); if (r < 0) { @@ -708,9 +972,18 @@ static int setup_keys(void) { return log_oom(); if (access(p, F_OK) >= 0) { - log_error("Sealing key file %s exists already.", p); - r = -EEXIST; - goto finish; + if (arg_force) { + r = unlink(p); + if (r < 0) { + log_error("unlink(\"%s\") failed: %m", p); + r = -errno; + goto finish; + } + } else { + log_error("Sealing key file %s exists already. (--force to recreate)", p); + r = -EEXIST; + goto finish; + } } if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX", @@ -1028,11 +1301,12 @@ static int access_check(sd_journal *j) { int main(int argc, char *argv[]) { int r; - _cleanup_journal_close_ sd_journal*j = NULL; + _cleanup_journal_close_ sd_journal *j = NULL; bool need_seek = false; sd_id128_t previous_boot_id; bool previous_boot_id_valid = false, first_line = true; int n_shown = 0; + bool ellipsized = false; setlocale(LC_ALL, ""); log_parse_environment(); @@ -1090,11 +1364,15 @@ int main(int argc, char *argv[]) { } if (arg_directory) - r = sd_journal_open_directory(&j, arg_directory, 0); + r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); + else if (arg_file) + r = sd_journal_open_files(&j, (const char**) arg_file, 0); else - r = sd_journal_open(&j, arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); if (r < 0) { - log_error("Failed to open journal: %s", strerror(-r)); + log_error("Failed to open %s: %s", + arg_directory ? arg_directory : arg_file ? "files" : "journal", + strerror(-r)); return EXIT_FAILURE; } @@ -1125,7 +1403,13 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - r = add_this_boot(j); + /* add_boot() must be called first! + * It may need to seek the journal to find parent boot IDs. */ + r = add_boot(j); + if (r < 0) + return EXIT_FAILURE; + + r = add_dmesg(j); if (r < 0) return EXIT_FAILURE; @@ -1144,10 +1428,12 @@ int main(int argc, char *argv[]) { if (r < 0) return EXIT_FAILURE; - /* Opening the fd now means the first sd_journal_wait() will actually wait */ - r = sd_journal_get_fd(j); - if (r < 0) - return EXIT_FAILURE; + if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) { + _cleanup_free_ char *filter; + + filter = journal_make_match_string(j); + log_debug("Journal filter: %s", filter); + } if (arg_field) { const void *data; @@ -1183,16 +1469,27 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - if (arg_cursor) { - r = sd_journal_seek_cursor(j, arg_cursor); + /* Opening the fd now means the first sd_journal_wait() will actually wait */ + if (arg_follow) { + r = sd_journal_get_fd(j); + if (r < 0) + return EXIT_FAILURE; + } + + if (arg_cursor || arg_after_cursor) { + r = sd_journal_seek_cursor(j, arg_cursor ? arg_cursor : arg_after_cursor); if (r < 0) { log_error("Failed to seek to cursor: %s", strerror(-r)); return EXIT_FAILURE; } if (!arg_reverse) - r = sd_journal_next(j); + r = sd_journal_next_skip(j, 1 + !!arg_after_cursor); else - r = sd_journal_previous(j); + r = sd_journal_previous_skip(j, 1 + !!arg_after_cursor); + + if (arg_after_cursor && r < 2 && !arg_follow) + /* We couldn't find the next entry after the cursor. */ + arg_lines = 0; } else if (arg_since_set && !arg_reverse) { r = sd_journal_seek_realtime_usec(j, arg_since); @@ -1280,11 +1577,10 @@ int main(int argc, char *argv[]) { log_error("Failed to iterate through journal: %s", strerror(-r)); goto finish; } + if (r == 0) + break; } - if (r == 0) - break; - if (arg_until_set && !arg_reverse) { usec_t usec; @@ -1316,7 +1612,8 @@ int main(int argc, char *argv[]) { if (r >= 0) { if (previous_boot_id_valid && !sd_id128_equal(boot_id, previous_boot_id)) - printf(ANSI_HIGHLIGHT_ON "-- Reboot --" ANSI_HIGHLIGHT_OFF "\n"); + printf("%s-- Reboot --%s\n", + ansi_highlight(), ansi_highlight_off()); previous_boot_id = boot_id; previous_boot_id_valid = true; @@ -1329,16 +1626,29 @@ int main(int argc, char *argv[]) { on_tty() * OUTPUT_COLOR | arg_catalog * OUTPUT_CATALOG; - r = output_journal(stdout, j, arg_output, 0, flags); - if (r < 0 || ferror(stdout)) + r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized); + need_seek = true; + if (r == -EADDRNOTAVAIL) + break; + else if (r < 0 || ferror(stdout)) goto finish; - need_seek = true; n_shown++; } - if (!arg_follow) + if (!arg_follow) { + if (arg_show_cursor) { + _cleanup_free_ char *cursor = NULL; + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0 && r != -EADDRNOTAVAIL) + log_error("Failed to get cursor: %s", strerror(-r)); + else if (r >= 0) + printf("-- cursor: %s\n", cursor); + } + break; + } r = sd_journal_wait(j, (uint64_t) -1); if (r < 0) { diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index 2f536320f8..21649d06ce 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -151,7 +151,8 @@ static void dev_kmsg_record(Server *s, char *p, size_t l) { /* Did we lose any? */ if (serial > *s->kernel_seqnum) - server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, "Missed %llu kernel messages", (unsigned long long) serial - *s->kernel_seqnum - 1); + server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, "Missed %"PRIu64" kernel messages", + serial - *s->kernel_seqnum - 1); /* Make sure we never read this one again. Note that * we always store the next message serial we expect @@ -303,7 +304,7 @@ static void dev_kmsg_record(Server *s, char *p, size_t l) { if (message) IOVEC_SET_STRING(iovec[n++], message); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority); + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority, 0); finish: for (j = 0; j < z; j++) diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index f878dfc911..c50cf64f5c 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -71,6 +71,10 @@ static bool valid_user_field(const char *p, size_t l) { return true; } +static bool allow_object_pid(struct ucred *ucred) { + return ucred && ucred->uid == 0; +} + void server_process_native_message( Server *s, const void *buffer, size_t buffer_size, @@ -79,11 +83,12 @@ void server_process_native_message( const char *label, size_t label_len) { struct iovec *iovec = NULL; - unsigned n = 0, m = 0, j, tn = (unsigned) -1; + unsigned n = 0, j, tn = (unsigned) -1; const char *p; - size_t remaining; + size_t remaining, m = 0; int priority = LOG_INFO; char *identifier = NULL, *message = NULL; + pid_t object_pid = 0; assert(s); assert(buffer || buffer_size == 0); @@ -104,7 +109,7 @@ void server_process_native_message( if (e == p) { /* Entry separator */ - server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority); + server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); n = 0; priority = LOG_INFO; @@ -124,19 +129,10 @@ void server_process_native_message( /* A property follows */ /* n received properties, +1 for _TRANSPORT */ - if (n + 1 + N_IOVEC_META_FIELDS >= m) { - struct iovec *c; - unsigned u; - - u = MAX((n + 1 + N_IOVEC_META_FIELDS) * 2U, 4U); - c = realloc(iovec, u * sizeof(struct iovec)); - if (!c) { - log_oom(); - break; - } - - iovec = c; - m = u; + if (!GREEDY_REALLOC(iovec, m, n + 1 + N_IOVEC_META_FIELDS + + !!object_pid * N_IOVEC_OBJECT_FIELDS)) { + log_oom(); + break; } q = memchr(p, '=', e - p); @@ -158,23 +154,23 @@ void server_process_native_message( * of this entry for the rate limiting * logic */ if (l == 10 && - memcmp(p, "PRIORITY=", 9) == 0 && + startswith(p, "PRIORITY=") && p[9] >= '0' && p[9] <= '9') priority = (priority & LOG_FACMASK) | (p[9] - '0'); else if (l == 17 && - memcmp(p, "SYSLOG_FACILITY=", 16) == 0 && + startswith(p, "SYSLOG_FACILITY=") && p[16] >= '0' && p[16] <= '9') priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3); else if (l == 18 && - memcmp(p, "SYSLOG_FACILITY=", 16) == 0 && + startswith(p, "SYSLOG_FACILITY=") && p[16] >= '0' && p[16] <= '9' && p[17] >= '0' && p[17] <= '9') priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); else if (l >= 19 && - memcmp(p, "SYSLOG_IDENTIFIER=", 18) == 0) { + startswith(p, "SYSLOG_IDENTIFIER=")) { char *t; t = strndup(p + 18, l - 18); @@ -183,7 +179,7 @@ void server_process_native_message( identifier = t; } } else if (l >= 8 && - memcmp(p, "MESSAGE=", 8) == 0) { + startswith(p, "MESSAGE=")) { char *t; t = strndup(p + 8, l - 8); @@ -191,6 +187,16 @@ void server_process_native_message( free(message); message = t; } + } else if (l > strlen("OBJECT_PID=") && + l < strlen("OBJECT_PID=") + DECIMAL_STR_MAX(pid_t) && + startswith(p, "OBJECT_PID=") && + allow_object_pid(ucred)) { + char buf[DECIMAL_STR_MAX(pid_t)]; + memcpy(buf, p + strlen("OBJECT_PID="), l - strlen("OBJECT_PID=")); + char_array_0(buf); + + /* ignore error */ + parse_pid(buf, &object_pid); } } @@ -260,7 +266,7 @@ void server_process_native_message( server_forward_console(s, priority, identifier, message, ucred); } - server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority); + server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); finish: for (j = 0; j < n; j++) { diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index cc52b8a5c9..4f47eb1c11 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -65,8 +65,8 @@ #define USER_JOURNALS_MAX 1024 #define DEFAULT_SYNC_INTERVAL_USEC (5*USEC_PER_MINUTE) -#define DEFAULT_RATE_LIMIT_INTERVAL (10*USEC_PER_SEC) -#define DEFAULT_RATE_LIMIT_BURST 200 +#define DEFAULT_RATE_LIMIT_INTERVAL (30*USEC_PER_SEC) +#define DEFAULT_RATE_LIMIT_BURST 1000 #define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC) @@ -89,21 +89,22 @@ static const char* const split_mode_table[] = { DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting"); -static uint64_t available_space(Server *s) { +static uint64_t available_space(Server *s, bool verbose) { char ids[33]; _cleanup_free_ char *p = NULL; - const char *f; sd_id128_t machine; struct statvfs ss; - uint64_t sum = 0, avail = 0, ss_avail = 0; + uint64_t sum = 0, ss_avail = 0, avail = 0; int r; _cleanup_closedir_ DIR *d = NULL; usec_t ts; + const char *f; JournalMetrics *m; ts = now(CLOCK_MONOTONIC); - if (s->cached_available_space_timestamp + RECHECK_AVAILABLE_SPACE_USEC > ts) + if (s->cached_available_space_timestamp + RECHECK_AVAILABLE_SPACE_USEC > ts + && !verbose) return s->cached_available_space; r = sd_id128_get_machine(&machine); @@ -156,38 +157,27 @@ static uint64_t available_space(Server *s) { sum += (uint64_t) st.st_blocks * 512UL; } - avail = sum >= m->max_use ? 0 : m->max_use - sum; - ss_avail = ss.f_bsize * ss.f_bavail; + avail = ss_avail > m->keep_free ? ss_avail - m->keep_free : 0; - ss_avail = ss_avail < m->keep_free ? 0 : ss_avail - m->keep_free; - - if (ss_avail < avail) - avail = ss_avail; - - s->cached_available_space = avail; + s->cached_available_space = MIN(m->max_use, avail) > sum ? MIN(m->max_use, avail) - sum : 0; s->cached_available_space_timestamp = ts; - return avail; -} - -static void server_read_file_gid(Server *s) { - const char *g = "systemd-journal"; - int r; - - assert(s); - - if (s->file_gid_valid) - return; - - r = get_group_creds(&g, &s->file_gid); - if (r < 0) - log_warning("Failed to resolve '%s' group: %s", g, strerror(-r)); + if (verbose) { + char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX], + fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX]; + + server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, + "%s journal is using %s (max %s, leaving %s of free %s, current limit %s).", + s->system_journal ? "Permanent" : "Runtime", + format_bytes(fb1, sizeof(fb1), sum), + format_bytes(fb2, sizeof(fb2), m->max_use), + format_bytes(fb3, sizeof(fb3), m->keep_free), + format_bytes(fb4, sizeof(fb4), ss_avail), + format_bytes(fb5, sizeof(fb5), MIN(m->max_use, avail))); + } - /* if we couldn't read the gid, then it will be 0, but that's - * fine and we shouldn't try to resolve the group again, so - * let's just pretend it worked right-away. */ - s->file_gid_valid = true; + return s->cached_available_space; } void server_fix_perms(Server *s, JournalFile *f, uid_t uid) { @@ -200,11 +190,9 @@ void server_fix_perms(Server *s, JournalFile *f, uid_t uid) { assert(f); - server_read_file_gid(s); - - r = fchmod_and_fchown(f->fd, 0640, 0, s->file_gid); + r = fchmod(f->fd, 0640); if (r < 0) - log_warning("Failed to fix access mode/rights on %s, ignoring: %s", f->path, strerror(-r)); + log_warning("Failed to fix access mode on %s, ignoring: %s", f->path, strerror(-r)); #ifdef HAVE_ACL if (uid <= 0) @@ -227,9 +215,11 @@ void server_fix_perms(Server *s, JournalFile *f, uid_t uid) { } } - /* We do not recalculate the mask here, so that the fchmod() mask above stays intact. */ + /* We do not recalculate the mask unconditionally here, + * so that the fchmod() mask above stays intact. */ if (acl_get_permset(entry, &permset) < 0 || - acl_add_perm(permset, ACL_READ) < 0) { + acl_add_perm(permset, ACL_READ) < 0 || + calc_acl_mask_if_needed(&acl) < 0) { log_warning("Failed to patch ACL on %s, ignoring: %m", f->path); goto finish; } @@ -243,7 +233,7 @@ finish: } static JournalFile* find_journal(Server *s, uid_t uid) { - char *p; + _cleanup_free_ char *p = NULL; int r; JournalFile *f; sd_id128_t machine; @@ -280,9 +270,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) { journal_file_close(f); } - r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, s->system_journal, &f); - free(p); - + r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &f); if (r < 0) return s->system_journal; @@ -343,13 +331,12 @@ void server_rotate(Server *s) { } void server_sync(Server *s) { + static const struct itimerspec sync_timer_disable = {}; JournalFile *f; void *k; Iterator i; int r; - static const struct itimerspec sync_timer_disable = {}; - if (s->system_journal) { r = journal_file_set_offline(s->system_journal); if (r < 0) @@ -370,7 +357,6 @@ void server_sync(Server *s) { } void server_vacuum(Server *s) { - char *p; char ids[33]; sd_id128_t machine; int r; @@ -388,29 +374,19 @@ void server_vacuum(Server *s) { sd_id128_to_string(machine, ids); if (s->system_journal) { - p = strappend("/var/log/journal/", ids); - if (!p) { - log_oom(); - return; - } + char *p = strappenda("/var/log/journal/", ids); r = journal_directory_vacuum(p, s->system_metrics.max_use, s->system_metrics.keep_free, s->max_retention_usec, &s->oldest_file_usec); if (r < 0 && r != -ENOENT) log_error("Failed to vacuum %s: %s", p, strerror(-r)); - free(p); } if (s->runtime_journal) { - p = strappend("/run/log/journal/", ids); - if (!p) { - log_oom(); - return; - } + char *p = strappenda("/run/log/journal/", ids); r = journal_directory_vacuum(p, s->runtime_metrics.max_use, s->runtime_metrics.keep_free, s->max_retention_usec, &s->oldest_file_usec); if (r < 0 && r != -ENOENT) log_error("Failed to vacuum %s: %s", p, strerror(-r)); - free(p); } s->cached_available_space_timestamp = 0; @@ -445,7 +421,7 @@ bool shall_try_append_again(JournalFile *f, int r) { return true; } -static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n) { +static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n, int priority) { JournalFile *f; bool vacuumed = false; int r; @@ -471,12 +447,17 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); if (r >= 0) { - server_schedule_sync(s); + server_schedule_sync(s, priority); return; } if (vacuumed || !shall_try_append_again(f, r)) { - log_error("Failed to write entry, ignoring: %s", strerror(-r)); + size_t size = 0; + unsigned i; + for (i = 0; i < n; i++) + size += iovec[i].iov_len; + + log_error("Failed to write entry (%d items, %zu bytes), ignoring: %s", n, size, strerror(-r)); return; } @@ -489,8 +470,15 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned log_debug("Retrying write."); r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); - if (r < 0) - log_error("Failed to write entry, ignoring: %s", strerror(-r)); + if (r < 0) { + size_t size = 0; + unsigned i; + for (i = 0; i < n; i++) + size += iovec[i].iov_len; + + log_error("Failed to write entry (%d items, %zu bytes) despite vacuuming, ignoring: %s", n, size, strerror(-r)); + } else + server_schedule_sync(s, priority); } static void dispatch_message_real( @@ -499,24 +487,33 @@ static void dispatch_message_real( struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len, - const char *unit_id) { + const char *unit_id, + int priority, + pid_t object_pid) { - char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], + char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)], gid[sizeof("_GID=") + DECIMAL_STR_MAX(gid_t)], owner_uid[sizeof("_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)], source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)], boot_id[sizeof("_BOOT_ID=") + 32] = "_BOOT_ID=", - machine_id[sizeof("_MACHINE_ID=") + 32] = "_MACHINE_ID="; - char *comm, *exe, *cmdline, *cgroup, *session, *unit, *hostname; + machine_id[sizeof("_MACHINE_ID=") + 32] = "_MACHINE_ID=", + o_uid[sizeof("OBJECT_UID=") + DECIMAL_STR_MAX(uid_t)], + o_gid[sizeof("OBJECT_GID=") + DECIMAL_STR_MAX(gid_t)], + o_owner_uid[sizeof("OBJECT_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)]; + uid_t object_uid; + gid_t object_gid; + char *x; sd_id128_t id; int r; char *t, *c; uid_t realuid = 0, owner = 0, journal_uid; bool owner_valid = false; #ifdef HAVE_AUDIT - char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)]; + char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], + audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)], + o_audit_session[sizeof("OBJECT_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], + o_audit_loginuid[sizeof("OBJECT_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)]; uint32_t audit; uid_t loginuid; @@ -525,7 +522,7 @@ static void dispatch_message_real( assert(s); assert(iovec); assert(n > 0); - assert(n + N_IOVEC_META_FIELDS <= m); + assert(n + N_IOVEC_META_FIELDS + (object_pid ? N_IOVEC_OBJECT_FIELDS : 0) <= m); if (ucred) { realuid = ucred->uid; @@ -541,23 +538,30 @@ static void dispatch_message_real( r = get_process_comm(ucred->pid, &t); if (r >= 0) { - comm = strappenda("_COMM=", t); + x = strappenda("_COMM=", t); free(t); - IOVEC_SET_STRING(iovec[n++], comm); + IOVEC_SET_STRING(iovec[n++], x); } r = get_process_exe(ucred->pid, &t); if (r >= 0) { - exe = strappenda("_EXE=", t); + x = strappenda("_EXE=", t); free(t); - IOVEC_SET_STRING(iovec[n++], exe); + IOVEC_SET_STRING(iovec[n++], x); } r = get_process_cmdline(ucred->pid, 0, false, &t); if (r >= 0) { - cmdline = strappenda("_CMDLINE=", t); + x = strappenda("_CMDLINE=", t); free(t); - IOVEC_SET_STRING(iovec[n++], cmdline); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_capeff(ucred->pid, &t); + if (r >= 0) { + x = strappenda("_CAP_EFFECTIVE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); } #ifdef HAVE_AUDIT @@ -576,8 +580,10 @@ static void dispatch_message_real( r = cg_pid_get_path_shifted(ucred->pid, NULL, &c); if (r >= 0) { - cgroup = strappenda("_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], cgroup); + char *session = NULL; + + x = strappenda("_SYSTEMD_CGROUP=", c); + IOVEC_SET_STRING(iovec[n++], x); r = cg_path_get_session(c, &t); if (r >= 0) { @@ -594,43 +600,133 @@ static void dispatch_message_real( } if (cg_path_get_unit(c, &t) >= 0) { - unit = strappenda("_SYSTEMD_UNIT=", t); + x = strappenda("_SYSTEMD_UNIT=", t); free(t); - } else if (cg_path_get_user_unit(c, &t) >= 0) { - unit = strappenda("_SYSTEMD_USER_UNIT=", t); + IOVEC_SET_STRING(iovec[n++], x); + } else if (unit_id && !session) { + x = strappenda("_SYSTEMD_UNIT=", unit_id); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_user_unit(c, &t) >= 0) { + x = strappenda("_SYSTEMD_USER_UNIT=", t); free(t); - } else if (unit_id) { - if (session) - unit = strappenda("_SYSTEMD_USER_UNIT=", unit_id); - else - unit = strappenda("_SYSTEMD_UNIT=", unit_id); - } else - unit = NULL; + IOVEC_SET_STRING(iovec[n++], x); + } else if (unit_id && session) { + x = strappenda("_SYSTEMD_USER_UNIT=", unit_id); + IOVEC_SET_STRING(iovec[n++], x); + } - if (unit) - IOVEC_SET_STRING(iovec[n++], unit); + if (cg_path_get_slice(c, &t) >= 0) { + x = strappenda("_SYSTEMD_SLICE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } free(c); } #ifdef HAVE_SELINUX if (label) { - char *selinux_context = alloca(sizeof("_SELINUX_CONTEXT=") + label_len); + x = alloca(sizeof("_SELINUX_CONTEXT=") + label_len); - *((char*) mempcpy(stpcpy(selinux_context, "_SELINUX_CONTEXT="), label, label_len)) = 0; - IOVEC_SET_STRING(iovec[n++], selinux_context); + *((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0; + IOVEC_SET_STRING(iovec[n++], x); } else { security_context_t con; if (getpidcon(ucred->pid, &con) >= 0) { - char *selinux_context = strappenda("_SELINUX_CONTEXT=", con); + x = strappenda("_SELINUX_CONTEXT=", con); freecon(con); - IOVEC_SET_STRING(iovec[n++], selinux_context); + IOVEC_SET_STRING(iovec[n++], x); } } #endif } + assert(n <= m); + + if (object_pid) { + r = get_process_uid(object_pid, &object_uid); + if (r >= 0) { + sprintf(o_uid, "OBJECT_UID=%lu", (unsigned long) object_uid); + IOVEC_SET_STRING(iovec[n++], o_uid); + } + + r = get_process_gid(object_pid, &object_gid); + if (r >= 0) { + sprintf(o_gid, "OBJECT_GID=%lu", (unsigned long) object_gid); + IOVEC_SET_STRING(iovec[n++], o_gid); + } + + r = get_process_comm(object_pid, &t); + if (r >= 0) { + x = strappenda("OBJECT_COMM=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_exe(object_pid, &t); + if (r >= 0) { + x = strappenda("OBJECT_EXE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + r = get_process_cmdline(object_pid, 0, false, &t); + if (r >= 0) { + x = strappenda("OBJECT_CMDLINE=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + +#ifdef HAVE_AUDIT + r = audit_session_from_pid(object_pid, &audit); + if (r >= 0) { + sprintf(o_audit_session, "OBJECT_AUDIT_SESSION=%lu", (unsigned long) audit); + IOVEC_SET_STRING(iovec[n++], o_audit_session); + } + + r = audit_loginuid_from_pid(object_pid, &loginuid); + if (r >= 0) { + sprintf(o_audit_loginuid, "OBJECT_AUDIT_LOGINUID=%lu", (unsigned long) loginuid); + IOVEC_SET_STRING(iovec[n++], o_audit_loginuid); + } +#endif + + r = cg_pid_get_path_shifted(object_pid, NULL, &c); + if (r >= 0) { + x = strappenda("OBJECT_SYSTEMD_CGROUP=", c); + IOVEC_SET_STRING(iovec[n++], x); + + r = cg_path_get_session(c, &t); + if (r >= 0) { + x = strappenda("OBJECT_SYSTEMD_SESSION=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_owner_uid(c, &owner) >= 0) { + sprintf(o_owner_uid, "OBJECT_SYSTEMD_OWNER_UID=%lu", (unsigned long) owner); + IOVEC_SET_STRING(iovec[n++], o_owner_uid); + } + + if (cg_path_get_unit(c, &t) >= 0) { + x = strappenda("OBJECT_SYSTEMD_UNIT=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + if (cg_path_get_user_unit(c, &t) >= 0) { + x = strappenda("OBJECT_SYSTEMD_USER_UNIT=", t); + free(t); + IOVEC_SET_STRING(iovec[n++], x); + } + + free(c); + } + } + assert(n <= m); if (tv) { sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu", (unsigned long long) timeval_load(tv)); @@ -642,21 +738,21 @@ static void dispatch_message_real( * anyway. However, we need this indexed, too. */ r = sd_id128_get_boot(&id); if (r >= 0) { - sd_id128_to_string(id, boot_id + sizeof("_BOOT_ID=") - 1); + sd_id128_to_string(id, boot_id + strlen("_BOOT_ID=")); IOVEC_SET_STRING(iovec[n++], boot_id); } r = sd_id128_get_machine(&id); if (r >= 0) { - sd_id128_to_string(id, machine_id + sizeof("_MACHINE_ID=") - 1); + sd_id128_to_string(id, machine_id + strlen("_MACHINE_ID=")); IOVEC_SET_STRING(iovec[n++], machine_id); } t = gethostname_malloc(); if (t) { - hostname = strappenda("_HOSTNAME=", t); + x = strappenda("_HOSTNAME=", t); free(t); - IOVEC_SET_STRING(iovec[n++], hostname); + IOVEC_SET_STRING(iovec[n++], x); } assert(n <= m); @@ -675,7 +771,7 @@ static void dispatch_message_real( else journal_uid = 0; - write_to_journal(s, journal_uid, iovec, n); + write_to_journal(s, journal_uid, iovec, n, priority); } void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) { @@ -709,7 +805,7 @@ void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ucred.uid = getuid(); ucred.gid = getgid(); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); } void server_dispatch_message( @@ -719,7 +815,8 @@ void server_dispatch_message( struct timeval *tv, const char *label, size_t label_len, const char *unit_id, - int priority) { + int priority, + pid_t object_pid) { int rl, r; _cleanup_free_ char *path = NULL; @@ -734,6 +831,11 @@ void server_dispatch_message( if (LOG_PRI(priority) > s->max_level_store) return; + /* Stop early in case the information will not be stored + * in a journal. */ + if (s->storage == STORAGE_NONE) + return; + if (!ucred) goto finish; @@ -758,7 +860,7 @@ void server_dispatch_message( } rl = journal_rate_limit_test(s->rate_limit, path, - priority & LOG_PRIMASK, available_space(s)); + priority & LOG_PRIMASK, available_space(s, false)); if (rl == 0) return; @@ -769,7 +871,7 @@ void server_dispatch_message( "Suppressed %u messages from %s", rl - 1, path); finish: - dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id); + dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); } @@ -780,8 +882,10 @@ static int system_journal_open(Server *s) { char ids[33]; r = sd_id128_get_machine(&machine); - if (r < 0) + if (r < 0) { + log_error("Failed to get machine id: %s", strerror(-r)); return r; + } sd_id128_to_string(machine, ids); @@ -798,29 +902,15 @@ static int system_journal_open(Server *s) { if (s->storage == STORAGE_PERSISTENT) (void) mkdir("/var/log/journal/", 0755); - fn = strappend("/var/log/journal/", ids); - if (!fn) - return -ENOMEM; - + fn = strappenda("/var/log/journal/", ids); (void) mkdir(fn, 0755); - free(fn); - - fn = strjoin("/var/log/journal/", ids, "/system.journal", NULL); - if (!fn) - return -ENOMEM; + fn = strappenda(fn, "/system.journal"); r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &s->system_journal); - free(fn); - - if (r >= 0) { - char fb[FORMAT_BYTES_MAX]; + if (r >= 0) server_fix_perms(s, s->system_journal, 0); - server_driver_message(s, SD_ID128_NULL, "Allowing system journal files to grow to %s.", - format_bytes(fb, sizeof(fb), s->system_metrics.max_use)); - - } else if (r < 0) { - + else if (r < 0) { if (r != -ENOENT && r != -EROFS) log_warning("Failed to open system journal: %s", strerror(-r)); @@ -866,15 +956,12 @@ static int system_journal_open(Server *s) { } } - if (s->runtime_journal) { - char fb[FORMAT_BYTES_MAX]; - + if (s->runtime_journal) server_fix_perms(s, s->runtime_journal, 0); - server_driver_message(s, SD_ID128_NULL, "Allowing runtime journal files to grow to %s.", - format_bytes(fb, sizeof(fb), s->runtime_metrics.max_use)); - } } + available_space(s, true); + return r; } @@ -900,10 +987,8 @@ int server_flush_to_var(Server *s) { log_debug("Flushing to /var..."); r = sd_id128_get_machine(&machine); - if (r < 0) { - log_error("Failed to get machine id: %s", strerror(-r)); + if (r < 0) return r; - } r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY); if (r < 0) { @@ -975,7 +1060,8 @@ int process_event(Server *s, struct epoll_event *ev) { ssize_t n; if (ev->events != EPOLLIN) { - log_error("Got invalid event from epoll."); + log_error("Got invalid event from epoll for %s: %"PRIx32, + "signal fd", ev->events); return -EIO; } @@ -992,6 +1078,8 @@ int process_event(Server *s, struct epoll_event *ev) { } if (sfsi.ssi_signo == SIGUSR1) { + log_info("Received request to flush runtime journal from PID %"PRIu32, + sfsi.ssi_pid); touch("/run/systemd/journal/flushed"); server_flush_to_var(s); server_sync(s); @@ -999,6 +1087,8 @@ int process_event(Server *s, struct epoll_event *ev) { } if (sfsi.ssi_signo == SIGUSR2) { + log_info("Received request to rotate journal from PID %"PRIu32, + sfsi.ssi_pid); server_rotate(s); server_vacuum(s); return 1; @@ -1024,8 +1114,12 @@ int process_event(Server *s, struct epoll_event *ev) { } else if (ev->data.fd == s->dev_kmsg_fd) { int r; - if (ev->events != EPOLLIN) { - log_error("Got invalid event from epoll."); + if (ev->events & EPOLLERR) + log_warning("/dev/kmsg buffer overrun, some messages lost."); + + if (!(ev->events & EPOLLIN)) { + log_error("Got invalid event from epoll for %s: %"PRIx32, + "/dev/kmsg", ev->events); return -EIO; } @@ -1039,7 +1133,9 @@ int process_event(Server *s, struct epoll_event *ev) { ev->data.fd == s->syslog_fd) { if (ev->events != EPOLLIN) { - log_error("Got invalid event from epoll."); + log_error("Got invalid event from epoll for %s: %"PRIx32, + ev->data.fd == s->native_fd ? "native fd" : "syslog fd", + ev->events); return -EIO; } @@ -1126,8 +1222,8 @@ int process_event(Server *s, struct epoll_event *ev) { label = (char*) CMSG_DATA(cmsg); label_len = cmsg->cmsg_len - CMSG_LEN(0); } else if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SO_TIMESTAMP && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) tv = (struct timeval*) CMSG_DATA(cmsg); else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { @@ -1137,15 +1233,8 @@ int process_event(Server *s, struct epoll_event *ev) { } if (ev->data.fd == s->syslog_fd) { - char *e; - if (n > 0 && n_fds == 0) { - e = memchr(s->buffer, '\n', n); - if (e) - *e = 0; - else - s->buffer[n] = 0; - + s->buffer[n] = 0; server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len); } else if (n_fds > 0) log_warning("Got file descriptors via syslog socket. Ignoring."); @@ -1167,7 +1256,8 @@ int process_event(Server *s, struct epoll_event *ev) { } else if (ev->data.fd == s->stdout_fd) { if (ev->events != EPOLLIN) { - log_error("Got invalid event from epoll."); + log_error("Got invalid event from epoll for %s: %"PRIx32, + "stdout fd", ev->events); return -EIO; } @@ -1178,7 +1268,8 @@ int process_event(Server *s, struct epoll_event *ev) { StdoutStream *stream; if ((ev->events|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) { - log_error("Got invalid event from epoll."); + log_error("Got invalid event from epoll for %s: %"PRIx32, + "stdout stream", ev->events); return -EIO; } @@ -1323,19 +1414,24 @@ static int server_open_sync_timer(Server *s) { return 0; } -int server_schedule_sync(Server *s) { +int server_schedule_sync(Server *s, int priority) { int r; assert(s); + if (priority <= LOG_CRIT) { + /* Immediately sync to disk when this is of priority CRIT, ALERT, EMERG */ + server_sync(s); + return 0; + } + if (s->sync_scheduled) return 0; if (s->sync_interval_usec) { - struct itimerspec sync_timer_enable = { - .it_value.tv_sec = s->sync_interval_usec / USEC_PER_SEC, - .it_value.tv_nsec = s->sync_interval_usec % MSEC_PER_SEC, - }; + struct itimerspec sync_timer_enable = {}; + + timespec_store(&sync_timer_enable.it_value, s->sync_interval_usec); r = timerfd_settime(s->sync_timer_fd, 0, &sync_timer_enable, NULL); if (r < 0) @@ -1354,7 +1450,7 @@ int server_init(Server *s) { zero(*s); s->sync_timer_fd = s->syslog_fd = s->native_fd = s->stdout_fd = - s->signal_fd = s->epoll_fd = s->dev_kmsg_fd = -1; + s->signal_fd = s->epoll_fd = s->dev_kmsg_fd = -1; s->compress = true; s->seal = true; diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 129f7e8ab4..10e9958be0 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -97,9 +97,6 @@ typedef struct Server { usec_t max_file_usec; usec_t oldest_file_usec; - gid_t file_gid; - bool file_gid_valid; - LIST_HEAD(StdoutStream, stdout_streams); unsigned n_stdout_streams; @@ -125,11 +122,12 @@ typedef struct Server { bool sync_scheduled; } Server; -#define N_IOVEC_META_FIELDS 17 +#define N_IOVEC_META_FIELDS 20 #define N_IOVEC_KERNEL_FIELDS 64 #define N_IOVEC_UDEV_FIELDS 32 +#define N_IOVEC_OBJECT_FIELDS 11 -void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority); +void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) _printf_attr_(3,4); /* gperf lookup function */ @@ -152,7 +150,7 @@ void server_done(Server *s); void server_sync(Server *s); void server_vacuum(Server *s); void server_rotate(Server *s); -int server_schedule_sync(Server *s); +int server_schedule_sync(Server *s, int priority); int server_flush_to_var(Server *s); int process_event(Server *s, struct epoll_event *ev); void server_maybe_append_tags(Server *s); diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 6d51c29083..9c4efec9bc 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -90,7 +90,7 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { priority = s->priority; if (s->level_prefix) - syslog_parse_priority((char**) &p, &priority); + syslog_parse_priority((char**) &p, &priority, false); if (s->forward_to_syslog || s->server->forward_to_syslog) server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL); @@ -127,7 +127,7 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { } #endif - server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, s->unit_id, priority); + server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, s->unit_id, priority, 0); free(message); free(syslog_priority); @@ -440,7 +440,7 @@ int server_open_stdout_socket(Server *s) { chmod(sa.un.sun_path, 0666); if (listen(s->stdout_fd, SOMAXCONN) < 0) { - log_error("liste() failed: %m"); + log_error("listen() failed: %m"); return -errno; } } else diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index 000f5acc10..c2770a53d0 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -236,7 +236,7 @@ size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) return e; } -void syslog_parse_priority(char **p, int *priority) { +void syslog_parse_priority(char **p, int *priority, bool with_facility) { int a = 0, b = 0, c = 0; int k; @@ -265,10 +265,14 @@ void syslog_parse_priority(char **p, int *priority) { } else return; - if (a < 0 || b < 0 || c < 0) + if (a < 0 || b < 0 || c < 0 || + (!with_facility && (a || b || c > 7))) return; - *priority = a*100+b*10+c; + if (with_facility) + *priority = a*100 + b*10 + c; + else + *priority = (*priority & LOG_FACMASK) | c; *p += k; } @@ -361,7 +365,7 @@ void server_process_syslog_message( assert(buf); orig = buf; - syslog_parse_priority((char**) &buf, &priority); + syslog_parse_priority((char**) &buf, &priority, true); if (s->forward_to_syslog) forward_syslog_raw(s, priority, orig, ucred, tv); @@ -400,7 +404,7 @@ void server_process_syslog_message( if (message) IOVEC_SET_STRING(iovec[n++], message); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority); + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0); free(message); free(identifier); diff --git a/src/journal/journald-syslog.h b/src/journal/journald-syslog.h index 324b70eef0..8ccdb77a09 100644 --- a/src/journal/journald-syslog.h +++ b/src/journal/journald-syslog.h @@ -25,7 +25,7 @@ int syslog_fixup_facility(int priority) _const_; -void syslog_parse_priority(char **p, int *priority); +void syslog_parse_priority(char **p, int *priority, bool with_facility); size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid); void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred, struct timeval *tv); diff --git a/src/journal/journald.conf b/src/journal/journald.conf index 5410477201..54f6833a17 100644 --- a/src/journal/journald.conf +++ b/src/journal/journald.conf @@ -13,8 +13,8 @@ #Seal=yes #SplitMode=login #SyncIntervalSec=5m -#RateLimitInterval=10s -#RateLimitBurst=200 +#RateLimitInterval=30s +#RateLimitBurst=1000 #SystemMaxUse= #SystemKeepFree= #SystemMaxFileSize= diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym index 449f37c4da..4eb15910d2 100644 --- a/src/journal/libsystemd-journal.sym +++ b/src/journal/libsystemd-journal.sym @@ -104,3 +104,8 @@ LIBSYSTEMD_JOURNAL_202 { global: sd_journal_add_conjunction; } LIBSYSTEMD_JOURNAL_201; + +LIBSYSTEMD_JOURNAL_205 { +global: + sd_journal_open_files; +} LIBSYSTEMD_JOURNAL_202; diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c index 767f555526..03b57beb04 100644 --- a/src/journal/mmap-cache.c +++ b/src/journal/mmap-cache.c @@ -307,9 +307,13 @@ static void mmap_cache_free(MMapCache *m) { while ((c = hashmap_first(m->contexts))) context_free(c); + hashmap_free(m->contexts); + while ((f = hashmap_first(m->fds))) fd_free(f); + hashmap_free(m->fds); + while (m->unused) window_free(m->unused); diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index c21712b7c4..7700d6cb12 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -33,6 +33,7 @@ #include "journal-file.h" #include "hashmap.h" #include "list.h" +#include "strv.h" #include "path-util.h" #include "lookup3.h" #include "compress.h" @@ -49,6 +50,15 @@ #define DEFAULT_DATA_THRESHOLD (64*1024) +static bool journal_pid_changed(sd_journal *j) { + assert(j); + + /* We don't support people creating a journal object and + * keeping it around over a fork(). Let's complain. */ + + return j->original_pid != getpid(); +} + /* We return an error here only if we didn't manage to memorize the real error. */ static int set_put_error(sd_journal *j, int r) { @@ -101,7 +111,8 @@ static void init_location(Location *l, LocationType type, JournalFile *f, Object l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true; } -static void set_location(sd_journal *j, LocationType type, JournalFile *f, Object *o, uint64_t offset) { +static void set_location(sd_journal *j, LocationType type, JournalFile *f, Object *o, + direction_t direction, uint64_t offset) { assert(j); assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK); assert(f); @@ -109,12 +120,10 @@ static void set_location(sd_journal *j, LocationType type, JournalFile *f, Objec init_location(&j->current_location, type, f, o); - if (j->current_file) - j->current_file->current_offset = 0; - j->current_file = f; j->current_field = 0; + f->last_direction = direction; f->current_offset = offset; } @@ -163,7 +172,7 @@ static bool same_field(const void *_a, size_t s, const void *_b, size_t t) { return true; } - return true; + assert_not_reached("\"=\" not found"); } static Match *match_new(Match *p, MatchType t) { @@ -197,9 +206,7 @@ static void match_free(Match *m) { } static void match_free_if_empty(Match *m) { - assert(m); - - if (m->matches) + if (!m || m->matches) return; match_free(m); @@ -211,6 +218,8 @@ _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!data) return -EINVAL; @@ -296,23 +305,19 @@ _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) return 0; fail: - if (add_here) - match_free_if_empty(add_here); - - if (j->level2) - match_free_if_empty(j->level2); - - if (j->level1) - match_free_if_empty(j->level1); - - if (j->level0) - match_free_if_empty(j->level0); + match_free_if_empty(add_here); + match_free_if_empty(j->level2); + match_free_if_empty(j->level1); + match_free_if_empty(j->level0); return -ENOMEM; } _public_ int sd_journal_add_conjunction(sd_journal *j) { - assert(j); + if (!j) + return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!j->level0) return 0; @@ -330,7 +335,10 @@ _public_ int sd_journal_add_conjunction(sd_journal *j) { } _public_ int sd_journal_add_disjunction(sd_journal *j) { - assert(j); + if (!j) + return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!j->level0) return 0; @@ -354,7 +362,7 @@ static char *match_make_string(Match *m) { bool enclose = false; if (!m) - return strdup(""); + return strdup("none"); if (m->type == MATCH_DISCRETE) return strndup(m->data, m->size); @@ -380,10 +388,8 @@ static char *match_make_string(Match *m) { p = k; enclose = true; - } else { - free(p); + } else p = t; - } } if (enclose) { @@ -402,7 +408,6 @@ char *journal_make_match_string(sd_journal *j) { } _public_ void sd_journal_flush_matches(sd_journal *j) { - if (!j) return; @@ -596,52 +601,47 @@ static int next_for_match( if (r < 0) return r; else if (r > 0) { - if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp)) + if (np == 0 || (direction == DIRECTION_DOWN ? cp < np : cp > np)) np = cp; } } + if (np == 0) + return 0; + } else if (m->type == MATCH_AND_TERM) { - Match *i; - bool continue_looking; + Match *i, *last_moved; /* Always jump to the next matching entry and repeat - * this until we fine and offset that matches for all + * this until we find an offset that matches for all * matches. */ if (!m->matches) return 0; - np = 0; - do { - continue_looking = false; + r = next_for_match(j, m->matches, f, after_offset, direction, NULL, &np); + if (r <= 0) + return r; - LIST_FOREACH(matches, i, m->matches) { - uint64_t cp, limit; + assert(direction == DIRECTION_DOWN ? np >= after_offset : np <= after_offset); + last_moved = m->matches; - if (np == 0) - limit = after_offset; - else if (direction == DIRECTION_DOWN) - limit = MAX(np, after_offset); - else - limit = MIN(np, after_offset); + LIST_LOOP_BUT_ONE(matches, i, m->matches, last_moved) { + uint64_t cp; - r = next_for_match(j, i, f, limit, direction, NULL, &cp); - if (r <= 0) - return r; + r = next_for_match(j, i, f, np, direction, NULL, &cp); + if (r <= 0) + return r; - if ((direction == DIRECTION_DOWN ? cp >= after_offset : cp <= after_offset) && - (np == 0 || (direction == DIRECTION_DOWN ? cp > np : cp < np))) { - np = cp; - continue_looking = true; - } + assert(direction == DIRECTION_DOWN ? cp >= np : cp <= np); + if (direction == DIRECTION_DOWN ? cp > np : cp < np) { + np = cp; + last_moved = i; } - - } while (continue_looking); + } } - if (np == 0) - return 0; + assert(np > 0); r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); if (r < 0) @@ -746,7 +746,7 @@ static int find_location_for_match( if (r <= 0) return r; - if (np == 0 || (direction == DIRECTION_DOWN ? np < cp : np > cp)) + if (np == 0 || (direction == DIRECTION_DOWN ? cp > np : cp < np)) np = cp; } @@ -826,7 +826,7 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc assert(j); assert(f); - if (f->current_offset > 0) { + if (f->last_direction == direction && f->current_offset > 0) { cp = f->current_offset; r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c); @@ -842,7 +842,7 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc return r; } - /* OK, we found the spot, now let's advance until to an entry + /* OK, we found the spot, now let's advance until an entry * that is actually different from what we were previously * looking at. This is necessary to handle entries which exist * in two (or more) journal files, and which shall all be @@ -886,6 +886,8 @@ static int real_journal_next(sd_journal *j, direction_t direction) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; HASHMAP_FOREACH(f, j->files, i) { bool found; @@ -904,10 +906,7 @@ static int real_journal_next(sd_journal *j, direction_t direction) { k = compare_entry_order(f, o, new_file, new_offset); - if (direction == DIRECTION_DOWN) - found = k < 0; - else - found = k > 0; + found = direction == DIRECTION_DOWN ? k < 0 : k > 0; } if (found) { @@ -923,7 +922,7 @@ static int real_journal_next(sd_journal *j, direction_t direction) { if (r < 0) return r; - set_location(j, LOCATION_DISCRETE, new_file, o, new_offset); + set_location(j, LOCATION_DISCRETE, new_file, o, direction, new_offset); return 1; } @@ -941,6 +940,8 @@ static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (skip == 0) { /* If this is not a discrete skip, then at least @@ -981,6 +982,8 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!cursor) return -EINVAL; @@ -995,11 +998,11 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { sd_id128_to_string(o->entry.boot_id, bid); if (asprintf(cursor, - "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx", - sid, (unsigned long long) le64toh(o->entry.seqnum), - bid, (unsigned long long) le64toh(o->entry.monotonic), - (unsigned long long) le64toh(o->entry.realtime), - (unsigned long long) le64toh(o->entry.xor_hash)) < 0) + "s=%s;i=%"PRIx64";b=%s;m=%"PRIx64";t=%"PRIx64";x=%"PRIx64, + sid, le64toh(o->entry.seqnum), + bid, le64toh(o->entry.monotonic), + le64toh(o->entry.realtime), + le64toh(o->entry.xor_hash)) < 0) return -ENOMEM; return 0; @@ -1020,6 +1023,8 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (isempty(cursor)) return -EINVAL; @@ -1119,6 +1124,8 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (isempty(cursor)) return -EINVAL; @@ -1197,6 +1204,8 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; reset_location(j); j->current_location.type = LOCATION_SEEK; @@ -1210,6 +1219,8 @@ _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, u _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; reset_location(j); j->current_location.type = LOCATION_SEEK; @@ -1222,6 +1233,8 @@ _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { _public_ int sd_journal_seek_head(sd_journal *j) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; reset_location(j); j->current_location.type = LOCATION_HEAD; @@ -1232,6 +1245,8 @@ _public_ int sd_journal_seek_head(sd_journal *j) { _public_ int sd_journal_seek_tail(sd_journal *j) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; reset_location(j); j->current_location.type = LOCATION_TAIL; @@ -1251,48 +1266,67 @@ static void check_network(sd_journal *j, int fd) { return; j->on_network = - F_TYPE_CMP(sfs.f_type, CIFS_MAGIC_NUMBER) || - F_TYPE_CMP(sfs.f_type, CODA_SUPER_MAGIC) || - F_TYPE_CMP(sfs.f_type, NCP_SUPER_MAGIC) || - F_TYPE_CMP(sfs.f_type, NFS_SUPER_MAGIC) || - F_TYPE_CMP(sfs.f_type, SMB_SUPER_MAGIC); + F_TYPE_EQUAL(sfs.f_type, CIFS_MAGIC_NUMBER) || + F_TYPE_EQUAL(sfs.f_type, CODA_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, NCP_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, NFS_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SMB_SUPER_MAGIC); } -static int add_file(sd_journal *j, const char *prefix, const char *filename) { - _cleanup_free_ char *path = NULL; - int r; - JournalFile *f; +static bool file_has_type_prefix(const char *prefix, const char *filename) { + const char *full, *tilded, *atted; - assert(j); - assert(prefix); - assert(filename); + full = strappend(prefix, ".journal"); + tilded = strappenda(full, "~"); + atted = strappenda(prefix, "@"); - if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) && - !(streq(filename, "system.journal") || - streq(filename, "system.journal~") || - (startswith(filename, "system@") && - (endswith(filename, ".journal") || endswith(filename, ".journal~"))))) - return 0; + return streq(filename, full) || + streq(filename, tilded) || + startswith(filename, atted); +} - path = strjoin(prefix, "/", filename, NULL); - if (!path) - return -ENOMEM; +static bool file_type_wanted(int flags, const char *filename) { + if (!endswith(filename, ".journal") && !endswith(filename, ".journal~")) + return false; + + /* no flags set → every type is OK */ + if (!(flags & (SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER))) + return true; + + if (flags & SD_JOURNAL_SYSTEM && file_has_type_prefix("system", filename)) + return true; + + if (flags & SD_JOURNAL_CURRENT_USER) { + char prefix[5 + DECIMAL_STR_MAX(uid_t) + 1]; + + assert_se(snprintf(prefix, sizeof(prefix), "user-%lu", (unsigned long) getuid()) + < (int) sizeof(prefix)); + + if (file_has_type_prefix(prefix, filename)) + return true; + } + + return false; +} + +static int add_any_file(sd_journal *j, const char *path) { + JournalFile *f; + int r; + + assert(j); + assert(path); if (hashmap_get(j->files, path)) return 0; if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) { - log_debug("Too many open journal files, not adding %s, ignoring.", path); + log_warning("Too many open journal files, not adding %s.", path); return set_put_error(j, -ETOOMANYREFS); } r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, &f); - if (r < 0) { - if (errno == ENOENT) - return 0; - + if (r < 0) return r; - } /* journal_file_dump(f); */ @@ -1302,7 +1336,7 @@ static int add_file(sd_journal *j, const char *prefix, const char *filename) { return r; } - log_debug("File %s got added.", f->path); + log_debug("File %s added.", f->path); check_network(j, f->fd); @@ -1311,6 +1345,28 @@ static int add_file(sd_journal *j, const char *prefix, const char *filename) { return 0; } +static int add_file(sd_journal *j, const char *prefix, const char *filename) { + _cleanup_free_ char *path = NULL; + int r; + + assert(j); + assert(prefix); + assert(filename); + + if (j->no_new_files || + !file_type_wanted(j->flags, filename)) + return 0; + + path = strjoin(prefix, "/", filename, NULL); + if (!path) + return -ENOMEM; + + r = add_any_file(j, path); + if (r == -ENOENT) + return 0; + return 0; +} + static int remove_file(sd_journal *j, const char *prefix, const char *filename) { char *path; JournalFile *f; @@ -1330,7 +1386,7 @@ static int remove_file(sd_journal *j, const char *prefix, const char *filename) hashmap_remove(j->files, f->path); - log_debug("File %s got removed.", f->path); + log_debug("File %s removed.", f->path); if (j->current_file == f) { j->current_file = NULL; @@ -1397,7 +1453,7 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) path = NULL; /* avoid freeing in cleanup */ j->current_invalidate_counter ++; - log_debug("Directory %s got added.", m->path); + log_debug("Directory %s added.", m->path); } else if (m->is_root) return 0; @@ -1476,7 +1532,7 @@ static int add_root_directory(sd_journal *j, const char *p) { j->current_invalidate_counter ++; - log_debug("Root directory %s got added.", m->path); + log_debug("Root directory %s added.", m->path); } else if (!m->is_root) return 0; @@ -1491,6 +1547,9 @@ static int add_root_directory(sd_journal *j, const char *p) { inotify_rm_watch(j->inotify_fd, m->wd); } + if (j->no_new_files) + return 0; + for (;;) { struct dirent *de; union dirent_storage buf; @@ -1537,9 +1596,9 @@ static int remove_directory(sd_journal *j, Directory *d) { hashmap_remove(j->directories_by_path, d->path); if (d->is_root) - log_debug("Root directory %s got removed.", d->path); + log_debug("Root directory %s removed.", d->path); else - log_debug("Directory %s got removed.", d->path); + log_debug("Directory %s removed.", d->path); free(d->path); free(d); @@ -1571,6 +1630,36 @@ static int add_search_paths(sd_journal *j) { return 0; } +static int add_current_paths(sd_journal *j) { + Iterator i; + JournalFile *f; + + assert(j); + assert(j->no_new_files); + + /* Simply adds all directories for files we have open as + * "root" directories. We don't expect errors here, so we + * treat them as fatal. */ + + HASHMAP_FOREACH(f, j->files, i) { + int r; + _cleanup_free_ char *dir; + + dir = dirname_malloc(f->path); + if (!dir) + return -ENOMEM; + + r = add_root_directory(j, dir); + if (r < 0) { + set_put_error(j, r); + return r; + } + } + + return 0; +} + + static int allocate_inotify(sd_journal *j) { assert(j); @@ -1596,6 +1685,7 @@ static sd_journal *journal_new(int flags, const char *path) { if (!j) return NULL; + j->original_pid = getpid(); j->inotify_fd = -1; j->flags = flags; j->data_threshold = DEFAULT_DATA_THRESHOLD; @@ -1628,7 +1718,8 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) { if (flags & ~(SD_JOURNAL_LOCAL_ONLY| SD_JOURNAL_RUNTIME_ONLY| - SD_JOURNAL_SYSTEM_ONLY)) + SD_JOURNAL_SYSTEM| + SD_JOURNAL_CURRENT_USER)) return -EINVAL; j = journal_new(flags, NULL); @@ -1680,6 +1771,40 @@ fail: return r; } +_public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) { + sd_journal *j; + const char **path; + int r; + + if (!ret) + return -EINVAL; + + if (flags != 0) + return -EINVAL; + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + STRV_FOREACH(path, paths) { + r = add_any_file(j, *path); + if (r < 0) { + log_error("Failed to open %s: %s", *path, strerror(-r)); + goto fail; + } + } + + j->no_new_files = true; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + + return r; +} + _public_ void sd_journal_close(sd_journal *j) { Directory *d; JournalFile *f; @@ -1722,6 +1847,8 @@ _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!ret) return -EINVAL; @@ -1748,6 +1875,8 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; f = j->current_file; if (!f) @@ -1814,6 +1943,8 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!field) return -EINVAL; if (!data) @@ -1940,6 +2071,8 @@ _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!data) return -EINVAL; if (!size) @@ -1990,6 +2123,8 @@ _public_ int sd_journal_get_fd(sd_journal *j) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (j->inotify_fd >= 0) return j->inotify_fd; @@ -2000,7 +2135,9 @@ _public_ int sd_journal_get_fd(sd_journal *j) { /* Iterate through all dirs again, to add them to the * inotify */ - if (j->path) + if (j->no_new_files) + r = add_current_paths(j); + else if (j->path) r = add_root_directory(j, j->path); else r = add_search_paths(j); @@ -2015,6 +2152,8 @@ _public_ int sd_journal_get_events(sd_journal *j) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; fd = sd_journal_get_fd(j); if (fd < 0) @@ -2028,6 +2167,8 @@ _public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!timeout_usec) return -EINVAL; @@ -2128,6 +2269,8 @@ _public_ int sd_journal_process(sd_journal *j) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; j->last_process_usec = now(CLOCK_MONOTONIC); @@ -2166,7 +2309,10 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { int r; uint64_t t; - assert(j); + if (!j) + return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (j->inotify_fd < 0) { @@ -2215,8 +2361,12 @@ _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!from && !to) return -EINVAL; + if (from == to) + return -EINVAL; HASHMAP_FOREACH(f, j->files, i) { usec_t fr, t; @@ -2254,8 +2404,12 @@ _public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!from && !to) return -EINVAL; + if (from == to) + return -EINVAL; HASHMAP_FOREACH(f, j->files, i) { usec_t fr, t; @@ -2309,6 +2463,8 @@ _public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!bytes) return -EINVAL; @@ -2330,6 +2486,8 @@ _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (isempty(field)) return -EINVAL; if (!field_is_valid(field)) @@ -2354,6 +2512,8 @@ _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_ if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!data) return -EINVAL; if (!l) @@ -2466,6 +2626,8 @@ _public_ void sd_journal_restart_unique(sd_journal *j) { _public_ int sd_journal_reliable_fd(sd_journal *j) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; return !j->on_network; } @@ -2499,6 +2661,8 @@ _public_ int sd_journal_get_catalog(sd_journal *j, char **ret) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!ret) return -EINVAL; @@ -2536,6 +2700,8 @@ _public_ int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **ret) { _public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; j->data_threshold = sz; return 0; @@ -2544,6 +2710,8 @@ _public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) { _public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) { if (!j) return -EINVAL; + if (journal_pid_changed(j)) + return -ECHILD; if (!sz) return -EINVAL; diff --git a/src/journal/test-catalog.c b/src/journal/test-catalog.c index 987867f0c8..5db5bed8de 100644 --- a/src/journal/test-catalog.c +++ b/src/journal/test-catalog.c @@ -31,6 +31,16 @@ #include "sd-messages.h" #include "catalog.h" +static const char *catalog_dirs[] = { + CATALOG_DIR, + NULL, +}; + +static const char *no_catalog_dirs[] = { + "/bin/hopefully/with/no/catalog", + NULL +}; + static void test_import(Hashmap *h, struct strbuf *sb, const char* contents, ssize_t size, int code) { int r; @@ -100,9 +110,13 @@ static void test_catalog_update(void) { r = catalog_update(database, NULL, NULL); assert(r >= 0); - /* Note: this might actually not find anything, if systemd was - * not installed before. That should be fine too. */ - r = catalog_update(database, NULL, catalog_file_dirs); + /* Test what happens if there are no files in the directory. */ + r = catalog_update(database, NULL, no_catalog_dirs); + assert(r >= 0); + + /* Make sure that we at least have some files loaded or the + catalog_list below will fail. */ + r = catalog_update(database, NULL, catalog_dirs); assert(r >= 0); } diff --git a/src/journal/test-journal-init.c b/src/journal/test-journal-init.c new file mode 100644 index 0000000000..58f260d6c2 --- /dev/null +++ b/src/journal/test-journal-init.c @@ -0,0 +1,60 @@ +/*-*- 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 <systemd/sd-journal.h> + +#include "log.h" +#include "util.h" + +int main(int argc, char *argv[]) { + sd_journal *j; + int r, i, I = 100; + char t[] = "/tmp/journal-stream-XXXXXX"; + + log_set_max_level(LOG_DEBUG); + + if (argc >= 2) + safe_atoi(argv[1], &I); + log_info("Running %d loops", I); + + assert_se(mkdtemp(t)); + + for (i = 0; i < I; i++) { + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + assert_se(r == 0); + + sd_journal_close(j); + + r = sd_journal_open_directory(&j, t, 0); + assert_se(r == 0); + + sd_journal_close(j); + + j = NULL; + r = sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY); + assert_se(r == -EINVAL); + assert_se(j == NULL); + } + + assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + + return 0; +} diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c new file mode 100644 index 0000000000..1a058eaedd --- /dev/null +++ b/src/journal/test-journal-interleaving.c @@ -0,0 +1,303 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Marius Vollmer + 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 <systemd/sd-journal.h> + +#include "journal-file.h" +#include "journal-internal.h" +#include "journal-vacuum.h" +#include "util.h" +#include "log.h" + +/* This program tests skipping around in a multi-file journal. + */ + +static bool arg_keep = false; + +_noreturn_ static void log_assert_errno(const char *text, int eno, const char *file, int line, const char *func) { + log_meta(LOG_CRIT, file, line, func, + "'%s' failed at %s:%u (%s): %s.", + text, file, line, func, strerror(eno)); + abort(); +} + +#define assert_ret(expr) \ + do { \ + int _r_ = (expr); \ + if (_unlikely_(_r_ < 0)) \ + log_assert_errno(#expr, -_r_, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +static JournalFile *test_open (const char *name) +{ + JournalFile *f; + assert_ret(journal_file_open(name, O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, &f)); + return f; +} + +static void test_close (JournalFile *f) +{ + journal_file_close (f); +} + +static void append_number(JournalFile *f, int n, uint64_t *seqnum) +{ + char *p; + dual_timestamp ts; + struct iovec iovec[1]; + + dual_timestamp_get(&ts); + + assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); + iovec[0].iov_base = p; + iovec[0].iov_len = strlen(p); + assert_ret(journal_file_append_entry(f, &ts, iovec, 1, seqnum, NULL, NULL)); + free (p); +} + +static void test_check_number (sd_journal *j, int n) +{ + const void *d; + char *k; + size_t l; + int x; + + assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l)); + assert_se(k = strndup(d, l)); + printf("%s\n", k); + + assert_se(safe_atoi(k + 7, &x) >= 0); + assert_se(n == x); +} + +static void test_check_numbers_down (sd_journal *j, int count) +{ + for (int i = 1; i <= count; i++) { + int r; + test_check_number(j, i); + assert_ret(r = sd_journal_next(j)); + if (i == count) + assert_se(r == 0); + else + assert_se(r == 1); + } + +} + +static void test_check_numbers_up (sd_journal *j, int count) +{ + for (int i = count; i >= 1; i--) { + int r; + test_check_number(j, i); + assert_ret(r = sd_journal_previous(j)); + if (i == 1) + assert_se(r == 0); + else + assert_se(r == 1); + } + +} + +static void setup_sequential(void) { + JournalFile *one, *two; + one = test_open("one.journal"); + two = test_open("two.journal"); + append_number(one, 1, NULL); + append_number(one, 2, NULL); + append_number(two, 3, NULL); + append_number(two, 4, NULL); + test_close(one); + test_close(two); +} + +static void setup_interleaved(void) { + JournalFile *one, *two; + one = test_open("one.journal"); + two = test_open("two.journal"); + append_number(one, 1, NULL); + append_number(two, 2, NULL); + append_number(one, 3, NULL); + append_number(two, 4, NULL); + test_close(one); + test_close(two); +} + +static void test_skip(void (*setup)(void)) +{ + char t[] = "/tmp/journal-skip-XXXXXX"; + sd_journal *j; + int r; + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + setup(); + + /* Seek to head, iterate down. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_ret(sd_journal_next(j)); + test_check_numbers_down(j, 4); + sd_journal_close(j); + + /* Seek to tail, iterate up. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_ret(sd_journal_previous(j)); + test_check_numbers_up(j, 4); + sd_journal_close(j); + + /* Seek to tail, skip to head, iterate down. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_ret(r = sd_journal_previous_skip(j, 4)); + assert_se(r == 4); + test_check_numbers_down(j, 4); + sd_journal_close(j); + + /* Seek to head, skip to tail, iterate up. + */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_ret(r = sd_journal_next_skip(j, 4)); + assert_se(r == 4); + test_check_numbers_up(j, 4); + sd_journal_close(j); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL); + + assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + } + + puts("------------------------------------------------------------"); +} + +static void test_sequence_numbers(void) { + + char t[] = "/tmp/journal-seq-XXXXXX"; + JournalFile *one, *two; + uint64_t seqnum = 0; + sd_id128_t seqnum_id; + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + assert_se(journal_file_open("one.journal", O_RDWR|O_CREAT, 0644, + true, false, NULL, NULL, NULL, &one) == 0); + + append_number(one, 1, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 1); + append_number(one, 2, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 2); + + assert(one->header->state == STATE_ONLINE); + assert(!sd_id128_equal(one->header->file_id, one->header->machine_id)); + assert(!sd_id128_equal(one->header->file_id, one->header->boot_id)); + assert(sd_id128_equal(one->header->file_id, one->header->seqnum_id)); + + memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); + + assert_se(journal_file_open("two.journal", O_RDWR|O_CREAT, 0644, + true, false, NULL, NULL, one, &two) == 0); + + assert(two->header->state == STATE_ONLINE); + assert(!sd_id128_equal(two->header->file_id, one->header->file_id)); + assert(sd_id128_equal(one->header->machine_id, one->header->machine_id)); + assert(sd_id128_equal(one->header->boot_id, one->header->boot_id)); + assert(sd_id128_equal(one->header->seqnum_id, one->header->seqnum_id)); + + append_number(two, 3, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 3); + append_number(two, 4, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 4); + + test_close(two); + + append_number(one, 5, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 5); + + append_number(one, 6, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 6); + + test_close(one); + + /* restart server */ + seqnum = 0; + + assert_se(journal_file_open("two.journal", O_RDWR, 0, + true, false, NULL, NULL, NULL, &two) == 0); + + assert(sd_id128_equal(two->header->seqnum_id, seqnum_id)); + + append_number(two, 7, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert(seqnum == 5); + + /* So..., here we have the same seqnum in two files with the + * same seqnum_id. */ + + test_close(two); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL); + + assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + } +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + + arg_keep = argc > 1; + + test_skip(setup_sequential); + test_skip(setup_interleaved); + + test_sequence_numbers(); + + return 0; +} diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c index 4aba7febc7..8e1d08d596 100644 --- a/src/journal/test-journal-stream.c +++ b/src/journal/test-journal-stream.c @@ -75,11 +75,15 @@ int main(int argc, char *argv[]) { JournalFile *one, *two, *three; char t[] = "/tmp/journal-stream-XXXXXX"; unsigned i; - _cleanup_journal_close_ sd_journal*j = NULL; + _cleanup_journal_close_ sd_journal *j = NULL; char *z; const void *data; size_t l; + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + log_set_max_level(LOG_DEBUG); assert_se(mkdtemp(t)); diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c index ad2e2d4c3b..0540074207 100644 --- a/src/journal/test-journal-verify.c +++ b/src/journal/test-journal-verify.c @@ -77,6 +77,10 @@ int main(int argc, char *argv[]) { struct stat st; uint64_t p; + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; + log_set_max_level(LOG_DEBUG); assert_se(mkdtemp(t)); @@ -130,10 +134,10 @@ int main(int argc, char *argv[]) { for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) { bit_toggle("test.journal", p); - log_info("[ %llu+%llu]", (unsigned long long) p / 8, (unsigned long long) p % 8); + log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8); if (raw_verify("test.journal", verification_key) >= 0) - log_notice(ANSI_HIGHLIGHT_RED_ON ">>>> %llu (bit %llu) can be toggled without detection." ANSI_HIGHLIGHT_OFF, (unsigned long long) p / 8, (unsigned long long) p % 8); + log_notice(ANSI_HIGHLIGHT_RED_ON ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_HIGHLIGHT_OFF, p / 8, p % 8); bit_toggle("test.journal", p); } diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c index f4dc52cd81..190c426eba 100644 --- a/src/journal/test-journal.c +++ b/src/journal/test-journal.c @@ -29,7 +29,9 @@ #include "journal-authenticate.h" #include "journal-vacuum.h" -int main(int argc, char *argv[]) { +static bool arg_keep = false; + +static void test_non_empty(void) { dual_timestamp ts; JournalFile *f; struct iovec iovec; @@ -119,11 +121,65 @@ int main(int argc, char *argv[]) { journal_file_close(f); - journal_directory_vacuum(".", 3000000, 0, 0, NULL); + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL); + + assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + } + + puts("------------------------------------------------------------"); +} + +static void test_empty(void) { + JournalFile *f1, *f2, *f3, *f4; + char t[] = "/tmp/journal-XXXXXX"; + + log_set_max_level(LOG_DEBUG); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + + assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, false, false, NULL, NULL, NULL, &f1) == 0); + + assert_se(journal_file_open("test-compress.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, &f2) == 0); + + assert_se(journal_file_open("test-seal.journal", O_RDWR|O_CREAT, 0666, false, true, NULL, NULL, NULL, &f3) == 0); + + assert_se(journal_file_open("test-seal-compress.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, &f4) == 0); + + journal_file_print_header(f1); + puts(""); + journal_file_print_header(f2); + puts(""); + journal_file_print_header(f3); + puts(""); + journal_file_print_header(f4); + puts(""); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL); + + assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + } +} + +int main(int argc, char *argv[]) { + arg_keep = argc > 1; - log_error("Exiting..."); + /* journal_file_open requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return EXIT_TEST_SKIP; - assert_se(rm_rf_dangerous(t, false, true, false) >= 0); + test_non_empty(); + test_empty(); return 0; } diff --git a/src/kernel-install/90-loaderentry.install b/src/kernel-install/90-loaderentry.install index 55b4d24672..a6a8abc2bd 100644 --- a/src/kernel-install/90-loaderentry.install +++ b/src/kernel-install/90-loaderentry.install @@ -38,21 +38,30 @@ if ! [[ $PRETTY_NAME ]]; then PRETTY_NAME="Linux $KERNEL_VERSION" fi +declare -a BOOT_OPTIONS + if [[ -f /etc/kernel/cmdline ]]; then readarray -t BOOT_OPTIONS < /etc/kernel/cmdline fi if ! [[ ${BOOT_OPTIONS[*]} ]]; then - readarray -t BOOT_OPTIONS < /proc/cmdline + readarray -t line < /proc/cmdline + for i in ${line[*]}; do + if [[ "${i#initrd=*}" == "$i" ]]; then + BOOT_OPTIONS[${#BOOT_OPTIONS[@]}]="$i" + fi + done fi -if ! [[ $BOOT_OPTIONS ]]; then +if ! [[ ${BOOT_OPTIONS[*]} ]]; then echo "Could not determine the kernel command line parameters." >&2 echo "Please specify the kernel command line in /etc/kernel/cmdline!" >&2 exit 1 fi -cp --preserve "$KERNEL_IMAGE" "$BOOT_DIR_ABS/linux" || { +cp "$KERNEL_IMAGE" "$BOOT_DIR_ABS/linux" && + chown root:root "$BOOT_DIR_ABS/linux" && + chmod 0644 "$BOOT_DIR_ABS/linux" || { echo "Could not copy '$KERNEL_IMAGE to '$BOOT_DIR_ABS/linux'." >&2 exit 1 } @@ -70,6 +79,7 @@ mkdir -p "${LOADER_ENTRY%/*}" || { echo "linux $BOOT_DIR/linux" [[ -f $BOOT_DIR_ABS/initrd ]] && \ echo "initrd $BOOT_DIR/initrd" + : } > "$LOADER_ENTRY" || { echo "Could not create loader entry '$LOADER_ENTRY'." >&2 exit 1 diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install index fb2ee57b5b..9d3e75db08 100644 --- a/src/kernel-install/kernel-install +++ b/src/kernel-install/kernel-install @@ -54,9 +54,15 @@ dropindirs_sort() export LC_COLLATE=C -COMMAND="$1" -KERNEL_VERSION="$2" -KERNEL_IMAGE="$3" +if [[ "${0##*/}" == 'installkernel' ]]; then + COMMAND='add' +else + COMMAND="$1" + shift +fi + +KERNEL_VERSION="$1" +KERNEL_IMAGE="$2" if [[ -f /etc/machine-id ]]; then read MACHINE_ID < /etc/machine-id diff --git a/src/libsystemd-bus/bus-bloom.c b/src/libsystemd-bus/bus-bloom.c index cb65e47b4c..04bee8581e 100644 --- a/src/libsystemd-bus/bus-bloom.c +++ b/src/libsystemd-bus/bus-bloom.c @@ -49,6 +49,8 @@ void bloom_add_data(uint64_t filter[BLOOM_SIZE/8], const void *data, size_t n) { for (k = 0; k < ELEMENTSOF(hash); k++) set_bit(filter, hash[k] & 511); + + /* log_debug("bloom: adding <%.*s>", (int) n, (char*) data); */ } void bloom_add_pair(uint64_t filter[BLOOM_SIZE/8], const char *a, const char *b) { diff --git a/src/libsystemd-bus/bus-control.c b/src/libsystemd-bus/bus-control.c index a4dc9bf511..0ba8585805 100644 --- a/src/libsystemd-bus/bus-control.c +++ b/src/libsystemd-bus/bus-control.c @@ -32,6 +32,7 @@ #include "bus-internal.h" #include "bus-message.h" #include "bus-control.h" +#include "bus-bloom.h" int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { int r; @@ -40,6 +41,8 @@ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { return -EINVAL; if (!unique) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; r = bus_ensure_running(bus); if (r < 0) @@ -60,6 +63,10 @@ int sd_bus_request_name(sd_bus *bus, const char *name, int flags) { return -EINVAL; if (!bus->bus_client) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (bus->is_kernel) { struct kdbus_cmd_name *n; @@ -68,7 +75,7 @@ int sd_bus_request_name(sd_bus *bus, const char *name, int flags) { l = strlen(name); n = alloca0(offsetof(struct kdbus_cmd_name, name) + l + 1); n->size = offsetof(struct kdbus_cmd_name, name) + l + 1; - n->name_flags = flags; + n->flags = flags; memcpy(n->name, name, l+1); #ifdef HAVE_VALGRIND_MEMCHECK_H @@ -79,7 +86,7 @@ int sd_bus_request_name(sd_bus *bus, const char *name, int flags) { if (r < 0) return -errno; - return n->name_flags; + return n->flags; } else { r = sd_bus_call_method( bus, @@ -114,6 +121,10 @@ int sd_bus_release_name(sd_bus *bus, const char *name) { return -EINVAL; if (!bus->bus_client) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (bus->is_kernel) { struct kdbus_cmd_name *n; @@ -131,7 +142,7 @@ int sd_bus_release_name(sd_bus *bus, const char *name) { if (r < 0) return -errno; - return n->name_flags; + return n->flags; } else { r = sd_bus_call_method( bus, @@ -163,6 +174,10 @@ int sd_bus_list_names(sd_bus *bus, char ***l) { return -EINVAL; if (!l) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = sd_bus_call_method( bus, @@ -213,6 +228,10 @@ int sd_bus_get_owner(sd_bus *bus, const char *name, char **owner) { return -EINVAL; if (!name) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = sd_bus_call_method( bus, @@ -255,6 +274,10 @@ int sd_bus_get_owner_uid(sd_bus *bus, const char *name, uid_t *uid) { return -EINVAL; if (!uid) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = sd_bus_call_method( bus, @@ -288,6 +311,10 @@ int sd_bus_get_owner_pid(sd_bus *bus, const char *name, pid_t *pid) { return -EINVAL; if (!pid) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = sd_bus_call_method( bus, @@ -313,36 +340,201 @@ int sd_bus_get_owner_pid(sd_bus *bus, const char *name, pid_t *pid) { return 0; } -int bus_add_match_internal(sd_bus *bus, const char *match) { +int bus_add_match_internal( + sd_bus *bus, + const char *match, + struct bus_match_component *components, + unsigned n_components, + uint64_t cookie) { + + int r; + assert(bus); assert(match); - return sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/", - "org.freedesktop.DBus", - "AddMatch", - NULL, - NULL, - "s", - match); + if (bus->is_kernel) { + struct kdbus_cmd_match *m; + struct kdbus_item *item; + uint64_t bloom[BLOOM_SIZE/8]; + size_t sz; + const char *sender = NULL; + size_t sender_length = 0; + uint64_t src_id = KDBUS_MATCH_SRC_ID_ANY; + bool using_bloom = false; + unsigned i; + + zero(bloom); + + sz = offsetof(struct kdbus_cmd_match, items); + + for (i = 0; i < n_components; i++) { + struct bus_match_component *c = &components[i]; + + switch (c->type) { + + case BUS_MATCH_SENDER: + r = bus_kernel_parse_unique_name(c->value_str, &src_id); + if (r < 0) + return r; + + if (r > 0) { + sender = c->value_str; + sender_length = strlen(sender); + sz += ALIGN8(offsetof(struct kdbus_item, str) + sender_length + 1); + } + + break; + + case BUS_MATCH_MESSAGE_TYPE: + bloom_add_pair(bloom, "message-type", bus_message_type_to_string(c->value_u8)); + using_bloom = true; + break; + + case BUS_MATCH_INTERFACE: + bloom_add_pair(bloom, "interface", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_MEMBER: + bloom_add_pair(bloom, "member", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_PATH: + bloom_add_pair(bloom, "path", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_PATH_NAMESPACE: + if (!streq(c->value_str, "/")) { + bloom_add_pair(bloom, "path-slash-prefix", c->value_str); + using_bloom = true; + } + break; + + case BUS_MATCH_ARG...BUS_MATCH_ARG_LAST: { + char buf[sizeof("arg")-1 + 2 + 1]; + + snprintf(buf, sizeof(buf), "arg%u", c->type - BUS_MATCH_ARG); + bloom_add_pair(bloom, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_ARG_PATH...BUS_MATCH_ARG_PATH_LAST: { + char buf[sizeof("arg")-1 + 2 + sizeof("-slash-prefix")]; + + snprintf(buf, sizeof(buf), "arg%u-slash-prefix", c->type - BUS_MATCH_ARG_PATH); + bloom_add_pair(bloom, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_ARG_NAMESPACE...BUS_MATCH_ARG_NAMESPACE_LAST: { + char buf[sizeof("arg")-1 + 2 + sizeof("-dot-prefix")]; + + snprintf(buf, sizeof(buf), "arg%u-dot-prefix", c->type - BUS_MATCH_ARG_NAMESPACE); + bloom_add_pair(bloom, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_DESTINATION: + /* The bloom filter does not include + the destination, since it is only + available for broadcast messages + which do not carry a destination + since they are undirected. */ + break; + + case BUS_MATCH_ROOT: + case BUS_MATCH_VALUE: + case BUS_MATCH_LEAF: + case _BUS_MATCH_NODE_TYPE_MAX: + case _BUS_MATCH_NODE_TYPE_INVALID: + assert_not_reached("Invalid match type?"); + } + } + + if (using_bloom) + sz += ALIGN8(offsetof(struct kdbus_item, data64) + BLOOM_SIZE); + + m = alloca0(sz); + m->size = sz; + m->cookie = cookie; + m->src_id = src_id; + + item = m->items; + + if (using_bloom) { + item->size = offsetof(struct kdbus_item, data64) + BLOOM_SIZE; + item->type = KDBUS_MATCH_BLOOM; + memcpy(item->data64, bloom, BLOOM_SIZE); + + item = KDBUS_ITEM_NEXT(item); + } + + if (sender) { + item->size = offsetof(struct kdbus_item, str) + sender_length + 1; + item->type = KDBUS_MATCH_SRC_NAME; + memcpy(item->str, sender, sender_length + 1); + } + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + + } else { + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "AddMatch", + NULL, + NULL, + "s", + match); + } + + return 0; } -int bus_remove_match_internal(sd_bus *bus, const char *match) { +int bus_remove_match_internal( + sd_bus *bus, + const char *match, + uint64_t cookie) { + + int r; + assert(bus); assert(match); - return sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/", - "org.freedesktop.DBus", - "RemoveMatch", - NULL, - NULL, - "s", - match); + if (bus->is_kernel) { + struct kdbus_cmd_match m; + + zero(m); + m.size = offsetof(struct kdbus_cmd_match, items); + m.cookie = cookie; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_REMOVE, &m); + if (r < 0) + return -errno; + + } else { + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "RemoveMatch", + NULL, + NULL, + "s", + match); + } + + return 0; } int sd_bus_get_owner_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) { @@ -354,6 +546,10 @@ int sd_bus_get_owner_machine_id(sd_bus *bus, const char *name, sd_id128_t *machi return -EINVAL; if (!name) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (streq_ptr(name, bus->unique_name)) return sd_id128_get_machine(machine); diff --git a/src/libsystemd-bus/bus-control.h b/src/libsystemd-bus/bus-control.h index 34ecb260c3..2cac5d83ae 100644 --- a/src/libsystemd-bus/bus-control.h +++ b/src/libsystemd-bus/bus-control.h @@ -23,5 +23,5 @@ #include "sd-bus.h" -int bus_add_match_internal(sd_bus *bus, const char *match); -int bus_remove_match_internal(sd_bus *bus, const char *match); +int bus_add_match_internal(sd_bus *bus, const char *match, struct bus_match_component *components, unsigned n_components, uint64_t cookie); +int bus_remove_match_internal(sd_bus *bus, const char *match, uint64_t cookie); diff --git a/src/libsystemd-bus/bus-error.c b/src/libsystemd-bus/bus-error.c index 5faa17384e..4696a88f76 100644 --- a/src/libsystemd-bus/bus-error.c +++ b/src/libsystemd-bus/bus-error.c @@ -142,6 +142,9 @@ int bus_error_to_errno(const sd_bus_error* e) { /* Better replce this with a gperf table */ + if (!e) + return -EIO; + if (!e->name) return -EIO; @@ -152,6 +155,30 @@ int bus_error_to_errno(const sd_bus_error* e) { streq(e->name, "org.freedesktop.DBus.Error.AccessDenied")) return -EPERM; + if (streq(e->name, "org.freedesktop.DBus.Error.InvalidArgs")) + return -EINVAL; + + if (streq(e->name, "org.freedesktop.DBus.Error.UnixProcessIdUnknown")) + return -ESRCH; + + if (streq(e->name, "org.freedesktop.DBus.Error.FileNotFound")) + return -ENOENT; + + if (streq(e->name, "org.freedesktop.DBus.Error.FileExists")) + return -EEXIST; + + if (streq(e->name, "org.freedesktop.DBus.Error.Timeout")) + return -ETIMEDOUT; + + if (streq(e->name, "org.freedesktop.DBus.Error.IOError")) + return -EIO; + + if (streq(e->name, "org.freedesktop.DBus.Error.Disconnected")) + return -ECONNRESET; + + if (streq(e->name, "org.freedesktop.DBus.Error.NotSupported")) + return -ENOTSUP; + return -EIO; } @@ -159,13 +186,54 @@ int bus_error_from_errno(sd_bus_error *e, int error) { if (!e) return error; - if (error == -ENOMEM) - sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.NoMemory", strerror(-error)); - else if (error == -EPERM || error == -EACCES) - sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.AccessDenied", strerror(-error)); - else - sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.Failed", "Operation failed"); + switch (error) { + + case -ENOMEM: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.NoMemory", "Out of memory"); + break; + + case -EPERM: + case -EACCES: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.AccessDenied", "Access denied"); + break; + + case -EINVAL: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.InvalidArgs", "Invalid argument"); + break; + + case -ESRCH: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.UnixProcessIdUnknown", "No such process"); + break; + + case -ENOENT: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.FileNotFound", "File not found"); + break; + + case -EEXIST: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.FileExists", "File exists"); + break; + + case -ETIMEDOUT: + case -ETIME: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.Timeout", "Timed out"); + break; + + case -EIO: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.IOError", "Input/output error"); + break; + + case -ENETRESET: + case -ECONNABORTED: + case -ECONNRESET: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.Disconnected", "Disconnected"); + break; + + case -ENOTSUP: + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.NotSupported", "Not supported"); + break; + } + sd_bus_error_set_const(e, "org.freedesktop.DBus.Error.Failed", "Operation failed"); return error; } diff --git a/src/libsystemd-bus/bus-internal.h b/src/libsystemd-bus/bus-internal.h index 4babfac86d..30b8d519a0 100644 --- a/src/libsystemd-bus/bus-internal.h +++ b/src/libsystemd-bus/bus-internal.h @@ -24,15 +24,18 @@ #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> +#include <pthread.h> #include "hashmap.h" #include "prioq.h" #include "list.h" #include "util.h" +#include "refcnt.h" #include "sd-bus.h" #include "bus-error.h" #include "bus-match.h" +#include "bus-kernel.h" struct reply_callback { sd_bus_message_handler_t callback; @@ -66,9 +69,14 @@ enum bus_state { BUS_OPENING, BUS_AUTHENTICATING, BUS_HELLO, - BUS_RUNNING + BUS_RUNNING, + BUS_CLOSED }; +static inline bool BUS_IS_OPEN(enum bus_state state) { + return state > BUS_UNSET && state < BUS_CLOSED; +} + enum bus_auth { _BUS_AUTH_INVALID, BUS_AUTH_EXTERNAL, @@ -76,13 +84,21 @@ enum bus_auth { }; struct sd_bus { - unsigned n_ref; + /* We use atomic ref counting here since sd_bus_message + objects retain references to their originating sd_bus but + we want to allow them to be processed in a different + thread. We won't provide full thread safety, but only the + bare minimum that makes it possible to use sd_bus and + sd_bus_message objects independently and on different + threads as long as each object is used only once at the + same time. */ + RefCount n_ref; + enum bus_state state; int input_fd, output_fd; int message_version; bool is_kernel:1; - bool negotiate_fds:1; bool can_fds:1; bool bus_client:1; bool ucred_valid:1; @@ -95,6 +111,8 @@ struct sd_bus { bool filter_callbacks_modified:1; bool object_callbacks_modified:1; + int use_memfd; + void *rbuffer; size_t rbuffer_size; @@ -150,6 +168,24 @@ struct sd_bus { uint64_t hello_serial; unsigned iteration_counter; + + void *kdbus_buffer; + + /* We do locking around the memfd cache, since we want to + * allow people to process a sd_bus_message in a different + * thread then it was generated on and free it there. Since + * adding something to the memfd cache might happen when a + * message is released, we hence need to protect this bit with + * a mutex. */ + pthread_mutex_t memfd_cache_mutex; + struct memfd_cache memfd_cache[MEMFD_CACHE_MAX]; + unsigned n_memfd_cache; + + pid_t original_pid; + + uint64_t hello_flags; + + uint64_t match_cookie; }; static inline void bus_unrefp(sd_bus **b) { @@ -196,3 +232,5 @@ const char *bus_message_type_to_string(uint8_t u); int bus_ensure_running(sd_bus *bus); int bus_start_running(sd_bus *bus); int bus_next_address(sd_bus *bus); + +bool bus_pid_changed(sd_bus *bus); diff --git a/src/libsystemd-bus/bus-kernel.c b/src/libsystemd-bus/bus-kernel.c index 0762b7836f..bf8de04ab6 100644 --- a/src/libsystemd-bus/bus-kernel.c +++ b/src/libsystemd-bus/bus-kernel.c @@ -25,6 +25,7 @@ #include <fcntl.h> #include <malloc.h> +#include <sys/mman.h> #include "util.h" @@ -33,18 +34,7 @@ #include "bus-kernel.h" #include "bus-bloom.h" -#define KDBUS_ITEM_NEXT(item) \ - (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) - -#define KDBUS_ITEM_FOREACH(item, head) \ - for (item = (head)->items; \ - (uint8_t *)(item) < (uint8_t *)(head) + (head)->size; \ - item = KDBUS_ITEM_NEXT(item)) - -#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) -#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) - -static int parse_unique_name(const char *s, uint64_t *id) { +int bus_kernel_parse_unique_name(const char *s, uint64_t *id) { int r; assert(s); @@ -62,19 +52,36 @@ static int parse_unique_name(const char *s, uint64_t *id) { static void append_payload_vec(struct kdbus_item **d, const void *p, size_t sz) { assert(d); - assert(p); assert(sz > 0); *d = ALIGN8_PTR(*d); + /* Note that p can be NULL, which encodes a region full of + * zeroes, which is useful to optimize certain padding + * conditions */ + (*d)->size = offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec); (*d)->type = KDBUS_MSG_PAYLOAD_VEC; - (*d)->vec.address = (intptr_t) p; + (*d)->vec.address = PTR_TO_UINT64(p); (*d)->vec.size = sz; *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); } +static void append_payload_memfd(struct kdbus_item **d, int memfd, size_t sz) { + assert(d); + assert(memfd >= 0); + assert(sz > 0); + + *d = ALIGN8_PTR(*d); + (*d)->size = offsetof(struct kdbus_item, memfd) + sizeof(struct kdbus_memfd); + (*d)->type = KDBUS_MSG_PAYLOAD_MEMFD; + (*d)->memfd.fd = memfd; + (*d)->memfd.size = sz; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + static void append_destination(struct kdbus_item **d, const char *s, size_t length) { assert(d); assert(s); @@ -111,7 +118,7 @@ static void append_fds(struct kdbus_item **d, const int fds[], unsigned n_fds) { *d = ALIGN8_PTR(*d); (*d)->size = offsetof(struct kdbus_item, fds) + sizeof(int) * n_fds; - (*d)->type = KDBUS_MSG_UNIX_FDS; + (*d)->type = KDBUS_MSG_FDS; memcpy((*d)->fds, fds, sizeof(int) * n_fds); *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); @@ -134,6 +141,7 @@ static int bus_message_setup_bloom(sd_bus_message *m, void *bloom) { bloom_add_pair(bloom, "member", m->member); if (m->path) { bloom_add_pair(bloom, "path", m->path); + bloom_add_pair(bloom, "path-slash-prefix", m->path); bloom_add_prefixes(bloom, "path-slash-prefix", m->path, '/'); } @@ -181,10 +189,12 @@ static int bus_message_setup_bloom(sd_bus_message *m, void *bloom) { } static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { + struct bus_body_part *part; struct kdbus_item *d; bool well_known; uint64_t unique; size_t sz, dl; + unsigned i; int r; assert(b); @@ -195,7 +205,7 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { return 0; if (m->destination) { - r = parse_unique_name(m->destination, &unique); + r = bus_kernel_parse_unique_name(m->destination, &unique); if (r < 0) return r; @@ -205,8 +215,12 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { sz = offsetof(struct kdbus_msg, items); + assert_cc(ALIGN8(offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec)) == + ALIGN8(offsetof(struct kdbus_item, memfd) + sizeof(struct kdbus_memfd))); + /* Add in fixed header, fields header and payload */ - sz += 3 * ALIGN8(offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec)); + sz += (1 + m->n_body_parts) * + ALIGN8(offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec)); /* Add space for bloom filter */ sz += ALIGN8(offsetof(struct kdbus_item, data) + BLOOM_SIZE); @@ -222,9 +236,12 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { sz += ALIGN8(offsetof(struct kdbus_item, fds) + sizeof(int)*m->n_fds); m->kdbus = memalign(8, sz); - if (!m->kdbus) - return -ENOMEM; + if (!m->kdbus) { + r = -ENOMEM; + goto fail; + } + m->free_kdbus = true; memset(m->kdbus, 0, sz); m->kdbus->flags = @@ -243,24 +260,43 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { if (well_known) append_destination(&d, m->destination, dl); - append_payload_vec(&d, m->header, sizeof(*m->header)); + append_payload_vec(&d, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - if (m->fields) - append_payload_vec(&d, m->fields, ALIGN8(m->header->fields_size)); + MESSAGE_FOREACH_PART(part, i, m) { + if (part->is_zero) { + /* If this is padding then simply send a + * vector with a NULL data pointer which the + * kernel will just pass through. This is the + * most efficient way to encode zeroes */ - if (m->body) - append_payload_vec(&d, m->body, m->header->body_size); + append_payload_vec(&d, NULL, part->size); + continue; + } + + if (part->memfd >= 0 && part->sealed && m->destination) { + /* Try to send a memfd, if the part is + * sealed and this is not a broadcast. Since we can only */ + + append_payload_memfd(&d, part->memfd, part->size); + continue; + } + + /* Otherwise let's send a vector to the actual data, + * for that we need to map it first. */ + r = bus_body_part_map(part); + if (r < 0) + goto fail; + + append_payload_vec(&d, part->data, part->size); + } if (m->kdbus->dst_id == KDBUS_DST_ID_BROADCAST) { void *p; p = append_bloom(&d, BLOOM_SIZE); r = bus_message_setup_bloom(m, p); - if (r < 0) { - free(m->kdbus); - m->kdbus = NULL; - return -r; - } + if (r < 0) + goto fail; } if (m->n_fds > 0) @@ -269,23 +305,15 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { m->kdbus->size = (uint8_t*) d - (uint8_t*) m->kdbus; assert(m->kdbus->size <= sz); - m->free_kdbus = true; - return 0; + +fail: + m->poisoned = true; + return r; } int bus_kernel_take_fd(sd_bus *b) { - struct kdbus_cmd_hello hello = { - .conn_flags = - KDBUS_HELLO_ACCEPT_FD| - KDBUS_HELLO_ATTACH_COMM| - KDBUS_HELLO_ATTACH_EXE| - KDBUS_HELLO_ATTACH_CMDLINE| - KDBUS_HELLO_ATTACH_CGROUP| - KDBUS_HELLO_ATTACH_CAPS| - KDBUS_HELLO_ATTACH_SECLABEL| - KDBUS_HELLO_ATTACH_AUDIT - }; + struct kdbus_cmd_hello hello; int r; assert(b); @@ -293,10 +321,25 @@ int bus_kernel_take_fd(sd_bus *b) { if (b->is_server) return -EINVAL; + b->use_memfd = 1; + + zero(hello); + hello.size = sizeof(hello); + hello.conn_flags = b->hello_flags; + hello.pool_size = KDBUS_POOL_SIZE; + r = ioctl(b->input_fd, KDBUS_CMD_HELLO, &hello); if (r < 0) return -errno; + if (!b->kdbus_buffer) { + b->kdbus_buffer = mmap(NULL, KDBUS_POOL_SIZE, PROT_READ, MAP_SHARED, b->input_fd, 0); + if (b->kdbus_buffer == MAP_FAILED) { + b->kdbus_buffer = NULL; + return -errno; + } + } + /* The higher 32bit of both flags fields are considered * 'incompatible flags'. Refuse them all for now. */ if (hello.bus_flags > 0xFFFFFFFFULL || @@ -311,7 +354,7 @@ int bus_kernel_take_fd(sd_bus *b) { b->is_kernel = true; b->bus_client = true; - b->can_fds = true; + b->can_fds = !!(hello.conn_flags & KDBUS_HELLO_ACCEPT_FD); r = bus_start_running(b); if (r < 0) @@ -356,22 +399,29 @@ int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m) { return 1; } -static void close_kdbus_msg(struct kdbus_msg *k) { +static void close_kdbus_msg(sd_bus *bus, struct kdbus_msg *k) { + uint64_t off; struct kdbus_item *d; - KDBUS_ITEM_FOREACH(d, k) { + assert(bus); + assert(k); - if (d->type != KDBUS_MSG_UNIX_FDS) - continue; + off = (uint8_t *)k - (uint8_t *)bus->kdbus_buffer; + ioctl(bus->input_fd, KDBUS_CMD_MSG_RELEASE, &off); - close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); + KDBUS_ITEM_FOREACH(d, k) { + + if (d->type == KDBUS_MSG_FDS) + close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); + else if (d->type == KDBUS_MSG_PAYLOAD_MEMFD) + close_nointr_nofail(d->memfd.fd); } } static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_message **ret) { sd_bus_message *m = NULL; struct kdbus_item *d; - unsigned n_payload = 0, n_fds = 0; + unsigned n_fds = 0; _cleanup_free_ int *fds = NULL; struct bus_header *h = NULL; size_t total, n_bytes = 0, idx = 0; @@ -390,19 +440,25 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_mess l = d->size - offsetof(struct kdbus_item, data); - if (d->type == KDBUS_MSG_PAYLOAD) { + if (d->type == KDBUS_MSG_PAYLOAD_OFF) { if (!h) { - if (l < sizeof(struct bus_header)) - return -EBADMSG; + h = (struct bus_header *)((uint8_t *)bus->kdbus_buffer + d->vec.offset); - h = (struct bus_header*) d->data; + if (!bus_header_is_complete(h, d->vec.size)) + return -EBADMSG; } - n_payload++; - n_bytes += l; + n_bytes += d->vec.size; + + } else if (d->type == KDBUS_MSG_PAYLOAD_MEMFD) { - } else if (d->type == KDBUS_MSG_UNIX_FDS) { + if (!h) + return -EBADMSG; + + n_bytes += d->memfd.size; + + } else if (d->type == KDBUS_MSG_FDS) { int *f; unsigned j; @@ -415,16 +471,14 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_mess memcpy(fds + n_fds, d->fds, sizeof(int) * j); n_fds += j; - } else if (d->type == KDBUS_MSG_DST_NAME) - destination = d->str; - else if (d->type == KDBUS_MSG_SRC_SECLABEL) + } else if (d->type == KDBUS_MSG_SRC_SECLABEL) seclabel = d->str; } if (!h) return -EBADMSG; - r = bus_header_size(h, &total); + r = bus_header_message_size(h, &total); if (r < 0) return r; @@ -440,20 +494,59 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_mess l = d->size - offsetof(struct kdbus_item, data); - if (d->type == KDBUS_MSG_PAYLOAD) { + if (d->type == KDBUS_MSG_PAYLOAD_OFF) { + size_t begin_body; - if (idx == sizeof(struct bus_header) && - l == ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m))) - m->fields = d->data; - else if (idx == sizeof(struct bus_header) + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)) && - l == BUS_MESSAGE_BODY_SIZE(m)) - m->body = d->data; - else if (!(idx == 0 && l == sizeof(struct bus_header))) { - sd_bus_message_unref(m); - return -EBADMSG; + begin_body = BUS_MESSAGE_BODY_BEGIN(m); + + if (idx + d->vec.size > begin_body) { + struct bus_body_part *part; + + /* Contains body material */ + + part = message_append_part(m); + if (!part) { + r = -ENOMEM; + goto fail; + } + + /* A -1 offset is NUL padding. */ + part->is_zero = d->vec.offset == ~0ULL; + + if (idx >= begin_body) { + if (!part->is_zero) + part->data = (uint8_t *)bus->kdbus_buffer + d->vec.offset; + part->size = d->vec.size; + } else { + if (!part->is_zero) + part->data = (uint8_t *)bus->kdbus_buffer + d->vec.offset + (begin_body - idx); + part->size = d->vec.size - (begin_body - idx); + } + + part->sealed = true; } - idx += l; + idx += d->vec.size; + } else if (d->type == KDBUS_MSG_PAYLOAD_MEMFD) { + struct bus_body_part *part; + + if (idx < BUS_MESSAGE_BODY_BEGIN(m)) { + r = -EBADMSG; + goto fail; + } + + part = message_append_part(m); + if (!part) { + r = -ENOMEM; + goto fail; + } + + part->memfd = d->memfd.fd; + part->size = d->memfd.size; + part->sealed = true; + + idx += d->memfd.size; + } else if (d->type == KDBUS_MSG_SRC_CREDS) { m->pid_starttime = d->creds.starttime / NSEC_PER_USEC; m->uid = d->creds.uid; @@ -480,15 +573,16 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_mess else if (d->type == KDBUS_MSG_SRC_CAPS) { m->capability = d->data; m->capability_size = l; - } else + } else if (d->type == KDBUS_MSG_DST_NAME) + destination = d->str; + else if (d->type != KDBUS_MSG_FDS && + d->type != KDBUS_MSG_SRC_SECLABEL) log_debug("Got unknown field from kernel %llu", d->type); } r = bus_message_parse_fields(m); - if (r < 0) { - sd_bus_message_unref(m); - return r; - } + if (r < 0) + goto fail; if (k->src_id == KDBUS_SRC_ID_KERNEL) m->sender = "org.freedesktop.DBus"; @@ -509,66 +603,58 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k, sd_bus_mess /* We take possession of the kmsg struct now */ m->kdbus = k; - m->free_kdbus = true; + m->bus = sd_bus_ref(bus); + m->release_kdbus = true; m->free_fds = true; fds = NULL; *ret = m; return 1; + +fail: + if (m) { + struct bus_body_part *part; + unsigned i; + + /* Make sure the memfds are not freed twice */ + MESSAGE_FOREACH_PART(part, i, m) + if (part->memfd >= 0) + part->memfd = -1; + + sd_bus_message_unref(m); + } + + return r; } int bus_kernel_read_message(sd_bus *bus, sd_bus_message **m) { + uint64_t off; struct kdbus_msg *k; - size_t sz = 1024; int r; assert(bus); assert(m); - for (;;) { - void *q; - - q = memalign(8, sz); - if (!q) - return -errno; - - free(bus->rbuffer); - k = bus->rbuffer = q; - k->size = sz; - - /* Let's tell valgrind that there's really no need to - * initialize this fully. This should be removed again - * when valgrind learned the kdbus ioctls natively. */ -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(k, sz); -#endif - - r = ioctl(bus->input_fd, KDBUS_CMD_MSG_RECV, bus->rbuffer); - if (r >= 0) - break; - + r = ioctl(bus->input_fd, KDBUS_CMD_MSG_RECV, &off); + if (r < 0) { if (errno == EAGAIN) return 0; - if (errno != ENOBUFS) - return -errno; - - sz *= 2; + return -errno; } + k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + off); r = bus_kernel_make_message(bus, k, m); - if (r > 0) - bus->rbuffer = NULL; - else - close_kdbus_msg(k); + if (r <= 0) + close_kdbus_msg(bus, k); return r < 0 ? r : 1; } int bus_kernel_create(const char *name, char **s) { struct kdbus_cmd_bus_make *make; - struct kdbus_item *n, *cg; + struct kdbus_item *n; size_t l; int fd; char *p; @@ -585,18 +671,13 @@ int bus_kernel_create(const char *name, char **s) { KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t) + KDBUS_ITEM_HEADER_SIZE + DECIMAL_STR_MAX(uid_t) + 1 + l + 1); - cg = make->items; - cg->type = KDBUS_MAKE_CGROUP; - cg->data64[0] = 1; - cg->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t); - - n = KDBUS_ITEM_NEXT(cg); + n = make->items; n->type = KDBUS_MAKE_NAME; sprintf(n->str, "%lu-%s", (unsigned long) getuid(), name); n->size = KDBUS_ITEM_HEADER_SIZE + strlen(n->str) + 1; - make->size = offsetof(struct kdbus_cmd_bus_make, items) + cg->size + n->size; - make->flags = KDBUS_MAKE_ACCESS_WORLD | KDBUS_MAKE_POLICY_OPEN; + make->size = offsetof(struct kdbus_cmd_bus_make, items) + n->size; + make->flags = KDBUS_MAKE_POLICY_OPEN; make->bus_flags = 0; make->bloom_size = BLOOM_SIZE; assert_cc(BLOOM_SIZE % 8 == 0); @@ -616,3 +697,95 @@ int bus_kernel_create(const char *name, char **s) { return fd; } + +int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *size) { + struct memfd_cache *c; + int fd; + + assert(address); + assert(size); + + if (!bus || !bus->is_kernel) + return -ENOTSUP; + + assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) >= 0); + + if (bus->n_memfd_cache <= 0) { + int r; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) >= 0); + + r = ioctl(bus->input_fd, KDBUS_CMD_MEMFD_NEW, &fd); + if (r < 0) + return -errno; + + *address = NULL; + *size = 0; + return fd; + } + + c = &bus->memfd_cache[--bus->n_memfd_cache]; + + assert(c->fd >= 0); + assert(c->size == 0 || c->address); + + *address = c->address; + *size = c->size; + fd = c->fd; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) >= 0); + + return fd; +} + +static void close_and_munmap(int fd, void *address, size_t size) { + if (size > 0) + assert_se(munmap(address, PAGE_ALIGN(size)) >= 0); + + close_nointr_nofail(fd); +} + +void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t size) { + struct memfd_cache *c; + uint64_t max_sz = PAGE_ALIGN(MEMFD_CACHE_ITEM_SIZE_MAX); + + assert(fd >= 0); + assert(size == 0 || address); + + if (!bus || !bus->is_kernel) { + close_and_munmap(fd, address, size); + return; + } + + assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) >= 0); + + if (bus->n_memfd_cache >= ELEMENTSOF(bus->memfd_cache)) { + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) >= 0); + + close_and_munmap(fd, address, size); + return; + } + + c = &bus->memfd_cache[bus->n_memfd_cache++]; + c->fd = fd; + c->address = address; + + /* If overly long, let's return a bit to the OS */ + if (size > max_sz) { + assert_se(ioctl(fd, KDBUS_CMD_MEMFD_SIZE_SET, &max_sz) >= 0); + assert_se(munmap((uint8_t*) address + max_sz, PAGE_ALIGN(size - max_sz)) >= 0); + c->size = max_sz; + } else + c->size = size; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) >= 0); +} + +void bus_kernel_flush_memfd(sd_bus *b) { + unsigned i; + + assert(b); + + for (i = 0; i < b->n_memfd_cache; i++) + close_and_munmap(b->memfd_cache[i].fd, b->memfd_cache[i].address, b->memfd_cache[i].size); +} diff --git a/src/libsystemd-bus/bus-kernel.h b/src/libsystemd-bus/bus-kernel.h index ac746afe03..c4573c9222 100644 --- a/src/libsystemd-bus/bus-kernel.h +++ b/src/libsystemd-bus/bus-kernel.h @@ -23,6 +23,37 @@ #include "sd-bus.h" +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) + +#define KDBUS_ITEM_FOREACH(item, head) \ + for (item = (head)->items; \ + (uint8_t *)(item) < (uint8_t *)(head) + (head)->size; \ + item = KDBUS_ITEM_NEXT(item)) + +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) + +#define MEMFD_CACHE_MAX 32 + +/* When we cache a memfd block for reuse, we will truncate blocks + * longer than this in order not to keep too much data around. */ +#define MEMFD_CACHE_ITEM_SIZE_MAX (128*1024) + +/* This determines at which minimum size we prefer sending memfds over + * sending vectors */ +#define MEMFD_MIN_SIZE (128*1024) + +/* The size of the per-connection memory pool that we set up and where + * the kernel places our incoming messages */ +#define KDBUS_POOL_SIZE (16*1024*1024) + +struct memfd_cache { + int fd; + void *address; + size_t size; +}; + int bus_kernel_connect(sd_bus *b); int bus_kernel_take_fd(sd_bus *b); @@ -30,3 +61,10 @@ int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m); int bus_kernel_read_message(sd_bus *bus, sd_bus_message **m); int bus_kernel_create(const char *name, char **s); + +int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *size); +void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t size); + +void bus_kernel_flush_memfd(sd_bus *bus); + +int bus_kernel_parse_unique_name(const char *s, uint64_t *id); diff --git a/src/libsystemd-bus/bus-match.c b/src/libsystemd-bus/bus-match.c index 501a38df70..1411167a7f 100644 --- a/src/libsystemd-bus/bus-match.c +++ b/src/libsystemd-bus/bus-match.c @@ -199,7 +199,6 @@ static bool value_node_same( int bus_match_run( sd_bus *bus, struct bus_match_node *node, - int ret, sd_bus_message *m) { @@ -230,7 +229,7 @@ int bus_match_run( * we won't call any. The children of the root node * are compares or leaves, they will automatically * call their siblings. */ - return bus_match_run(bus, node->child, ret, m); + return bus_match_run(bus, node->child, m); case BUS_MATCH_VALUE: @@ -240,7 +239,7 @@ int bus_match_run( * automatically call their siblings */ assert(node->child); - return bus_match_run(bus, node->child, ret, m); + return bus_match_run(bus, node->child, m); case BUS_MATCH_LEAF: @@ -256,12 +255,13 @@ int bus_match_run( return r; /* Run the callback. And then invoke siblings. */ - assert(node->leaf.callback); - r = node->leaf.callback(bus, ret, m, node->leaf.userdata); - if (r != 0) - return r; + if (node->leaf.callback) { + r = node->leaf.callback(bus, m, node->leaf.userdata); + if (r != 0) + return r; + } - return bus_match_run(bus, node->next, ret, m); + return bus_match_run(bus, node->next, m); case BUS_MATCH_MESSAGE_TYPE: test_u8 = m->header->type; @@ -318,7 +318,7 @@ int bus_match_run( found = NULL; if (found) { - r = bus_match_run(bus, found, ret, m); + r = bus_match_run(bus, found, m); if (r != 0) return r; } @@ -331,7 +331,7 @@ int bus_match_run( if (!value_node_test(c, node->type, test_u8, test_str)) continue; - r = bus_match_run(bus, c, ret, m); + r = bus_match_run(bus, c, m); if (r != 0) return r; } @@ -341,7 +341,7 @@ int bus_match_run( return 0; /* And now, let's invoke our siblings */ - return bus_match_run(bus, node->next, ret, m); + return bus_match_run(bus, node->next, m); } static int bus_match_add_compare_value( @@ -500,6 +500,7 @@ static int bus_match_add_leaf( struct bus_match_node *where, sd_bus_message_handler_t callback, void *userdata, + uint64_t cookie, struct bus_match_node **ret) { struct bus_match_node *n; @@ -519,6 +520,7 @@ static int bus_match_add_leaf( n->next->prev = n; n->leaf.callback = callback; n->leaf.userdata = userdata; + n->leaf.cookie = cookie; where->child = n; @@ -553,22 +555,22 @@ static int bus_match_find_leaf( enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) { assert(k); - if (n == 4 && memcmp(k, "type", 4) == 0) + if (n == 4 && startswith(k, "type")) return BUS_MATCH_MESSAGE_TYPE; - if (n == 6 && memcmp(k, "sender", 6) == 0) + if (n == 6 && startswith(k, "sender")) return BUS_MATCH_SENDER; - if (n == 11 && memcmp(k, "destination", 11) == 0) + if (n == 11 && startswith(k, "destination")) return BUS_MATCH_DESTINATION; - if (n == 9 && memcmp(k, "interface", 9) == 0) + if (n == 9 && startswith(k, "interface")) return BUS_MATCH_INTERFACE; - if (n == 6 && memcmp(k, "member", 6) == 0) + if (n == 6 && startswith(k, "member")) return BUS_MATCH_MEMBER; - if (n == 4 && memcmp(k, "path", 4) == 0) + if (n == 4 && startswith(k, "path")) return BUS_MATCH_PATH; - if (n == 14 && memcmp(k, "path_namespace", 14) == 0) + if (n == 14 && startswith(k, "path_namespace")) return BUS_MATCH_PATH_NAMESPACE; - if (n == 4 && memcmp(k, "arg", 3) == 0) { + if (n == 4 && startswith(k, "arg")) { int j; j = undecchar(k[3]); @@ -578,7 +580,7 @@ enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n return BUS_MATCH_ARG + j; } - if (n == 5 && memcmp(k, "arg", 3) == 0) { + if (n == 5 && startswith(k, "arg")) { int a, b; enum bus_match_node_type t; @@ -594,7 +596,7 @@ enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n return t; } - if (n == 8 && memcmp(k, "arg", 3) == 0 && memcmp(k + 4, "path", 4) == 0) { + if (n == 8 && startswith(k, "arg") && startswith(k + 4, "path")) { int j; j = undecchar(k[3]); @@ -604,7 +606,7 @@ enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n return BUS_MATCH_ARG_PATH + j; } - if (n == 9 && memcmp(k, "arg", 3) == 0 && memcmp(k + 5, "path", 4) == 0) { + if (n == 9 && startswith(k, "arg") && startswith(k + 5, "path")) { enum bus_match_node_type t; int a, b; @@ -620,7 +622,7 @@ enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n return t; } - if (n == 13 && memcmp(k, "arg", 3) == 0 && memcmp(k + 4, "namespace", 9) == 0) { + if (n == 13 && startswith(k, "arg") && startswith(k + 4, "namespace")) { int j; j = undecchar(k[3]); @@ -630,7 +632,7 @@ enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n return BUS_MATCH_ARG_NAMESPACE + j; } - if (n == 14 && memcmp(k, "arg", 3) == 0 && memcmp(k + 5, "namespace", 9) == 0) { + if (n == 14 && startswith(k, "arg") && startswith(k + 5, "namespace")) { enum bus_match_node_type t; int a, b; @@ -649,14 +651,8 @@ enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n return -EINVAL; } -struct match_component { - enum bus_match_node_type type; - uint8_t value_u8; - char *value_str; -}; - static int match_component_compare(const void *a, const void *b) { - const struct match_component *x = a, *y = b; + const struct bus_match_component *x = a, *y = b; if (x->type < y->type) return -1; @@ -666,7 +662,7 @@ static int match_component_compare(const void *a, const void *b) { return 0; } -static void free_components(struct match_component *components, unsigned n_components) { +void bus_match_parse_free(struct bus_match_component *components, unsigned n_components) { unsigned i; for (i = 0; i < n_components; i++) @@ -675,13 +671,13 @@ static void free_components(struct match_component *components, unsigned n_compo free(components); } -static int parse_match( +int bus_match_parse( const char *match, - struct match_component **_components, + struct bus_match_component **_components, unsigned *_n_components) { const char *p = match; - struct match_component *components = NULL; + struct bus_match_component *components = NULL; size_t components_allocated = 0; unsigned n_components = 0, i; _cleanup_free_ char *value = NULL; @@ -772,7 +768,7 @@ static int parse_match( } /* Order the whole thing, so that we always generate the same tree */ - qsort(components, n_components, sizeof(struct match_component), match_component_compare); + qsort(components, n_components, sizeof(struct bus_match_component), match_component_compare); /* Check for duplicates */ for (i = 0; i+1 < n_components; i++) @@ -787,29 +783,24 @@ static int parse_match( return 0; fail: - free_components(components, n_components); + bus_match_parse_free(components, n_components); return r; } int bus_match_add( struct bus_match_node *root, - const char *match, + struct bus_match_component *components, + unsigned n_components, sd_bus_message_handler_t callback, void *userdata, + uint64_t cookie, struct bus_match_node **ret) { - struct match_component *components = NULL; - unsigned n_components = 0, i; + unsigned i; struct bus_match_node *n; int r; assert(root); - assert(match); - assert(callback); - - r = parse_match(match, &components, &n_components); - if (r < 0) - return r; n = root; for (i = 0; i < n_components; i++) { @@ -817,38 +808,32 @@ int bus_match_add( n, components[i].type, components[i].value_u8, components[i].value_str, &n); if (r < 0) - goto finish; + return r; } - r = bus_match_add_leaf(n, callback, userdata, &n); + r = bus_match_add_leaf(n, callback, userdata, cookie, &n); if (r < 0) - goto finish; + return r; if (ret) *ret = n; -finish: - free_components(components, n_components); - return r; + return 0; } int bus_match_remove( struct bus_match_node *root, - const char *match, + struct bus_match_component *components, + unsigned n_components, sd_bus_message_handler_t callback, - void *userdata) { + void *userdata, + uint64_t *cookie) { - struct match_component *components = NULL; - unsigned n_components = 0, i; + unsigned i; struct bus_match_node *n, **gc; int r; assert(root); - assert(match); - - r = parse_match(match, &components, &n_components); - if (r < 0) - return r; gc = newa(struct bus_match_node*, n_components); @@ -859,14 +844,17 @@ int bus_match_remove( components[i].value_u8, components[i].value_str, &n); if (r <= 0) - goto finish; + return r; gc[i] = n; } r = bus_match_find_leaf(n, callback, userdata, &n); if (r <= 0) - goto finish; + return r; + + if (cookie) + *cookie = n->leaf.cookie; /* Free the leaf */ bus_match_node_free(n); @@ -882,8 +870,6 @@ int bus_match_remove( break; } -finish: - free_components(components, n_components); return r; } diff --git a/src/libsystemd-bus/bus-match.h b/src/libsystemd-bus/bus-match.h index 075f1a9e3a..d24aeec43d 100644 --- a/src/libsystemd-bus/bus-match.h +++ b/src/libsystemd-bus/bus-match.h @@ -61,6 +61,7 @@ struct bus_match_node { sd_bus_message_handler_t callback; void *userdata; unsigned last_iteration; + uint64_t cookie; } leaf; struct { /* If this is set, then the child is NULL */ @@ -69,10 +70,16 @@ struct bus_match_node { }; }; -int bus_match_run(sd_bus *bus, struct bus_match_node *root, int ret, sd_bus_message *m); +struct bus_match_component { + enum bus_match_node_type type; + uint8_t value_u8; + char *value_str; +}; -int bus_match_add(struct bus_match_node *root, const char *match, sd_bus_message_handler_t callback, void *userdata, struct bus_match_node **ret); -int bus_match_remove(struct bus_match_node *root, const char *match, sd_bus_message_handler_t callback, void *userdata); +int bus_match_run(sd_bus *bus, struct bus_match_node *root, sd_bus_message *m); + +int bus_match_add(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, sd_bus_message_handler_t callback, void *userdata, uint64_t cookie, struct bus_match_node **ret); +int bus_match_remove(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, sd_bus_message_handler_t callback, void *userdata, uint64_t *cookie); void bus_match_free(struct bus_match_node *node); @@ -80,3 +87,6 @@ void bus_match_dump(struct bus_match_node *node, unsigned level); const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l); enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n); + +int bus_match_parse(const char *match, struct bus_match_component **_components, unsigned *_n_components); +void bus_match_parse_free(struct bus_match_component *components, unsigned n_components); diff --git a/src/libsystemd-bus/bus-message.c b/src/libsystemd-bus/bus-message.c index 835a9f9a44..760a148fad 100644 --- a/src/libsystemd-bus/bus-message.c +++ b/src/libsystemd-bus/bus-message.c @@ -21,6 +21,7 @@ #include <errno.h> #include <fcntl.h> +#include <sys/mman.h> #include "util.h" #include "utf8.h" @@ -36,7 +37,69 @@ static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); -static void reset_containers(sd_bus_message *m) { +static void *adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) { + + if (p == NULL) + return NULL; + + if (old_base == new_base) + return (void*) p; + + if ((uint8_t*) p < (uint8_t*) old_base) + return (void*) p; + + if ((uint8_t*) p >= (uint8_t*) old_base + sz) + return (void*) p; + + return (uint8_t*) new_base + ((uint8_t*) p - (uint8_t*) old_base); +} + +static void message_free_part(sd_bus_message *m, struct bus_body_part *part) { + assert(m); + assert(part); + + if (part->memfd >= 0) { + /* If we can reuse the memfd, try that. For that it + * can't be sealed yet. */ + + if (!part->sealed) + bus_kernel_push_memfd(m->bus, part->memfd, part->data, part->mapped); + else { + if (part->mapped > 0) + assert_se(munmap(part->data, part->mapped) == 0); + + close_nointr_nofail(part->memfd); + } + + } else if (part->munmap_this) + munmap(part->data, part->mapped); + else if (part->free_this) + free(part->data); + + if (part != &m->body) + free(part); +} + +static void message_reset_parts(sd_bus_message *m) { + struct bus_body_part *part; + + assert(m); + + part = &m->body; + while (m->n_body_parts > 0) { + struct bus_body_part *next = part->next; + message_free_part(m, part); + part = next; + m->n_body_parts--; + } + + m->body_end = NULL; + + m->cached_rindex_part = NULL; + m->cached_rindex_part_begin = 0; +} + +static void message_reset_containers(sd_bus_message *m) { unsigned i; assert(m); @@ -57,23 +120,32 @@ static void message_free(sd_bus_message *m) { if (m->free_header) free(m->header); - if (m->free_fields) - free(m->fields); - - if (m->free_body) - free(m->body); + message_reset_parts(m); if (m->free_kdbus) free(m->kdbus); + if (m->release_kdbus) { + uint64_t off; + + off = (uint8_t *)m->kdbus - (uint8_t *)m->bus->kdbus_buffer; + ioctl(m->bus->input_fd, KDBUS_CMD_MSG_RELEASE, &off); + } + + if (m->bus) + sd_bus_unref(m->bus); + if (m->free_fds) { close_many(m->fds, m->n_fds); free(m->fds); } + if (m->iovec != m->iovec_fixed) + free(m->iovec); + free(m->cmdline_array); - reset_containers(m); + message_reset_containers(m); free(m->root_container.signature); free(m->peeked_signature); @@ -84,67 +156,64 @@ static void message_free(sd_bus_message *m) { free(m); } -static void* buffer_extend(void **p, uint32_t *sz, size_t align, size_t extend) { - size_t start, n; - void *k; - - assert(p); - assert(sz); - assert(align > 0); - - start = ALIGN_TO((size_t) *sz, align); - n = start + extend; +static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz) { + void *op, *np; + size_t old_size, new_size, start; - if (n == *sz) - return (uint8_t*) *p + start; + assert(m); - if (n > (size_t) ((uint32_t) -1)) + if (m->poisoned) return NULL; - k = realloc(*p, n); - if (!k) - return NULL; + old_size = sizeof(struct bus_header) + m->header->fields_size; + start = ALIGN_TO(old_size, align); + new_size = start + sz; - /* Zero out padding */ - if (start > *sz) - memset((uint8_t*) k + *sz, 0, start - *sz); + if (old_size == new_size) + return (uint8_t*) m->header + old_size; - *p = k; - *sz = n; + if (new_size > (size_t) ((uint32_t) -1)) + goto poison; - return (uint8_t*) k + start; -} + if (m->free_header) { + np = realloc(m->header, ALIGN8(new_size)); + if (!np) + goto poison; + } else { + /* Initially, the header is allocated as part of of + * the sd_bus_message itself, let's replace it by + * dynamic data */ -static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz) { - void *p, *o; + np = malloc(ALIGN8(new_size)); + if (!np) + goto poison; - assert(m); + memcpy(np, m->header, sizeof(struct bus_header)); + } - o = m->fields; - p = buffer_extend(&m->fields, &m->header->fields_size, align, sz); - if (!p) - return NULL; + /* Zero out padding */ + if (start > old_size) + memset((uint8_t*) np + old_size, 0, start - old_size); - if (o != m->fields) { - /* Adjust quick access pointers */ + op = m->header; + m->header = np; + m->header->fields_size = new_size - sizeof(struct bus_header); - if (m->path) - m->path = (const char*) m->fields + (m->path - (const char*) o); - if (m->interface) - m->interface = (const char*) m->fields + (m->interface - (const char*) o); - if (m->member) - m->member = (const char*) m->fields + (m->member - (const char*) o); - if (m->destination) - m->destination = (const char*) m->fields + (m->destination - (const char*) o); - if (m->sender) - m->sender = (const char*) m->fields + (m->sender - (const char*) o); - if (m->error.name) - m->error.name = (const char*) m->fields + (m->error.name - (const char*) o); - } + /* Adjust quick access pointers */ + m->path = adjust_pointer(m->path, op, old_size, m->header); + m->interface = adjust_pointer(m->interface, op, old_size, m->header); + m->member = adjust_pointer(m->member, op, old_size, m->header); + m->destination = adjust_pointer(m->destination, op, old_size, m->header); + m->sender = adjust_pointer(m->sender, op, old_size, m->header); + m->error.name = adjust_pointer(m->error.name, op, old_size, m->header); + + m->free_header = true; - m->free_fields = true; + return (uint8_t*) np + start; - return p; +poison: + m->poisoned = true; + return NULL; } static int message_append_field_string( @@ -177,7 +246,7 @@ static int message_append_field_string( memcpy(p + 8, s, l + 1); if (ret) - *ret = (const char*) p + 8; + *ret = (char*) p + 8; return 0; } @@ -324,10 +393,14 @@ int bus_message_from_malloc( goto fail; } - m->fields = (uint8_t*) buffer + sizeof(struct bus_header); - m->body = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)); + m->n_body_parts = 1; + m->body.data = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)); + m->body.size = length - sizeof(struct bus_header) - ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)); + m->body.sealed = true; + m->body.memfd = -1; m->n_iovec = 1; + m->iovec = m->iovec_fixed; m->iovec[0].iov_base = buffer; m->iovec[0].iov_len = length; @@ -361,6 +434,9 @@ static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { m->header->version = bus ? bus->message_version : 1; m->allow_fds = !bus || bus->can_fds || (bus->state != BUS_HELLO && bus->state != BUS_RUNNING); + if (bus) + m->bus = sd_bus_ref(bus); + return m; } @@ -492,7 +568,7 @@ static int message_new_reply( goto fail; if (call->sender) { - r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->sender); + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->destination); if (r < 0) goto fail; } @@ -551,6 +627,43 @@ fail: return r; } +int bus_message_new_synthetic_error( + sd_bus *bus, + uint64_t serial, + const sd_bus_error *e, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + assert(sd_bus_error_is_set(e)); + assert(m); + + t = message_new(bus, SD_BUS_MESSAGE_TYPE_METHOD_ERROR); + if (!t) + return -ENOMEM; + + t->header->flags |= SD_BUS_MESSAGE_NO_REPLY_EXPECTED; + t->reply_serial = serial; + + r = message_append_field_uint32(t, SD_BUS_MESSAGE_HEADER_REPLY_SERIAL, t->reply_serial); + if (r < 0) + goto fail; + + if (bus && bus->unique_name) { + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, bus->unique_name, &t->destination); + if (r < 0) + goto fail; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + sd_bus_message* sd_bus_message_ref(sd_bus_message *m) { if (!m) return NULL; @@ -1016,35 +1129,205 @@ static struct bus_container *message_get_container(sd_bus_message *m) { return m->containers + m->n_containers - 1; } -static void *message_extend_body(sd_bus_message *m, size_t align, size_t sz) { - void *p, *o; - size_t added; +struct bus_body_part *message_append_part(sd_bus_message *m) { + struct bus_body_part *part; + + assert(m); + + if (m->poisoned) + return NULL; + + if (m->n_body_parts <= 0) { + part = &m->body; + zero(*part); + } else { + assert(m->body_end); + + part = new0(struct bus_body_part, 1); + if (!part) { + m->poisoned = true; + return NULL; + } + + m->body_end->next = part; + } + + part->memfd = -1; + m->body_end = part; + m->n_body_parts ++; + + return part; +} + +static void part_zero(struct bus_body_part *part, size_t sz) { + assert(part); + assert(sz > 0); + assert(sz < 8); + + /* All other fields can be left in their defaults */ + assert(!part->data); + assert(part->memfd < 0); + + part->size = sz; + part->is_zero = true; + part->sealed = true; +} + +static int part_make_space( + struct sd_bus_message *m, + struct bus_body_part *part, + size_t sz, + void **q) { + + void *n; + int r; + + assert(m); + assert(part); + assert(!part->sealed); + + if (m->poisoned) + return -ENOMEM; + + if (!part->data && part->memfd < 0) + part->memfd = bus_kernel_pop_memfd(m->bus, &part->data, &part->mapped); + + if (part->memfd >= 0) { + uint64_t u = sz; + + r = ioctl(part->memfd, KDBUS_CMD_MEMFD_SIZE_SET, &u); + if (r < 0) { + m->poisoned = true; + return -errno; + } + + if (!part->data || sz > part->mapped) { + size_t psz = PAGE_ALIGN(sz > 0 ? sz : 1); + + if (part->mapped <= 0) + n = mmap(NULL, psz, PROT_READ|PROT_WRITE, MAP_SHARED, part->memfd, 0); + else + n = mremap(part->data, part->mapped, psz, MREMAP_MAYMOVE); + + if (n == MAP_FAILED) { + m->poisoned = true; + return -errno; + } + + part->mapped = psz; + part->data = n; + } + + part->munmap_this = true; + } else { + n = realloc(part->data, sz); + if (!n) { + m->poisoned = true; + return -ENOMEM; + } + + part->data = n; + part->free_this = true; + } + + if (q) + *q = part->data ? (uint8_t*) part->data + part->size : NULL; + + part->size = sz; + return 0; +} + +static void message_extend_containers(sd_bus_message *m, size_t expand) { struct bus_container *c; assert(m); + + if (expand <= 0) + return; + + /* Update counters */ + for (c = m->containers; c < m->containers + m->n_containers; c++) + if (c->array_size) + *c->array_size += expand; +} + +static void *message_extend_body(sd_bus_message *m, size_t align, size_t sz) { + struct bus_body_part *part = NULL; + size_t start_body, end_body, padding, start_part, end_part, added; + bool add_new_part; + void *p; + int r; + + assert(m); assert(align > 0); + assert(!m->sealed); + + if (m->poisoned) + return NULL; - o = m->body; - added = m->header->body_size; + start_body = ALIGN_TO((size_t) m->header->body_size, align); + end_body = start_body + sz; - p = buffer_extend(&m->body, &m->header->body_size, align, sz); - if (!p) + padding = start_body - m->header->body_size; + added = padding + sz; + + /* Check for 32bit overflows */ + if (end_body > (size_t) ((uint32_t) -1)) { + m->poisoned = true; return NULL; + } - added = m->header->body_size - added; + add_new_part = + m->n_body_parts <= 0 || + m->body_end->sealed || + padding != ALIGN_TO(m->body_end->size, align) - m->body_end->size; - for (c = m->containers; c < m->containers + m->n_containers; c++) - if (c->array_size) { - c->array_size = (uint32_t*) ((uint8_t*) m->body + ((uint8_t*) c->array_size - (uint8_t*) o)); - *c->array_size += added; + if (add_new_part) { + if (padding > 0) { + part = message_append_part(m); + if (!part) + return NULL; + + part_zero(part, padding); + } + + part = message_append_part(m); + if (!part) + return NULL; + + r = part_make_space(m, part, sz, &p); + if (r < 0) + return NULL; + } else { + struct bus_container *c; + void *op; + size_t os; + + part = m->body_end; + op = part->data; + os = part->size; + + start_part = ALIGN_TO(part->size, align); + end_part = start_part + sz; + + r = part_make_space(m, part, end_part, &p); + if (r < 0) + return NULL; + + if (padding > 0) { + memset(p, 0, padding); + p = (uint8_t*) p + padding; } - if (o != m->body) { - if (m->error.message) - m->error.message = (const char*) m->body + (m->error.message - (const char*) o); + /* Readjust pointers */ + for (c = m->containers; c < m->containers + m->n_containers; c++) + c->array_size = adjust_pointer(c->array_size, op, os, part->data); + + m->error.message = (const char*) adjust_pointer(m->error.message, op, os, part->data); } - m->free_body = true; + m->header->body_size = end_body; + message_extend_containers(m, added); return p; } @@ -1054,7 +1337,6 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void ssize_t align, sz; uint32_t k; void *a; - char *e = NULL; int fd = -1; uint32_t fdi = 0; int r; @@ -1067,6 +1349,8 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void return -EPERM; if (!bus_type_is_basic(type)) return -EINVAL; + if (m->poisoned) + return -ESTALE; c = message_get_container(m); @@ -1076,13 +1360,17 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void if (c->signature[c->index] != type) return -ENXIO; } else { + char *e; + /* Maybe we can append to the signature? But only if this is the top-level container*/ if (c->enclosing != 0) return -ENXIO; e = strextend(&c->signature, CHAR_TO_STR(type), NULL); - if (!e) + if (!e) { + m->poisoned = true; return -ENOMEM; + } } switch (type) { @@ -1133,6 +1421,7 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void f = realloc(m->fds, sizeof(int) * (m->n_fds + 1)); if (!f) { + m->poisoned = true; r = -ENOMEM; goto fail; } @@ -1193,10 +1482,6 @@ int message_append_basic(sd_bus_message *m, char type, const void *p, const void return 0; fail: - /* Truncate extended signature again */ - if (e) - c->signature[c->index] = 0; - if (fd >= 0) close_nointr_nofail(fd); @@ -1207,6 +1492,55 @@ int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) { return message_append_basic(m, type, p, NULL); } +int sd_bus_message_append_string_space(sd_bus_message *m, size_t size, char **s) { + struct bus_container *c; + void *a; + + if (!m) + return -EINVAL; + if (!s) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (m->poisoned) + return -ESTALE; + + c = message_get_container(m); + + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != SD_BUS_TYPE_STRING) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container*/ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + a = message_extend_body(m, 4, 4 + size + 1); + if (!a) + return -ENOMEM; + + *(uint32_t*) a = size; + *s = (char*) a + 4; + + (*s)[size] = 0; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + static int bus_message_open_array( sd_bus_message *m, struct bus_container *c, @@ -1214,10 +1548,10 @@ static int bus_message_open_array( uint32_t **array_size) { unsigned nindex; - char *e = NULL; - void *a, *b; + void *a, *op; int alignment; - size_t saved; + size_t os; + struct bus_body_part *o; assert(m); assert(c); @@ -1243,45 +1577,42 @@ static int bus_message_open_array( nindex = c->index + 1 + strlen(contents); } else { + char *e; + if (c->enclosing != 0) return -ENXIO; /* Extend the existing signature */ e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents, NULL); - if (!e) + if (!e) { + m->poisoned = true; return -ENOMEM; + } nindex = e - c->signature; } - saved = m->header->body_size; a = message_extend_body(m, 4, 4); - if (!a) { - /* Truncate extended signature again */ - if (e) - c->signature[c->index] = 0; - + if (!a) return -ENOMEM; - } - b = m->body; - if (!message_extend_body(m, alignment, 0)) { - /* Add alignment between size and first element */ - if (e) - c->signature[c->index] = 0; + o = m->body_end; + op = m->body_end->data; + os = m->body_end->size; - m->header->body_size = saved; + /* Add alignment between size and first element */ + if (!message_extend_body(m, alignment, 0)) return -ENOMEM; - } if (c->enclosing != SD_BUS_TYPE_ARRAY) c->index = nindex; - /* m->body might have changed so let's readjust a */ - a = (uint8_t*) m->body + ((uint8_t*) a - (uint8_t*) b); - *(uint32_t*) a = 0; + /* location of array size might have changed so let's readjust a */ + if (o == m->body_end) + a = adjust_pointer(a, op, os, m->body_end->data); + *(uint32_t*) a = 0; *array_size = a; return 0; } @@ -1291,7 +1622,6 @@ static int bus_message_open_variant( struct bus_container *c, const char *contents) { - char *e = NULL; size_t l; void *a; @@ -1311,23 +1641,22 @@ static int bus_message_open_variant( return -ENXIO; } else { + char *e; + if (c->enclosing != 0) return -ENXIO; e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); - if (!e) + if (!e) { + m->poisoned = true; return -ENOMEM; + } } l = strlen(contents); a = message_extend_body(m, 1, 1 + l + 1); - if (!a) { - /* Truncate extended signature again */ - if (e) - c->signature[c->index] = 0; - + if (!a) return -ENOMEM; - } *(uint8_t*) a = l; memcpy((uint8_t*) a + 1, contents, l + 1); @@ -1344,7 +1673,6 @@ static int bus_message_open_struct( const char *contents) { size_t nindex; - char *e = NULL; assert(m); assert(c); @@ -1365,23 +1693,23 @@ static int bus_message_open_struct( nindex = c->index + 1 + l + 1; } else { + char *e; + if (c->enclosing != 0) return -ENXIO; e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END), NULL); - if (!e) + if (!e) { + m->poisoned = true; return -ENOMEM; + } nindex = e - c->signature; } /* Align contents to 8 byte boundary */ - if (!message_extend_body(m, 8, 0)) { - if (e) - c->signature[c->index] = 0; - + if (!message_extend_body(m, 8, 0)) return -ENOMEM; - } if (c->enclosing != SD_BUS_TYPE_ARRAY) c->index = nindex; @@ -1438,6 +1766,7 @@ int sd_bus_message_open_container( struct bus_container *c, *w; uint32_t *array_size = NULL; char *signature; + size_t before; int r; if (!m) @@ -1446,18 +1775,30 @@ int sd_bus_message_open_container( return -EPERM; if (!contents) return -EINVAL; + if (m->poisoned) + return -ESTALE; /* Make sure we have space for one more container */ w = realloc(m->containers, sizeof(struct bus_container) * (m->n_containers + 1)); - if (!w) + if (!w) { + m->poisoned = true; return -ENOMEM; + } + m->containers = w; c = message_get_container(m); signature = strdup(contents); - if (!signature) + if (!signature) { + m->poisoned = true; return -ENOMEM; + } + + /* Save old index in the parent container, in case we have to + * abort this container */ + c->saved_index = c->index; + before = m->header->body_size; if (type == SD_BUS_TYPE_ARRAY) r = bus_message_open_array(m, c, contents, &array_size); @@ -1481,7 +1822,8 @@ int sd_bus_message_open_container( w->signature = signature; w->index = 0; w->array_size = array_size; - w->begin = 0; + w->before = before; + w->begin = m->rindex; return 0; } @@ -1495,6 +1837,8 @@ int sd_bus_message_close_container(sd_bus_message *m) { return -EPERM; if (m->n_containers <= 0) return -EINVAL; + if (m->poisoned) + return -ESTALE; c = message_get_container(m); if (c->enclosing != SD_BUS_TYPE_ARRAY) @@ -1507,7 +1851,6 @@ int sd_bus_message_close_container(sd_bus_message *m) { return 0; } - typedef struct { const char *types; unsigned n_struct; @@ -1752,6 +2095,8 @@ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { return -EINVAL; if (m->sealed) return -EPERM; + if (m->poisoned) + return -ESTALE; if (!types) return 0; @@ -1762,16 +2107,280 @@ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { return r; } +int sd_bus_message_append_array_space(sd_bus_message *m, char type, size_t size, void **ptr) { + ssize_t align, sz; + void *a; + int r; + + if (!m) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (!bus_type_is_trivial(type)) + return -EINVAL; + if (!ptr && size > 0) + return -EINVAL; + if (m->poisoned) + return -ESTALE; + + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + + assert_se(align > 0); + assert_se(sz > 0); + + if (size % sz != 0) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r < 0) + return r; + + a = message_extend_body(m, align, size); + if (!a) + return -ENOMEM; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + *ptr = a; + return 0; +} + +int sd_bus_message_append_array(sd_bus_message *m, char type, const void *ptr, size_t size) { + int r; + void *p; + + if (!ptr && size > 0) + return -EINVAL; + + r = sd_bus_message_append_array_space(m, type, size, &p); + if (r < 0) + return r; + + if (size > 0) + memcpy(p, ptr, size); + + return 0; +} + +int sd_bus_message_append_array_memfd(sd_bus_message *m, char type, sd_memfd *memfd) { + _cleanup_close_ int copy_fd = -1; + struct bus_body_part *part; + ssize_t align, sz; + uint64_t size; + void *a; + int r; + + if (!m) + return -EINVAL; + if (!memfd) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (!bus_type_is_trivial(type)) + return -EINVAL; + if (m->poisoned) + return -ESTALE; + + r = sd_memfd_set_sealed(memfd, true); + if (r < 0) + return r; + + copy_fd = sd_memfd_dup_fd(memfd); + if (copy_fd < 0) + return copy_fd; + + r = sd_memfd_get_size(memfd, &size); + if (r < 0) + return r; + + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + + assert_se(align > 0); + assert_se(sz > 0); + + if (size % sz != 0) + return -EINVAL; + + if (size > (uint64_t) (uint32_t) -1) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r < 0) + return r; + + a = message_extend_body(m, align, 0); + if (!a) + return -ENOMEM; + + part = message_append_part(m); + if (!part) + return -ENOMEM; + + part->memfd = copy_fd; + part->sealed = true; + part->size = size; + copy_fd = -1; + + message_extend_containers(m, size); + m->header->body_size += size; + + return sd_bus_message_close_container(m); +} + +int sd_bus_message_append_string_memfd(sd_bus_message *m, sd_memfd *memfd) { + _cleanup_close_ int copy_fd = -1; + struct bus_body_part *part; + struct bus_container *c; + uint64_t size; + void *a; + int r; + + if (!m) + return -EINVAL; + if (!memfd) + return -EINVAL; + if (m->sealed) + return -EPERM; + if (m->poisoned) + return -ESTALE; + + r = sd_memfd_set_sealed(memfd, true); + if (r < 0) + return r; + + copy_fd = sd_memfd_dup_fd(memfd); + if (copy_fd < 0) + return copy_fd; + + r = sd_memfd_get_size(memfd, &size); + if (r < 0) + return r; + + /* We require this to be NUL terminated */ + if (size == 0) + return -EINVAL; + + if (size > (uint64_t) (uint32_t) -1) + return -EINVAL; + + c = message_get_container(m); + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != SD_BUS_TYPE_STRING) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container*/ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + a = message_extend_body(m, 4, 4); + if (!a) + return -ENOMEM; + + *(uint32_t*) a = size - 1; + + part = message_append_part(m); + if (!part) + return -ENOMEM; + + part->memfd = copy_fd; + part->sealed = true; + part->size = size; + copy_fd = -1; + + message_extend_containers(m, size); + m->header->body_size += size; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +int bus_body_part_map(struct bus_body_part *part) { + void *p; + size_t psz; + + assert_se(part); + + if (part->data) + return 0; + + if (part->size <= 0) + return 0; + + /* For smaller zero parts (as used for padding) we don't need to map anything... */ + if (part->memfd < 0 && part->is_zero && part->size < 8) { + static const uint8_t zeroes[7] = { }; + part->data = (void*) zeroes; + return 0; + } + + psz = PAGE_ALIGN(part->size); + + if (part->memfd >= 0) + p = mmap(NULL, psz, PROT_READ, MAP_SHARED, part->memfd, 0); + else if (part->is_zero) + p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + else + return -EINVAL; + + if (p == MAP_FAILED) + return -errno; + + part->mapped = psz; + part->data = p; + part->munmap_this = true; + + return 0; +} + +void bus_body_part_unmap(struct bus_body_part *part) { + + assert_se(part); + + if (part->memfd < 0) + return; + + if (!part->data) + return; + + if (!part->munmap_this) + return; + + assert_se(munmap(part->data, part->mapped) == 0); + + part->data = NULL; + part->mapped = 0; + part->munmap_this = false; + + return; +} + static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, size_t nbytes, void **r) { - size_t k, start, n; + size_t k, start, end; assert(rindex); assert(align > 0); start = ALIGN_TO((size_t) *rindex, align); - n = start + nbytes; + end = start + nbytes; - if (n > sz) + if (end > sz) return -EBADMSG; /* Verify that padding is 0 */ @@ -1782,7 +2391,7 @@ static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, if (r) *r = (uint8_t*) p + start; - *rindex = n; + *rindex = end; return 1; } @@ -1799,7 +2408,58 @@ static bool message_end_of_array(sd_bus_message *m, size_t index) { return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size); } -static int message_peek_body(sd_bus_message *m, size_t *rindex, size_t align, size_t nbytes, void **ret) { +static struct bus_body_part* find_part(sd_bus_message *m, size_t index, size_t sz, void **p) { + struct bus_body_part *part; + size_t begin; + int r; + + assert(m); + + if (m->cached_rindex_part && index >= m->cached_rindex_part_begin) { + part = m->cached_rindex_part; + begin = m->cached_rindex_part_begin; + } else { + part = &m->body; + begin = 0; + } + + while (part) { + if (index < begin) + return NULL; + + if (index + sz <= begin + part->size) { + + r = bus_body_part_map(part); + if (r < 0) + return NULL; + + if (p) + *p = (uint8_t*) part->data + index - begin; + + m->cached_rindex_part = part; + m->cached_rindex_part_begin = begin; + + return part; + } + + begin += part->size; + part = part->next; + } + + return NULL; +} + +static int message_peek_body( + sd_bus_message *m, + size_t *rindex, + size_t align, + size_t nbytes, + void **ret) { + + size_t k, start, end, padding; + struct bus_body_part *part; + uint8_t *q; + assert(m); assert(rindex); assert(align > 0); @@ -1807,7 +2467,34 @@ static int message_peek_body(sd_bus_message *m, size_t *rindex, size_t align, si if (message_end_of_array(m, *rindex)) return 0; - return buffer_peek(m->body, BUS_MESSAGE_BODY_SIZE(m), rindex, align, nbytes, ret); + start = ALIGN_TO((size_t) *rindex, align); + padding = start - *rindex; + end = start + nbytes; + + if (end > BUS_MESSAGE_BODY_SIZE(m)) + return -EBADMSG; + + part = find_part(m, *rindex, padding, (void**) &q); + if (!part) + return -EBADMSG; + + if (q) { + /* Verify padding */ + for (k = 0; k < padding; k++) + if (q[k] != 0) + return -EBADMSG; + } + + part = find_part(m, start, nbytes, (void**) &q); + if (!part || !q) + return -EBADMSG; + + *rindex = end; + + if (ret) + *ret = q; + + return 1; } static bool validate_nul(const char *s, size_t l) { @@ -1990,7 +2677,7 @@ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) { assert_not_reached("Unknown basic type..."); } - m->rindex = rindex; + m->rindex = rindex; break; } @@ -2186,6 +2873,7 @@ int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *con struct bus_container *c, *w; uint32_t *array_size = NULL; char *signature; + size_t before; int r; if (!m) @@ -2228,6 +2916,9 @@ int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *con if (!signature) return -ENOMEM; + c->saved_index = c->index; + before = m->rindex; + if (type == SD_BUS_TYPE_ARRAY) r = bus_message_enter_array(m, c, contents, &array_size); else if (type == SD_BUS_TYPE_VARIANT) @@ -2250,6 +2941,7 @@ int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *con w->signature = signature; w->index = 0; w->array_size = array_size; + w->before = before; w->begin = m->rindex; return 1; @@ -2284,6 +2976,28 @@ int sd_bus_message_exit_container(sd_bus_message *m) { return 1; } +static void message_quit_container(sd_bus_message *m) { + struct bus_container *c; + + assert(m); + assert(m->sealed); + assert(m->n_containers > 0); + + c = message_get_container(m); + + /* Undo seeks */ + assert(m->rindex >= c->before); + m->rindex = c->before; + + /* Free container */ + free(c->signature); + m->n_containers--; + + /* Correct index of new top-level container */ + c = message_get_container(m); + c->index = c->saved_index; +} + int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) { struct bus_container *c; int r; @@ -2415,7 +3129,7 @@ int sd_bus_message_rewind(sd_bus_message *m, int complete) { return -EPERM; if (complete) { - reset_containers(m); + message_reset_containers(m); m->rindex = 0; m->root_container.index = 0; @@ -2627,6 +3341,59 @@ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { return r; } +int sd_bus_message_read_array(sd_bus_message *m, char type, const void **ptr, size_t *size) { + struct bus_container *c; + void *p; + size_t sz; + ssize_t align; + int r; + + if (!m) + return -EINVAL; + if (!m->sealed) + return -EPERM; + if (!bus_type_is_trivial(type)) + return -EINVAL; + if (!ptr) + return -EINVAL; + if (!size) + return -EINVAL; + if (BUS_MESSAGE_NEED_BSWAP(m)) + return -ENOTSUP; + + align = bus_type_get_alignment(type); + if (align < 0) + return align; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r <= 0) + return r; + + c = message_get_container(m); + sz = BUS_MESSAGE_BSWAP32(m, *c->array_size); + + r = message_peek_body(m, &m->rindex, align, sz, &p); + if (r < 0) + goto fail; + if (r == 0) { + r = -EBADMSG; + goto fail; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + goto fail; + + *ptr = (const void*) p; + *size = sz; + + return 1; + +fail: + message_quit_container(m); + return r; +} + static int message_peek_fields( sd_bus_message *m, size_t *rindex, @@ -2638,7 +3405,7 @@ static int message_peek_fields( assert(rindex); assert(align > 0); - return buffer_peek(m->fields, BUS_MESSAGE_FIELDS_SIZE(m), rindex, align, nbytes, ret); + return buffer_peek(BUS_MESSAGE_FIELDS(m), BUS_MESSAGE_FIELDS_SIZE(m), rindex, align, nbytes, ret); } static int message_peek_field_uint32( @@ -3061,8 +3828,10 @@ int bus_message_parse_fields(sd_bus_message *m) { } int bus_message_seal(sd_bus_message *m, uint64_t serial) { - int r; + struct bus_body_part *part; size_t l, a; + unsigned i; + int r; assert(m); @@ -3072,6 +3841,9 @@ int bus_message_seal(sd_bus_message *m, uint64_t serial) { if (m->n_containers > 0) return -EBADMSG; + if (m->poisoned) + return -ESTALE; + /* If there's a non-trivial signature set, then add it in here */ if (!isempty(m->root_container.signature)) { r = message_append_field_signature(m, SD_BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL); @@ -3085,20 +3857,26 @@ int bus_message_seal(sd_bus_message *m, uint64_t serial) { return r; } + /* Add padding at the end of the fields part, since we know + * the body needs to start at an 8 byte alignment. We made + * sure we allocated enough space for this, so all we need to + * do here is to zero it out. */ l = BUS_MESSAGE_FIELDS_SIZE(m); a = ALIGN8(l) - l; - - if (a > 0) { - /* Add padding at the end, since we know the body - * needs to start at an 8 byte alignment. */ - void *p; - - p = message_extend_fields(m, 1, a); - if (!p) - return -ENOMEM; - - memset(p, 0, a); - m->header->fields_size -= a; + if (a > 0) + memset((uint8_t*) BUS_MESSAGE_FIELDS(m) + l, 0, a); + + /* If this is something we can send as memfd, then let's seal + the memfd now. Note that we can send memfds as payload only + for directed messages, and not for broadcasts. */ + if (m->destination && m->bus && m->bus->use_memfd) { + MESSAGE_FOREACH_PART(part, i, m) + if (part->memfd >= 0 && !part->sealed && (part->size > MEMFD_MIN_SIZE || m->bus->use_memfd < 0)) { + bus_body_part_unmap(part); + + if (ioctl(part->memfd, KDBUS_CMD_MEMFD_SEAL_SET, 1) >= 0) + part->sealed = true; + } } m->header->serial = serial; @@ -3148,7 +3926,8 @@ int bus_message_dump(sd_bus_message *m) { "\treply_serial=%u\n" "\terror.name=%s\n" "\terror.message=%s\n" - "\tsealed=%s\n", + "\tsealed=%s\n" + "\tn_body_parts=%u\n", m, m->n_ref, m->header->endian, @@ -3167,7 +3946,8 @@ int bus_message_dump(sd_bus_message *m) { m->reply_serial, strna(m->error.name), strna(m->error.message), - yes_no(m->sealed)); + yes_no(m->sealed), + m->n_body_parts); if (m->pid != 0) printf("\tpid=%lu\n", (unsigned long) m->pid); @@ -3381,6 +4161,8 @@ int bus_message_dump(sd_bus_message *m) { int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { size_t total; void *p, *e; + unsigned i; + struct bus_body_part *part; assert(m); assert(buffer); @@ -3392,17 +4174,9 @@ int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { if (!p) return -ENOMEM; - e = mempcpy(p, m->header, sizeof(*m->header)); - - if (m->fields) { - e = mempcpy(e, m->fields, m->header->fields_size); - - if (m->header->fields_size % 8 != 0) - e = mempset(e, 0, 8 - (m->header->fields_size % 8)); - } - - if (m->body) - e = mempcpy(e, m->body, m->header->body_size); + e = mempcpy(p, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + MESSAGE_FOREACH_PART(part, i, m) + e = mempcpy(e, part->data, part->size); assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p)); @@ -3474,7 +4248,22 @@ const char* bus_message_get_arg(sd_bus_message *m, unsigned i) { return t; } -int bus_header_size(struct bus_header *h, size_t *sum) { +bool bus_header_is_complete(struct bus_header *h, size_t size) { + size_t full; + + assert(h); + assert(size); + + if (size < sizeof(struct bus_header)) + return false; + + full = sizeof(struct bus_header) + + (h->endian == SD_BUS_NATIVE_ENDIAN ? h->fields_size : bswap_32(h->fields_size)); + + return size >= full; +} + +int bus_header_message_size(struct bus_header *h, size_t *sum) { size_t fs, bs; assert(h); @@ -3492,3 +4281,12 @@ int bus_header_size(struct bus_header *h, size_t *sum) { *sum = sizeof(struct bus_header) + ALIGN8(fs) + bs; return 0; } + +int bus_message_to_errno(sd_bus_message *m) { + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_ERROR) + return 0; + + return bus_error_to_errno(&m->error); +} diff --git a/src/libsystemd-bus/bus-message.h b/src/libsystemd-bus/bus-message.h index 9c0829c7fa..2fb11ea3b1 100644 --- a/src/libsystemd-bus/bus-message.h +++ b/src/libsystemd-bus/bus-message.h @@ -33,11 +33,12 @@ struct bus_container { char enclosing; + unsigned index, saved_index; + char *signature; - unsigned index; uint32_t *array_size; - size_t begin; + size_t before, begin; }; struct bus_header { @@ -50,9 +51,23 @@ struct bus_header { uint32_t fields_size; } _packed_; +struct bus_body_part { + struct bus_body_part *next; + void *data; + size_t size; + size_t mapped; + int memfd; + bool free_this:1; + bool munmap_this:1; + bool sealed:1; + bool is_zero:1; +}; + struct sd_bus_message { unsigned n_ref; + sd_bus *bus; + uint32_t reply_serial; const char *path; @@ -77,19 +92,21 @@ struct sd_bus_message { bool uid_valid:1; bool gid_valid:1; bool free_header:1; - bool free_fields:1; - bool free_body:1; bool free_kdbus:1; bool free_fds:1; + bool release_kdbus:1; + bool poisoned:1; struct bus_header *header; - void *fields; - void *body; - struct kdbus_msg *kdbus; + struct bus_body_part body; + struct bus_body_part *body_end; + unsigned n_body_parts; char *label; size_t rindex; + struct bus_body_part *cached_rindex_part; + size_t cached_rindex_part_begin; uint32_t n_fds; int *fds; @@ -97,9 +114,12 @@ struct sd_bus_message { struct bus_container root_container, *containers; unsigned n_containers; - struct iovec iovec[3]; + struct iovec *iovec; + struct iovec iovec_fixed[2]; unsigned n_iovec; + struct kdbus_msg *kdbus; + char *peeked_signature; usec_t timeout; @@ -159,6 +179,16 @@ static inline uint32_t BUS_MESSAGE_SIZE(sd_bus_message *m) { BUS_MESSAGE_BODY_SIZE(m); } +static inline uint32_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { + return + sizeof(struct bus_header) + + ALIGN8(BUS_MESSAGE_FIELDS_SIZE(m)); +} + +static inline void* BUS_MESSAGE_FIELDS(sd_bus_message *m) { + return (uint8_t*) m->header + sizeof(struct bus_header); +} + static inline void bus_message_unrefp(sd_bus_message **m) { sd_bus_message_unref(*m); } @@ -195,4 +225,17 @@ int bus_message_append_ap(sd_bus_message *m, const char *types, va_list ap); int bus_message_parse_fields(sd_bus_message *m); -int bus_header_size(struct bus_header *h, size_t *sum); +bool bus_header_is_complete(struct bus_header *h, size_t size); +int bus_header_message_size(struct bus_header *h, size_t *sum); + +struct bus_body_part *message_append_part(sd_bus_message *m); + +#define MESSAGE_FOREACH_PART(part, i, m) \ + for ((i) = 0, (part) = &(m)->body; (i) < (m)->n_body_parts; (i)++, (part) = (part)->next) + +int bus_body_part_map(struct bus_body_part *part); +void bus_body_part_unmap(struct bus_body_part *part); + +int bus_message_to_errno(sd_bus_message *m); + +int bus_message_new_synthetic_error(sd_bus *bus, uint64_t serial, const sd_bus_error *e, sd_bus_message **m); diff --git a/src/libsystemd-bus/bus-socket.c b/src/libsystemd-bus/bus-socket.c index 8a86b02c68..b60facb20f 100644 --- a/src/libsystemd-bus/bus-socket.c +++ b/src/libsystemd-bus/bus-socket.c @@ -58,7 +58,7 @@ static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { } } -static void append_iovec(sd_bus_message *m, const void *p, size_t sz) { +static int append_iovec(sd_bus_message *m, const void *p, size_t sz) { assert(m); assert(p); assert(sz > 0); @@ -66,22 +66,55 @@ static void append_iovec(sd_bus_message *m, const void *p, size_t sz) { m->iovec[m->n_iovec].iov_base = (void*) p; m->iovec[m->n_iovec].iov_len = sz; m->n_iovec++; + + return 0; } -static void bus_message_setup_iovec(sd_bus_message *m) { +static int bus_message_setup_iovec(sd_bus_message *m) { + struct bus_body_part *part; + unsigned n, i; + int r; + assert(m); assert(m->sealed); if (m->n_iovec > 0) - return; + return 0; + + assert(!m->iovec); + + n = 1 + m->n_body_parts; + if (n < ELEMENTSOF(m->iovec_fixed)) + m->iovec = m->iovec_fixed; + else { + m->iovec = new(struct iovec, n); + if (!m->iovec) { + r = -ENOMEM; + goto fail; + } + } + + r = append_iovec(m, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + if (r < 0) + goto fail; + + MESSAGE_FOREACH_PART(part, i, m) { + r = bus_body_part_map(part); + if (r < 0) + goto fail; - append_iovec(m, m->header, sizeof(*m->header)); + r = append_iovec(m, part->data, part->size); + if (r < 0) + goto fail; + } - if (m->fields) - append_iovec(m, m->fields, ALIGN8(m->header->fields_size)); + assert(n == m->n_iovec); - if (m->body) - append_iovec(m, m->body, m->header->body_size); + return 0; + +fail: + m->poisoned = true; + return r; } bool bus_socket_auth_needs_write(sd_bus *b) { @@ -148,7 +181,7 @@ static int bus_socket_auth_verify_client(sd_bus *b) { if (!e) return 0; - if (b->negotiate_fds) { + if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) { f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); if (!f) return 0; @@ -431,7 +464,7 @@ static int bus_socket_auth_verify_server(sd_bus *b) { r = bus_socket_auth_write_ok(b); } } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) { - if (b->auth == _BUS_AUTH_INVALID || !b->negotiate_fds) + if (b->auth == _BUS_AUTH_INVALID || !(b->hello_flags & KDBUS_HELLO_ACCEPT_FD)) r = bus_socket_auth_write(b, "ERROR\r\n"); else { b->can_fds = true; @@ -577,6 +610,8 @@ static int bus_socket_setup(sd_bus *b) { * socket, just in case. */ enable = !b->bus_client; setsockopt(b->input_fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)); + + enable = !b->bus_client && (b->hello_flags & KDBUS_HELLO_ATTACH_SECLABEL); setsockopt(b->input_fd, SOL_SOCKET, SO_PASSSEC, &enable, sizeof(enable)); /* Increase the buffers to a MB */ @@ -618,7 +653,7 @@ static int bus_socket_start_auth_client(sd_bus *b) { if (!b->auth_buffer) return -ENOMEM; - if (b->negotiate_fds) + if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; else auth_suffix = "\r\nBEGIN\r\n"; @@ -640,11 +675,11 @@ static int bus_socket_start_auth(sd_bus *b) { b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_DEFAULT_TIMEOUT; if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) - b->negotiate_fds = false; + b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; if (b->output_fd != b->input_fd) if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0) - b->negotiate_fds = false; + b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; if (b->is_server) return bus_socket_read_auth(b); @@ -749,6 +784,7 @@ int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { ssize_t k; size_t n; unsigned j; + int r; assert(bus); assert(m); @@ -758,7 +794,9 @@ int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { if (*idx >= BUS_MESSAGE_SIZE(m)) return 0; - bus_message_setup_iovec(m); + r = bus_message_setup_iovec(m); + if (r < 0) + return r; n = m->n_iovec * sizeof(struct iovec); iov = alloca(n); diff --git a/src/libsystemd-bus/bus-type.c b/src/libsystemd-bus/bus-type.c index 0557328085..6354c84f2c 100644 --- a/src/libsystemd-bus/bus-type.c +++ b/src/libsystemd-bus/bus-type.c @@ -92,6 +92,23 @@ bool bus_type_is_basic(char c) { return !!memchr(valid, c, sizeof(valid)); } +bool bus_type_is_trivial(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE + }; + + return !!memchr(valid, c, sizeof(valid)); +} + + bool bus_type_is_container(char c) { static const char valid[] = { SD_BUS_TYPE_ARRAY, diff --git a/src/libsystemd-bus/bus-type.h b/src/libsystemd-bus/bus-type.h index e261136084..122628c66b 100644 --- a/src/libsystemd-bus/bus-type.h +++ b/src/libsystemd-bus/bus-type.h @@ -29,6 +29,7 @@ bool bus_type_is_valid(char c); bool bus_type_is_valid_in_signature(char c); bool bus_type_is_basic(char c); +bool bus_type_is_trivial(char c); bool bus_type_is_container(char c); int bus_type_get_alignment(char c); int bus_type_get_size(char c); diff --git a/src/libsystemd-bus/kdbus.h b/src/libsystemd-bus/kdbus.h index db5e243c17..06c2c245f3 100644 --- a/src/libsystemd-bus/kdbus.h +++ b/src/libsystemd-bus/kdbus.h @@ -21,6 +21,18 @@ #endif #define KDBUS_IOC_MAGIC 0x95 +#define KDBUS_SRC_ID_KERNEL (0) +#define KDBUS_DST_ID_WELL_KNOWN_NAME (0) +#define KDBUS_MATCH_SRC_ID_ANY (~0ULL) +#define KDBUS_DST_ID_BROADCAST (~0ULL) + +/* Common first elements in a structure which are used to iterate over + * a list of elements. */ +#define KDBUS_PART_HEADER \ + struct { \ + __u64 size; \ + __u64 type; \ + } /* Message sent from kernel to userspace, when the owner or starter of * a well-known name changes */ @@ -60,24 +72,35 @@ struct kdbus_timestamp { __u64 realtime_ns; }; -#define KDBUS_SRC_ID_KERNEL (0) -#define KDBUS_DST_ID_WELL_KNOWN_NAME (0) -#define KDBUS_MATCH_SRC_ID_ANY (~0ULL) -#define KDBUS_DST_ID_BROADCAST (~0ULL) +struct kdbus_vec { + __u64 size; + union { + __u64 address; + __u64 offset; + }; +}; + +struct kdbus_memfd { + __u64 size; + int fd; + __u32 __pad; +}; /* Message Item Types */ enum { - KDBUS_MSG_NULL, + _KDBUS_MSG_NULL, /* Filled in by userspace */ - KDBUS_MSG_PAYLOAD, /* .data, inline memory */ KDBUS_MSG_PAYLOAD_VEC, /* .data_vec, reference to memory area */ - KDBUS_MSG_UNIX_FDS, /* .data_fds of file descriptors */ + KDBUS_MSG_PAYLOAD_OFF, /* .data_vec, reference to memory area */ + KDBUS_MSG_PAYLOAD_MEMFD, /* file descriptor of a special data file */ + KDBUS_MSG_FDS, /* .data_fds of file descriptors */ KDBUS_MSG_BLOOM, /* for broadcasts, carries bloom filter blob in .data */ KDBUS_MSG_DST_NAME, /* destination's well-known name, in .str */ + KDBUS_MSG_PRIORITY, /* queue priority for message */ /* Filled in by kernelspace */ - KDBUS_MSG_SRC_NAMES = 0x200,/* NUL separated string list with well-known names of source */ + KDBUS_MSG_SRC_NAMES = 0x400,/* NUL separated string list with well-known names of source */ KDBUS_MSG_TIMESTAMP, /* .timestamp */ KDBUS_MSG_SRC_CREDS, /* .creds */ KDBUS_MSG_SRC_PID_COMM, /* optional, in .str */ @@ -90,7 +113,7 @@ enum { KDBUS_MSG_SRC_AUDIT, /* .audit */ /* Special messages from kernel, consisting of one and only one of these data blocks */ - KDBUS_MSG_NAME_ADD = 0x400,/* .name_change */ + KDBUS_MSG_NAME_ADD = 0x800,/* .name_change */ KDBUS_MSG_NAME_REMOVE, /* .name_change */ KDBUS_MSG_NAME_CHANGE, /* .name_change */ KDBUS_MSG_ID_ADD, /* .id_change */ @@ -99,16 +122,6 @@ enum { KDBUS_MSG_REPLY_DEAD, /* dito */ }; -enum { - KDBUS_VEC_ALIGNED = 1 << 0, -}; - -struct kdbus_vec { - __u64 address; - __u64 size; - __u64 flags; -}; - /** * struct kdbus_item - chain of data blocks * @@ -116,8 +129,7 @@ struct kdbus_vec { * type: kdbus_item type of data */ struct kdbus_item { - __u64 size; - __u64 type; + KDBUS_PART_HEADER; union { /* inline data */ __u8 data[0]; @@ -137,6 +149,7 @@ struct kdbus_item { struct kdbus_timestamp timestamp; /* specific fields */ + struct kdbus_memfd memfd; int fds[0]; struct kdbus_manager_msg_name_change name_change; struct kdbus_manager_msg_id_change id_change; @@ -149,7 +162,7 @@ enum { }; enum { - KDBUS_PAYLOAD_NULL, + KDBUS_PAYLOAD_KERNEL, KDBUS_PAYLOAD_DBUS1 = 0x4442757356657231ULL, /* 'DBusVer1' */ KDBUS_PAYLOAD_GVARIANT = 0x4756617269616e74ULL, /* 'GVariant' */ }; @@ -180,13 +193,13 @@ struct kdbus_msg { }; enum { - KDBUS_POLICY_NULL, + _KDBUS_POLICY_NULL, KDBUS_POLICY_NAME, KDBUS_POLICY_ACCESS, }; enum { - KDBUS_POLICY_ACCESS_NULL, + _KDBUS_POLICY_ACCESS_NULL, KDBUS_POLICY_ACCESS_USER, KDBUS_POLICY_ACCESS_GROUP, KDBUS_POLICY_ACCESS_WORLD, @@ -198,23 +211,25 @@ enum { KDBUS_POLICY_OWN = 1 << 0, }; +struct kdbus_policy_access { + __u64 type; /* USER, GROUP, WORLD */ + __u64 bits; /* RECV, SEND, OWN */ + __u64 id; /* uid, gid, 0 */ +}; + +//FIXME: convert access to access[] struct kdbus_policy { - __u64 size; - __u64 type; /* NAME or ACCESS */ + KDBUS_PART_HEADER; union { char name[0]; - struct { - __u32 type; /* USER, GROUP, WORLD */ - __u32 bits; /* RECV, SEND, OWN */ - __u64 id; /* uid, gid, 0 */ - } access; + struct kdbus_policy_access access; }; }; +/* A series of KDBUS_POLICY_NAME, plus one or more KDBUS_POLICY_ACCESS */ struct kdbus_cmd_policy { __u64 size; - __u8 buffer[0]; /* a series of KDBUS_POLICY_NAME plus one or - * more KDBUS_POLICY_ACCESS each. */ + struct kdbus_policy policies[0]; }; /* Flags for struct kdbus_cmd_hello */ @@ -233,11 +248,6 @@ enum { KDBUS_HELLO_ATTACH_AUDIT = 1 << 16, }; -/* Items to append to struct kdbus_cmd_hello */ -enum { - KDBUS_HELLO_NULL, -}; - struct kdbus_cmd_hello { __u64 size; @@ -258,6 +268,7 @@ struct kdbus_cmd_hello { __u64 id; /* id assigned to this connection */ __u64 bloom_size; /* The bloom filter size chosen by the * bus owner */ + __u64 pool_size; /* maximum size of pool buffer */ struct kdbus_item items[0]; }; @@ -270,13 +281,11 @@ enum { /* Items to append to kdbus_cmd_{bus,ep,ns}_make */ enum { - KDBUS_MAKE_NULL, + _KDBUS_MAKE_NULL, KDBUS_MAKE_NAME, - KDBUS_MAKE_CGROUP, /* the cgroup hierarchy ID for which to attach - * cgroup membership paths * to messages. */ KDBUS_MAKE_CRED, /* allow translator services which connect * to the bus on behalf of somebody else, - * allow specifying the credentials of the + * allow specifiying the credentials of the * client to connect on behalf on. Needs * privileges */ }; @@ -293,7 +302,6 @@ struct kdbus_cmd_bus_make { * KDBUS_CMD_HELLO, later */ __u64 bloom_size; /* size of the bloom filter for this bus */ struct kdbus_item items[0]; - }; struct kdbus_cmd_ep_make { @@ -328,10 +336,11 @@ enum { KDBUS_NAME_IN_QUEUE = 1 << 16, }; +/* We allow (de)regestration of names of other peers */ struct kdbus_cmd_name { __u64 size; - __u64 name_flags; - __u64 id; /* We allow registration/deregestration of names of other peers */ + __u64 flags; + __u64 id; __u64 conn_flags; char name[0]; }; @@ -342,7 +351,7 @@ struct kdbus_cmd_names { }; enum { - KDBUS_NAME_INFO_ITEM_NULL, + _KDBUS_NAME_INFO_ITEM_NULL, KDBUS_NAME_INFO_ITEM_NAME, /* userspace → kernel */ KDBUS_NAME_INFO_ITEM_SECLABEL, /* kernel → userspace */ KDBUS_NAME_INFO_ITEM_AUDIT, /* kernel → userspace */ @@ -357,7 +366,7 @@ struct kdbus_cmd_name_info { }; enum { - KDBUS_MATCH_NULL, + _KDBUS_MATCH_NULL, KDBUS_MATCH_BLOOM, /* Matches a mask blob against KDBUS_MSG_BLOOM */ KDBUS_MATCH_SRC_NAME, /* Matches a name string against KDBUS_MSG_SRC_NAMES */ KDBUS_MATCH_NAME_ADD, /* Matches a name string against KDBUS_MSG_NAME_ADD */ @@ -377,7 +386,8 @@ struct kdbus_cmd_match { struct kdbus_cmd_monitor { __u64 id; /* We allow setting the monitor flag of other peers */ - unsigned int enabled; /* A boolean to enable/disable monitoring */ + unsigned int enable; /* A boolean to enable/disable monitoring */ + __u32 __pad; }; /* FD states: @@ -390,29 +400,37 @@ struct kdbus_cmd_monitor { * starter (via KDBUS_CMD_HELLO with KDBUS_CMD_HELLO_STARTER) * ep owner (via KDBUS_CMD_EP_MAKE) */ -enum kdbus_cmd { +enum { /* kdbus control node commands: require unset state */ - KDBUS_CMD_BUS_MAKE = _IOWR(KDBUS_IOC_MAGIC, 0x00, struct kdbus_cmd_bus_make), - KDBUS_CMD_NS_MAKE = _IOWR(KDBUS_IOC_MAGIC, 0x10, struct kdbus_cmd_ns_make), + KDBUS_CMD_BUS_MAKE = _IOW(KDBUS_IOC_MAGIC, 0x00, struct kdbus_cmd_bus_make), + KDBUS_CMD_NS_MAKE = _IOR(KDBUS_IOC_MAGIC, 0x10, struct kdbus_cmd_ns_make), /* kdbus ep node commands: require unset state */ - KDBUS_CMD_EP_MAKE = _IOWR(KDBUS_IOC_MAGIC, 0x20, struct kdbus_cmd_ep_make), + KDBUS_CMD_EP_MAKE = _IOW(KDBUS_IOC_MAGIC, 0x20, struct kdbus_cmd_ep_make), KDBUS_CMD_HELLO = _IOWR(KDBUS_IOC_MAGIC, 0x30, struct kdbus_cmd_hello), /* kdbus ep node commands: require connected state */ - KDBUS_CMD_MSG_SEND = _IOWR(KDBUS_IOC_MAGIC, 0x40, struct kdbus_msg), - KDBUS_CMD_MSG_RECV = _IOWR(KDBUS_IOC_MAGIC, 0x41, struct kdbus_msg), + KDBUS_CMD_MSG_SEND = _IOW(KDBUS_IOC_MAGIC, 0x40, struct kdbus_msg), + KDBUS_CMD_MSG_RECV = _IOR(KDBUS_IOC_MAGIC, 0x41, __u64 *), + KDBUS_CMD_MSG_RELEASE = _IOW(KDBUS_IOC_MAGIC, 0x42, __u64 *), KDBUS_CMD_NAME_ACQUIRE = _IOWR(KDBUS_IOC_MAGIC, 0x50, struct kdbus_cmd_name), - KDBUS_CMD_NAME_RELEASE = _IOWR(KDBUS_IOC_MAGIC, 0x51, struct kdbus_cmd_name), + KDBUS_CMD_NAME_RELEASE = _IOW(KDBUS_IOC_MAGIC, 0x51, struct kdbus_cmd_name), KDBUS_CMD_NAME_LIST = _IOWR(KDBUS_IOC_MAGIC, 0x52, struct kdbus_cmd_names), KDBUS_CMD_NAME_QUERY = _IOWR(KDBUS_IOC_MAGIC, 0x53, struct kdbus_cmd_name_info), - KDBUS_CMD_MATCH_ADD = _IOWR(KDBUS_IOC_MAGIC, 0x60, struct kdbus_cmd_match), - KDBUS_CMD_MATCH_REMOVE = _IOWR(KDBUS_IOC_MAGIC, 0x61, struct kdbus_cmd_match), - KDBUS_CMD_MONITOR = _IOWR(KDBUS_IOC_MAGIC, 0x62, struct kdbus_cmd_monitor), + KDBUS_CMD_MATCH_ADD = _IOW(KDBUS_IOC_MAGIC, 0x60, struct kdbus_cmd_match), + KDBUS_CMD_MATCH_REMOVE = _IOW(KDBUS_IOC_MAGIC, 0x61, struct kdbus_cmd_match), + KDBUS_CMD_MONITOR = _IOW(KDBUS_IOC_MAGIC, 0x62, struct kdbus_cmd_monitor), /* kdbus ep node commands: require ep owner state */ - KDBUS_CMD_EP_POLICY_SET = _IOWR(KDBUS_IOC_MAGIC, 0x70, struct kdbus_cmd_policy), + KDBUS_CMD_EP_POLICY_SET = _IOW(KDBUS_IOC_MAGIC, 0x70, struct kdbus_cmd_policy), + + /* kdbus memfd commands: */ + KDBUS_CMD_MEMFD_NEW = _IOR(KDBUS_IOC_MAGIC, 0x80, int *), + KDBUS_CMD_MEMFD_SIZE_GET = _IOR(KDBUS_IOC_MAGIC, 0x81, __u64 *), + KDBUS_CMD_MEMFD_SIZE_SET = _IOW(KDBUS_IOC_MAGIC, 0x82, __u64 *), + KDBUS_CMD_MEMFD_SEAL_GET = _IOR(KDBUS_IOC_MAGIC, 0x83, int *), + KDBUS_CMD_MEMFD_SEAL_SET = _IO(KDBUS_IOC_MAGIC, 0x84), }; #endif diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c index 7d6d848ec5..3f766fb519 100644 --- a/src/libsystemd-bus/sd-bus.c +++ b/src/libsystemd-bus/sd-bus.c @@ -26,6 +26,8 @@ #include <netdb.h> #include <sys/poll.h> #include <byteswap.h> +#include <sys/mman.h> +#include <pthread.h> #include "util.h" #include "macro.h" @@ -43,6 +45,18 @@ static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); +static void bus_close_fds(sd_bus *b) { + assert(b); + + if (b->input_fd >= 0) + close_nointr_nofail(b->input_fd); + + if (b->output_fd >= 0 && b->output_fd != b->input_fd) + close_nointr_nofail(b->output_fd); + + b->input_fd = b->output_fd = -1; +} + static void bus_free(sd_bus *b) { struct filter_callback *f; struct object_callback *c; @@ -50,7 +64,10 @@ static void bus_free(sd_bus *b) { assert(b); - sd_bus_close(b); + bus_close_fds(b); + + if (b->kdbus_buffer) + munmap(b->kdbus_buffer, KDBUS_POOL_SIZE); free(b->rbuffer); free(b->unique_name); @@ -86,9 +103,12 @@ static void bus_free(sd_bus *b) { } hashmap_free(b->object_callbacks); - bus_match_free(&b->match_callbacks); + bus_kernel_flush_memfd(b); + + assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0); + free(b); } @@ -102,10 +122,13 @@ int sd_bus_new(sd_bus **ret) { if (!r) return -ENOMEM; - r->n_ref = 1; + r->n_ref = REFCNT_INIT; r->input_fd = r->output_fd = -1; r->message_version = 1; - r->negotiate_fds = true; + r->hello_flags |= KDBUS_HELLO_ACCEPT_FD; + r->original_pid = getpid(); + + assert_se(pthread_mutex_init(&r->memfd_cache_mutex, NULL) == 0); /* We guarantee that wqueue always has space for at least one * entry */ @@ -128,6 +151,8 @@ int sd_bus_set_address(sd_bus *bus, const char *address) { return -EPERM; if (!address) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; a = strdup(address); if (!a) @@ -148,6 +173,8 @@ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) { return -EINVAL; if (output_fd < 0) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; bus->input_fd = input_fd; bus->output_fd = output_fd; @@ -165,6 +192,8 @@ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) { return -EINVAL; if (strv_isempty(argv)) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; p = strdup(path); if (!p) @@ -190,18 +219,106 @@ int sd_bus_set_bus_client(sd_bus *bus, int b) { return -EINVAL; if (bus->state != BUS_UNSET) return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; bus->bus_client = !!b; return 0; } -int sd_bus_set_negotiate_fds(sd_bus *bus, int b) { +int sd_bus_negotiate_fds(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ACCEPT_FD, b); + return 0; +} + +int sd_bus_negotiate_attach_comm(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_COMM, b); + return 0; +} + +int sd_bus_negotiate_attach_exe(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_EXE, b); + return 0; +} + +int sd_bus_negotiate_attach_cmdline(sd_bus *bus, int b) { if (!bus) return -EINVAL; if (bus->state != BUS_UNSET) return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; - bus->negotiate_fds = !!b; + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_CMDLINE, b); + return 0; +} + +int sd_bus_negotiate_attach_cgroup(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_CGROUP, b); + return 0; +} + +int sd_bus_negotiate_attach_caps(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_CAPS, b); + return 0; +} + +int sd_bus_negotiate_attach_selinux_context(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_SECLABEL, b); + return 0; +} + +int sd_bus_negotiate_attach_audit(sd_bus *bus, int b) { + if (!bus) + return -EINVAL; + if (bus->state != BUS_UNSET) + return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ATTACH_AUDIT, b); return 0; } @@ -212,6 +329,8 @@ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { return -EINVAL; if (bus->state != BUS_UNSET) return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; bus->is_server = !!b; bus->server_id = server_id; @@ -223,23 +342,25 @@ int sd_bus_set_anonymous(sd_bus *bus, int b) { return -EINVAL; if (bus->state != BUS_UNSET) return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; bus->anonymous_auth = !!b; return 0; } -static int hello_callback(sd_bus *bus, int error, sd_bus_message *reply, void *userdata) { +static int hello_callback(sd_bus *bus, sd_bus_message *reply, void *userdata) { const char *s; int r; assert(bus); assert(bus->state == BUS_HELLO); - - if (error != 0) - return -error; - assert(reply); + r = bus_message_to_errno(reply); + if (r < 0) + return r; + r = sd_bus_message_read(reply, "s", &s); if (r < 0) return r; @@ -811,6 +932,8 @@ int sd_bus_start(sd_bus *bus) { return -EINVAL; if (bus->state != BUS_UNSET) return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; bus->state = BUS_OPENING; @@ -920,22 +1043,29 @@ fail: void sd_bus_close(sd_bus *bus) { if (!bus) return; + if (bus->state == BUS_CLOSED) + return; + if (bus_pid_changed(bus)) + return; - if (bus->input_fd >= 0) - close_nointr_nofail(bus->input_fd); - if (bus->output_fd >= 0 && bus->output_fd != bus->input_fd) - close_nointr_nofail(bus->output_fd); + bus->state = BUS_CLOSED; - bus->input_fd = bus->output_fd = -1; + if (!bus->is_kernel) + bus_close_fds(bus); + + /* We'll leave the fd open in case this is a kernel bus, since + * there might still be memblocks around that reference this + * bus, and they might need to invoke the + * KDBUS_CMD_MSG_RELEASE ioctl on the fd when they are + * freed. */ } sd_bus *sd_bus_ref(sd_bus *bus) { if (!bus) return NULL; - assert(bus->n_ref > 0); + assert_se(REFCNT_INC(bus->n_ref) >= 2); - bus->n_ref++; return bus; } @@ -943,10 +1073,7 @@ sd_bus *sd_bus_unref(sd_bus *bus) { if (!bus) return NULL; - assert(bus->n_ref > 0); - bus->n_ref--; - - if (bus->n_ref <= 0) + if (REFCNT_DEC(bus->n_ref) <= 0) bus_free(bus); return NULL; @@ -955,8 +1082,10 @@ sd_bus *sd_bus_unref(sd_bus *bus) { int sd_bus_is_open(sd_bus *bus) { if (!bus) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; - return bus->state != BUS_UNSET && bus->input_fd >= 0; + return BUS_IS_OPEN(bus->state); } int sd_bus_can_send(sd_bus *bus, char type) { @@ -964,11 +1093,13 @@ int sd_bus_can_send(sd_bus *bus, char type) { if (!bus) return -EINVAL; - if (bus->output_fd < 0) + if (bus->state == BUS_UNSET) return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (type == SD_BUS_TYPE_UNIX_FD) { - if (!bus->negotiate_fds) + if (!(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) return 0; r = bus_ensure_running(bus); @@ -988,6 +1119,8 @@ int sd_bus_get_server_id(sd_bus *bus, sd_id128_t *server_id) { return -EINVAL; if (!server_id) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; r = bus_ensure_running(bus); if (r < 0) @@ -1015,9 +1148,6 @@ static int dispatch_wqueue(sd_bus *bus) { assert(bus); assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - if (bus->output_fd < 0) - return -ENOTCONN; - while (bus->wqueue_size > 0) { if (bus->is_kernel) @@ -1062,9 +1192,6 @@ static int dispatch_rqueue(sd_bus *bus, sd_bus_message **m) { assert(m); assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - if (bus->input_fd < 0) - return -ENOTCONN; - if (bus->rqueue_size > 0) { /* Dispatch a queued message */ @@ -1100,12 +1227,12 @@ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *serial) { if (!bus) return -EINVAL; - if (bus->state == BUS_UNSET) - return -ENOTCONN; - if (bus->output_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; if (!m) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; if (m->n_fds > 0) { r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); @@ -1213,9 +1340,7 @@ int sd_bus_send_with_reply( if (!bus) return -EINVAL; - if (bus->state == BUS_UNSET) - return -ENOTCONN; - if (bus->output_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; if (!m) return -EINVAL; @@ -1225,6 +1350,8 @@ int sd_bus_send_with_reply( return -EINVAL; if (m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; r = hashmap_ensure_allocated(&bus->reply_callbacks, uint64_hash_func, uint64_compare_func); if (r < 0) @@ -1280,6 +1407,8 @@ int sd_bus_send_with_reply_cancel(sd_bus *bus, uint64_t serial) { return -EINVAL; if (serial == 0) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; c = hashmap_remove(bus->reply_callbacks, &serial); if (!c) @@ -1297,11 +1426,8 @@ int bus_ensure_running(sd_bus *bus) { assert(bus); - if (bus->input_fd < 0) - return -ENOTCONN; - if (bus->state == BUS_UNSET) + if (bus->state == BUS_UNSET || bus->state == BUS_CLOSED) return -ENOTCONN; - if (bus->state == BUS_RUNNING) return 1; @@ -1334,9 +1460,7 @@ int sd_bus_send_with_reply_and_block( if (!bus) return -EINVAL; - if (bus->output_fd < 0) - return -ENOTCONN; - if (bus->state == BUS_UNSET) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; if (!m) return -EINVAL; @@ -1346,6 +1470,8 @@ int sd_bus_send_with_reply_and_block( return -EINVAL; if (bus_error_is_dirty(error)) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; r = bus_ensure_running(bus); if (r < 0) @@ -1452,10 +1578,12 @@ int sd_bus_send_with_reply_and_block( int sd_bus_get_fd(sd_bus *bus) { if (!bus) return -EINVAL; - if (bus->input_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; if (bus->input_fd != bus->output_fd) return -EPERM; + if (bus_pid_changed(bus)) + return -ECHILD; return bus->input_fd; } @@ -1465,10 +1593,10 @@ int sd_bus_get_events(sd_bus *bus) { if (!bus) return -EINVAL; - if (bus->state == BUS_UNSET) - return -ENOTCONN; - if (bus->input_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (bus->state == BUS_OPENING) flags |= POLLOUT; @@ -1496,10 +1624,10 @@ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { return -EINVAL; if (!timeout_usec) return -EINVAL; - if (bus->state == BUS_UNSET) - return -ENOTCONN; - if (bus->input_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (bus->state == BUS_AUTHENTICATING) { *timeout_usec = bus->auth_timeout; @@ -1522,6 +1650,7 @@ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { } static int process_timeout(sd_bus *bus) { + _cleanup_bus_message_unref_ sd_bus_message* m = NULL; struct reply_callback *c; usec_t n; int r; @@ -1536,10 +1665,18 @@ static int process_timeout(sd_bus *bus) { if (c->timeout > n) return 0; + r = bus_message_new_synthetic_error( + bus, + c->serial, + &SD_BUS_ERROR_MAKE("org.freedesktop.DBus.Error.Timeout", "Timed out"), + &m); + if (r < 0) + return r; + assert_se(prioq_pop(bus->reply_callbacks_prioq) == c); hashmap_remove(bus->reply_callbacks, &c->serial); - r = c->callback(bus, ETIMEDOUT, NULL, c->userdata); + r = c->callback(bus, m, c->userdata); free(c); return r < 0 ? r : 1; @@ -1589,7 +1726,7 @@ static int process_reply(sd_bus *bus, sd_bus_message *m) { if (r < 0) return r; - r = c->callback(bus, 0, m, c->userdata); + r = c->callback(bus, m, c->userdata); free(c); return r; @@ -1620,7 +1757,7 @@ static int process_filter(sd_bus *bus, sd_bus_message *m) { if (r < 0) return r; - r = l->callback(bus, 0, m, l->userdata); + r = l->callback(bus, m, l->userdata); if (r != 0) return r; @@ -1640,7 +1777,7 @@ static int process_match(sd_bus *bus, sd_bus_message *m) { do { bus->match_callbacks_modified = false; - r = bus_match_run(bus, &bus->match_callbacks, 0, m); + r = bus_match_run(bus, &bus->match_callbacks, m); if (r != 0) return r; @@ -1733,7 +1870,7 @@ static int process_object(sd_bus *bus, sd_bus_message *m) { if (r < 0) return r; - r = c->callback(bus, 0, m, c->userdata); + r = c->callback(bus, m, c->userdata); if (r != 0) return r; @@ -1763,7 +1900,7 @@ static int process_object(sd_bus *bus, sd_bus_message *m) { if (r < 0) return r; - r = c->callback(bus, 0, m, c->userdata); + r = c->callback(bus, m, c->userdata); if (r != 0) return r; @@ -1986,8 +2123,8 @@ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { if (!bus) return -EINVAL; - if (bus->input_fd < 0) - return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; /* We don't allow recursively invoking sd_bus_process(). */ if (bus->processing) @@ -1996,6 +2133,7 @@ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { switch (bus->state) { case BUS_UNSET: + case BUS_CLOSED: return -ENOTCONN; case BUS_OPENING: @@ -2036,7 +2174,7 @@ static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { assert(bus); - if (bus->input_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; e = sd_bus_get_events(bus); @@ -2082,10 +2220,11 @@ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { if (!bus) return -EINVAL; - if (bus->state == BUS_UNSET) - return -ENOTCONN; - if (bus->input_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + if (bus->rqueue_size > 0) return 0; @@ -2097,10 +2236,10 @@ int sd_bus_flush(sd_bus *bus) { if (!bus) return -EINVAL; - if (bus->state == BUS_UNSET) - return -ENOTCONN; - if (bus->output_fd < 0) + if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = bus_ensure_running(bus); if (r < 0) @@ -2130,6 +2269,8 @@ int sd_bus_add_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *user return -EINVAL; if (!callback) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; f = new0(struct filter_callback, 1); if (!f) @@ -2149,6 +2290,8 @@ int sd_bus_remove_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *u return -EINVAL; if (!callback) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; LIST_FOREACH(callbacks, f, bus->filter_callbacks) { if (f->callback == callback && f->userdata == userdata) { @@ -2178,6 +2321,8 @@ static int bus_add_object( return -EINVAL; if (!callback) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; r = hashmap_ensure_allocated(&bus->object_callbacks, string_hash_func, string_compare_func); if (r < 0) @@ -2223,6 +2368,8 @@ static int bus_remove_object( return -EINVAL; if (!callback) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; c = hashmap_get(bus->object_callbacks, path); if (!c) @@ -2257,51 +2404,68 @@ int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handl } int sd_bus_add_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + uint64_t cookie = 0; int r = 0; if (!bus) return -EINVAL; if (!match) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + goto finish; if (bus->bus_client) { - r = bus_add_match_internal(bus, match); + cookie = ++bus->match_cookie; + + r = bus_add_match_internal(bus, match, components, n_components, cookie); if (r < 0) - return r; + goto finish; } - if (callback) { - bus->match_callbacks_modified = true; - r = bus_match_add(&bus->match_callbacks, match, callback, userdata, NULL); - if (r < 0) { - - if (bus->bus_client) - bus_remove_match_internal(bus, match); - } + bus->match_callbacks_modified = true; + r = bus_match_add(&bus->match_callbacks, components, n_components, callback, userdata, cookie, NULL); + if (r < 0) { + if (bus->bus_client) + bus_remove_match_internal(bus, match, cookie); } +finish: + bus_match_parse_free(components, n_components); return r; } int sd_bus_remove_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; int r = 0, q = 0; + uint64_t cookie = 0; if (!bus) return -EINVAL; if (!match) return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + return r; + + bus->match_callbacks_modified = true; + r = bus_match_remove(&bus->match_callbacks, components, n_components, callback, userdata, &cookie); if (bus->bus_client) - r = bus_remove_match_internal(bus, match); + q = bus_remove_match_internal(bus, match, cookie); - if (callback) { - bus->match_callbacks_modified = true; - q = bus_match_remove(&bus->match_callbacks, match, callback, userdata); - } + bus_match_parse_free(components, n_components); - if (r < 0) - return r; - return q; + return r < 0 ? r : q; } int sd_bus_emit_signal( @@ -2317,6 +2481,10 @@ int sd_bus_emit_signal( if (!bus) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = sd_bus_message_new_signal(bus, path, interface, member, &m); if (r < 0) @@ -2346,7 +2514,12 @@ int sd_bus_call_method( int r; if (!bus) + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; r = sd_bus_message_new_method_call(bus, destination, path, interface, member, &m); if (r < 0) @@ -2378,6 +2551,10 @@ int sd_bus_reply_method_return( return -EPERM; if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) return 0; @@ -2413,6 +2590,10 @@ int sd_bus_reply_method_error( return -EINVAL; if (!sd_bus_error_is_set(e)) return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) return 0; @@ -2423,3 +2604,12 @@ int sd_bus_reply_method_error( return sd_bus_send(bus, m, NULL); } + +bool bus_pid_changed(sd_bus *bus) { + assert(bus); + + /* We don't support people creating a bus connection and + * keeping it around over a fork(). Let's complain. */ + + return bus->original_pid != getpid(); +} diff --git a/src/libsystemd-bus/sd-memfd.c b/src/libsystemd-bus/sd-memfd.c new file mode 100644 index 0000000000..bd14da3a70 --- /dev/null +++ b/src/libsystemd-bus/sd-memfd.c @@ -0,0 +1,231 @@ +/*-*- 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 <stdio.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +#include "util.h" +#include "kdbus.h" + +#include "sd-memfd.h" + +struct sd_memfd { + int fd; + FILE *f; +}; + +int sd_memfd_new(sd_memfd **m) { + _cleanup_close_ int kdbus = -1; + sd_memfd *n; + int fd; + + if (!m) + return -EINVAL; + + kdbus = open("/dev/kdbus/control", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (kdbus < 0) + return -errno; + + if (ioctl(kdbus, KDBUS_CMD_MEMFD_NEW, &fd) < 0) + return -errno; + + n = new0(struct sd_memfd, 1); + if (!n) + return -ENOMEM; + + n->fd = fd; + *m = n; + return 0; +} + +int sd_memfd_make(int fd, sd_memfd **m) { + sd_memfd *n; + uint64_t sz; + + if (!m) + return -EINVAL; + if (fd < 0) + return -EINVAL; + + /* Check if this is a valid memfd */ + if (ioctl(fd, KDBUS_CMD_MEMFD_SIZE_GET, &sz) < 0) + return -ENOTTY; + + n = new0(struct sd_memfd, 1); + if (!n) + return -ENOMEM; + + n->fd = fd; + *m = n; + + return 0; +} + +void sd_memfd_free(sd_memfd *m) { + if (!m) + return; + + if (m->f) + fclose(m->f); + else + close_nointr_nofail(m->fd); + + free(m); +} + +int sd_memfd_get_fd(sd_memfd *m) { + if (!m) + return -EINVAL; + + return m->fd; +} + +int sd_memfd_get_file(sd_memfd *m, FILE **f) { + if (!m) + return -EINVAL; + if (!f) + return -EINVAL; + + if (!m->f) { + m->f = fdopen(m->fd, "r+"); + if (!m->f) + return -errno; + } + + *f = m->f; + return 0; +} + +int sd_memfd_dup_fd(sd_memfd *m) { + int fd; + + if (!m) + return -EINVAL; + + fd = fcntl(m->fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return -errno; + + return fd; +} + +int sd_memfd_map(sd_memfd *m, uint64_t offset, size_t size, void **p) { + void *q; + int sealed; + + if (!m) + return -EINVAL; + if (size <= 0) + return -EINVAL; + if (!p) + return -EINVAL; + + sealed = sd_memfd_get_sealed(m); + if (sealed < 0) + return sealed; + + q = mmap(NULL, size, sealed ? PROT_READ : PROT_READ|PROT_WRITE, MAP_SHARED, m->fd, offset); + if (q == MAP_FAILED) + return -errno; + + *p = q; + return 0; +} + +int sd_memfd_set_sealed(sd_memfd *m, int b) { + int r; + + if (!m) + return -EINVAL; + + r = ioctl(m->fd, KDBUS_CMD_MEMFD_SEAL_SET, b); + if (r < 0) + return -errno; + + return 0; +} + +int sd_memfd_get_sealed(sd_memfd *m) { + int r, b; + + if (!m) + return -EINVAL; + + r = ioctl(m->fd, KDBUS_CMD_MEMFD_SEAL_GET, &b); + if (r < 0) + return -errno; + + return !!b; +} + +int sd_memfd_get_size(sd_memfd *m, uint64_t *sz) { + int r; + + if (!m) + return -EINVAL; + if (!sz) + return -EINVAL; + + r = ioctl(m->fd, KDBUS_CMD_MEMFD_SIZE_GET, sz); + if (r < 0) + return -errno; + + return r; +} + +int sd_memfd_set_size(sd_memfd *m, uint64_t sz) { + int r; + + if (!m) + return -EINVAL; + + r = ioctl(m->fd, KDBUS_CMD_MEMFD_SIZE_SET, &sz); + if (r < 0) + return -errno; + + return r; +} + +int sd_memfd_new_and_map(sd_memfd **m, size_t sz, void **p) { + sd_memfd *n; + int r; + + r = sd_memfd_new(&n); + if (r < 0) + return r; + + r = sd_memfd_set_size(n, sz); + if (r < 0) { + sd_memfd_free(n); + return r; + } + + r = sd_memfd_map(n, 0, sz, p); + if (r < 0) { + sd_memfd_free(n); + return r; + } + + *m = n; + return 0; +} diff --git a/src/libsystemd-bus/test-bus-chat.c b/src/libsystemd-bus/test-bus-chat.c index f457c8f88a..f308eddbb0 100644 --- a/src/libsystemd-bus/test-bus-chat.c +++ b/src/libsystemd-bus/test-bus-chat.c @@ -35,17 +35,17 @@ #include "bus-match.h" #include "bus-internal.h" -static int match_callback(sd_bus *bus, int error, sd_bus_message *m, void *userdata) { +static int match_callback(sd_bus *bus, sd_bus_message *m, void *userdata) { log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m))); return 0; } -static int object_callback(sd_bus *bus, int error, sd_bus_message *m, void *userdata) { +static int object_callback(sd_bus *bus, sd_bus_message *m, void *userdata) { int r; assert(bus); - if (error != 0) + if (sd_bus_message_is_method_error(m, NULL)) return 0; if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) { @@ -356,10 +356,10 @@ finish: return INT_TO_PTR(r); } -static int quit_callback(sd_bus *b, int ret, sd_bus_message *m, void *userdata) { +static int quit_callback(sd_bus *b, sd_bus_message *m, void *userdata) { bool *x = userdata; - log_error("Quit callback: %s", strerror(ret)); + log_error("Quit callback: %s", strerror(bus_message_to_errno(m))); *x = 1; return 1; diff --git a/src/libsystemd-bus/test-bus-kernel-benchmark.c b/src/libsystemd-bus/test-bus-kernel-benchmark.c new file mode 100644 index 0000000000..2e84cd9244 --- /dev/null +++ b/src/libsystemd-bus/test-bus-kernel-benchmark.c @@ -0,0 +1,302 @@ +/*-*- 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 <ctype.h> +#include <sys/wait.h> + +#include "util.h" +#include "log.h" +#include "time-util.h" + +#include "sd-bus.h" +#include "bus-message.h" +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-internal.h" + +#define MAX_SIZE (4*1024*1024) + +static usec_t arg_loop_usec = 100 * USEC_PER_MSEC; + +static void server(sd_bus *b, size_t *result) { + int r; + + for (;;) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + + r = sd_bus_process(b, &m); + assert_se(r >= 0); + + if (r == 0) + assert_se(sd_bus_wait(b, (usec_t) -1) >= 0); + if (!m) + continue; + + if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping")) + assert_se(sd_bus_reply_method_return(b, m, NULL) >= 0); + else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) { + const void *p; + size_t sz; + + /* Make sure the mmap is mapped */ + assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0); + + assert_se(sd_bus_reply_method_return(b, m, NULL) >= 0); + } else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) { + uint64_t res; + assert_se(sd_bus_message_read(m, "t", &res) > 0); + + *result = res; + return; + + } else + assert_not_reached("Unknown method"); + } +} + +static void transaction(sd_bus *b, size_t sz) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + uint8_t *p; + + assert_se(sd_bus_message_new_method_call(b, ":1.1", "/", "benchmark.server", "Work", &m) >= 0); + assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0); + + memset(p, 0x80, sz); + + assert_se(sd_bus_send_with_reply_and_block(b, m, 0, NULL, &reply) >= 0); +} + +static void client_bisect(const char *address) { + _cleanup_bus_message_unref_ sd_bus_message *x = NULL; + size_t lsize, rsize, csize; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + assert_se(sd_bus_call_method(b, ":1.1", "/", "benchmark.server", "Ping", NULL, NULL, NULL) >= 0); + + lsize = 1; + rsize = MAX_SIZE; + + printf("SIZE\tCOPY\tMEMFD\n"); + + for (;;) { + usec_t t; + unsigned n_copying, n_memfd; + + csize = (lsize + rsize) / 2; + + if (csize <= lsize) + break; + + if (csize <= 0) + break; + + printf("%zu\t", csize); + + b->use_memfd = 0; + + t = now(CLOCK_MONOTONIC); + for (n_copying = 0;; n_copying++) { + transaction(b, csize); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); + + b->use_memfd = -1; + + t = now(CLOCK_MONOTONIC); + for (n_memfd = 0;; n_memfd++) { + transaction(b, csize); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); + + if (n_copying == n_memfd) + break; + + if (n_copying > n_memfd) + lsize = csize; + else + rsize = csize; + } + + b->use_memfd = 1; + assert_se(sd_bus_message_new_method_call(b, ":1.1", "/", "benchmark.server", "Exit", &x) >= 0); + assert_se(sd_bus_message_append(x, "t", csize) >= 0); + assert_se(sd_bus_send(b, x, NULL) >= 0); + + sd_bus_unref(b); +} + +static void client_chart(const char *address) { + _cleanup_bus_message_unref_ sd_bus_message *x = NULL; + size_t csize; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + assert_se(sd_bus_call_method(b, ":1.1", "/", "benchmark.server", "Ping", NULL, NULL, NULL) >= 0); + + printf("SIZE\tCOPY\tMEMFD\n"); + + for (csize = 1; csize <= MAX_SIZE; csize *= 2) { + usec_t t; + unsigned n_copying, n_memfd; + + printf("%zu\t", csize); + + b->use_memfd = 0; + + t = now(CLOCK_MONOTONIC); + for (n_copying = 0;; n_copying++) { + transaction(b, csize); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + + printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); + + b->use_memfd = -1; + + t = now(CLOCK_MONOTONIC); + for (n_memfd = 0;; n_memfd++) { + transaction(b, csize); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + + printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); + } + + b->use_memfd = 1; + assert_se(sd_bus_message_new_method_call(b, ":1.1", "/", "benchmark.server", "Exit", &x) >= 0); + assert_se(sd_bus_message_append(x, "t", csize) >= 0); + assert_se(sd_bus_send(b, x, NULL) >= 0); + + sd_bus_unref(b); +} + +int main(int argc, char *argv[]) { + enum { + MODE_BISECT, + MODE_CHART, + } mode = MODE_BISECT; + int i; + _cleanup_free_ char *bus_name = NULL, *address = NULL; + _cleanup_close_ int bus_ref = -1; + cpu_set_t cpuset; + size_t result; + sd_bus *b; + pid_t pid; + int r; + + log_set_max_level(LOG_DEBUG); + + for (i = 1; i < argc; i++) { + if (streq(argv[i], "chart")) { + mode = MODE_CHART; + continue; + } + + assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0); + } + + assert_se(arg_loop_usec > 0); + + bus_ref = bus_kernel_create("deine-mutter", &bus_name); + if (bus_ref == -ENOENT) + exit(EXIT_TEST_SKIP); + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + sync(); + setpriority(PRIO_PROCESS, 0, -19); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + + close_nointr_nofail(bus_ref); + sd_bus_unref(b); + + switch (mode) { + case MODE_BISECT: + client_bisect(address); + break; + + case MODE_CHART: + client_chart(address); + break; + } + + _exit(0); + } + + CPU_ZERO(&cpuset); + CPU_SET(1, &cpuset); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + + server(b, &result); + + if (mode == MODE_BISECT) + printf("Copying/memfd are equally fast at %zu bytes\n", result); + + assert_se(waitpid(pid, NULL, 0) == pid); + + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-kernel-bloom.c b/src/libsystemd-bus/test-bus-kernel-bloom.c new file mode 100644 index 0000000000..5445d3488f --- /dev/null +++ b/src/libsystemd-bus/test-bus-kernel-bloom.c @@ -0,0 +1,112 @@ +/*-*- 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 "log.h" + +#include "sd-bus.h" +#include "bus-message.h" +#include "bus-error.h" +#include "bus-kernel.h" + +static void test_one( + const char *path, + const char *interface, + const char *member, + const char *arg0, + const char *match, + bool good) { + + _cleanup_close_ int bus_ref = -1; + _cleanup_free_ char *bus_name = NULL, *address = NULL; + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + sd_bus *a, *b; + int r; + + bus_ref = bus_kernel_create("deine-mutter", &bus_name); + if (bus_ref == -ENOENT) + exit(EXIT_TEST_SKIP); + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + log_debug("match"); + r = sd_bus_add_match(b, match, NULL, NULL); + assert_se(r >= 0); + + log_debug("signal"); + r = sd_bus_emit_signal(a, path, interface, member, "s", arg0); + assert_se(r >= 0); + + r = sd_bus_process(b, &m); + assert_se(r >= 0 && (good == !!m)); + + sd_bus_unref(a); + sd_bus_unref(b); +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar/waldo/tuut'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "interface='waldo.com'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "member='Piep'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "member='Pi_ep'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "arg0='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "arg0='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar2'", false); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path='/foo/bar/waldo/quux'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path_namespace='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path_namespace='/foo/bar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path_namespace='/foo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path_namespace='/'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", "foobar", "path_namespace='/quux'", false); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-kernel.c b/src/libsystemd-bus/test-bus-kernel.c index 1095e57e42..680dcde5b4 100644 --- a/src/libsystemd-bus/test-bus-kernel.c +++ b/src/libsystemd-bus/test-bus-kernel.c @@ -22,6 +22,7 @@ #include <fcntl.h> #include "util.h" +#include "log.h" #include "sd-bus.h" #include "bus-message.h" @@ -36,6 +37,8 @@ int main(int argc, char *argv[]) { sd_bus *a, *b; int r, pipe_fds[2]; + log_set_max_level(LOG_DEBUG); + bus_ref = bus_kernel_create("deine-mutter", &bus_name); if (bus_ref == -ENOENT) return EXIT_TEST_SKIP; @@ -57,6 +60,22 @@ int main(int argc, char *argv[]) { r = sd_bus_set_address(b, address); assert_se(r >= 0); + assert_se(sd_bus_negotiate_attach_comm(a, 1) >= 0); + assert_se(sd_bus_negotiate_attach_exe(a, 1) >= 0); + assert_se(sd_bus_negotiate_attach_cmdline(a, 1) >= 0); + assert_se(sd_bus_negotiate_attach_cgroup(a, 1) >= 0); + assert_se(sd_bus_negotiate_attach_caps(a, 1) >= 0); + assert_se(sd_bus_negotiate_attach_selinux_context(a, 1) >= 0); + assert_se(sd_bus_negotiate_attach_audit(a, 1) >= 0); + + assert_se(sd_bus_negotiate_attach_comm(b, 1) >= 0); + assert_se(sd_bus_negotiate_attach_exe(b, 1) >= 0); + assert_se(sd_bus_negotiate_attach_cmdline(b, 1) >= 0); + assert_se(sd_bus_negotiate_attach_cgroup(b, 1) >= 0); + assert_se(sd_bus_negotiate_attach_caps(b, 1) >= 0); + assert_se(sd_bus_negotiate_attach_selinux_context(b, 1) >= 0); + assert_se(sd_bus_negotiate_attach_audit(b, 1) >= 0); + r = sd_bus_start(a); assert_se(r >= 0); @@ -73,19 +92,8 @@ int main(int argc, char *argv[]) { printf("unique b: %s\n", ub); - { - //FIXME: - struct kdbus_cmd_match cmd_match; - - cmd_match.size = sizeof(cmd_match); - cmd_match.src_id = KDBUS_MATCH_SRC_ID_ANY; - - r = ioctl(sd_bus_get_fd(a), KDBUS_CMD_MATCH_ADD, &cmd_match); - assert_se(r >= 0); - - r = ioctl(sd_bus_get_fd(b), KDBUS_CMD_MATCH_ADD, &cmd_match); - assert_se(r >= 0); - } + r = sd_bus_add_match(b, "interface='waldo.com',member='Piep'", NULL, NULL); + assert_se(r >= 0); r = sd_bus_emit_signal(a, "/foo/bar/waldo", "waldo.com", "Piep", "sss", "I am a string", "/this/is/a/path", "and.this.a.domain.name"); assert_se(r >= 0); diff --git a/src/libsystemd-bus/test-bus-marshal.c b/src/libsystemd-bus/test-bus-marshal.c index 20ae723fbe..ef1a77f5fc 100644 --- a/src/libsystemd-bus/test-bus-marshal.c +++ b/src/libsystemd-bus/test-bus-marshal.c @@ -43,6 +43,8 @@ int main(int argc, char *argv[]) { void *buffer = NULL; size_t sz; char *h; + const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array; + char *s; r = sd_bus_message_new_method_call(NULL, "foobar.waldo", "/", "foobar.waldo", "Piep", &m); assert_se(r >= 0); @@ -77,6 +79,13 @@ int main(int argc, char *argv[]) { r = sd_bus_message_close_container(m); assert_se(r >= 0); + r = sd_bus_message_append_string_space(m, 5, &s); + assert_se(r >= 0); + strcpy(s, "hallo"); + + r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array)); + assert_se(r >= 0); + r = bus_message_seal(m, 4711); assert_se(r >= 0); @@ -168,6 +177,15 @@ int main(int argc, char *argv[]) { assert_se(streq(x, "foobar")); assert_se(streq(y, "waldo")); + r = sd_bus_message_read_basic(m, 's', &s); + assert_se(r > 0); + assert_se(streq(s, "hallo")); + + r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz); + assert_se(r > 0); + assert_se(sz == sizeof(integer_array)); + assert_se(memcmp(integer_array, return_array, sz) == 0); + r = sd_bus_message_peek_type(m, NULL, NULL); assert_se(r == 0); diff --git a/src/libsystemd-bus/test-bus-match.c b/src/libsystemd-bus/test-bus-match.c index 9cf994009d..db977f726e 100644 --- a/src/libsystemd-bus/test-bus-match.c +++ b/src/libsystemd-bus/test-bus-match.c @@ -30,7 +30,7 @@ static bool mask[32]; -static int filter(sd_bus *b, int ret, sd_bus_message *m, void *userdata) { +static int filter(sd_bus *b, sd_bus_message *m, void *userdata) { log_info("Ran %i", PTR_TO_INT(userdata)); mask[PTR_TO_INT(userdata)] = true; return 0; @@ -55,6 +55,36 @@ static bool mask_contains(unsigned a[], unsigned n) { return true; } +static int match_add(struct bus_match_node *root, const char *match, int value) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + int r; + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + return r; + + r = bus_match_add(root, components, n_components, filter, INT_TO_PTR(value), 0, NULL); + bus_match_parse_free(components, n_components); + + return r; +} + +static int match_remove(struct bus_match_node *root, const char *match, int value) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + int r; + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + return r; + + r = bus_match_remove(root, components, n_components, filter, INT_TO_PTR(value), 0); + bus_match_parse_free(components, n_components); + + return r; +} + int main(int argc, char *argv[]) { struct bus_match_node root; _cleanup_bus_message_unref_ sd_bus_message *m = NULL; @@ -63,20 +93,20 @@ int main(int argc, char *argv[]) { zero(root); root.type = BUS_MATCH_ROOT; - assert_se(bus_match_add(&root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(1), NULL) >= 0); - assert_se(bus_match_add(&root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(2), NULL) >= 0); - assert_se(bus_match_add(&root, "arg3='test',sender='foo',type='signal',interface='bar',", filter, INT_TO_PTR(3), NULL) >= 0); - assert_se(bus_match_add(&root, "arg3='test',sender='foo',type='method_call',interface='bar',", filter, INT_TO_PTR(4), NULL) >= 0); - assert_se(bus_match_add(&root, "", filter, INT_TO_PTR(5), NULL) >= 0); - assert_se(bus_match_add(&root, "interface='quux'", filter, INT_TO_PTR(6), NULL) >= 0); - assert_se(bus_match_add(&root, "interface='bar'", filter, INT_TO_PTR(7), NULL) >= 0); - assert_se(bus_match_add(&root, "member='waldo',path='/foo/bar'", filter, INT_TO_PTR(8), NULL) >= 0); - assert_se(bus_match_add(&root, "path='/foo/bar'", filter, INT_TO_PTR(9), NULL) >= 0); - assert_se(bus_match_add(&root, "path_namespace='/foo'", filter, INT_TO_PTR(10), NULL) >= 0); - assert_se(bus_match_add(&root, "path_namespace='/foo/quux'", filter, INT_TO_PTR(11), NULL) >= 0); - assert_se(bus_match_add(&root, "arg1='two'", filter, INT_TO_PTR(12), NULL) >= 0); - assert_se(bus_match_add(&root, "member='waldo',arg2path='/prefix/'", filter, INT_TO_PTR(13), NULL) >= 0); - assert_se(bus_match_add(&root, "member='waldo',path='/foo/bar',arg3namespace='prefix'", filter, INT_TO_PTR(14), NULL) >= 0); + assert_se(match_add(&root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar',", 1) >= 0); + assert_se(match_add(&root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar',", 2) >= 0); + assert_se(match_add(&root, "arg3='test',sender='foo',type='signal',interface='bar',", 3) >= 0); + assert_se(match_add(&root, "arg3='test',sender='foo',type='method_call',interface='bar',", 4) >= 0); + assert_se(match_add(&root, "", 5) >= 0); + assert_se(match_add(&root, "interface='quux'", 6) >= 0); + assert_se(match_add(&root, "interface='bar'", 7) >= 0); + assert_se(match_add(&root, "member='waldo',path='/foo/bar'", 8) >= 0); + assert_se(match_add(&root, "path='/foo/bar'", 9) >= 0); + assert_se(match_add(&root, "path_namespace='/foo'", 10) >= 0); + assert_se(match_add(&root, "path_namespace='/foo/quux'", 11) >= 0); + assert_se(match_add(&root, "arg1='two'", 12) >= 0); + assert_se(match_add(&root, "member='waldo',arg2path='/prefix/'", 13) >= 0); + assert_se(match_add(&root, "member='waldo',path='/foo/bar',arg3namespace='prefix'", 14) >= 0); bus_match_dump(&root, 0); @@ -85,17 +115,17 @@ int main(int argc, char *argv[]) { assert_se(bus_message_seal(m, 1) >= 0); zero(mask); - assert_se(bus_match_run(NULL, &root, 0, m) == 0); + assert_se(bus_match_run(NULL, &root, m) == 0); assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14 }, 8)); - assert_se(bus_match_remove(&root, "member='waldo',path='/foo/bar'", filter, INT_TO_PTR(8)) > 0); - assert_se(bus_match_remove(&root, "arg2path='/prefix/',member='waldo'", filter, INT_TO_PTR(13)) > 0); - assert_se(bus_match_remove(&root, "interface='barxx'", filter, INT_TO_PTR(7)) == 0); + assert_se(match_remove(&root, "member='waldo',path='/foo/bar'", 8) > 0); + assert_se(match_remove(&root, "arg2path='/prefix/',member='waldo'", 13) > 0); + assert_se(match_remove(&root, "interface='barxx'", 7) == 0); bus_match_dump(&root, 0); zero(mask); - assert_se(bus_match_run(NULL, &root, 0, m) == 0); + assert_se(bus_match_run(NULL, &root, m) == 0); assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7 }, 6)); for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) { diff --git a/src/libsystemd-bus/test-bus-memfd.c b/src/libsystemd-bus/test-bus-memfd.c new file mode 100644 index 0000000000..05ef555f0d --- /dev/null +++ b/src/libsystemd-bus/test-bus-memfd.c @@ -0,0 +1,174 @@ +/*-*- 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 <sys/mman.h> +#include <sys/uio.h> + +#include "log.h" +#include "macro.h" +#include "util.h" + +#include "sd-memfd.h" + +int main(int argc, char *argv[]) { + sd_memfd *m; + char *s; + uint64_t sz; + int r, fd; + FILE *f; + char buf[3] = {}; + struct iovec iov[3] = {}; + char bufv[3][3] = {}; + + log_set_max_level(LOG_DEBUG); + + r = sd_memfd_new(&m); + if (r == -ENOENT) + return EXIT_TEST_SKIP; + + r = sd_memfd_map(m, 0, 12, (void**) &s); + assert_se(r >= 0); + + strcpy(s, "----- world"); + + r = sd_memfd_set_sealed(m, 1); + assert_se(r == -ETXTBSY); + + assert_se(write(sd_memfd_get_fd(m), "he", 2) == 2); + assert_se(write(sd_memfd_get_fd(m), "XXX", 3) == 3); + assert_se(streq(s, "heXXX world")); + + /* fix "hello" */ + assert_se(lseek(sd_memfd_get_fd(m), 2, SEEK_SET) == 2); + assert_se(write(sd_memfd_get_fd(m), "ll", 2) == 2); + + assert_se(sd_memfd_get_file(m, &f) >= 0); + fputc('o', f); + fflush(f); + + /* check content */ + assert_se(streq(s, "hello world")); + + assert_se(munmap(s, 12) == 0); + + r = sd_memfd_get_sealed(m); + assert_se(r == 0); + + r = sd_memfd_get_size(m, &sz); + assert_se(r >= 0); + assert_se(sz = page_size()); + + /* truncate it */ + r = sd_memfd_set_size(m, 6); + assert_se(r >= 0); + + /* get back new value */ + r = sd_memfd_get_size(m, &sz); + assert_se(r >= 0); + assert_se(sz == 6); + + r = sd_memfd_set_sealed(m, 1); + assert_se(r >= 0); + + r = sd_memfd_get_sealed(m); + assert_se(r == 1); + + fd = sd_memfd_dup_fd(m); + assert_se(fd >= 0); + + sd_memfd_free(m); + + /* new sd_memfd, same underlying memfd */ + r = sd_memfd_make(fd, &m); + assert_se(r >= 0); + + /* we did truncate it to 6 */ + r = sd_memfd_get_size(m, &sz); + assert_se(sz == 6); + + /* map it, check content */ + r = sd_memfd_map(m, 0, 12, (void **)&s); + assert_se(r >= 0); + + /* we only see the truncated size */ + assert_se(streq(s, "hello ")); + + /* it was already sealed */ + r = sd_memfd_set_sealed(m, 1); + assert_se(r == -EALREADY); + + /* we cannot break the seal, it is mapped */ + r = sd_memfd_set_sealed(m, 0); + assert_se(r == -ETXTBSY); + + /* unmap it; become the single owner */ + assert_se(munmap(s, 12) == 0); + + /* now we can do flip the sealing */ + r = sd_memfd_set_sealed(m, 0); + assert_se(r == 0); + r = sd_memfd_get_sealed(m); + assert_se(r == 0); + + r = sd_memfd_set_sealed(m, 1); + assert_se(r == 0); + r = sd_memfd_get_sealed(m); + assert_se(r == 1); + + r = sd_memfd_set_sealed(m, 0); + assert_se(r == 0); + r = sd_memfd_get_sealed(m); + assert_se(r == 0); + + /* seek at 2, read() 2 bytes */ + assert_se(lseek(fd, 2, SEEK_SET) == 2); + assert_se(read(fd, buf, 2) == 2); + + /* check content */ + assert_se(memcmp(buf, "ll", 2) == 0); + + /* writev it out*/ + iov[0].iov_base = (char *)"ABC"; + iov[0].iov_len = 3; + iov[1].iov_base = (char *)"DEF"; + iov[1].iov_len = 3; + iov[2].iov_base = (char *)"GHI"; + iov[2].iov_len = 3; + assert_se(pwritev(fd, iov, 3, 0) == 9); + + /* readv it back */ + iov[0].iov_base = bufv[0]; + iov[0].iov_len = 3; + iov[1].iov_base = bufv[1]; + iov[1].iov_len = 3; + iov[2].iov_base = bufv[2]; + iov[2].iov_len = 3; + assert_se(preadv(fd, iov, 3, 0) == 9); + + /* check content */ + assert_se(memcmp(bufv[0], "ABC", 3) == 0); + assert_se(memcmp(bufv[1], "DEF", 3) == 0); + assert_se(memcmp(bufv[2], "GHI", 3) == 0); + + sd_memfd_free(m); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-server.c b/src/libsystemd-bus/test-bus-server.c index a9772624f2..ef26a65d87 100644 --- a/src/libsystemd-bus/test-bus-server.c +++ b/src/libsystemd-bus/test-bus-server.c @@ -55,8 +55,8 @@ static void *server(void *p) { assert_se(sd_bus_new(&bus) >= 0); assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); assert_se(sd_bus_set_server(bus, 1, id) >= 0); - assert_se(sd_bus_set_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0); + assert_se(sd_bus_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); assert_se(sd_bus_start(bus) >= 0); while (!quit) { @@ -134,7 +134,7 @@ static int client(struct context *c) { assert_se(sd_bus_new(&bus) >= 0); assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); - assert_se(sd_bus_set_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); + assert_se(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0); assert_se(sd_bus_start(bus) >= 0); diff --git a/src/libsystemd-bus/test-bus-zero-copy.c b/src/libsystemd-bus/test-bus-zero-copy.c new file mode 100644 index 0000000000..db3906e274 --- /dev/null +++ b/src/libsystemd-bus/test-bus-zero-copy.c @@ -0,0 +1,183 @@ +/*-*- 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 <fcntl.h> +#include <sys/mman.h> + +#include "util.h" +#include "log.h" + +#include "sd-bus.h" +#include "sd-memfd.h" +#include "bus-message.h" +#include "bus-error.h" +#include "bus-kernel.h" + +#define FIRST_ARRAY 17 +#define SECOND_ARRAY 33 + +#define STRING_SIZE 123 + +int main(int argc, char *argv[]) { + _cleanup_free_ char *bus_name = NULL, *address = NULL; + uint8_t *p; + sd_bus *a, *b; + int r, bus_ref; + sd_bus_message *m; + sd_memfd *f; + uint64_t sz; + uint32_t u32; + size_t i, l; + char *s; + + log_set_max_level(LOG_DEBUG); + + bus_ref = bus_kernel_create("deine-mutter", &bus_name); + if (bus_ref == -ENOENT) + return EXIT_TEST_SKIP; + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_message_new_method_call(b, ":1.1", "/a/path", "an.inter.face", "AMethod", &m); + assert_se(r >= 0); + + r = sd_bus_message_open_container(m, 'r', "aysay"); + assert_se(r >= 0); + + r = sd_bus_message_append_array_space(m, 'y', FIRST_ARRAY, (void**) &p); + assert_se(r >= 0); + + memset(p, 'L', FIRST_ARRAY); + + r = sd_memfd_new_and_map(&f, STRING_SIZE, (void**) &s); + assert_se(r >= 0); + + for (i = 0; i < STRING_SIZE-1; i++) + s[i] = '0' + (i % 10); + + s[STRING_SIZE-1] = 0; + munmap(s, STRING_SIZE); + + r = sd_memfd_get_size(f, &sz); + assert_se(r >= 0); + assert_se(sz == STRING_SIZE); + + r = sd_bus_message_append_string_memfd(m, f); + assert_se(r >= 0); + + sd_memfd_free(f); + + r = sd_memfd_new_and_map(&f, SECOND_ARRAY, (void**) &p); + assert_se(r >= 0); + + memset(p, 'P', SECOND_ARRAY); + munmap(p, SECOND_ARRAY); + + r = sd_memfd_get_size(f, &sz); + assert_se(r >= 0); + assert_se(sz == SECOND_ARRAY); + + r = sd_bus_message_append_array_memfd(m, 'y', f); + assert_se(r >= 0); + + sd_memfd_free(f); + + r = sd_bus_message_close_container(m); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "u", 4711); + assert_se(r >= 0); + + r = bus_message_seal(m, 55); + assert_se(r >= 0); + + bus_message_dump(m); + + r = sd_bus_send(b, m, NULL); + assert_se(r >= 0); + + sd_bus_message_unref(m); + + r = sd_bus_process(a, &m); + assert_se(r > 0); + + bus_message_dump(m); + sd_bus_message_rewind(m, true); + + r = sd_bus_message_enter_container(m, 'r', "aysay"); + assert_se(r > 0); + + r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); + assert_se(r > 0); + assert_se(l == FIRST_ARRAY); + + for (i = 0; i < l; i++) + assert_se(p[i] == 'L'); + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + + for (i = 0; i < STRING_SIZE-1; i++) + assert_se(s[i] == (char) ('0' + (i % 10))); + assert_se(s[STRING_SIZE-1] == 0); + + r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); + assert_se(r > 0); + assert_se(l == SECOND_ARRAY); + + for (i = 0; i < l; i++) + assert_se(p[i] == 'P'); + + r = sd_bus_message_exit_container(m); + assert_se(r > 0); + + r = sd_bus_message_read(m, "u", &u32); + assert_se(r > 0); + assert_se(u32 == 4711); + + sd_bus_message_unref(m); + + sd_bus_unref(a); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c index 6bb2e41510..a644904757 100644 --- a/src/libudev/libudev-device.c +++ b/src/libudev/libudev-device.c @@ -42,7 +42,7 @@ * * Representation of kernel sys devices. Devices are uniquely identified * by their syspath, every device has exactly one path in the kernel sys - * filesystem. Devices usually belong to a kernel subsystem, and and have + * filesystem. Devices usually belong to a kernel subsystem, and have * a unique name inside that subsystem. */ @@ -780,7 +780,7 @@ _public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char * * Returns: a new udev device, or #NULL, if it does not exist **/ -_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, char *id) +_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) { char type; int maj, min; diff --git a/src/libudev/libudev-enumerate.c b/src/libudev/libudev-enumerate.c index 5ccaabdc6c..8146f27e4f 100644 --- a/src/libudev/libudev-enumerate.c +++ b/src/libudev/libudev-enumerate.c @@ -270,8 +270,9 @@ _public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enume return NULL; if (!udev_enumerate->devices_uptodate) { unsigned int i; + int move_later = -1; unsigned int max; - struct syspath *prev = NULL, *move_later = NULL; + struct syspath *prev = NULL; size_t move_later_prefix = 0; udev_list_cleanup(&udev_enumerate->devices_list); @@ -299,27 +300,29 @@ _public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enume /* skip to be delayed devices, and move the to * the point where the prefix changes. We can * only move one item at a time. */ - if (!move_later) { + if (move_later == -1) { move_later_prefix = devices_delay_later(udev_enumerate->udev, entry->syspath); if (move_later_prefix > 0) { - move_later = entry; + move_later = i; continue; } } - if (move_later && - !strneq(entry->syspath, move_later->syspath, move_later_prefix)) { + if ((move_later >= 0) && + !strneq(entry->syspath, udev_enumerate->devices[move_later].syspath, move_later_prefix)) { - udev_list_entry_add(&udev_enumerate->devices_list, move_later->syspath, NULL); - move_later = NULL; + udev_list_entry_add(&udev_enumerate->devices_list, + udev_enumerate->devices[move_later].syspath, NULL); + move_later = -1; } udev_list_entry_add(&udev_enumerate->devices_list, entry->syspath, NULL); } - if (move_later) - udev_list_entry_add(&udev_enumerate->devices_list, move_later->syspath, NULL); + if (move_later >= 0) + udev_list_entry_add(&udev_enumerate->devices_list, + udev_enumerate->devices[move_later].syspath, NULL); /* add and cleanup delayed devices from end of list */ for (i = max; i < udev_enumerate->devices_cur; i++) { @@ -718,10 +721,14 @@ static bool match_subsystem(struct udev_enumerate *udev_enumerate, const char *s { struct udev_list_entry *list_entry; + if (!subsystem) + return false; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->subsystem_nomatch_list)) { if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0) return false; } + if (udev_list_get_entry(&udev_enumerate->subsystem_match_list) != NULL) { udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_enumerate->subsystem_match_list)) { if (fnmatch(udev_list_entry_get_name(list_entry), subsystem, 0) == 0) @@ -729,6 +736,7 @@ static bool match_subsystem(struct udev_enumerate *udev_enumerate, const char *s } return false; } + return true; } @@ -826,23 +834,27 @@ nomatch: static int parent_add_child(struct udev_enumerate *enumerate, const char *path) { struct udev_device *dev; + int r = 0; dev = udev_device_new_from_syspath(enumerate->udev, path); if (dev == NULL) return -ENODEV; if (!match_subsystem(enumerate, udev_device_get_subsystem(dev))) - return 0; + goto nomatch; if (!match_sysname(enumerate, udev_device_get_sysname(dev))) - return 0; + goto nomatch; if (!match_property(enumerate, dev)) - return 0; + goto nomatch; if (!match_sysattr(enumerate, dev)) - return 0; + goto nomatch; syspath_add(enumerate, udev_device_get_syspath(dev)); + r = 1; + +nomatch: udev_device_unref(dev); - return 1; + return r; } static int parent_crawl_children(struct udev_enumerate *enumerate, const char *path, int maxdepth) diff --git a/src/libudev/libudev-hwdb.c b/src/libudev/libudev-hwdb.c index 42ab6d9a6b..de1cb83188 100644 --- a/src/libudev/libudev-hwdb.c +++ b/src/libudev/libudev-hwdb.c @@ -140,9 +140,13 @@ static const struct trie_node_f *node_lookup_f(struct udev_hwdb *hwdb, const str } static int hwdb_add_property(struct udev_hwdb *hwdb, const char *key, const char *value) { - /* TODO: add sub-matches (+) against DMI data */ + /* + * Silently ignore all properties which do not start with a + * space; future extensions might use additional prefixes. + */ if (key[0] != ' ') return 0; + if (udev_list_entry_add(&hwdb->properties_list, key+1, value) == NULL) return -ENOMEM; return 0; @@ -300,11 +304,11 @@ _public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { } log_debug("=== trie on-disk ===\n"); - log_debug("tool version: %llu", (unsigned long long)le64toh(hwdb->head->tool_version)); - log_debug("file size: %8llu bytes\n", (unsigned long long)hwdb->st.st_size); - log_debug("header size %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->header_size)); - log_debug("strings %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->strings_len)); - log_debug("nodes %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->nodes_len)); + log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); + log_debug("file size: %8llu bytes\n", (unsigned long long) hwdb->st.st_size); + log_debug("header size %8"PRIu64" bytes\n", le64toh(hwdb->head->header_size)); + log_debug("strings %8"PRIu64" bytes\n", le64toh(hwdb->head->strings_len)); + log_debug("nodes %8"PRIu64" bytes\n", le64toh(hwdb->head->nodes_len)); return hwdb; } @@ -354,7 +358,7 @@ bool udev_hwdb_validate(struct udev_hwdb *hwdb) { return false; if (!hwdb->f) return false; - if (fstat(fileno(hwdb->f), &st) < 0) + if (stat("/etc/udev/hwdb.bin", &st) < 0) return true; if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim)) return true; diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c index 714dc50ae9..b5b9db67fc 100644 --- a/src/libudev/libudev-util.c +++ b/src/libudev/libudev-util.c @@ -32,8 +32,10 @@ #include <sys/stat.h> #include <sys/param.h> +#include "device-nodes.h" #include "libudev.h" #include "libudev-private.h" +#include "utf8.h" /** * SECTION:libudev-util @@ -306,129 +308,6 @@ void util_remove_trailing_chars(char *path, char c) path[--len] = '\0'; } -/* count of characters used to encode one unicode char */ -static int utf8_encoded_expected_len(const char *str) -{ - unsigned char c = (unsigned char)str[0]; - - if (c < 0x80) - return 1; - if ((c & 0xe0) == 0xc0) - return 2; - if ((c & 0xf0) == 0xe0) - return 3; - if ((c & 0xf8) == 0xf0) - return 4; - if ((c & 0xfc) == 0xf8) - return 5; - if ((c & 0xfe) == 0xfc) - return 6; - return 0; -} - -/* decode one unicode char */ -static int utf8_encoded_to_unichar(const char *str) -{ - int unichar; - int len; - int i; - - len = utf8_encoded_expected_len(str); - switch (len) { - case 1: - return (int)str[0]; - case 2: - unichar = str[0] & 0x1f; - break; - case 3: - unichar = (int)str[0] & 0x0f; - break; - case 4: - unichar = (int)str[0] & 0x07; - break; - case 5: - unichar = (int)str[0] & 0x03; - break; - case 6: - unichar = (int)str[0] & 0x01; - break; - default: - return -1; - } - - for (i = 1; i < len; i++) { - if (((int)str[i] & 0xc0) != 0x80) - return -1; - unichar <<= 6; - unichar |= (int)str[i] & 0x3f; - } - - return unichar; -} - -/* expected size used to encode one unicode char */ -static int utf8_unichar_to_encoded_len(int unichar) -{ - if (unichar < 0x80) - return 1; - if (unichar < 0x800) - return 2; - if (unichar < 0x10000) - return 3; - if (unichar < 0x200000) - return 4; - if (unichar < 0x4000000) - return 5; - return 6; -} - -/* check if unicode char has a valid numeric range */ -static int utf8_unichar_valid_range(int unichar) -{ - if (unichar > 0x10ffff) - return 0; - if ((unichar & 0xfffff800) == 0xd800) - return 0; - if ((unichar > 0xfdcf) && (unichar < 0xfdf0)) - return 0; - if ((unichar & 0xffff) == 0xffff) - return 0; - return 1; -} - -/* validate one encoded unicode char and return its length */ -static int utf8_encoded_valid_unichar(const char *str) -{ - int len; - int unichar; - int i; - - len = utf8_encoded_expected_len(str); - if (len == 0) - return -1; - - /* ascii is valid */ - if (len == 1) - return 1; - - /* check if expected encoded chars are available */ - for (i = 0; i < len; i++) - if ((str[i] & 0x80) != 0x80) - return -1; - - unichar = utf8_encoded_to_unichar(str); - - /* check if encoded length matches encoded value */ - if (utf8_unichar_to_encoded_len(unichar) != len) - return -1; - - /* check if value has valid range */ - if (!utf8_unichar_valid_range(unichar)) - return -1; - - return len; -} - int util_replace_whitespace(const char *str, char *to, size_t len) { size_t i, j; @@ -457,17 +336,6 @@ int util_replace_whitespace(const char *str, char *to, size_t len) return 0; } -static int is_whitelisted(char c, const char *white) -{ - if ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - strchr("#+-.:=@_", c) != NULL || - (white != NULL && strchr(white, c) != NULL)) - return 1; - return 0; -} - /* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */ int util_replace_chars(char *str, const char *white) { @@ -477,7 +345,7 @@ int util_replace_chars(char *str, const char *white) while (str[i] != '\0') { int len; - if (is_whitelisted(str[i], white)) { + if (whitelisted_char_for_devnode(str[i], white)) { i++; continue; } @@ -525,39 +393,7 @@ int util_replace_chars(char *str, const char *white) **/ _public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len) { - size_t i, j; - - if (str == NULL || str_enc == NULL) - return -1; - - for (i = 0, j = 0; str[i] != '\0'; i++) { - int seqlen; - - seqlen = utf8_encoded_valid_unichar(&str[i]); - if (seqlen > 1) { - if (len-j < (size_t)seqlen) - goto err; - memcpy(&str_enc[j], &str[i], seqlen); - j += seqlen; - i += (seqlen-1); - } else if (str[i] == '\\' || !is_whitelisted(str[i], NULL)) { - if (len-j < 4) - goto err; - sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); - j += 4; - } else { - if (len-j < 1) - goto err; - str_enc[j] = str[i]; - j++; - } - } - if (len-j < 1) - goto err; - str_enc[j] = '\0'; - return 0; -err: - return -1; + return encode_devnode_name(str, str_enc, len); } /* diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h index 61567b1d67..b9b8f13e44 100644 --- a/src/libudev/libudev.h +++ b/src/libudev/libudev.h @@ -81,7 +81,7 @@ struct udev *udev_device_get_udev(struct udev_device *udev_device); struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); -struct udev_device *udev_device_new_from_device_id(struct udev *udev, char *id); +struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); struct udev_device *udev_device_new_from_environment(struct udev *udev); /* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */ struct udev_device *udev_device_get_parent(struct udev_device *udev_device); diff --git a/src/libudev/libudev.sym b/src/libudev/libudev.sym index 8e09430aec..1e6f885141 100644 --- a/src/libudev/libudev.sym +++ b/src/libudev/libudev.sym @@ -109,5 +109,6 @@ global: } LIBUDEV_189; LIBUDEV_199 { +global: udev_device_set_sysattr_value; } LIBUDEV_196; diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 50250c4b47..8259c0af5f 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -46,7 +46,8 @@ static enum transport { TRANSPORT_POLKIT } arg_transport = TRANSPORT_NORMAL; static bool arg_ask_password = true; -static const char *arg_host = NULL; +static char *arg_host = NULL; +static char *arg_user = NULL; static bool arg_convert = true; static void pager_open_if_enabled(void) { @@ -223,7 +224,7 @@ static int show_status(DBusConnection *bus, char **args, unsigned n) { static int set_locale(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; - dbus_bool_t interactive = true; + dbus_bool_t interactive = arg_ask_password; DBusError error; DBusMessageIter iter; int r; @@ -459,7 +460,7 @@ static int list_locales(DBusConnection *bus, char **args, unsigned n) { static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true, b; + dbus_bool_t interactive = arg_ask_password, b; const char *map, *toggle_map; assert(bus); @@ -537,6 +538,7 @@ static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) { if (!keymaps) return log_oom(); + nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS); nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS); nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS); nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS); @@ -565,7 +567,7 @@ static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) { static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true, b; + dbus_bool_t interactive = arg_ask_password, b; const char *layout, *model, *variant, *options; assert(bus); @@ -712,6 +714,7 @@ static int help(void) { " --version Show package version\n" " --no-convert Don't convert keyboard mappings\n" " --no-pager Do not pipe output into a pager\n" + " -P --privileged Acquire privileges before execution\n" " --no-ask-password Do not prompt for password\n" " -H --host=[USER@]HOST Operate on remote host\n\n" "Commands:\n" @@ -757,7 +760,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) { switch (c) { @@ -776,7 +779,7 @@ static int parse_argv(int argc, char *argv[]) { case 'H': arg_transport = TRANSPORT_SSH; - arg_host = optarg; + parse_user_at_host(optarg, &arg_user, &arg_host); break; case ARG_NO_CONVERT: @@ -787,6 +790,10 @@ static int parse_argv(int argc, char *argv[]) { arg_no_pager = true; break; + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + case '?': return -EINVAL; diff --git a/src/login/70-uaccess.rules b/src/login/70-uaccess.rules index a118f8e887..01484c95f1 100644 --- a/src/login/70-uaccess.rules +++ b/src/login/70-uaccess.rules @@ -25,7 +25,8 @@ SUBSYSTEM=="block", ENV{ID_CDROM}=="1", TAG+="uaccess" SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", TAG+="uaccess" # Sound devices -SUBSYSTEM=="sound", TAG+="uaccess" +SUBSYSTEM=="sound", TAG+="uaccess" \ + OPTIONS+="static_node=snd/timer", OPTIONS+="static_node=snd/seq" # ffado is an userspace driver for firewire sound cards SUBSYSTEM=="firewire", ENV{ID_FFADO}=="1", TAG+="uaccess" diff --git a/src/login/libsystemd-login.sym b/src/login/libsystemd-login.sym index 925fb91095..54aa91c609 100644 --- a/src/login/libsystemd-login.sym +++ b/src/login/libsystemd-login.sym @@ -75,3 +75,13 @@ LIBSYSTEMD_LOGIN_203 { global: sd_get_machine_names; } LIBSYSTEMD_LOGIN_202; + +LIBSYSTEMD_LOGIN_205 { +global: + sd_pid_get_slice; +} LIBSYSTEMD_LOGIN_203; + +LIBSYSTEMD_LOGIN_207 { +global: + sd_session_get_vt; +} LIBSYSTEMD_LOGIN_205; diff --git a/src/login/login-shared.c b/src/login/login-shared.c new file mode 100644 index 0000000000..054c77503b --- /dev/null +++ b/src/login/login-shared.c @@ -0,0 +1,29 @@ +/*-*- 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 "login-shared.h" +#include "def.h" + +bool session_id_valid(const char *id) { + assert(id); + + return id[0] && id[strspn(id, LETTERS DIGITS)] == '\0'; +} diff --git a/src/login/login-shared.h b/src/login/login-shared.h new file mode 100644 index 0000000000..b2787c9c62 --- /dev/null +++ b/src/login/login-shared.h @@ -0,0 +1,24 @@ +/*-*- 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 <stdbool.h> + +bool session_id_valid(const char *id); diff --git a/src/login/loginctl.c b/src/login/loginctl.c index caaea8dfaa..736db6a11b 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -34,8 +34,10 @@ #include "dbus-common.h" #include "build.h" #include "strv.h" -#include "cgroup-show.h" +#include "unit-name.h" #include "sysfs-show.h" +#include "cgroup-show.h" +#include "cgroup-util.h" #include "spawn-polkit-agent.h" static char **arg_property = NULL; @@ -50,7 +52,8 @@ static enum transport { TRANSPORT_POLKIT } arg_transport = TRANSPORT_NORMAL; static bool arg_ask_password = true; -static const char *arg_host = NULL; +static char *arg_host = NULL; +static char *arg_user = NULL; static void pager_open_if_enabled(void) { @@ -260,12 +263,82 @@ static int list_seats(DBusConnection *bus, char **args, unsigned n) { return 0; } +static int show_unit_cgroup(DBusConnection *bus, const char *interface, const char *unit, pid_t leader) { + const char *property = "ControlGroup"; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_free_ char *path = NULL; + DBusMessageIter iter, sub; + const char *cgroup; + DBusError error; + int r, output_flags; + unsigned c; + + assert(bus); + assert(unit); + + if (arg_transport == TRANSPORT_SSH) + return 0; + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get", + &reply, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to query ControlGroup: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_recurse(&iter, &sub); + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_get_basic(&sub, &cgroup); + + if (isempty(cgroup)) + return 0; + + if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0) + return 0; + + output_flags = + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH; + + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags); + return 0; +} + typedef struct SessionStatusInfo { const char *id; uid_t uid; const char *name; usec_t timestamp; - const char *default_control_group; int vtnr; const char *seat; const char *tty; @@ -278,16 +351,17 @@ typedef struct SessionStatusInfo { const char *type; const char *class; const char *state; + const char *scope; } SessionStatusInfo; typedef struct UserStatusInfo { uid_t uid; const char *name; usec_t timestamp; - const char *default_control_group; const char *state; char **sessions; const char *display; + const char *slice; } UserStatusInfo; typedef struct SeatStatusInfo { @@ -296,7 +370,7 @@ typedef struct SeatStatusInfo { char **sessions; } SeatStatusInfo; -static void print_session_status_info(SessionStatusInfo *i) { +static void print_session_status_info(DBusConnection *bus, SessionStatusInfo *i) { char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; char since2[FORMAT_TIMESTAMP_MAX], *s2; assert(i); @@ -317,15 +391,13 @@ static void print_session_status_info(SessionStatusInfo *i) { printf("\t Since: %s\n", s2); if (i->leader > 0) { - char *t = NULL; + _cleanup_free_ char *t = NULL; printf("\t Leader: %u", (unsigned) i->leader); get_process_comm(i->leader, &t); - if (t) { + if (t) printf(" (%s)", t); - free(t); - } printf("\n"); } @@ -374,30 +446,13 @@ static void print_session_status_info(SessionStatusInfo *i) { if (i->state) printf("\t State: %s\n", i->state); - if (i->default_control_group) { - unsigned c; - int output_flags = - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH; - - printf("\t CGroup: %s\n", i->default_control_group); - - if (arg_transport != TRANSPORT_SSH) { - c = columns(); - if (c > 18) - c -= 18; - else - c = 0; - - show_cgroup_and_extra_by_spec(i->default_control_group, - "\t\t ", c, false, &i->leader, - i->leader > 0 ? 1 : 0, - output_flags); - } + if (i->scope) { + printf("\t Unit: %s\n", i->scope); + show_unit_cgroup(bus, "org.freedesktop.systemd1.Scope", i->scope, i->leader); } } -static void print_user_status_info(UserStatusInfo *i) { +static void print_user_status_info(DBusConnection *bus, UserStatusInfo *i) { char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; char since2[FORMAT_TIMESTAMP_MAX], *s2; assert(i); @@ -418,6 +473,7 @@ static void print_user_status_info(UserStatusInfo *i) { if (!isempty(i->state)) printf("\t State: %s\n", i->state); + if (!strv_isempty(i->sessions)) { char **l; printf("\tSessions:"); @@ -432,24 +488,9 @@ static void print_user_status_info(UserStatusInfo *i) { printf("\n"); } - if (i->default_control_group) { - unsigned c; - int output_flags = - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH; - - printf("\t CGroup: %s\n", i->default_control_group); - - if (arg_transport != TRANSPORT_SSH) { - c = columns(); - if (c > 18) - c -= 18; - else - c = 0; - - show_cgroup_by_path(i->default_control_group, "\t\t ", - c, false, output_flags); - } + if (i->slice) { + printf("\t Unit: %s\n", i->slice); + show_unit_cgroup(bus, "org.freedesktop.systemd1.Slice", i->slice, 0); } } @@ -504,8 +545,6 @@ static int status_property_session(const char *name, DBusMessageIter *iter, Sess i->id = s; else if (streq(name, "Name")) i->name = s; - else if (streq(name, "DefaultControlGroup")) - i->default_control_group = s; else if (streq(name, "TTY")) i->tty = s; else if (streq(name, "Display")) @@ -520,6 +559,8 @@ static int status_property_session(const char *name, DBusMessageIter *iter, Sess i->type = s; else if (streq(name, "Class")) i->class = s; + else if (streq(name, "Scope")) + i->scope = s; else if (streq(name, "State")) i->state = s; } @@ -603,8 +644,8 @@ static int status_property_user(const char *name, DBusMessageIter *iter, UserSta if (!isempty(s)) { if (streq(name, "Name")) i->name = s; - else if (streq(name, "DefaultControlGroup")) - i->default_control_group = s; + else if (streq(name, "Slice")) + i->slice = s; else if (streq(name, "State")) i->state = s; } @@ -913,9 +954,9 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo if (!show_properties) { if (strstr(verb, "session")) - print_session_status_info(&session_info); + print_session_status_info(bus, &session_info); else if (strstr(verb, "user")) - print_user_status_info(&user_info); + print_user_status_info(bus, &user_info); else print_seat_status_info(&seat_info); } @@ -980,7 +1021,7 @@ static int show(DBusConnection *bus, char **args, unsigned n) { } u = (uint32_t) uid; - ret = bus_method_call_with_reply ( + ret = bus_method_call_with_reply( bus, "org.freedesktop.login1", "/org/freedesktop/login1", @@ -990,9 +1031,10 @@ static int show(DBusConnection *bus, char **args, unsigned n) { NULL, DBUS_TYPE_UINT32, &u, DBUS_TYPE_INVALID); + } else { - ret = bus_method_call_with_reply ( + ret = bus_method_call_with_reply( bus, "org.freedesktop.login1", "/org/freedesktop/login1", @@ -1002,8 +1044,10 @@ static int show(DBusConnection *bus, char **args, unsigned n) { NULL, DBUS_TYPE_STRING, &args[i], DBUS_TYPE_INVALID); + } - if (ret) + + if (ret < 0) goto finish; if (!dbus_message_get_args(reply, &error, @@ -1296,7 +1340,7 @@ static int help(void) { " -p --property=NAME Show only properties by this name\n" " -a --all Show all properties, including empty ones\n" " --kill-who=WHO Who to send signal to\n" - " --full Do not ellipsize output\n" + " -l --full Do not ellipsize output\n" " -s --signal=SIGNAL Which signal to send\n" " --no-ask-password Don't prompt for password\n" " -H --host=[USER@]HOST Show information for remote host\n" @@ -1338,7 +1382,6 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_PAGER, ARG_KILL_WHO, ARG_NO_ASK_PASSWORD, - ARG_FULL, }; static const struct option options[] = { @@ -1346,13 +1389,13 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "property", required_argument, NULL, 'p' }, { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "kill-who", required_argument, NULL, ARG_KILL_WHO }, { "signal", required_argument, NULL, 's' }, { "host", required_argument, NULL, 'H' }, { "privileged", no_argument, NULL, 'P' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "full", no_argument, NULL, ARG_FULL }, { NULL, 0, NULL, 0 } }; @@ -1361,7 +1404,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hp:as:H:P", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) { switch (c) { @@ -1395,6 +1438,10 @@ static int parse_argv(int argc, char *argv[]) { arg_all = true; break; + case 'l': + arg_full = true; + break; + case ARG_NO_PAGER: arg_no_pager = true; break; @@ -1421,11 +1468,7 @@ static int parse_argv(int argc, char *argv[]) { case 'H': arg_transport = TRANSPORT_SSH; - arg_host = optarg; - break; - - case ARG_FULL: - arg_full = true; + parse_user_at_host(optarg, &arg_user, &arg_host); break; case '?': @@ -1452,29 +1495,29 @@ static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError const int argc; int (* const dispatch)(DBusConnection *bus, char **args, unsigned n); } verbs[] = { - { "list-sessions", LESS, 1, list_sessions }, - { "session-status", MORE, 2, show }, - { "show-session", MORE, 1, show }, - { "activate", EQUAL, 2, activate }, - { "lock-session", MORE, 2, activate }, - { "unlock-session", MORE, 2, activate }, - { "lock-sessions", EQUAL, 1, lock_sessions }, - { "unlock-sessions", EQUAL, 1, lock_sessions }, - { "terminate-session", MORE, 2, activate }, - { "kill-session", MORE, 2, kill_session }, - { "list-users", EQUAL, 1, list_users }, - { "user-status", MORE, 2, show }, - { "show-user", MORE, 1, show }, - { "enable-linger", MORE, 2, enable_linger }, - { "disable-linger", MORE, 2, enable_linger }, - { "terminate-user", MORE, 2, terminate_user }, - { "kill-user", MORE, 2, kill_user }, - { "list-seats", EQUAL, 1, list_seats }, - { "seat-status", MORE, 2, show }, - { "show-seat", MORE, 1, show }, - { "attach", MORE, 3, attach }, - { "flush-devices", EQUAL, 1, flush_devices }, - { "terminate-seat", MORE, 2, terminate_seat }, + { "list-sessions", LESS, 1, list_sessions }, + { "session-status", MORE, 2, show }, + { "show-session", MORE, 1, show }, + { "activate", EQUAL, 2, activate }, + { "lock-session", MORE, 2, activate }, + { "unlock-session", MORE, 2, activate }, + { "lock-sessions", EQUAL, 1, lock_sessions }, + { "unlock-sessions", EQUAL, 1, lock_sessions }, + { "terminate-session", MORE, 2, activate }, + { "kill-session", MORE, 2, kill_session }, + { "list-users", EQUAL, 1, list_users }, + { "user-status", MORE, 2, show }, + { "show-user", MORE, 1, show }, + { "enable-linger", MORE, 2, enable_linger }, + { "disable-linger", MORE, 2, enable_linger }, + { "terminate-user", MORE, 2, terminate_user }, + { "kill-user", MORE, 2, kill_user }, + { "list-seats", EQUAL, 1, list_seats }, + { "seat-status", MORE, 2, show }, + { "show-seat", MORE, 1, show }, + { "attach", MORE, 3, attach }, + { "flush-devices", EQUAL, 1, flush_devices }, + { "terminate-seat", MORE, 2, terminate_seat }, }; int left; diff --git a/src/login/logind-acl.c b/src/login/logind-acl.c index cb045a9928..25abcbcf80 100644 --- a/src/login/logind-acl.c +++ b/src/login/logind-acl.c @@ -28,6 +28,7 @@ #include "logind-acl.h" #include "util.h" #include "acl-util.h" +#include "set.h" static int flush_acl(acl_t acl) { acl_entry_t i; @@ -179,23 +180,34 @@ int devnode_acl_all(struct udev *udev, struct udev_list_entry *item = NULL, *first = NULL; struct udev_enumerate *e; + Set *nodes; + Iterator i; + char *n; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; int r; assert(udev); - if (isempty(seat)) - seat = "seat0"; + nodes = set_new(string_hash_func, string_compare_func); + if (!nodes) { + return -ENOMEM; + } e = udev_enumerate_new(udev); - if (!e) - return -ENOMEM; + if (!e) { + r = -ENOMEM; + goto finish; + } + + if (isempty(seat)) + seat = "seat0"; /* We can only match by one tag in libudev. We choose * "uaccess" for that. If we could match for two tags here we * could add the seat name as second match tag, but this would * be hardly optimizable in libudev, and hence checking the * second tag manually in our loop is a good solution. */ - r = udev_enumerate_add_match_tag(e, "uaccess"); if (r < 0) goto finish; @@ -231,18 +243,52 @@ int devnode_acl_all(struct udev *udev, continue; } - log_debug("Fixing up %s for seat %s...", node, sn); - - r = devnode_acl(node, flush, del, old_uid, add, new_uid); + n = strdup(node); udev_device_unref(d); + if (!n) + goto finish; + log_debug("Found udev node %s for seat %s", n, seat); + r = set_put(nodes, n); if (r < 0) goto finish; } -finish: - if (e) - udev_enumerate_unref(e); + /* udev exports "dead" device nodes to allow module on-demand loading, + * these devices are not known to the kernel at this moment */ + dir = opendir("/run/udev/static_node-tags/uaccess"); + if (dir) { + FOREACH_DIRENT(dent, dir, r = -errno; goto finish) { + _cleanup_free_ char *unescaped_devname = NULL; + unescaped_devname = cunescape(dent->d_name); + if (unescaped_devname == NULL) { + r = -ENOMEM; + goto finish; + } + + n = strappend("/dev/", unescaped_devname); + if (!n) { + r = -ENOMEM; + goto finish; + } + + log_debug("Found static node %s for seat %s", n, seat); + r = set_put(nodes, n); + if (r < 0 && r != -EEXIST) + goto finish; + else + r = 0; + } + } + + SET_FOREACH(n, nodes, i) { + log_debug("Fixing up ACLs at %s for seat %s", n, seat); + r = devnode_acl(n, flush, del, old_uid, add, new_uid); + } + +finish: + udev_enumerate_unref(e); + set_free_free(nodes); return r; } diff --git a/src/login/logind-action.c b/src/login/logind-action.c index c930591023..74114ee0a1 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -81,7 +81,7 @@ int manager_handle_action( else if (handle == HANDLE_HYBRID_SLEEP) supported = can_sleep("hybrid-sleep") > 0; else if (handle == HANDLE_KEXEC) - supported = access("/sbin/kexec", X_OK) >= 0; + supported = access(KEXEC, X_OK) >= 0; else supported = true; diff --git a/src/login/logind-core.c b/src/login/logind-core.c new file mode 100644 index 0000000000..36999ace40 --- /dev/null +++ b/src/login/logind-core.c @@ -0,0 +1,514 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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/>. +***/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <pwd.h> +#include <unistd.h> +#include <linux/vt.h> + +#include "logind.h" +#include "dbus-common.h" +#include "strv.h" + +int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_device) { + Device *d; + + assert(m); + assert(sysfs); + + d = hashmap_get(m->devices, sysfs); + if (d) { + if (_device) + *_device = d; + + /* we support adding master-flags, but not removing them */ + d->master = d->master || master; + + return 0; + } + + d = device_new(m, sysfs, master); + if (!d) + return -ENOMEM; + + if (_device) + *_device = d; + + return 0; +} + +int manager_add_seat(Manager *m, const char *id, Seat **_seat) { + Seat *s; + + assert(m); + assert(id); + + s = hashmap_get(m->seats, id); + if (s) { + if (_seat) + *_seat = s; + + return 0; + } + + s = seat_new(m, id); + if (!s) + return -ENOMEM; + + if (_seat) + *_seat = s; + + return 0; +} + +int manager_add_session(Manager *m, const char *id, Session **_session) { + Session *s; + + assert(m); + assert(id); + + s = hashmap_get(m->sessions, id); + if (s) { + if (_session) + *_session = s; + + return 0; + } + + s = session_new(m, id); + if (!s) + return -ENOMEM; + + if (_session) + *_session = s; + + return 0; +} + +int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) { + User *u; + + assert(m); + assert(name); + + u = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); + if (u) { + if (_user) + *_user = u; + + return 0; + } + + u = user_new(m, uid, gid, name); + if (!u) + return -ENOMEM; + + if (_user) + *_user = u; + + return 0; +} + +int manager_add_user_by_name(Manager *m, const char *name, User **_user) { + uid_t uid; + gid_t gid; + int r; + + assert(m); + assert(name); + + r = get_user_creds(&name, &uid, &gid, NULL, NULL); + if (r < 0) + return r; + + return manager_add_user(m, uid, gid, name, _user); +} + +int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) { + struct passwd *p; + + assert(m); + + errno = 0; + p = getpwuid(uid); + if (!p) + return errno ? -errno : -ENOENT; + + return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user); +} + +int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor) { + Inhibitor *i; + + assert(m); + assert(id); + + i = hashmap_get(m->inhibitors, id); + if (i) { + if (_inhibitor) + *_inhibitor = i; + + return 0; + } + + i = inhibitor_new(m, id); + if (!i) + return -ENOMEM; + + if (_inhibitor) + *_inhibitor = i; + + return 0; +} + +int manager_add_button(Manager *m, const char *name, Button **_button) { + Button *b; + + assert(m); + assert(name); + + b = hashmap_get(m->buttons, name); + if (b) { + if (_button) + *_button = b; + + return 0; + } + + b = button_new(m, name); + if (!b) + return -ENOMEM; + + if (_button) + *_button = b; + + return 0; +} + +int manager_watch_busname(Manager *m, const char *name) { + char *n; + int r; + + assert(m); + assert(name); + + if (hashmap_get(m->busnames, name)) + return 0; + + n = strdup(name); + if (!n) + return -ENOMEM; + + r = hashmap_put(m->busnames, n, n); + if (r < 0) { + free(n); + return r; + } + + return 0; +} + +void manager_drop_busname(Manager *m, const char *name) { + Session *session; + Iterator i; + char *key; + + assert(m); + assert(name); + + if (!hashmap_get(m->busnames, name)) + return; + + /* keep it if the name still owns a controller */ + HASHMAP_FOREACH(session, m->sessions, i) + if (session_is_controller(session, name)) + return; + + key = hashmap_remove(m->busnames, name); + if (key) + free(key); +} + +int manager_process_seat_device(Manager *m, struct udev_device *d) { + Device *device; + int r; + + assert(m); + + if (streq_ptr(udev_device_get_action(d), "remove")) { + + device = hashmap_get(m->devices, udev_device_get_syspath(d)); + if (!device) + return 0; + + seat_add_to_gc_queue(device->seat); + device_free(device); + + } else { + const char *sn; + Seat *seat = NULL; + bool master; + + sn = udev_device_get_property_value(d, "ID_SEAT"); + if (isempty(sn)) + sn = "seat0"; + + if (!seat_name_is_valid(sn)) { + log_warning("Device with invalid seat name %s found, ignoring.", sn); + return 0; + } + + /* ignore non-master devices for unknown seats */ + master = udev_device_has_tag(d, "master-of-seat"); + if (!master && !(seat = hashmap_get(m->seats, sn))) + return 0; + + r = manager_add_device(m, udev_device_get_syspath(d), master, &device); + if (r < 0) + return r; + + if (!seat) { + r = manager_add_seat(m, sn, &seat); + if (r < 0) { + if (!device->seat) + device_free(device); + + return r; + } + } + + device_attach(device, seat); + seat_start(seat); + } + + return 0; +} + +int manager_process_button_device(Manager *m, struct udev_device *d) { + Button *b; + + int r; + + assert(m); + + if (streq_ptr(udev_device_get_action(d), "remove")) { + + b = hashmap_get(m->buttons, udev_device_get_sysname(d)); + if (!b) + return 0; + + button_free(b); + + } else { + const char *sn; + + r = manager_add_button(m, udev_device_get_sysname(d), &b); + if (r < 0) + return r; + + sn = udev_device_get_property_value(d, "ID_SEAT"); + if (isempty(sn)) + sn = "seat0"; + + button_set_seat(b, sn); + button_open(b); + } + + return 0; +} + +int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) { + _cleanup_free_ char *unit = NULL; + Session *s; + int r; + + assert(m); + assert(session); + + if (pid < 1) + return -EINVAL; + + r = cg_pid_get_unit(pid, &unit); + if (r < 0) + return r; + + s = hashmap_get(m->session_units, unit); + if (!s) + return 0; + + *session = s; + return 1; +} + +int manager_get_user_by_pid(Manager *m, pid_t pid, User **user) { + _cleanup_free_ char *unit = NULL; + User *u; + int r; + + assert(m); + assert(user); + + if (pid < 1) + return -EINVAL; + + r = cg_pid_get_slice(pid, &unit); + if (r < 0) + return r; + + u = hashmap_get(m->user_units, unit); + if (!u) + return 0; + + *user = u; + return 1; +} + +int manager_get_idle_hint(Manager *m, dual_timestamp *t) { + Session *s; + bool idle_hint; + dual_timestamp ts = { 0, 0 }; + Iterator i; + + assert(m); + + idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0); + + HASHMAP_FOREACH(s, m->sessions, i) { + dual_timestamp k; + int ih; + + ih = session_get_idle_hint(s, &k); + if (ih < 0) + return ih; + + if (!ih) { + if (!idle_hint) { + if (k.monotonic < ts.monotonic) + ts = k; + } else { + idle_hint = false; + ts = k; + } + } else if (idle_hint) { + + if (k.monotonic > ts.monotonic) + ts = k; + } + } + + if (t) + *t = ts; + + return idle_hint; +} + +bool manager_shall_kill(Manager *m, const char *user) { + assert(m); + assert(user); + + if (!m->kill_user_processes) + return false; + + if (strv_contains(m->kill_exclude_users, user)) + return false; + + if (strv_isempty(m->kill_only_users)) + return true; + + return strv_contains(m->kill_only_users, user); +} + +static int vt_is_busy(int vtnr) { + struct vt_stat vt_stat; + int r = 0, fd; + + assert(vtnr >= 1); + + /* We explicitly open /dev/tty1 here instead of /dev/tty0. If + * we'd open the latter we'd open the foreground tty which + * hence would be unconditionally busy. By opening /dev/tty1 + * we avoid this. Since tty1 is special and needs to be an + * explicitly loaded getty or DM this is safe. */ + + fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0) + r = -errno; + else + r = !!(vt_stat.v_state & (1 << vtnr)); + + close_nointr_nofail(fd); + + return r; +} + +int manager_spawn_autovt(Manager *m, int vtnr) { + int r; + char *name = NULL; + const char *mode = "fail"; + + assert(m); + assert(vtnr >= 1); + + if ((unsigned) vtnr > m->n_autovts && + (unsigned) vtnr != m->reserve_vt) + return 0; + + if ((unsigned) vtnr != m->reserve_vt) { + /* If this is the reserved TTY, we'll start the getty + * on it in any case, but otherwise only if it is not + * busy. */ + + r = vt_is_busy(vtnr); + if (r < 0) + return r; + else if (r > 0) + return -EBUSY; + } + + if (asprintf(&name, "autovt@tty%i.service", vtnr) < 0) { + log_error("Could not allocate service name."); + r = -ENOMEM; + goto finish; + } + + r = bus_method_call_with_reply ( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + NULL, + NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + +finish: + free(name); + + return r; +} diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 4a84b860f1..bb85c7d4af 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -36,6 +36,10 @@ #include "systemd/sd-messages.h" #include "fileio-label.h" #include "label.h" +#include "utf8.h" +#include "unit-name.h" +#include "bus-errors.h" +#include "virt.h" #define BUS_MANAGER_INTERFACE \ " <interface name=\"org.freedesktop.login1.Manager\">\n" \ @@ -51,6 +55,10 @@ " <arg name=\"uid\" type=\"u\" direction=\"in\"/>\n" \ " <arg name=\"user\" type=\"o\" direction=\"out\"/>\n" \ " </method>\n" \ + " <method name=\"GetUserByPID\">\n" \ + " <arg name=\"pid\" type=\"u\" direction=\"in\"/>\n" \ + " <arg name=\"user\" type=\"o\" direction=\"out\"/>\n" \ + " </method>\n" \ " <method name=\"GetSeat\">\n" \ " <arg name=\"id\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"seat\" type=\"o\" direction=\"out\"/>\n" \ @@ -77,9 +85,7 @@ " <arg name=\"remote\" type=\"b\" direction=\"in\"/>\n" \ " <arg name=\"remote_user\" type=\"s\" direction=\"in\"/>\n" \ " <arg name=\"remote_host\" type=\"s\" direction=\"in\"/>\n" \ - " <arg name=\"controllers\" type=\"as\" direction=\"in\"/>\n" \ - " <arg name=\"reset_controllers\" type=\"as\" direction=\"in\"/>\n" \ - " <arg name=\"kill_processes\" type=\"b\" direction=\"in\"/>\n" \ + " <arg name=\"scope_properties\" type=\"a(sv)\" direction=\"in\"/>\n" \ " <arg name=\"id\" type=\"s\" direction=\"out\"/>\n" \ " <arg name=\"path\" type=\"o\" direction=\"out\"/>\n" \ " <arg name=\"runtime_path\" type=\"o\" direction=\"out\"/>\n" \ @@ -207,9 +213,6 @@ " <signal name=\"PrepareForSleep\">\n" \ " <arg name=\"active\" type=\"b\"/>\n" \ " </signal>\n" \ - " <property name=\"ControlGroupHierarchy\" type=\"s\" access=\"read\"/>\n" \ - " <property name=\"Controllers\" type=\"as\" access=\"read\"/>\n" \ - " <property name=\"ResetControllers\" type=\"as\" access=\"read\"/>\n" \ " <property name=\"NAutoVTs\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"KillOnlyUsers\" type=\"as\" access=\"read\"/>\n" \ " <property name=\"KillExcludeUsers\" type=\"as\" access=\"read\"/>\n" \ @@ -308,27 +311,24 @@ static int bus_manager_append_preparing(DBusMessageIter *i, const char *property return 0; } -static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMessage **_reply) { +static int bus_manager_create_session(Manager *m, DBusMessage *message) { + const char *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *service; uint32_t uid, leader, audit_id = 0; - dbus_bool_t remote, kill_processes, exists; - _cleanup_strv_free_ char **controllers = NULL, **reset_controllers = NULL; - _cleanup_free_ char *cgroup = NULL, *id = NULL, *p = NULL; - SessionType t; - SessionClass c; - DBusMessageIter iter; - int r; - uint32_t vtnr = 0; - _cleanup_close_ int fifo_fd = -1; - _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_free_ char *id = NULL; Session *session = NULL; User *user = NULL; Seat *seat = NULL; + DBusMessageIter iter; + dbus_bool_t remote; + uint32_t vtnr = 0; + SessionType t; + SessionClass c; bool b; + int r; assert(m); assert(message); - assert(_reply); if (!dbus_message_iter_init(message, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) @@ -342,8 +342,7 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess dbus_message_iter_get_basic(&iter, &leader); - if (leader <= 0 || - !dbus_message_iter_next(&iter) || + if (!dbus_message_iter_next(&iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return -EINVAL; @@ -405,8 +404,8 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess int v; if (!seat) - seat = m->vtconsole; - else if (seat != m->vtconsole) + seat = m->seat0; + else if (seat != m->seat0) return -EINVAL; v = vtnr_from_tty(tty); @@ -421,8 +420,8 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess } else if (tty_is_console(tty)) { if (!seat) - seat = m->vtconsole; - else if (seat != m->vtconsole) + seat = m->seat0; + else if (seat != m->seat0) return -EINVAL; if (vtnr != 0) @@ -430,7 +429,7 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess } if (seat) { - if (seat_can_multi_session(seat)) { + if (seat_has_vts(seat)) { if (vtnr > 63) return -EINVAL; } else { @@ -479,61 +478,37 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess dbus_message_iter_get_basic(&iter, &remote_host); - if (!dbus_message_iter_next(&iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || - dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) - return -EINVAL; - - r = bus_parse_strv_iter(&iter, &controllers); - if (r < 0) - return -EINVAL; - - if (!dbus_message_iter_next(&iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || - dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) { - r = -EINVAL; - goto fail; - } - - r = bus_parse_strv_iter(&iter, &reset_controllers); - if (r < 0) - goto fail; - - if (!dbus_message_iter_next(&iter) || - dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) { - r = -EINVAL; - goto fail; + if (leader <= 0) { + leader = bus_get_unix_process_id(m->bus, dbus_message_get_sender(message), NULL); + if (leader == 0) + return -EINVAL; } - dbus_message_iter_get_basic(&iter, &kill_processes); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, leader, &cgroup); - if (r < 0) - goto fail; + r = manager_get_session_by_pid(m, leader, &session); + if (session) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_free_ char *path = NULL; + _cleanup_close_ int fifo_fd = -1; + dbus_bool_t exists; - r = manager_get_session_by_cgroup(m, cgroup, &session); - if (r < 0) - goto fail; + /* Session already exists, client is probably + * something like "su" which changes uid but is still + * the same session */ - if (session) { fifo_fd = session_create_fifo(session); if (fifo_fd < 0) { r = fifo_fd; goto fail; } - /* Session already exists, client is probably - * something like "su" which changes uid but - * is still the same audit session */ - - reply = dbus_message_new_method_return(message); - if (!reply) { + path = session_bus_path(session); + if (!path) { r = -ENOMEM; goto fail; } - p = session_bus_path(session); - if (!p) { + reply = dbus_message_new_method_return(message); + if (!reply) { r = -ENOMEM; goto fail; } @@ -545,7 +520,7 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess b = dbus_message_append_args( reply, DBUS_TYPE_STRING, &session->id, - DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &session->user->runtime_path, DBUS_TYPE_UNIX_FD, &fifo_fd, DBUS_TYPE_STRING, &cseat, @@ -557,8 +532,10 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess goto fail; } - *_reply = reply; - reply = NULL; + if (!dbus_connection_send(m->bus, reply, NULL)) { + r = -ENOMEM; + goto fail; + } return 0; } @@ -577,6 +554,7 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess * the audit data and let's better register a new * ID */ if (hashmap_get(m->sessions, id)) { + log_warning("Existing logind session ID %s used by new audit session, ignoring", id); audit_id = 0; free(id); @@ -601,22 +579,19 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess if (r < 0) goto fail; - r = manager_add_session(m, user, id, &session); + r = manager_add_session(m, id, &session); if (r < 0) goto fail; + session_set_user(session, user); + session->leader = leader; session->audit_id = audit_id; session->type = t; session->class = c; session->remote = remote; - session->kill_processes = kill_processes; session->vtnr = vtnr; - session->controllers = cg_shorten_controllers(controllers); - session->reset_controllers = cg_shorten_controllers(reset_controllers); - controllers = reset_controllers = NULL; - if (!isempty(tty)) { session->tty = strdup(tty); if (!session->tty) { @@ -657,12 +632,6 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess } } - fifo_fd = session_create_fifo(session); - if (fifo_fd < 0) { - r = fifo_fd; - goto fail; - } - if (seat) { r = seat_attach_session(seat, session); if (r < 0) @@ -673,38 +642,11 @@ static int bus_manager_create_session(Manager *m, DBusMessage *message, DBusMess if (r < 0) goto fail; - reply = dbus_message_new_method_return(message); - if (!reply) { - r = -ENOMEM; - goto fail; - } - - p = session_bus_path(session); - if (!p) { - r = -ENOMEM; - goto fail; - } - - cseat = seat ? seat->id : ""; - exists = false; - b = dbus_message_append_args( - reply, - DBUS_TYPE_STRING, &session->id, - DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_STRING, &session->user->runtime_path, - DBUS_TYPE_UNIX_FD, &fifo_fd, - DBUS_TYPE_STRING, &cseat, - DBUS_TYPE_UINT32, &vtnr, - DBUS_TYPE_BOOLEAN, &exists, - DBUS_TYPE_INVALID); - - if (!b) { - r = -ENOMEM; - goto fail; - } + session->create_message = dbus_message_ref(message); - *_reply = reply; - reply = NULL; + /* Now, let's wait until the slice unit and stuff got + * created. We send the reply back from + * session_send_create_reply().*/ return 0; @@ -1015,6 +957,7 @@ static int have_multiple_sessions( * count, and non-login sessions do not count either. */ HASHMAP_FOREACH(session, m->sessions, i) if (session->class == SESSION_USER && + !session->closing && session->user->uid != uid) return true; @@ -1387,9 +1330,6 @@ static int bus_manager_do_shutdown_or_sleep( static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_handle_action, handle_action, HandleAction); static const BusProperty bus_login_manager_properties[] = { - { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_path), true }, - { "Controllers", bus_property_append_strv, "as", offsetof(Manager, controllers), true }, - { "ResetControllers", bus_property_append_strv, "as", offsetof(Manager, reset_controllers), true }, { "NAutoVTs", bus_property_append_unsigned, "u", offsetof(Manager, n_autovts) }, { "KillOnlyUsers", bus_property_append_strv, "as", offsetof(Manager, kill_only_users), true }, { "KillExcludeUsers", bus_property_append_strv, "as", offsetof(Manager, kill_exclude_users), true }, @@ -1530,6 +1470,40 @@ static DBusHandlerResult manager_message_handler( if (!b) goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetUserByPID")) { + uint32_t pid; + char *p; + User *user; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = manager_get_user_by_pid(m, pid, &user); + if (r <= 0) + return bus_send_error_reply(connection, message, NULL, r < 0 ? r : -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = user_bus_path(user); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "GetSeat")) { const char *name; char *p; @@ -1612,7 +1586,6 @@ static DBusHandlerResult manager_message_handler( goto oom; } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ListUsers")) { - char *p; User *user; Iterator i; DBusMessageIter iter, sub; @@ -1627,6 +1600,7 @@ static DBusHandlerResult manager_message_handler( goto oom; HASHMAP_FOREACH(user, m->users, i) { + _cleanup_free_ char *p = NULL; DBusMessageIter sub2; uint32_t uid; @@ -1646,8 +1620,6 @@ static DBusHandlerResult manager_message_handler( goto oom; } - free(p); - if (!dbus_message_iter_close_container(&sub, &sub2)) goto oom; } @@ -1656,7 +1628,6 @@ static DBusHandlerResult manager_message_handler( goto oom; } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "ListSeats")) { - char *p; Seat *seat; Iterator i; DBusMessageIter iter, sub; @@ -1671,6 +1642,7 @@ static DBusHandlerResult manager_message_handler( goto oom; HASHMAP_FOREACH(seat, m->seats, i) { + _cleanup_free_ char *p = NULL; DBusMessageIter sub2; if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) @@ -1686,8 +1658,6 @@ static DBusHandlerResult manager_message_handler( goto oom; } - free(p); - if (!dbus_message_iter_close_container(&sub, &sub2)) goto oom; } @@ -1739,6 +1709,7 @@ static DBusHandlerResult manager_message_handler( if (!dbus_message_iter_close_container(&iter, &sub)) goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "Inhibit")) { r = bus_manager_inhibit(m, connection, message, &error, &reply); @@ -1749,7 +1720,7 @@ static DBusHandlerResult manager_message_handler( } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CreateSession")) { - r = bus_manager_create_session(m, message, &reply); + r = bus_manager_create_session(m, message); /* Don't delay the work on OOM here, since it might be * triggered by a low RLIMIT_NOFILE here (since we @@ -2286,7 +2257,7 @@ static DBusHandlerResult manager_message_handler( } HASHMAP_FOREACH(user, m->users, i) - fprintf(f, "<node name=\"user/%llu\"/>", (unsigned long long) user->uid); + fprintf(f, "<node name=\"user/_%llu\"/>", (unsigned long long) user->uid); HASHMAP_FOREACH(session, m->sessions, i) { p = bus_path_escape(session->id); @@ -2326,7 +2297,7 @@ static DBusHandlerResult manager_message_handler( if (reply) { if (!bus_maybe_send_reply(connection, message, reply)) - goto oom; + goto oom; } return DBUS_HANDLER_RESULT_HANDLED; @@ -2355,29 +2326,23 @@ DBusHandlerResult bus_message_filter( dbus_error_init(&error); - if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Agent", "Released")) { - const char *cgroup; - - if (!dbus_message_get_args(message, &error, - DBUS_TYPE_STRING, &cgroup, - DBUS_TYPE_INVALID)) - log_error("Failed to parse Released message: %s", bus_error_message(&error)); - else - manager_cgroup_notify_empty(m, cgroup); + log_debug("Got message: %s %s %s", strna(dbus_message_get_sender(message)), strna(dbus_message_get_interface(message)), strna(dbus_message_get_member(message))); - } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { - uint32_t id; + if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { const char *path, *result, *unit; + uint32_t id; if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &id, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &unit, DBUS_TYPE_STRING, &result, - DBUS_TYPE_INVALID)) + DBUS_TYPE_INVALID)) { log_error("Failed to parse JobRemoved message: %s", bus_error_message(&error)); + goto finish; + } - else if (m->action_job && streq(m->action_job, path)) { + if (m->action_job && streq(m->action_job, path)) { log_info("Operation finished."); /* Tell people that they now may take a lock again */ @@ -2387,9 +2352,141 @@ DBusHandlerResult bus_message_filter( m->action_job = NULL; m->action_unit = NULL; m->action_what = 0; + + } else { + Session *s; + User *u; + + s = hashmap_get(m->session_units, unit); + if (s) { + if (streq_ptr(path, s->scope_job)) { + free(s->scope_job); + s->scope_job = NULL; + + if (s->started) { + if (streq(result, "done")) + session_send_create_reply(s, NULL); + else { + dbus_set_error(&error, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); + session_send_create_reply(s, &error); + } + } else + session_save(s); + } + + session_add_to_gc_queue(s); + } + + u = hashmap_get(m->user_units, unit); + if (u) { + if (streq_ptr(path, u->service_job)) { + free(u->service_job); + u->service_job = NULL; + } + + if (streq_ptr(path, u->slice_job)) { + free(u->slice_job); + u->slice_job = NULL; + } + + user_save(u); + user_add_to_gc_queue(u); + } + } + + } else if (dbus_message_is_signal(message, "org.freedesktop.DBus.Properties", "PropertiesChanged")) { + + _cleanup_free_ char *unit = NULL; + const char *path; + + path = dbus_message_get_path(message); + if (!path) + goto finish; + + unit_name_from_dbus_path(path, &unit); + if (unit) { + Session *s; + User *u; + + s = hashmap_get(m->session_units, unit); + if (s) + session_add_to_gc_queue(s); + + u = hashmap_get(m->user_units, unit); + if (u) + user_add_to_gc_queue(u); + } + + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "UnitRemoved")) { + + const char *path, *unit; + Session *session; + User *user; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse UnitRemoved message: %s", bus_error_message(&error)); + goto finish; + } + + session = hashmap_get(m->session_units, unit); + if (session) + session_add_to_gc_queue(session); + + user = hashmap_get(m->user_units, unit); + if (user) + user_add_to_gc_queue(user); + + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "Reloading")) { + dbus_bool_t b; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse Reloading message: %s", bus_error_message(&error)); + goto finish; + } + + /* systemd finished reloading, let's recheck all our sessions */ + if (!b) { + Session *session; + Iterator i; + + log_debug("System manager has been reloaded, rechecking sessions..."); + + HASHMAP_FOREACH(session, m->sessions, i) + session_add_to_gc_queue(session); + } + + } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + const char *name, *old, *new; + char *key; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse NameOwnerChanged message: %s", bus_error_message(&error)); + goto finish; + } + + /* drop all controllers owned by this name */ + if (*old && !*new && (key = hashmap_remove(m->busnames, old))) { + Session *session; + Iterator i; + + free(key); + + HASHMAP_FOREACH(session, m->sessions, i) + if (session_is_controller(session, old)) + session_drop_controller(session); } } +finish: dbus_error_free(&error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -2444,3 +2541,356 @@ int manager_dispatch_delayed(Manager *manager) { return 1; } + +int manager_start_scope( + Manager *manager, + const char *scope, + pid_t pid, + const char *slice, + const char *description, + const char *after, + const char *kill_mode, + DBusError *error, + char **job) { + + const char *timeout_stop_property = "TimeoutStopUSec", *send_sighup_property = "SendSIGHUP", *pids_property = "PIDs"; + _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; + DBusMessageIter iter, sub, sub2, sub3, sub4; + uint64_t timeout = 500 * USEC_PER_MSEC; + dbus_bool_t send_sighup = true; + const char *fail = "fail"; + uint32_t u; + + assert(manager); + assert(scope); + assert(pid > 1); + + if (!slice) + slice = ""; + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (!m) + return log_oom(); + + dbus_message_iter_init_append(m, &iter); + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &scope) || + !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &fail) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub)) + return log_oom(); + + if (!isempty(slice)) { + const char *slice_property = "Slice"; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &slice_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &slice) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + } + + if (!isempty(description)) { + const char *description_property = "Description"; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &description) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + } + + if (!isempty(after)) { + const char *after_property = "After"; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &after_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "as", &sub3) || + !dbus_message_iter_open_container(&sub3, DBUS_TYPE_ARRAY, "s", &sub4) || + !dbus_message_iter_append_basic(&sub4, DBUS_TYPE_STRING, &after) || + !dbus_message_iter_close_container(&sub3, &sub4) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + } + + if (!isempty(kill_mode)) { + const char *kill_mode_property = "KillMode"; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &kill_mode_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &kill_mode) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + } + + /* cgroup empty notification is not available in containers + * currently. To make this less problematic, let's shorten the + * stop timeout for sessions, so that we don't wait + * forever. */ + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &timeout_stop_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "t", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_UINT64, &timeout) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + /* Make sure that the session shells are terminated with + * SIGHUP since bash and friends tend to ignore SIGTERM */ + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &send_sighup_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "b", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_BOOLEAN, &send_sighup) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + u = pid; + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &pids_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "au", &sub3) || + !dbus_message_iter_open_container(&sub3, DBUS_TYPE_ARRAY, "u", &sub4) || + !dbus_message_iter_append_basic(&sub4, DBUS_TYPE_UINT32, &u) || + !dbus_message_iter_close_container(&sub3, &sub4) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + if (!dbus_message_iter_close_container(&iter, &sub)) + return log_oom(); + + reply = dbus_connection_send_with_reply_and_block(manager->bus, m, -1, error); + if (!reply) + return -EIO; + + if (job) { + const char *j; + char *copy; + + if (!dbus_message_get_args(reply, error, DBUS_TYPE_OBJECT_PATH, &j, DBUS_TYPE_INVALID)) + return -EIO; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 0; +} + +int manager_start_unit(Manager *manager, const char *unit, DBusError *error, char **job) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *fail = "fail"; + int r; + + assert(manager); + assert(unit); + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + &reply, + error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_STRING, &fail, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to start unit %s: %s", unit, bus_error(error, r)); + return r; + } + + if (job) { + const char *j; + char *copy; + + if (!dbus_message_get_args(reply, error, + DBUS_TYPE_OBJECT_PATH, &j, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply."); + return -EIO; + } + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 0; +} + +int manager_stop_unit(Manager *manager, const char *unit, DBusError *error, char **job) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *fail = "fail"; + int r; + + assert(manager); + assert(unit); + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + &reply, + error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_STRING, &fail, + DBUS_TYPE_INVALID); + if (r < 0) { + if (dbus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || + dbus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { + + if (job) + *job = NULL; + + dbus_error_free(error); + return 0; + } + + log_error("Failed to stop unit %s: %s", unit, bus_error(error, r)); + return r; + } + + if (job) { + const char *j; + char *copy; + + if (!dbus_message_get_args(reply, error, + DBUS_TYPE_OBJECT_PATH, &j, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply."); + return -EIO; + } + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 1; +} + +int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, DBusError *error) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *w; + int r; + + assert(manager); + assert(unit); + + w = who == KILL_LEADER ? "process" : "cgroup"; + assert_cc(sizeof(signo) == sizeof(int32_t)); + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + &reply, + error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_STRING, &w, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to stop unit %s: %s", unit, bus_error(error, r)); + return r; + } + + return 0; +} + +int manager_unit_is_active(Manager *manager, const char *unit) { + + const char *interface = "org.freedesktop.systemd1.Unit"; + const char *property = "ActiveState"; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_free_ char *path = NULL; + DBusMessageIter iter, sub; + const char *state; + DBusError error; + int r; + + assert(manager); + assert(unit); + + dbus_error_init(&error); + + path = unit_dbus_path_from_name(unit); + if (!path) + return -ENOMEM; + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get", + &reply, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID); + if (r < 0) { + if (dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY) || + dbus_error_has_name(&error, DBUS_ERROR_DISCONNECTED)) { + /* systemd might have droppped off + * momentarily, let's not make this an + * error */ + + dbus_error_free(&error); + return true; + } + + if (dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || + dbus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) { + /* If the unit is already unloaded then it's + * not active */ + + dbus_error_free(&error); + return false; + } + + log_error("Failed to query ActiveState: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_recurse(&iter, &sub); + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_get_basic(&sub, &state); + + return !streq(state, "inactive") && !streq(state, "failed"); +} diff --git a/src/login/logind-device.c b/src/login/logind-device.c index 51b15358ba..95c2307baf 100644 --- a/src/login/logind-device.c +++ b/src/login/logind-device.c @@ -25,7 +25,7 @@ #include "logind-device.h" #include "util.h" -Device* device_new(Manager *m, const char *sysfs) { +Device* device_new(Manager *m, const char *sysfs, bool master) { Device *d; assert(m); @@ -48,6 +48,7 @@ Device* device_new(Manager *m, const char *sysfs) { } d->manager = m; + d->master = master; dual_timestamp_get(&d->timestamp); return d; @@ -66,20 +67,29 @@ void device_free(Device *d) { void device_detach(Device *d) { Seat *s; + SessionDevice *sd; assert(d); if (!d->seat) return; + while ((sd = d->session_devices)) + session_device_free(sd); + s = d->seat; LIST_REMOVE(Device, devices, d->seat->devices, d); d->seat = NULL; - seat_add_to_gc_queue(s); - seat_send_changed(s, "CanGraphical\0"); + if (!seat_has_master_device(s)) { + seat_add_to_gc_queue(s); + seat_send_changed(s, "CanGraphical\0"); + } } void device_attach(Device *d, Seat *s) { + Device *i; + bool had_master; + assert(d); assert(s); @@ -90,7 +100,26 @@ void device_attach(Device *d, Seat *s) { device_detach(d); d->seat = s; - LIST_PREPEND(Device, devices, s->devices, d); + had_master = seat_has_master_device(s); + + /* We keep the device list sorted by the "master" flag. That is, master + * devices are at the front, other devices at the tail. As there is no + * way to easily add devices at the list-tail, we need to iterate the + * list to find the first non-master device when adding non-master + * devices. We assume there is only a few (normally 1) master devices + * per seat, so we iterate only a few times. */ + + if (d->master || !s->devices) + LIST_PREPEND(Device, devices, s->devices, d); + else { + LIST_FOREACH(devices, i, s->devices) { + if (!i->devices_next || !i->master) { + LIST_INSERT_AFTER(Device, devices, s->devices, i, d); + break; + } + } + } - seat_send_changed(s, "CanGraphical\0"); + if (!had_master && d->master) + seat_send_changed(s, "CanGraphical\0"); } diff --git a/src/login/logind-device.h b/src/login/logind-device.h index 3b153568cb..fa6eda7e55 100644 --- a/src/login/logind-device.h +++ b/src/login/logind-device.h @@ -27,19 +27,22 @@ typedef struct Device Device; #include "util.h" #include "logind.h" #include "logind-seat.h" +#include "logind-session-device.h" struct Device { Manager *manager; char *sysfs; Seat *seat; + bool master; dual_timestamp timestamp; LIST_FIELDS(struct Device, devices); + LIST_HEAD(SessionDevice, session_devices); }; -Device* device_new(Manager *m, const char *sysfs); +Device* device_new(Manager *m, const char *sysfs, bool master); void device_free(Device *d); void device_attach(Device *d, Seat *s); void device_detach(Device *d); diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf index 735d2dbc9c..845302a54d 100644 --- a/src/login/logind-gperf.gperf +++ b/src/login/logind-gperf.gperf @@ -19,8 +19,6 @@ Login.ReserveVT, config_parse_unsigned, 0, offsetof(Manag Login.KillUserProcesses, config_parse_bool, 0, offsetof(Manager, kill_user_processes) Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_users) Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users) -Login.Controllers, config_parse_strv, 0, offsetof(Manager, controllers) -Login.ResetControllers, config_parse_strv, 0, offsetof(Manager, reset_controllers) Login.InhibitDelayMaxSec, config_parse_sec, 0, offsetof(Manager, inhibit_delay_max) Login.HandlePowerKey, config_parse_handle_action, 0, offsetof(Manager, handle_power_key) Login.HandleSuspendKey, config_parse_handle_action, 0, offsetof(Manager, handle_suspend_key) diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c index 5c535ba0ec..230f7f082a 100644 --- a/src/login/logind-seat-dbus.c +++ b/src/login/logind-seat-dbus.c @@ -209,8 +209,8 @@ static int bus_seat_append_idle_hint_since(DBusMessageIter *i, const char *prope } static int get_seat_for_path(Manager *m, const char *path, Seat **_s) { + _cleanup_free_ char *id = NULL; Seat *s; - char *id; assert(m); assert(path); @@ -224,8 +224,6 @@ static int get_seat_for_path(Manager *m, const char *path, Seat **_s) { return -ENOMEM; s = hashmap_get(m->seats, id); - free(id); - if (!s) return -ENOENT; @@ -348,7 +346,7 @@ const DBusObjectPathVTable bus_seat_vtable = { }; char *seat_bus_path(Seat *s) { - _cleanup_free_ char *t; + _cleanup_free_ char *t = NULL; assert(s); diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 470d08bc05..feebcf4558 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -105,11 +105,11 @@ int seat_save(Seat *s) { fprintf(f, "# This is private data. Do not parse.\n" - "IS_VTCONSOLE=%i\n" + "IS_SEAT0=%i\n" "CAN_MULTI_SESSION=%i\n" "CAN_TTY=%i\n" "CAN_GRAPHICAL=%i\n", - seat_is_vtconsole(s), + seat_is_seat0(s), seat_can_multi_session(s), seat_can_tty(s), seat_can_graphical(s)); @@ -201,7 +201,7 @@ int seat_preallocate_vts(Seat *s) { if (s->manager->n_autovts <= 0) return 0; - if (!seat_can_multi_session(s)) + if (!seat_has_vts(s)) return 0; for (i = 1; i <= s->manager->n_autovts; i++) { @@ -246,10 +246,17 @@ int seat_set_active(Seat *s, Session *session) { old_active = s->active; s->active = session; + if (old_active) { + session_device_pause_all(old_active); + session_send_changed(old_active, "Active\0"); + } + seat_apply_acls(s, old_active); - if (session && session->started) + if (session && session->started) { session_send_changed(session, "Active\0"); + session_device_resume_all(session); + } if (!session || session->started) seat_send_changed(s, "ActiveSession\0"); @@ -277,7 +284,7 @@ int seat_active_vt_changed(Seat *s, int vtnr) { assert(s); assert(vtnr >= 1); - if (!seat_can_multi_session(s)) + if (!seat_has_vts(s)) return -EINVAL; log_debug("VT changed to %i", vtnr); @@ -301,7 +308,7 @@ int seat_read_active_vt(Seat *s) { assert(s); - if (!seat_can_multi_session(s)) + if (!seat_has_vts(s)) return 0; lseek(s->manager->console_active_fd, SEEK_SET, 0); @@ -412,46 +419,64 @@ int seat_attach_session(Seat *s, Session *session) { seat_send_changed(s, "Sessions\0"); - /* Note that even if a seat is not multi-session capable it - * still might have multiple sessions on it since old, dead - * sessions might continue to be tracked until all their - * processes are gone. The most recently added session - * (i.e. the first in s->sessions) is the one that matters. */ - - if (!seat_can_multi_session(s)) + /* On seats with VTs, the VT logic defines which session is active. On + * seats without VTs, we automatically activate the first session. */ + if (!seat_has_vts(s) && !s->active) seat_set_active(s, session); return 0; } -bool seat_is_vtconsole(Seat *s) { +void seat_complete_switch(Seat *s) { + Session *session; + assert(s); - return s->manager->vtconsole == s; + /* if no session-switch is pending or if it got canceled, do nothing */ + if (!s->pending_switch) + return; + + session = s->pending_switch; + s->pending_switch = NULL; + + seat_set_active(s, session); } -bool seat_can_multi_session(Seat *s) { +bool seat_has_vts(Seat *s) { assert(s); - if (!seat_is_vtconsole(s)) - return false; + return seat_is_seat0(s) && s->manager->console_active_fd >= 0; +} - /* If we can't watch which VT is in the foreground, we don't - * support VT switching */ +bool seat_is_seat0(Seat *s) { + assert(s); - return s->manager->console_active_fd >= 0; + return s->manager->seat0 == s; +} + +bool seat_can_multi_session(Seat *s) { + assert(s); + + return seat_has_vts(s); } bool seat_can_tty(Seat *s) { assert(s); - return seat_is_vtconsole(s); + return seat_has_vts(s); +} + +bool seat_has_master_device(Seat *s) { + assert(s); + + /* device list is ordered by "master" flag */ + return !!s->devices && s->devices->master; } bool seat_can_graphical(Seat *s) { assert(s); - return !!s->devices; + return seat_has_master_device(s); } int seat_get_idle_hint(Seat *s, dual_timestamp *t) { @@ -496,10 +521,10 @@ int seat_check_gc(Seat *s, bool drop_not_started) { if (drop_not_started && !s->started) return 0; - if (seat_is_vtconsole(s)) + if (seat_is_seat0(s)) return 1; - return !!s->devices; + return seat_has_master_device(s); } void seat_add_to_gc_queue(Seat *s) { diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h index c8ab17f7cf..be6db6eed1 100644 --- a/src/login/logind-seat.h +++ b/src/login/logind-seat.h @@ -38,6 +38,7 @@ struct Seat { LIST_HEAD(Device, devices); Session *active; + Session *pending_switch; LIST_HEAD(Session, sessions); bool in_gc_queue:1; @@ -59,10 +60,13 @@ int seat_read_active_vt(Seat *s); int seat_preallocate_vts(Seat *s); int seat_attach_session(Seat *s, Session *session); +void seat_complete_switch(Seat *s); -bool seat_is_vtconsole(Seat *s); +bool seat_has_vts(Seat *s); +bool seat_is_seat0(Seat *s); bool seat_can_multi_session(Seat *s); bool seat_can_tty(Seat *s); +bool seat_has_master_device(Seat *s); bool seat_can_graphical(Seat *s); int seat_get_idle_hint(Seat *s, dual_timestamp *t); diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index ec823af547..5f6bafbc6a 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -24,6 +24,7 @@ #include "logind.h" #include "logind-session.h" +#include "logind-session-device.h" #include "dbus-common.h" #include "util.h" @@ -40,6 +41,34 @@ " <arg name=\"who\" type=\"s\"/>\n" \ " <arg name=\"signal\" type=\"s\"/>\n" \ " </method>\n" \ + " <method name=\"TakeControl\"/>\n" \ + " <arg name=\"force\" type=\"b\"/>\n" \ + " </method>\n" \ + " <method name=\"ReleaseControl\"/>\n" \ + " <method name=\"TakeDevice\">\n" \ + " <arg name=\"major\" type=\"u\" direction=\"in\"/>\n" \ + " <arg name=\"minor\" type=\"u\" direction=\"in\"/>\n" \ + " <arg name=\"fd\" type=\"h\" direction=\"out\"/>\n" \ + " <arg name=\"paused\" type=\"b\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"ReleaseDevice\">\n" \ + " <arg name=\"major\" type=\"u\"/>\n" \ + " <arg name=\"minor\" type=\"u\"/>\n" \ + " </method>\n" \ + " <method name=\"PauseDeviceComplete\">\n" \ + " <arg name=\"major\" type=\"u\"/>\n" \ + " <arg name=\"minor\" type=\"u\"/>\n" \ + " </method>\n" \ + " <signal name=\"PauseDevice\">\n" \ + " <arg name=\"major\" type=\"u\"/>\n" \ + " <arg name=\"minor\" type=\"u\"/>\n" \ + " <arg name=\"type\" type=\"s\"/>\n" \ + " </signal>\n" \ + " <signal name=\"ResumeDevice\">\n" \ + " <arg name=\"major\" type=\"u\"/>\n" \ + " <arg name=\"minor\" type=\"u\"/>\n" \ + " <arg name=\"fd\" type=\"h\"/>\n" \ + " </signal>\n" \ " <signal name=\"Lock\"/>\n" \ " <signal name=\"Unlock\"/>\n" \ " <property name=\"Id\" type=\"s\" access=\"read\"/>\n" \ @@ -47,7 +76,6 @@ " <property name=\"Name\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Timestamp\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"TimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ - " <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"VTNr\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"Seat\" type=\"(so)\" access=\"read\"/>\n" \ " <property name=\"TTY\" type=\"s\" access=\"read\"/>\n" \ @@ -56,15 +84,13 @@ " <property name=\"RemoteHost\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"RemoteUser\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Service\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"Scope\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Leader\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"Audit\" type=\"u\" access=\"read\"/>\n" \ " <property name=\"Type\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Class\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Active\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"State\" type=\"s\" access=\"read\"/>\n" \ - " <property name=\"Controllers\" type=\"as\" access=\"read\"/>\n" \ - " <property name=\"ResetControllers\" type=\"as\" access=\"read\"/>\n" \ - " <property name=\"KillProcesses\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \ @@ -195,24 +221,6 @@ static int bus_session_append_idle_hint_since(DBusMessageIter *i, const char *pr return 0; } -static int bus_session_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) { - Session *s = data; - _cleanup_free_ char *t = NULL; - int r; - bool success; - - assert(i); - assert(property); - assert(s); - - r = cg_join_spec(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, &t); - if (r < 0) - return r; - - success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t); - return success ? 0 : -ENOMEM; -} - static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_type, session_type, SessionType); static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_class, session_class, SessionClass); @@ -233,8 +241,8 @@ static int bus_session_append_state(DBusMessageIter *i, const char *property, vo } static int get_session_for_path(Manager *m, const char *path, Session **_s) { + _cleanup_free_ char *id = NULL; Session *s; - char *id; assert(m); assert(path); @@ -248,8 +256,6 @@ static int get_session_for_path(Manager *m, const char *path, Session **_s) { return -ENOMEM; s = hashmap_get(m->sessions, id); - free(id); - if (!s) return -ENOENT; @@ -261,7 +267,6 @@ static const BusProperty bus_login_session_properties[] = { { "Id", bus_property_append_string, "s", offsetof(Session, id), true }, { "Timestamp", bus_property_append_usec, "t", offsetof(Session, timestamp.realtime) }, { "TimestampMonotonic", bus_property_append_usec, "t", offsetof(Session, timestamp.monotonic) }, - { "DefaultControlGroup", bus_session_append_default_cgroup, "s", 0, }, { "VTNr", bus_property_append_uint32, "u", offsetof(Session, vtnr) }, { "Seat", bus_session_append_seat, "(so)", 0 }, { "TTY", bus_property_append_string, "s", offsetof(Session, tty), true }, @@ -270,15 +275,13 @@ static const BusProperty bus_login_session_properties[] = { { "RemoteUser", bus_property_append_string, "s", offsetof(Session, remote_user), true }, { "RemoteHost", bus_property_append_string, "s", offsetof(Session, remote_host), true }, { "Service", bus_property_append_string, "s", offsetof(Session, service), true }, + { "Scope", bus_property_append_string, "s", offsetof(Session, scope), true }, { "Leader", bus_property_append_pid, "u", offsetof(Session, leader) }, { "Audit", bus_property_append_uint32, "u", offsetof(Session, audit_id) }, { "Type", bus_session_append_type, "s", offsetof(Session, type) }, { "Class", bus_session_append_class, "s", offsetof(Session, class) }, { "Active", bus_session_append_active, "b", 0 }, { "State", bus_session_append_state, "s", 0 }, - { "Controllers", bus_property_append_strv, "as", offsetof(Session, controllers), true }, - { "ResetControllers", bus_property_append_strv, "as", offsetof(Session, reset_controllers), true }, - { "KillProcesses", bus_property_append_bool, "b", offsetof(Session, kill_processes) }, { "IdleHint", bus_session_append_idle_hint, "b", 0 }, { "IdleSinceHint", bus_session_append_idle_hint_since, "t", 0 }, { "IdleSinceHintMonotonic", bus_session_append_idle_hint_since, "t", 0 }, @@ -392,6 +395,147 @@ static DBusHandlerResult session_message_dispatch( if (!reply) goto oom; + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "TakeControl")) { + dbus_bool_t force; + unsigned long ul; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_BOOLEAN, &force, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), &error); + if (ul == (unsigned long) -1) + return bus_send_error_reply(connection, message, &error, -EIO); + + if (ul != 0 && (force || ul != s->user->uid)) + return bus_send_error_reply(connection, message, NULL, -EPERM); + + r = session_set_controller(s, bus_message_get_sender_with_fallback(message), force); + 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 (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "ReleaseControl")) { + const char *sender = bus_message_get_sender_with_fallback(message); + + if (!session_is_controller(s, sender)) + return bus_send_error_reply(connection, message, NULL, -EPERM); + + session_drop_controller(s); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "TakeDevice")) { + SessionDevice *sd; + bool b; + dbus_bool_t paused; + uint32_t major, minor; + dev_t dev; + + if (!session_is_controller(s, bus_message_get_sender_with_fallback(message))) + return bus_send_error_reply(connection, message, NULL, -EPERM); + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + dev = makedev(major, minor); + sd = hashmap_get(s->devices, &dev); + if (sd) { + /* We don't allow retrieving a device multiple times. + * The related ReleaseDevice call is not ref-counted. + * The caller should use dup() if it requires more than + * one fd (it would be functionally equivalent). */ + return bus_send_error_reply(connection, message, &error, -EBUSY); + } + + r = session_device_new(s, dev, &sd); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) { + session_device_free(sd); + goto oom; + } + + paused = !sd->active; + b = dbus_message_append_args( + reply, + DBUS_TYPE_UNIX_FD, &sd->fd, + DBUS_TYPE_BOOLEAN, &paused, + DBUS_TYPE_INVALID); + if (!b) { + session_device_free(sd); + return bus_send_error_reply(connection, message, NULL, -ENOMEM); + } + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "ReleaseDevice")) { + SessionDevice *sd; + uint32_t major, minor; + dev_t dev; + + if (!session_is_controller(s, bus_message_get_sender_with_fallback(message))) + return bus_send_error_reply(connection, message, NULL, -EPERM); + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + dev = makedev(major, minor); + sd = hashmap_get(s->devices, &dev); + if (!sd) + return bus_send_error_reply(connection, message, NULL, -ENODEV); + + session_device_free(sd); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "PauseDeviceComplete")) { + SessionDevice *sd; + uint32_t major, minor; + dev_t dev; + + if (!session_is_controller(s, bus_message_get_sender_with_fallback(message))) + return bus_send_error_reply(connection, message, NULL, -EPERM); + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + dev = makedev(major, minor); + sd = hashmap_get(s->devices, &dev); + if (!sd) + return bus_send_error_reply(connection, message, NULL, -ENODEV); + + session_device_complete_pause(sd); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + } else { const BusBoundProperties bps[] = { { "org.freedesktop.login1.Session", bus_login_session_properties, s }, @@ -448,7 +592,7 @@ const DBusObjectPathVTable bus_session_vtable = { }; char *session_bus_path(Session *s) { - _cleanup_free_ char *t; + _cleanup_free_ char *t = NULL; assert(s); @@ -552,3 +696,81 @@ int session_send_lock_all(Manager *m, bool lock) { return r; } + +int session_send_create_reply(Session *s, DBusError *error) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + + assert(s); + + if (!s->create_message) + return 0; + + /* This is called after the session scope was successfully + * created, and finishes where bus_manager_create_session() + * left off. */ + + if (error) { + DBusError buffer; + + dbus_error_init(&buffer); + + if (!dbus_error_is_set(error)) { + dbus_set_error_const(&buffer, DBUS_ERROR_INVALID_ARGS, "Invalid Arguments"); + error = &buffer; + } + + reply = dbus_message_new_error(s->create_message, error->name, error->message); + dbus_error_free(&buffer); + + if (!reply) + return log_oom(); + } else { + _cleanup_close_ int fifo_fd = -1; + _cleanup_free_ char *path = NULL; + const char *cseat; + uint32_t vtnr; + dbus_bool_t exists; + + fifo_fd = session_create_fifo(s); + if (fifo_fd < 0) { + log_error("Failed to create fifo: %s", strerror(-fifo_fd)); + return fifo_fd; + } + + path = session_bus_path(s); + if (!path) + return log_oom(); + + reply = dbus_message_new_method_return(s->create_message); + if (!reply) + return log_oom(); + + cseat = s->seat ? s->seat->id : ""; + vtnr = s->vtnr; + exists = false; + + if (!dbus_message_append_args( + reply, + DBUS_TYPE_STRING, &s->id, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &s->user->runtime_path, + DBUS_TYPE_UNIX_FD, &fifo_fd, + DBUS_TYPE_STRING, &cseat, + DBUS_TYPE_UINT32, &vtnr, + DBUS_TYPE_BOOLEAN, &exists, + DBUS_TYPE_INVALID)) + return log_oom(); + } + + /* Update the state file before we notify the client about the + * result */ + session_save(s); + + if (!dbus_connection_send(s->manager->bus, reply, NULL)) + return log_oom(); + + dbus_message_unref(s->create_message); + s->create_message = NULL; + + return 0; +} diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c new file mode 100644 index 0000000000..6605935f3c --- /dev/null +++ b/src/login/logind-session-device.c @@ -0,0 +1,483 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann + + 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 <assert.h> +#include <fcntl.h> +#include <libudev.h> +#include <linux/input.h> +#include <linux/ioctl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "dbus-common.h" +#include "logind-session-device.h" +#include "util.h" +#include "missing.h" + +enum SessionDeviceNotifications { + SESSION_DEVICE_RESUME, + SESSION_DEVICE_TRY_PAUSE, + SESSION_DEVICE_PAUSE, + SESSION_DEVICE_RELEASE, +}; + +static void session_device_notify(SessionDevice *sd, enum SessionDeviceNotifications type) { + _cleanup_dbus_message_unref_ DBusMessage *m = NULL; + _cleanup_free_ char *path = NULL; + const char *t = NULL; + uint32_t major, minor; + + assert(sd); + + major = major(sd->dev); + minor = minor(sd->dev); + + if (!sd->session->controller) + return; + + path = session_bus_path(sd->session); + if (!path) + return; + + m = dbus_message_new_signal(path, + "org.freedesktop.login1.Session", + (type == SESSION_DEVICE_RESUME) ? "ResumeDevice" : "PauseDevice"); + if (!m) + return; + + if (!dbus_message_set_destination(m, sd->session->controller)) + return; + + switch (type) { + case SESSION_DEVICE_RESUME: + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_UNIX_FD, &sd->fd, + DBUS_TYPE_INVALID)) + return; + break; + case SESSION_DEVICE_TRY_PAUSE: + t = "pause"; + break; + case SESSION_DEVICE_PAUSE: + t = "force"; + break; + case SESSION_DEVICE_RELEASE: + t = "gone"; + break; + default: + return; + } + + if (t && !dbus_message_append_args(m, + DBUS_TYPE_UINT32, &major, + DBUS_TYPE_UINT32, &minor, + DBUS_TYPE_STRING, &t, + DBUS_TYPE_INVALID)) + return; + + dbus_connection_send(sd->session->manager->bus, m, NULL); +} + +static int sd_eviocrevoke(int fd) { + static bool warned; + int r; + + assert(fd >= 0); + + r = ioctl(fd, EVIOCREVOKE, 1); + if (r < 0) { + r = -errno; + if (r == -EINVAL && !warned) { + warned = true; + log_warning("kernel does not support evdev-revocation"); + } + } + + return 0; +} + +static int sd_drmsetmaster(int fd) { + int r; + + assert(fd >= 0); + + r = ioctl(fd, DRM_IOCTL_SET_MASTER, 0); + if (r < 0) + return -errno; + + return 0; +} + +static int sd_drmdropmaster(int fd) { + int r; + + assert(fd >= 0); + + r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0); + if (r < 0) + return -errno; + + return 0; +} + +static int session_device_open(SessionDevice *sd, bool active) { + int fd, r; + + assert(sd->type != DEVICE_TYPE_UNKNOWN); + + /* open device and try to get an udev_device from it */ + fd = open(sd->node, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (fd < 0) + return -errno; + + switch (sd->type) { + case DEVICE_TYPE_DRM: + if (active) { + /* Weird legacy DRM semantics might return an error + * even though we're master. No way to detect that so + * fail at all times and let caller retry in inactive + * state. */ + r = sd_drmsetmaster(fd); + if (r < 0) { + close(fd); + return r; + } + } else { + /* DRM-Master is granted to the first user who opens a + * device automatically (ughh, racy!). Hence, we just + * drop DRM-Master in case we were the first. */ + sd_drmdropmaster(fd); + } + break; + case DEVICE_TYPE_EVDEV: + if (!active) + sd_eviocrevoke(fd); + break; + case DEVICE_TYPE_UNKNOWN: + default: + /* fallback for devices wihout synchronizations */ + break; + } + + return fd; +} + +static int session_device_start(SessionDevice *sd) { + int r; + + assert(sd); + assert(session_is_active(sd->session)); + + if (sd->active) + return 0; + + switch (sd->type) { + case DEVICE_TYPE_DRM: + /* Device is kept open. Simply call drmSetMaster() and hope + * there is no-one else. In case it fails, we keep the device + * paused. Maybe at some point we have a drmStealMaster(). */ + r = sd_drmsetmaster(sd->fd); + if (r < 0) + return r; + break; + case DEVICE_TYPE_EVDEV: + /* Evdev devices are revoked while inactive. Reopen it and we + * are fine. */ + r = session_device_open(sd, true); + if (r < 0) + return r; + close_nointr_nofail(sd->fd); + sd->fd = r; + break; + case DEVICE_TYPE_UNKNOWN: + default: + /* fallback for devices wihout synchronizations */ + break; + } + + sd->active = true; + return 0; +} + +static void session_device_stop(SessionDevice *sd) { + assert(sd); + + if (!sd->active) + return; + + switch (sd->type) { + case DEVICE_TYPE_DRM: + /* On DRM devices we simply drop DRM-Master but keep it open. + * This allows the user to keep resources allocated. The + * CAP_SYS_ADMIN restriction to DRM-Master prevents users from + * circumventing this. */ + sd_drmdropmaster(sd->fd); + break; + case DEVICE_TYPE_EVDEV: + /* Revoke access on evdev file-descriptors during deactivation. + * This will basically prevent any operations on the fd and + * cannot be undone. Good side is: it needs no CAP_SYS_ADMIN + * protection this way. */ + sd_eviocrevoke(sd->fd); + break; + case DEVICE_TYPE_UNKNOWN: + default: + /* fallback for devices without synchronization */ + break; + } + + sd->active = false; +} + +static DeviceType detect_device_type(struct udev_device *dev) { + const char *sysname, *subsystem; + DeviceType type; + + sysname = udev_device_get_sysname(dev); + subsystem = udev_device_get_subsystem(dev); + type = DEVICE_TYPE_UNKNOWN; + + if (streq_ptr(subsystem, "drm")) { + if (startswith(sysname, "card")) + type = DEVICE_TYPE_DRM; + } else if (streq_ptr(subsystem, "input")) { + if (startswith(sysname, "event")) + type = DEVICE_TYPE_EVDEV; + } + + return type; +} + +static int session_device_verify(SessionDevice *sd) { + struct udev_device *dev, *p = NULL; + const char *sp, *node; + int r; + + dev = udev_device_new_from_devnum(sd->session->manager->udev, 'c', sd->dev); + if (!dev) + return -ENODEV; + + sp = udev_device_get_syspath(dev); + node = udev_device_get_devnode(dev); + if (!node) { + r = -EINVAL; + goto err_dev; + } + + /* detect device type so we can find the correct sysfs parent */ + sd->type = detect_device_type(dev); + if (sd->type == DEVICE_TYPE_UNKNOWN) { + r = -ENODEV; + goto err_dev; + } else if (sd->type == DEVICE_TYPE_EVDEV) { + /* for evdev devices we need the parent node as device */ + p = dev; + dev = udev_device_get_parent_with_subsystem_devtype(p, "input", NULL); + if (!dev) { + r = -ENODEV; + goto err_dev; + } + sp = udev_device_get_syspath(dev); + } else if (sd->type != DEVICE_TYPE_DRM) { + /* Prevent opening unsupported devices. Especially devices of + * subsystem "input" must be opened via the evdev node as + * we require EVIOCREVOKE. */ + r = -ENODEV; + goto err_dev; + } + + /* search for an existing seat device and return it if available */ + sd->device = hashmap_get(sd->session->manager->devices, sp); + if (!sd->device) { + /* The caller might have gotten the udev event before we were + * able to process it. Hence, fake the "add" event and let the + * logind-manager handle the new device. */ + r = manager_process_seat_device(sd->session->manager, dev); + if (r < 0) + goto err_dev; + + /* if it's still not available, then the device is invalid */ + sd->device = hashmap_get(sd->session->manager->devices, sp); + if (!sd->device) { + r = -ENODEV; + goto err_dev; + } + } + + if (sd->device->seat != sd->session->seat) { + r = -EPERM; + goto err_dev; + } + + sd->node = strdup(node); + if (!sd->node) { + r = -ENOMEM; + goto err_dev; + } + + r = 0; +err_dev: + udev_device_unref(p ? : dev); + return r; +} + +int session_device_new(Session *s, dev_t dev, SessionDevice **out) { + SessionDevice *sd; + int r; + + assert(s); + assert(out); + + if (!s->seat) + return -EPERM; + + sd = new0(SessionDevice, 1); + if (!sd) + return -ENOMEM; + + sd->session = s; + sd->dev = dev; + sd->fd = -1; + sd->type = DEVICE_TYPE_UNKNOWN; + + r = session_device_verify(sd); + if (r < 0) + goto error; + + r = hashmap_put(s->devices, &sd->dev, sd); + if (r < 0) { + r = -ENOMEM; + goto error; + } + + /* Open the device for the first time. We need a valid fd to pass back + * to the caller. If the session is not active, this _might_ immediately + * revoke access and thus invalidate the fd. But this is still needed + * to pass a valid fd back. */ + sd->active = session_is_active(s); + r = session_device_open(sd, sd->active); + if (r < 0) { + /* EINVAL _may_ mean a master is active; retry inactive */ + if (sd->active && r == -EINVAL) { + sd->active = false; + r = session_device_open(sd, false); + } + if (r < 0) + goto error; + } + sd->fd = r; + + LIST_PREPEND(SessionDevice, sd_by_device, sd->device->session_devices, sd); + + *out = sd; + return 0; + +error: + hashmap_remove(s->devices, &sd->dev); + free(sd->node); + free(sd); + return r; +} + +void session_device_free(SessionDevice *sd) { + assert(sd); + + session_device_stop(sd); + session_device_notify(sd, SESSION_DEVICE_RELEASE); + close_nointr_nofail(sd->fd); + + LIST_REMOVE(SessionDevice, sd_by_device, sd->device->session_devices, sd); + + hashmap_remove(sd->session->devices, &sd->dev); + + free(sd->node); + free(sd); +} + +void session_device_complete_pause(SessionDevice *sd) { + SessionDevice *iter; + Iterator i; + + if (!sd->active) + return; + + session_device_stop(sd); + + /* if not all devices are paused, wait for further completion events */ + HASHMAP_FOREACH(iter, sd->session->devices, i) + if (iter->active) + return; + + /* complete any pending session switch */ + seat_complete_switch(sd->session->seat); +} + +void session_device_resume_all(Session *s) { + SessionDevice *sd; + Iterator i; + int r; + + assert(s); + + HASHMAP_FOREACH(sd, s->devices, i) { + if (!sd->active) { + r = session_device_start(sd); + if (!r) + session_device_notify(sd, SESSION_DEVICE_RESUME); + } + } +} + +void session_device_pause_all(Session *s) { + SessionDevice *sd; + Iterator i; + + assert(s); + + HASHMAP_FOREACH(sd, s->devices, i) { + if (sd->active) { + session_device_stop(sd); + session_device_notify(sd, SESSION_DEVICE_PAUSE); + } + } +} + +unsigned int session_device_try_pause_all(Session *s) { + SessionDevice *sd; + Iterator i; + unsigned int num_pending = 0; + + assert(s); + + HASHMAP_FOREACH(sd, s->devices, i) { + if (sd->active) { + session_device_notify(sd, SESSION_DEVICE_TRY_PAUSE); + ++num_pending; + } + } + + return num_pending; +} diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h new file mode 100644 index 0000000000..61a843d09d --- /dev/null +++ b/src/login/logind-session-device.h @@ -0,0 +1,59 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 David Herrmann + + 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 enum DeviceType DeviceType; +typedef struct SessionDevice SessionDevice; + +#include "list.h" +#include "util.h" +#include "logind.h" +#include "logind-device.h" +#include "logind-seat.h" +#include "logind-session.h" + +enum DeviceType { + DEVICE_TYPE_UNKNOWN, + DEVICE_TYPE_DRM, + DEVICE_TYPE_EVDEV, +}; + +struct SessionDevice { + Session *session; + Device *device; + + dev_t dev; + char *node; + int fd; + bool active; + DeviceType type; + + LIST_FIELDS(struct SessionDevice, sd_by_device); +}; + +int session_device_new(Session *s, dev_t dev, SessionDevice **out); +void session_device_free(SessionDevice *sd); +void session_device_complete_pause(SessionDevice *sd); + +void session_device_resume_all(Session *s); +void session_device_pause_all(Session *s); +unsigned int session_device_try_pause_all(Session *s); diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 662273b07f..27aa335142 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -25,21 +25,38 @@ #include <sys/epoll.h> #include <fcntl.h> -#include "systemd/sd-id128.h" -#include "systemd/sd-messages.h" +#include <systemd/sd-id128.h> +#include <systemd/sd-messages.h> + #include "strv.h" #include "util.h" #include "mkdir.h" #include "path-util.h" -#include "cgroup-util.h" -#include "logind-session.h" #include "fileio.h" +#include "dbus-common.h" +#include "logind-session.h" + +static unsigned devt_hash_func(const void *p) { + uint64_t u = *(const dev_t*)p; + + return uint64_hash_func(&u); +} + +static int devt_compare_func(const void *_a, const void *_b) { + dev_t a, b; -Session* session_new(Manager *m, User *u, const char *id) { + a = *(const dev_t*) _a; + b = *(const dev_t*) _b; + + return a < b ? -1 : (a > b ? 1 : 0); +} + +Session* session_new(Manager *m, const char *id) { Session *s; assert(m); assert(id); + assert(session_id_valid(id)); s = new0(Session, 1); if (!s) @@ -51,9 +68,17 @@ Session* session_new(Manager *m, User *u, const char *id) { return NULL; } + s->devices = hashmap_new(devt_hash_func, devt_compare_func); + if (!s->devices) { + free(s->state_file); + free(s); + return NULL; + } + s->id = path_get_file_name(s->state_file); if (hashmap_put(m->sessions, s->id, s) < 0) { + hashmap_free(s->devices); free(s->state_file); free(s); return NULL; @@ -61,19 +86,25 @@ Session* session_new(Manager *m, User *u, const char *id) { s->manager = m; s->fifo_fd = -1; - s->user = u; - - LIST_PREPEND(Session, sessions_by_user, u->sessions, s); return s; } void session_free(Session *s) { + SessionDevice *sd; + assert(s); if (s->in_gc_queue) LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s); + session_drop_controller(s); + + while ((sd = hashmap_first(s->devices))) + session_device_free(sd); + + hashmap_free(s->devices); + if (s->user) { LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s); @@ -84,15 +115,21 @@ void session_free(Session *s) { if (s->seat) { if (s->seat->active == s) s->seat->active = NULL; + if (s->seat->pending_switch == s) + s->seat->pending_switch = NULL; LIST_REMOVE(Session, sessions_by_seat, s->seat->sessions, s); } - if (s->cgroup_path) - hashmap_remove(s->manager->session_cgroups, s->cgroup_path); + if (s->scope) { + hashmap_remove(s->manager->session_units, s->scope); + free(s->scope); + } + + free(s->scope_job); - free(s->cgroup_path); - strv_free(s->controllers); + if (s->create_message) + dbus_message_unref(s->create_message); free(s->tty); free(s->display); @@ -107,13 +144,24 @@ void session_free(Session *s) { free(s); } +void session_set_user(Session *s, User *u) { + assert(s); + assert(!s->user); + + s->user = u; + LIST_PREPEND(Session, sessions_by_user, u->sessions, s); +} + int session_save(Session *s) { - FILE *f; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *temp_path = NULL; int r = 0; - char *temp_path; assert(s); + if (!s->user) + return -ESTALE; + if (!s->started) return 0; @@ -135,79 +183,61 @@ int session_save(Session *s) { "USER=%s\n" "ACTIVE=%i\n" "STATE=%s\n" - "REMOTE=%i\n" - "KILL_PROCESSES=%i\n", + "REMOTE=%i\n", (unsigned long) s->user->uid, s->user->name, session_is_active(s), session_state_to_string(session_get_state(s)), - s->remote, - s->kill_processes); + s->remote); if (s->type >= 0) - fprintf(f, - "TYPE=%s\n", - session_type_to_string(s->type)); + fprintf(f, "TYPE=%s\n", session_type_to_string(s->type)); if (s->class >= 0) - fprintf(f, - "CLASS=%s\n", - session_class_to_string(s->class)); + fprintf(f, "CLASS=%s\n", session_class_to_string(s->class)); - if (s->cgroup_path) - fprintf(f, - "CGROUP=%s\n", - s->cgroup_path); + if (s->scope) + fprintf(f, "SCOPE=%s\n", s->scope); + + if (s->scope_job) + fprintf(f, "SCOPE_JOB=%s\n", s->scope_job); if (s->fifo_path) - fprintf(f, - "FIFO=%s\n", - s->fifo_path); + fprintf(f, "FIFO=%s\n", s->fifo_path); if (s->seat) - fprintf(f, - "SEAT=%s\n", - s->seat->id); + fprintf(f, "SEAT=%s\n", s->seat->id); if (s->tty) - fprintf(f, - "TTY=%s\n", - s->tty); + fprintf(f, "TTY=%s\n", s->tty); if (s->display) - fprintf(f, - "DISPLAY=%s\n", - s->display); + fprintf(f, "DISPLAY=%s\n", s->display); if (s->remote_host) - fprintf(f, - "REMOTE_HOST=%s\n", - s->remote_host); + fprintf(f, "REMOTE_HOST=%s\n", s->remote_host); if (s->remote_user) - fprintf(f, - "REMOTE_USER=%s\n", - s->remote_user); + fprintf(f, "REMOTE_USER=%s\n", s->remote_user); if (s->service) - fprintf(f, - "SERVICE=%s\n", - s->service); + fprintf(f, "SERVICE=%s\n", s->service); - if (s->seat && seat_can_multi_session(s->seat)) - fprintf(f, - "VTNR=%i\n", - s->vtnr); + if (s->seat && seat_has_vts(s->seat)) + fprintf(f, "VTNR=%i\n", s->vtnr); if (s->leader > 0) - fprintf(f, - "LEADER=%lu\n", - (unsigned long) s->leader); + fprintf(f, "LEADER=%lu\n", (unsigned long) s->leader); if (s->audit_id > 0) + fprintf(f, "AUDIT=%"PRIu32"\n", s->audit_id); + + if (dual_timestamp_is_set(&s->timestamp)) fprintf(f, - "AUDIT=%llu\n", - (unsigned long long) s->audit_id); + "REALTIME=%llu\n" + "MONOTONIC=%llu\n", + (unsigned long long) s->timestamp.realtime, + (unsigned long long) s->timestamp.monotonic); fflush(f); @@ -217,9 +247,6 @@ int session_save(Session *s) { unlink(temp_path); } - fclose(f); - free(temp_path); - finish: if (r < 0) log_error("Failed to save session data for %s: %s", s->id, strerror(-r)); @@ -228,14 +255,15 @@ finish: } int session_load(Session *s) { - char *remote = NULL, - *kill_processes = NULL, + _cleanup_free_ char *remote = NULL, *seat = NULL, *vtnr = NULL, *leader = NULL, - *audit_id = NULL, *type = NULL, - *class = NULL; + *class = NULL, + *uid = NULL, + *realtime = NULL, + *monotonic = NULL; int k, r; @@ -243,8 +271,8 @@ int session_load(Session *s) { r = parse_env_file(s->state_file, NEWLINE, "REMOTE", &remote, - "KILL_PROCESSES", &kill_processes, - "CGROUP", &s->cgroup_path, + "SCOPE", &s->scope, + "SCOPE_JOB", &s->scope_job, "FIFO", &s->fifo_path, "SEAT", &seat, "TTY", &s->tty, @@ -256,10 +284,39 @@ int session_load(Session *s) { "LEADER", &leader, "TYPE", &type, "CLASS", &class, + "UID", &uid, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, NULL); - if (r < 0) - goto finish; + if (r < 0) { + log_error("Failed to read %s: %s", s->state_file, strerror(-r)); + return r; + } + + if (!s->user) { + uid_t u; + User *user; + + if (!uid) { + log_error("UID not specified for session %s", s->id); + return -ENOENT; + } + + r = parse_uid(uid, &u); + if (r < 0) { + log_error("Failed to parse UID value %s for session %s.", uid, s->id); + return r; + } + + user = hashmap_get(s->manager->users, ULONG_TO_PTR((unsigned long) u)); + if (!user) { + log_error("User of session %s not known.", s->id); + return -ENOENT; + } + + session_set_user(s, user); + } if (remote) { k = parse_boolean(remote); @@ -267,12 +324,6 @@ int session_load(Session *s) { s->remote = k; } - if (kill_processes) { - k = parse_boolean(kill_processes); - if (k >= 0) - s->kill_processes = k; - } - if (seat && !s->seat) { Seat *o; @@ -281,7 +332,7 @@ int session_load(Session *s) { seat_attach_session(o, s); } - if (vtnr && s->seat && seat_can_multi_session(s->seat)) { + if (vtnr && s->seat && seat_has_vts(s->seat)) { int v; k = safe_atoi(vtnr, &v); @@ -324,25 +375,26 @@ int session_load(Session *s) { close_nointr_nofail(fd); } -finish: - free(remote); - free(kill_processes); - free(seat); - free(vtnr); - free(leader); - free(audit_id); - free(class); + if (realtime) { + unsigned long long l; + if (sscanf(realtime, "%llu", &l) > 0) + s->timestamp.realtime = l; + } + + if (monotonic) { + unsigned long long l; + if (sscanf(monotonic, "%llu", &l) > 0) + s->timestamp.monotonic = l; + } return r; } int session_activate(Session *s) { - int r; + unsigned int num_pending; assert(s); - - if (s->vtnr < 0) - return -ENOTSUP; + assert(s->user); if (!s->seat) return -ENOTSUP; @@ -350,17 +402,34 @@ int session_activate(Session *s) { if (s->seat->active == s) return 0; - assert(seat_is_vtconsole(s->seat)); + /* on seats with VTs, we let VTs manage session-switching */ + if (seat_has_vts(s->seat)) { + if (s->vtnr <= 0) + return -ENOTSUP; - r = chvt(s->vtnr); - if (r < 0) - return r; + return chvt(s->vtnr); + } + + /* On seats without VTs, we implement session-switching in logind. We + * try to pause all session-devices and wait until the session + * controller acknowledged them. Once all devices are asleep, we simply + * switch the active session and be done. + * We save the session we want to switch to in seat->pending_switch and + * seat_complete_switch() will perform the final switch. */ - return seat_set_active(s->seat, s); + s->seat->pending_switch = s; + + /* if no devices are running, immediately perform the session switch */ + num_pending = session_device_try_pause_all(s); + if (!num_pending) + seat_complete_switch(s->seat); + + return 0; } static int session_link_x11_socket(Session *s) { - char *t, *f, *c; + _cleanup_free_ char *t = NULL, *f = NULL; + char *c; size_t k; assert(s); @@ -384,7 +453,6 @@ static int session_link_x11_socket(Session *s) { if (access(f, F_OK) < 0) { log_warning("Session %s has display %s with non-existing socket %s.", s->id, s->display, f); - free(f); return -ENOENT; } @@ -393,10 +461,8 @@ static int session_link_x11_socket(Session *s) { * path is owned by the user */ t = strappend(s->user->runtime_path, "/X11-display"); - if (!t) { - free(f); + if (!t) return log_oom(); - } if (link(f, t) < 0) { if (errno == EEXIST) { @@ -416,128 +482,60 @@ static int session_link_x11_socket(Session *s) { } log_error("Failed to link %s to %s: %m", f, t); - free(f); - free(t); return -errno; } } done: log_info("Linked %s to %s.", f, t); - free(f); - free(t); - s->user->display = s; return 0; } -static int session_create_one_group(Session *s, const char *controller, const char *path) { - int r; - - assert(s); - assert(path); - - if (s->leader > 0) { - r = cg_create_and_attach(controller, path, s->leader); - if (r < 0) - r = cg_create(controller, path, NULL); - } else - r = cg_create(controller, path, NULL); - - if (r < 0) - return r; - - r = cg_set_task_access(controller, path, 0644, s->user->uid, s->user->gid, -1); - if (r >= 0) - r = cg_set_group_access(controller, path, 0755, s->user->uid, s->user->gid); - - return r; -} - -static int session_create_cgroup(Session *s) { - char **k; - char *p; +static int session_start_scope(Session *s) { + DBusError error; int r; assert(s); assert(s->user); - assert(s->user->cgroup_path); + assert(s->user->slice); - if (!s->cgroup_path) { - _cleanup_free_ char *name = NULL, *escaped = NULL; + dbus_error_init(&error); - name = strappend(s->id, ".session"); - if (!name) - return log_oom(); + if (!s->scope) { + _cleanup_free_ char *description = NULL; + const char *kill_mode; + char *scope, *job; - escaped = cg_escape(name); - if (!escaped) + description = strjoin("Session ", s->id, " of user ", s->user->name, NULL); + if (!description) return log_oom(); - p = strjoin(s->user->cgroup_path, "/", escaped, NULL); - if (!p) + scope = strjoin("session-", s->id, ".scope", NULL); + if (!scope) return log_oom(); - } else - p = s->cgroup_path; - - r = session_create_one_group(s, SYSTEMD_CGROUP_CONTROLLER, p); - if (r < 0) { - log_error("Failed to create "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r)); - free(p); - s->cgroup_path = NULL; - return r; - } - - s->cgroup_path = p; - STRV_FOREACH(k, s->controllers) { + kill_mode = manager_shall_kill(s->manager, s->user->name) ? "control-group" : "none"; - if (strv_contains(s->reset_controllers, *k)) - continue; + r = manager_start_scope(s->manager, scope, s->leader, s->user->slice, description, "systemd-user-sessions.service", kill_mode, &error, &job); + if (r < 0) { + log_error("Failed to start session scope %s: %s %s", + scope, bus_error(&error, r), error.name); + dbus_error_free(&error); - r = session_create_one_group(s, *k, p); - if (r < 0) - log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r)); - } - - STRV_FOREACH(k, s->manager->controllers) { - - if (strv_contains(s->reset_controllers, *k) || - strv_contains(s->manager->reset_controllers, *k) || - strv_contains(s->controllers, *k)) - continue; - - r = session_create_one_group(s, *k, p); - if (r < 0) - log_warning("Failed to create %s:%s: %s", *k, p, strerror(-r)); - } - - if (s->leader > 0) { - - STRV_FOREACH(k, s->reset_controllers) { - r = cg_attach(*k, "/", s->leader); - if (r < 0) - log_warning("Failed to reset controller %s: %s", *k, strerror(-r)); - - } - - STRV_FOREACH(k, s->manager->reset_controllers) { - - if (strv_contains(s->reset_controllers, *k) || - strv_contains(s->controllers, *k)) - continue; - - r = cg_attach(*k, "/", s->leader); - if (r < 0) - log_warning("Failed to reset controller %s: %s", *k, strerror(-r)); + free(scope); + return r; + } else { + s->scope = scope; + free(s->scope_job); + s->scope_job = job; } } - r = hashmap_put(s->manager->session_cgroups, s->cgroup_path, s); - if (r < 0) - log_warning("Failed to create mapping between cgroup and session"); + if (s->scope) + hashmap_put(s->manager->session_units, s->scope, s); return 0; } @@ -546,7 +544,9 @@ int session_start(Session *s) { int r; assert(s); - assert(s->user); + + if (!s->user) + return -ESTALE; if (s->started) return 0; @@ -555,6 +555,11 @@ int session_start(Session *s) { if (r < 0) return r; + /* Create cgroup */ + r = session_start_scope(s); + if (r < 0) + return r; + log_struct(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG, MESSAGE_ID(SD_MESSAGE_SESSION_START), "SESSION_ID=%s", s->id, @@ -563,15 +568,11 @@ int session_start(Session *s) { "MESSAGE=New session %s of user %s.", s->id, s->user->name, NULL); - /* Create cgroup */ - r = session_create_cgroup(s); - if (r < 0) - return r; - /* Create X11 symlink */ session_link_x11_socket(s); - dual_timestamp_get(&s->timestamp); + if (!dual_timestamp_is_set(&s->timestamp)) + dual_timestamp_get(&s->timestamp); if (s->seat) seat_read_active_vt(s->seat); @@ -598,79 +599,33 @@ int session_start(Session *s) { return 0; } -static bool session_shall_kill(Session *s) { - assert(s); - - if (!s->kill_processes) - return false; - - if (strv_contains(s->manager->kill_exclude_users, s->user->name)) - return false; - - if (strv_isempty(s->manager->kill_only_users)) - return true; - - return strv_contains(s->manager->kill_only_users, s->user->name); -} - -static int session_terminate_cgroup(Session *s) { +static int session_stop_scope(Session *s) { + DBusError error; + char *job; int r; - char **k; assert(s); - if (!s->cgroup_path) - return 0; - - cg_trim(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false); + dbus_error_init(&error); - if (session_shall_kill(s)) { - - r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true); - if (r < 0) - log_error("Failed to kill session cgroup: %s", strerror(-r)); - - } else { - if (s->leader > 0) { - Session *t; - - /* We still send a HUP to the leader process, - * even if we are not supposed to kill the - * whole cgroup. But let's first check the - * leader still exists and belongs to our - * session... */ - - r = manager_get_session_by_pid(s->manager, s->leader, &t); - if (r > 0 && t == s) { - kill(s->leader, SIGTERM); /* for normal processes */ - kill(s->leader, SIGHUP); /* for shells */ - kill(s->leader, SIGCONT); /* in case they are stopped */ - } - } + if (!s->scope) + return 0; - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, true); - if (r < 0) - log_error("Failed to check session cgroup: %s", strerror(-r)); - else if (r > 0) { - r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path); - if (r < 0) - log_error("Failed to delete session cgroup: %s", strerror(-r)); - } + r = manager_stop_unit(s->manager, s->scope, &error, &job); + if (r < 0) { + log_error("Failed to stop session scope: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; } - STRV_FOREACH(k, s->user->manager->controllers) - cg_trim(*k, s->cgroup_path, true); - - hashmap_remove(s->manager->session_cgroups, s->cgroup_path); - - free(s->cgroup_path); - s->cgroup_path = NULL; + free(s->scope_job); + s->scope_job = job; return 0; } static int session_unlink_x11_socket(Session *s) { - char *t; + _cleanup_free_ char *t = NULL; int r; assert(s); @@ -686,16 +641,34 @@ static int session_unlink_x11_socket(Session *s) { return log_oom(); r = unlink(t); - free(t); - return r < 0 ? -errno : 0; } int session_stop(Session *s) { - int r = 0, k; + int r; + + assert(s); + + if (!s->user) + return -ESTALE; + + /* Kill cgroup */ + r = session_stop_scope(s); + + session_save(s); + + return r; +} + +int session_finalize(Session *s) { + int r = 0; + SessionDevice *sd; assert(s); + if (!s->user) + return -ESTALE; + if (s->started) log_struct(s->type == SESSION_TTY || s->type == SESSION_X11 ? LOG_INFO : LOG_DEBUG, MESSAGE_ID(SD_MESSAGE_SESSION_STOP), @@ -705,10 +678,9 @@ int session_stop(Session *s) { "MESSAGE=Removed session %s.", s->id, NULL); - /* Kill cgroup */ - k = session_terminate_cgroup(s); - if (k < 0) - r = k; + /* Kill session devices */ + while ((sd = hashmap_first(s->devices))) + session_device_free(sd); /* Remove X11 symlink */ session_unlink_x11_socket(s); @@ -717,10 +689,10 @@ int session_stop(Session *s) { session_add_to_gc_queue(s); user_add_to_gc_queue(s->user); - if (s->started) + if (s->started) { session_send_signal(s, false); - - s->started = false; + s->started = false; + } if (s->seat) { if (s->seat->active == s) @@ -817,28 +789,6 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) { goto found_atime; } - /* For other TTY sessions, let's find the most recent atime of - * the ttys of any of the processes of the session */ - if (s->cgroup_path) { - _cleanup_fclose_ FILE *f = NULL; - - if (cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, &f) >= 0) { - pid_t pid; - - atime = 0; - while (cg_read_pid(f, &pid) > 0) { - usec_t a; - - if (get_process_ctty_atime(pid, &a) >= 0) - if (atime == 0 || atime < a) - atime = a; - } - - if (atime != 0) - goto found_atime; - } - } - dont_know: if (t) *t = s->idle_hint_timestamp; @@ -961,8 +911,10 @@ int session_check_gc(Session *s, bool drop_not_started) { if (drop_not_started && !s->started) return 0; - if (s->fifo_fd >= 0) { + if (!s->user) + return 0; + if (s->fifo_fd >= 0) { r = pipe_eof(s->fifo_fd); if (r < 0) return r; @@ -971,15 +923,11 @@ int session_check_gc(Session *s, bool drop_not_started) { return 1; } - if (s->cgroup_path) { + if (s->scope_job) + return 1; - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, false); - if (r < 0) - return r; - - if (r <= 0) - return 1; - } + if (s->scope) + return manager_unit_is_active(s->manager, s->scope) != 0; return 0; } @@ -997,6 +945,12 @@ void session_add_to_gc_queue(Session *s) { SessionState session_get_state(Session *s) { assert(s); + if (s->closing) + return SESSION_CLOSING; + + if (s->scope_job) + return SESSION_OPENING; + if (s->fifo_fd < 0) return SESSION_CLOSING; @@ -1007,47 +961,69 @@ SessionState session_get_state(Session *s) { } int session_kill(Session *s, KillWho who, int signo) { - int r = 0; - Set *pid_set = NULL; - assert(s); - if (!s->cgroup_path) + if (!s->scope) return -ESRCH; - if (s->leader <= 0 && who == KILL_LEADER) - return -ESRCH; + return manager_kill_unit(s->manager, s->scope, who, signo, NULL); +} - if (s->leader > 0) - if (kill(s->leader, signo) < 0) - r = -errno; +bool session_is_controller(Session *s, const char *sender) +{ + assert(s); + + return streq_ptr(s->controller, sender); +} - if (who == KILL_ALL) { - int q; +int session_set_controller(Session *s, const char *sender, bool force) { + char *t; + int r; - pid_set = set_new(trivial_hash_func, trivial_compare_func); - if (!pid_set) - return -ENOMEM; + assert(s); + assert(sender); - if (s->leader > 0) { - q = set_put(pid_set, LONG_TO_PTR(s->leader)); - if (q < 0) - r = q; - } + if (session_is_controller(s, sender)) + return 0; + if (s->controller && !force) + return -EBUSY; - q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_path, signo, false, true, false, pid_set); - if (q < 0) - if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; + t = strdup(sender); + if (!t) + return -ENOMEM; + + r = manager_watch_busname(s->manager, sender); + if (r) { + free(t); + return r; } - if (pid_set) - set_free(pid_set); + session_drop_controller(s); - return r; + s->controller = t; + return 0; +} + +void session_drop_controller(Session *s) { + SessionDevice *sd; + + assert(s); + + if (!s->controller) + return; + + manager_drop_busname(s->manager, s->controller); + free(s->controller); + s->controller = NULL; + + /* Drop all devices as they're now unused. Do that after the controller + * is released to avoid sending out useles dbus signals. */ + while ((sd = hashmap_first(s->devices))) + session_device_free(sd); } -static const char* const session_state_table[_SESSION_TYPE_MAX] = { +static const char* const session_state_table[_SESSION_STATE_MAX] = { + [SESSION_OPENING] = "opening", [SESSION_ONLINE] = "online", [SESSION_ACTIVE] = "active", [SESSION_CLOSING] = "closing" diff --git a/src/login/logind-session.h b/src/login/logind-session.h index a73df3a3bc..f175a8995e 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -22,17 +22,21 @@ ***/ typedef struct Session Session; +typedef enum KillWho KillWho; #include "list.h" #include "util.h" #include "logind.h" #include "logind-seat.h" +#include "logind-session-device.h" #include "logind-user.h" +#include "login-shared.h" typedef enum SessionState { + SESSION_OPENING, /* Session scope is being created */ SESSION_ONLINE, /* Logged in */ SESSION_ACTIVE, /* Logged in and in the fg */ - SESSION_CLOSING, /* Logged out, but processes still remain */ + SESSION_CLOSING, /* Logged out, but scope is still there */ _SESSION_STATE_MAX, _SESSION_STATE_INVALID = -1 } SessionState; @@ -54,12 +58,12 @@ typedef enum SessionType { _SESSION_TYPE_INVALID = -1 } SessionType; -typedef enum KillWho { +enum KillWho { KILL_LEADER, KILL_ALL, _KILL_WHO_MAX, _KILL_WHO_INVALID = -1 -} KillWho; +}; struct Session { Manager *manager; @@ -80,9 +84,11 @@ struct Session { bool remote; char *remote_user; char *remote_host; - char *service; + char *scope; + char *scope_job; + int vtnr; Seat *seat; @@ -92,15 +98,17 @@ struct Session { int fifo_fd; char *fifo_path; - char *cgroup_path; - char **controllers, **reset_controllers; - bool idle_hint; dual_timestamp idle_hint_timestamp; - bool kill_processes; bool in_gc_queue:1; bool started:1; + bool closing:1; + + DBusMessage *create_message; + + char *controller; + Hashmap *devices; LIST_FIELDS(Session, sessions_by_user); LIST_FIELDS(Session, sessions_by_seat); @@ -108,8 +116,9 @@ struct Session { LIST_FIELDS(Session, gc_queue); }; -Session *session_new(Manager *m, User *u, const char *id); +Session *session_new(Manager *m, const char *id); void session_free(Session *s); +void session_set_user(Session *s, User *u); int session_check_gc(Session *s, bool drop_not_started); void session_add_to_gc_queue(Session *s); int session_activate(Session *s); @@ -120,6 +129,7 @@ int session_create_fifo(Session *s); void session_remove_fifo(Session *s); int session_start(Session *s); int session_stop(Session *s); +int session_finalize(Session *s); int session_save(Session *s); int session_load(Session *s); int session_kill(Session *s, KillWho who, int signo); @@ -135,6 +145,8 @@ int session_send_changed(Session *s, const char *properties); int session_send_lock(Session *s, bool lock); int session_send_lock_all(Manager *m, bool lock); +int session_send_create_reply(Session *s, DBusError *error); + const char* session_state_to_string(SessionState t) _const_; SessionState session_state_from_string(const char *s) _pure_; @@ -146,3 +158,7 @@ SessionClass session_class_from_string(const char *s) _pure_; const char *kill_who_to_string(KillWho k) _const_; KillWho kill_who_from_string(const char *s) _pure_; + +bool session_is_controller(Session *s, const char *sender); +int session_set_controller(Session *s, const char *sender, bool force); +void session_drop_controller(Session *s); diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c index 3ec3ff8e61..fa2ecba53c 100644 --- a/src/login/logind-user-dbus.c +++ b/src/login/logind-user-dbus.c @@ -38,15 +38,15 @@ " <property name=\"Timestamp\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"TimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"RuntimePath\" type=\"s\" access=\"read\"/>\n" \ - " <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Service\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"Slice\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Display\" type=\"(so)\" access=\"read\"/>\n" \ " <property name=\"State\" type=\"s\" access=\"read\"/>\n" \ " <property name=\"Sessions\" type=\"a(so)\" access=\"read\"/>\n" \ " <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n" \ " <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \ " <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \ - " </interface>\n" \ + " </interface>\n" #define INTROSPECTION \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ @@ -185,24 +185,6 @@ static int bus_user_append_idle_hint_since(DBusMessageIter *i, const char *prope return 0; } -static int bus_user_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) { - User *u = data; - _cleanup_free_ char *t = NULL; - int r; - bool success; - - assert(i); - assert(property); - assert(u); - - r = cg_join_spec(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &t); - if (r < 0) - return r; - - success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t); - return success ? 0 : -ENOMEM; -} - static int get_user_for_path(Manager *m, const char *path, User **_u) { User *u; unsigned long lu; @@ -212,10 +194,10 @@ static int get_user_for_path(Manager *m, const char *path, User **_u) { assert(path); assert(_u); - if (!startswith(path, "/org/freedesktop/login1/user/")) + if (!startswith(path, "/org/freedesktop/login1/user/_")) return -EINVAL; - r = safe_atolu(path + 29, &lu); + r = safe_atolu(path + 30, &lu); if (r < 0) return r; @@ -234,8 +216,8 @@ static const BusProperty bus_login_user_properties[] = { { "Timestamp", bus_property_append_usec, "t", offsetof(User, timestamp.realtime) }, { "TimestampMonotonic", bus_property_append_usec, "t", offsetof(User, timestamp.monotonic) }, { "RuntimePath", bus_property_append_string, "s", offsetof(User, runtime_path), true }, - { "DefaultControlGroup", bus_user_append_default_cgroup, "s", 0 }, { "Service", bus_property_append_string, "s", offsetof(User, service), true }, + { "Slice", bus_property_append_string, "s", offsetof(User, slice), true }, { "Display", bus_user_append_display, "(so)", 0 }, { "State", bus_user_append_state, "s", 0 }, { "Sessions", bus_user_append_sessions, "a(so)", 0 }, @@ -348,7 +330,7 @@ char *user_bus_path(User *u) { assert(u); - if (asprintf(&s, "/org/freedesktop/login1/user/%llu", (unsigned long long) u->uid) < 0) + if (asprintf(&s, "/org/freedesktop/login1/user/_%llu", (unsigned long long) u->uid) < 0) return NULL; return s; diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 9e2cbf646b..adbe638d46 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -23,13 +23,16 @@ #include <unistd.h> #include <errno.h> -#include "logind-user.h" #include "util.h" #include "mkdir.h" #include "cgroup-util.h" #include "hashmap.h" #include "strv.h" #include "fileio.h" +#include "special.h" +#include "unit-name.h" +#include "dbus-common.h" +#include "logind-user.h" User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) { User *u; @@ -42,29 +45,27 @@ User* user_new(Manager *m, uid_t uid, gid_t gid, const char *name) { return NULL; u->name = strdup(name); - if (!u->name) { - free(u); - return NULL; - } + if (!u->name) + goto fail; - if (asprintf(&u->state_file, "/run/systemd/users/%lu", (unsigned long) uid) < 0) { - free(u->name); - free(u); - return NULL; - } + if (asprintf(&u->state_file, "/run/systemd/users/%lu", (unsigned long) uid) < 0) + goto fail; - if (hashmap_put(m->users, ULONG_TO_PTR((unsigned long) uid), u) < 0) { - free(u->state_file); - free(u->name); - free(u); - return NULL; - } + if (hashmap_put(m->users, ULONG_TO_PTR((unsigned long) uid), u) < 0) + goto fail; u->manager = m; u->uid = uid; u->gid = gid; return u; + +fail: + free(u->state_file); + free(u->name); + free(u); + + return NULL; } void user_free(User *u) { @@ -76,11 +77,19 @@ void user_free(User *u) { while (u->sessions) session_free(u->sessions); - if (u->cgroup_path) - hashmap_remove(u->manager->user_cgroups, u->cgroup_path); - free(u->cgroup_path); + if (u->slice) { + hashmap_remove(u->manager->user_units, u->slice); + free(u->slice); + } + + if (u->service) { + hashmap_remove(u->manager->user_units, u->service); + free(u->service); + } + + free(u->slice_job); + free(u->service_job); - free(u->service); free(u->runtime_path); hashmap_remove(u->manager->users, ULONG_TO_PTR((unsigned long) u->uid)); @@ -91,9 +100,9 @@ void user_free(User *u) { } int user_save(User *u) { - FILE *f; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; int r; - char *temp_path; assert(u); assert(u->state_file); @@ -118,25 +127,28 @@ int user_save(User *u) { u->name, user_state_to_string(user_get_state(u))); - if (u->cgroup_path) - fprintf(f, - "CGROUP=%s\n", - u->cgroup_path); - if (u->runtime_path) - fprintf(f, - "RUNTIME=%s\n", - u->runtime_path); + fprintf(f, "RUNTIME=%s\n", u->runtime_path); if (u->service) - fprintf(f, - "SERVICE=%s\n", - u->service); + fprintf(f, "SERVICE=%s\n", u->service); + if (u->service_job) + fprintf(f, "SERVICE_JOB=%s\n", u->service_job); + + if (u->slice) + fprintf(f, "SLICE=%s\n", u->slice); + if (u->slice_job) + fprintf(f, "SLICE_JOB=%s\n", u->slice_job); if (u->display) + fprintf(f, "DISPLAY=%s\n", u->display->id); + + if (dual_timestamp_is_set(&u->timestamp)) fprintf(f, - "DISPLAY=%s\n", - u->display->id); + "REALTIME=%llu\n" + "MONOTONIC=%llu\n", + (unsigned long long) u->timestamp.realtime, + (unsigned long long) u->timestamp.monotonic); if (u->sessions) { Session *i; @@ -233,9 +245,6 @@ int user_save(User *u) { unlink(temp_path); } - fclose(f); - free(temp_path); - finish: if (r < 0) log_error("Failed to save user data for %s: %s", u->name, strerror(-r)); @@ -244,21 +253,23 @@ finish: } int user_load(User *u) { - int r; - char *display = NULL; + _cleanup_free_ char *display = NULL, *realtime = NULL, *monotonic = NULL; Session *s = NULL; + int r; assert(u); r = parse_env_file(u->state_file, NEWLINE, - "CGROUP", &u->cgroup_path, - "RUNTIME", &u->runtime_path, - "SERVICE", &u->service, - "DISPLAY", &display, + "RUNTIME", &u->runtime_path, + "SERVICE", &u->service, + "SERVICE_JOB", &u->service_job, + "SLICE", &u->slice, + "SLICE_JOB", &u->slice_job, + "DISPLAY", &display, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, NULL); if (r < 0) { - free(display); - if (r == -ENOENT) return 0; @@ -266,14 +277,24 @@ int user_load(User *u) { return r; } - if (display) { + if (display) s = hashmap_get(u->manager->sessions, display); - free(display); - } if (s && s->display && display_is_local(s->display)) u->display = s; + if (realtime) { + unsigned long long l; + if (sscanf(realtime, "%llu", &l) > 0) + u->timestamp.realtime = l; + } + + if (monotonic) { + unsigned long long l; + if (sscanf(monotonic, "%llu", &l) > 0) + u->timestamp.monotonic = l; + } + return r; } @@ -307,60 +328,76 @@ static int user_mkdir_runtime_path(User *u) { return 0; } -static int user_create_cgroup(User *u) { - char **k; - char *p; +static int user_start_slice(User *u) { + DBusError error; + char *job; int r; assert(u); - if (!u->cgroup_path) { - _cleanup_free_ char *name = NULL, *escaped = NULL; - - if (asprintf(&name, "%lu.user", (unsigned long) u->uid) < 0) - return log_oom(); - - escaped = cg_escape(name); - if (!escaped) - return log_oom(); - - p = strjoin(u->manager->cgroup_path, "/", escaped, NULL); - if (!p) - return log_oom(); - } else - p = u->cgroup_path; + dbus_error_init(&error); - r = cg_create(SYSTEMD_CGROUP_CONTROLLER, p, NULL); - if (r < 0) { - log_error("Failed to create cgroup "SYSTEMD_CGROUP_CONTROLLER":%s: %s", p, strerror(-r)); - free(p); - u->cgroup_path = NULL; - return r; - } + if (!u->slice) { + char lu[DECIMAL_STR_MAX(unsigned long) + 1], *slice; + sprintf(lu, "%lu", (unsigned long) u->uid); - u->cgroup_path = p; + r = build_subslice(SPECIAL_USER_SLICE, lu, &slice); + if (r < 0) + return r; - STRV_FOREACH(k, u->manager->controllers) { + r = manager_start_unit(u->manager, slice, &error, &job); + if (r < 0) { + log_error("Failed to start user slice: %s", bus_error(&error, r)); + dbus_error_free(&error); - if (strv_contains(u->manager->reset_controllers, *k)) - continue; + free(slice); + } else { + u->slice = slice; - r = cg_create(*k, p, NULL); - if (r < 0) - log_warning("Failed to create cgroup %s:%s: %s", *k, p, strerror(-r)); + free(u->slice_job); + u->slice_job = job; + } } - r = hashmap_put(u->manager->user_cgroups, u->cgroup_path, u); - if (r < 0) - log_warning("Failed to create mapping between cgroup and user"); + if (u->slice) + hashmap_put(u->manager->user_units, u->slice, u); return 0; } static int user_start_service(User *u) { + DBusError error; + char *job; + int r; + assert(u); - /* FIXME: Fill me in later ... */ + dbus_error_init(&error); + + if (!u->service) { + char lu[DECIMAL_STR_MAX(unsigned long) + 1], *service; + sprintf(lu, "%lu", (unsigned long) u->uid); + + service = unit_name_build("user", lu, ".service"); + if (!service) + return log_oom(); + + r = manager_start_unit(u->manager, service, &error, &job); + if (r < 0) { + log_error("Failed to start user service: %s", bus_error(&error, r)); + dbus_error_free(&error); + + free(service); + } else { + u->service = service; + + free(u->service_job); + u->service_job = job; + } + } + + if (u->service) + hashmap_put(u->manager->user_units, u->service, u); return 0; } @@ -381,7 +418,7 @@ int user_start(User *u) { return r; /* Create cgroup */ - r = user_create_cgroup(u); + r = user_start_slice(u); if (r < 0) return r; @@ -390,7 +427,8 @@ int user_start(User *u) { if (r < 0) return r; - dual_timestamp_get(&u->timestamp); + if (!dual_timestamp_is_set(&u->timestamp)) + dual_timestamp_get(&u->timestamp); u->started = true; @@ -402,66 +440,52 @@ int user_start(User *u) { return 0; } -static int user_stop_service(User *u) { - assert(u); - - if (!u->service) - return 0; - - return 0; -} +static int user_stop_slice(User *u) { + DBusError error; + char *job; + int r; -static int user_shall_kill(User *u) { assert(u); - if (!u->manager->kill_user_processes) - return false; + dbus_error_init(&error); + + if (!u->slice) + return 0; - if (strv_contains(u->manager->kill_exclude_users, u->name)) - return false; + r = manager_stop_unit(u->manager, u->slice, &error, &job); + if (r < 0) { + log_error("Failed to stop user slice: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; + } - if (strv_isempty(u->manager->kill_only_users)) - return true; + free(u->slice_job); + u->slice_job = job; - return strv_contains(u->manager->kill_only_users, u->name); + return r; } -static int user_terminate_cgroup(User *u) { +static int user_stop_service(User *u) { + DBusError error; + char *job; int r; - char **k; assert(u); - if (!u->cgroup_path) - return 0; - - cg_trim(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false); - - if (user_shall_kill(u)) { + dbus_error_init(&error); - r = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true); - if (r < 0) - log_error("Failed to kill user cgroup: %s", strerror(-r)); - } else { + if (!u->service) + return 0; - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true); - if (r < 0) - log_error("Failed to check user cgroup: %s", strerror(-r)); - else if (r > 0) { - r = cg_delete(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); - if (r < 0) - log_error("Failed to delete user cgroup: %s", strerror(-r)); - } else - r = -EBUSY; + r = manager_stop_unit(u->manager, u->service, &error, &job); + if (r < 0) { + log_error("Failed to stop user service: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; } - STRV_FOREACH(k, u->manager->controllers) - cg_trim(*k, u->cgroup_path, true); - - hashmap_remove(u->manager->user_cgroups, u->cgroup_path); - - free(u->cgroup_path); - u->cgroup_path = NULL; + free(u->service_job); + u->service_job = job; return r; } @@ -489,9 +513,6 @@ int user_stop(User *u) { int r = 0, k; assert(u); - if (u->started) - log_debug("User %s logged out.", u->name); - LIST_FOREACH(sessions_by_user, s, u->sessions) { k = session_stop(s); if (k < 0) @@ -504,10 +525,30 @@ int user_stop(User *u) { r = k; /* Kill cgroup */ - k = user_terminate_cgroup(u); + k = user_stop_slice(u); if (k < 0) r = k; + user_save(u); + + return r; +} + +int user_finalize(User *u) { + Session *s; + int r = 0, k; + + assert(u); + + if (u->started) + log_debug("User %s logged out.", u->name); + + LIST_FOREACH(sessions_by_user, s, u->sessions) { + k = session_finalize(s); + if (k < 0) + r = k; + } + /* Kill XDG_RUNTIME_DIR */ k = user_remove_runtime_path(u); if (k < 0) @@ -516,10 +557,10 @@ int user_stop(User *u) { unlink(u->state_file); user_add_to_gc_queue(u); - if (u->started) + if (u->started) { user_send_signal(u, false); - - u->started = false; + u->started = false; + } return r; } @@ -574,8 +615,6 @@ static int user_check_linger_file(User *u) { } int user_check_gc(User *u, bool drop_not_started) { - int r; - assert(u); if (drop_not_started && !u->started) @@ -587,14 +626,14 @@ int user_check_gc(User *u, bool drop_not_started) { if (user_check_linger_file(u) > 0) return 1; - if (u->cgroup_path) { - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, false); - if (r < 0) - return r; + if (u->slice_job || u->service_job) + return 1; - if (r <= 0) - return 1; - } + if (u->slice && manager_unit_is_active(u->manager, u->slice) != 0) + return 1; + + if (u->service && manager_unit_is_active(u->manager, u->service) != 0) + return 1; return 0; } @@ -615,6 +654,11 @@ UserState user_get_state(User *u) { assert(u); + if (u->closing) + return USER_CLOSING; + + if (u->slice_job || u->service_job) + return USER_OPENING; LIST_FOREACH(sessions_by_user, i, u->sessions) { if (session_is_active(i)) @@ -633,31 +677,17 @@ UserState user_get_state(User *u) { } int user_kill(User *u, int signo) { - int r = 0, q; - Set *pid_set = NULL; - assert(u); - if (!u->cgroup_path) + if (!u->slice) return -ESRCH; - pid_set = set_new(trivial_hash_func, trivial_compare_func); - if (!pid_set) - return -ENOMEM; - - q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set); - if (q < 0) - if (q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - - if (pid_set) - set_free(pid_set); - - return r; + return manager_kill_unit(u->manager, u->slice, KILL_ALL, signo, NULL); } static const char* const user_state_table[_USER_STATE_MAX] = { [USER_OFFLINE] = "offline", + [USER_OPENING] = "opening", [USER_LINGERING] = "lingering", [USER_ONLINE] = "online", [USER_ACTIVE] = "active", diff --git a/src/login/logind-user.h b/src/login/logind-user.h index 080354da74..b9171d345d 100644 --- a/src/login/logind-user.h +++ b/src/login/logind-user.h @@ -30,6 +30,7 @@ typedef struct User User; typedef enum UserState { USER_OFFLINE, /* Not logged in at all */ + USER_OPENING, /* Is logging in */ USER_LINGERING, /* Lingering has been enabled by the admin for this user */ USER_ONLINE, /* User logged in */ USER_ACTIVE, /* User logged in and has a session in the fg */ @@ -47,8 +48,12 @@ struct User { char *state_file; char *runtime_path; + char *service; - char *cgroup_path; + char *slice; + + char *service_job; + char *slice_job; Session *display; @@ -56,6 +61,7 @@ struct User { bool in_gc_queue:1; bool started:1; + bool closing:1; LIST_HEAD(Session, sessions); LIST_FIELDS(User, gc_queue); @@ -67,6 +73,7 @@ int user_check_gc(User *u, bool drop_not_started); void user_add_to_gc_queue(User *u); int user_start(User *u); int user_stop(User *u); +int user_finalize(User *u); UserState user_get_state(User *u); int user_get_idle_hint(User *u, dual_timestamp *t); int user_save(User *u); diff --git a/src/login/logind.c b/src/login/logind.c index 5a394401dc..0628032ae5 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -20,13 +20,11 @@ ***/ #include <errno.h> -#include <pwd.h> #include <libudev.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <sys/epoll.h> -#include <sys/ioctl.h> #include <linux/vt.h> #include <sys/timerfd.h> @@ -74,24 +72,24 @@ Manager *manager_new(void) { m->users = hashmap_new(trivial_hash_func, trivial_compare_func); m->inhibitors = hashmap_new(string_hash_func, string_compare_func); m->buttons = hashmap_new(string_hash_func, string_compare_func); + m->busnames = hashmap_new(string_hash_func, string_compare_func); - m->user_cgroups = hashmap_new(string_hash_func, string_compare_func); - m->session_cgroups = hashmap_new(string_hash_func, string_compare_func); + m->user_units = hashmap_new(string_hash_func, string_compare_func); + m->session_units = hashmap_new(string_hash_func, string_compare_func); m->session_fds = hashmap_new(trivial_hash_func, trivial_compare_func); m->inhibitor_fds = hashmap_new(trivial_hash_func, trivial_compare_func); m->button_fds = hashmap_new(trivial_hash_func, trivial_compare_func); - if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || - !m->user_cgroups || !m->session_cgroups || + if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || !m->busnames || + !m->user_units || !m->session_units || !m->session_fds || !m->inhibitor_fds || !m->button_fds) { manager_free(m); return NULL; } - m->reset_controllers = strv_new("cpu", NULL); m->kill_exclude_users = strv_new("root", NULL); - if (!m->reset_controllers || !m->kill_exclude_users) { + if (!m->kill_exclude_users) { manager_free(m); return NULL; } @@ -102,11 +100,6 @@ Manager *manager_new(void) { return NULL; } - if (cg_get_user_path(&m->cgroup_path) < 0) { - manager_free(m); - return NULL; - } - return m; } @@ -117,6 +110,7 @@ void manager_free(Manager *m) { Seat *s; Inhibitor *i; Button *b; + char *n; assert(m); @@ -138,15 +132,19 @@ void manager_free(Manager *m) { while ((b = hashmap_first(m->buttons))) button_free(b); + while ((n = hashmap_first(m->busnames))) + free(hashmap_remove(m->busnames, n)); + hashmap_free(m->devices); hashmap_free(m->seats); hashmap_free(m->sessions); hashmap_free(m->users); hashmap_free(m->inhibitors); hashmap_free(m->buttons); + hashmap_free(m->busnames); - hashmap_free(m->user_cgroups); - hashmap_free(m->session_cgroups); + hashmap_free(m->user_units); + hashmap_free(m->session_units); hashmap_free(m->session_fds); hashmap_free(m->inhibitor_fds); @@ -157,6 +155,8 @@ void manager_free(Manager *m) { if (m->udev_seat_monitor) udev_monitor_unref(m->udev_seat_monitor); + if (m->udev_device_monitor) + udev_monitor_unref(m->udev_device_monitor); if (m->udev_vcsa_monitor) udev_monitor_unref(m->udev_vcsa_monitor); if (m->udev_button_monitor) @@ -183,269 +183,13 @@ void manager_free(Manager *m) { if (m->idle_action_fd >= 0) close_nointr_nofail(m->idle_action_fd); - strv_free(m->controllers); - strv_free(m->reset_controllers); strv_free(m->kill_only_users); strv_free(m->kill_exclude_users); free(m->action_job); - - free(m->cgroup_path); free(m); } -int manager_add_device(Manager *m, const char *sysfs, Device **_device) { - Device *d; - - assert(m); - assert(sysfs); - - d = hashmap_get(m->devices, sysfs); - if (d) { - if (_device) - *_device = d; - - return 0; - } - - d = device_new(m, sysfs); - if (!d) - return -ENOMEM; - - if (_device) - *_device = d; - - return 0; -} - -int manager_add_seat(Manager *m, const char *id, Seat **_seat) { - Seat *s; - - assert(m); - assert(id); - - s = hashmap_get(m->seats, id); - if (s) { - if (_seat) - *_seat = s; - - return 0; - } - - s = seat_new(m, id); - if (!s) - return -ENOMEM; - - if (_seat) - *_seat = s; - - return 0; -} - -int manager_add_session(Manager *m, User *u, const char *id, Session **_session) { - Session *s; - - assert(m); - assert(id); - - s = hashmap_get(m->sessions, id); - if (s) { - if (_session) - *_session = s; - - return 0; - } - - s = session_new(m, u, id); - if (!s) - return -ENOMEM; - - if (_session) - *_session = s; - - return 0; -} - -int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) { - User *u; - - assert(m); - assert(name); - - u = hashmap_get(m->users, ULONG_TO_PTR((unsigned long) uid)); - if (u) { - if (_user) - *_user = u; - - return 0; - } - - u = user_new(m, uid, gid, name); - if (!u) - return -ENOMEM; - - if (_user) - *_user = u; - - return 0; -} - -int manager_add_user_by_name(Manager *m, const char *name, User **_user) { - uid_t uid; - gid_t gid; - int r; - - assert(m); - assert(name); - - r = get_user_creds(&name, &uid, &gid, NULL, NULL); - if (r < 0) - return r; - - return manager_add_user(m, uid, gid, name, _user); -} - -int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) { - struct passwd *p; - - assert(m); - - errno = 0; - p = getpwuid(uid); - if (!p) - return errno ? -errno : -ENOENT; - - return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user); -} - -int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor) { - Inhibitor *i; - - assert(m); - assert(id); - - i = hashmap_get(m->inhibitors, id); - if (i) { - if (_inhibitor) - *_inhibitor = i; - - return 0; - } - - i = inhibitor_new(m, id); - if (!i) - return -ENOMEM; - - if (_inhibitor) - *_inhibitor = i; - - return 0; -} - -int manager_add_button(Manager *m, const char *name, Button **_button) { - Button *b; - - assert(m); - assert(name); - - b = hashmap_get(m->buttons, name); - if (b) { - if (_button) - *_button = b; - - return 0; - } - - b = button_new(m, name); - if (!b) - return -ENOMEM; - - if (_button) - *_button = b; - - return 0; -} - -int manager_process_seat_device(Manager *m, struct udev_device *d) { - Device *device; - int r; - - assert(m); - - if (streq_ptr(udev_device_get_action(d), "remove")) { - - device = hashmap_get(m->devices, udev_device_get_syspath(d)); - if (!device) - return 0; - - seat_add_to_gc_queue(device->seat); - device_free(device); - - } else { - const char *sn; - Seat *seat; - - sn = udev_device_get_property_value(d, "ID_SEAT"); - if (isempty(sn)) - sn = "seat0"; - - if (!seat_name_is_valid(sn)) { - log_warning("Device with invalid seat name %s found, ignoring.", sn); - return 0; - } - - r = manager_add_device(m, udev_device_get_syspath(d), &device); - if (r < 0) - return r; - - r = manager_add_seat(m, sn, &seat); - if (r < 0) { - if (!device->seat) - device_free(device); - - return r; - } - - device_attach(device, seat); - seat_start(seat); - } - - return 0; -} - -int manager_process_button_device(Manager *m, struct udev_device *d) { - Button *b; - - int r; - - assert(m); - - if (streq_ptr(udev_device_get_action(d), "remove")) { - - b = hashmap_get(m->buttons, udev_device_get_sysname(d)); - if (!b) - return 0; - - button_free(b); - - } else { - const char *sn; - - r = manager_add_button(m, udev_device_get_sysname(d), &b); - if (r < 0) - return r; - - sn = udev_device_get_property_value(d, "ID_SEAT"); - if (isempty(sn)) - sn = "seat0"; - - button_set_seat(b, sn); - button_open(b); - } - - return 0; -} - int manager_enumerate_devices(Manager *m) { struct udev_list_entry *item = NULL, *first = NULL; struct udev_enumerate *e; @@ -554,7 +298,7 @@ finish: } int manager_enumerate_seats(Manager *m) { - DIR *d; + _cleanup_closedir_ DIR *d = NULL; struct dirent *de; int r = 0; @@ -573,7 +317,7 @@ int manager_enumerate_seats(Manager *m) { return -errno; } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, return -errno) { Seat *s; int k; @@ -591,66 +335,16 @@ int manager_enumerate_seats(Manager *m) { r = k; } - closedir(d); - - return r; -} - -static int manager_enumerate_users_from_cgroup(Manager *m) { - _cleanup_closedir_ DIR *d = NULL; - int r = 0, k; - char *name; - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, &d); - if (r < 0) { - if (r == -ENOENT) - return 0; - - log_error("Failed to open %s: %s", m->cgroup_path, strerror(-r)); - return r; - } - - while ((k = cg_read_subgroup(d, &name)) > 0) { - User *user; - char *e; - - e = endswith(name, ".user"); - if (e) { - *e = 0; - - k = manager_add_user_by_name(m, name, &user); - if (k < 0) { - free(name); - r = k; - continue; - } - - user_add_to_gc_queue(user); - - if (!user->cgroup_path) { - user->cgroup_path = strjoin(m->cgroup_path, "/", name, NULL); - if (!user->cgroup_path) { - k = log_oom(); - free(name); - break; - } - } - } - - free(name); - } - - if (k < 0) - r = k; - return r; } static int manager_enumerate_linger_users(Manager *m) { - DIR *d; + _cleanup_closedir_ DIR *d = NULL; struct dirent *de; int r = 0; + assert(m); + d = opendir("/var/lib/systemd/linger"); if (!d) { if (errno == ENOENT) @@ -660,7 +354,7 @@ static int manager_enumerate_linger_users(Manager *m) { return -errno; } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, return -errno) { int k; if (!dirent_is_file(de)) @@ -673,27 +367,20 @@ static int manager_enumerate_linger_users(Manager *m) { } } - closedir(d); - return r; } int manager_enumerate_users(Manager *m) { - DIR *d; + _cleanup_closedir_ DIR *d = NULL; struct dirent *de; int r, k; assert(m); - /* First, enumerate user cgroups */ - r = manager_enumerate_users_from_cgroup(m); + /* Add lingering users */ + r = manager_enumerate_linger_users(m); - /* Second, add lingering users on top */ - k = manager_enumerate_linger_users(m); - if (k < 0) - r = k; - - /* Third, read in user data stored on disk */ + /* Read in user data stored on disk */ d = opendir("/run/systemd/users"); if (!d) { if (errno == ENOENT) @@ -703,88 +390,23 @@ int manager_enumerate_users(Manager *m) { return -errno; } - while ((de = readdir(d))) { - uid_t uid; + FOREACH_DIRENT(de, d, return -errno) { User *u; if (!dirent_is_file(de)) continue; - k = parse_uid(de->d_name, &uid); + k = manager_add_user_by_name(m, de->d_name, &u); if (k < 0) { - log_error("Failed to parse file name %s: %s", de->d_name, strerror(-k)); - continue; - } - - u = hashmap_get(m->users, ULONG_TO_PTR(uid)); - if (!u) { - unlinkat(dirfd(d), de->d_name, 0); - continue; - } + log_error("Failed to add user by file name %s: %s", de->d_name, strerror(-k)); - k = user_load(u); - if (k < 0) - r = k; - } - - closedir(d); - - return r; -} - -static int manager_enumerate_sessions_from_cgroup(Manager *m) { - User *u; - Iterator i; - int r = 0; - - HASHMAP_FOREACH(u, m->users, i) { - _cleanup_closedir_ DIR *d = NULL; - char *name; - int k; - - if (!u->cgroup_path) - continue; - - k = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &d); - if (k < 0) { - if (k == -ENOENT) - continue; - - log_error("Failed to open %s: %s", u->cgroup_path, strerror(-k)); r = k; continue; } - while ((k = cg_read_subgroup(d, &name)) > 0) { - Session *session; - char *e; - - e = endswith(name, ".session"); - if (e) { - *e = 0; - - k = manager_add_session(m, u, name, &session); - if (k < 0) { - free(name); - r = k; - continue; - } - - session_add_to_gc_queue(session); - - if (!session->cgroup_path) { - session->cgroup_path = strjoin(m->cgroup_path, "/", name, NULL); - if (!session->cgroup_path) { - k = log_oom(); - free(name); - break; - } - } - } - - free(name); - } + user_add_to_gc_queue(u); + k = user_load(u); if (k < 0) r = k; } @@ -793,16 +415,13 @@ static int manager_enumerate_sessions_from_cgroup(Manager *m) { } int manager_enumerate_sessions(Manager *m) { - DIR *d; + _cleanup_closedir_ DIR *d = NULL; struct dirent *de; int r = 0; assert(m); - /* First enumerate session cgroups */ - r = manager_enumerate_sessions_from_cgroup(m); - - /* Second, read in session data stored on disk */ + /* Read in session data stored on disk */ d = opendir("/run/systemd/sessions"); if (!d) { if (errno == ENOENT) @@ -812,31 +431,39 @@ int manager_enumerate_sessions(Manager *m) { return -errno; } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, return -errno) { struct Session *s; int k; if (!dirent_is_file(de)) continue; - s = hashmap_get(m->sessions, de->d_name); - if (!s) { - unlinkat(dirfd(d), de->d_name, 0); + if (!session_id_valid(de->d_name)) { + log_warning("Invalid session file name '%s', ignoring.", de->d_name); + r = -EINVAL; + continue; + } + + k = manager_add_session(m, de->d_name, &s); + if (k < 0) { + log_error("Failed to add session by file name %s: %s", de->d_name, strerror(-k)); + + r = k; continue; } + session_add_to_gc_queue(s); + k = session_load(s); if (k < 0) r = k; } - closedir(d); - return r; } int manager_enumerate_inhibitors(Manager *m) { - DIR *d; + _cleanup_closedir_ DIR *d = NULL; struct dirent *de; int r = 0; @@ -851,7 +478,7 @@ int manager_enumerate_inhibitors(Manager *m) { return -errno; } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, return -errno) { int k; Inhibitor *i; @@ -870,8 +497,6 @@ int manager_enumerate_inhibitors(Manager *m) { r = k; } - closedir(d); - return r; } @@ -891,6 +516,22 @@ int manager_dispatch_seat_udev(Manager *m) { return r; } +static int manager_dispatch_device_udev(Manager *m) { + struct udev_device *d; + int r; + + assert(m); + + d = udev_monitor_receive_device(m->udev_device_monitor); + if (!d) + return -ENOMEM; + + r = manager_process_seat_device(m, d); + udev_device_unref(d); + + return r; +} + int manager_dispatch_vcsa_udev(Manager *m) { struct udev_device *d; int r = 0; @@ -908,7 +549,7 @@ int manager_dispatch_vcsa_udev(Manager *m) { * VTs, to make sure our auto VTs never go away. */ if (name && startswith(name, "vcsa") && streq_ptr(udev_device_get_action(d), "remove")) - r = seat_preallocate_vts(m->vtconsole); + r = seat_preallocate_vts(m->seat0); udev_device_unref(d); @@ -933,87 +574,13 @@ int manager_dispatch_button_udev(Manager *m) { int manager_dispatch_console(Manager *m) { assert(m); + assert(m->seat0); - if (m->vtconsole) - seat_read_active_vt(m->vtconsole); + seat_read_active_vt(m->seat0); return 0; } -static int vt_is_busy(int vtnr) { - struct vt_stat vt_stat; - int r = 0, fd; - - assert(vtnr >= 1); - - /* We explicitly open /dev/tty1 here instead of /dev/tty0. If - * we'd open the latter we'd open the foreground tty which - * hence would be unconditionally busy. By opening /dev/tty1 - * we avoid this. Since tty1 is special and needs to be an - * explicitly loaded getty or DM this is safe. */ - - fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0) - r = -errno; - else - r = !!(vt_stat.v_state & (1 << vtnr)); - - close_nointr_nofail(fd); - - return r; -} - -int manager_spawn_autovt(Manager *m, int vtnr) { - int r; - char *name = NULL; - const char *mode = "fail"; - - assert(m); - assert(vtnr >= 1); - - if ((unsigned) vtnr > m->n_autovts && - (unsigned) vtnr != m->reserve_vt) - return 0; - - if ((unsigned) vtnr != m->reserve_vt) { - /* If this is the reserved TTY, we'll start the getty - * on it in any case, but otherwise only if it is not - * busy. */ - - r = vt_is_busy(vtnr); - if (r < 0) - return r; - else if (r > 0) - return -EBUSY; - } - - if (asprintf(&name, "autovt@tty%i.service", vtnr) < 0) { - log_error("Could not allocate service name."); - r = -ENOMEM; - goto finish; - } - - r = bus_method_call_with_reply ( - m->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - NULL, - NULL, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &mode, - DBUS_TYPE_INVALID); - -finish: - free(name); - - return r; -} - static int manager_reserve_vt(Manager *m) { _cleanup_free_ char *p = NULL; @@ -1037,107 +604,6 @@ static int manager_reserve_vt(Manager *m) { return 0; } -int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session) { - Session *s; - char *p; - - assert(m); - assert(cgroup); - assert(session); - - s = hashmap_get(m->session_cgroups, cgroup); - if (s) { - *session = s; - return 1; - } - - p = strdupa(cgroup); - - for (;;) { - char *e; - - e = strrchr(p, '/'); - if (!e || e == p) { - *session = NULL; - return 0; - } - - *e = 0; - - s = hashmap_get(m->session_cgroups, p); - if (s) { - *session = s; - return 1; - } - } -} - -int manager_get_user_by_cgroup(Manager *m, const char *cgroup, User **user) { - User *u; - char *p; - - assert(m); - assert(cgroup); - assert(user); - - u = hashmap_get(m->user_cgroups, cgroup); - if (u) { - *user = u; - return 1; - } - - p = strdupa(cgroup); - if (!p) - return log_oom(); - - for (;;) { - char *e; - - e = strrchr(p, '/'); - if (!e || e == p) { - *user = NULL; - return 0; - } - - *e = 0; - - u = hashmap_get(m->user_cgroups, p); - if (u) { - *user = u; - return 1; - } - } -} - -int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) { - _cleanup_free_ char *p = NULL; - int r; - - assert(m); - assert(pid >= 1); - assert(session); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &p); - if (r < 0) - return r; - - return manager_get_session_by_cgroup(m, p, session); -} - -void manager_cgroup_notify_empty(Manager *m, const char *cgroup) { - Session *s; - User *u; - int r; - - r = manager_get_session_by_cgroup(m, cgroup, &s); - if (r > 0) - session_add_to_gc_queue(s); - - r = manager_get_user_by_cgroup(m, cgroup, &u); - if (r > 0) - user_add_to_gc_queue(u); -} - static void manager_dispatch_other(Manager *m, int fd) { Session *s; Inhibitor *i; @@ -1204,15 +670,75 @@ static int manager_connect_bus(Manager *m) { dbus_bus_add_match(m->bus, "type='signal'," - "interface='org.freedesktop.systemd1.Agent'," - "member='Released'," - "path='/org/freedesktop/systemd1/agent'", + "sender='"DBUS_SERVICE_DBUS"'," + "interface='"DBUS_INTERFACE_DBUS"'," + "member='NameOwnerChanged'," + "path='"DBUS_PATH_DBUS"'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for NameOwnerChanged: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for JobRemoved: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='UnitRemoved'," + "path='/org/freedesktop/systemd1'", + &error); if (dbus_error_is_set(&error)) { - log_error("Failed to register match: %s", bus_error_message(&error)); - r = -EIO; - goto fail; + log_error("Failed to add match for UnitRemoved: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for PropertiesChanged: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='Reloading'," + "path='/org/freedesktop/systemd1'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for Reloading: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + r = bus_method_call_with_reply( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + NULL, + &error, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to enable subscription: %s", bus_error(&error, r)); + dbus_error_free(&error); } r = dbus_bus_request_name(m->bus, "org.freedesktop.login1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); @@ -1289,6 +815,7 @@ static int manager_connect_udev(Manager *m) { assert(m); assert(!m->udev_seat_monitor); + assert(!m->udev_device_monitor); assert(!m->udev_vcsa_monitor); assert(!m->udev_button_monitor); @@ -1309,6 +836,33 @@ static int manager_connect_udev(Manager *m) { if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_seat_fd, &ev) < 0) return -errno; + m->udev_device_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); + if (!m->udev_device_monitor) + return -ENOMEM; + + r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_device_monitor, "input", NULL); + if (r < 0) + return r; + + r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_device_monitor, "graphics", NULL); + if (r < 0) + return r; + + r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_device_monitor, "drm", NULL); + if (r < 0) + return r; + + r = udev_monitor_enable_receiving(m->udev_device_monitor); + if (r < 0) + return r; + + m->udev_device_fd = udev_monitor_get_fd(m->udev_device_monitor); + zero(ev); + ev.events = EPOLLIN; + ev.data.u32 = FD_DEVICE_UDEV; + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_device_fd, &ev) < 0) + return -errno; + /* Don't watch keys if nobody cares */ if (m->handle_power_key != HANDLE_IGNORE || m->handle_suspend_key != HANDLE_IGNORE || @@ -1390,6 +944,7 @@ void manager_gc(Manager *m, bool drop_not_started) { if (session_check_gc(session, drop_not_started) == 0) { session_stop(session); + session_finalize(session); session_free(session); } } @@ -1400,50 +955,12 @@ void manager_gc(Manager *m, bool drop_not_started) { if (user_check_gc(user, drop_not_started) == 0) { user_stop(user); + user_finalize(user); user_free(user); } } } -int manager_get_idle_hint(Manager *m, dual_timestamp *t) { - Session *s; - bool idle_hint; - dual_timestamp ts = { 0, 0 }; - Iterator i; - - assert(m); - - idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0); - - HASHMAP_FOREACH(s, m->sessions, i) { - dual_timestamp k; - int ih; - - ih = session_get_idle_hint(s, &k); - if (ih < 0) - return ih; - - if (!ih) { - if (!idle_hint) { - if (k.monotonic < ts.monotonic) - ts = k; - } else { - idle_hint = false; - ts = k; - } - } else if (idle_hint) { - - if (k.monotonic > ts.monotonic) - ts = k; - } - } - - if (t) - *t = ts; - - return idle_hint; -} - int manager_dispatch_idle_action(Manager *m) { struct dual_timestamp since; struct itimerspec its = {}; @@ -1526,9 +1043,6 @@ int manager_startup(Manager *m) { assert(m); assert(m->epoll_fd <= 0); - cg_shorten_controllers(m->reset_controllers); - cg_shorten_controllers(m->controllers); - m->epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (m->epoll_fd < 0) return -errno; @@ -1549,17 +1063,34 @@ int manager_startup(Manager *m) { return r; /* Instantiate magic seat 0 */ - r = manager_add_seat(m, "seat0", &m->vtconsole); + r = manager_add_seat(m, "seat0", &m->seat0); if (r < 0) return r; /* Deserialize state */ - manager_enumerate_devices(m); - manager_enumerate_seats(m); - manager_enumerate_users(m); - manager_enumerate_sessions(m); - manager_enumerate_inhibitors(m); - manager_enumerate_buttons(m); + r = manager_enumerate_devices(m); + if (r < 0) + log_warning("Device enumeration failed: %s", strerror(-r)); + + r = manager_enumerate_seats(m); + if (r < 0) + log_warning("Seat enumeration failed: %s", strerror(-r)); + + r = manager_enumerate_users(m); + if (r < 0) + log_warning("User enumeration failed: %s", strerror(-r)); + + r = manager_enumerate_sessions(m); + if (r < 0) + log_warning("Session enumeration failed: %s", strerror(-r)); + + r = manager_enumerate_inhibitors(m); + if (r < 0) + log_warning("Inhibitor enumeration failed: %s", strerror(-r)); + + r = manager_enumerate_buttons(m); + if (r < 0) + log_warning("Button enumeration failed: %s", strerror(-r)); /* Remove stale objects before we start them */ manager_gc(m, false); @@ -1653,6 +1184,10 @@ int manager_run(Manager *m) { manager_dispatch_seat_udev(m); break; + case FD_DEVICE_UDEV: + manager_dispatch_device_udev(m); + break; + case FD_VCSA_UDEV: manager_dispatch_vcsa_udev(m); break; diff --git a/src/login/logind.conf b/src/login/logind.conf index 0861d73e0b..c0abf01b0c 100644 --- a/src/login/logind.conf +++ b/src/login/logind.conf @@ -13,8 +13,6 @@ #KillUserProcesses=no #KillOnlyUsers= #KillExcludeUsers=root -#Controllers= -#ResetControllers=cpu #InhibitDelayMaxSec=5 #HandlePowerKey=poweroff #HandleSuspendKey=suspend diff --git a/src/login/logind.h b/src/login/logind.h index 904dc20467..9e6296cb7e 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -51,15 +51,17 @@ struct Manager { Hashmap *users; Hashmap *inhibitors; Hashmap *buttons; + Hashmap *busnames; LIST_HEAD(Seat, seat_gc_queue); LIST_HEAD(Session, session_gc_queue); LIST_HEAD(User, user_gc_queue); struct udev *udev; - struct udev_monitor *udev_seat_monitor, *udev_vcsa_monitor, *udev_button_monitor; + struct udev_monitor *udev_seat_monitor, *udev_device_monitor, *udev_vcsa_monitor, *udev_button_monitor; int udev_seat_fd; + int udev_device_fd; int udev_vcsa_fd; int udev_button_fd; @@ -72,20 +74,16 @@ struct Manager { unsigned reserve_vt; int reserve_vt_fd; - Seat *vtconsole; - - char *cgroup_path; - char **controllers, **reset_controllers; + Seat *seat0; char **kill_only_users, **kill_exclude_users; - bool kill_user_processes; unsigned long session_counter; unsigned long inhibit_counter; - Hashmap *session_cgroups; - Hashmap *user_cgroups; + Hashmap *session_units; + Hashmap *user_units; Hashmap *session_fds; Hashmap *inhibitor_fds; @@ -125,6 +123,7 @@ struct Manager { enum { FD_SEAT_UDEV, + FD_DEVICE_UDEV, FD_VCSA_UDEV, FD_BUTTON_UDEV, FD_CONSOLE, @@ -136,10 +135,10 @@ enum { Manager *manager_new(void); void manager_free(Manager *m); -int manager_add_device(Manager *m, const char *sysfs, Device **_device); +int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_device); int manager_add_button(Manager *m, const char *name, Button **_button); int manager_add_seat(Manager *m, const char *id, Seat **_seat); -int manager_add_session(Manager *m, User *u, const char *id, Session **_session); +int manager_add_session(Manager *m, const char *id, Session **_session); int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user); int manager_add_user_by_name(Manager *m, const char *name, User **_user); int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user); @@ -165,14 +164,13 @@ int manager_startup(Manager *m); int manager_run(Manager *m); int manager_spawn_autovt(Manager *m, int vtnr); -void manager_cgroup_notify_empty(Manager *m, const char *cgroup); - void manager_gc(Manager *m, bool drop_not_started); +bool manager_shall_kill(Manager *m, const char *user); + int manager_get_idle_hint(Manager *m, dual_timestamp *t); -int manager_get_user_by_cgroup(Manager *m, const char *cgroup, User **user); -int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session); +int manager_get_user_by_pid(Manager *m, pid_t pid, User **user); int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session); extern const DBusObjectPathVTable bus_manager_vtable; @@ -185,5 +183,14 @@ int manager_send_changed(Manager *manager, const char *properties); int manager_dispatch_delayed(Manager *manager); +int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, const char *after, const char *kill_mode, DBusError *error, char **job); +int manager_start_unit(Manager *manager, const char *unit, DBusError *error, char **job); +int manager_stop_unit(Manager *manager, const char *unit, DBusError *error, char **job); +int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, DBusError *error); +int manager_unit_is_active(Manager *manager, const char *unit); + /* gperf lookup function */ const struct ConfigPerfItem* logind_gperf_lookup(const char *key, unsigned length); + +int manager_watch_busname(Manager *manager, const char *name); +void manager_drop_busname(Manager *manager, const char *name); diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf index 6c1f2f57e5..0407609c19 100644 --- a/src/login/org.freedesktop.login1.conf +++ b/src/login/org.freedesktop.login1.conf @@ -62,6 +62,10 @@ <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Manager" + send_member="ListMachines"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" send_member="ListSeats"/> <allow send_destination="org.freedesktop.login1" diff --git a/src/login/org.freedesktop.login1.policy.in b/src/login/org.freedesktop.login1.policy.in index 0c551d4f9b..b96d32d526 100644 --- a/src/login/org.freedesktop.login1.policy.in +++ b/src/login/org.freedesktop.login1.policy.in @@ -190,7 +190,7 @@ <defaults> <allow_any>auth_admin_keep</allow_any> <allow_inactive>auth_admin_keep</allow_inactive> - <allow_active>auth_admin_keep</allow_active> + <allow_active>yes</allow_active> </defaults> <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate> </action> diff --git a/src/login/pam-module.c b/src/login/pam-module.c index 13290fd8ea..49296b5d63 100644 --- a/src/login/pam-module.c +++ b/src/login/pam-module.c @@ -43,11 +43,6 @@ static int parse_argv(pam_handle_t *handle, int argc, const char **argv, - char ***controllers, - char ***reset_controllers, - bool *kill_processes, - char ***kill_only_users, - char ***kill_exclude_users, const char **class, bool *debug) { @@ -59,89 +54,15 @@ static int parse_argv(pam_handle_t *handle, for (i = 0; i < (unsigned) argc; i++) { int k; - if (startswith(argv[i], "kill-session-processes=")) { - if ((k = parse_boolean(argv[i] + 23)) < 0) { - pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument."); - return k; - } - - if (kill_processes) - *kill_processes = k; - - } else if (startswith(argv[i], "kill-session=")) { - /* As compatibility for old versions */ - - if ((k = parse_boolean(argv[i] + 13)) < 0) { - pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument."); - return k; - } - - if (kill_processes) - *kill_processes = k; - - } else if (startswith(argv[i], "controllers=")) { - - if (controllers) { - char **l; - - if (!(l = strv_split(argv[i] + 12, ","))) { - pam_syslog(handle, LOG_ERR, "Out of memory."); - return -ENOMEM; - } - - strv_free(*controllers); - *controllers = l; - } - - } else if (startswith(argv[i], "reset-controllers=")) { - - if (reset_controllers) { - char **l; - - if (!(l = strv_split(argv[i] + 18, ","))) { - pam_syslog(handle, LOG_ERR, "Out of memory."); - return -ENOMEM; - } - - strv_free(*reset_controllers); - *reset_controllers = l; - } - - } else if (startswith(argv[i], "kill-only-users=")) { - - if (kill_only_users) { - char **l; - - if (!(l = strv_split(argv[i] + 16, ","))) { - pam_syslog(handle, LOG_ERR, "Out of memory."); - return -ENOMEM; - } - - strv_free(*kill_only_users); - *kill_only_users = l; - } - - } else if (startswith(argv[i], "kill-exclude-users=")) { - - if (kill_exclude_users) { - char **l; - - if (!(l = strv_split(argv[i] + 19, ","))) { - pam_syslog(handle, LOG_ERR, "Out of memory."); - return -ENOMEM; - } - - strv_free(*kill_exclude_users); - *kill_exclude_users = l; - } - - } else if (startswith(argv[i], "class=")) { + if (startswith(argv[i], "class=")) { if (class) *class = argv[i] + 6; } else if (startswith(argv[i], "debug=")) { - if ((k = parse_boolean(argv[i] + 6)) < 0) { + k = parse_boolean(argv[i] + 6); + + if (k < 0) { pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument."); return k; } @@ -149,14 +70,9 @@ static int parse_argv(pam_handle_t *handle, if (debug) *debug = k; - } else if (startswith(argv[i], "create-session=") || - startswith(argv[i], "kill-user=")) { - - pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]); - } else { - pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]); - return -EINVAL; + pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]); + return 0; } } @@ -206,55 +122,6 @@ static int get_user_data( return PAM_SUCCESS; } -static bool check_user_lists( - pam_handle_t *handle, - uid_t uid, - char **kill_only_users, - char **kill_exclude_users) { - - const char *name = NULL; - char **l; - - assert(handle); - - if (uid == 0) - name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */ - else { - struct passwd *pw; - - pw = pam_modutil_getpwuid(handle, uid); - if (pw) - name = pw->pw_name; - } - - STRV_FOREACH(l, kill_exclude_users) { - uid_t u; - - if (parse_uid(*l, &u) >= 0) - if (u == uid) - return false; - - if (name && streq(name, *l)) - return false; - } - - if (strv_isempty(kill_only_users)) - return true; - - STRV_FOREACH(l, kill_only_users) { - uid_t u; - - if (parse_uid(*l, &u) >= 0) - if (u == uid) - return true; - - if (name && streq(name, *l)) - return true; - } - - return false; -} - static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) { _cleanup_free_ char *p = NULL; int r; @@ -316,13 +183,11 @@ _public_ PAM_EXTERN int pam_sm_open_session( int argc, const char **argv) { struct passwd *pw; - bool kill_processes = false, debug = false; + bool debug = false; const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, *class_pam = NULL, *cvtnr = NULL; - char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL; DBusError error; uint32_t uid, pid; DBusMessageIter iter; - dbus_bool_t kp; int session_fd = -1; DBusConnection *bus = NULL; DBusMessage *m = NULL, *reply = NULL; @@ -342,9 +207,8 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (parse_argv(handle, argc, argv, - &controllers, &reset_controllers, - &kill_processes, &kill_only_users, &kill_exclude_users, - &class_pam, &debug) < 0) { + &class_pam, + &debug) < 0) { r = PAM_SESSION_ERR; goto finish; } @@ -356,11 +220,11 @@ _public_ PAM_EXTERN int pam_sm_open_session( /* Make sure we don't enter a loop by talking to * systemd-logind when it is actually waiting for the * background to finish start-up. If the service is - * "systemd-shared" we simply set XDG_RUNTIME_DIR and + * "systemd-user" we simply set XDG_RUNTIME_DIR and * leave. */ pam_get_item(handle, PAM_SERVICE, (const void**) &service); - if (streq_ptr(service, "systemd-shared")) { + if (streq_ptr(service, "systemd-user")) { char *p, *rt = NULL; if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) { @@ -393,9 +257,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( goto finish; } - if (kill_processes) - kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users); - dbus_connection_set_change_sigpipe(FALSE); bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); @@ -510,27 +371,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( dbus_message_iter_init_append(m, &iter); - r = bus_append_strv_iter(&iter, controllers); - if (r < 0) { - pam_syslog(handle, LOG_ERR, "Could not attach parameter to message."); - r = PAM_BUF_ERR; - goto finish; - } - - r = bus_append_strv_iter(&iter, reset_controllers); - if (r < 0) { - pam_syslog(handle, LOG_ERR, "Could not attach parameter to message."); - r = PAM_BUF_ERR; - goto finish; - } - - kp = kill_processes; - if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) { - pam_syslog(handle, LOG_ERR, "Could not attach parameter to message."); - r = PAM_BUF_ERR; - goto finish; - } - if (debug) pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: " "uid=%u pid=%u service=%s type=%s class=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s", @@ -613,11 +453,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( r = PAM_SUCCESS; finish: - strv_free(controllers); - strv_free(reset_controllers); - strv_free(kill_only_users); - strv_free(kill_exclude_users); - dbus_error_free(&error); if (bus) { diff --git a/src/login/sd-login.c b/src/login/sd-login.c index d0dc42f685..71d8c2942e 100644 --- a/src/login/sd-login.c +++ b/src/login/sd-login.c @@ -31,6 +31,7 @@ #include "sd-login.h" #include "strv.h" #include "fileio.h" +#include "login-shared.h" _public_ int sd_pid_get_session(pid_t pid, char **session) { if (pid < 0) @@ -72,6 +73,16 @@ _public_ int sd_pid_get_machine_name(pid_t pid, char **name) { return cg_pid_get_machine_name(pid, name); } +_public_ int sd_pid_get_slice(pid_t pid, char **slice) { + + if (pid < 0) + return -EINVAL; + if (!slice) + return -EINVAL; + + return cg_pid_get_slice(pid, slice); +} + _public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) { if (pid < 0) @@ -216,17 +227,19 @@ static int file_of_session(const char *session, char **_p) { assert(_p); - if (session) + if (session) { + if (!session_id_valid(session)) + return -EINVAL; + p = strappend("/run/systemd/sessions/", session); - else { - char *buf; + } else { + _cleanup_free_ char *buf = NULL; r = sd_pid_get_session(0, &buf); if (r < 0) return r; p = strappend("/run/systemd/sessions/", buf); - free(buf); } if (!p) @@ -245,7 +258,6 @@ _public_ int sd_session_is_active(const char *session) { return r; r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL); - if (r < 0) return r; @@ -337,6 +349,23 @@ _public_ int sd_session_get_tty(const char *session, char **tty) { return session_get_string(session, "TTY", tty); } +_public_ int sd_session_get_vt(const char *session, unsigned *vtnr) { + _cleanup_free_ char *vtnr_string; + unsigned u; + int r; + + r = session_get_string(session, "VTNr", &vtnr_string); + if (r < 0) + return r; + + r = safe_atou(vtnr_string, &u); + if (r < 0) + return r; + + *vtnr = u; + return 0; +} + _public_ int sd_session_get_service(const char *session, char **service) { return session_get_string(session, "SERVICE", service); } @@ -592,40 +621,7 @@ _public_ int sd_get_uids(uid_t **users) { } _public_ int sd_get_machine_names(char ***machines) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_strv_free_ char **l = NULL; - _cleanup_free_ char *md = NULL; - char *n; - int c = 0, r; - - r = cg_get_machine_path(NULL, &md); - if (r < 0) - return r; - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, md, &d); - if (r < 0) - return r; - - while ((r = cg_read_subgroup(d, &n)) > 0) { - - r = strv_push(&l, n); - if (r < 0) { - free(n); - return -ENOMEM; - } - - c++; - } - - if (r < 0) - return r; - - if (machines) { - *machines = l; - l = NULL; - } - - return c; + return get_files_in_directory("/run/systemd/machines/", machines); } static inline int MONITOR_TO_FD(sd_login_monitor *m) { @@ -678,18 +674,7 @@ _public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { } if (!category || streq(category, "machine")) { - _cleanup_free_ char *md = NULL, *p = NULL; - int r; - - r = cg_get_machine_path(NULL, &md); - if (r < 0) - return r; - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, md, NULL, &p); - if (r < 0) - return r; - - k = inotify_add_watch(fd, p, IN_MOVED_TO|IN_CREATE|IN_DELETE); + k = inotify_add_watch(fd, "/run/systemd/machines/", IN_MOVED_TO|IN_DELETE); if (k < 0) { close_nointr_nofail(fd); return -errno; diff --git a/src/login/systemd-user b/src/login/systemd-user new file mode 100644 index 0000000000..7b57dbf784 --- /dev/null +++ b/src/login/systemd-user @@ -0,0 +1,8 @@ +#%PAM-1.0 + +# Used by systemd when launching systemd user instances. + +account include system-auth +session include system-auth +auth required pam_deny.so +password required pam_deny.so diff --git a/src/timestamp/timestamp.c b/src/login/test-login-shared.c index 1152f1b52e..d29d7e7921 100644 --- a/src/timestamp/timestamp.c +++ b/src/login/test-login-shared.c @@ -3,7 +3,7 @@ /*** This file is part of systemd. - Copyright 2010 Lennart Poettering + 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 @@ -19,21 +19,23 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdio.h> +#include "macro.h" +#include "login-shared.h" -#include "util.h" +static void test_session_id_valid(void) { + assert_se(session_id_valid("c1")); + assert_se(session_id_valid("1234")); -int main(int argc, char *argv[]) { - struct dual_timestamp t; + assert_se(!session_id_valid("1-2")); + assert_se(!session_id_valid("")); + assert_se(!session_id_valid("\tid")); +} - /* This is mostly useful for stuff like init ram disk scripts - * which want to take a proper timestamp to do minimal bootup - * profiling. */ +int main(int argc, char* argv[]) { + log_parse_environment(); + log_open(); - dual_timestamp_get(&t); - printf("%llu %llu\n", - (unsigned long long) t.realtime, - (unsigned long long) t.monotonic); + test_session_id_valid(); return 0; } diff --git a/src/login/test-login-tables.c b/src/login/test-login-tables.c new file mode 100644 index 0000000000..a4196bf14b --- /dev/null +++ b/src/login/test-login-tables.c @@ -0,0 +1,35 @@ +/*** + 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 "logind-action.h" +#include "logind-session.h" + +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table(handle_action, HANDLE_ACTION); + test_table(inhibit_mode, INHIBIT_MODE); + test_table(kill_who, KILL_WHO); + test_table(session_class, SESSION_CLASS); + test_table(session_state, SESSION_STATE); + test_table(session_type, SESSION_TYPE); + test_table(user_state, USER_STATE); + + return EXIT_SUCCESS; +} diff --git a/src/login/test-login.c b/src/login/test-login.c index 945cb38be9..228ddb2933 100644 --- a/src/login/test-login.c +++ b/src/login/test-login.c @@ -27,7 +27,7 @@ #include "util.h" #include "strv.h" -int main(int argc, char* argv[]) { +static void test_login(void) { int r, k; uid_t u, u2; char *seat, *type, *class, *display; @@ -215,6 +215,13 @@ int main(int argc, char* argv[]) { } sd_login_monitor_unref(m); +} + +int main(int argc, char* argv[]) { + log_parse_environment(); + log_open(); + + test_login(); return 0; } diff --git a/src/login/user-sessions.c b/src/login/user-sessions.c index 41d32044e9..45fb427671 100644 --- a/src/login/user-sessions.c +++ b/src/login/user-sessions.c @@ -25,7 +25,6 @@ #include "log.h" #include "util.h" -#include "cgroup-util.h" #include "fileio.h" int main(int argc, char*argv[]) { @@ -67,29 +66,12 @@ int main(int argc, char*argv[]) { goto finish; } else if (streq(argv[1], "stop")) { - int r, q; - char *cgroup_user_tree = NULL; + int r; r = write_string_file_atomic("/run/nologin", "System is going down."); if (r < 0) log_error("Failed to create /run/nologin: %s", strerror(-r)); - q = cg_get_user_path(&cgroup_user_tree); - if (q < 0) { - log_error("Failed to determine use path: %s", strerror(-q)); - goto finish; - } - - q = cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, cgroup_user_tree, true); - free(cgroup_user_tree); - if (q < 0) { - log_error("Failed to kill sessions: %s", strerror(-q)); - goto finish; - } - - if (r < 0) - goto finish; - } else { log_error("Unknown verb %s.", argv[1]); goto finish; diff --git a/src/machine/Makefile b/src/machine/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/machine/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c new file mode 100644 index 0000000000..ceab96e078 --- /dev/null +++ b/src/machine/machine-dbus.c @@ -0,0 +1,364 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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/>. +***/ + +#include <errno.h> +#include <string.h> + +#include "machined.h" +#include "machine.h" +#include "dbus-common.h" + +#define BUS_MACHINE_INTERFACE \ + " <interface name=\"org.freedesktop.machine1.Machine\">\n" \ + " <method name=\"Terminate\"/>\n" \ + " <method name=\"Kill\">\n" \ + " <arg name=\"who\" type=\"s\"/>\n" \ + " <arg name=\"signal\" type=\"s\"/>\n" \ + " </method>\n" \ + " <property name=\"Name\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"Id\" type=\"ay\" access=\"read\"/>\n" \ + " <property name=\"Timestamp\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"TimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \ + " <property name=\"Service\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"Scope\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"Leader\" type=\"u\" access=\"read\"/>\n" \ + " <property name=\"Class\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"State\" type=\"s\" access=\"read\"/>\n" \ + " <property name=\"RootDirectory\" type=\"s\" access=\"read\"/>\n" \ + " </interface>\n" + +#define INTROSPECTION \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>\n" \ + BUS_MACHINE_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE \ + "</node>\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.machine1.Machine\0" + +static int bus_machine_append_id(DBusMessageIter *i, const char *property, void *data) { + DBusMessageIter sub; + Machine *m = data; + dbus_bool_t b; + void *p; + + assert(i); + assert(property); + assert(m); + + if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "y", &sub)) + return -ENOMEM; + + p = &m->id; + b = dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &p, 16); + if (!b) + return -ENOMEM; + + if (!dbus_message_iter_close_container(i, &sub)) + return -ENOMEM; + + return 0; +} + +static int bus_machine_append_state(DBusMessageIter *i, const char *property, void *data) { + Machine *m = data; + const char *state; + + assert(i); + assert(property); + assert(m); + + state = machine_state_to_string(machine_get_state(m)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state)) + return -ENOMEM; + + return 0; +} + +static int get_machine_for_path(Manager *m, const char *path, Machine **_machine) { + _cleanup_free_ char *e = NULL; + Machine *machine; + + assert(m); + assert(path); + assert(_machine); + + if (!startswith(path, "/org/freedesktop/machine1/machine/")) + return -EINVAL; + + e = bus_path_unescape(path + 34); + if (!e) + return -ENOMEM; + + machine = hashmap_get(m->machines, e); + if (!machine) + return -ENOENT; + + *_machine = machine; + return 0; +} + +static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_machine_append_class, machine_class, MachineClass); + +static const BusProperty bus_machine_machine_properties[] = { + { "Name", bus_property_append_string, "s", offsetof(Machine, name), true }, + { "Id", bus_machine_append_id, "ay", 0 }, + { "Timestamp", bus_property_append_usec, "t", offsetof(Machine, timestamp.realtime) }, + { "TimestampMonotonic", bus_property_append_usec, "t", offsetof(Machine, timestamp.monotonic) }, + { "Service", bus_property_append_string, "s", offsetof(Machine, service), true }, + { "Scope", bus_property_append_string, "s", offsetof(Machine, scope), true }, + { "Leader", bus_property_append_pid, "u", offsetof(Machine, leader) }, + { "Class", bus_machine_append_class, "s", offsetof(Machine, class) }, + { "State", bus_machine_append_state, "s", 0 }, + { "RootDirectory", bus_property_append_string, "s", offsetof(Machine, root_directory), true }, + { NULL, } +}; + +static DBusHandlerResult machine_message_dispatch( + Machine *m, + DBusConnection *connection, + DBusMessage *message) { + + DBusError error; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + int r; + + assert(m); + assert(connection); + assert(message); + + if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Machine", "Terminate")) { + + r = machine_stop(m); + 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 (dbus_message_is_method_call(message, "org.freedesktop.machine1.Machine", "Kill")) { + const char *swho; + int32_t signo; + KillWho who; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = machine_kill(m, who, signo); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + } else { + const BusBoundProperties bps[] = { + { "org.freedesktop.machine1.Machine", bus_machine_machine_properties, m }, + { NULL, } + }; + + return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); + } + + if (reply) { + if (!bus_maybe_send_reply(connection, message, reply)) + goto oom; + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult machine_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *manager = userdata; + Machine *m; + int r; + + r = get_machine_for_path(manager, dbus_message_get_path(message), &m); + if (r < 0) { + + if (r == -ENOMEM) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (r == -ENOENT) { + DBusError e; + + dbus_error_init(&e); + dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown machine"); + return bus_send_error_reply(connection, message, &e, r); + } + + return bus_send_error_reply(connection, message, NULL, r); + } + + return machine_message_dispatch(m, connection, message); +} + +const DBusObjectPathVTable bus_machine_vtable = { + .message_function = machine_message_handler +}; + +char *machine_bus_path(Machine *m) { + _cleanup_free_ char *e = NULL; + + assert(m); + + e = bus_path_escape(m->name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/machine/", e); +} + +int machine_send_signal(Machine *m, bool new_machine) { + _cleanup_dbus_message_unref_ DBusMessage *msg = NULL; + _cleanup_free_ char *p = NULL; + + assert(m); + + msg = dbus_message_new_signal("/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + new_machine ? "MachineNew" : "MachineRemoved"); + + if (!m) + return -ENOMEM; + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + if (!dbus_message_append_args( + msg, + DBUS_TYPE_STRING, &m->name, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID)) + return -ENOMEM; + + if (!dbus_connection_send(m->manager->bus, msg, NULL)) + return -ENOMEM; + + return 0; +} + +int machine_send_changed(Machine *m, const char *properties) { + _cleanup_dbus_message_unref_ DBusMessage *msg = NULL; + _cleanup_free_ char *p = NULL; + + assert(m); + + if (!m->started) + return 0; + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + msg = bus_properties_changed_new(p, "org.freedesktop.machine1.Machine", properties); + if (!msg) + return -ENOMEM; + + if (!dbus_connection_send(m->manager->bus, msg, NULL)) + return -ENOMEM; + + return 0; +} + +int machine_send_create_reply(Machine *m, DBusError *error) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + + assert(m); + + if (!m->create_message) + return 0; + + if (error) { + DBusError buffer; + + dbus_error_init(&buffer); + + if (!error || !dbus_error_is_set(error)) { + dbus_set_error_const(&buffer, DBUS_ERROR_INVALID_ARGS, "Invalid Arguments"); + error = &buffer; + } + + reply = dbus_message_new_error(m->create_message, error->name, error->message); + dbus_error_free(&buffer); + + if (!reply) + return log_oom(); + } else { + _cleanup_free_ char *p = NULL; + + p = machine_bus_path(m); + if (!p) + return log_oom(); + + reply = dbus_message_new_method_return(m->create_message); + if (!reply) + return log_oom(); + + if (!dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &p, DBUS_TYPE_INVALID)) + return log_oom(); + } + + /* Update the machine state file before we notify the client + * about the result. */ + machine_save(m); + + if (!dbus_connection_send(m->manager->bus, reply, NULL)) + return log_oom(); + + dbus_message_unref(m->create_message); + m->create_message = NULL; + + return 0; +} diff --git a/src/machine/machine.c b/src/machine/machine.c new file mode 100644 index 0000000000..602aa18be6 --- /dev/null +++ b/src/machine/machine.c @@ -0,0 +1,414 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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/>. +***/ + +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <systemd/sd-messages.h> + +#include "util.h" +#include "mkdir.h" +#include "hashmap.h" +#include "strv.h" +#include "fileio.h" +#include "special.h" +#include "unit-name.h" +#include "dbus-common.h" +#include "machine.h" + +Machine* machine_new(Manager *manager, const char *name) { + Machine *m; + + assert(manager); + assert(name); + + m = new0(Machine, 1); + if (!m) + return NULL; + + m->name = strdup(name); + if (!m->name) + goto fail; + + m->state_file = strappend("/run/systemd/machines/", m->name); + if (!m->state_file) + goto fail; + + if (hashmap_put(manager->machines, m->name, m) < 0) + goto fail; + + m->class = _MACHINE_CLASS_INVALID; + m->manager = manager; + + return m; + +fail: + free(m->state_file); + free(m->name); + free(m); + + return NULL; +} + +void machine_free(Machine *m) { + assert(m); + + if (m->in_gc_queue) + LIST_REMOVE(Machine, gc_queue, m->manager->machine_gc_queue, m); + + if (m->scope) { + hashmap_remove(m->manager->machine_units, m->scope); + free(m->scope); + } + + free(m->scope_job); + + hashmap_remove(m->manager->machines, m->name); + + if (m->create_message) + dbus_message_unref(m->create_message); + + free(m->name); + free(m->state_file); + free(m->service); + free(m->root_directory); + free(m); +} + +int machine_save(Machine *m) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + assert(m->state_file); + + if (!m->started) + return 0; + + r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); + if (r < 0) + goto finish; + + r = fopen_temporary(m->state_file, &f, &temp_path); + if (r < 0) + goto finish; + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "NAME=%s\n", + m->name); + + if (m->scope) + fprintf(f, "SCOPE=%s\n", m->scope); + + if (m->scope_job) + fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); + + if (m->service) + fprintf(f, "SERVICE=%s\n", m->service); + + if (m->root_directory) + fprintf(f, "ROOT=%s\n", m->root_directory); + + if (!sd_id128_equal(m->id, SD_ID128_NULL)) + fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); + + if (m->leader != 0) + fprintf(f, "LEADER=%lu\n", (unsigned long) m->leader); + + if (m->class != _MACHINE_CLASS_INVALID) + fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); + + if (dual_timestamp_is_set(&m->timestamp)) + fprintf(f, + "REALTIME=%llu\n" + "MONOTONIC=%llu\n", + (unsigned long long) m->timestamp.realtime, + (unsigned long long) m->timestamp.monotonic); + + fflush(f); + + if (ferror(f) || rename(temp_path, m->state_file) < 0) { + r = -errno; + unlink(m->state_file); + unlink(temp_path); + } + +finish: + if (r < 0) + log_error("Failed to save machine data for %s: %s", m->name, strerror(-r)); + + return r; +} + +int machine_load(Machine *m) { + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL; + int r; + + assert(m); + + r = parse_env_file(m->state_file, NEWLINE, + "SCOPE", &m->scope, + "SCOPE_JOB", &m->scope_job, + "SERVICE", &m->service, + "ROOT", &m->root_directory, + "ID", &id, + "LEADER", &leader, + "CLASS", &class, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, + NULL); + if (r < 0) { + if (r == -ENOENT) + return 0; + + log_error("Failed to read %s: %s", m->state_file, strerror(-r)); + return r; + } + + if (id) + sd_id128_from_string(id, &m->id); + + if (leader) + parse_pid(leader, &m->leader); + + if (class) { + MachineClass c; + + c = machine_class_from_string(class); + if (c >= 0) + m->class = c; + } + + if (realtime) { + unsigned long long l; + if (sscanf(realtime, "%llu", &l) > 0) + m->timestamp.realtime = l; + } + + if (monotonic) { + unsigned long long l; + if (sscanf(monotonic, "%llu", &l) > 0) + m->timestamp.monotonic = l; + } + + return r; +} + +static int machine_start_scope(Machine *m, DBusMessageIter *iter) { + _cleanup_free_ char *description = NULL; + DBusError error; + char *job; + int r = 0; + + assert(m); + + dbus_error_init(&error); + + if (!m->scope) { + _cleanup_free_ char *escaped = NULL; + char *scope; + + escaped = unit_name_escape(m->name); + if (!escaped) + return log_oom(); + + scope = strjoin("machine-", escaped, ".scope", NULL); + if (!scope) + return log_oom(); + + description = strappend(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); + + r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, iter, &error, &job); + if (r < 0) { + log_error("Failed to start machine scope: %s", bus_error(&error, r)); + dbus_error_free(&error); + + free(scope); + return r; + } else { + m->scope = scope; + + free(m->scope_job); + m->scope_job = job; + } + } + + if (m->scope) + hashmap_put(m->manager->machine_units, m->scope, m); + + return r; +} + +int machine_start(Machine *m, DBusMessageIter *iter) { + int r; + + assert(m); + + if (m->started) + return 0; + + /* Create cgroup */ + r = machine_start_scope(m, iter); + if (r < 0) + return r; + + log_struct(LOG_INFO, + MESSAGE_ID(SD_MESSAGE_MACHINE_START), + "NAME=%s", m->name, + "LEADER=%lu", (unsigned long) m->leader, + "MESSAGE=New machine %s.", m->name, + NULL); + + if (!dual_timestamp_is_set(&m->timestamp)) + dual_timestamp_get(&m->timestamp); + + m->started = true; + + /* Save new machine data */ + machine_save(m); + + machine_send_signal(m, true); + + return 0; +} + +static int machine_stop_scope(Machine *m) { + DBusError error; + char *job; + int r; + + assert(m); + + dbus_error_init(&error); + + if (!m->scope) + return 0; + + r = manager_stop_unit(m->manager, m->scope, &error, &job); + if (r < 0) { + log_error("Failed to stop machine scope: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; + } + + free(m->scope_job); + m->scope_job = job; + + return r; +} + +int machine_stop(Machine *m) { + int r = 0, k; + assert(m); + + if (m->started) + log_struct(LOG_INFO, + MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), + "NAME=%s", m->name, + "LEADER=%lu", (unsigned long) m->leader, + "MESSAGE=Machine %s terminated.", m->name, + NULL); + + /* Kill cgroup */ + k = machine_stop_scope(m); + if (k < 0) + r = k; + + unlink(m->state_file); + machine_add_to_gc_queue(m); + + if (m->started) + machine_send_signal(m, false); + + m->started = false; + + return r; +} + +int machine_check_gc(Machine *m, bool drop_not_started) { + assert(m); + + if (drop_not_started && !m->started) + return 0; + + if (m->scope_job) + return 1; + + if (m->scope) + return manager_unit_is_active(m->manager, m->scope) != 0; + + return 0; +} + +void machine_add_to_gc_queue(Machine *m) { + assert(m); + + if (m->in_gc_queue) + return; + + LIST_PREPEND(Machine, gc_queue, m->manager->machine_gc_queue, m); + m->in_gc_queue = true; +} + +MachineState machine_get_state(Machine *s) { + assert(s); + + if (s->scope_job) + return s->started ? MACHINE_OPENING : MACHINE_CLOSING; + + return MACHINE_RUNNING; +} + +int machine_kill(Machine *m, KillWho who, int signo) { + assert(m); + + if (!m->scope) + return -ESRCH; + + return manager_kill_unit(m->manager, m->scope, who, signo, NULL); +} + +static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { + [MACHINE_CONTAINER] = "container", + [MACHINE_VM] = "vm" +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); + +static const char* const machine_state_table[_MACHINE_STATE_MAX] = { + [MACHINE_OPENING] = "opening", + [MACHINE_RUNNING] = "running", + [MACHINE_CLOSING] = "closing" +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_LEADER] = "leader", + [KILL_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/machine/machine.h b/src/machine/machine.h new file mode 100644 index 0000000000..c5d52a968b --- /dev/null +++ b/src/machine/machine.h @@ -0,0 +1,109 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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/>. +***/ + +typedef struct Machine Machine; +typedef enum KillWho KillWho; + +#include "list.h" +#include "util.h" +#include "machined.h" + +typedef enum MachineState { + MACHINE_OPENING, /* Machine is being registered */ + MACHINE_RUNNING, /* Machine is running */ + MACHINE_CLOSING, /* Machine is terminating */ + _MACHINE_STATE_MAX, + _MACHINE_STATE_INVALID = -1 +} MachineState; + +typedef enum MachineClass { + MACHINE_CONTAINER, + MACHINE_VM, + _MACHINE_CLASS_MAX, + _MACHINE_CLASS_INVALID = -1 +} MachineClass; + +enum KillWho { + KILL_LEADER, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +}; + +struct Machine { + Manager *manager; + + char *name; + sd_id128_t id; + + MachineState state; + MachineClass class; + + char *state_file; + char *service; + char *root_directory; + + char *scope; + char *scope_job; + + pid_t leader; + + dual_timestamp timestamp; + + bool in_gc_queue:1; + bool started:1; + + DBusMessage *create_message; + + LIST_FIELDS(Machine, gc_queue); +}; + +Machine* machine_new(Manager *manager, const char *name); +void machine_free(Machine *m); +int machine_check_gc(Machine *m, bool drop_not_started); +void machine_add_to_gc_queue(Machine *m); +int machine_start(Machine *m, DBusMessageIter *iter); +int machine_stop(Machine *m); +int machine_save(Machine *m); +int machine_load(Machine *m); +int machine_kill(Machine *m, KillWho who, int signo); + +char *machine_bus_path(Machine *s); + +MachineState machine_get_state(Machine *u); + +extern const DBusObjectPathVTable bus_machine_vtable; + +int machine_send_signal(Machine *m, bool new_machine); +int machine_send_changed(Machine *m, const char *properties); + +int machine_send_create_reply(Machine *m, DBusError *error); + +const char* machine_class_to_string(MachineClass t) _const_; +MachineClass machine_class_from_string(const char *s) _pure_; + +const char* machine_state_to_string(MachineState t) _const_; +MachineState machine_state_from_string(const char *s) _pure_; + +const char *kill_who_to_string(KillWho k) _const_; +KillWho kill_who_from_string(const char *s) _pure_; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c new file mode 100644 index 0000000000..97c2193551 --- /dev/null +++ b/src/machine/machinectl.c @@ -0,0 +1,816 @@ +/*-*- 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 <dbus/dbus.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <getopt.h> +#include <pwd.h> +#include <locale.h> + +#include "log.h" +#include "util.h" +#include "macro.h" +#include "pager.h" +#include "dbus-common.h" +#include "build.h" +#include "strv.h" +#include "unit-name.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "spawn-polkit-agent.h" + +static char **arg_property = NULL; +static bool arg_all = false; +static bool arg_full = false; +static bool arg_no_pager = false; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static enum transport { + TRANSPORT_NORMAL, + TRANSPORT_SSH, + TRANSPORT_POLKIT +} arg_transport = TRANSPORT_NORMAL; +static bool arg_ask_password = true; +static char *arg_host = NULL; +static char *arg_user = NULL; + +static void pager_open_if_enabled(void) { + + /* Cache result before we open the pager */ + if (arg_no_pager) + return; + + pager_open(false); +} + +static int list_machines(DBusConnection *bus, char **args, unsigned n) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + DBusMessageIter iter, sub, sub2; + unsigned k = 0; + int r; + + pager_open_if_enabled(); + + r = bus_method_call_with_reply ( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListMachines", + &reply, + NULL, + DBUS_TYPE_INVALID); + if (r) + return r; + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (on_tty()) + printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE"); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *name, *class, *service, *object; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &class, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &service, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) { + log_error("Failed to parse reply."); + return -EIO; + } + + printf("%-32s %-9s %-16s\n", name, class, service); + + k++; + + dbus_message_iter_next(&sub); + } + + if (on_tty()) + printf("\n%u machines listed.\n", k); + + return 0; +} + +static int show_scope_cgroup(DBusConnection *bus, const char *unit, pid_t leader) { + const char *interface = "org.freedesktop.systemd1.Scope"; + const char *property = "ControlGroup"; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_free_ char *path = NULL; + DBusMessageIter iter, sub; + const char *cgroup; + DBusError error; + int r, output_flags; + unsigned c; + + assert(bus); + assert(unit); + + if (arg_transport == TRANSPORT_SSH) + return 0; + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get", + &reply, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to query ControlGroup: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_recurse(&iter, &sub); + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_get_basic(&sub, &cgroup); + + if (isempty(cgroup)) + return 0; + + if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0) + return 0; + + output_flags = + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH; + + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags); + return 0; +} + +typedef struct MachineStatusInfo { + const char *name; + sd_id128_t id; + const char *class; + const char *service; + const char *scope; + const char *root_directory; + pid_t leader; + usec_t timestamp; +} MachineStatusInfo; + +static void print_machine_status_info(DBusConnection *bus, MachineStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + assert(i); + + fputs(strna(i->name), stdout); + + if (!sd_id128_equal(i->id, SD_ID128_NULL)) + printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); + else + putchar('\n'); + + s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (i->leader > 0) { + _cleanup_free_ char *t = NULL; + + printf("\t Leader: %u", (unsigned) i->leader); + + get_process_comm(i->leader, &t); + if (t) + printf(" (%s)", t); + + putchar('\n'); + } + + if (i->service) { + printf("\t Service: %s", i->service); + + if (i->class) + printf("; class %s", i->class); + + putchar('\n'); + } else if (i->class) + printf("\t Class: %s\n", i->class); + + if (i->root_directory) + printf("\t Root: %s\n", i->root_directory); + + if (i->scope) { + printf("\t Unit: %s\n", i->scope); + show_scope_cgroup(bus, i->scope, i->leader); + } +} + +static int status_property_machine(const char *name, DBusMessageIter *iter, MachineStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Name")) + i->name = s; + else if (streq(name, "Class")) + i->class = s; + else if (streq(name, "Service")) + i->service = s; + else if (streq(name, "Scope")) + i->scope = s; + else if (streq(name, "RootDirectory")) + i->root_directory = s; + } + break; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "Leader")) + i->leader = (pid_t) u; + + break; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "Timestamp")) + i->timestamp = (usec_t) u; + + break; + } + + case DBUS_TYPE_ARRAY: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE && streq(name, "Id")) { + void *v; + int n; + + dbus_message_iter_get_fixed_array(&sub, &v, &n); + if (n == 0) + i->id = SD_ID128_NULL; + else if (n == 16) + memcpy(&i->id, v, n); + } + + break; + } + } + + return 0; +} + +static int print_property(const char *name, DBusMessageIter *iter) { + assert(name); + assert(iter); + + if (arg_property && !strv_find(arg_property, name)) + return 0; + + if (generic_print_property(name, iter, arg_all) > 0) + return 0; + + if (arg_all) + printf("%s=[unprintable]\n", name); + + return 0; +} + +static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *interface = ""; + int r; + DBusMessageIter iter, sub, sub2, sub3; + MachineStatusInfo machine_info = {}; + + assert(path); + assert(new_line); + + r = bus_method_call_with_reply( + bus, + "org.freedesktop.machine1", + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &reply, + NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID); + if (r < 0) + goto finish; + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (*new_line) + printf("\n"); + + *new_line = true; + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *name; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub2, &sub3); + + if (show_properties) + r = print_property(name, &sub3); + else + r = status_property_machine(name, &sub3, &machine_info); + + if (r < 0) { + log_error("Failed to parse reply."); + goto finish; + } + + dbus_message_iter_next(&sub); + } + + if (!show_properties) + print_machine_status_info(bus, &machine_info); + + r = 0; + +finish: + + return r; +} + +static int show(DBusConnection *bus, char **args, unsigned n) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + int r, ret = 0; + DBusError error; + unsigned i; + bool show_properties, new_line = false; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + show_properties = !strstr(args[0], "status"); + + pager_open_if_enabled(); + + if (show_properties && n <= 1) { + /* If not argument is specified inspect the manager + * itself */ + + ret = show_one(args[0], bus, "/org/freedesktop/machine1", show_properties, &new_line); + goto finish; + } + + for (i = 1; i < n; i++) { + const char *path = NULL; + + ret = bus_method_call_with_reply( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachine", + &reply, + NULL, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID); + if (ret < 0) + goto finish; + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + r = show_one(args[0], bus, path, show_properties, &new_line); + if (r != 0) + ret = r; + } + +finish: + dbus_error_free(&error); + + return ret; +} + +static int kill_machine(DBusConnection *bus, char **args, unsigned n) { + unsigned i; + + assert(args); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < n; i++) { + int r; + + r = bus_method_call_with_reply ( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "KillMachine", + NULL, + NULL, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_STRING, &arg_kill_who, + DBUS_TYPE_INT32, &arg_signal, + DBUS_TYPE_INVALID); + if (r) + return r; + } + + return 0; +} + +static int terminate_machine(DBusConnection *bus, char **args, unsigned n) { + unsigned i; + + assert(args); + + for (i = 1; i < n; i++) { + int r; + + r = bus_method_call_with_reply ( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "TerminateMachine", + NULL, + NULL, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID); + if (r) + return r; + } + + return 0; +} + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the virtual machine and container registration manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all properties, including empty ones\n" + " --kill-who=WHO Who to send signal to\n" + " -l --full Do not ellipsize output\n" + " -s --signal=SIGNAL Which signal to send\n" + " --no-ask-password Don't prompt for password\n" + " -H --host=[USER@]HOST Show information for remote host\n" + " -P --privileged Acquire privileges before execution\n" + " --no-pager Do not pipe output into a pager\n\n" + "Commands:\n" + " list List running VMs and containers\n" + " status [NAME...] Show VM/container status\n" + " show [NAME...] Show properties of one or more VMs/containers\n" + " terminate [NAME...] Terminate one or more VMs/containers\n" + " kill [NAME...] Send signal to processes of a VM/container\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_KILL_WHO, + ARG_NO_ASK_PASSWORD, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "privileged", no_argument, NULL, 'P' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + + case 'p': { + char **l; + + l = strv_append(arg_property, optarg); + if (!l) + return -ENOMEM; + + strv_free(arg_property); + arg_property = l; + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + } + + case 'a': + arg_all = true; + break; + + case 'l': + arg_full = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case 'P': + arg_transport = TRANSPORT_POLKIT; + break; + + case 'H': + arg_transport = TRANSPORT_SSH; + parse_user_at_host(optarg, &arg_user, &arg_host); + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +static int machinectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(DBusConnection *bus, char **args, unsigned n); + } verbs[] = { + { "list", LESS, 1, list_machines }, + { "status", MORE, 2, show }, + { "show", MORE, 1, show }, + { "terminate", MORE, 2, terminate_machine }, + { "kill", MORE, 2, kill_machine }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + assert(error); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "list-sessions" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + if (!bus) { + log_error("Failed to get D-Bus connection: %s", error->message); + return -EIO; + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +int main(int argc, char*argv[]) { + int r, retval = EXIT_FAILURE; + DBusConnection *bus = NULL; + DBusError error; + + dbus_error_init(&error); + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + goto finish; + else if (r == 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (arg_transport == TRANSPORT_NORMAL) + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + else if (arg_transport == TRANSPORT_POLKIT) + bus_connect_system_polkit(&bus, &error); + else if (arg_transport == TRANSPORT_SSH) + bus_connect_system_ssh(NULL, arg_host, &bus, &error); + else + assert_not_reached("Uh, invalid transport..."); + + r = machinectl_main(bus, argc, argv, &error); + retval = r < 0 ? EXIT_FAILURE : r; + +finish: + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); + dbus_shutdown(); + + strv_free(arg_property); + + pager_close(); + + return retval; +} diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c new file mode 100644 index 0000000000..22caadfceb --- /dev/null +++ b/src/machine/machined-dbus.c @@ -0,0 +1,1042 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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/>. +***/ + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> + +#include <systemd/sd-id128.h> +#include <systemd/sd-messages.h> + +#include "machined.h" +#include "dbus-common.h" +#include "strv.h" +#include "mkdir.h" +#include "path-util.h" +#include "special.h" +#include "sleep-config.h" +#include "fileio-label.h" +#include "label.h" +#include "utf8.h" +#include "unit-name.h" +#include "bus-errors.h" +#include "virt.h" +#include "cgroup-util.h" + +#define BUS_MANAGER_INTERFACE \ + " <interface name=\"org.freedesktop.machine1.Manager\">\n" \ + " <method name=\"GetMachine\">\n" \ + " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"machine\" type=\"o\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"GetMachineByPID\">\n" \ + " <arg name=\"pid\" type=\"u\" direction=\"in\"/>\n" \ + " <arg name=\"machine\" type=\"o\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"ListMachines\">\n" \ + " <arg name=\"machines\" type=\"a(ssso)\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"CreateMachine\">\n" \ + " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"id\" type=\"ay\" direction=\"in\"/>\n" \ + " <arg name=\"service\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"class\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"leader\" type=\"u\" direction=\"in\"/>\n" \ + " <arg name=\"root_directory\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"scope_properties\" type=\"a(sv)\" direction=\"in\"/>\n" \ + " <arg name=\"path\" type=\"o\" direction=\"out\"/>\n" \ + " </method>\n" \ + " <method name=\"KillMachine\">\n" \ + " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"who\" type=\"s\" direction=\"in\"/>\n" \ + " <arg name=\"signal\" type=\"s\" direction=\"in\"/>\n" \ + " </method>\n" \ + " <method name=\"TerminateMachine\">\n" \ + " <arg name=\"id\" type=\"s\" direction=\"in\"/>\n" \ + " </method>\n" \ + " <signal name=\"MachineNew\">\n" \ + " <arg name=\"machine\" type=\"s\"/>\n" \ + " <arg name=\"path\" type=\"o\"/>\n" \ + " </signal>\n" \ + " <signal name=\"MachineRemoved\">\n" \ + " <arg name=\"machine\" type=\"s\"/>\n" \ + " <arg name=\"path\" type=\"o\"/>\n" \ + " </signal>\n" \ + " </interface>\n" + +#define INTROSPECTION_BEGIN \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>\n" \ + BUS_MANAGER_INTERFACE \ + BUS_PROPERTIES_INTERFACE \ + BUS_PEER_INTERFACE \ + BUS_INTROSPECTABLE_INTERFACE + +#define INTROSPECTION_END \ + "</node>\n" + +#define INTERFACES_LIST \ + BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.machine1.Manager\0" + +static bool valid_machine_name(const char *p) { + size_t l; + + if (!filename_is_safe(p)) + return false; + + if (!ascii_is_valid(p)) + return false; + + l = strlen(p); + + if (l < 1 || l> 64) + return false; + + return true; +} + +static int bus_manager_create_machine(Manager *manager, DBusMessage *message) { + + const char *name, *service, *class, *root_directory; + DBusMessageIter iter, sub; + MachineClass c; + uint32_t leader; + sd_id128_t id; + Machine *m; + int n, r; + void *v; + + assert(manager); + assert(message); + + if (!dbus_message_iter_init(message, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &name); + + if (!valid_machine_name(name) || + !dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE) + return -EINVAL; + + dbus_message_iter_recurse(&iter, &sub); + dbus_message_iter_get_fixed_array(&sub, &v, &n); + + if (n == 0) + id = SD_ID128_NULL; + else if (n == 16) + memcpy(&id, v, n); + else + return -EINVAL; + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &service); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &class); + + if (isempty(class)) + c = _MACHINE_CLASS_INVALID; + else { + c = machine_class_from_string(class); + if (c < 0) + return -EINVAL; + } + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &leader); + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&iter, &root_directory); + + if (!(isempty(root_directory) || path_is_absolute(root_directory))) + return -EINVAL; + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(&iter, &sub); + + if (hashmap_get(manager->machines, name)) + return -EEXIST; + + if (leader <= 0) { + leader = bus_get_unix_process_id(manager->bus, dbus_message_get_sender(message), NULL); + if (leader == 0) + return -EINVAL; + } + + r = manager_add_machine(manager, name, &m); + if (r < 0) + goto fail; + + m->leader = leader; + m->class = c; + m->id = id; + + if (!isempty(service)) { + m->service = strdup(service); + if (!m->service) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(root_directory)) { + m->root_directory = strdup(root_directory); + if (!m->root_directory) { + r = -ENOMEM; + goto fail; + } + } + + r = machine_start(m, &sub); + if (r < 0) + goto fail; + + m->create_message = dbus_message_ref(message); + + return 0; + +fail: + if (m) + machine_add_to_gc_queue(m); + + return r; +} + +static DBusHandlerResult manager_message_handler( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + + DBusError error; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + int r; + + assert(connection); + assert(message); + assert(m); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "GetMachine")) { + Machine *machine; + const char *name; + char *p; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + machine = hashmap_get(m->machines, name); + if (!machine) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = machine_bus_path(machine); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "GetMachineByPID")) { + uint32_t pid; + char *p; + Machine *machine; + bool b; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r <= 0) + return bus_send_error_reply(connection, message, NULL, r < 0 ? r : -ENOENT); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + p = machine_bus_path(machine); + if (!p) + goto oom; + + b = dbus_message_append_args( + reply, + DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + free(p); + + if (!b) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "ListMachines")) { + Machine *machine; + Iterator i; + DBusMessageIter iter, sub; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssso)", &sub)) + goto oom; + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_free_ char *p = NULL; + DBusMessageIter sub2; + const char *class; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + goto oom; + + p = machine_bus_path(machine); + if (!p) + goto oom; + + class = strempty(machine_class_to_string(machine->class)); + + if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &machine->name) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &class) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &machine->service) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) { + free(p); + goto oom; + } + + if (!dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "CreateMachine")) { + + r = bus_manager_create_machine(m, message); + if (r < 0) + return bus_send_error_reply(connection, message, NULL, r); + + } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "KillMachine")) { + const char *swho; + int32_t signo; + KillWho who; + const char *name; + Machine *machine; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &swho, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return bus_send_error_reply(connection, message, &error, -EINVAL); + } + + if (signo <= 0 || signo >= _NSIG) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + machine = hashmap_get(m->machines, name); + if (!machine) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = machine_kill(machine, who, signo); + 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 (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "TerminateMachine")) { + const char *name; + Machine *machine; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + machine = hashmap_get(m->machines, name); + if (!machine) + return bus_send_error_reply(connection, message, &error, -ENOENT); + + r = machine_stop(machine); + 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 (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + char *introspection = NULL; + FILE *f; + Iterator i; + Machine *machine; + size_t size; + char *p; + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + /* We roll our own introspection code here, instead of + * relying on bus_default_message_handler() because we + * need to generate our introspection string + * dynamically. */ + + f = open_memstream(&introspection, &size); + if (!f) + goto oom; + + fputs(INTROSPECTION_BEGIN, f); + + HASHMAP_FOREACH(machine, m->machines, i) { + p = bus_path_escape(machine->name); + + if (p) { + fprintf(f, "<node name=\"machine/%s\"/>", p); + free(p); + } + } + + fputs(INTROSPECTION_END, f); + + if (ferror(f)) { + fclose(f); + free(introspection); + goto oom; + } + + fclose(f); + + if (!introspection) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) { + free(introspection); + goto oom; + } + + free(introspection); + } else + return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, NULL); + + if (reply) { + if (!bus_maybe_send_reply(connection, message, reply)) + goto oom; + } + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +const DBusObjectPathVTable bus_manager_vtable = { + .message_function = manager_message_handler +}; + +DBusHandlerResult bus_message_filter( + DBusConnection *connection, + DBusMessage *message, + void *userdata) { + + Manager *m = userdata; + DBusError error; + + assert(m); + assert(connection); + assert(message); + + dbus_error_init(&error); + + log_debug("Got message: %s %s %s", strna(dbus_message_get_sender(message)), strna(dbus_message_get_interface(message)), strna(dbus_message_get_member(message))); + + if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { + const char *path, *result, *unit; + Machine *mm; + uint32_t id; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_STRING, &result, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse JobRemoved message: %s", bus_error_message(&error)); + goto finish; + } + + mm = hashmap_get(m->machine_units, unit); + if (mm) { + if (streq_ptr(path, mm->scope_job)) { + free(mm->scope_job); + mm->scope_job = NULL; + + if (mm->started) { + if (streq(result, "done")) + machine_send_create_reply(mm, NULL); + else { + dbus_set_error(&error, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); + machine_send_create_reply(mm, &error); + } + } else + machine_save(mm); + } + + machine_add_to_gc_queue(mm); + } + + } else if (dbus_message_is_signal(message, "org.freedesktop.DBus.Properties", "PropertiesChanged")) { + + _cleanup_free_ char *unit = NULL; + const char *path; + + path = dbus_message_get_path(message); + if (!path) + goto finish; + + unit_name_from_dbus_path(path, &unit); + if (unit) { + Machine *mm; + + mm = hashmap_get(m->machine_units, unit); + if (mm) + machine_add_to_gc_queue(mm); + } + + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "UnitRemoved")) { + const char *path, *unit; + Machine *mm; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse UnitRemoved message: %s", bus_error_message(&error)); + goto finish; + } + + mm = hashmap_get(m->machine_units, unit); + if (mm) + machine_add_to_gc_queue(mm); + + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "Reloading")) { + dbus_bool_t b; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse Reloading message: %s", bus_error_message(&error)); + goto finish; + } + + /* systemd finished reloading, let's recheck all our machines */ + if (!b) { + Machine *mm; + Iterator i; + + log_debug("System manager has been reloaded, rechecking machines..."); + + HASHMAP_FOREACH(mm, m->machines, i) + machine_add_to_gc_queue(mm); + } + } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int copy_many_fields(DBusMessageIter *dest, DBusMessageIter *src); + +static int copy_one_field(DBusMessageIter *dest, DBusMessageIter *src) { + int type, r; + + type = dbus_message_iter_get_arg_type(src); + + switch (type) { + + case DBUS_TYPE_STRUCT: { + DBusMessageIter dest_sub, src_sub; + + dbus_message_iter_recurse(src, &src_sub); + + if (!dbus_message_iter_open_container(dest, DBUS_TYPE_STRUCT, NULL, &dest_sub)) + return log_oom(); + + r = copy_many_fields(&dest_sub, &src_sub); + if (r < 0) + return r; + + if (!dbus_message_iter_close_container(dest, &dest_sub)) + return log_oom(); + + return 0; + } + + case DBUS_TYPE_ARRAY: { + DBusMessageIter dest_sub, src_sub; + + dbus_message_iter_recurse(src, &src_sub); + + if (!dbus_message_iter_open_container(dest, DBUS_TYPE_ARRAY, dbus_message_iter_get_signature(&src_sub), &dest_sub)) + return log_oom(); + + r = copy_many_fields(&dest_sub, &src_sub); + if (r < 0) + return r; + + if (!dbus_message_iter_close_container(dest, &dest_sub)) + return log_oom(); + + return 0; + } + + case DBUS_TYPE_VARIANT: { + DBusMessageIter dest_sub, src_sub; + + dbus_message_iter_recurse(src, &src_sub); + + if (!dbus_message_iter_open_container(dest, DBUS_TYPE_VARIANT, dbus_message_iter_get_signature(&src_sub), &dest_sub)) + return log_oom(); + + r = copy_one_field(&dest_sub, &src_sub); + if (r < 0) + return r; + + if (!dbus_message_iter_close_container(dest, &dest_sub)) + return log_oom(); + + return 0; + } + + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_BYTE: + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_UINT16: + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_INT64: + case DBUS_TYPE_DOUBLE: + case DBUS_TYPE_SIGNATURE: { + const void *p; + + dbus_message_iter_get_basic(src, &p); + dbus_message_iter_append_basic(dest, type, &p); + return 0; + } + + default: + return -EINVAL; + } +} + +static int copy_many_fields(DBusMessageIter *dest, DBusMessageIter *src) { + int r; + + assert(dest); + assert(src); + + while (dbus_message_iter_get_arg_type(src) != DBUS_TYPE_INVALID) { + + r = copy_one_field(dest, src); + if (r < 0) + return r; + + dbus_message_iter_next(src); + } + + return 0; +} + +int manager_start_scope( + Manager *manager, + const char *scope, + pid_t pid, + const char *slice, + const char *description, + DBusMessageIter *more_properties, + DBusError *error, + char **job) { + + _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; + DBusMessageIter iter, sub, sub2, sub3, sub4; + const char *timeout_stop_property = "TimeoutStopUSec"; + const char *pids_property = "PIDs"; + uint64_t timeout = 500 * USEC_PER_MSEC; + const char *fail = "fail"; + uint32_t u; + int r; + + assert(manager); + assert(scope); + assert(pid > 1); + + if (!slice) + slice = ""; + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (!m) + return log_oom(); + + dbus_message_iter_init_append(m, &iter); + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &scope) || + !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &fail) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub)) + return log_oom(); + + if (!isempty(slice)) { + const char *slice_property = "Slice"; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &slice_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &slice) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + } + + if (!isempty(description)) { + const char *description_property = "Description"; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &description) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + } + + /* cgroup empty notification is not available in containers + * currently. To make this less problematic, let's shorten the + * stop timeout for sessions, so that we don't wait + * forever. */ + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &timeout_stop_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "t", &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_UINT64, &timeout) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + u = pid; + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &pids_property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "au", &sub3) || + !dbus_message_iter_open_container(&sub3, DBUS_TYPE_ARRAY, "u", &sub4) || + !dbus_message_iter_append_basic(&sub4, DBUS_TYPE_UINT32, &u) || + !dbus_message_iter_close_container(&sub3, &sub4) || + !dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + if (more_properties) { + r = copy_many_fields(&sub, more_properties); + if (r < 0) + return r; + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + return log_oom(); + + reply = dbus_connection_send_with_reply_and_block(manager->bus, m, -1, error); + if (!reply) + return -EIO; + + if (job) { + const char *j; + char *copy; + + if (!dbus_message_get_args(reply, error, DBUS_TYPE_OBJECT_PATH, &j, DBUS_TYPE_INVALID)) + return -EIO; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 0; +} + +int manager_stop_unit(Manager *manager, const char *unit, DBusError *error, char **job) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *fail = "fail"; + int r; + + assert(manager); + assert(unit); + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + &reply, + error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_STRING, &fail, + DBUS_TYPE_INVALID); + if (r < 0) { + if (dbus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || + dbus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { + + if (job) + *job = NULL; + + dbus_error_free(error); + return 0; + } + + log_error("Failed to stop unit %s: %s", unit, bus_error(error, r)); + return r; + } + + if (job) { + const char *j; + char *copy; + + if (!dbus_message_get_args(reply, error, + DBUS_TYPE_OBJECT_PATH, &j, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply."); + return -EIO; + } + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 1; +} + +int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, DBusError *error) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *w; + int r; + + assert(manager); + assert(unit); + + w = who == KILL_LEADER ? "process" : "cgroup"; + assert_cc(sizeof(signo) == sizeof(int32_t)); + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + &reply, + error, + DBUS_TYPE_STRING, &unit, + DBUS_TYPE_STRING, &w, + DBUS_TYPE_INT32, &signo, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to stop unit %s: %s", unit, bus_error(error, r)); + return r; + } + + return 0; +} + +int manager_unit_is_active(Manager *manager, const char *unit) { + + const char *interface = "org.freedesktop.systemd1.Unit"; + const char *property = "ActiveState"; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_free_ char *path = NULL; + DBusMessageIter iter, sub; + const char *state; + DBusError error; + int r; + + assert(manager); + assert(unit); + + dbus_error_init(&error); + + path = unit_dbus_path_from_name(unit); + if (!path) + return -ENOMEM; + + r = bus_method_call_with_reply( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get", + &reply, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID); + if (r < 0) { + if (dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY) || + dbus_error_has_name(&error, DBUS_ERROR_DISCONNECTED)) { + dbus_error_free(&error); + return true; + } + + if (dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || + dbus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) { + dbus_error_free(&error); + return false; + } + + log_error("Failed to query ActiveState: %s", bus_error(&error, r)); + dbus_error_free(&error); + return r; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_recurse(&iter, &sub); + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + return -EINVAL; + } + + dbus_message_iter_get_basic(&sub, &state); + + return !streq(state, "inactive") && !streq(state, "failed"); +} + +int manager_add_machine(Manager *m, const char *name, Machine **_machine) { + Machine *machine; + + assert(m); + assert(name); + + machine = hashmap_get(m->machines, name); + if (machine) { + if (_machine) + *_machine = machine; + + return 0; + } + + machine = machine_new(m, name); + if (!machine) + return -ENOMEM; + + if (_machine) + *_machine = machine; + + return 0; +} + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { + _cleanup_free_ char *unit = NULL; + Machine *mm; + int r; + + assert(m); + assert(pid >= 1); + assert(machine); + + r = cg_pid_get_unit(pid, &unit); + if (r < 0) + return r; + + mm = hashmap_get(m->machine_units, unit); + if (!mm) + return 0; + + *machine = mm; + return 1; +} diff --git a/src/machine/machined.c b/src/machine/machined.c new file mode 100644 index 0000000000..ad804a1e14 --- /dev/null +++ b/src/machine/machined.c @@ -0,0 +1,386 @@ +/*-*- 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 <errno.h> +#include <pwd.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <sys/epoll.h> + +#include <systemd/sd-daemon.h> + +#include "machined.h" +#include "dbus-common.h" +#include "dbus-loop.h" +#include "strv.h" +#include "conf-parser.h" +#include "mkdir.h" + +Manager *manager_new(void) { + Manager *m; + + m = new0(Manager, 1); + if (!m) + return NULL; + + m->bus_fd = -1; + m->epoll_fd = -1; + + m->machines = hashmap_new(string_hash_func, string_compare_func); + m->machine_units = hashmap_new(string_hash_func, string_compare_func); + + if (!m->machines || !m->machine_units) { + manager_free(m); + return NULL; + } + + return m; +} + +void manager_free(Manager *m) { + Machine *machine; + + assert(m); + + while ((machine = hashmap_first(m->machines))) + machine_free(machine); + + hashmap_free(m->machines); + hashmap_free(m->machine_units); + + if (m->bus) { + dbus_connection_flush(m->bus); + dbus_connection_close(m->bus); + dbus_connection_unref(m->bus); + } + + if (m->bus_fd >= 0) + close_nointr_nofail(m->bus_fd); + + if (m->epoll_fd >= 0) + close_nointr_nofail(m->epoll_fd); + + free(m); +} + +int manager_enumerate_machines(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(m); + + /* Read in machine data stored on disk */ + d = opendir("/run/systemd/machines"); + if (!d) { + if (errno == ENOENT) + return 0; + + log_error("Failed to open /run/systemd/machines: %m"); + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + struct Machine *machine; + int k; + + if (!dirent_is_file(de)) + continue; + + k = manager_add_machine(m, de->d_name, &machine); + if (k < 0) { + log_error("Failed to add machine by file name %s: %s", de->d_name, strerror(-k)); + + r = k; + continue; + } + + machine_add_to_gc_queue(machine); + + k = machine_load(machine); + if (k < 0) + r = k; + } + + return r; +} + +static int manager_connect_bus(Manager *m) { + DBusError error; + int r; + struct epoll_event ev = { + .events = EPOLLIN, + .data.u32 = FD_BUS, + }; + + assert(m); + assert(!m->bus); + assert(m->bus_fd < 0); + + dbus_error_init(&error); + + m->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (!m->bus) { + log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error)); + r = -ECONNREFUSED; + goto fail; + } + + if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/machine1", &bus_manager_vtable, m) || + !dbus_connection_register_fallback(m->bus, "/org/freedesktop/machine1/machine", &bus_machine_vtable, m) || + !dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) { + r = log_oom(); + goto fail; + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for JobRemoved: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='UnitRemoved'," + "path='/org/freedesktop/systemd1'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for UnitRemoved: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for PropertiesChanged: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + dbus_bus_add_match(m->bus, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='Reloading'," + "path='/org/freedesktop/systemd1'", + &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to add match for Reloading: %s", bus_error_message(&error)); + dbus_error_free(&error); + } + + r = bus_method_call_with_reply( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + NULL, + &error, + DBUS_TYPE_INVALID); + if (r < 0) { + log_error("Failed to enable subscription: %s", bus_error(&error, r)); + dbus_error_free(&error); + } + + r = dbus_bus_request_name(m->bus, "org.freedesktop.machine1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); + if (dbus_error_is_set(&error)) { + log_error("Failed to register name on bus: %s", bus_error_message(&error)); + r = -EIO; + goto fail; + } + + if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + log_error("Failed to acquire name."); + r = -EEXIST; + goto fail; + } + + m->bus_fd = bus_loop_open(m->bus); + if (m->bus_fd < 0) { + r = m->bus_fd; + goto fail; + } + + if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->bus_fd, &ev) < 0) + goto fail; + + return 0; + +fail: + dbus_error_free(&error); + + return r; +} + +void manager_gc(Manager *m, bool drop_not_started) { + Machine *machine; + + assert(m); + + while ((machine = m->machine_gc_queue)) { + LIST_REMOVE(Machine, gc_queue, m->machine_gc_queue, machine); + machine->in_gc_queue = false; + + if (machine_check_gc(machine, drop_not_started) == 0) { + machine_stop(machine); + machine_free(machine); + } + } +} + +int manager_startup(Manager *m) { + int r; + Machine *machine; + Iterator i; + + assert(m); + assert(m->epoll_fd <= 0); + + m->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (m->epoll_fd < 0) + return -errno; + + /* Connect to the bus */ + r = manager_connect_bus(m); + if (r < 0) + return r; + + /* Deserialize state */ + manager_enumerate_machines(m); + + /* Remove stale objects before we start them */ + manager_gc(m, false); + + /* And start everything */ + HASHMAP_FOREACH(machine, m->machines, i) + machine_start(machine, NULL); + + return 0; +} + +int manager_run(Manager *m) { + assert(m); + + for (;;) { + struct epoll_event event; + int n; + + manager_gc(m, true); + + if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE) + continue; + + manager_gc(m, true); + + n = epoll_wait(m->epoll_fd, &event, 1, -1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + log_error("epoll() failed: %m"); + return -errno; + } + + if (n == 0) + continue; + + switch (event.data.u32) { + + case FD_BUS: + bus_loop_dispatch(m->bus_fd); + break; + + default: + assert_not_reached("Unknown fd"); + } + } + + return 0; +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_AUTH); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + /* Always create the directories people can create inotify + * watches in. Note that some applications might check for the + * existence of /run/systemd/seats/ to determine whether + * machined is available, so please always make sure this check + * stays in. */ + mkdir_label("/run/systemd/machines", 0755); + + m = manager_new(); + if (!m) { + r = log_oom(); + goto finish; + } + + r = manager_startup(m); + if (r < 0) { + log_error("Failed to fully start up daemon: %s", strerror(-r)); + goto finish; + } + + log_debug("systemd-machined running as pid %lu", (unsigned long) getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + + log_debug("systemd-machined stopped as pid %lu", (unsigned long) getpid()); + +finish: + sd_notify(false, + "STATUS=Shutting down..."); + + if (m) + manager_free(m); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/machined.h b/src/machine/machined.h new file mode 100644 index 0000000000..780f51678c --- /dev/null +++ b/src/machine/machined.h @@ -0,0 +1,73 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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 <stdbool.h> +#include <inttypes.h> +#include <dbus/dbus.h> + +#include "util.h" +#include "list.h" +#include "hashmap.h" + +typedef struct Manager Manager; + +#include "machine.h" + +struct Manager { + DBusConnection *bus; + + int bus_fd; + int epoll_fd; + + Hashmap *machines; + Hashmap *machine_units; + + LIST_HEAD(Machine, machine_gc_queue); +}; + +enum { + FD_BUS +}; + +Manager *manager_new(void); +void manager_free(Manager *m); + +int manager_add_machine(Manager *m, const char *name, Machine **_machine); + +int manager_enumerate_machines(Manager *m); + +int manager_startup(Manager *m); +int manager_run(Manager *m); + +void manager_gc(Manager *m, bool drop_not_started); + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); + +extern const DBusObjectPathVTable bus_manager_vtable; + +DBusHandlerResult bus_message_filter(DBusConnection *c, DBusMessage *message, void *userdata); + +int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, DBusMessageIter *more_properties, DBusError *error, char **job); +int manager_stop_unit(Manager *manager, const char *unit, DBusError *error, char **job); +int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, DBusError *error); +int manager_unit_is_active(Manager *manager, const char *unit); diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf new file mode 100644 index 0000000000..b2d6df3121 --- /dev/null +++ b/src/machine/org.freedesktop.machine1.conf @@ -0,0 +1,50 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<!-- + This file is part of systemd. + + 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. +--> + +<busconfig> + + <policy user="root"> + <allow own="org.freedesktop.machine1"/> + <allow send_destination="org.freedesktop.machine1"/> + <allow receive_sender="org.freedesktop.machine1"/> + </policy> + + <policy context="default"> + <deny send_destination="org.freedesktop.machine1"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Introspectable"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Peer"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Properties" + send_member="Get"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Properties" + send_member="GetAll"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="ListMachines"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="GetMachine"/> + + <allow receive_sender="org.freedesktop.machine1"/> + </policy> + +</busconfig> diff --git a/src/machine/org.freedesktop.machine1.service b/src/machine/org.freedesktop.machine1.service new file mode 100644 index 0000000000..d3dc99852b --- /dev/null +++ b/src/machine/org.freedesktop.machine1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.machine1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/test-machine-tables.c b/src/machine/test-machine-tables.c new file mode 100644 index 0000000000..4aae426050 --- /dev/null +++ b/src/machine/test-machine-tables.c @@ -0,0 +1,30 @@ +/*** + 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 "machine.h" + +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table(machine_class, MACHINE_CLASS); + test_table(machine_state, MACHINE_STATE); + test_table(kill_who, KILL_WHO); + + return EXIT_SUCCESS; +} diff --git a/src/notify/notify.c b/src/notify/notify.c index 1e9766f862..a688a9f879 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -157,7 +157,8 @@ int main(int argc, char* argv[]) { log_parse_environment(); log_open(); - if ((r = parse_argv(argc, argv)) <= 0) { + r = parse_argv(argc, argv); + if (r <= 0) { retval = r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; goto finish; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 09153c87ce..eb9605c356 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -41,12 +41,10 @@ #include <linux/fs.h> #include <sys/un.h> #include <sys/socket.h> - -#ifdef HAVE_XATTR -#include <attr/xattr.h> -#endif +#include <linux/netlink.h> #include <systemd/sd-daemon.h> +#include <systemd/sd-bus.h> #include "log.h" #include "util.h" @@ -63,6 +61,8 @@ #include "fdset.h" #include "build.h" #include "fileio.h" +#include "bus-internal.h" +#include "bus-message.h" #ifndef TTY_GID #define TTY_GID 5 @@ -77,9 +77,9 @@ typedef enum LinkJournal { static char *arg_directory = NULL; static char *arg_user = NULL; -static char **arg_controllers = NULL; -static char *arg_uuid = NULL; +static sd_id128_t arg_uuid = {}; static char *arg_machine = NULL; +static const char *arg_slice = NULL; static bool arg_private_network = false; static bool arg_read_only = false; static bool arg_boot = false; @@ -122,10 +122,9 @@ static int help(void) { " -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" " -M --machine=NAME Set the machine name for the container\n" + " -S --slice=SLICE Place the container in the specified slice\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" @@ -158,7 +157,6 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "directory", required_argument, NULL, 'D' }, { "user", required_argument, NULL, 'u' }, - { "controllers", required_argument, NULL, 'C' }, { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, { "boot", no_argument, NULL, 'b' }, { "uuid", required_argument, NULL, ARG_UUID }, @@ -168,15 +166,16 @@ static int parse_argv(int argc, char *argv[]) { { "bind", required_argument, NULL, ARG_BIND }, { "bind-ro", required_argument, NULL, ARG_BIND_RO }, { "machine", required_argument, NULL, 'M' }, + { "slice", required_argument, NULL, 'S' }, { NULL, 0, NULL, 0 } }; - int c; + int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hD:u:C:bM:j", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "+hD:u:bM:jS:", options, NULL)) >= 0) { switch (c) { @@ -207,15 +206,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case 'C': - strv_free(arg_controllers); - arg_controllers = strv_split(optarg, ","); - if (!arg_controllers) - return log_oom(); - - cg_shorten_controllers(arg_controllers); - break; - case ARG_PRIVATE_NETWORK: arg_private_network = true; break; @@ -225,12 +215,15 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_UUID: - if (!id128_is_valid(optarg)) { + r = sd_id128_from_string(optarg, &arg_uuid); + if (r < 0) { log_error("Invalid UUID: %s", optarg); - return -EINVAL; + return r; } + break; - arg_uuid = optarg; + case 'S': + arg_slice = strdup(optarg); break; case 'M': @@ -300,7 +293,6 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_free_ char *a = NULL, *b = NULL; char *e; char ***x; - int r; x = c == ARG_BIND ? &arg_bind : &arg_bind_ro; @@ -419,12 +411,39 @@ static int mount_binds(const char *dest, char **l, unsigned long flags) { STRV_FOREACH_PAIR(x, y, l) { _cleanup_free_ char *where = NULL; + struct stat source_st, dest_st; + + if (stat(*x, &source_st) < 0) { + log_error("failed to stat %s: %m", *x); + return -errno; + } where = strjoin(dest, "/", *y, NULL); if (!where) return log_oom(); - mkdir_p_label(where, 0755); + if (stat(where, &dest_st) == 0) { + if ((source_st.st_mode & S_IFMT) != (dest_st.st_mode & S_IFMT)) { + log_error("The file types of %s and %s do not match. Refusing bind mount", + *x, where); + return -EINVAL; + } + } else { + /* Create the mount point, but be conservative -- refuse to create block + * and char devices. */ + if (S_ISDIR(source_st.st_mode)) + mkdir_p_label(where, 0755); + else if (S_ISFIFO(source_st.st_mode)) + mkfifo(where, 0644); + else if (S_ISSOCK(source_st.st_mode)) + mknod(where, 0644 | S_IFSOCK, 0); + else if (S_ISREG(source_st.st_mode)) + touch(where); + else { + log_error("Refusing to create mountpoint for file: %s", *x); + return -ENOTSUP; + } + } if (mount(*x, where, "bind", MS_BIND, NULL) < 0) { log_error("mount(%s) failed: %m", where); @@ -911,68 +930,6 @@ static int setup_journal(const char *directory) { return 0; } -static int setup_cgroup(const char *path) { - char **c; - int r; - - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, path, 1); - if (r < 0) { - log_error("Failed to create cgroup: %s", strerror(-r)); - return r; - } - - STRV_FOREACH(c, arg_controllers) { - r = cg_create_and_attach(*c, path, 1); - if (r < 0) - log_warning("Failed to create cgroup in controller %s: %s", *c, strerror(-r)); - } - - return 0; -} - -static int save_attributes(const char *cgroup, pid_t pid, const char *uuid, const char *directory) { -#ifdef HAVE_XATTR - _cleanup_free_ char *path = NULL; - char buf[DECIMAL_STR_MAX(pid_t)]; - int r = 0, k; - - assert(cgroup); - assert(pid >= 0); - assert(arg_directory); - - assert_se(snprintf(buf, sizeof(buf), "%lu", (unsigned long) pid) < (int) sizeof(buf)); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, cgroup, NULL, &path); - if (r < 0) { - log_error("Failed to get path: %s", strerror(-r)); - return r; - } - - r = setxattr(path, "trusted.init_pid", buf, strlen(buf), XATTR_CREATE); - if (r < 0) - log_warning("Failed to set %s attribute on %s: %m", "trusted.init_pid", path); - - if (uuid) { - k = setxattr(path, "trusted.machine_id", uuid, strlen(uuid), XATTR_CREATE); - if (k < 0) { - log_warning("Failed to set %s attribute on %s: %m", "trusted.machine_id", path); - if (r == 0) - r = k; - } - } - - k = setxattr(path, "trusted.root_directory", directory, strlen(directory), XATTR_CREATE); - if (k < 0) { - log_warning("Failed to set %s attribute on %s: %m", "trusted.root_directory", path); - if (r == 0) - r = k; - } - return r; -#else - return 0; -#endif -} - static int drop_capabilities(void) { return capability_bounding_set_drop(~arg_retain, false); } @@ -1219,10 +1176,55 @@ finish: return r; } +static int register_machine(void) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_unref_ sd_bus *bus = NULL; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error("Failed to open system bus: %s", strerror(-r)); + return r; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CreateMachine", + &error, + NULL, + "sayssusa(sv)", + arg_machine, + SD_BUS_APPEND_ID128(arg_uuid), + "nspawn", + "container", + (uint32_t) 0, + strempty(arg_directory), + 1, "Slice", "s", strempty(arg_slice)); + if (r < 0) { + log_error("Failed to register machine: %s", error.message ? error.message : strerror(-r)); + return r; + } + + return 0; +} + +static bool audit_enabled(void) { + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_AUDIT); + if (fd >= 0) { + close_nointr_nofail(fd); + return true; + } + return false; +} + int main(int argc, char *argv[]) { pid_t pid = 0; int r = EXIT_FAILURE, k; - _cleanup_free_ char *newcg = NULL; _cleanup_close_ int master = -1; int n_fd_passed; const char *console = NULL; @@ -1284,6 +1286,13 @@ int main(int argc, char *argv[]) { goto finish; } + if (arg_boot && audit_enabled()) { + log_warning("The kernel auditing subsystem is known to be incompatible with containers.\n" + "Please make sure to turn off auditing with 'audit=0' on the kernel command\n" + "line before using systemd-nspawn. Sleeping for 5s...\n"); + sleep(5); + } + if (path_equal(arg_directory, "/")) { log_error("Spawning container on root directory not supported."); goto finish; @@ -1306,22 +1315,6 @@ int main(int argc, char *argv[]) { fdset_close_others(fds); log_open(); - k = cg_get_machine_path(arg_machine, &newcg); - if (k < 0) { - log_error("Failed to determine machine cgroup path: %s", strerror(-k)); - goto finish; - } - - k = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, newcg, true); - if (k <= 0 && k != -ENOENT) { - log_error("Container already running."); - - free(newcg); - newcg = NULL; - - goto finish; - } - master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); if (master < 0) { log_error("Failed to acquire pseudo tty: %m"); @@ -1465,11 +1458,12 @@ int main(int argc, char *argv[]) { goto child_fail; } - if (setup_cgroup(newcg) < 0) - goto child_fail; - close_pipe(pipefd2); + r = register_machine(); + if (r < 0) + goto finish; + /* Mark everything as slave, so that we still * receive mounts from the real root, but don't * propagate mounts to the real root. */ @@ -1620,8 +1614,8 @@ int main(int argc, char *argv[]) { goto child_fail; } - if (arg_uuid) { - if (asprintf((char**)(envp + n_env++), "container_uuid=%s", arg_uuid) < 0) { + if (!sd_id128_equal(arg_uuid, SD_ID128_NULL)) { + if (asprintf((char**)(envp + n_env++), "container_uuid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid)) < 0) { log_oom(); goto child_fail; } @@ -1635,7 +1629,7 @@ int main(int argc, char *argv[]) { } if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", n_fd_passed) < 0) || - (asprintf((char **)(envp + n_env++), "LISTEN_PID=%lu", (unsigned long) 1) < 0)) { + (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) { log_oom(); goto child_fail; } @@ -1683,8 +1677,6 @@ int main(int argc, char *argv[]) { fd_wait_for_event(pipefd2[0], POLLHUP, -1); close_nointr_nofail(pipefd2[0]); - save_attributes(newcg, pid, arg_uuid, arg_directory); - fdset_free(fds); fds = NULL; @@ -1737,12 +1729,11 @@ finish: close_pipe(kmsg_socket_pair); - if (newcg) - cg_kill_recursive_and_wait(SYSTEMD_CGROUP_CONTROLLER, newcg, true); + if (pid > 0) + kill(pid, SIGKILL); free(arg_directory); free(arg_machine); - strv_free(arg_controllers); fdset_free(fds); diff --git a/src/python-systemd/_daemon.c b/src/python-systemd/_daemon.c index d3b4807368..6b84fb81c7 100644 --- a/src/python-systemd/_daemon.c +++ b/src/python-systemd/_daemon.c @@ -40,43 +40,6 @@ PyDoc_STRVAR(module__doc__, "running under systemd." ); -static PyObject* set_error(int r, const char* invalid_message) { - assert (r < 0); - - if (r == -EINVAL && invalid_message) - PyErr_SetString(PyExc_ValueError, invalid_message); - else if (r == -ENOMEM) - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - else { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - } - - return NULL; -} - - -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 -static int Unicode_FSConverter(PyObject* obj, void *_result) { - PyObject **result = _result; - - assert(result); - - if (!obj) - /* cleanup: we don't return Py_CLEANUP_SUPPORTED, so - * we can assume that it was PyUnicode_FSConverter. */ - return PyUnicode_FSConverter(obj, result); - - if (obj == Py_None) { - *result = NULL; - return 1; - } - - return PyUnicode_FSConverter(obj, result); -} -#endif - - PyDoc_STRVAR(booted__doc__, "booted() -> bool\n\n" "Return True iff this system is running under systemd.\n" @@ -88,8 +51,45 @@ static PyObject* booted(PyObject *self, PyObject *args) { assert(args == NULL); r = sd_booted(); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, NULL, NULL)) + return NULL; + + return PyBool_FromLong(r); +} + +PyDoc_STRVAR(notify__doc__, + "notify(status, unset_environment=False) -> bool\n\n" + "Send a message to the init system about a status change.\n" + "Wraps sd_notify(3)."); + +static PyObject* notify(PyObject *self, PyObject *args, PyObject *keywds) { + int r; + const char* msg; + int unset = false; + + static const char* const kwlist[] = { + "status", + "unset_environment", + NULL, + }; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|p:notify", + (char**) kwlist, &msg, &unset)) + return NULL; +#else + PyObject *obj = NULL; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|O:notify", + (char**) kwlist, &msg, &obj)) + return NULL; + if (obj != NULL) + unset = PyObject_IsTrue(obj); + if (unset < 0) + return NULL; +#endif + + r = sd_notify(unset, msg); + if (set_error(r, NULL, NULL)) + return NULL; return PyBool_FromLong(r); } @@ -102,16 +102,19 @@ PyDoc_STRVAR(listen_fds__doc__, "Wraps sd_listen_fds(3)." ); -static PyObject* listen_fds(PyObject *self, PyObject *args) { +static PyObject* listen_fds(PyObject *self, PyObject *args, PyObject *keywds) { int r; int unset = true; + static const char* const kwlist[] = {"unset_environment", NULL}; #if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 - if (!PyArg_ParseTuple(args, "|p:_listen_fds", &unset)) + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|p:_listen_fds", + (char**) kwlist, &unset)) return NULL; #else PyObject *obj = NULL; - if (!PyArg_ParseTuple(args, "|O:_listen_fds", &obj)) + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|O:_listen_fds", + (char**) kwlist, &unset, &obj)) return NULL; if (obj != NULL) unset = PyObject_IsTrue(obj); @@ -120,8 +123,8 @@ static PyObject* listen_fds(PyObject *self, PyObject *args) { #endif r = sd_listen_fds(unset); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, NULL, NULL)) + return NULL; return long_FromLong(r); } @@ -148,8 +151,8 @@ static PyObject* is_fifo(PyObject *self, PyObject *args) { #endif r = sd_is_fifo(fd, path); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, path, NULL)) + return NULL; return PyBool_FromLong(r); } @@ -176,8 +179,8 @@ static PyObject* is_mq(PyObject *self, PyObject *args) { #endif r = sd_is_mq(fd, path); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, path, NULL)) + return NULL; return PyBool_FromLong(r); } @@ -200,8 +203,8 @@ static PyObject* is_socket(PyObject *self, PyObject *args) { return NULL; r = sd_is_socket(fd, family, type, listening); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, NULL, NULL)) + return NULL; return PyBool_FromLong(r); } @@ -221,12 +224,14 @@ static PyObject* is_socket_inet(PyObject *self, PyObject *args) { &fd, &family, &type, &listening, &port)) return NULL; - if (port < 0 || port > INT16_MAX) - return set_error(-EINVAL, "port must fit into uint16_t"); + if (port < 0 || port > INT16_MAX) { + set_error(-EINVAL, NULL, "port must fit into uint16_t"); + return NULL; + } r = sd_is_socket_inet(fd, family, type, listening, (uint16_t) port); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, NULL, NULL)) + return NULL; return PyBool_FromLong(r); } @@ -260,8 +265,8 @@ static PyObject* is_socket_unix(PyObject *self, PyObject *args) { #endif r = sd_is_socket_unix(fd, type, listening, path, length); - if (r < 0) - return set_error(r, NULL); + if (set_error(r, path, NULL)) + return NULL; return PyBool_FromLong(r); } @@ -269,7 +274,8 @@ static PyObject* is_socket_unix(PyObject *self, PyObject *args) { static PyMethodDef methods[] = { { "booted", booted, METH_NOARGS, booted__doc__}, - { "_listen_fds", listen_fds, METH_VARARGS, listen_fds__doc__}, + { "notify", (PyCFunction) notify, METH_VARARGS | METH_KEYWORDS, notify__doc__}, + { "_listen_fds", (PyCFunction) listen_fds, METH_VARARGS | METH_KEYWORDS, listen_fds__doc__}, { "_is_fifo", is_fifo, METH_VARARGS, is_fifo__doc__}, { "_is_mq", is_mq, METH_VARARGS, is_mq__doc__}, { "_is_socket", is_socket, METH_VARARGS, is_socket__doc__}, diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index d20c58d2a8..bc5db19049 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -30,6 +30,7 @@ #include "pyutil.h" #include "macro.h" #include "util.h" +#include "strv.h" #include "build.h" typedef struct { @@ -38,20 +39,6 @@ typedef struct { } Reader; static PyTypeObject ReaderType; -static int set_error(int r, const char* path, const char* invalid_message) { - if (r >= 0) - return r; - if (r == -EINVAL && invalid_message) - PyErr_SetString(PyExc_ValueError, invalid_message); - else if (r == -ENOMEM) - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - else { - errno = -r; - PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); - } - return -1; -} - PyDoc_STRVAR(module__doc__, "Class to reads the systemd journal similar to journalctl."); @@ -77,6 +64,70 @@ static PyStructSequence_Desc Monotonic_desc = { }; #endif +/** + * Convert a Python sequence object into a strv (char**), and + * None into a NULL pointer. + */ +static int strv_converter(PyObject* obj, void *_result) { + char ***result = _result; + Py_ssize_t i, len; + + assert(result); + + if (!obj) + return 0; + + if (obj == Py_None) { + *result = NULL; + return 1; + } + + if (!PySequence_Check(obj)) + return 0; + + len = PySequence_Length(obj); + *result = new0(char*, len + 1); + if (!*result) { + set_error(-ENOMEM, NULL, NULL); + return 0; + } + + for (i = 0; i < len; i++) { + PyObject *item; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + int r; + PyObject *bytes; +#endif + char *s, *s2; + + item = PySequence_ITEM(obj, i); +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + r = PyUnicode_FSConverter(item, &bytes); + if (r == 0) + goto cleanup; + + s = PyBytes_AsString(bytes); +#else + s = PyString_AsString(item); +#endif + if (!s) + goto cleanup; + + s2 = strdup(s); + if (!s2) + log_oom(); + + (*result)[i] = s2; + } + + return 1; + +cleanup: + strv_free(*result); + *result = NULL; + + return 0; +} static void Reader_dealloc(Reader* self) { @@ -85,40 +136,45 @@ static void Reader_dealloc(Reader* self) } PyDoc_STRVAR(Reader__doc__, - "_Reader([flags | path]) -> ...\n\n" + "_Reader([flags | path | files]) -> ...\n\n" "_Reader allows filtering and retrieval of Journal entries.\n" "Note: this is a low-level interface, and probably not what you\n" "want, use systemd.journal.Reader instead.\n\n" "Argument `flags` sets open flags of the journal, which can be one\n" "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n" "journal on local machine only; RUNTIME_ONLY opens only\n" - "volatile journal files; and SYSTEM_ONLY opens only\n" - "journal files of system services and the kernel.\n\n" - "Argument `path` is the directory of journal files. Note that\n" - "`flags` and `path` are exclusive.\n\n" + "volatile journal files; and SYSTEM opens journal files of\n" + "system services and the kernel, and CURRENT_USER opens files\n" + "of the current user.\n\n" + "Argument `path` is the directory of journal files.\n" + "Argument `files` is a list of files. Note that\n" + "`flags`, `path`, and `files` are exclusive.\n\n" "_Reader implements the context manager protocol: the journal\n" "will be closed when exiting the block."); static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) { int flags = 0, r; char *path = NULL; + char **files = NULL; + + static const char* const kwlist[] = {"flags", "path", "files", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izO&:__init__", (char**) kwlist, + &flags, &path, strv_converter, &files)) + return -1; - static const char* const kwlist[] = {"flags", "path", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist, - &flags, &path)) + if (!!flags + !!path + !!files > 1) { + PyErr_SetString(PyExc_ValueError, "cannot use more than one of flags, path, and files"); return -1; + } if (!flags) flags = SD_JOURNAL_LOCAL_ONLY; - else - if (path) { - PyErr_SetString(PyExc_ValueError, "cannot use both flags and path"); - return -1; - } Py_BEGIN_ALLOW_THREADS if (path) r = sd_journal_open_directory(&self->j, path, 0); + else if (files) + r = sd_journal_open_files(&self->j, (const char**) files, 0); else r = sd_journal_open(&self->j, flags); Py_END_ALLOW_THREADS @@ -177,7 +233,7 @@ PyDoc_STRVAR(Reader_get_timeout__doc__, "Returns a timeout value for usage in poll(), the time since the\n" "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" "is necessary.\n\n" - "The return value must be converted to a relative timeout in \n" + "The return value must be converted to a relative timeout in\n" "milliseconds if it is to be used as an argument for poll().\n" "See man:sd_journal_get_timeout(3) for further discussion."); static PyObject* Reader_get_timeout(Reader *self, PyObject *args) @@ -275,11 +331,7 @@ PyDoc_STRVAR(Reader___exit____doc__, "Closes the journal.\n"); static PyObject* Reader___exit__(Reader *self, PyObject *args) { - assert(self); - - sd_journal_close(self->j); - self->j = NULL; - Py_RETURN_NONE; + return Reader_close(self, args); } @@ -869,9 +921,9 @@ static PyObject* Reader_get_catalog(Reader *self, PyObject *args) r = sd_journal_get_data(self->j, "MESSAGE_ID", &mid, &mid_len); if (r == 0) { - const int l = sizeof("MESSAGE_ID"); + const size_t l = sizeof("MESSAGE_ID"); assert(mid_len > l); - PyErr_Format(PyExc_KeyError, "%.*s", (int) mid_len - l, + PyErr_Format(PyExc_KeyError, "%.*s", (int) (mid_len - l), (const char*) mid + l); } else if (r == -ENOENT) PyErr_SetString(PyExc_IndexError, "no MESSAGE_ID field"); @@ -1007,48 +1059,20 @@ static PyMethodDef Reader_methods[] = { static PyTypeObject ReaderType = { PyVarObject_HEAD_INIT(NULL, 0) - "_reader._Reader", /*tp_name*/ - sizeof(Reader), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)Reader_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - Reader__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Reader_methods, /* tp_methods */ - 0, /* tp_members */ - Reader_getsetters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) Reader_init, /* tp_init */ - 0, /* tp_alloc */ - PyType_GenericNew, /* tp_new */ + .tp_name = "_reader._Reader", + .tp_basicsize = sizeof(Reader), + .tp_dealloc = (destructor) Reader_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = Reader__doc__, + .tp_methods = Reader_methods, + .tp_getset = Reader_getsetters, + .tp_init = (initproc) Reader_init, + .tp_new = PyType_GenericNew, }; static PyMethodDef methods[] = { { "_get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__}, - { NULL, NULL, 0, NULL } /* Sentinel */ + {} /* Sentinel */ }; #if PY_MAJOR_VERSION >= 3 @@ -1058,7 +1082,6 @@ static PyModuleDef module = { module__doc__, -1, methods, - NULL, NULL, NULL, NULL }; #endif @@ -1115,7 +1138,9 @@ init_reader(void) PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) || PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) || PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) || + PyModule_AddIntConstant(m, "SYSTEM", SD_JOURNAL_SYSTEM) || PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY) || + PyModule_AddIntConstant(m, "CURRENT_USER", SD_JOURNAL_CURRENT_USER) || PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { #if PY_MAJOR_VERSION >= 3 Py_DECREF(m); diff --git a/src/python-systemd/daemon.py b/src/python-systemd/daemon.py index e2829d1671..1c386bb6fc 100644 --- a/src/python-systemd/daemon.py +++ b/src/python-systemd/daemon.py @@ -1,5 +1,6 @@ from ._daemon import (__version__, booted, + notify, _listen_fds, _is_fifo, _is_socket, diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst index 08756b99be..ea74cf85c4 100644 --- a/src/python-systemd/docs/journal.rst +++ b/src/python-systemd/docs/journal.rst @@ -42,7 +42,7 @@ event loop: >>> j = journal.Reader() >>> j.seek_tail() >>> p = select.poll() - >>> p.register(j, select.POLLIN) + >>> p.register(j, j.get_events()) >>> p.poll() [(3, 1)] >>> j.get_next() @@ -53,7 +53,8 @@ Journal access types .. autoattribute:: systemd.journal.LOCAL_ONLY .. autoattribute:: systemd.journal.RUNTIME_ONLY -.. autoattribute:: systemd.journal.SYSTEM_ONLY +.. autoattribute:: systemd.journal.SYSTEM +.. autoattribute:: systemd.journal.CURRENT_USER Journal event types ~~~~~~~~~~~~~~~~~~~ diff --git a/src/python-systemd/docs/login.rst b/src/python-systemd/docs/login.rst index 2cd9d8cbee..6b4de64c55 100644 --- a/src/python-systemd/docs/login.rst +++ b/src/python-systemd/docs/login.rst @@ -3,3 +3,26 @@ .. automodule:: systemd.login :members: + +.. autoclass:: Monitor + :undoc-members: + :inherited-members: + +Example: polling for events +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows that session/uid/seat/machine events can be waited +for (using e.g. `poll`). This makes it easy to integrate Monitor in an +external event loop: + + >>> import select + >>> from systemd import login + >>> m = login.Monitor("machine") + >>> p = select.poll() + >>> p.register(m, m.get_events()) + >>> login.machine_names() + [] + >>> p.poll() + [(3, 1)] + >>> login.machine_names() + ['fedora-19.nspawn'] diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 9ef1ede229..d0bcd24d15 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -33,7 +33,8 @@ from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) from ._journal import __version__, sendv, stream_fd from ._reader import (_Reader, NOP, APPEND, INVALIDATE, - LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY, + LOCAL_ONLY, RUNTIME_ONLY, + SYSTEM, SYSTEM_ONLY, CURRENT_USER, _get_catalog) from . import id128 as _id128 @@ -55,6 +56,9 @@ def _convert_realtime(t): def _convert_timestamp(s): return _datetime.datetime.fromtimestamp(int(s) / 1000000) +def _convert_trivial(x): + return x + if _sys.version_info >= (3,): def _convert_uuid(s): return _uuid.UUID(s.decode()) @@ -87,6 +91,7 @@ DEFAULT_CONVERTERS = { '__REALTIME_TIMESTAMP': _convert_realtime, '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic, '__MONOTONIC_TIMESTAMP': _convert_monotonic, + '__CURSOR': _convert_trivial, 'COREDUMP': bytes, 'COREDUMP_PID': int, 'COREDUMP_UID': int, @@ -119,7 +124,7 @@ class Reader(_Reader): See systemd.journal-fields(7) for more info on typical fields found in the journal. """ - def __init__(self, flags=0, path=None, converters=None): + def __init__(self, flags=0, path=None, files=None, converters=None): """Create an instance of Reader, which allows filtering and return of journal entries. @@ -145,7 +150,7 @@ class Reader(_Reader): Reader implements the context manager protocol: the journal will be closed when exiting the block. """ - super(Reader, self).__init__(flags, path) + super(Reader, self).__init__(flags, path, files) if _sys.version_info >= (3,3): self.converters = _ChainMap() if converters is not None: @@ -187,18 +192,18 @@ class Reader(_Reader): """ return self - if _sys.version_info >= (3,): - def __next__(self): - """Part of iterator protocol. - Returns self.get_next(). - """ - return self.get_next() - else: - def next(self): - """Part of iterator protocol. - Returns self.get_next(). - """ - return self.get_next() + def __next__(self): + """Part of iterator protocol. + Returns self.get_next() or raises StopIteration. + """ + ans = self.get_next() + if ans: + return ans + else: + raise StopIteration() + + if _sys.version_info < (3,): + next = __next__ def add_match(self, *args, **kwargs): """Add one or more matches to the filter journal log entries. diff --git a/src/python-systemd/login.c b/src/python-systemd/login.c index 1dbe5ac5bf..dd2edbca00 100644 --- a/src/python-systemd/login.c +++ b/src/python-systemd/login.c @@ -133,6 +133,200 @@ static PyMethodDef methods[] = { {} /* Sentinel */ }; + +typedef struct { + PyObject_HEAD + sd_login_monitor *monitor; +} Monitor; +static PyTypeObject MonitorType; + +static void Monitor_dealloc(Monitor* self) +{ + sd_login_monitor_unref(self->monitor); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +PyDoc_STRVAR(Monitor__doc__, + "Monitor([category]) -> ...\n\n" + "Monitor may be used to monitor login sessions, users, seats,\n" + "and virtual machines/containers. Monitor provides a file\n" + "descriptor which can be integrated in an external event loop.\n" + "See man:sd_login_monitor_new(3) for the details about what\n" + "can be monitored."); +static int Monitor_init(Monitor *self, PyObject *args, PyObject *keywds) +{ + const char *category = NULL; + int r; + + static const char* const kwlist[] = {"category", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|z:__init__", (char**) kwlist, + &category)) + return -1; + + Py_BEGIN_ALLOW_THREADS + r = sd_login_monitor_new(category, &self->monitor); + Py_END_ALLOW_THREADS + + return set_error(r, NULL, "Invalid category"); +} + + +PyDoc_STRVAR(Monitor_fileno__doc__, + "fileno() -> int\n\n" + "Get a file descriptor to poll for events.\n" + "This method wraps sd_login_monitor_get_fd(3)."); +static PyObject* Monitor_fileno(Monitor *self, PyObject *args) +{ + int fd = sd_login_monitor_get_fd(self->monitor); + set_error(fd, NULL, NULL); + if (fd < 0) + return NULL; + return long_FromLong(fd); +} + + +PyDoc_STRVAR(Monitor_get_events__doc__, + "get_events() -> int\n\n" + "Returns a mask of poll() events to wait for on the file\n" + "descriptor returned by .fileno().\n\n" + "See man:sd_login_monitor_get_events(3) for further discussion."); +static PyObject* Monitor_get_events(Monitor *self, PyObject *args) +{ + int r = sd_login_monitor_get_events(self->monitor); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + return long_FromLong(r); +} + + +PyDoc_STRVAR(Monitor_get_timeout__doc__, + "get_timeout() -> int or None\n\n" + "Returns a timeout value for usage in poll(), the time since the\n" + "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" + "is necessary.\n\n" + "The return value must be converted to a relative timeout in\n" + "milliseconds if it is to be used as an argument for poll().\n" + "See man:sd_login_monitor_get_timeout(3) for further discussion."); +static PyObject* Monitor_get_timeout(Monitor *self, PyObject *args) +{ + int r; + uint64_t t; + + r = sd_login_monitor_get_timeout(self->monitor, &t); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + if (t == (uint64_t) -1) + Py_RETURN_NONE; + + assert_cc(sizeof(unsigned long long) == sizeof(t)); + return PyLong_FromUnsignedLongLong(t); +} + + +PyDoc_STRVAR(Monitor_get_timeout_ms__doc__, + "get_timeout_ms() -> int\n\n" + "Returns a timeout value suitable for usage in poll(), the value\n" + "returned by .get_timeout() converted to relative ms, or -1 if\n" + "no timeout is necessary."); +static PyObject* Monitor_get_timeout_ms(Monitor *self, PyObject *args) +{ + int r; + uint64_t t; + + r = sd_login_monitor_get_timeout(self->monitor, &t); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + return absolute_timeout(t); +} + + +PyDoc_STRVAR(Monitor_close__doc__, + "close() -> None\n\n" + "Free resources allocated by this Monitor object.\n" + "This method invokes sd_login_monitor_unref().\n" + "See man:sd_login_monitor_unref(3)."); +static PyObject* Monitor_close(Monitor *self, PyObject *args) +{ + assert(self); + assert(!args); + + sd_login_monitor_unref(self->monitor); + self->monitor = NULL; + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Monitor_flush__doc__, + "flush() -> None\n\n" + "Reset the wakeup state of the monitor object.\n" + "This method invokes sd_login_monitor_flush().\n" + "See man:sd_login_monitor_flush(3)."); +static PyObject* Monitor_flush(Monitor *self, PyObject *args) +{ + assert(self); + assert(!args); + + Py_BEGIN_ALLOW_THREADS + sd_login_monitor_flush(self->monitor); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Monitor___enter____doc__, + "__enter__() -> self\n\n" + "Part of the context manager protocol.\n" + "Returns self.\n"); +static PyObject* Monitor___enter__(PyObject *self, PyObject *args) +{ + assert(self); + assert(!args); + + Py_INCREF(self); + return self; +} + + +PyDoc_STRVAR(Monitor___exit____doc__, + "__exit__(type, value, traceback) -> None\n\n" + "Part of the context manager protocol.\n" + "Closes the monitor..\n"); +static PyObject* Monitor___exit__(Monitor *self, PyObject *args) +{ + return Monitor_close(self, args); +} + + +static PyMethodDef Monitor_methods[] = { + {"fileno", (PyCFunction) Monitor_fileno, METH_NOARGS, Monitor_fileno__doc__}, + {"get_events", (PyCFunction) Monitor_get_events, METH_NOARGS, Monitor_get_events__doc__}, + {"get_timeout", (PyCFunction) Monitor_get_timeout, METH_NOARGS, Monitor_get_timeout__doc__}, + {"get_timeout_ms", (PyCFunction) Monitor_get_timeout_ms, METH_NOARGS, Monitor_get_timeout_ms__doc__}, + {"close", (PyCFunction) Monitor_close, METH_NOARGS, Monitor_close__doc__}, + {"flush", (PyCFunction) Monitor_flush, METH_NOARGS, Monitor_flush__doc__}, + {"__enter__", (PyCFunction) Monitor___enter__, METH_NOARGS, Monitor___enter____doc__}, + {"__exit__", (PyCFunction) Monitor___exit__, METH_VARARGS, Monitor___exit____doc__}, + {} /* Sentinel */ +}; + +static PyTypeObject MonitorType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "login.Monitor", + .tp_basicsize = sizeof(Monitor), + .tp_dealloc = (destructor) Monitor_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = Monitor__doc__, + .tp_methods = Monitor_methods, + .tp_init = (initproc) Monitor_init, + .tp_new = PyType_GenericNew, +}; + + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" @@ -141,10 +335,17 @@ static PyMethodDef methods[] = { PyMODINIT_FUNC initlogin(void) { PyObject *m; + if (PyType_Ready(&MonitorType) < 0) + return; + m = Py_InitModule3("login", methods, module__doc__); if (m == NULL) return; + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); + + Py_INCREF(&MonitorType); + PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType); } #else @@ -159,6 +360,9 @@ static struct PyModuleDef module = { PyMODINIT_FUNC PyInit_login(void) { PyObject *m; + if (PyType_Ready(&MonitorType) < 0) + return NULL; + m = PyModule_Create(&module); if (m == NULL) return NULL; @@ -168,6 +372,13 @@ PyMODINIT_FUNC PyInit_login(void) { return NULL; } + Py_INCREF(&MonitorType); + if (PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType)) { + Py_DECREF(&MonitorType); + Py_DECREF(m); + return NULL; + } + return m; } diff --git a/src/python-systemd/pyutil.c b/src/python-systemd/pyutil.c index 9510acdddb..722c4f5b5f 100644 --- a/src/python-systemd/pyutil.c +++ b/src/python-systemd/pyutil.c @@ -30,17 +30,51 @@ void cleanup_Py_DECREFp(PyObject **p) { } PyObject* absolute_timeout(uint64_t t) { - if (t == (uint64_t) -1) - return PyLong_FromLong(-1); - else { - struct timespec ts; - uint64_t n; - int msec; - - clock_gettime(CLOCK_MONOTONIC, &ts); - n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; - msec = t > n ? (int) ((t - n + 999) / 1000) : 0; - - return PyLong_FromLong(msec); - } + if (t == (uint64_t) -1) + return PyLong_FromLong(-1); + else { + struct timespec ts; + uint64_t n; + int msec; + + clock_gettime(CLOCK_MONOTONIC, &ts); + n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; + msec = t > n ? (int) ((t - n + 999) / 1000) : 0; + + return PyLong_FromLong(msec); + } +} + +int set_error(int r, const char* path, const char* invalid_message) { + if (r >= 0) + return r; + if (r == -EINVAL && invalid_message) + PyErr_SetString(PyExc_ValueError, invalid_message); + else if (r == -ENOMEM) + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + else { + errno = -r; + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + } + return -1; +} + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 +int Unicode_FSConverter(PyObject* obj, void *_result) { + PyObject **result = _result; + + assert(result); + + if (!obj) + /* cleanup: we don't return Py_CLEANUP_SUPPORTED, so + * we can assume that it was PyUnicode_FSConverter. */ + return PyUnicode_FSConverter(obj, result); + + if (obj == Py_None) { + *result = NULL; + return 1; + } + + return PyUnicode_FSConverter(obj, result); } +#endif diff --git a/src/python-systemd/pyutil.h b/src/python-systemd/pyutil.h index 5c7ea37cdb..1477e7bf9c 100644 --- a/src/python-systemd/pyutil.h +++ b/src/python-systemd/pyutil.h @@ -28,6 +28,11 @@ void cleanup_Py_DECREFp(PyObject **p); PyObject* absolute_timeout(uint64_t t); +int set_error(int r, const char* path, const char* invalid_message); + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 +int Unicode_FSConverter(PyObject* obj, void *_result); +#endif #define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) diff --git a/src/random-seed/random-seed.c b/src/random-seed/random-seed.c index fdcaa1e154..afbd5002dd 100644 --- a/src/random-seed/random-seed.c +++ b/src/random-seed/random-seed.c @@ -32,11 +32,11 @@ #define POOL_SIZE_MIN 512 int main(int argc, char *argv[]) { - int seed_fd = -1, random_fd = -1; - int ret = EXIT_FAILURE; - void* buf; + _cleanup_close_ int seed_fd = -1, random_fd = -1; + _cleanup_free_ void* buf = NULL; size_t buf_size = 0; - ssize_t r; + ssize_t k; + int r; FILE *f; if (argc != 2) { @@ -51,7 +51,8 @@ int main(int argc, char *argv[]) { umask(0022); /* Read pool size, if possible */ - if ((f = fopen("/proc/sys/kernel/random/poolsize", "re"))) { + f = fopen("/proc/sys/kernel/random/poolsize", "re"); + if (f) { if (fscanf(f, "%zu", &buf_size) > 0) { /* poolsize is in bits on 2.6, but we want bytes */ buf_size /= 8; @@ -63,13 +64,15 @@ int main(int argc, char *argv[]) { if (buf_size <= POOL_SIZE_MIN) buf_size = POOL_SIZE_MIN; - if (!(buf = malloc(buf_size))) { - log_error("Failed to allocate buffer."); + buf = malloc(buf_size); + if (!buf) { + r = log_oom(); goto finish; } - if (mkdir_parents_label(RANDOM_SEED, 0755) < 0) { - log_error("Failed to create directories parents of %s: %m", RANDOM_SEED); + r = mkdir_parents_label(RANDOM_SEED, 0755); + if (r < 0) { + log_error("Failed to create parent directory of " RANDOM_SEED ": %s", strerror(-r)); goto finish; } @@ -79,45 +82,64 @@ int main(int argc, char *argv[]) { if (streq(argv[1], "load")) { - if ((seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600)) < 0) { - if ((seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) { + seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); + if (seed_fd < 0) { + seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (seed_fd < 0) { log_error("Failed to open random seed: %m"); + r = -errno; goto finish; } } - if ((random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600)) < 0) { - if ((random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600)) < 0) { + random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); + if (random_fd < 0) { + random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600); + if (random_fd < 0) { log_error("Failed to open /dev/urandom: %m"); + r = -errno; goto finish; } } - if ((r = loop_read(seed_fd, buf, buf_size, false)) <= 0) { + k = loop_read(seed_fd, buf, buf_size, false); + if (k <= 0) { if (r != 0) log_error("Failed to read seed file: %m"); + + r = k == 0 ? -EIO : (int) k; + } else { lseek(seed_fd, 0, SEEK_SET); - if ((r = loop_write(random_fd, buf, (size_t) r, false)) <= 0) - log_error("Failed to write seed to /dev/urandom: %s", - r < 0 ? strerror(errno) : "short write"); + k = loop_write(random_fd, buf, (size_t) k, false); + if (k <= 0) { + log_error("Failed to write seed to /dev/urandom: %s", r < 0 ? strerror(-r) : "short write"); + + r = k == 0 ? -EIO : (int) k; + } } } else if (streq(argv[1], "save")) { - if ((seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600)) < 0) { + seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); + if (seed_fd < 0) { log_error("Failed to open random seed: %m"); + r = -errno; goto finish; } - if ((random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY)) < 0) { + random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (random_fd < 0) { log_error("Failed to open /dev/urandom: %m"); + r = -errno; goto finish; } + } else { log_error("Unknown verb %s.", argv[1]); + r = -EINVAL; goto finish; } @@ -127,23 +149,18 @@ int main(int argc, char *argv[]) { fchmod(seed_fd, 0600); fchown(seed_fd, 0, 0); - if ((r = loop_read(random_fd, buf, buf_size, false)) <= 0) - log_error("Failed to read new seed from /dev/urandom: %s", r < 0 ? strerror(errno) : "EOF"); - else { - if ((r = loop_write(seed_fd, buf, (size_t) r, false)) <= 0) - log_error("Failed to write new random seed file: %s", r < 0 ? strerror(errno) : "short write"); + k = loop_read(random_fd, buf, buf_size, false); + if (k <= 0) { + log_error("Failed to read new seed from /dev/urandom: %s", r < 0 ? strerror(-r) : "EOF"); + r = k == 0 ? -EIO : (int) k; + } else { + r = loop_write(seed_fd, buf, (size_t) k, false); + if (r <= 0) { + log_error("Failed to write new random seed file: %s", r < 0 ? strerror(-r) : "short write"); + r = r == 0 ? -EIO : r; + } } - ret = EXIT_SUCCESS; - finish: - if (random_fd >= 0) - close_nointr_nofail(random_fd); - - if (seed_fd >= 0) - close_nointr_nofail(seed_fd); - - free(buf); - - return ret; + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/readahead/readahead-collect.c b/src/readahead/readahead-collect.c index ccd8408c1b..32888add01 100644 --- a/src/readahead/readahead-collect.c +++ b/src/readahead/readahead-collect.c @@ -44,6 +44,10 @@ #include <sys/inotify.h> #include <math.h> +#ifdef HAVE_LINUX_BTRFS_H +#include <linux/btrfs.h> +#endif + #ifdef HAVE_FANOTIFY_INIT #include <sys/fanotify.h> #endif @@ -506,7 +510,7 @@ done: on_ssd = fs_on_ssd(root) > 0; log_debug("On SSD: %s", yes_no(on_ssd)); - on_btrfs = statfs(root, &sfs) >= 0 && F_TYPE_CMP(sfs.f_type, BTRFS_SUPER_MAGIC); + on_btrfs = statfs(root, &sfs) >= 0 && F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); log_debug("On btrfs: %s", yes_no(on_btrfs)); if (asprintf(&pack_fn_new, "%s/.readahead.new", root) < 0) { diff --git a/src/run/Makefile b/src/run/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/run/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/run/run.c b/src/run/run.c new file mode 100644 index 0000000000..18a4920f03 --- /dev/null +++ b/src/run/run.c @@ -0,0 +1,376 @@ +/*-*- 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 <stdio.h> +#include <getopt.h> + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "strv.h" +#include "build.h" +#include "unit-name.h" +#include "path-util.h" + +static bool arg_scope = false; +static bool arg_user = false; +static bool arg_remain_after_exit = false; +static const char *arg_unit = NULL; +static const char *arg_description = NULL; +static const char *arg_slice = NULL; +static bool arg_send_sighup = false; + +static int help(void) { + + printf("%s [OPTIONS...] COMMAND [ARGS...]\n\n" + "Run the specified command in a transient scope or service unit.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --user Run as user unit\n" + " --scope Run this as scope rather than service\n" + " --unit=UNIT Run under the specified unit name\n" + " --description=TEXT Description for unit\n" + " --slice=SLICE Run in the specified slice\n" + " -r --remain-after-exit Leave service around until explicitly stopped\n" + " --send-sighup Send SIGHUP when terminating\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_USER, + ARG_SCOPE, + ARG_UNIT, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SEND_SIGHUP, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "user", no_argument, NULL, ARG_USER }, + { "scope", no_argument, NULL, ARG_SCOPE }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "slice", required_argument, NULL, ARG_SLICE }, + { "remain-after-exit", no_argument, NULL, 'r' }, + { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, + { NULL, 0, NULL, 0 }, + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hr", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SCOPE: + arg_scope = true; + break; + + case ARG_UNIT: + arg_unit = optarg; + break; + + case ARG_DESCRIPTION: + arg_description = optarg; + break; + + case ARG_SLICE: + arg_slice = optarg; + break; + + case ARG_SEND_SIGHUP: + arg_send_sighup = true; + break; + + case 'r': + arg_remain_after_exit = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind >= argc) { + log_error("Command line to execute required."); + return -EINVAL; + } + + return 1; +} + +static int message_start_transient_unit_new(sd_bus *bus, const char *name, sd_bus_message **ret) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + log_info("Running as unit %s.", name); + + r = sd_bus_message_new_method_call( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit", &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "ss", name, "fail"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); + if (r < 0) + return r; + + if (!isempty(arg_slice)) { + _cleanup_free_ char *slice; + + slice = unit_name_mangle_with_suffix(arg_slice, ".slice"); + if (!slice) + return -ENOMEM; + + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup); + if (r < 0) + return r; + + *ret = m; + m = NULL; + + return 0; +} + +static int message_start_transient_unit_send(sd_bus *bus, sd_bus_message *m, sd_bus_error *error, sd_bus_message **reply) { + int r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, 0, error, reply); +} + +static int start_transient_service( + sd_bus *bus, + char **argv, + sd_bus_error *error) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_free_ char *name = NULL; + char **i; + int r; + + if (arg_unit) + name = unit_name_mangle_with_suffix(arg_unit, ".service"); + else + asprintf(&name, "run-%lu.service", (unsigned long) getpid()); + if (!name) + return -ENOMEM; + + r = message_start_transient_unit_new(bus, name, &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", argv[0]); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + STRV_FOREACH(i, argv) { + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + return message_start_transient_unit_send(bus, m, error, &reply); +} + +static int start_transient_scope( + sd_bus *bus, + char **argv, + sd_bus_error *error) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_free_ char *name = NULL; + int r; + + if (arg_unit) + name = unit_name_mangle_with_suffix(arg_unit, ".scope"); + else + asprintf(&name, "run-%lu.scope", (unsigned long) getpid()); + if (!name) + return -ENOMEM; + + r = message_start_transient_unit_new(bus, name, &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid()); + if (r < 0) + return r; + + r = message_start_transient_unit_send(bus, m, error, &reply); + if (r < 0) + return r; + + execvp(argv[0], argv); + log_error("Failed to execute: %m"); + return -errno; +} + +int main(int argc, char* argv[]) { + sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_unref_ sd_bus *bus = NULL; + _cleanup_free_ char *description = NULL, *command = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto fail; + + r = find_binary(argv[optind], &command); + if (r < 0) { + log_error("Failed to find executable %s: %s", argv[optind], strerror(-r)); + goto fail; + } + argv[optind] = command; + + if (!arg_description) { + description = strv_join(argv + optind, " "); + if (!description) { + r = log_oom(); + goto fail; + } + + arg_description = description; + } + + if (arg_user) + r = sd_bus_open_user(&bus); + else + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error("Failed to create new bus connection: %s", strerror(-r)); + goto fail; + } + + if (arg_scope) + r = start_transient_scope(bus, argv + optind, &error); + else + r = start_transient_service(bus, argv + optind, &error); + if (r < 0) { + log_error("Failed start transient unit: %s", error.message ? error.message : strerror(-r)); + sd_bus_error_free(&error); + goto fail; + } + +fail: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index 48bb12f46b..fb04e49dc4 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -69,6 +69,34 @@ int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { return 0; } +int calc_acl_mask_if_needed(acl_t *acl_p) { + acl_entry_t i; + int found; + + assert(acl_p); + + for (found = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); + found > 0; + found = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag == ACL_MASK) + return 0; + } + + if (found < 0) + return -errno; + + if (acl_calc_mask(acl_p) < 0) + return -errno; + + return 0; +} + int search_acl_groups(char*** dst, const char* path, bool* belong) { acl_t acl; diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h index 23090d9984..36ef490d7e 100644 --- a/src/shared/acl-util.h +++ b/src/shared/acl-util.h @@ -24,4 +24,5 @@ #include <stdbool.h> int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); +int calc_acl_mask_if_needed(acl_t *acl_p); int search_acl_groups(char*** dst, const char* path, bool* belong); diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c new file mode 100644 index 0000000000..b094f34a5f --- /dev/null +++ b/src/shared/acpi-fpdt.c @@ -0,0 +1,155 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers + + 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 <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> + +#include <util.h> +#include <fileio.h> +#include <time-util.h> +#include <acpi-fpdt.h> + +struct acpi_table_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + char asl_compiler_id[4]; + uint32_t asl_compiler_revision; +}; + +enum { + ACPI_FPDT_TYPE_BOOT = 0, + ACPI_FPDT_TYPE_S3PERF = 1, +}; + +struct acpi_fpdt_header { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t ptr; +}; + +struct acpi_fpdt_boot_header { + char signature[4]; + uint32_t length; +}; + +enum { + ACPI_FPDT_S3PERF_RESUME_REC = 0, + ACPI_FPDT_S3PERF_SUSPEND_REC = 1, + ACPI_FPDT_BOOT_REC = 2, +}; + +struct acpi_fpdt_boot { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t reset_end; + uint64_t load_start; + uint64_t startup_start; + uint64_t exit_services_entry; + uint64_t exit_services_exit; +}; + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { + char *buf; + struct acpi_table_header *tbl; + size_t l; + struct acpi_fpdt_header *rec; + int r; + uint64_t ptr = 0; + _cleanup_close_ int fd = -1; + struct acpi_fpdt_boot_header hbrec; + struct acpi_fpdt_boot brec; + + r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); + if (r < 0) + return r; + + if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) + return -EINVAL; + + tbl = (struct acpi_table_header *)buf; + if (l != tbl->length) + return -EINVAL; + + if (memcmp(tbl->signature, "FPDT", 4) != 0) + return -EINVAL; + + /* find Firmware Basic Boot Performance Pointer Record */ + for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); + (char *)rec < buf + l; + rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { + if (rec->type != ACPI_FPDT_TYPE_BOOT) + continue; + if (rec->length != sizeof(struct acpi_fpdt_header)) + continue; + + ptr = rec->ptr; + break; + } + + if (ptr == 0) + return -EINVAL; + + /* read Firmware Basic Boot Performance Data Record */ + fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); + if (fd < 0) + return -errno; + + l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); + if (l != sizeof(struct acpi_fpdt_boot_header)) + return -EINVAL; + + if (memcmp(hbrec.signature, "FBPT", 4) != 0) + return -EINVAL; + + if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); + if (l != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.length != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.type != ACPI_FPDT_BOOT_REC) + return -EINVAL; + + if (loader_start) + *loader_start = brec.startup_start / 1000; + if (loader_exit) + *loader_exit = brec.exit_services_exit / 1000; + + return 0; +} diff --git a/src/shared/acpi-fpdt.h b/src/shared/acpi-fpdt.h new file mode 100644 index 0000000000..fc4fe6f10f --- /dev/null +++ b/src/shared/acpi-fpdt.h @@ -0,0 +1,26 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers + + 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 <time-util.h> + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit); diff --git a/src/shared/boot-timestamps.c b/src/shared/boot-timestamps.c new file mode 100644 index 0000000000..944996582e --- /dev/null +++ b/src/shared/boot-timestamps.c @@ -0,0 +1,65 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Kay Sievers + + 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 "boot-timestamps.h" +#include "acpi-fpdt.h" +#include "efivars.h" + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { + usec_t x, y, a; + int r; + dual_timestamp _n; + + assert(firmware); + assert(loader); + + if (!n) { + dual_timestamp_get(&_n); + n = &_n; + } + + r = acpi_get_boot_usec(&x, &y); + if (r < 0) { + r = efi_loader_get_boot_usec(&x, &y); + if (r < 0) + return r; + } + + /* Let's convert this to timestamps where the firmware + * began/loader began working. To make this more confusing: + * since usec_t is unsigned and the kernel's monotonic clock + * begins at kernel initialization we'll actually initialize + * the monotonic timestamps here as negative of the actual + * value. */ + + firmware->monotonic = y; + loader->monotonic = y - x; + + a = n->monotonic + firmware->monotonic; + firmware->realtime = n->realtime > a ? n->realtime - a : 0; + + a = n->monotonic + loader->monotonic; + loader->realtime = n->realtime > a ? n->realtime - a : 0; + + return 0; +} diff --git a/src/shared/boot-timestamps.h b/src/shared/boot-timestamps.h new file mode 100644 index 0000000000..a3d2405b56 --- /dev/null +++ b/src/shared/boot-timestamps.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Kay Sievers + + 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 <time-util.h> + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); diff --git a/src/shared/cgroup-label.c b/src/shared/cgroup-label.c deleted file mode 100644 index 5b5163c250..0000000000 --- a/src/shared/cgroup-label.c +++ /dev/null @@ -1,77 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2010 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 <errno.h> -#include <unistd.h> -#include <signal.h> -#include <string.h> -#include <stdlib.h> -#include <dirent.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <ftw.h> - -#include "cgroup-util.h" -#include "log.h" -#include "set.h" -#include "macro.h" -#include "util.h" -#include "mkdir.h" - -int cg_create(const char *controller, const char *path, const char *suffix) { - _cleanup_free_ char *fs = NULL; - int r; - - r = cg_get_path_and_check(controller, path, suffix, &fs); - if (r < 0) - return r; - - r = mkdir_parents_label(fs, 0755); - if (r < 0) - return r; - - if (mkdir(fs, 0755) < 0) { - - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { - int r, q; - - assert(pid >= 0); - - r = cg_create(controller, path, NULL); - if (r < 0) - return r; - - q = cg_attach(controller, path, pid); - if (q < 0) - return q; - - /* This does not remove the cgroup on failure */ - return r; -} diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index 83cc0731b8..e971f36190 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -241,7 +241,6 @@ static int show_extra_pids(const char *controller, const char *path, const char unsigned i, j; int r; - assert(controller); assert(path); if (n_pids <= 0) diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c index 43c415d760..8a4eddab7a 100644 --- a/src/shared/cgroup-util.c +++ b/src/shared/cgroup-util.c @@ -38,6 +38,8 @@ #include "strv.h" #include "unit-name.h" #include "fileio.h" +#include "special.h" +#include "mkdir.h" int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { _cleanup_free_ char *fs = NULL; @@ -58,25 +60,6 @@ int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) return 0; } -int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) { - _cleanup_free_ char *fs = NULL; - FILE *f; - int r; - - assert(_f); - - r = cg_get_path(controller, path, "tasks", &fs); - if (r < 0) - return r; - - f = fopen(fs, "re"); - if (!f) - return -errno; - - *_f = f; - return 0; -} - int cg_read_pid(FILE *f, pid_t *_pid) { unsigned long ul; @@ -150,7 +133,7 @@ int cg_read_subgroup(DIR *d, char **fn) { return 0; } -int cg_rmdir(const char *controller, const char *path, bool honour_sticky) { +int cg_rmdir(const char *controller, const char *path) { _cleanup_free_ char *p = NULL; int r; @@ -158,22 +141,6 @@ int cg_rmdir(const char *controller, const char *path, bool honour_sticky) { if (r < 0) return r; - if (honour_sticky) { - char *tasks; - - /* If the sticky bit is set don't remove the directory */ - - tasks = strappend(p, "/tasks"); - if (!tasks) - return -ENOMEM; - - r = file_is_priv_sticky(tasks); - free(tasks); - - if (r > 0) - return 0; - } - r = rmdir(p); if (r < 0 && errno != ENOENT) return -errno; @@ -304,7 +271,7 @@ int cg_kill_recursive(const char *controller, const char *path, int sig, bool si ret = r; if (rem) { - r = cg_rmdir(controller, path, true); + r = cg_rmdir(controller, path); if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) return r; } @@ -365,7 +332,7 @@ int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char pid_t pid = 0; done = true; - r = cg_enumerate_tasks(cfrom, pfrom, &f); + r = cg_enumerate_processes(cfrom, pfrom, &f); if (r < 0) { if (ret >= 0 && r != -ENOENT) return r; @@ -413,7 +380,14 @@ int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char return ret; } -int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem) { +int cg_migrate_recursive( + const char *cfrom, + const char *pfrom, + const char *cto, + const char *pto, + bool ignore_self, + bool rem) { + _cleanup_closedir_ DIR *d = NULL; int r, ret = 0; char *fn; @@ -454,7 +428,7 @@ int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, ret = r; if (rem) { - r = cg_rmdir(cfrom, pfrom, true); + r = cg_rmdir(cfrom, pfrom); if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) return r; } @@ -462,6 +436,37 @@ int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, return ret; } +int cg_migrate_recursive_fallback( + const char *cfrom, + const char *pfrom, + const char *cto, + const char *pto, + bool ignore_self, + bool rem) { + + int r; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem); + if (r < 0) { + char prefix[strlen(pto) + 1]; + + /* This didn't work? Then let's try all prefixes of the destination */ + + PATH_FOREACH_PREFIX(prefix, pto) { + r = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem); + if (r >= 0) + break; + } + } + + return 0; +} + static const char *normalize_controller(const char *controller) { assert(controller); @@ -477,19 +482,19 @@ static const char *normalize_controller(const char *controller) { static int join_path(const char *controller, const char *path, const char *suffix, char **fs) { char *t = NULL; - if (controller) { - if (path && suffix) + if (!isempty(controller)) { + if (!isempty(path) && !isempty(suffix)) t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL); - else if (path) + else if (!isempty(path)) t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL); - else if (suffix) + else if (!isempty(suffix)) t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL); else t = strappend("/sys/fs/cgroup/", controller); } else { - if (path && suffix) + if (!isempty(path) && !isempty(suffix)) t = strjoin(path, "/", suffix, NULL); - else if (path) + else if (!isempty(path)) t = strdup(path); else return -EINVAL; @@ -564,8 +569,9 @@ int cg_get_path_and_check(const char *controller, const char *path, const char * } static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { - char *p; - bool is_sticky; + assert(path); + assert(sb); + assert(ftwbuf); if (typeflag != FTW_DP) return 0; @@ -573,18 +579,6 @@ static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct if (ftwbuf->level < 1) return 0; - p = strappend(path, "/tasks"); - if (!p) { - errno = ENOMEM; - return 1; - } - - is_sticky = file_is_priv_sticky(p) > 0; - free(p); - - if (is_sticky) - return 0; - rmdir(path); return 0; } @@ -604,19 +598,8 @@ int cg_trim(const char *controller, const char *path, bool delete_root) { r = errno ? -errno : -EIO; if (delete_root) { - bool is_sticky; - char *p; - - p = strappend(fs, "/tasks"); - if (!p) - return -ENOMEM; - - is_sticky = file_is_priv_sticky(p) > 0; - free(p); - - if (!is_sticky) - if (rmdir(fs) < 0 && errno != ENOENT && r == 0) - return -errno; + if (rmdir(fs) < 0 && errno != ENOENT) + return -errno; } return r; @@ -636,6 +619,46 @@ int cg_delete(const char *controller, const char *path) { return r == -ENOENT ? 0 : r; } +int cg_create(const char *controller, const char *path) { + _cleanup_free_ char *fs = NULL; + int r; + + r = cg_get_path_and_check(controller, path, NULL, &fs); + if (r < 0) + return r; + + r = mkdir_parents(fs, 0755); + if (r < 0) + return r; + + if (mkdir(fs, 0755) < 0) { + + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + int r, q; + + assert(pid >= 0); + + r = cg_create(controller, path); + if (r < 0) + return r; + + q = cg_attach(controller, path, pid); + if (q < 0) + return q; + + /* This does not remove the cgroup on failure */ + return r; +} + int cg_attach(const char *controller, const char *path, pid_t pid) { _cleanup_free_ char *fs = NULL; char c[DECIMAL_STR_MAX(pid_t) + 2]; @@ -644,7 +667,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { assert(path); assert(pid >= 0); - r = cg_get_path_and_check(controller, path, "tasks", &fs); + r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs); if (r < 0) return r; @@ -656,6 +679,30 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { return write_string_file(fs, c); } +int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { + int r; + + assert(controller); + assert(path); + assert(pid >= 0); + + r = cg_attach(controller, path, pid); + if (r < 0) { + char prefix[strlen(path) + 1]; + + /* This didn't work? Then let's try all prefixes of + * the destination */ + + PATH_FOREACH_PREFIX(prefix, path) { + r = cg_attach(controller, prefix, pid); + if (r >= 0) + break; + } + } + + return 0; +} + int cg_set_group_access( const char *controller, const char *path, @@ -683,52 +730,30 @@ int cg_set_task_access( const char *path, mode_t mode, uid_t uid, - gid_t gid, - int sticky) { + gid_t gid) { _cleanup_free_ char *fs = NULL, *procs = NULL; int r; assert(path); - if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0) + if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1) return 0; if (mode != (mode_t) -1) mode &= 0666; - r = cg_get_path(controller, path, "tasks", &fs); + r = cg_get_path(controller, path, "cgroup.procs", &fs); if (r < 0) return r; - if (sticky >= 0 && mode != (mode_t) -1) - /* Both mode and sticky param are passed */ - mode |= (sticky ? S_ISVTX : 0); - else if ((sticky >= 0 && mode == (mode_t) -1) || - (mode != (mode_t) -1 && sticky < 0)) { - struct stat st; - - /* Only one param is passed, hence read the current - * mode from the file itself */ - - r = lstat(fs, &st); - if (r < 0) - return -errno; - - if (mode == (mode_t) -1) - /* No mode set, we just shall set the sticky bit */ - mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0); - else - /* Only mode set, leave sticky bit untouched */ - mode = (st.st_mode & ~0777) | mode; - } - r = chmod_and_chown(fs, mode, uid, gid); if (r < 0) return r; - /* Always keep values for "cgroup.procs" in sync with "tasks" */ - r = cg_get_path(controller, path, "cgroup.procs", &procs); + /* Compatibility, Always keep values for "tasks" in sync with + * "cgroup.procs" */ + r = cg_get_path(controller, path, "tasks", &procs); if (r < 0) return r; @@ -861,6 +886,32 @@ int cg_install_release_agent(const char *controller, const char *agent) { return 0; } +int cg_uninstall_release_agent(const char *controller) { + _cleanup_free_ char *fs = NULL; + int r; + + r = cg_get_path(controller, NULL, "notify_on_release", &fs); + if (r < 0) + return r; + + r = write_string_file(fs, "0"); + if (r < 0) + return r; + + free(fs); + fs = NULL; + + r = cg_get_path(controller, NULL, "release_agent", &fs); + if (r < 0) + return r; + + r = write_string_file(fs, ""); + if (r < 0) + return r; + + return 0; +} + int cg_is_empty(const char *controller, const char *path, bool ignore_self) { _cleanup_fclose_ FILE *f = NULL; pid_t pid = 0, self_pid; @@ -869,7 +920,7 @@ int cg_is_empty(const char *controller, const char *path, bool ignore_self) { assert(path); - r = cg_enumerate_tasks(controller, path, &f); + r = cg_enumerate_processes(controller, path, &f); if (r < 0) return r == -ENOENT ? 1 : r; @@ -993,19 +1044,28 @@ int cg_split_spec(const char *spec, char **controller, char **path) { return -EINVAL; } - u = strdup(e+1); - if (!u) { - free(t); - return -ENOMEM; - } - if (!path_is_safe(u) || - !path_is_absolute(u)) { - free(t); - free(u); - return -EINVAL; - } + if (streq(e+1, "")) { + u = strdup("/"); + if (!u) { + free(t); + return -ENOMEM; + } + } else { + u = strdup(e+1); + if (!u) { + free(t); + return -ENOMEM; + } + + if (!path_is_safe(u) || + !path_is_absolute(u)) { + free(t); + free(u); + return -EINVAL; + } - path_kill_slashes(u); + path_kill_slashes(u); + } if (controller) *controller = t; @@ -1075,96 +1135,20 @@ int cg_mangle_path(const char *path, char **result) { return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result); } -int cg_get_system_path(char **path) { - char *p; - int r; - - assert(path); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); - if (r < 0) { - p = strdup("/system"); - if (!p) - return -ENOMEM; - } - - if (endswith(p, "/system")) - *path = p; - else { - char *q; - - q = strappend(p, "/system"); - free(p); - if (!q) - return -ENOMEM; - - *path = q; - } - - return 0; -} - int cg_get_root_path(char **path) { - char *root, *e; + char *p, *e; int r; assert(path); - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &root); + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); if (r < 0) return r; - e = endswith(root, "/system"); - if (e == root) - e[1] = 0; - else if (e) + e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); + if (e) *e = 0; - *path = root; - return 0; -} - -int cg_get_user_path(char **path) { - _cleanup_free_ char *root = NULL; - char *p; - - assert(path); - - /* Figure out the place to put user cgroups below. We use the - * same as PID 1 has but with the "/system" suffix replaced by - * "/user" */ - - if (cg_get_root_path(&root) < 0 || streq(root, "/")) - p = strdup("/user"); - else - p = strappend(root, "/user"); - - if (!p) - return -ENOMEM; - - *path = p; - return 0; -} - -int cg_get_machine_path(const char *machine, char **path) { - _cleanup_free_ char *root = NULL, *escaped = NULL; - char *p; - - assert(path); - - if (machine) { - const char *name = strappenda(machine, ".nspawn"); - - escaped = cg_escape(name); - if (!escaped) - return -ENOMEM; - } - - p = strjoin(cg_get_root_path(&root) >= 0 && !streq(root, "/") ? root : "", - "/machine", machine ? "/" : "", machine ? escaped : "", NULL); - if (!p) - return -ENOMEM; - *path = p; return 0; } @@ -1247,7 +1231,7 @@ int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup) { } int cg_path_decode_unit(const char *cgroup, char **unit){ - char *p, *e, *c, *s, *k; + char *e, *c, *s; assert(cgroup); assert(unit); @@ -1256,33 +1240,31 @@ int cg_path_decode_unit(const char *cgroup, char **unit){ c = strndupa(cgroup, e - cgroup); c = cg_unescape(c); - /* Could this be a valid unit name? */ - if (!unit_name_is_valid(c, true)) + if (!unit_name_is_valid(c, false)) return -EINVAL; - if (!unit_name_is_template(c)) - s = strdup(c); - else { - if (*e != '/') - return -EINVAL; + s = strdup(c); + if (!s) + return -ENOMEM; - e += strspn(e, "/"); + *unit = s; + return 0; +} - p = strchrnul(e, '/'); - k = strndupa(e, p - e); - k = cg_unescape(k); +static const char *skip_slices(const char *p) { + /* Skips over all slice assignments */ - if (!unit_name_is_valid(k, false)) - return -EINVAL; + for (;;) { + size_t n; - s = strdup(k); - } + p += strspn(p, "/"); - if (!s) - return -ENOMEM; + n = strcspn(p, "/"); + if (n <= 6 || memcmp(p + n - 6, ".slice", 6) != 0) + return p; - *unit = s; - return 0; + p += n; + } } int cg_path_get_unit(const char *path, char **unit) { @@ -1291,9 +1273,7 @@ int cg_path_get_unit(const char *path, char **unit) { assert(path); assert(unit); - e = path_startswith(path, "/system/"); - if (!e) - return -ENOENT; + e = skip_slices(path); return cg_path_decode_unit(e, unit); } @@ -1311,15 +1291,21 @@ int cg_pid_get_unit(pid_t pid, char **unit) { return cg_path_get_unit(cgroup, unit); } -_pure_ static const char *skip_label(const char *e) { - assert(e); +static const char *skip_session(const char *p) { + size_t n; - e = strchr(e, '/'); - if (!e) + assert(p); + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (n <= 12 || memcmp(p, "session-", 8) != 0 || memcmp(p + n - 6, ".scope", 6) != 0) return NULL; - e += strspn(e, "/"); - return e; + p += n; + p += strspn(p, "/"); + + return p; } int cg_path_get_user_unit(const char *path, char **unit) { @@ -1332,24 +1318,16 @@ int cg_path_get_user_unit(const char *path, char **unit) { * cgroups might have arbitrary child cgroups and we shouldn't get * confused by those */ - e = path_startswith(path, "/user/"); - if (!e) - return -ENOENT; - - /* Skip the user name */ - e = skip_label(e); - if (!e) - return -ENOENT; + /* Skip slices, if there are any */ + e = skip_slices(path); - /* Skip the session ID */ - e = skip_label(e); + /* Skip the session scope, require that there is one */ + e = skip_session(e); if (!e) return -ENOENT; - /* Skip the systemd cgroup */ - e = skip_label(e); - if (!e) - return -ENOENT; + /* And skip more slices */ + e = skip_slices(e); return cg_path_decode_unit(e, unit); } @@ -1368,23 +1346,34 @@ int cg_pid_get_user_unit(pid_t pid, char **unit) { } int cg_path_get_machine_name(const char *path, char **machine) { - const char *e, *n; + const char *e, *n, *x; char *s, *r; + size_t l; assert(path); assert(machine); - e = path_startswith(path, "/machine/"); - if (!e) - return -ENOENT; + /* Skip slices, if there are any */ + e = skip_slices(path); n = strchrnul(e, '/'); if (e == n) return -ENOENT; s = strndupa(e, n - e); + s = cg_unescape(s); + + x = startswith(s, "machine-"); + if (!x) + return -ENOENT; + if (!endswith(x, ".scope")) + return -ENOENT; + + l = strlen(x); + if (l <= 6) + return -ENOENT; - r = strdup(cg_unescape(s)); + r = strndup(x, l - 6); if (!r) return -ENOMEM; @@ -1406,32 +1395,38 @@ int cg_pid_get_machine_name(pid_t pid, char **machine) { } int cg_path_get_session(const char *path, char **session) { - const char *e, *n; - char *s; + const char *e, *n, *x; + char *s, *r; + size_t l; assert(path); assert(session); - e = path_startswith(path, "/user/"); - if (!e) - return -ENOENT; + /* Skip slices, if there are any */ + e = skip_slices(path); - /* Skip the user name */ - e = skip_label(e); - if (!e) + n = strchrnul(e, '/'); + if (e == n) return -ENOENT; - n = strchrnul(e, '/'); - if (n - e < 8) + s = strndupa(e, n - e); + s = cg_unescape(s); + + x = startswith(s, "session-"); + if (!x) return -ENOENT; - if (memcmp(n - 8, ".session", 8) != 0) + if (!endswith(x, ".scope")) return -ENOENT; - s = strndup(e, n - e - 8); - if (!s) + l = strlen(x); + if (l <= 6) + return -ENOENT; + + r = strndup(x, l - 6); + if (!r) return -ENOMEM; - *session = s; + *session = r; return 0; } @@ -1449,23 +1444,25 @@ int cg_pid_get_session(pid_t pid, char **session) { } int cg_path_get_owner_uid(const char *path, uid_t *uid) { - const char *e, *n; + _cleanup_free_ char *slice = NULL; + const char *e; char *s; + int r; assert(path); assert(uid); - e = path_startswith(path, "/user/"); - if (!e) - return -ENOENT; + r = cg_path_get_slice(path, &slice); + if (r < 0) + return r; - n = strchrnul(e, '/'); - if (n - e < 5) + e = startswith(slice, "user-"); + if (!e) return -ENOENT; - if (memcmp(n - 5, ".user", 5) != 0) + if (!endswith(slice, ".slice")) return -ENOENT; - s = strndupa(e, n - e - 5); + s = strndupa(e, strlen(e) - 6); if (!s) return -ENOMEM; @@ -1485,6 +1482,53 @@ int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { return cg_path_get_owner_uid(cgroup, uid); } +int cg_path_get_slice(const char *p, char **slice) { + const char *e = NULL; + size_t m = 0; + + assert(p); + assert(slice); + + for (;;) { + size_t n; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (n <= 6 || memcmp(p + n - 6, ".slice", 6) != 0) { + char *s; + + if (!e) + return -ENOENT; + + s = strndup(e, m); + if (!s) + return -ENOMEM; + + *slice = s; + return 0; + } + + e = p; + m = n; + + p += n; + } +} + +int cg_pid_get_slice(pid_t pid, char **slice) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(slice); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_slice(cgroup, slice); +} + int cg_controller_from_attr(const char *attr, char **controller) { const char *dot; char *c; @@ -1572,9 +1616,7 @@ char *cg_unescape(const char *p) { } #define CONTROLLER_VALID \ - "0123456789" \ - "abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + DIGITS LETTERS \ "_" bool cg_controller_is_valid(const char *p, bool allow_named) { @@ -1601,3 +1643,188 @@ bool cg_controller_is_valid(const char *p, bool allow_named) { return true; } + +int cg_slice_to_path(const char *unit, char **ret) { + _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; + const char *dash; + + assert(unit); + assert(ret); + + if (!unit_name_is_valid(unit, false)) + return -EINVAL; + + if (!endswith(unit, ".slice")) + return -EINVAL; + + p = unit_name_to_prefix(unit); + if (!p) + return -ENOMEM; + + dash = strchr(p, '-'); + while (dash) { + _cleanup_free_ char *escaped = NULL; + char n[dash - p + sizeof(".slice")]; + + strcpy(stpncpy(n, p, dash - p), ".slice"); + + if (!unit_name_is_valid(n, false)) + return -EINVAL; + + escaped = cg_escape(n); + if (!escaped) + return -ENOMEM; + + if (!strextend(&s, escaped, "/", NULL)) + return -ENOMEM; + + dash = strchr(dash+1, '-'); + } + + e = cg_escape(unit); + if (!e) + return -ENOMEM; + + if (!strextend(&s, e, NULL)) + return -ENOMEM; + + *ret = s; + s = NULL; + + return 0; +} + +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, attribute, &p); + if (r < 0) + return r; + + return write_string_file(p, value); +} + +static const char mask_names[] = + "cpu\0" + "cpuacct\0" + "blkio\0" + "memory\0" + "devices\0"; + +int cg_create_everywhere(CGroupControllerMask supported, CGroupControllerMask mask, const char *path) { + CGroupControllerMask bit = 1; + const char *n; + int r; + + /* This one will create a cgroup in our private tree, but also + * duplicate it in the trees specified in mask, and remove it + * in all others */ + + /* First create the cgroup in our own hierarchy. */ + r = cg_create(SYSTEMD_CGROUP_CONTROLLER, path); + if (r < 0) + return r; + + /* Then, do the same in the other hierarchies */ + NULSTR_FOREACH(n, mask_names) { + if (mask & bit) + cg_create(n, path); + else if (supported & bit) + cg_trim(n, path, true); + + bit <<= 1; + } + + return 0; +} + +int cg_attach_everywhere(CGroupControllerMask supported, const char *path, pid_t pid) { + CGroupControllerMask bit = 1; + const char *n; + int r; + + r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid); + if (r < 0) + return r; + + NULSTR_FOREACH(n, mask_names) { + if (supported & bit) + cg_attach_fallback(n, path, pid); + + bit <<= 1; + } + + return 0; +} + +int cg_attach_many_everywhere(CGroupControllerMask supported, const char *path, Set* pids) { + Iterator i; + void *pidp; + int r = 0; + + SET_FOREACH(pidp, pids, i) { + pid_t pid = PTR_TO_LONG(pidp); + int q; + + q = cg_attach_everywhere(supported, path, pid); + if (q < 0) + r = q; + } + + return r; +} + +int cg_migrate_everywhere(CGroupControllerMask supported, const char *from, const char *to) { + CGroupControllerMask bit = 1; + const char *n; + int r; + + if (!path_equal(from, to)) { + r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true); + if (r < 0) + return r; + } + + NULSTR_FOREACH(n, mask_names) { + if (supported & bit) + cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, n, to, false, false); + + bit <<= 1; + } + + return 0; +} + +int cg_trim_everywhere(CGroupControllerMask supported, const char *path, bool delete_root) { + CGroupControllerMask bit = 1; + const char *n; + int r; + + r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root); + if (r < 0) + return r; + + NULSTR_FOREACH(n, mask_names) { + if (supported & bit) + cg_trim(n, path, delete_root); + + bit <<= 1; + } + + return 0; +} + +CGroupControllerMask cg_mask_supported(void) { + CGroupControllerMask bit = 1, mask = 0; + const char *n; + + NULSTR_FOREACH(n, mask_names) { + if (check_hierarchy(n) >= 0) + mask |= bit; + + bit <<= 1; + } + + return mask; +} diff --git a/src/shared/cgroup-util.h b/src/shared/cgroup-util.h index 25dd277ba5..0963450b08 100644 --- a/src/shared/cgroup-util.h +++ b/src/shared/cgroup-util.h @@ -28,6 +28,15 @@ #include "set.h" #include "def.h" +/* A bit mask of well known cgroup controllers */ +typedef enum CGroupControllerMask { + CGROUP_CPU = 1, + CGROUP_CPUACCT = 2, + CGROUP_BLKIO = 4, + CGROUP_MEMORY = 8, + CGROUP_DEVICE = 16 +} CGroupControllerMask; + /* * General rules: * @@ -44,7 +53,6 @@ */ int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); -int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f); int cg_read_pid(FILE *f, pid_t *_pid); int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); @@ -56,6 +64,7 @@ int cg_kill_recursive_and_wait(const char *controller, const char *path, bool re int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self); int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool remove); +int cg_migrate_recursive_fallback(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem); int cg_split_spec(const char *spec, char **controller, char **path); int cg_join_spec(const char *controller, const char *path, char **spec); @@ -68,32 +77,34 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path); int cg_trim(const char *controller, const char *path, bool delete_root); -int cg_rmdir(const char *controller, const char *path, bool honour_sticky); +int cg_rmdir(const char *controller, const char *path); int cg_delete(const char *controller, const char *path); -int cg_create(const char *controller, const char *path, const char *suffix); +int cg_create(const char *controller, const char *path); int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_attach_fallback(const char *controller, const char *path, pid_t pid); int cg_create_and_attach(const char *controller, const char *path, pid_t pid); +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); + int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); -int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); int cg_install_release_agent(const char *controller, const char *agent); +int cg_uninstall_release_agent(const char *controller); int cg_is_empty(const char *controller, const char *path, bool ignore_self); int cg_is_empty_by_spec(const char *spec, bool ignore_self); int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self); int cg_get_root_path(char **path); -int cg_get_system_path(char **path); -int cg_get_user_path(char **path); -int cg_get_machine_path(const char *machine, char **path); int cg_path_get_session(const char *path, char **session); int cg_path_get_owner_uid(const char *path, uid_t *uid); int cg_path_get_unit(const char *path, char **unit); int cg_path_get_user_unit(const char *path, char **unit); int cg_path_get_machine_name(const char *path, char **machine); +int cg_path_get_slice(const char *path, char **slice); int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup); @@ -102,6 +113,7 @@ int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); int cg_pid_get_unit(pid_t pid, char **unit); int cg_pid_get_user_unit(pid_t pid, char **unit); int cg_pid_get_machine_name(pid_t pid, char **machine); +int cg_pid_get_slice(pid_t pid, char **slice); int cg_path_decode_unit(const char *cgroup, char **unit); @@ -113,3 +125,13 @@ char *cg_escape(const char *p); char *cg_unescape(const char *p) _pure_; bool cg_controller_is_valid(const char *p, bool allow_named); + +int cg_slice_to_path(const char *unit, char **ret); + +int cg_create_everywhere(CGroupControllerMask supported, CGroupControllerMask mask, const char *path); +int cg_attach_everywhere(CGroupControllerMask supported, const char *path, pid_t pid); +int cg_attach_many_everywhere(CGroupControllerMask supported, const char *path, Set* pids); +int cg_migrate_everywhere(CGroupControllerMask supported, const char *from, const char *to); +int cg_trim_everywhere(CGroupControllerMask supported, const char *path, bool delete_root); + +CGroupControllerMask cg_mask_supported(void); diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 2303d9a50b..6085d33391 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -599,6 +599,7 @@ int config_parse_path(const char *unit, char **s = data; char *n; + int offset; assert(filename); assert(lvalue); @@ -611,7 +612,9 @@ int config_parse_path(const char *unit, return 0; } - if (!path_is_absolute(rvalue)) { + offset = rvalue[0] == '-' && (streq(lvalue, "InaccessibleDirectories") || + streq(lvalue, "ReadOnlyDirectories")); + if (!path_is_absolute(rvalue + offset)) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Not an absolute path, ignoring: %s", rvalue); return 0; @@ -713,6 +716,7 @@ int config_parse_path_strv(const char *unit, FOREACH_WORD_QUOTED(w, l, rvalue, state) { _cleanup_free_ char *n; + int offset; n = strndup(w, l); if (!n) @@ -724,7 +728,9 @@ int config_parse_path_strv(const char *unit, continue; } - if (!path_is_absolute(n)) { + offset = n[0] == '-' && (streq(lvalue, "InaccessibleDirectories") || + streq(lvalue, "ReadOnlyDirectories")); + if (!path_is_absolute(n + offset)) { log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Not an absolute path, ignoring: %s", rvalue); continue; diff --git a/src/shared/dbus-common.c b/src/shared/dbus-common.c index b8c15cb9fc..c727cae7cd 100644 --- a/src/shared/dbus-common.c +++ b/src/shared/dbus-common.c @@ -178,9 +178,9 @@ int bus_connect_system_ssh(const char *user, const char *host, DBusConnection ** assert(user || host); if (user && host) - asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host); + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s%%40%s,argv3=systemd-stdio-bridge", user, host); else if (user) - asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user); + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s%%40localhost,argv3=systemd-stdio-bridge", user); else if (host) asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host); @@ -1383,6 +1383,8 @@ int bus_method_call_with_reply( r = -EACCES; else if (dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY)) r = -ETIMEDOUT; + else if (dbus_error_has_name(&error, DBUS_ERROR_DISCONNECTED)) + r = -ECONNRESET; else r = -EIO; goto finish; diff --git a/src/shared/def.h b/src/shared/def.h index 5ba170f965..edd0bcf7a4 100644 --- a/src/shared/def.h +++ b/src/shared/def.h @@ -32,4 +32,9 @@ #define SYSTEMD_CGROUP_CONTROLLER "name=systemd" #define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT -#define SIGNALS_IGNORE SIGKILL,SIGPIPE +#define SIGNALS_IGNORE SIGPIPE + +#define DIGITS "0123456789" +#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz" +#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index b0ac02d461..50a187fda9 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -54,13 +54,19 @@ void dev_setup(const char *prefix) { const char *j, *k; static const char symlinks[] = - "/proc/kcore\0" "/dev/core\0" + "-/proc/kcore\0" "/dev/core\0" "/proc/self/fd\0" "/dev/fd\0" "/proc/self/fd/0\0" "/dev/stdin\0" "/proc/self/fd/1\0" "/dev/stdout\0" "/proc/self/fd/2\0" "/dev/stderr\0"; NULSTR_FOREACH_PAIR(j, k, symlinks) { + if (j[0] == '-') { + j++; + + if (access(j, F_OK)) + continue; + } if (prefix) { char *linkname; diff --git a/src/shared/device-nodes.c b/src/shared/device-nodes.c new file mode 100644 index 0000000000..9837375099 --- /dev/null +++ b/src/shared/device-nodes.c @@ -0,0 +1,74 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2008-2011 Kay Sievers + + 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 <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <sys/types.h> + +#include "device-nodes.h" +#include "utf8.h" + +int whitelisted_char_for_devnode(char c, const char *white) { + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + strchr("#+-.:=@_", c) != NULL || + (white != NULL && strchr(white, c) != NULL)) + return 1; + return 0; +} + +int encode_devnode_name(const char *str, char *str_enc, size_t len) { + size_t i, j; + + if (str == NULL || str_enc == NULL) + return -1; + + for (i = 0, j = 0; str[i] != '\0'; i++) { + int seqlen; + + seqlen = utf8_encoded_valid_unichar(&str[i]); + if (seqlen > 1) { + if (len-j < (size_t)seqlen) + goto err; + memcpy(&str_enc[j], &str[i], seqlen); + j += seqlen; + i += (seqlen-1); + } else if (str[i] == '\\' || !whitelisted_char_for_devnode(str[i], NULL)) { + if (len-j < 4) + goto err; + sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); + j += 4; + } else { + if (len-j < 1) + goto err; + str_enc[j] = str[i]; + j++; + } + } + if (len-j < 1) + goto err; + str_enc[j] = '\0'; + return 0; +err: + return -1; +} diff --git a/src/shared/device-nodes.h b/src/shared/device-nodes.h new file mode 100644 index 0000000000..04ba4897e5 --- /dev/null +++ b/src/shared/device-nodes.h @@ -0,0 +1,25 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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/>. +***/ + +int encode_devnode_name(const char *str, char *str_enc, size_t len); +int whitelisted_char_for_devnode(char c, const char *additional); diff --git a/src/shared/efivars.c b/src/shared/efivars.c index 8d004bad33..1d5b6f9e72 100644 --- a/src/shared/efivars.c +++ b/src/shared/efivars.c @@ -24,6 +24,7 @@ #include <fcntl.h> #include <ctype.h> +#include "acpi-fpdt.h" #include "util.h" #include "utf8.h" #include "efivars.h" @@ -413,7 +414,7 @@ static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { return 0; } -static int get_boot_usec(usec_t *firmware, usec_t *loader) { +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { uint64_t x, y; int r; @@ -440,43 +441,7 @@ static int get_boot_usec(usec_t *firmware, usec_t *loader) { return 0; } -int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { - usec_t x, y, a; - int r; - dual_timestamp _n; - - assert(firmware); - assert(loader); - - if (!n) { - dual_timestamp_get(&_n); - n = &_n; - } - - r = get_boot_usec(&x, &y); - if (r < 0) - return r; - - /* Let's convert this to timestamps where the firmware - * began/loader began working. To make this more confusing: - * since usec_t is unsigned and the kernel's monotonic clock - * begins at kernel initialization we'll actually initialize - * the monotonic timestamps here as negative of the actual - * value. */ - - firmware->monotonic = y; - loader->monotonic = y - x; - - a = n->monotonic + firmware->monotonic; - firmware->realtime = n->realtime > a ? n->realtime - a : 0; - - a = n->monotonic + loader->monotonic; - loader->realtime = n->realtime > a ? n->realtime - a : 0; - - return 0; -} - -int efi_get_loader_device_part_uuid(sd_id128_t *u) { +int efi_loader_get_device_part_uuid(sd_id128_t *u) { _cleanup_free_ char *p = NULL; int r, parsed[16]; unsigned i; diff --git a/src/shared/efivars.h b/src/shared/efivars.h index 2b88c6075c..7921bedc9f 100644 --- a/src/shared/efivars.h +++ b/src/shared/efivars.h @@ -42,6 +42,5 @@ int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *partuuid, char ** int efi_get_boot_order(uint16_t **order); int efi_get_boot_options(uint16_t **options); -int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); - -int efi_get_loader_device_part_uuid(sd_id128_t *u); +int efi_loader_get_device_part_uuid(sd_id128_t *u); +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); diff --git a/src/shared/env-util.c b/src/shared/env-util.c index 6a52fb960d..5e29629efd 100644 --- a/src/shared/env-util.c +++ b/src/shared/env-util.c @@ -27,11 +27,10 @@ #include "utf8.h" #include "util.h" #include "env-util.h" +#include "def.h" #define VALID_CHARS_ENV_NAME \ - "0123456789" \ - "abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + DIGITS LETTERS \ "_" #ifndef ARG_MAX diff --git a/src/shared/fileio.c b/src/shared/fileio.c index ad068bf30d..603a1c7b38 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -23,7 +23,8 @@ #include "fileio.h" #include "util.h" #include "strv.h" - +#include "utf8.h" +#include "ctype.h" int write_string_to_file(FILE *f, const char *line) { errno = 0; @@ -178,13 +179,15 @@ int read_full_file(const char *fn, char **contents, size_t *size) { static int parse_env_file_internal( const char *fname, const char *newline, - int (*push) (const char *key, char *value, void *userdata), + int (*push) (const char *filename, unsigned line, + const char *key, char *value, void *userdata), void *userdata) { _cleanup_free_ char *contents = NULL, *key = NULL; size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1; char *p, *value = NULL; int r; + unsigned line = 1; enum { PRE_KEY, @@ -231,6 +234,7 @@ static int parse_env_file_internal( case KEY: if (strchr(newline, c)) { state = PRE_KEY; + line ++; n_key = 0; } else if (c == '=') { state = PRE_VALUE; @@ -254,6 +258,7 @@ static int parse_env_file_internal( case PRE_VALUE: if (strchr(newline, c)) { state = PRE_KEY; + line ++; key[n_key] = 0; if (value) @@ -263,7 +268,7 @@ static int parse_env_file_internal( if (last_key_whitespace != (size_t) -1) key[last_key_whitespace] = 0; - r = push(key, value, userdata); + r = push(fname, line, key, value, userdata); if (r < 0) goto fail; @@ -293,6 +298,7 @@ static int parse_env_file_internal( case VALUE: if (strchr(newline, c)) { state = PRE_KEY; + line ++; key[n_key] = 0; @@ -307,7 +313,7 @@ static int parse_env_file_internal( if (last_key_whitespace != (size_t) -1) key[last_key_whitespace] = 0; - r = push(key, value, userdata); + r = push(fname, line, key, value, userdata); if (r < 0) goto fail; @@ -409,8 +415,10 @@ static int parse_env_file_internal( case COMMENT: if (c == '\\') state = COMMENT_ESCAPE; - else if (strchr(newline, c)) + else if (strchr(newline, c)) { state = PRE_KEY; + line ++; + } break; case COMMENT_ESCAPE: @@ -440,7 +448,7 @@ static int parse_env_file_internal( if (last_key_whitespace != (size_t) -1) key[last_key_whitespace] = 0; - r = push(key, value, userdata); + r = push(fname, line, key, value, userdata); if (r < 0) goto fail; } @@ -452,27 +460,36 @@ fail: return r; } -static int parse_env_file_push(const char *key, char *value, void *userdata) { - const char *k; - va_list* ap = (va_list*) userdata; - va_list aq; +static int parse_env_file_push(const char *filename, unsigned line, + const char *key, char *value, void *userdata) { + assert(utf8_is_valid(key)); + + if (value && !utf8_is_valid(value)) + /* FIXME: filter UTF-8 */ + log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.", + filename, line, key, value); + else { + const char *k; + va_list* ap = (va_list*) userdata; + va_list aq; - va_copy(aq, *ap); + va_copy(aq, *ap); - while ((k = va_arg(aq, const char *))) { - char **v; + while ((k = va_arg(aq, const char *))) { + char **v; - v = va_arg(aq, char **); + v = va_arg(aq, char **); - if (streq(key, k)) { - va_end(aq); - free(*v); - *v = value; - return 1; + if (streq(key, k)) { + va_end(aq); + free(*v); + *v = value; + return 1; + } } - } - va_end(aq); + va_end(aq); + } free(value); return 0; @@ -495,19 +512,28 @@ int parse_env_file( return r; } -static int load_env_file_push(const char *key, char *value, void *userdata) { - char ***m = userdata; - char *p; - int r; +static int load_env_file_push(const char *filename, unsigned line, + const char *key, char *value, void *userdata) { + assert(utf8_is_valid(key)); - p = strjoin(key, "=", strempty(value), NULL); - if (!p) - return -ENOMEM; + if (value && !utf8_is_valid(value)) + /* FIXME: filter UTF-8 */ + log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.", + filename, line, key, value); + else { + char ***m = userdata; + char *p; + int r; - r = strv_push(m, p); - if (r < 0) { - free(p); - return r; + p = strjoin(key, "=", strempty(value), NULL); + if (!p) + return -ENOMEM; + + r = strv_push(m, p); + if (r < 0) { + free(p); + return r; + } } free(value); @@ -594,3 +620,79 @@ int write_env_file(const char *fname, char **l) { return r; } + +int executable_is_script(const char *path, char **interpreter) { + int r; + char _cleanup_free_ *line = NULL; + int len; + char *ans; + + assert(path); + + r = read_one_line_file(path, &line); + if (r < 0) + return r; + + if (!startswith(line, "#!")) + return 0; + + ans = strstrip(line + 2); + len = strcspn(ans, " \t"); + + if (len == 0) + return 0; + + ans = strndup(ans, len); + if (!ans) + return -ENOMEM; + + *interpreter = ans; + return 1; +} + +/** + * Retrieve one field from a file like /proc/self/status. pattern + * should start with '\n' and end with a ':'. Whitespace and zeros + * after the ':' will be skipped. field must be freed afterwards. + */ +int get_status_field(const char *filename, const char *pattern, char **field) { + _cleanup_free_ char *status = NULL; + char *t; + size_t len; + int r; + + assert(filename); + assert(field); + + r = read_full_file(filename, &status, NULL); + if (r < 0) + return r; + + t = strstr(status, pattern); + if (!t) + return -ENOENT; + + t += strlen(pattern); + if (*t) { + t += strspn(t, " \t"); + + /* Also skip zeros, because when this is used for + * capabilities, we don't want the zeros. This way the + * same capability set always maps to the same string, + * irrespective of the total capability set size. For + * other numbers it shouldn't matter. */ + t += strspn(t, "0"); + /* Back off one char if there's nothing but whitespace + and zeros */ + if (!*t || isspace(*t)) + t --; + } + + len = strcspn(t, WHITESPACE); + + *field = strndup(t, len); + if (!*field) + return -ENOMEM; + + return 0; +} diff --git a/src/shared/fileio.h b/src/shared/fileio.h index 0ca6878ea4..59e41502b1 100644 --- a/src/shared/fileio.h +++ b/src/shared/fileio.h @@ -35,3 +35,7 @@ int read_full_file(const char *fn, char **contents, size_t *size); int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; int load_env_file(const char *fname, const char *separator, char ***l); int write_env_file(const char *fname, char **l); + +int executable_is_script(const char *path, char **interpreter); + +int get_status_field(const char *filename, const char *pattern, char **field); diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c index 9f7db34397..f06fce6ef3 100644 --- a/src/shared/hashmap.c +++ b/src/shared/hashmap.c @@ -24,11 +24,15 @@ #include <string.h> #include <errno.h> +#ifdef HAVE_SYS_AUXV_H +#include <sys/auxv.h> +#endif + #include "util.h" #include "hashmap.h" #include "macro.h" -#define NBUCKETS 127 +#define INITIAL_N_BUCKETS 31 struct hashmap_entry { const void *key; @@ -42,13 +46,14 @@ struct Hashmap { compare_func_t compare_func; struct hashmap_entry *iterate_list_head, *iterate_list_tail; - unsigned n_entries; + struct hashmap_entry ** buckets; + unsigned n_buckets, n_entries; + + unsigned random_xor; bool from_pool; }; -#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap)))) - struct pool { struct pool *next; unsigned n_tiles; @@ -64,6 +69,11 @@ static void *first_entry_tile = NULL; static void* allocate_tile(struct pool **first_pool, void **first_tile, size_t tile_size) { unsigned i; + /* When a tile is released we add it to the list and simply + * place the next pointer at its offset 0. */ + + assert(tile_size >= sizeof(void*)); + if (*first_tile) { void *r; @@ -166,14 +176,19 @@ int uint64_compare_func(const void *_a, const void *_b) { return a < b ? -1 : (a > b ? 1 : 0); } +static unsigned bucket_hash(Hashmap *h, const void *p) { + return (h->hash_func(p) ^ h->random_xor) % h->n_buckets; +} + Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) { bool b; Hashmap *h; size_t size; + void *auxv; b = is_main_thread(); - size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry*); + size = ALIGN(sizeof(Hashmap)) + INITIAL_N_BUCKETS * sizeof(struct hashmap_entry*); if (b) { h = allocate_tile(&first_hashmap_pool, &first_hashmap_tile, size); @@ -191,23 +206,43 @@ Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) { h->hash_func = hash_func ? hash_func : trivial_hash_func; h->compare_func = compare_func ? compare_func : trivial_compare_func; + h->n_buckets = INITIAL_N_BUCKETS; h->n_entries = 0; h->iterate_list_head = h->iterate_list_tail = NULL; + h->buckets = (struct hashmap_entry**) ((uint8_t*) h + ALIGN(sizeof(Hashmap))); + h->from_pool = b; + /* Let's randomize our hash functions a bit so that they are + * harder to guess for clients. For this, start out by cheaply + * using some bits the kernel passed into the process using + * the auxiliary vector. If the hashmap grows later on we will + * rehash everything using a new random XOR mask from + * /dev/random. */ +#ifdef HAVE_SYS_AUXV_H + auxv = (void*) getauxval(AT_RANDOM); + h->random_xor = auxv ? *(unsigned*) auxv : random_u(); +#else + h->random_xor = random_u(); +#endif + return h; } int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) { + Hashmap *q; + assert(h); if (*h) return 0; - if (!(*h = hashmap_new(hash_func, compare_func))) + q = hashmap_new(hash_func, compare_func); + if (!q) return -ENOMEM; + *h = q; return 0; } @@ -216,11 +251,11 @@ static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { assert(e); /* Insert into hash table */ - e->bucket_next = BY_HASH(h)[hash]; + e->bucket_next = h->buckets[hash]; e->bucket_previous = NULL; - if (BY_HASH(h)[hash]) - BY_HASH(h)[hash]->bucket_previous = e; - BY_HASH(h)[hash] = e; + if (h->buckets[hash]) + h->buckets[hash]->bucket_previous = e; + h->buckets[hash] = e; /* Insert into iteration list */ e->iterate_previous = h->iterate_list_tail; @@ -260,7 +295,7 @@ static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { if (e->bucket_previous) e->bucket_previous->bucket_next = e->bucket_next; else - BY_HASH(h)[hash] = e->bucket_next; + h->buckets[hash] = e->bucket_next; assert(h->n_entries >= 1); h->n_entries--; @@ -272,8 +307,7 @@ static void remove_entry(Hashmap *h, struct hashmap_entry *e) { assert(h); assert(e); - hash = h->hash_func(e->key) % NBUCKETS; - + hash = bucket_hash(h, e->key); unlink_entry(h, e, hash); if (h->from_pool) @@ -291,6 +325,9 @@ void hashmap_free(Hashmap*h) { hashmap_clear(h); + if (h->buckets != (struct hashmap_entry**) ((uint8_t*) h + ALIGN(sizeof(Hashmap)))) + free(h->buckets); + if (h->from_pool) deallocate_tile(&first_hashmap_tile, h); else @@ -353,36 +390,91 @@ void hashmap_clear_free_free(Hashmap *h) { } } - static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) { struct hashmap_entry *e; assert(h); - assert(hash < NBUCKETS); + assert(hash < h->n_buckets); - for (e = BY_HASH(h)[hash]; e; e = e->bucket_next) + for (e = h->buckets[hash]; e; e = e->bucket_next) if (h->compare_func(e->key, key) == 0) return e; return NULL; } +static bool resize_buckets(Hashmap *h) { + struct hashmap_entry **n, *i; + unsigned m, nxor; + + assert(h); + + if (_likely_(h->n_entries*4 < h->n_buckets*3)) + return false; + + /* Increase by four */ + m = (h->n_entries+1)*4-1; + + /* If we hit OOM we simply risk packed hashmaps... */ + n = new0(struct hashmap_entry*, m); + if (!n) + return false; + + /* Let's use a different randomized xor value for the + * extension, so that people cannot guess what we are using + * here forever */ + nxor = random_u(); + + for (i = h->iterate_list_head; i; i = i->iterate_next) { + unsigned hash, x; + + hash = h->hash_func(i->key); + + /* First, drop from old bucket table */ + if (i->bucket_next) + i->bucket_next->bucket_previous = i->bucket_previous; + + if (i->bucket_previous) + i->bucket_previous->bucket_next = i->bucket_next; + else + h->buckets[(hash ^ h->random_xor) % h->n_buckets] = i->bucket_next; + + /* Then, add to new backet table */ + x = (hash ^ nxor) % m; + + i->bucket_next = n[x]; + i->bucket_previous = NULL; + if (n[x]) + n[x]->bucket_previous = i; + n[x] = i; + } + + if (h->buckets != (struct hashmap_entry**) ((uint8_t*) h + ALIGN(sizeof(Hashmap)))) + free(h->buckets); + + h->buckets = n; + h->n_buckets = m; + h->random_xor = nxor; + + return true; +} + int hashmap_put(Hashmap *h, const void *key, void *value) { struct hashmap_entry *e; unsigned hash; assert(h); - hash = h->hash_func(key) % NBUCKETS; - + hash = bucket_hash(h, key); e = hash_scan(h, hash, key); if (e) { - if (e->value == value) return 0; - return -EEXIST; } + if (resize_buckets(h)) + hash = bucket_hash(h, key); + if (h->from_pool) e = allocate_tile(&first_entry_pool, &first_entry_tile, sizeof(struct hashmap_entry)); else @@ -405,7 +497,7 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) { assert(h); - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); e = hash_scan(h, hash, key); if (e) { e->key = key; @@ -422,7 +514,7 @@ int hashmap_update(Hashmap *h, const void *key, void *value) { assert(h); - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); e = hash_scan(h, hash, key); if (!e) return -ENOENT; @@ -438,7 +530,7 @@ void* hashmap_get(Hashmap *h, const void *key) { if (!h) return NULL; - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); e = hash_scan(h, hash, key); if (!e) return NULL; @@ -453,7 +545,7 @@ void* hashmap_get2(Hashmap *h, const void *key, void **key2) { if (!h) return NULL; - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); e = hash_scan(h, hash, key); if (!e) return NULL; @@ -470,12 +562,8 @@ bool hashmap_contains(Hashmap *h, const void *key) { if (!h) return false; - hash = h->hash_func(key) % NBUCKETS; - - if (!hash_scan(h, hash, key)) - return false; - - return true; + hash = bucket_hash(h, key); + return !!hash_scan(h, hash, key); } void* hashmap_remove(Hashmap *h, const void *key) { @@ -486,9 +574,9 @@ void* hashmap_remove(Hashmap *h, const void *key) { if (!h) return NULL; - hash = h->hash_func(key) % NBUCKETS; - - if (!(e = hash_scan(h, hash, key))) + hash = bucket_hash(h, key); + e = hash_scan(h, hash, key); + if (!e) return NULL; data = e->value; @@ -504,11 +592,12 @@ int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, if (!h) return -ENOENT; - old_hash = h->hash_func(old_key) % NBUCKETS; - if (!(e = hash_scan(h, old_hash, old_key))) + old_hash = bucket_hash(h, old_key); + e = hash_scan(h, old_hash, old_key); + if (!e) return -ENOENT; - new_hash = h->hash_func(new_key) % NBUCKETS; + new_hash = bucket_hash(h, new_key); if (hash_scan(h, new_hash, new_key)) return -EEXIST; @@ -529,13 +618,14 @@ int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_ if (!h) return -ENOENT; - old_hash = h->hash_func(old_key) % NBUCKETS; - if (!(e = hash_scan(h, old_hash, old_key))) + old_hash = bucket_hash(h, old_key); + e = hash_scan(h, old_hash, old_key); + if (!e) return -ENOENT; - new_hash = h->hash_func(new_key) % NBUCKETS; - - if ((k = hash_scan(h, new_hash, new_key))) + new_hash = bucket_hash(h, new_key); + k = hash_scan(h, new_hash, new_key); + if (k) if (e != k) remove_entry(h, k); @@ -556,9 +646,10 @@ void* hashmap_remove_value(Hashmap *h, const void *key, void *value) { if (!h) return NULL; - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); - if (!(e = hash_scan(h, hash, key))) + e = hash_scan(h, hash, key); + if (!e) return NULL; if (e->value != value) @@ -646,9 +737,10 @@ void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) { if (!h) return NULL; - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); - if (!(e = hash_scan(h, hash, key))) + e = hash_scan(h, hash, key); + if (!e) return NULL; *i = (Iterator) e; @@ -727,6 +819,14 @@ unsigned hashmap_size(Hashmap *h) { return h->n_entries; } +unsigned hashmap_buckets(Hashmap *h) { + + if (!h) + return 0; + + return h->n_buckets; +} + bool hashmap_isempty(Hashmap *h) { if (!h) @@ -746,9 +846,9 @@ int hashmap_merge(Hashmap *h, Hashmap *other) { for (e = other->iterate_list_head; e; e = e->iterate_next) { int r; - if ((r = hashmap_put(h, e->key, e->value)) < 0) - if (r != -EEXIST) - return r; + r = hashmap_put(h, e->key, e->value); + if (r < 0 && r != -EEXIST) + return r; } return 0; @@ -770,13 +870,11 @@ void hashmap_move(Hashmap *h, Hashmap *other) { n = e->iterate_next; - h_hash = h->hash_func(e->key) % NBUCKETS; - + h_hash = bucket_hash(h, e->key); if (hash_scan(h, h_hash, e->key)) continue; - other_hash = other->hash_func(e->key) % NBUCKETS; - + other_hash = bucket_hash(other, e->key); unlink_entry(other, e, other_hash); link_entry(h, e, h_hash); } @@ -791,12 +889,13 @@ int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { assert(h); - h_hash = h->hash_func(key) % NBUCKETS; + h_hash = bucket_hash(h, key); if (hash_scan(h, h_hash, key)) return -EEXIST; - other_hash = other->hash_func(key) % NBUCKETS; - if (!(e = hash_scan(other, other_hash, key))) + other_hash = bucket_hash(other, key); + e = hash_scan(other, other_hash, key); + if (!e) return -ENOENT; unlink_entry(other, e, other_hash); @@ -810,7 +909,8 @@ Hashmap *hashmap_copy(Hashmap *h) { assert(h); - if (!(copy = hashmap_new(h->hash_func, h->compare_func))) + copy = hashmap_new(h->hash_func, h->compare_func); + if (!copy) return NULL; if (hashmap_merge(copy, h) < 0) { @@ -849,7 +949,7 @@ void *hashmap_next(Hashmap *h, const void *key) { if (!h) return NULL; - hash = h->hash_func(key) % NBUCKETS; + hash = bucket_hash(h, key); e = hash_scan(h, hash, key); if (!e) return NULL; diff --git a/src/shared/hashmap.h b/src/shared/hashmap.h index 15b7e27585..3d4f6721bc 100644 --- a/src/shared/hashmap.h +++ b/src/shared/hashmap.h @@ -76,6 +76,7 @@ int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key); unsigned hashmap_size(Hashmap *h) _pure_; bool hashmap_isempty(Hashmap *h) _pure_; +unsigned hashmap_buckets(Hashmap *h) _pure_; void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key); void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key); diff --git a/src/shared/hwclock.c b/src/shared/hwclock.c index cc11faa6c3..17f12de51f 100644 --- a/src/shared/hwclock.c +++ b/src/shared/hwclock.c @@ -151,7 +151,7 @@ int hwclock_reset_timezone(void) { /* * The very first time we set the kernel's timezone, it will warp * the clock. Do a dummy call here, so the time warping is sealed - * and we set only the time zone with next call. + * and we set only the timezone with next call. */ if (settimeofday(tv_null, &tz) < 0) return -errno; diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index c44459b4e0..1ee1243f4d 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -27,21 +27,35 @@ #include "util.h" #include "install-printf.h" -static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) { +static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { InstallInfo *i = userdata; + char *n; + assert(i); - return unit_name_to_prefix_and_instance(i->name); + n = unit_name_to_prefix_and_instance(i->name); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -static char *specifier_prefix(char specifier, void *data, void *userdata) { +static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { InstallInfo *i = userdata; + char *n; + assert(i); - return unit_name_to_prefix(i->name); + n = unit_name_to_prefix(i->name); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -static char *specifier_instance(char specifier, void *data, void *userdata) { +static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { InstallInfo *i = userdata; char *instance; int r; @@ -50,14 +64,19 @@ static char *specifier_instance(char specifier, void *data, void *userdata) { r = unit_name_to_instance(i->name, &instance); if (r < 0) - return NULL; - if (instance != NULL) - return instance; - else - return strdup(""); + return r; + + if (!instance) { + instance = strdup(""); + if (!instance) + return -ENOMEM; + } + + *ret = instance; + return 0; } -static char *specifier_user_name(char specifier, void *data, void *userdata) { +static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { InstallInfo *i = userdata; const char *username; _cleanup_free_ char *tmp = NULL; @@ -82,18 +101,20 @@ static char *specifier_user_name(char specifier, void *data, void *userdata) { r = get_user_creds(&username, &uid, NULL, NULL, NULL); if (r < 0) - return NULL; + return r; if (asprintf(&printed, "%d", uid) < 0) - return NULL; + return -ENOMEM; break; }} - return printed; + + *ret = printed; + return 0; } -char *install_full_printf(InstallInfo *i, const char *format) { +int install_full_printf(InstallInfo *i, const char *format, char **ret) { /* This is similar to unit_full_printf() but does not support * anything path-related. @@ -108,6 +129,7 @@ char *install_full_printf(InstallInfo *i, const char *format) { * %m the machine ID of the running system * %H the host name of the running system * %b the boot ID of the running system + * %v `uname -r` of the running system */ const Specifier table[] = { @@ -122,11 +144,13 @@ char *install_full_printf(InstallInfo *i, const char *format) { { 'm', specifier_machine_id, NULL }, { 'H', specifier_host_name, NULL }, { 'b', specifier_boot_id, NULL }, - { 0, NULL, NULL } + { 'v', specifier_kernel_release, NULL }, + {} }; assert(i); assert(format); + assert(ret); - return specifier_printf(format, table, i); + return specifier_printf(format, table, i, ret); } diff --git a/src/shared/install-printf.h b/src/shared/install-printf.h index 46f5294d21..6ffa488b1b 100644 --- a/src/shared/install-printf.h +++ b/src/shared/install-printf.h @@ -22,4 +22,4 @@ #pragma once #include "install.h" -char *install_full_printf(InstallInfo *i, const char *format); +int install_full_printf(InstallInfo *i, const char *format, char **ret); diff --git a/src/shared/install.c b/src/shared/install.c index edf4d2a9fe..9722ed4e1c 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -506,7 +506,7 @@ static int find_symlinks_in_scope( UnitFileState *state) { int r; - _cleanup_free_ char *path = NULL; + _cleanup_free_ char *path2 = NULL; bool same_name_link_runtime = false, same_name_link = false; assert(scope >= 0); @@ -514,6 +514,7 @@ static int find_symlinks_in_scope( assert(name); if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) { + _cleanup_free_ char *path = NULL; /* First look in runtime config path */ r = get_config_path(scope, true, root_dir, &path); @@ -530,11 +531,11 @@ static int find_symlinks_in_scope( } /* Then look in the normal config path */ - r = get_config_path(scope, false, root_dir, &path); + r = get_config_path(scope, false, root_dir, &path2); if (r < 0) return r; - r = find_symlinks(name, path, &same_name_link); + r = find_symlinks(name, path2, &same_name_link); if (r < 0) return r; else if (r > 0) { @@ -966,14 +967,15 @@ static int config_parse_user(const char *unit, InstallInfo *i = data; char* printed; + int r; assert(filename); assert(lvalue); assert(rvalue); - printed = install_full_printf(i, rvalue); - if (!printed) - return -ENOMEM; + r = install_full_printf(i, rvalue, &printed); + if (r < 0) + return r; free(i->user); i->user = printed; @@ -1061,8 +1063,8 @@ static int unit_file_search( info->path = path; else { if (r == -ENOENT && unit_name_is_instance(info->name)) { - /* unit file doesn't exist, however instance enablement was request */ - /* we will check if it is possible to load template unit file */ + /* Unit file doesn't exist, however instance enablement was requested. + * We will check if it is possible to load template unit file. */ char *template = NULL, *template_path = NULL, *template_dir = NULL; @@ -1073,7 +1075,7 @@ static int unit_file_search( return -ENOMEM; } - /* we will reuse path variable since we don't need it anymore */ + /* We will reuse path variable since we don't need it anymore. */ template_dir = path; *(strrchr(path, '/') + 1) = '\0'; @@ -1084,7 +1086,7 @@ static int unit_file_search( return -ENOMEM; } - /* let's try to load template unit */ + /* Let's try to load template unit. */ r = unit_file_load(c, info, template_path, allow_symlink); if (r >= 0) { info->path = strdup(template_path); @@ -1199,9 +1201,9 @@ static int install_info_symlink_alias( STRV_FOREACH(s, i->aliases) { _cleanup_free_ char *alias_path = NULL, *dst = NULL; - dst = install_full_printf(i, *s); - if (!dst) - return -ENOMEM; + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; alias_path = path_make_absolute(dst, config_path); if (!alias_path) @@ -1231,9 +1233,9 @@ static int install_info_symlink_wants( STRV_FOREACH(s, i->wanted_by) { _cleanup_free_ char *path = NULL, *dst = NULL; - dst = install_full_printf(i, *s); - if (!dst) - return -ENOMEM; + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; if (!unit_name_is_valid(dst, true)) { r = -EINVAL; @@ -1268,9 +1270,9 @@ static int install_info_symlink_requires( STRV_FOREACH(s, i->required_by) { _cleanup_free_ char *path = NULL, *dst = NULL; - dst = install_full_printf(i, *s); - if (!dst) - return -ENOMEM; + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; if (!unit_name_is_valid(dst, true)) { r = -EINVAL; @@ -1413,7 +1415,9 @@ static int install_context_mark_for_removal( assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0); q = unit_file_search(c, i, paths, root_dir, false); - if (q < 0) { + if (q == -ENOENT) { + /* do nothing */ + } else if (q < 0) { if (r >= 0) r = q; @@ -1422,16 +1426,30 @@ static int install_context_mark_for_removal( r += q; if (unit_name_is_instance(i->name)) { - char *unit_file = NULL; + char *unit_file; + + if (i->path) { + unit_file = path_get_file_name(i->path); + + if (unit_name_is_instance(unit_file)) + /* unit file named as instance exists, thus all symlinks + * pointing to it will be removed */ + q = mark_symlink_for_removal(remove_symlinks_to, i->name); + else + /* does not exist, thus we will mark for removal symlinks + * to template unit file */ + q = mark_symlink_for_removal(remove_symlinks_to, unit_file); + } else { + /* If i->path is not set, it means that we didn't actually find + * the unit file. But we can still remove symlinks to the + * nonexistent template. */ + unit_file = unit_name_template(i->name); + if (!unit_file) + return log_oom(); - unit_file = path_get_file_name(i->path); - - if (unit_name_is_instance(unit_file)) - /* unit file named as instance exists, thus all symlinks pointing to it, will be removed */ - q = mark_symlink_for_removal(remove_symlinks_to, i->name); - else - /* does not exist, thus we will mark for removal symlinks to template unit file */ q = mark_symlink_for_removal(remove_symlinks_to, unit_file); + free(unit_file); + } } else q = mark_symlink_for_removal(remove_symlinks_to, i->name); @@ -1531,43 +1549,101 @@ int unit_file_reenable( bool force, UnitFileChange **changes, unsigned *n_changes) { + int r; + + r = unit_file_disable(scope, runtime, root_dir, files, + changes, n_changes); + if (r < 0) + return r; + + return unit_file_enable(scope, runtime, root_dir, files, force, + changes, n_changes); +} + +int unit_file_set_default( + UnitFileScope scope, + const char *root_dir, + char *file, + UnitFileChange **changes, + unsigned *n_changes) { _cleanup_lookup_paths_free_ LookupPaths paths = {}; _cleanup_install_context_done_ InstallContext c = {}; - char **i; _cleanup_free_ char *config_path = NULL; - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - int r, q; + char *path; + int r; + InstallInfo *i = NULL; assert(scope >= 0); assert(scope < _UNIT_FILE_SCOPE_MAX); + if (unit_name_to_type(file) != UNIT_TARGET) + return -EINVAL; + r = lookup_paths_init_from_scope(&paths, scope); if (r < 0) return r; - r = get_config_path(scope, runtime, root_dir, &config_path); + r = get_config_path(scope, false, root_dir, &config_path); if (r < 0) return r; - STRV_FOREACH(i, files) { - r = mark_symlink_for_removal(&remove_symlinks_to, *i); - if (r < 0) - return r; + r = install_info_add_auto(&c, file); + if (r < 0) + return r; - r = install_info_add_auto(&c, *i); - if (r < 0) + i = (InstallInfo*)hashmap_first(c.will_install); + + r = unit_file_search(&c, i, &paths, root_dir, false); + if (r < 0) + return r; + + path = strappenda(config_path, "/default.target"); + r = create_symlink(i->path, path, true, changes, n_changes); + if (r < 0) + return r; + + return 0; +} + +int unit_file_get_default( + UnitFileScope scope, + const char *root_dir, + char **name) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + char **p; + int r; + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + STRV_FOREACH(p, paths.unit_path) { + _cleanup_free_ char *path = NULL, *tmp = NULL; + + if (isempty(root_dir)) + path = strappend(*p, "/default.target"); + else + path = strjoin(root_dir, "/", *p, "/default.target", NULL); + + if (!path) + return -ENOMEM; + + r = readlink_malloc(path, &tmp); + if (r == -ENOENT) + continue; + else if (r < 0) return r; - } - r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files); + *name = strdup(path_get_file_name(tmp)); + if (!*name) + return -ENOMEM; - /* Returns number of symlinks that where supposed to be installed. */ - q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes); - if (r == 0) - r = q; + return 0; + } - return r; + return -ENOENT; } UnitFileState unit_file_get_state( @@ -1609,24 +1685,29 @@ UnitFileState unit_file_get_state( if (!path) return -ENOMEM; + /* + * Search for a unit file in our default paths, to + * be sure, that there are no broken symlinks. + */ if (lstat(path, &st) < 0) { r = -errno; - if (errno == ENOENT) - continue; - - return -errno; - } + if (errno != ENOENT) + return r; - if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) - return -ENOENT; + if (!unit_name_is_instance(name)) + continue; + } else { + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + return -ENOENT; - r = null_or_empty_path(path); - if (r < 0 && r != -ENOENT) - return r; - else if (r > 0) { - state = path_startswith(*i, "/run") ? - UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; - return state; + r = null_or_empty_path(path); + if (r < 0 && r != -ENOENT) + return r; + else if (r > 0) { + state = path_startswith(*i, "/run") ? + UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + return state; + } } r = find_symlinks_in_scope(scope, root_dir, name, &state); diff --git a/src/shared/install.h b/src/shared/install.h index 94516c9d05..5609d1e8df 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -80,6 +80,8 @@ int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes); +int unit_file_set_default(UnitFileScope scope, const char *root_dir, char *file, UnitFileChange **changes, unsigned *n_changes); +int unit_file_get_default(UnitFileScope scope, const char *root_dir, char **name); UnitFileState unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename); diff --git a/src/shared/label.c b/src/shared/label.c index 1fe4574633..fde39f2259 100644 --- a/src/shared/label.c +++ b/src/shared/label.c @@ -257,14 +257,14 @@ void label_free(const char *label) { #endif } -int label_mkdir(const char *path, mode_t mode, bool apply) { +int label_mkdir(const char *path, mode_t mode) { /* Creates a directory and labels it according to the SELinux policy */ #ifdef HAVE_SELINUX int r; security_context_t fcon = NULL; - if (!apply || !use_selinux() || !label_hnd) + if (!use_selinux() || !label_hnd) goto skipped; if (path_is_absolute(path)) diff --git a/src/shared/label.h b/src/shared/label.h index dda4d1c024..09e15e3c08 100644 --- a/src/shared/label.h +++ b/src/shared/label.h @@ -40,7 +40,7 @@ void label_free(const char *label); int label_get_create_label_from_exe(const char *exe, char **label); -int label_mkdir(const char *path, mode_t mode, bool apply); +int label_mkdir(const char *path, mode_t mode); void label_retest_selinux(void); diff --git a/src/shared/list.h b/src/shared/list.h index 47f275a019..476757460a 100644 --- a/src/shared/list.h +++ b/src/shared/list.h @@ -81,7 +81,7 @@ (head) = _item; \ } while (false) -/* Find the head of the list */ +/* Find the tail of the list */ #define LIST_FIND_TAIL(t,name,item,tail) \ do { \ t *_item = (item); \ @@ -123,3 +123,10 @@ #define LIST_FOREACH_AFTER(name,i,p) \ for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) + +/* Loop starting from p->next until p->prev. + p can be adjusted meanwhile. */ +#define LIST_LOOP_BUT_ONE(name,i,head,p) \ + for ((i) = (p)->name##_next ? (p)->name##_next : (head); \ + (i) != (p); \ + (i) = (i)->name##_next ? (i)->name##_next : (head)) diff --git a/src/shared/log.c b/src/shared/log.c index 27317f7ed3..8f4995a0c8 100644 --- a/src/shared/log.c +++ b/src/shared/log.c @@ -115,16 +115,20 @@ void log_close_syslog(void) { static int create_log_socket(int type) { int fd; + struct timeval tv; - /* All output to the syslog/journal fds we do asynchronously, - * and if the buffers are full we just drop the messages */ - - fd = socket(AF_UNIX, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + fd = socket(AF_UNIX, type|SOCK_CLOEXEC, 0); if (fd < 0) return -errno; fd_inc_sndbuf(fd, SNDBUF_SIZE); + /* We need a blocking fd here since we'd otherwise lose + messages way too early. However, let's not hang forever in the + unlikely case of a deadlock. */ + timeval_store(&tv, 1*USEC_PER_MINUTE); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + return fd; } diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 116dc8a36c..7bb19b4006 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -32,7 +32,11 @@ #include "hashmap.h" #include "journal-internal.h" -#define PRINT_THRESHOLD 128 +/* up to three lines (each up to 100 characters), + or 300 characters, whichever is less */ +#define PRINT_LINE_THRESHOLD 3 +#define PRINT_CHAR_THRESHOLD 300 + #define JSON_THRESHOLD 4096 static int print_catalog(FILE *f, sd_journal *j) { @@ -92,15 +96,91 @@ static bool shall_print(const char *p, size_t l, OutputFlags flags) { if (flags & OUTPUT_SHOW_ALL) return true; - if (l >= PRINT_THRESHOLD) + if (l >= PRINT_CHAR_THRESHOLD) return false; - if (!utf8_is_printable_n(p, l)) + if (!utf8_is_printable(p, l)) return false; return true; } +static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) { + const char *color_on = "", *color_off = ""; + const char *pos, *end; + bool ellipsized = false; + int line = 0; + + if (flags & OUTPUT_COLOR) { + if (priority <= LOG_ERR) { + color_on = ANSI_HIGHLIGHT_RED_ON; + color_off = ANSI_HIGHLIGHT_OFF; + } else if (priority <= LOG_NOTICE) { + color_on = ANSI_HIGHLIGHT_ON; + color_off = ANSI_HIGHLIGHT_OFF; + } + } + + for (pos = message; + pos < message + message_len; + pos = end + 1, line++) { + bool continuation = line > 0; + bool tail_line; + int len; + for (end = pos; end < message + message_len && *end != '\n'; end++) + ; + len = end - pos; + assert(len >= 0); + + /* We need to figure out when we are showing not-last line, *and* + * will skip subsequent lines. In that case, we will put the dots + * at the end of the line, instead of putting dots in the middle + * or not at all. + */ + tail_line = + line + 1 == PRINT_LINE_THRESHOLD || + end + 1 >= message + PRINT_CHAR_THRESHOLD; + + if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || + (prefix + len + 1 < n_columns && !tail_line)) { + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + continue; + } + + /* Beyond this point, ellipsization will happen. */ + ellipsized = true; + + if (prefix < n_columns && n_columns - prefix >= 3) { + if (n_columns - prefix > (unsigned) len + 3) + fprintf(f, "%*s%s%.*s...%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + else { + _cleanup_free_ char *e; + + e = ellipsize_mem(pos, len, n_columns - prefix, + tail_line ? 100 : 90); + if (!e) + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + else + fprintf(f, "%*s%s%s%s\n", + continuation * prefix, "", + color_on, e, color_off); + } + } else + fputs("...\n", f); + + if (tail_line) + break; + } + + return ellipsized; +} + static int output_short( FILE *f, sd_journal *j, @@ -115,14 +195,20 @@ static int output_short( _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; int p = LOG_INFO; - const char *color_on = "", *color_off = ""; + bool ellipsized = false; assert(f); assert(j); - sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : PRINT_THRESHOLD); + /* Set the threshold to one bigger than the actual print + * threshold, so that if the line is actually longer than what + * we're willing to print, ellipsization will occur. This way + * we won't output a misleading line without any indication of + * truncation. + */ + sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); - SD_JOURNAL_FOREACH_DATA(j, data, length) { + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); if (r < 0) @@ -177,6 +263,9 @@ static int output_short( return r; } + if (r < 0) + return r; + if (!message) return 0; @@ -199,7 +288,7 @@ static int output_short( r = sd_journal_get_monotonic_usec(j, &t, &boot_id); if (r < 0) { - log_error("Failed to get monotonic: %s", strerror(-r)); + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); return r; } @@ -224,14 +313,30 @@ static int output_short( r = sd_journal_get_realtime_usec(j, &x); if (r < 0) { - log_error("Failed to get realtime: %s", strerror(-r)); + log_error("Failed to get realtime timestamp: %s", strerror(-r)); return r; } t = (time_t) (x / USEC_PER_SEC); - if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) { + + switch(mode) { + case OUTPUT_SHORT_ISO: + r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", localtime_r(&t, &tm)); + break; + case OUTPUT_SHORT_PRECISE: + r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)); + if (r > 0) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + ".%06llu", x % USEC_PER_SEC); + } + break; + default: + r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)); + } + + if (r <= 0) { log_error("Failed to format time."); - return r; + return -EINVAL; } fputs(buf, f); @@ -260,39 +365,19 @@ static int output_short( n += fake_pid_len + 2; } - if (flags & OUTPUT_COLOR) { - if (p <= LOG_ERR) { - color_on = ANSI_HIGHLIGHT_RED_ON; - color_off = ANSI_HIGHLIGHT_OFF; - } else if (p <= LOG_NOTICE) { - color_on = ANSI_HIGHLIGHT_ON; - color_off = ANSI_HIGHLIGHT_OFF; - } - } - - if (flags & OUTPUT_SHOW_ALL) - fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); - else if (!utf8_is_printable_n(message, message_len)) { + if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { char bytes[FORMAT_BYTES_MAX]; fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); - } else if ((flags & OUTPUT_FULL_WIDTH) || (message_len + n + 1 < n_columns)) - fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); - else if (n < n_columns && n_columns - n - 2 >= 3) { - _cleanup_free_ char *e; - - e = ellipsize_mem(message, message_len, n_columns - n - 2, 90); - - if (!e) - fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); - else - fprintf(f, ": %s%s%s\n", color_on, e, color_off); - } else - fputs("\n", f); + } else { + fputs(": ", f); + ellipsized |= + print_multiline(f, n + 2, n_columns, flags, p, message, message_len); + } if (flags & OUTPUT_CATALOG) print_catalog(f, j); - return 0; + return ellipsized; } static int output_verbose( @@ -306,7 +391,7 @@ static int output_verbose( size_t length; _cleanup_free_ char *cursor = NULL; uint64_t realtime; - char ts[FORMAT_TIMESTAMP_MAX]; + char ts[FORMAT_TIMESTAMP_MAX + 7]; int r; assert(f); @@ -314,10 +399,35 @@ static int output_verbose( sd_journal_set_data_threshold(j, 0); - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) { - log_error("Failed to get realtime timestamp: %s", strerror(-r)); + r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); + if (r == -ENOENT) + log_debug("Source realtime timestamp not found"); + else if (r < 0) { + log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, + "Failed to get source realtime timestamp: %s", strerror(-r)); return r; + } else { + _cleanup_free_ char *value = NULL; + size_t size; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, &size); + if (r < 0) + log_debug("_SOURCE_REALTIME_TIMESTAMP invalid: %s", strerror(-r)); + else { + r = safe_atou64(value, &realtime); + if (r < 0) + log_debug("Failed to parse realtime timestamp: %s", + strerror(-r)); + } + } + + if (r < 0) { + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, + "Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } } r = sd_journal_get_cursor(j, &cursor); @@ -327,28 +437,47 @@ static int output_verbose( } fprintf(f, "%s [%s]\n", - format_timestamp(ts, sizeof(ts), realtime), + format_timestamp_us(ts, sizeof(ts), realtime), cursor); - SD_JOURNAL_FOREACH_DATA(j, data, length) { - if (!shall_print(data, length, flags)) { - const char *c; - char bytes[FORMAT_BYTES_MAX]; + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *c; + int fieldlen; + const char *on = "", *off = ""; - c = memchr(data, '=', length); - if (!c) { - log_error("Invalid field."); - return -EINVAL; - } + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + fieldlen = c - (const char*) data; - fprintf(f, "\t%.*s=[%s blob data]\n", - (int) (c - (const char*) data), - (const char*) data, - format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1)); - } else - fprintf(f, "\t%.*s\n", (int) length, (const char*) data); + if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { + on = ANSI_HIGHLIGHT_ON; + off = ANSI_HIGHLIGHT_OFF; + } + + if (flags & OUTPUT_SHOW_ALL || + (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH) + && utf8_is_printable(data, length))) { + fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data); + print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1); + fputs(off, f); + } else { + char bytes[FORMAT_BYTES_MAX]; + + fprintf(f, " %s%.*s=[%s blob data]%s\n", + on, + (int) (c - (const char*) data), + (const char*) data, + format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1), + off); + } } + if (r < 0) + return r; + if (flags & OUTPUT_CATALOG) print_catalog(f, j); @@ -402,15 +531,15 @@ static int output_export( (unsigned long long) monotonic, sd_id128_to_string(boot_id, sid)); - SD_JOURNAL_FOREACH_DATA(j, data, length) { + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { /* We already printed the boot id, from the data in * the header, hence let's suppress it here */ if (length >= 9 && - memcmp(data, "_BOOT_ID=", 9) == 0) + startswith(data, "_BOOT_ID=")) continue; - if (!utf8_is_printable_n(data, length)) { + if (!utf8_is_printable(data, length)) { const char *c; uint64_t le64; @@ -431,6 +560,9 @@ static int output_export( fputc('\n', f); } + if (r < 0) + return r; + fputc('\n', f); return 0; @@ -449,7 +581,7 @@ void json_escape( fputs("null", f); - else if (!utf8_is_printable_n(p, l)) { + else if (!utf8_is_printable(p, l)) { bool not_first = false; fputs("[ ", f); @@ -474,7 +606,9 @@ void json_escape( if (*p == '"' || *p == '\\') { fputc('\\', f); fputc(*p, f); - } else if (*p < ' ') + } else if (*p == '\n') + fputs("\\n", f); + else if (*p < ' ') fprintf(f, "\\u%04x", *p); else fputc(*p, f); @@ -557,7 +691,7 @@ static int output_json( return -ENOMEM; /* First round, iterate through the entry and count how often each field appears */ - SD_JOURNAL_FOREACH_DATA(j, data, length) { + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { const char *eq; char *n; unsigned u; @@ -591,6 +725,9 @@ static int output_json( } } + if (r < 0) + return r; + separator = true; do { done = true; @@ -747,6 +884,8 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( OutputFlags flags) = { [OUTPUT_SHORT] = output_short, + [OUTPUT_SHORT_ISO] = output_short, + [OUTPUT_SHORT_PRECISE] = output_short, [OUTPUT_SHORT_MONOTONIC] = output_short, [OUTPUT_VERBOSE] = output_verbose, [OUTPUT_EXPORT] = output_export, @@ -761,7 +900,8 @@ int output_journal( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags) { + OutputFlags flags, + bool *ellipsized) { int ret; assert(mode >= 0); @@ -772,6 +912,10 @@ int output_journal( ret = output_funcs[mode](f, j, mode, n_columns, flags); fflush(stdout); + + if (ellipsized && ret > 0) + *ellipsized = true; + return ret; } @@ -781,7 +925,8 @@ static int show_journal(FILE *f, unsigned n_columns, usec_t not_before, unsigned how_many, - OutputFlags flags) { + OutputFlags flags, + bool *ellipsized) { int r; unsigned line = 0; @@ -832,7 +977,7 @@ static int show_journal(FILE *f, line ++; - r = output_journal(f, j, mode, n_columns, flags); + r = output_journal(f, j, mode, n_columns, flags, ellipsized); if (r < 0) goto finish; } @@ -872,15 +1017,15 @@ finish: int add_matches_for_unit(sd_journal *j, const char *unit) { int r; - _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL; + char *m1, *m2, *m3, *m4; assert(j); assert(unit); - if (asprintf(&m1, "_SYSTEMD_UNIT=%s", unit) < 0 || - asprintf(&m2, "COREDUMP_UNIT=%s", unit) < 0 || - asprintf(&m3, "UNIT=%s", unit) < 0) - return -ENOMEM; + m1 = strappenda("_SYSTEMD_UNIT=", unit); + m2 = strappenda("COREDUMP_UNIT=", unit); + m3 = strappenda("UNIT=", unit); + m4 = strappenda("OBJECT_SYSTEMD_UNIT=", unit); (void)( /* Look for messages from the service itself */ @@ -888,49 +1033,112 @@ int add_matches_for_unit(sd_journal *j, const char *unit) { /* Look for coredumps of the service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, - "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || (r = sd_journal_add_match(j, m2, 0)) || /* Look for messages from PID 1 about this service */ (r = sd_journal_add_disjunction(j)) || (r = sd_journal_add_match(j, "_PID=1", 0)) || - (r = sd_journal_add_match(j, m3, 0)) + (r = sd_journal_add_match(j, m3, 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m4, 0)) ); + + if (r == 0 && endswith(unit, ".slice")) { + char *m5 = strappend("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) + ); + } + return r; } int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { int r; - _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL, *m4 = NULL; + char *m1, *m2, *m3, *m4; + char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; assert(j); assert(unit); - if (asprintf(&m1, "_SYSTEMD_USER_UNIT=%s", unit) < 0 || - asprintf(&m2, "USER_UNIT=%s", unit) < 0 || - asprintf(&m3, "COREDUMP_USER_UNIT=%s", unit) < 0 || - asprintf(&m4, "_UID=%d", uid) < 0) - return -ENOMEM; + m1 = strappenda("_SYSTEMD_USER_UNIT=", unit); + m2 = strappenda("USER_UNIT=", unit); + m3 = strappenda("COREDUMP_USER_UNIT=", unit); + m4 = strappenda("OBJECT_SYSTEMD_USER_UNIT=", unit); + sprintf(muid, "_UID=%lu", (unsigned long) uid); (void) ( /* Look for messages from the user service itself */ (r = sd_journal_add_match(j, m1, 0)) || - (r = sd_journal_add_match(j, m4, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || /* Look for messages from systemd about this service */ (r = sd_journal_add_disjunction(j)) || (r = sd_journal_add_match(j, m2, 0)) || - (r = sd_journal_add_match(j, m4, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || /* Look for coredumps of the service */ (r = sd_journal_add_disjunction(j)) || (r = sd_journal_add_match(j, m3, 0)) || - (r = sd_journal_add_match(j, m4, 0)) + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m4, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) ); + + if (r == 0 && endswith(unit, ".slice")) { + char *m5 = strappend("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) || + (r = sd_journal_add_match(j, muid, 0)) + ); + } + return r; } +int add_match_this_boot(sd_journal *j) { + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t boot_id; + int r; + + assert(j); + + r = sd_id128_get_boot(&boot_id); + if (r < 0) { + log_error("Failed to get boot id: %s", strerror(-r)); + return r; + } + + sd_id128_to_string(boot_id, match + 9); + r = sd_journal_add_match(j, match, strlen(match)); + if (r < 0) { + log_error("Failed to add match: %s", strerror(-r)); + return r; + } + + r = sd_journal_add_conjunction(j); + if (r < 0) + return r; + + return 0; +} + int show_journal_by_unit( FILE *f, const char *unit, @@ -940,11 +1148,12 @@ int show_journal_by_unit( unsigned how_many, uid_t uid, OutputFlags flags, - bool system) { + bool system, + bool *ellipsized) { _cleanup_journal_close_ sd_journal*j = NULL; int r; - int jflags = SD_JOURNAL_LOCAL_ONLY | system * SD_JOURNAL_SYSTEM_ONLY; + int jflags = SD_JOURNAL_LOCAL_ONLY | system * SD_JOURNAL_SYSTEM; assert(mode >= 0); assert(mode < _OUTPUT_MODE_MAX); @@ -957,6 +1166,10 @@ int show_journal_by_unit( if (r < 0) return r; + r = add_match_this_boot(j); + if (r < 0) + return r; + if (system) r = add_matches_for_unit(j, unit); else @@ -964,15 +1177,20 @@ int show_journal_by_unit( if (r < 0) return r; - r = show_journal(f, j, mode, n_columns, not_before, how_many, flags); - if (r < 0) - return r; + if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) { + _cleanup_free_ char *filter; - return 0; + filter = journal_make_match_string(j); + log_debug("Journal filter: %s", filter); + } + + return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); } static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_ISO] = "short-iso", + [OUTPUT_SHORT_PRECISE] = "short-precise", [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", [OUTPUT_VERBOSE] = "verbose", [OUTPUT_EXPORT] = "export", diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index b0f93a661a..11b3b59b7b 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -35,7 +35,10 @@ int output_journal( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags); + OutputFlags flags, + bool *ellipsized); + +int add_match_this_boot(sd_journal *j); int add_matches_for_unit( sd_journal *j, @@ -55,7 +58,8 @@ int show_journal_by_unit( unsigned how_many, uid_t uid, OutputFlags flags, - bool system); + bool system, + bool *ellipsized); void json_escape( FILE *f, diff --git a/src/shared/macro.h b/src/shared/macro.h index 0874102ece..d4f92b60ec 100644 --- a/src/shared/macro.h +++ b/src/shared/macro.h @@ -159,23 +159,25 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { } while (false) #endif +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) #define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) -#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) - -#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) -#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) +#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) +#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) +#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u))) #define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) -#define ULONG_TO_PTR(u) ((void*) ((uintptr_t) (u))) +#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u))) -#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) -#define INT_TO_PTR(u) ((void*) ((intptr_t) (u))) - -#define TO_INT32(p) ((int32_t) ((intptr_t) (p))) -#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u))) +#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u))) -#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) -#define LONG_TO_PTR(u) ((void*) ((intptr_t) (u))) +#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p))) +#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) +#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) #define memzero(x,l) (memset((x), 0, (l))) #define zero(x) (memzero(&(x), sizeof(x))) @@ -269,7 +271,7 @@ do { \ * the const magic to the type, otherwise the compiler warns about * signed/unsigned comparison, because the magic can be 32 bit unsigned. */ -#define F_TYPE_CMP(a, b) (a == (typeof(a)) b) +#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) /* Returns the number of chars needed to format variables of the @@ -282,4 +284,7 @@ do { \ sizeof(type) <= 4 ? 10 : \ sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) +#define SET_FLAG(v, flag, b) \ + (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) + #include "log.h" diff --git a/src/shared/missing.h b/src/shared/missing.h index d4ba0d3dcf..6c038d9f08 100644 --- a/src/shared/missing.h +++ b/src/shared/missing.h @@ -29,6 +29,7 @@ #include <stdlib.h> #include <unistd.h> #include <linux/oom.h> +#include <linux/input.h> #ifdef HAVE_AUDIT #include <libaudit.h> @@ -138,7 +139,8 @@ static inline int fanotify_init(unsigned int flags, unsigned int event_f_flags) #ifndef HAVE_FANOTIFY_MARK static inline int fanotify_mark(int fanotify_fd, unsigned int flags, uint64_t mask, int dfd, const char *pathname) { -#if defined _MIPS_SIM && _MIPS_SIM == _MIPS_SIM_ABI32 || defined __powerpc__ && !defined __powerpc64__ +#if defined _MIPS_SIM && _MIPS_SIM == _MIPS_SIM_ABI32 || defined __powerpc__ && !defined __powerpc64__ \ + || defined __arm__ && !defined __aarch64__ union { uint64_t _64; uint32_t _32[2]; @@ -161,15 +163,55 @@ static inline int fanotify_mark(int fanotify_fd, unsigned int flags, uint64_t ma #define BTRFS_PATH_NAME_MAX 4087 #endif +#ifndef BTRFS_DEVICE_PATH_NAME_MAX +#define BTRFS_DEVICE_PATH_NAME_MAX 1024 +#endif + +#ifndef BTRFS_FSID_SIZE +#define BTRFS_FSID_SIZE 16 +#endif + +#ifndef BTRFS_UUID_SIZE +#define BTRFS_UUID_SIZE 16 +#endif + +#ifndef HAVE_LINUX_BTRFS_H struct btrfs_ioctl_vol_args { int64_t fd; char name[BTRFS_PATH_NAME_MAX + 1]; }; +struct btrfs_ioctl_dev_info_args { + uint64_t devid; /* in/out */ + uint8_t uuid[BTRFS_UUID_SIZE]; /* in/out */ + uint64_t bytes_used; /* out */ + uint64_t total_bytes; /* out */ + uint64_t unused[379]; /* pad to 4k */ + char path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ +}; + +struct btrfs_ioctl_fs_info_args { + uint64_t max_id; /* out */ + uint64_t num_devices; /* out */ + uint8_t fsid[BTRFS_FSID_SIZE]; /* out */ + uint64_t reserved[124]; /* pad to 1k */ +}; +#endif + #ifndef BTRFS_IOC_DEFRAG #define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, struct btrfs_ioctl_vol_args) #endif +#ifndef BTRFS_IOC_DEV_INFO +#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \ + struct btrfs_ioctl_dev_info_args) +#endif + +#ifndef BTRFS_IOC_FS_INFO +#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \ + struct btrfs_ioctl_fs_info_args) +#endif + #ifndef BTRFS_SUPER_MAGIC #define BTRFS_SUPER_MAGIC 0x9123683E #endif @@ -265,3 +307,19 @@ static inline int name_to_handle_at(int fd, const char *name, struct file_handle #ifndef TFD_TIMER_CANCEL_ON_SET #define TFD_TIMER_CANCEL_ON_SET (1 << 1) #endif + +#ifndef SO_REUSEPORT +#define SO_REUSEPORT 15 +#endif + +#ifndef EVIOCREVOKE +#define EVIOCREVOKE _IOW('E', 0x91, int) +#endif + +#ifndef DRM_IOCTL_SET_MASTER +#define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) +#endif + +#ifndef DRM_IOCTL_DROP_MASTER +#define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) +#endif diff --git a/src/shared/mkdir-label.c b/src/shared/mkdir-label.c new file mode 100644 index 0000000000..4ee6251bcd --- /dev/null +++ b/src/shared/mkdir-label.c @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Kay Sievers + + 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 <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> + +#include "label.h" +#include "util.h" +#include "path-util.h" +#include "mkdir.h" + +int mkdir_label(const char *path, mode_t mode) { + return label_mkdir(path, mode); +} + +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return mkdir_safe_internal(path, mode, uid, gid, label_mkdir); +} + +int mkdir_parents_label(const char *path, mode_t mode) { + return mkdir_parents_internal(NULL, path, mode, label_mkdir); +} + +int mkdir_parents_prefix_label(const char *prefix, const char *path, mode_t mode) { + return mkdir_parents_internal(prefix, path, mode, label_mkdir); +} + +int mkdir_p_label(const char *path, mode_t mode) { + return mkdir_p_internal(NULL, path, mode, label_mkdir); +} diff --git a/src/shared/mkdir.c b/src/shared/mkdir.c index 0e51b64f69..b7e5c6e67b 100644 --- a/src/shared/mkdir.c +++ b/src/shared/mkdir.c @@ -26,18 +26,15 @@ #include <stdlib.h> #include <stdio.h> -#include "mkdir.h" #include "label.h" #include "util.h" +#include "path-util.h" +#include "mkdir.h" -int mkdir_label(const char *path, mode_t mode) { - return label_mkdir(path, mode, true); -} - -static int makedir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool apply) { +int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir) { struct stat st; - if (label_mkdir(path, mode, apply) >= 0) + if (_mkdir(path, mode) >= 0) if (chmod_and_chown(path, mode, uid, gid) < 0) return -errno; @@ -56,36 +53,46 @@ static int makedir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, boo } int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) { - return makedir_safe(path, mode, uid, gid, false); + return mkdir_safe_internal(path, mode, uid, gid, false); } -int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { - return makedir_safe(path, mode, uid, gid, true); +static int is_dir(const char* path) { + struct stat st; + + if (stat(path, &st) < 0) + return -errno; + + return S_ISDIR(st.st_mode); } -static int makedir_parents(const char *path, mode_t mode, bool apply) { - struct stat st; +int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { const char *p, *e; + int r; assert(path); + if (prefix && !path_startswith(path, prefix)) + return -ENOTDIR; + /* return immediately if directory exists */ e = strrchr(path, '/'); if (!e) return -EINVAL; + + if (e == path) + return 0; + p = strndupa(path, e - path); - if (stat(p, &st) >= 0) { - if ((st.st_mode & S_IFMT) == S_IFDIR) - return 0; - else - return -ENOTDIR; - } + r = is_dir(p); + if (r > 0) + return 0; + if (r == 0) + return -ENOTDIR; /* create every parent directory in the path, except the last component */ p = path + strspn(path, "/"); for (;;) { - int r; - char *t; + char t[strlen(path) + 1]; e = p + strcspn(p, "/"); p = e + strspn(e, "/"); @@ -95,43 +102,32 @@ static int makedir_parents(const char *path, mode_t mode, bool apply) { if (*p == 0) return 0; - t = strndup(path, e - path); - if (!t) - return -ENOMEM; + memcpy(t, path, e - path); + t[e-path] = 0; - r = label_mkdir(t, mode, apply); - free(t); + if (prefix && path_startswith(prefix, t)) + continue; + r = _mkdir(t, mode); if (r < 0 && errno != EEXIST) return -errno; } } int mkdir_parents(const char *path, mode_t mode) { - return makedir_parents(path, mode, false); -} - -int mkdir_parents_label(const char *path, mode_t mode) { - return makedir_parents(path, mode, true); -} - -static int is_dir(const char* path) { - struct stat st; - if (stat(path, &st) < 0) - return -errno; - return S_ISDIR(st.st_mode); + return mkdir_parents_internal(NULL, path, mode, mkdir); } -static int makedir_p(const char *path, mode_t mode, bool apply) { +int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { int r; /* Like mkdir -p */ - r = makedir_parents(path, mode, apply); + r = mkdir_parents_internal(prefix, path, mode, _mkdir); if (r < 0) return r; - r = label_mkdir(path, mode, apply); + r = _mkdir(path, mode); if (r < 0 && (errno != EEXIST || is_dir(path) <= 0)) return -errno; @@ -139,9 +135,9 @@ static int makedir_p(const char *path, mode_t mode, bool apply) { } int mkdir_p(const char *path, mode_t mode) { - return makedir_p(path, mode, false); + return mkdir_p_internal(NULL, path, mode, mkdir); } -int mkdir_p_label(const char *path, mode_t mode) { - return makedir_p(path, mode, true); +int mkdir_p_prefix(const char *prefix, const char *path, mode_t mode) { + return mkdir_p_internal(prefix, path, mode, mkdir); } diff --git a/src/shared/mkdir.h b/src/shared/mkdir.h index ce1c35e9ba..5b34db4229 100644 --- a/src/shared/mkdir.h +++ b/src/shared/mkdir.h @@ -7,6 +7,7 @@ This file is part of systemd. Copyright 2010 Lennart Poettering + Copyright 2013 Kay Sievers 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 @@ -22,11 +23,23 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -int mkdir_label(const char *path, mode_t mode); +#include <sys/types.h> + int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid); -int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); int mkdir_parents(const char *path, mode_t mode); -int mkdir_parents_label(const char *path, mode_t mode); int mkdir_p(const char *path, mode_t mode); +int mkdir_p_prefix(const char *prefix, const char *path, mode_t mode); + +/* selinux versions */ +int mkdir_label(const char *path, mode_t mode); +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents_label(const char *path, mode_t mode); int mkdir_p_label(const char *path, mode_t mode); +int mkdir_parents_prefix_label(const char *prefix, const char *path, mode_t mode); + +/* internally used */ +typedef int (*mkdir_func_t)(const char *pathname, mode_t mode); +int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir); +int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); +int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); #endif diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index 0efd430c5d..9da789db76 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -23,6 +23,8 @@ typedef enum OutputMode { OUTPUT_SHORT, + OUTPUT_SHORT_ISO, + OUTPUT_SHORT_PRECISE, OUTPUT_SHORT_MONOTONIC, OUTPUT_VERBOSE, OUTPUT_EXPORT, diff --git a/src/shared/path-util.c b/src/shared/path-util.c index 0c1b6a0ab0..45099eeda8 100644 --- a/src/shared/path-util.c +++ b/src/shared/path-util.c @@ -102,7 +102,8 @@ char **path_split_and_make_absolute(const char *p) { char **l; assert(p); - if (!(l = strv_split(p, ":"))) + l = strv_split(p, ":"); + if (!l) return NULL; if (!path_strv_make_absolute_cwd(l)) { @@ -126,7 +127,7 @@ char *path_make_absolute(const char *p, const char *prefix) { } char *path_make_absolute_cwd(const char *p) { - char *cwd, *r; + _cleanup_free_ char *cwd = NULL; assert(p); @@ -140,10 +141,7 @@ char *path_make_absolute_cwd(const char *p) { if (!cwd) return NULL; - r = path_make_absolute(p, cwd); - free(cwd); - - return r; + return path_make_absolute(p, cwd); } char **path_strv_make_absolute_cwd(char **l) { @@ -156,7 +154,8 @@ char **path_strv_make_absolute_cwd(char **l) { STRV_FOREACH(s, l) { char *t; - if (!(t = path_make_absolute_cwd(*s))) + t = path_make_absolute_cwd(*s); + if (!t) return NULL; free(*s); @@ -426,3 +425,51 @@ int path_is_os_tree(const char *path) { return r < 0 ? 0 : 1; } + +int find_binary(const char *name, char **filename) { + assert(name); + if (strchr(name, '/')) { + char *p; + + if (path_is_absolute(name)) + p = strdup(name); + else + p = path_make_absolute_cwd(name); + if (!p) + return -ENOMEM; + + *filename = p; + return 0; + } else { + const char *path; + char *state, *w; + size_t l; + + /** + * Plain getenv, not secure_getenv, because we want + * to actually allow the user to pick the binary. + */ + path = getenv("PATH"); + if (!path) + path = DEFAULT_PATH; + + FOREACH_WORD_SEPARATOR(w, l, path, ":", state) { + char *p; + + if (asprintf(&p, "%.*s/%s", (int) l, w, name) < 0) + return -ENOMEM; + + if (access(p, X_OK) < 0) { + free(p); + continue; + } + + path_kill_slashes(p); + *filename = p; + + return 0; + } + + return -ENOENT; + } +} diff --git a/src/shared/path-util.h b/src/shared/path-util.h index d187743769..0a42de7e27 100644 --- a/src/shared/path-util.h +++ b/src/shared/path-util.h @@ -25,6 +25,12 @@ #include "macro.h" +#ifdef HAVE_SPLIT_USR +# define DEFAULT_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +#else +# define DEFAULT_PATH "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" +#endif + bool is_path(const char *p) _pure_; char** path_split_and_make_absolute(const char *p); char* path_get_file_name(const char *p) _pure_; @@ -43,3 +49,15 @@ char** path_strv_canonicalize_uniq(char **l); int path_is_mount_point(const char *path, bool allow_symlink); int path_is_read_only_fs(const char *path); int path_is_os_tree(const char *path); + +int find_binary(const char *name, char **filename); + +/* Iterates through the path prefixes of the specified path, going up + * the tree, to root. Also returns "" (and not "/"!) for the root + * directory. Excludes the specified directory itself */ +#define PATH_FOREACH_PREFIX(prefix, path) \ + for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && !(*_slash = 0); _slash = strrchr((prefix), '/')) + +/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */ +#define PATH_FOREACH_PREFIX_MORE(prefix, path) \ + for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && !(*_slash = 0); _slash = strrchr((prefix), '/')) diff --git a/src/shared/polkit.c b/src/shared/polkit.c index cea7074ad3..1c5e9e3e0f 100644 --- a/src/shared/polkit.c +++ b/src/shared/polkit.c @@ -38,12 +38,8 @@ int verify_polkit( #ifdef ENABLE_POLKIT DBusMessage *m = NULL, *reply = NULL; - const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = ""; + const char *system_bus_name = "system-bus-name", *name = "name", *cancel_id = ""; uint32_t flags = interactive ? 1 : 0; - pid_t pid_raw; - uint32_t pid_u32; - unsigned long long starttime_raw; - uint64_t starttime_u64; DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant; int r; dbus_bool_t authorized = FALSE, challenge = FALSE; @@ -68,14 +64,6 @@ int verify_polkit( #ifdef ENABLE_POLKIT - pid_raw = bus_get_unix_process_id(c, sender, error); - if (pid_raw == 0) - return -EINVAL; - - r = get_starttime_of_pid(pid_raw, &starttime_raw); - if (r < 0) - return r; - m = dbus_message_new_method_call( "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", @@ -86,22 +74,13 @@ int verify_polkit( dbus_message_iter_init_append(m, &iter_msg); - pid_u32 = (uint32_t) pid_raw; - starttime_u64 = (uint64_t) starttime_raw; - if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) || - !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) || + !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &system_bus_name) || !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) || !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) || - !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) || - !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) || - !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) || - !dbus_message_iter_close_container(&iter_dict, &iter_variant) || - !dbus_message_iter_close_container(&iter_array, &iter_dict) || - !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) || - !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) || - !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) || - !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) || + !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &name) || + !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "s", &iter_variant) || + !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_STRING, &sender) || !dbus_message_iter_close_container(&iter_dict, &iter_variant) || !dbus_message_iter_close_container(&iter_array, &iter_dict) || !dbus_message_iter_close_container(&iter_struct, &iter_array) || diff --git a/src/shared/refcnt.h b/src/shared/refcnt.h new file mode 100644 index 0000000000..0502c20a2e --- /dev/null +++ b/src/shared/refcnt.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + 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/>. +***/ + +/* A type-safe atomic refcounter */ + +typedef struct { + volatile unsigned _value; +} RefCount; + +#define REFCNT_GET(r) ((r)._value) +#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1)) +#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1)) + +#define REFCNT_INIT ((RefCount) { ._value = 1 }) diff --git a/src/shared/replace-var.c b/src/shared/replace-var.c index e11c57a43d..478fc43a38 100644 --- a/src/shared/replace-var.c +++ b/src/shared/replace-var.c @@ -24,6 +24,7 @@ #include "macro.h" #include "util.h" #include "replace-var.h" +#include "def.h" /* * Generic infrastructure for replacing @FOO@ style variables in @@ -40,7 +41,7 @@ static int get_variable(const char *b, char **r) { if (*b != '@') return 0; - k = strspn(b + 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"); + k = strspn(b + 1, UPPERCASE_LETTERS "_"); if (k <= 0 || b[k+1] != '@') return 0; diff --git a/src/shared/set.c b/src/shared/set.c index c338dc3a44..5a4bf11bdf 100644 --- a/src/shared/set.c +++ b/src/shared/set.c @@ -50,9 +50,12 @@ int set_put(Set *s, void *value) { } int set_consume(Set *s, void *value) { - int r = set_put(s, value); + int r; + + r = set_put(s, value); if (r < 0) free(value); + return r; } diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index cd3238b405..d068bfce3c 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -163,6 +163,93 @@ int can_sleep_disk(char **types) { return false; } +#define HIBERNATION_SWAP_THRESHOLD 0.98 + +static int hibernation_partition_size(size_t *size, size_t *used) { + _cleanup_fclose_ FILE *f; + int i; + + assert(size); + assert(used); + + f = fopen("/proc/swaps", "r"); + if (!f) { + log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "Failed to retrieve open /proc/swaps: %m"); + assert(errno > 0); + return -errno; + } + + (void) fscanf(f, "%*s %*s %*s %*s %*s\n"); + + for (i = 1;; i++) { + _cleanup_free_ char *dev = NULL, *d = NULL, *type = NULL; + size_t size_field, used_field; + int k; + + k = fscanf(f, + "%ms " /* device/file */ + "%ms " /* type of swap */ + "%zd " /* swap size */ + "%zd " /* used */ + "%*i\n", /* priority */ + &dev, &type, &size_field, &used_field); + if (k != 4) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u", i); + continue; + } + + d = cunescape(dev); + if (!d) + return -ENOMEM; + + if (!streq(type, "partition")) { + log_debug("Partition %s has type %s, ignoring.", d, type); + continue; + } + + *size = size_field; + *used = used_field; + return 0; + } + + log_debug("No swap partitions were found."); + return -ENOSYS; +} + +static bool enough_memory_for_hibernation(void) { + _cleanup_free_ char *active = NULL; + unsigned long long act; + size_t size, used; + int r; + + r = hibernation_partition_size(&size, &used); + if (r < 0) + return false; + + r = get_status_field("/proc/meminfo", "\nActive(anon):", &active); + if (r < 0) { + log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r)); + return false; + } + + r = safe_atollu(active, &act); + if (r < 0) { + log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s", + active, strerror(-r)); + return false; + } + + r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD; + log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%", + r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD); + + return r; +} + int can_sleep(const char *verb) { _cleanup_strv_free_ char **modes = NULL, **states = NULL; int r; @@ -175,5 +262,8 @@ int can_sleep(const char *verb) { if (r < 0) return false; - return can_sleep_state(states) && can_sleep_disk(modes); + if (!can_sleep_state(states) || !can_sleep_disk(modes)) + return false; + + return streq(verb, "suspend") || enough_memory_for_hibernation(); } diff --git a/src/shared/socket-util.c b/src/shared/socket-util.c index c583d3dfea..9224208244 100644 --- a/src/shared/socket-util.c +++ b/src/shared/socket-util.c @@ -486,16 +486,16 @@ bool socket_address_is_netlink(const SocketAddress *a, const char *s) { return socket_address_equal(a, &b); } -bool socket_address_needs_mount(const SocketAddress *a, const char *prefix) { +const char* socket_address_get_path(const SocketAddress *a) { assert(a); if (socket_address_family(a) != AF_UNIX) - return false; + return NULL; if (a->sockaddr.un.sun_path[0] == 0) - return false; + return NULL; - return path_startswith(a->sockaddr.un.sun_path, prefix); + return a->sockaddr.un.sun_path; } bool socket_ipv6_is_supported(void) { diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h index 7829a337fc..e0b85adf9f 100644 --- a/src/shared/socket-util.h +++ b/src/shared/socket-util.h @@ -92,7 +92,7 @@ int make_socket_fd(const char* address, int flags); bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; -bool socket_address_needs_mount(const SocketAddress *a, const char *prefix); +const char* socket_address_get_path(const SocketAddress *a); const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; diff --git a/src/shared/specifier.c b/src/shared/specifier.c index 7577c91052..8fbf6db5df 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -20,6 +20,7 @@ ***/ #include <string.h> +#include <sys/utsname.h> #include "macro.h" #include "util.h" @@ -31,21 +32,22 @@ * */ -char *specifier_printf(const char *text, const Specifier table[], void *userdata) { - char *r, *t; +int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) { + char *ret, *t; const char *f; bool percent = false; size_t l; + int r; assert(text); assert(table); l = strlen(text); - r = new(char, l+1); - if (!r) - return NULL; + ret = new(char, l+1); + if (!ret) + return -ENOMEM; - t = r; + t = ret; for (f = text; *f; f++, l--) { @@ -60,32 +62,31 @@ char *specifier_printf(const char *text, const Specifier table[], void *userdata break; if (i->lookup) { - char *n, *w; + _cleanup_free_ char *w = NULL; + char *n; size_t k, j; - w = i->lookup(i->specifier, i->data, userdata); - if (!w) { - free(r); - return NULL; + r = i->lookup(i->specifier, i->data, userdata, &w); + if (r < 0) { + free(ret); + return r; } - j = t - r; + j = t - ret; k = strlen(w); n = new(char, j + k + l + 1); if (!n) { - free(r); - free(w); - return NULL; + free(ret); + return -ENOMEM; } - memcpy(n, r, j); + memcpy(n, ret, j); memcpy(n + j, w, k); - free(r); - free(w); + free(ret); - r = n; + ret = n; t = n + j + k; } else { *(t++) = '%'; @@ -101,47 +102,81 @@ char *specifier_printf(const char *text, const Specifier table[], void *userdata } *t = 0; - return r; + *_ret = ret; + return 0; } /* Generic handler for simple string replacements */ -char* specifier_string(char specifier, void *data, void *userdata) { - return strdup(strempty(data)); +int specifier_string(char specifier, void *data, void *userdata, char **ret) { + char *n; + + n = strdup(strempty(data)); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -char *specifier_machine_id(char specifier, void *data, void *userdata) { +int specifier_machine_id(char specifier, void *data, void *userdata, char **ret) { sd_id128_t id; - char *buf; + char *n; int r; r = sd_id128_get_machine(&id); if (r < 0) - return NULL; + return r; - buf = new(char, 33); - if (!buf) - return NULL; + n = new(char, 33); + if (!n) + return -ENOMEM; - return sd_id128_to_string(id, buf); + *ret = sd_id128_to_string(id, n); + return 0; } -char *specifier_boot_id(char specifier, void *data, void *userdata) { +int specifier_boot_id(char specifier, void *data, void *userdata, char **ret) { sd_id128_t id; - char *buf; + char *n; int r; r = sd_id128_get_boot(&id); if (r < 0) - return NULL; + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} - buf = new(char, 33); - if (!buf) - return NULL; +int specifier_host_name(char specifier, void *data, void *userdata, char **ret) { + char *n; - return sd_id128_to_string(id, buf); + n = gethostname_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } -char *specifier_host_name(char specifier, void *data, void *userdata) { - return gethostname_malloc(); +int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret) { + struct utsname uts; + char *n; + int r; + + r = uname(&uts); + if (r < 0) + return -errno; + + n = strdup(uts.release); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; } diff --git a/src/shared/specifier.h b/src/shared/specifier.h index 0440dcac48..fca206f665 100644 --- a/src/shared/specifier.h +++ b/src/shared/specifier.h @@ -21,7 +21,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -typedef char* (*SpecifierCallback)(char specifier, void *data, void *userdata); +typedef int (*SpecifierCallback)(char specifier, void *data, void *userdata, char **ret); typedef struct Specifier { const char specifier; @@ -29,10 +29,11 @@ typedef struct Specifier { void *data; } Specifier; -char *specifier_printf(const char *text, const Specifier table[], void *userdata); +int specifier_printf(const char *text, const Specifier table[], void *userdata, char **ret); -char *specifier_string(char specifier, void *data, void *userdata); +int specifier_string(char specifier, void *data, void *userdata, char **ret); -char *specifier_machine_id(char specifier, void *data, void *userdata); -char *specifier_boot_id(char specifier, void *data, void *userdata); -char *specifier_host_name(char specifier, void *data, void *userdata); +int specifier_machine_id(char specifier, void *data, void *userdata, char **ret); +int specifier_boot_id(char specifier, void *data, void *userdata, char **ret); +int specifier_host_name(char specifier, void *data, void *userdata, char **ret); +int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret); diff --git a/src/shared/strv.c b/src/shared/strv.c index a5ce7e9593..adeee282b7 100644 --- a/src/shared/strv.c +++ b/src/shared/strv.c @@ -356,6 +356,43 @@ char *strv_join(char **l, const char *separator) { return r; } +char *strv_join_quoted(char **l) { + char *buf = NULL; + char **s; + size_t allocated = 0, len = 0; + + STRV_FOREACH(s, l) { + /* assuming here that escaped string cannot be more + * than twice as long, and reserving space for the + * separator and quotes. + */ + _cleanup_free_ char *esc = NULL; + size_t needed; + + if (!GREEDY_REALLOC(buf, allocated, + len + strlen(*s) * 2 + 3)) + goto oom; + + esc = cescape(*s); + if (!esc) + goto oom; + + needed = snprintf(buf + len, allocated - len, "%s\"%s\"", + len > 0 ? " " : "", esc); + assert(needed < allocated - len); + len += needed; + } + + if (!buf) + buf = malloc0(1); + + return buf; + + oom: + free(buf); + return NULL; +} + char **strv_append(char **l, const char *s) { char **r, **k; diff --git a/src/shared/strv.h b/src/shared/strv.h index e35118752f..d1f2a0ef32 100644 --- a/src/shared/strv.h +++ b/src/shared/strv.h @@ -67,6 +67,7 @@ char **strv_split_quoted(const char *s); char **strv_split_newlines(const char *s); char *strv_join(char **l, const char *separator); +char *strv_join_quoted(char **l); char **strv_parse_nulstr(const char *s, size_t l); char **strv_split_nulstr(const char *s); diff --git a/src/shared/test-tables.h b/src/shared/test-tables.h new file mode 100644 index 0000000000..3261302077 --- /dev/null +++ b/src/shared/test-tables.h @@ -0,0 +1,51 @@ +/*** + 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 <stdio.h> +#include <stdlib.h> + +typedef const char* (*lookup_t)(int); +typedef int (*reverse_t)(const char*); + +static inline void _test_table(const char *name, + lookup_t lookup, + reverse_t reverse, + int size, + bool sparse) { + int i; + + for (i = -1; i < size + 1; i++) { + const char* val = lookup(i); + int rev; + + if (val) + rev = reverse(val); + else + rev = reverse("--no-such--value----"); + + printf("%s: %d → %s → %d\n", name, i, val, rev); + if (i >= 0 && i < size ? + sparse ? rev != i && rev != -1 : val == NULL || rev != i : + val != NULL || rev != -1) + exit(EXIT_FAILURE); + } +} + +#define test_table(lower, upper) \ + _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false) diff --git a/src/shared/time-util.c b/src/shared/time-util.c index 9ee711a49e..860be61e8b 100644 --- a/src/shared/time-util.c +++ b/src/shared/time-util.c @@ -168,6 +168,28 @@ char *format_timestamp(char *buf, size_t l, usec_t t) { return buf; } +char *format_timestamp_us(char *buf, size_t l, usec_t t) { + struct tm tm; + time_t sec; + + assert(buf); + assert(l > 0); + + if (t <= 0) + return NULL; + + sec = (time_t) (t / USEC_PER_SEC); + localtime_r(&sec, &tm); + + if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm) <= 0) + return NULL; + snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", t % USEC_PER_SEC); + if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) + return NULL; + + return buf; +} + char *format_timestamp_relative(char *buf, size_t l, usec_t t) { usec_t n, d; diff --git a/src/shared/time-util.h b/src/shared/time-util.h index f27a006891..7660fe1872 100644 --- a/src/shared/time-util.h +++ b/src/shared/time-util.h @@ -73,6 +73,7 @@ usec_t timeval_load(const struct timeval *tv) _pure_; struct timeval *timeval_store(struct timeval *tv, usec_t u); char *format_timestamp(char *buf, size_t l, usec_t t); +char *format_timestamp_us(char *buf, size_t l, usec_t t); char *format_timestamp_relative(char *buf, size_t l, usec_t t); char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); diff --git a/src/shared/unit-name.c b/src/shared/unit-name.c index a809713595..bc8094d112 100644 --- a/src/shared/unit-name.c +++ b/src/shared/unit-name.c @@ -26,11 +26,10 @@ #include "path-util.h" #include "util.h" #include "unit-name.h" +#include "def.h" #define VALID_CHARS \ - "0123456789" \ - "abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + DIGITS LETTERS \ ":-_.\\" static const char* const unit_type_table[_UNIT_TYPE_MAX] = { @@ -44,6 +43,8 @@ static const char* const unit_type_table[_UNIT_TYPE_MAX] = { [UNIT_TIMER] = "timer", [UNIT_SWAP] = "swap", [UNIT_PATH] = "path", + [UNIT_SLICE] = "slice", + [UNIT_SCOPE] = "scope" }; DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); @@ -51,6 +52,7 @@ DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { [UNIT_STUB] = "stub", [UNIT_LOADED] = "loaded", + [UNIT_NOT_FOUND] = "not-found", [UNIT_ERROR] = "error", [UNIT_MERGED] = "merged", [UNIT_MASKED] = "masked" @@ -184,6 +186,7 @@ char *unit_name_change_suffix(const char *n, const char *suffix) { assert(n); assert(unit_name_is_valid(n, true)); assert(suffix); + assert(suffix[0] == '.'); assert_se(e = strrchr(n, '.')); a = e - n; @@ -298,7 +301,7 @@ char *unit_name_path_escape(const char *f) { path_kill_slashes(p); - if (streq(p, "/")) { + if (streq(p, "/") || streq(p, "")) { free(p); return strdup("-"); } @@ -401,7 +404,6 @@ char *unit_name_template(const char *f) { strcpy(mempcpy(r, f, a), e); return r; - } char *unit_name_from_path(const char *path, const char *suffix) { @@ -453,7 +455,7 @@ char *unit_name_to_path(const char *name) { } char *unit_dbus_path_from_name(const char *name) { - char *e, *p; + _cleanup_free_ char *e = NULL; assert(name); @@ -461,10 +463,23 @@ char *unit_dbus_path_from_name(const char *name) { if (!e) return NULL; - p = strappend("/org/freedesktop/systemd1/unit/", e); - free(e); + return strappend("/org/freedesktop/systemd1/unit/", e); +} + +int unit_name_from_dbus_path(const char *path, char **name) { + const char *e; + char *n; + + e = startswith(path, "/org/freedesktop/systemd1/unit/"); + if (!e) + return -EINVAL; - return p; + n = bus_path_unescape(e); + if (!n) + return -ENOMEM; + + *name = n; + return 0; } char *unit_name_mangle(const char *name) { @@ -506,16 +521,18 @@ char *unit_name_mangle(const char *name) { return r; } -char *snapshot_name_mangle(const char *name) { +char *unit_name_mangle_with_suffix(const char *name, const char *suffix) { char *r, *t; const char *f; assert(name); + assert(suffix); + assert(suffix[0] == '.'); /* Similar to unit_name_mangle(), but is called when we know * that this is about snapshot units. */ - r = new(char, strlen(name) * 4 + 1 + sizeof(".snapshot")-1); + r = new(char, strlen(name) * 4 + strlen(suffix) + 1); if (!r) return NULL; @@ -528,8 +545,8 @@ char *snapshot_name_mangle(const char *name) { *(t++) = *f; } - if (!endswith(name, ".snapshot")) - strcpy(t, ".snapshot"); + if (!endswith(name, suffix)) + strcpy(t, suffix); else *t = 0; @@ -547,3 +564,30 @@ UnitType unit_name_to_type(const char *n) { return unit_type_from_string(e + 1); } + +int build_subslice(const char *slice, const char*name, char **subslice) { + char *ret; + + assert(slice); + assert(name); + assert(subslice); + + if (streq(slice, "-.slice")) + ret = strappend(name, ".slice"); + else { + char *e; + + e = endswith(slice, ".slice"); + if (!e) + return -EINVAL; + + ret = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); + if (!ret) + return -ENOMEM; + + stpcpy(stpcpy(stpcpy(mempcpy(ret, slice, e - slice), "-"), name), ".slice"); + } + + *subslice = ret; + return 0; +} diff --git a/src/shared/unit-name.h b/src/shared/unit-name.h index 9eca8eb3c1..20138df089 100644 --- a/src/shared/unit-name.h +++ b/src/shared/unit-name.h @@ -41,6 +41,8 @@ enum UnitType { UNIT_TIMER, UNIT_SWAP, UNIT_PATH, + UNIT_SLICE, + UNIT_SCOPE, _UNIT_TYPE_MAX, _UNIT_TYPE_INVALID = -1 }; @@ -48,6 +50,7 @@ enum UnitType { enum UnitLoadState { UNIT_STUB = 0, UNIT_LOADED, + UNIT_NOT_FOUND, UNIT_ERROR, UNIT_MERGED, UNIT_MASKED, @@ -92,6 +95,9 @@ char *unit_name_from_path_instance(const char *prefix, const char *path, const c char *unit_name_to_path(const char *name); char *unit_dbus_path_from_name(const char *name); +int unit_name_from_dbus_path(const char *path, char **name); char *unit_name_mangle(const char *name); -char *snapshot_name_mangle(const char *name); +char *unit_name_mangle_with_suffix(const char *name, const char *suffix); + +int build_subslice(const char *slice, const char*name, char **subslice); diff --git a/src/shared/utf8.c b/src/shared/utf8.c index 3964e8b1ce..a8e28accd3 100644 --- a/src/shared/utf8.c +++ b/src/shared/utf8.c @@ -3,6 +3,7 @@ /*** This file is part of systemd. + Copyright 2008-2011 Kay Sievers Copyright 2012 Lennart Poettering systemd is free software; you can redistribute it and/or modify it @@ -19,7 +20,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -/* This file is based on the GLIB utf8 validation functions. The +/* Parts of this file are based on the GLIB utf8 validation functions. The * original license text follows. */ /* gutf8.c - Operations on UTF-8 strings. @@ -51,8 +52,6 @@ #include "utf8.h" #include "util.h" -#define FILTER_CHAR '_' - static inline bool is_unicode_valid(uint32_t ch) { if (ch >= 0x110000) /* End of unicode space */ @@ -67,17 +66,6 @@ static inline bool is_unicode_valid(uint32_t ch) { return true; } -static inline bool is_continuation_char(uint8_t ch) { - if ((ch & 0xc0) != 0x80) /* 10xxxxxx */ - return false; - return true; -} - -static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) { - *u_ch <<= 6; - *u_ch |= ch & 0x3f; -} - static bool is_unicode_control(uint32_t ch) { /* @@ -86,170 +74,101 @@ static bool is_unicode_control(uint32_t ch) { '\t' is in C0 range, but more or less harmless and commonly used. */ - return (ch < ' ' && ch != '\t') || + return (ch < ' ' && ch != '\t' && ch != '\n') || (0x7F <= ch && ch <= 0x9F); } -char* utf8_is_printable_n(const char* str, size_t length) { - uint32_t val = 0; - uint32_t min = 0; - const uint8_t *p; - - assert(str); - - for (p = (const uint8_t*) str; length; p++, length--) { - if (*p < 128) { - val = *p; - } else { - if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ - min = 128; - val = (uint32_t) (*p & 0x1e); - goto ONE_REMAINING; - } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ - min = (1 << 11); - val = (uint32_t) (*p & 0x0f); - goto TWO_REMAINING; - } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ - min = (1 << 16); - val = (uint32_t) (*p & 0x07); - } else - goto error; - - p++; - length--; - if (!length || !is_continuation_char(*p)) - goto error; - merge_continuation_char(&val, *p); - - TWO_REMAINING: - p++; - length--; - if (!is_continuation_char(*p)) - goto error; - merge_continuation_char(&val, *p); - - ONE_REMAINING: - p++; - length--; - if (!is_continuation_char(*p)) - goto error; - merge_continuation_char(&val, *p); - - if (val < min) - goto error; - } +/* count of characters used to encode one unicode char */ +static int utf8_encoded_expected_len(const char *str) { + unsigned char c = (unsigned char)str[0]; + + if (c < 0x80) + return 1; + if ((c & 0xe0) == 0xc0) + return 2; + if ((c & 0xf0) == 0xe0) + return 3; + if ((c & 0xf8) == 0xf0) + return 4; + if ((c & 0xfc) == 0xf8) + return 5; + if ((c & 0xfe) == 0xfc) + return 6; + return 0; +} - if (is_unicode_control(val)) - goto error; +/* decode one unicode char */ +static int utf8_encoded_to_unichar(const char *str) { + int unichar; + int len; + int i; + + len = utf8_encoded_expected_len(str); + switch (len) { + case 1: + return (int)str[0]; + case 2: + unichar = str[0] & 0x1f; + break; + case 3: + unichar = (int)str[0] & 0x0f; + break; + case 4: + unichar = (int)str[0] & 0x07; + break; + case 5: + unichar = (int)str[0] & 0x03; + break; + case 6: + unichar = (int)str[0] & 0x01; + break; + default: + return -1; } - return (char*) str; + for (i = 1; i < len; i++) { + if (((int)str[i] & 0xc0) != 0x80) + return -1; + unichar <<= 6; + unichar |= (int)str[i] & 0x3f; + } -error: - return NULL; + return unichar; } -static char* utf8_validate(const char *str, char *output) { - uint32_t val = 0; - uint32_t min = 0; - const uint8_t *p, *last; - int size; - uint8_t *o; +bool utf8_is_printable(const char* str, size_t length) { + const uint8_t *p; assert(str); - o = (uint8_t*) output; - for (p = (const uint8_t*) str; *p; p++) { - if (*p < 128) { - if (o) - *o = *p; - } else { - last = p; - - if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ - size = 2; - min = 128; - val = (uint32_t) (*p & 0x1e); - goto ONE_REMAINING; - } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ - size = 3; - min = (1 << 11); - val = (uint32_t) (*p & 0x0f); - goto TWO_REMAINING; - } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ - size = 4; - min = (1 << 16); - val = (uint32_t) (*p & 0x07); - } else - goto error; - - p++; - if (!is_continuation_char(*p)) - goto error; - merge_continuation_char(&val, *p); - - TWO_REMAINING: - p++; - if (!is_continuation_char(*p)) - goto error; - merge_continuation_char(&val, *p); - - ONE_REMAINING: - p++; - if (!is_continuation_char(*p)) - goto error; - merge_continuation_char(&val, *p); - - if (val < min) - goto error; - - if (!is_unicode_valid(val)) - goto error; - - if (o) { - memcpy(o, last, (size_t) size); - o += size; - } - - continue; - - error: - if (o) { - *o = FILTER_CHAR; - p = last; /* We retry at the next character */ - } else - goto failure; - } + for (p = (const uint8_t*) str; length; p++) { + int encoded_len = utf8_encoded_valid_unichar((const char *)p); + int32_t val = utf8_encoded_to_unichar((const char*)p); - if (o) - o++; - } + if (encoded_len < 0 || val < 0 || is_unicode_control(val)) + return false; - if (o) { - *o = '\0'; - return output; + length -= encoded_len; } - return (char*) str; - -failure: - return NULL; -} - -char* utf8_is_valid (const char *str) { - return utf8_validate(str, NULL); + return true; } -char* utf8_filter (const char *str) { - char *new_str; +const char *utf8_is_valid(const char *str) { + const uint8_t *p; assert(str); - new_str = malloc(strlen(str) + 1); - if (!new_str) - return NULL; + for (p = (const uint8_t*) str; *p; ) { + int len = utf8_encoded_valid_unichar((const char *)p); - return utf8_validate(str, new_str); + if (len < 0) + return NULL; + + p += len; + } + + return str; } char *ascii_is_valid(const char *str) { @@ -320,3 +239,50 @@ char *utf16_to_utf8(const void *s, size_t length) { return r; } + +/* expected size used to encode one unicode char */ +static int utf8_unichar_to_encoded_len(int unichar) { + if (unichar < 0x80) + return 1; + if (unichar < 0x800) + return 2; + if (unichar < 0x10000) + return 3; + if (unichar < 0x200000) + return 4; + if (unichar < 0x4000000) + return 5; + return 6; +} + +/* validate one encoded unicode char and return its length */ +int utf8_encoded_valid_unichar(const char *str) { + int len; + int unichar; + int i; + + len = utf8_encoded_expected_len(str); + if (len == 0) + return -1; + + /* ascii is valid */ + if (len == 1) + return 1; + + /* check if expected encoded chars are available */ + for (i = 0; i < len; i++) + if ((str[i] & 0x80) != 0x80) + return -1; + + unichar = utf8_encoded_to_unichar(str); + + /* check if encoded length matches encoded value */ + if (utf8_unichar_to_encoded_len(unichar) != len) + return -1; + + /* check if value has valid range */ + if (!is_unicode_valid(unichar)) + return -1; + + return len; +} diff --git a/src/shared/utf8.h b/src/shared/utf8.h index 794ae15ab9..96a03ea7cb 100644 --- a/src/shared/utf8.h +++ b/src/shared/utf8.h @@ -21,14 +21,17 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <stdbool.h> + #include "macro.h" -char *utf8_is_valid(const char *s) _pure_; +const char *utf8_is_valid(const char *s) _pure_; char *ascii_is_valid(const char *s) _pure_; -char *utf8_is_printable_n(const char* str, size_t length) _pure_; +bool utf8_is_printable(const char* str, size_t length) _pure_; -char *utf8_filter(const char *s); char *ascii_filter(const char *s); char *utf16_to_utf8(const void *s, size_t length); + +int utf8_encoded_valid_unichar(const char *str); diff --git a/src/shared/util.c b/src/shared/util.c index 673e0da6b6..9be6acfc8f 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -73,6 +73,7 @@ #include "hashmap.h" #include "env-util.h" #include "fileio.h" +#include "device-nodes.h" int saved_argc = 0; char **saved_argv = NULL; @@ -128,40 +129,6 @@ char* endswith(const char *s, const char *postfix) { return (char*) s + sl - pl; } -char* startswith(const char *s, const char *prefix) { - const char *a, *b; - - assert(s); - assert(prefix); - - a = s, b = prefix; - for (;;) { - if (*b == 0) - return (char*) a; - if (*a != *b) - return NULL; - - a++, b++; - } -} - -char* startswith_no_case(const char *s, const char *prefix) { - const char *a, *b; - - assert(s); - assert(prefix); - - a = s, b = prefix; - for (;;) { - if (*b == 0) - return (char*) a; - if (tolower(*a) != tolower(*b)) - return NULL; - - a++, b++; - } -} - bool first_word(const char *s, const char *word) { size_t sl, wl; @@ -367,7 +334,7 @@ int safe_atolli(const char *s, long long int *ret_lli) { int safe_atod(const char *s, double *ret_d) { char *x = NULL; - double d; + double d = 0; assert(s); assert(ret_d); @@ -726,9 +693,24 @@ int is_kernel_thread(pid_t pid) { return 0; } +int get_process_capeff(pid_t pid, char **capeff) { + const char *p; + + assert(capeff); + assert(pid >= 0); + + if (pid == 0) + p = "/proc/self/status"; + else + p = procfs_file_alloca(pid, "status"); + + return get_status_field(p, "\nCapEff:", capeff); +} int get_process_exe(pid_t pid, char **name) { const char *p; + char *d; + int r; assert(pid >= 0); assert(name); @@ -738,7 +720,15 @@ int get_process_exe(pid_t pid, char **name) { else p = procfs_file_alloca(pid, "exe"); - return readlink_malloc(p, name); + r = readlink_malloc(p, name); + if (r < 0) + return r; + + d = endswith(*name, " (deleted)"); + if (d) + *d = '\0'; + + return 0; } static int get_process_id(pid_t pid, const char *field, uid_t *uid) { @@ -850,18 +840,18 @@ int readlink_malloc(const char *p, char **r) { } int readlink_and_make_absolute(const char *p, char **r) { - char *target, *k; + _cleanup_free_ char *target = NULL; + char *k; int j; assert(p); assert(r); - if ((j = readlink_malloc(p, &target)) < 0) + j = readlink_malloc(p, &target); + if (j < 0) return j; k = file_in_same_dir(p, target); - free(target); - if (!k) return -ENOMEM; @@ -1600,6 +1590,7 @@ bool fstype_is_network(const char *fstype) { "cifs\0" "smbfs\0" "ncpfs\0" + "ncp\0" "nfs\0" "nfs4\0" "gfs\0" @@ -1830,8 +1821,10 @@ int open_terminal(const char *name, int mode) { * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 */ + assert(!(mode & O_CREAT)); + for (;;) { - fd = open(name, mode); + fd = open(name, mode, 0); if (fd >= 0) break; @@ -2193,8 +2186,10 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { return n > 0 ? n : -errno; } - if (pollfd.revents != POLLIN) - return n > 0 ? n : -EIO; + /* We knowingly ignore the revents value here, + * and expect that any error/EOF is reported + * via read()/write() + */ continue; } @@ -2241,8 +2236,10 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { return n > 0 ? n : -errno; } - if (pollfd.revents != POLLOUT) - return n > 0 ? n : -EIO; + /* We knowingly ignore the revents value here, + * and expect that any error/EOF is reported + * via read()/write() + */ continue; } @@ -2261,7 +2258,7 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { int parse_bytes(const char *t, off_t *bytes) { static const struct { const char *suffix; - off_t factor; + unsigned long long factor; } table[] = { { "B", 1 }, { "K", 1024ULL }, @@ -2274,7 +2271,7 @@ int parse_bytes(const char *t, off_t *bytes) { }; const char *p; - off_t r = 0; + unsigned long long r = 0; assert(t); assert(bytes); @@ -2301,7 +2298,17 @@ int parse_bytes(const char *t, off_t *bytes) { for (i = 0; i < ELEMENTSOF(table); i++) if (startswith(e, table[i].suffix)) { - r += (off_t) l * table[i].factor; + unsigned long long tmp; + if ((unsigned long long) l > ULLONG_MAX / table[i].factor) + return -ERANGE; + tmp = l * table[i].factor; + if (tmp > ULLONG_MAX - r) + return -ERANGE; + + r += tmp; + if ((unsigned long long) (off_t) r != r) + return -ERANGE; + p = e + strlen(table[i].suffix); break; } @@ -2309,7 +2316,7 @@ int parse_bytes(const char *t, off_t *bytes) { if (i >= ELEMENTSOF(table)) return -EINVAL; - } while (*p != 0); + } while (*p); *bytes = r; @@ -2417,6 +2424,25 @@ fallback: return random() * RAND_MAX + random(); } +unsigned random_u(void) { + _cleanup_close_ int fd; + unsigned u; + ssize_t r; + + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + goto fallback; + + r = loop_read(fd, &u, sizeof(u), true); + if (r != sizeof(u)) + goto fallback; + + return u; + +fallback: + return random() * RAND_MAX + random(); +} + void rename_process(const char name[8]) { assert(name); @@ -2773,8 +2799,8 @@ int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct _pure_ static int is_temporary_fs(struct statfs *s) { assert(s); return - F_TYPE_CMP(s->f_type, TMPFS_MAGIC) || - F_TYPE_CMP(s->f_type, RAMFS_MAGIC); + F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || + F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { @@ -3275,7 +3301,7 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne r = new0(char, new_length+1); if (!r) - return r; + return NULL; x = (new_length * percent) / 100; @@ -3465,7 +3491,9 @@ DIR *xopendirat(int fd, const char *name, int flags) { int nfd; DIR *d; - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags); + assert(!(flags & O_CREAT)); + + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); if (nfd < 0) return NULL; @@ -3491,26 +3519,23 @@ int signal_from_string_try_harder(const char *s) { } static char *tag_to_udev_node(const char *tagvalue, const char *by) { - char *dn, *t, *u; - int r; - - /* FIXME: to follow udev's logic 100% we need to leave valid - * UTF8 chars unescaped */ + _cleanup_free_ char *t = NULL, *u = NULL; + char *dn; + size_t enc_len; u = unquote(tagvalue, "\"\'"); if (u == NULL) return NULL; - t = xescape(u, "/ "); - free(u); - + enc_len = strlen(u) * 4; + t = new(char, enc_len); if (t == NULL) return NULL; - r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t); - free(t); + if (encode_devnode_name(u, t, enc_len) < 0) + return NULL; - if (r < 0) + if (asprintf(&dn, "/dev/disk/by-%s/%s", by, t) < 0) return NULL; return dn; @@ -4343,7 +4368,7 @@ int in_group(const char *name) { int glob_exists(const char *path) { _cleanup_globfree_ glob_t g = {}; - int r, k; + int k; assert(path); @@ -4351,15 +4376,37 @@ int glob_exists(const char *path) { k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); if (k == GLOB_NOMATCH) - r = 0; + return 0; else if (k == GLOB_NOSPACE) - r = -ENOMEM; + return -ENOMEM; else if (k == 0) - r = !strv_isempty(g.gl_pathv); + return !strv_isempty(g.gl_pathv); else - r = errno ? -errno : -EIO; + return errno ? -errno : -EIO; +} - return r; +int glob_extend(char ***strv, const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + char **p; + + errno = 0; + k = glob(optarg, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + return -ENOENT; + else if (k == GLOB_NOSPACE) + return -ENOMEM; + else if (k != 0 || strv_isempty(g.gl_pathv)) + return errno ? -errno : -EIO; + + STRV_FOREACH(p, g.gl_pathv) { + k = strv_extend(strv, *p); + if (k < 0) + break; + } + + return k; } int dirent_ensure_type(DIR *d, struct dirent *de) { @@ -4388,38 +4435,31 @@ int dirent_ensure_type(DIR *d, struct dirent *de) { } int in_search_path(const char *path, char **search) { - char **i, *parent; + char **i; + _cleanup_free_ char *parent = NULL; int r; r = path_get_parent(path, &parent); if (r < 0) return r; - r = 0; - - STRV_FOREACH(i, search) { - if (path_equal(parent, *i)) { - r = 1; - break; - } - } - - free(parent); + STRV_FOREACH(i, search) + if (path_equal(parent, *i)) + return 1; - return r; + return 0; } int get_files_in_directory(const char *path, char ***list) { - DIR *d; - int r = 0; - unsigned n = 0; - char **l = NULL; + _cleanup_closedir_ DIR *d = NULL; + size_t bufsize = 0, n = 0; + _cleanup_strv_free_ char **l = NULL; assert(path); /* Returns all files in a directory in *list, and the number * of files as return value. If list is NULL returns only the - * number */ + * number. */ d = opendir(path); if (!d) @@ -4431,11 +4471,9 @@ int get_files_in_directory(const char *path, char ***list) { int k; k = readdir_r(d, &buf.de, &de); - if (k != 0) { - r = -k; - goto finish; - } - + assert(k >= 0); + if (k > 0) + return -k; if (!de) break; @@ -4445,43 +4483,25 @@ int get_files_in_directory(const char *path, char ***list) { continue; if (list) { - if ((unsigned) r >= n) { - char **t; - - n = MAX(16, 2*r); - t = realloc(l, sizeof(char*) * n); - if (!t) { - r = -ENOMEM; - goto finish; - } - - l = t; - } - - assert((unsigned) r < n); + /* one extra slot is needed for the terminating NULL */ + if (!GREEDY_REALLOC(l, bufsize, n + 2)) + return -ENOMEM; - l[r] = strdup(de->d_name); - if (!l[r]) { - r = -ENOMEM; - goto finish; - } + l[n] = strdup(de->d_name); + if (!l[n]) + return -ENOMEM; - l[++r] = NULL; + l[++n] = NULL; } else - r++; + n++; } -finish: - if (d) - closedir(d); - - if (r >= 0) { - if (list) - *list = l; - } else - strv_free(l); + if (list) { + *list = l; + l = NULL; /* avoid freeing */ + } - return r; + return n; } char *strjoin(const char *x, ...) { @@ -5264,13 +5284,17 @@ bool string_is_safe(const char *p) { return true; } +/** + * Check if a string contains control characters. + * Spaces and tabs are not considered control characters. + */ bool string_has_cc(const char *p) { const char *t; assert(p); for (t = p; *t; t++) - if (*t > 0 && *t < ' ') + if (*t > 0 && *t < ' ' && *t != '\t') return true; return false; @@ -5343,20 +5367,24 @@ bool is_locale_utf8(void) { goto out; } - /* For LC_CTYPE=="C" return true, - * because CTYPE is effectly unset and - * everything defaults to UTF-8 nowadays. */ - + /* For LC_CTYPE=="C" return true, because CTYPE is effectly + * unset and everything can do to UTF-8 nowadays. */ set = setlocale(LC_CTYPE, NULL); if (!set) { cached_answer = true; goto out; } - cached_answer = streq(set, "C"); + /* Check result, but ignore the result if C was set + * explicitly. */ + cached_answer = + streq(set, "C") && + !getenv("LC_ALL") && + !getenv("LC_CTYPE") && + !getenv("LANG"); out: - return (bool)cached_answer; + return (bool) cached_answer; } const char *draw_special_char(DrawSpecialChar ch) { @@ -5671,7 +5699,7 @@ int search_and_fopen_nulstr(const char *path, const char *mode, const char *sear int create_tmp_dir(char template[], char** dir_name) { int r = 0; - char *d, *dt; + char *d = NULL, *dt; assert(dir_name); @@ -5847,3 +5875,48 @@ bool id128_is_valid(const char *s) { return true; } + +void parse_user_at_host(char *arg, char **user, char **host) { + assert(arg); + assert(user); + assert(host); + + *host = strchr(arg, '@'); + if (*host == NULL) + *host = arg; + else { + *host[0]++ = '\0'; + *user = arg; + } +} + +int split_pair(const char *s, const char *sep, char **l, char **r) { + char *x, *a, *b; + + assert(s); + assert(sep); + assert(l); + assert(r); + + if (isempty(sep)) + return -EINVAL; + + x = strstr(s, sep); + if (!x) + return -EINVAL; + + a = strndup(s, x - s); + if (!a) + return -ENOMEM; + + b = strdup(x + strlen(sep)); + if (!b) { + free(a); + return -ENOMEM; + } + + *l = a; + *r = b; + + return 0; +} diff --git a/src/shared/util.h b/src/shared/util.h index 64e63b8c07..1b845b3803 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -106,9 +106,19 @@ static inline bool isempty(const char *p) { return !p || !p[0]; } +static inline const char *startswith(const char *s, const char *prefix) { + if (strncmp(s, prefix, strlen(prefix)) == 0) + return s + strlen(prefix); + return NULL; +} + +static inline const char *startswith_no_case(const char *s, const char *prefix) { + if (strncasecmp(s, prefix, strlen(prefix)) == 0) + return s + strlen(prefix); + return NULL; +} + char *endswith(const char *s, const char *postfix) _pure_; -char *startswith(const char *s, const char *prefix) _pure_; -char *startswith_no_case(const char *s, const char *prefix) _pure_; bool first_word(const char *s, const char *word) _pure_; @@ -210,6 +220,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * int get_process_exe(pid_t pid, char **name); int get_process_uid(pid_t pid, uid_t *uid); int get_process_gid(pid_t pid, gid_t *gid); +int get_process_capeff(pid_t pid, char **capeff); char hexchar(int x) _const_; int unhexchar(char c) _const_; @@ -242,6 +253,7 @@ int make_null_stdio(void); int make_console_stdio(void); unsigned long long random_ull(void); +unsigned random_u(void); /* For basic lookup tables with strictly enumerated entries */ #define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ @@ -373,6 +385,22 @@ void columns_lines_cache_reset(int _unused_ signum); bool on_tty(void); +static inline const char *ansi_highlight(void) { + return on_tty() ? ANSI_HIGHLIGHT_ON : ""; +} + +static inline const char *ansi_highlight_red(void) { + return on_tty() ? ANSI_HIGHLIGHT_RED_ON : ""; +} + +static inline const char *ansi_highlight_green(void) { + return on_tty() ? ANSI_HIGHLIGHT_GREEN_ON : ""; +} + +static inline const char *ansi_highlight_off(void) { + return on_tty() ? ANSI_HIGHLIGHT_OFF : ""; +} + int running_in_chroot(void); char *ellipsize(const char *s, size_t length, unsigned percent); @@ -439,6 +467,7 @@ char* uid_to_name(uid_t uid); char* gid_to_name(gid_t gid); int glob_exists(const char *path); +int glob_extend(char ***strv, const char *path); int dirent_ensure_type(DIR *d, struct dirent *de); @@ -732,3 +761,6 @@ static inline void _reset_locale_(struct _locale_struct_ *s) { _saved_locale_.quit = true) bool id128_is_valid(const char *s) _pure_; +void parse_user_at_host(char *arg, char **user, char **host); + +int split_pair(const char *s, const char *sep, char **l, char **r); diff --git a/src/shared/virt.c b/src/shared/virt.c index 1c86a3dd1e..4f8134a773 100644 --- a/src/shared/virt.c +++ b/src/shared/virt.c @@ -29,6 +29,8 @@ /* Returns a short identifier for the various VM implementations */ int detect_vm(const char **id) { + _cleanup_free_ char *cpuinfo_contents = NULL; + int r; #if defined(__i386__) || defined(__x86_64__) @@ -67,7 +69,6 @@ int detect_vm(const char **id) { const char *j, *k; bool hypervisor; _cleanup_free_ char *hvtype = NULL; - int r; /* Try high-level hypervisor sysfs file first: * @@ -164,6 +165,16 @@ int detect_vm(const char **id) { } #endif + + /* Detect User-Mode Linux by reading /proc/cpuinfo */ + r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL); + if (r < 0) + return r; + if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) { + *id = "uml"; + return 1; + } + return 0; } diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index a5bdb03416..ab1a43ab1a 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = sd_bus_set_negotiate_fds(a, is_unix); + r = sd_bus_negotiate_fds(a, is_unix); if (r < 0) { log_error("Failed to set FD negotiation: %s", strerror(-r)); goto finish; @@ -104,7 +104,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = sd_bus_set_negotiate_fds(b, is_unix); + r = sd_bus_negotiate_fds(b, is_unix); if (r < 0) { log_error("Failed to set FD negotiation: %s", strerror(-r)); goto finish; diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index db18dd9f6e..b5670dbb86 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -135,6 +135,7 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno log_debug("parse: %s\n", path); while (!feof(f)) { char l[LINE_MAX], *p, *value, *new_value, *property, *existing; + void *v; int k; if (!fgets(l, sizeof(l), f)) { @@ -167,13 +168,14 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno p = normalize_sysctl(strstrip(p)); value = strstrip(value); - existing = hashmap_get(sysctl_options, p); + existing = hashmap_get2(sysctl_options, p, &v); if (existing) { - if (!streq(value, existing)) - log_warning("Duplicate assignment of %s in file '%s', ignoring.", - p, path); + if (streq(value, existing)) + continue; - continue; + log_info("Overwriting earlier assignment of %s in file '%s'.", p, path); + free(hashmap_remove(sysctl_options, p)); + free(v); } property = strdup(p); @@ -188,7 +190,7 @@ static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_eno k = hashmap_put(sysctl_options, property, new_value); if (k < 0) { - log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-r)); + log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-k)); free(property); free(new_value); return k; @@ -304,8 +306,6 @@ int main(int argc, char *argv[]) { goto finish; } - r = parse_file(sysctl_options, "/etc/sysctl.conf", true); - STRV_FOREACH(f, files) { k = parse_file(sysctl_options, *f, true); if (k < 0 && r == 0) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 3cca861cf6..bb7ada9f32 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -69,9 +69,10 @@ #include "fileio.h" static char **arg_types = NULL; -static char **arg_load_states = NULL; +static char **arg_states = NULL; static char **arg_properties = NULL; static bool arg_all = false; +static bool original_stdout_is_tty; static enum dependency { DEPENDENCY_FORWARD, DEPENDENCY_REVERSE, @@ -93,7 +94,6 @@ static bool arg_quiet = false; static bool arg_full = false; static int arg_force = 0; static bool arg_ask_password = true; -static bool arg_failed = false; static bool arg_runtime = false; static char **arg_wall = NULL; static const char *arg_kill_who = NULL; @@ -129,7 +129,8 @@ static enum transport { TRANSPORT_SSH, TRANSPORT_POLKIT } arg_transport = TRANSPORT_NORMAL; -static const char *arg_host = NULL; +static char *arg_host = NULL; +static char *arg_user = NULL; static unsigned arg_lines = 10; static OutputMode arg_output = OUTPUT_SHORT; static bool arg_plain = false; @@ -175,30 +176,6 @@ static void polkit_agent_open_if_enabled(void) { } #endif -static const char *ansi_highlight(bool b) { - - if (!on_tty()) - return ""; - - return b ? ANSI_HIGHLIGHT_ON : ANSI_HIGHLIGHT_OFF; -} - -static const char *ansi_highlight_red(bool b) { - - if (!on_tty()) - return ""; - - return b ? ANSI_HIGHLIGHT_RED_ON : ANSI_HIGHLIGHT_OFF; -} - -static const char *ansi_highlight_green(bool b) { - - if (!on_tty()) - return ""; - - return b ? ANSI_HIGHLIGHT_GREEN_ON : ANSI_HIGHLIGHT_OFF; -} - static int translate_bus_error_to_exit_status(int r, const DBusError *error) { assert(error); @@ -300,12 +277,11 @@ static int compare_unit_info(const void *a, const void *b) { static bool output_show_unit(const struct unit_info *u) { const char *dot; - if (arg_failed) - return streq(u->active_state, "failed"); + if (!strv_isempty(arg_states)) + return strv_contains(arg_states, u->load_state) || strv_contains(arg_states, u->sub_state) || strv_contains(arg_states, u->active_state); return (!arg_types || ((dot = strrchr(u->id, '.')) && strv_find(arg_types, dot+1))) && - (!arg_load_states || strv_find(arg_load_states, u->load_state)) && (arg_all || !(streq(u->active_state, "inactive") || u->following[0]) || u->job_id > 0); } @@ -334,7 +310,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { } } - if (!arg_full) { + if (!arg_full && original_stdout_is_tty) { unsigned basic_len; id_len = MIN(max_id_len, 25u); basic_len = 5 + id_len + 5 + active_len + sub_len; @@ -380,15 +356,16 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { n_shown++; - if (streq(u->load_state, "error")) { - on_loaded = on = ansi_highlight_red(true); - off_loaded = off = ansi_highlight_red(false); + if (streq(u->load_state, "error") || + streq(u->load_state, "not-found")) { + on_loaded = on = ansi_highlight_red(); + off_loaded = off = ansi_highlight_off(); } else on_loaded = off_loaded = ""; if (streq(u->active_state, "failed")) { - on_active = on = ansi_highlight_red(true); - off_active = off = ansi_highlight_red(false); + on_active = on = ansi_highlight_red(); + off_active = off = ansi_highlight_off(); } else on_active = off_active = ""; @@ -400,7 +377,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { on_active, active_len, u->active_state, sub_len, u->sub_state, off_active, job_count ? job_len + 1 : 0, u->job_id ? u->job_type : ""); - if (!arg_full && arg_no_pager) + if (desc_len > 0) printf("%.*s\n", desc_len, u->description); else printf("%s\n", u->description); @@ -416,11 +393,11 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { if (job_count) printf("JOB = Pending job for the unit.\n"); puts(""); - on = ansi_highlight(true); - off = ansi_highlight(false); + on = ansi_highlight(); + off = ansi_highlight_off(); } else { - on = ansi_highlight_red(true); - off = ansi_highlight_red(false); + on = ansi_highlight_red(); + off = ansi_highlight_off(); } if (arg_all) @@ -434,8 +411,12 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { } } -static int get_unit_list(DBusConnection *bus, DBusMessage **reply, - struct unit_info **unit_infos, unsigned *c) { +static int get_unit_list( + DBusConnection *bus, + DBusMessage **reply, + struct unit_info **unit_infos, + unsigned *c) { + DBusMessageIter iter, sub; size_t size = 0; int r; @@ -497,9 +478,11 @@ static int list_units(DBusConnection *bus, char **args) { return 0; } -static int get_triggered_units(DBusConnection *bus, const char* unit_path, - char*** triggered) -{ +static int get_triggered_units( + DBusConnection *bus, + const char* unit_path, + char*** triggered) { + const char *interface = "org.freedesktop.systemd1.Unit", *triggers_property = "Triggers"; _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; @@ -655,11 +638,12 @@ static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { } if (cs) { - printf("%-*s %-*.*s%-*s %s\n", - pathlen, "LISTEN", - typelen + arg_show_types, typelen + arg_show_types, "TYPE ", - socklen, "UNIT", - "ACTIVATES"); + if (!arg_no_legend) + printf("%-*s %-*.*s%-*s %s\n", + pathlen, "LISTEN", + typelen + arg_show_types, typelen + arg_show_types, "TYPE ", + socklen, "UNIT", + "ACTIVATES"); for (s = socket_infos; s < socket_infos + cs; s++) { char **a; @@ -676,17 +660,20 @@ static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { printf("\n"); } - on = ansi_highlight(true); - off = ansi_highlight(false); - printf("\n"); + on = ansi_highlight(); + off = ansi_highlight_off(); + if (!arg_no_legend) + printf("\n"); } else { - on = ansi_highlight_red(true); - off = ansi_highlight_red(false); + on = ansi_highlight_red(); + off = ansi_highlight_off(); } - printf("%s%u sockets listed.%s\n", on, cs, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive sockets, too.\n"); + if (!arg_no_legend) { + printf("%s%u sockets listed.%s\n", on, cs, off); + if (!arg_all) + printf("Pass --all to see loaded but inactive sockets, too.\n"); + } return 0; } @@ -828,11 +815,11 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { u->state == UNIT_FILE_MASKED_RUNTIME || u->state == UNIT_FILE_DISABLED || u->state == UNIT_FILE_INVALID) { - on = ansi_highlight_red(true); - off = ansi_highlight_red(false); + on = ansi_highlight_red(); + off = ansi_highlight_off(); } else if (u->state == UNIT_FILE_ENABLED) { - on = ansi_highlight_green(true); - off = ansi_highlight_green(false); + on = ansi_highlight_green(); + off = ansi_highlight_off(); } else on = off = ""; @@ -1173,6 +1160,59 @@ static int list_dependencies(DBusConnection *bus, char **args) { return list_dependencies_one(bus, u, 0, &units, 0); } +static int get_default(DBusConnection *bus, char **args) { + char *path = NULL; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + int r; + _cleanup_dbus_error_free_ DBusError error; + + dbus_error_init(&error); + + if (!bus || avoid_bus()) { + r = unit_file_get_default(arg_scope, arg_root, &path); + + if (r < 0) { + log_error("Operation failed: %s", strerror(-r)); + goto finish; + } + + r = 0; + } else { + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetDefaultTarget", + &reply, + NULL, + DBUS_TYPE_INVALID); + + if (r < 0) { + log_error("Operation failed: %s", strerror(-r)); + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + dbus_error_free(&error); + return -EIO; + } + } + + if (path) + printf("%s\n", path); + +finish: + if ((!bus || avoid_bus()) && path) + free(path); + + return r; + +} + struct job_info { uint32_t id; char *name, *type, *state; @@ -1187,8 +1227,8 @@ static void list_jobs_print(struct job_info* jobs, size_t n) { assert(n == 0 || jobs); if (n == 0) { - on = ansi_highlight_green(true); - off = ansi_highlight_green(false); + on = ansi_highlight_green(); + off = ansi_highlight_off(); printf("%sNo jobs running.%s\n", on, off); return; @@ -1224,8 +1264,8 @@ static void list_jobs_print(struct job_info* jobs, size_t n) { _cleanup_free_ char *e = NULL; if (streq(j->state, "running")) { - on = ansi_highlight(true); - off = ansi_highlight(false); + on = ansi_highlight(); + off = ansi_highlight_off(); } else on = off = ""; @@ -1238,8 +1278,8 @@ static void list_jobs_print(struct job_info* jobs, size_t n) { } } - on = ansi_highlight(true); - off = ansi_highlight(false); + on = ansi_highlight(); + off = ansi_highlight_off(); if (on_tty()) printf("\n%s%zu jobs listed%s.\n", on, n, off); @@ -1325,36 +1365,6 @@ static int list_jobs(DBusConnection *bus, char **args) { return 0; } -static int load_unit(DBusConnection *bus, char **args) { - char **name; - - assert(args); - - STRV_FOREACH(name, args+1) { - _cleanup_free_ char *n = NULL; - int r; - - n = unit_name_mangle(*name); - if (!n) - return log_oom(); - - r = bus_method_call_with_reply( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LoadUnit", - NULL, - NULL, - DBUS_TYPE_STRING, &n, - DBUS_TYPE_INVALID); - if (r < 0) - return r; - } - - return 0; -} - static int cancel_job(DBusConnection *bus, char **args) { char **name; @@ -1390,8 +1400,9 @@ static int cancel_job(DBusConnection *bus, char **args) { return 0; } -static bool need_daemon_reload(DBusConnection *bus, const char *unit) { +static int need_daemon_reload(DBusConnection *bus, const char *unit) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + _cleanup_dbus_error_free_ DBusError error; dbus_bool_t b = FALSE; DBusMessageIter iter, sub; const char @@ -1401,6 +1412,8 @@ static bool need_daemon_reload(DBusConnection *bus, const char *unit) { _cleanup_free_ char *n = NULL; int r; + dbus_error_init(&error); + /* We ignore all errors here, since this is used to show a warning only */ n = unit_name_mangle(unit); @@ -1414,7 +1427,7 @@ static bool need_daemon_reload(DBusConnection *bus, const char *unit) { "org.freedesktop.systemd1.Manager", "GetUnit", &reply, - NULL, + &error, DBUS_TYPE_STRING, &n, DBUS_TYPE_INVALID); if (r < 0) @@ -1435,7 +1448,7 @@ static bool need_daemon_reload(DBusConnection *bus, const char *unit) { "org.freedesktop.DBus.Properties", "Get", &reply, - NULL, + &error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID); @@ -1483,6 +1496,7 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { uint32_t id; const char *path, *result, *unit; + char *r; if (dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &id, @@ -1491,7 +1505,11 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me DBUS_TYPE_STRING, &result, DBUS_TYPE_INVALID)) { - free(set_remove(d->set, (char*) path)); + r = set_remove(d->set, (char*) path); + if (!r) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + free(r); if (!isempty(result)) d->result = strdup(result); @@ -1501,7 +1519,7 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -#ifndef LEGACY +#ifndef NOLEGACY dbus_error_free(&error); if (dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &id, @@ -1511,7 +1529,11 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me /* Compatibility with older systemd versions < * 183 during upgrades. This should be dropped * one day. */ - free(set_remove(d->set, (char*) path)); + r = set_remove(d->set, (char*) path); + if (!r) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + free(r); if (*result) d->result = strdup(result); @@ -1855,9 +1877,9 @@ static int start_unit_one( return -EIO; } - if (need_daemon_reload(bus, n)) - log_warning("Warning: Unit file of %s changed on disk, 'systemctl %s daemon-reload' recommended.", - n, arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user"); + if (need_daemon_reload(bus, n) > 0) + log_warning("Warning: Unit file of %s changed on disk, 'systemctl %sdaemon-reload' recommended.", + n, arg_scope == UNIT_FILE_SYSTEM ? "" : "--user "); if (s) { char *p; @@ -2320,150 +2342,6 @@ static int kill_unit(DBusConnection *bus, char **args) { return 0; } -static int set_cgroup(DBusConnection *bus, char **args) { - _cleanup_free_ char *n = NULL; - const char *method, *runtime; - char **argument; - int r; - - assert(bus); - assert(args); - - method = - 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(); - - STRV_FOREACH(argument, args + 2) { - - 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; -} - -static int set_cgroup_attr(DBusConnection *bus, char **args) { - _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; - DBusError error; - DBusMessageIter iter; - _cleanup_free_ char *n = NULL; - const char *runtime; - int r; - - assert(bus); - assert(args); - - dbus_error_init(&error); - - 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", - "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_append_basic(&iter, DBUS_TYPE_STRING, &args[2])) - return log_oom(); - - r = bus_append_strv_iter(&iter, args + 3); - if (r < 0) - return log_oom(); - - 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; - } - - return 0; -} - -static int get_cgroup_attr(DBusConnection *bus, char **args) { - _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - _cleanup_free_ char *n = NULL; - char **argument; - int r; - - assert(bus); - assert(args); - - n = unit_name_mangle(args[1]); - if (!n) - return log_oom(); - - STRV_FOREACH(argument, args + 2) { - _cleanup_strv_free_ char **list = NULL; - DBusMessageIter iter; - char **a; - - 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; - - if (!dbus_message_iter_init(reply, &iter)) { - log_error("Failed to initialize iterator."); - return -EIO; - } - - 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); - } - } - - return 0; -} - typedef struct ExecStatusInfo { char *name; @@ -2581,7 +2459,7 @@ typedef struct UnitStatusInfo { const char *fragment_path; const char *source_path; - const char *default_control_group; + const char *control_group; char **dropin_paths; @@ -2600,6 +2478,7 @@ typedef struct UnitStatusInfo { pid_t main_pid; pid_t control_pid; const char *status_text; + const char *pid_file; bool running:1; usec_t start_timestamp; @@ -2609,6 +2488,10 @@ typedef struct UnitStatusInfo { usec_t condition_timestamp; bool condition_result; + bool failed_condition_trigger; + bool failed_condition_negate; + const char *failed_condition; + const char *failed_condition_param; /* Socket */ unsigned n_accepted; @@ -2630,7 +2513,8 @@ typedef struct UnitStatusInfo { LIST_HEAD(ExecStatusInfo, exec); } UnitStatusInfo; -static void print_status_info(UnitStatusInfo *i) { +static void print_status_info(UnitStatusInfo *i, + bool *ellipsized) { ExecStatusInfo *p; const char *on, *off, *ss; usec_t timestamp; @@ -2643,20 +2527,10 @@ static void print_status_info(UnitStatusInfo *i) { on_tty() * OUTPUT_COLOR | !arg_quiet * OUTPUT_WARN_CUTOFF | arg_full * OUTPUT_FULL_WIDTH; - int maxlen = 8; /* a value that'll suffice most of the time */ char **t, **t2; assert(i); - STRV_FOREACH_PAIR(t, t2, i->listen) - maxlen = MAX(maxlen, (int)(sizeof("Listen") - 1 + strlen(*t))); - if (i->accept) - maxlen = MAX(maxlen, (int)sizeof("Accept") - 1); - if (i->main_pid > 0) - maxlen = MAX(maxlen, (int)sizeof("Main PID") - 1); - else if (i->control_pid > 0) - maxlen = MAX(maxlen, (int)sizeof("Control") - 1); - /* This shows pretty information about a unit. See * print_property() for a low-level property printer */ @@ -2668,28 +2542,28 @@ static void print_status_info(UnitStatusInfo *i) { printf("\n"); if (i->following) - printf(" %*s: unit currently follows state of %s\n", maxlen, "Follow", i->following); + printf(" Follow: unit currently follows state of %s\n", i->following); if (streq_ptr(i->load_state, "error")) { - on = ansi_highlight_red(true); - off = ansi_highlight_red(false); + on = ansi_highlight_red(); + off = ansi_highlight_off(); } else on = off = ""; path = i->source_path ? i->source_path : i->fragment_path; if (i->load_error) - printf(" %*s: %s%s%s (Reason: %s)\n", - maxlen, "Loaded", on, strna(i->load_state), off, i->load_error); + printf(" Loaded: %s%s%s (Reason: %s)\n", + on, strna(i->load_state), off, i->load_error); else if (path && i->unit_file_state) - printf(" %*s: %s%s%s (%s; %s)\n", - maxlen, "Loaded", on, strna(i->load_state), off, path, i->unit_file_state); + printf(" Loaded: %s%s%s (%s; %s)\n", + on, strna(i->load_state), off, path, i->unit_file_state); else if (path) - printf(" %*s: %s%s%s (%s)\n", - maxlen, "Loaded", on, strna(i->load_state), off, path); + printf(" Loaded: %s%s%s (%s)\n", + on, strna(i->load_state), off, path); else - printf(" %*s: %s%s%s\n", - maxlen, "Loaded", on, strna(i->load_state), off); + printf(" Loaded: %s%s%s\n", + on, strna(i->load_state), off); if (!strv_isempty(i->dropin_paths)) { char ** dropin; @@ -2698,7 +2572,7 @@ static void print_status_info(UnitStatusInfo *i) { STRV_FOREACH(dropin, i->dropin_paths) { if (! dir || last) { - printf(" %*s ", maxlen, dir ? "" : "Drop-In:"); + printf(dir ? " " : " Drop-In: "); free(dir); @@ -2707,7 +2581,7 @@ static void print_status_info(UnitStatusInfo *i) { return; } - printf("%s\n %*s %s", dir, maxlen, "", + printf("%s\n %s", dir, draw_special_char(DRAW_TREE_RIGHT)); } @@ -2722,20 +2596,20 @@ static void print_status_info(UnitStatusInfo *i) { ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; if (streq_ptr(i->active_state, "failed")) { - on = ansi_highlight_red(true); - off = ansi_highlight_red(false); + on = ansi_highlight_red(); + off = ansi_highlight_off(); } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { - on = ansi_highlight_green(true); - off = ansi_highlight_green(false); + on = ansi_highlight_green(); + off = ansi_highlight_off(); } else on = off = ""; if (ss) - printf(" %*s: %s%s (%s)%s", - maxlen, "Active", on, strna(i->active_state), ss, off); + printf(" Active: %s%s (%s)%s", + on, strna(i->active_state), ss, off); else - printf(" %*s: %s%s%s", - maxlen, "Active", on, strna(i->active_state), off); + printf(" Active: %s%s%s", + on, strna(i->active_state), off); if (!isempty(i->result) && !streq(i->result, "success")) printf(" (Result: %s)", i->result); @@ -2761,27 +2635,32 @@ static void print_status_info(UnitStatusInfo *i) { s1 = format_timestamp_relative(since1, sizeof(since1), i->condition_timestamp); s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp); - if (s1) - printf(" %*s start condition failed at %s; %s\n", maxlen, "", s2, s1); - else if (s2) - printf(" %*s start condition failed at %s\n", maxlen, "", s2); + printf(" start condition failed at %s%s%s\n", + s2, s1 ? "; " : "", s1 ? s1 : ""); + if (i->failed_condition_trigger) + printf(" none of the trigger conditions were met\n"); + else if (i->failed_condition) + printf(" %s=%s%s was not met\n", + i->failed_condition, + i->failed_condition_negate ? "!" : "", + i->failed_condition_param); } if (i->sysfs_path) - printf(" %*s: %s\n", maxlen, "Device", i->sysfs_path); + printf(" Device: %s\n", i->sysfs_path); if (i->where) - printf(" %*s: %s\n", maxlen, "Where", i->where); + printf(" Where: %s\n", i->where); if (i->what) - printf(" %*s: %s\n", maxlen, "What", i->what); + printf(" What: %s\n", i->what); STRV_FOREACH(t, i->documentation) - printf(" %*s %s\n", maxlen+1, t == i->documentation ? "Docs:" : "", *t); + printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t); STRV_FOREACH_PAIR(t, t2, i->listen) - printf(" %*s %s (%s)\n", maxlen+1, t == i->listen ? "Listen:" : "", *t2, *t); + printf(" %*s %s (%s)\n", 9, t == i->listen ? "Listen:" : "", *t2, *t); if (i->accept) - printf(" %*s: %u; Connected: %u\n", maxlen, "Accepted", i->n_accepted, i->n_connections); + printf(" Accepted: %u; Connected: %u\n", i->n_accepted, i->n_connections); LIST_FOREACH(exec, p, i->exec) { _cleanup_free_ char *argv = NULL; @@ -2792,12 +2671,12 @@ static void print_status_info(UnitStatusInfo *i) { continue; argv = strv_join(p->argv, " "); - printf(" %*s: %u %s=%s ", maxlen, "Process", p->pid, p->name, strna(argv)); + printf(" Process: %u %s=%s ", p->pid, p->name, strna(argv)); good = is_clean_exit_lsb(p->code, p->status, NULL); if (!good) { - on = ansi_highlight_red(true); - off = ansi_highlight_red(false); + on = ansi_highlight_red(); + off = ansi_highlight_off(); } else on = off = ""; @@ -2829,7 +2708,7 @@ static void print_status_info(UnitStatusInfo *i) { if (i->main_pid > 0 || i->control_pid > 0) { if (i->main_pid > 0) { - printf(" %*s: %u", maxlen, "Main PID", (unsigned) i->main_pid); + printf(" Main PID: %u", (unsigned) i->main_pid); if (i->running) { _cleanup_free_ char *comm = NULL; @@ -2860,7 +2739,7 @@ static void print_status_info(UnitStatusInfo *i) { if (i->control_pid > 0) { _cleanup_free_ char *c = NULL; - printf(" %*s: %u", i->main_pid ? 0 : maxlen, "Control", (unsigned) i->control_pid); + printf(" %8s: %u", i->main_pid ? "" : " Control", (unsigned) i->control_pid); get_process_comm(i->control_pid, &c); if (c) @@ -2871,20 +2750,18 @@ static void print_status_info(UnitStatusInfo *i) { } if (i->status_text) - printf(" %*s: \"%s\"\n", maxlen, "Status", i->status_text); + printf(" Status: \"%s\"\n", i->status_text); - if (i->default_control_group && - (i->main_pid > 0 || i->control_pid > 0 || cg_is_empty_by_spec(i->default_control_group, false) == 0)) { + if (i->control_group && + (i->main_pid > 0 || i->control_pid > 0 || cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, i->control_group, false) == 0)) { unsigned c; - printf(" %*s: %s\n", maxlen, "CGroup", i->default_control_group); + printf(" CGroup: %s\n", i->control_group); if (arg_transport != TRANSPORT_SSH) { unsigned k = 0; pid_t extra[2]; - char prefix[maxlen + 4]; - memset(prefix, ' ', sizeof(prefix) - 1); - prefix[sizeof(prefix) - 1] = '\0'; + char prefix[] = " "; c = columns(); if (c > sizeof(prefix) - 1) @@ -2898,7 +2775,7 @@ static void print_status_info(UnitStatusInfo *i) { if (i->control_pid > 0) extra[k++] = i->control_pid; - show_cgroup_and_extra_by_spec(i->default_control_group, prefix, + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, false, extra, k, flags); } } @@ -2913,14 +2790,15 @@ static void print_status_info(UnitStatusInfo *i) { arg_lines, getuid(), flags, - arg_scope == UNIT_FILE_SYSTEM); + arg_scope == UNIT_FILE_SYSTEM, + ellipsized); } if (i->need_daemon_reload) - printf("\n%sWarning:%s Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n", - ansi_highlight_red(true), - ansi_highlight_red(false), - arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user"); + printf("\n%sWarning:%s Unit file changed on disk, 'systemctl %sdaemon-reload' recommended.\n", + ansi_highlight_red(), + ansi_highlight_off(), + arg_scope == UNIT_FILE_SYSTEM ? "" : "--user "); } static void show_unit_help(UnitStatusInfo *i) { @@ -3007,10 +2885,20 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn i->fragment_path = s; else if (streq(name, "SourcePath")) i->source_path = s; - else if (streq(name, "DefaultControlGroup")) - i->default_control_group = s; +#ifndef NOLEGACY + else if (streq(name, "DefaultControlGroup")) { + const char *e; + e = startswith(s, SYSTEMD_CGROUP_CONTROLLER ":"); + if (e) + i->control_group = e; + } +#endif + else if (streq(name, "ControlGroup")) + i->control_group = s; else if (streq(name, "StatusText")) i->status_text = s; + else if (streq(name, "PIDFile")) + i->pid_file = s; else if (streq(name, "SysFSPath")) i->sysfs_path = s; else if (streq(name, "Where")) @@ -3115,15 +3003,18 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn ExecStatusInfo *info; int r; - if (!(info = new0(ExecStatusInfo, 1))) + info = new0(ExecStatusInfo, 1); + if (!info) return -ENOMEM; - if (!(info->name = strdup(name))) { + info->name = strdup(name); + if (!info->name) { free(info); return -ENOMEM; } - if ((r = exec_status_info_deserialize(&sub, info)) < 0) { + r = exec_status_info_deserialize(&sub, info); + if (r < 0) { free(info); return r; } @@ -3133,7 +3024,8 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn dbus_message_iter_next(&sub); } - } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Listen")) { + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && + streq(name, "Listen")) { DBusMessageIter sub, sub2; dbus_message_iter_recurse(iter, &sub); @@ -3159,7 +3051,8 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn return 0; - } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING && streq(name, "DropInPaths")) { + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING && + streq(name, "DropInPaths")) { int r = bus_parse_strv_iter(iter, &i->dropin_paths); if (r < 0) return r; @@ -3182,6 +3075,36 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn dbus_message_iter_next(&sub); } + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && + streq(name, "Conditions")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *cond, *param; + dbus_bool_t trigger, negate; + dbus_int32_t state; + + dbus_message_iter_recurse(&sub, &sub2); + log_debug("here"); + + if(bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &cond, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &trigger, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &negate, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, ¶m, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &state, false) >= 0) { + log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); + if (state < 0 && (!trigger || !i->failed_condition)) { + i->failed_condition = cond; + i->failed_condition_trigger = trigger; + i->failed_condition_negate = negate; + i->failed_condition_param = param; + } + } + + dbus_message_iter_next(&sub); + } } break; @@ -3350,30 +3273,6 @@ static int print_property(const char *name, DBusMessageIter *iter) { return 0; - } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "ControlGroupAttributes")) { - DBusMessageIter sub, sub2; - - dbus_message_iter_recurse(iter, &sub); - while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { - const char *controller, *attr, *value; - - dbus_message_iter_recurse(&sub, &sub2); - - if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &controller, true) >= 0 && - bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &attr, true) >= 0 && - bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &value, false) >= 0) { - - printf("ControlGroupAttributes={ controller=%s ; attribute=%s ; value=\"%s\" }\n", - controller, - attr, - value); - } - - dbus_message_iter_next(&sub); - } - - return 0; - } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && startswith(name, "Exec")) { DBusMessageIter sub; @@ -3408,8 +3307,62 @@ static int print_property(const char *name, DBusMessageIter *iter) { } return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "DeviceAllow")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *path, *rwm; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &rwm, false) >= 0) + printf("%s=%s %s\n", name, strna(path), strna(rwm)); + + dbus_message_iter_next(&sub); + } + return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "BlockIODeviceWeight")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *path; + uint64_t weight; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &weight, false) >= 0) + printf("%s=%s %" PRIu64 "\n", name, strna(path), weight); + + dbus_message_iter_next(&sub); + } + return 0; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *path; + uint64_t bandwidth; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &bandwidth, false) >= 0) + printf("%s=%s %" PRIu64 "\n", name, strna(path), bandwidth); + + dbus_message_iter_next(&sub); + } + return 0; } + break; } @@ -3422,7 +3375,12 @@ static int print_property(const char *name, DBusMessageIter *iter) { return 0; } -static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) { +static int show_one(const char *verb, + DBusConnection *bus, + const char *path, + bool show_properties, + bool *new_line, + bool *ellipsized) { _cleanup_free_ DBusMessage *reply = NULL; const char *interface = ""; int r; @@ -3492,7 +3450,7 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo if (streq(verb, "help")) show_unit_help(&info); else - print_status_info(&info); + print_status_info(&info, ellipsized); } strv_free(info.documentation); @@ -3501,9 +3459,19 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo if (!streq_ptr(info.active_state, "active") && !streq_ptr(info.active_state, "reloading") && - streq(verb, "status")) + streq(verb, "status")) { /* According to LSB: "program not running" */ - r = 3; + /* 0: program is running or service is OK + * 1: program is dead and /var/run pid file exists + * 2: program is dead and /var/lock lock file exists + * 3: program is not running + * 4: program or service status is unknown + */ + if (info.pid_file && access(info.pid_file, F_OK) == 0) + r = 1; + else + r = 3; + } while ((p = info.exec)) { LIST_REMOVE(ExecStatusInfo, exec, info.exec, p); @@ -3513,7 +3481,11 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo return r; } -static int show_one_by_pid(const char *verb, DBusConnection *bus, uint32_t pid, bool *new_line) { +static int show_one_by_pid(const char *verb, + DBusConnection *bus, + uint32_t pid, + bool *new_line, + bool *ellipsized) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; const char *path = NULL; _cleanup_dbus_error_free_ DBusError error; @@ -3541,11 +3513,15 @@ static int show_one_by_pid(const char *verb, DBusConnection *bus, uint32_t pid, return -EIO; } - r = show_one(verb, bus, path, false, new_line); + r = show_one(verb, bus, path, false, new_line, ellipsized); return r; } -static int show_all(const char* verb, DBusConnection *bus, bool show_properties, bool *new_line) { +static int show_all(const char* verb, + DBusConnection *bus, + bool show_properties, + bool *new_line, + bool *ellipsized) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; _cleanup_free_ struct unit_info *unit_infos = NULL; unsigned c = 0; @@ -3570,7 +3546,7 @@ static int show_all(const char* verb, DBusConnection *bus, bool show_properties, printf("%s -> '%s'\n", u->id, p); - r = show_one(verb, bus, p, show_properties, new_line); + r = show_one(verb, bus, p, show_properties, new_line, ellipsized); if (r != 0) return r; } @@ -3582,6 +3558,7 @@ static int show(DBusConnection *bus, char **args) { int r, ret = 0; bool show_properties, show_status, new_line = false; char **name; + bool ellipsized = false; assert(bus); assert(args); @@ -3595,83 +3572,319 @@ static int show(DBusConnection *bus, char **args) { /* If no argument is specified inspect the manager itself */ if (show_properties && strv_length(args) <= 1) - return show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line); + return show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line, &ellipsized); if (show_status && strv_length(args) <= 1) - return show_all(args[0], bus, false, &new_line); + ret = show_all(args[0], bus, false, &new_line, &ellipsized); + else + STRV_FOREACH(name, args+1) { + uint32_t id; - STRV_FOREACH(name, args+1) { - uint32_t id; + if (safe_atou32(*name, &id) < 0) { + _cleanup_free_ char *p = NULL, *n = NULL; + /* Interpret as unit name */ - if (safe_atou32(*name, &id) < 0) { - _cleanup_free_ char *p = NULL, *n = NULL; - /* Interpret as unit name */ + n = unit_name_mangle(*name); + if (!n) + return log_oom(); - n = unit_name_mangle(*name); - if (!n) - return log_oom(); + p = unit_dbus_path_from_name(n); + if (!p) + return log_oom(); + + r = show_one(args[0], bus, p, show_properties, &new_line, &ellipsized); + if (r != 0) + ret = r; + + } else if (show_properties) { + _cleanup_free_ char *p = NULL; + + /* Interpret as job id */ + if (asprintf(&p, "/org/freedesktop/systemd1/job/%u", id) < 0) + return log_oom(); + + r = show_one(args[0], bus, p, show_properties, &new_line, &ellipsized); + if (r != 0) + ret = r; + + } else { + /* Interpret as PID */ + r = show_one_by_pid(args[0], bus, id, &new_line, &ellipsized); + if (r != 0) + ret = r; + } + } + + if (ellipsized && !arg_quiet) + printf("Hint: Some lines were ellipsized, use -l to show in full.\n"); + + return ret; +} + +static int append_assignment(DBusMessageIter *iter, const char *assignment) { + const char *eq; + char *field; + DBusMessageIter sub; + int r; + + assert(iter); + assert(assignment); + + eq = strchr(assignment, '='); + if (!eq) { + log_error("Not an assignment: %s", assignment); + return -EINVAL; + } + + field = strndupa(assignment, eq - assignment); + eq ++; + + if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &field)) + return log_oom(); + + if (streq(field, "CPUAccounting") || + streq(field, "MemoryAccounting") || + streq(field, "BlockIOAccounting")) { + dbus_bool_t b; + + r = parse_boolean(eq); + if (r < 0) { + log_error("Failed to parse boolean assignment %s.", assignment); + return -EINVAL; + } + + b = r; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "b", &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &b)) + return log_oom(); + + } else if (streq(field, "MemoryLimit")) { + off_t bytes; + uint64_t u; + + r = parse_bytes(eq, &bytes); + if (r < 0) { + log_error("Failed to parse bytes specification %s", assignment); + return -EINVAL; + } + + u = bytes; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "t", &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT64, &u)) + return log_oom(); - p = unit_dbus_path_from_name(n); - if (!p) + } else if (streq(field, "CPUShares") || streq(field, "BlockIOWeight")) { + uint64_t u; + + r = safe_atou64(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "t", &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT64, &u)) + return log_oom(); + + } else if (streq(field, "DevicePolicy")) { + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub) || + !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &eq)) + return log_oom(); + + } else if (streq(field, "DeviceAllow")) { + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(ss)", &sub) || + !dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "(ss)", &sub2)) + return log_oom(); + + if (!isempty(eq)) { + const char *path, *rwm; + DBusMessageIter sub3; + char *e; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + rwm = e+1; + } else { + path = eq; + rwm = ""; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + if (!dbus_message_iter_open_container(&sub2, DBUS_TYPE_STRUCT, NULL, &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &path) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &rwm) || + !dbus_message_iter_close_container(&sub2, &sub3)) return log_oom(); + } + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + } else if (streq(field, "BlockIOReadBandwidth") || streq(field, "BlockIOWriteBandwidth")) { + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(st)", &sub) || + !dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "(st)", &sub2)) + return log_oom(); + + if (!isempty(eq)) { + const char *path, *bandwidth; + DBusMessageIter sub3; + uint64_t u; + off_t bytes; + char *e; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + bandwidth = e+1; + } else { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } - r = show_one(args[0], bus, p, show_properties, &new_line); - if (r != 0) - ret = r; + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } - } else if (show_properties) { - _cleanup_free_ char *p = NULL; + r = parse_bytes(bandwidth, &bytes); + if (r < 0) { + log_error("Failed to parse byte value %s.", bandwidth); + return -EINVAL; + } - /* Interpret as job id */ - if (asprintf(&p, "/org/freedesktop/systemd1/job/%u", id) < 0) + u = (uint64_t) bytes; + + if (!dbus_message_iter_open_container(&sub2, DBUS_TYPE_STRUCT, NULL, &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &path) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_UINT64, &u) || + !dbus_message_iter_close_container(&sub2, &sub3)) return log_oom(); + } - r = show_one(args[0], bus, p, show_properties, &new_line); - if (r != 0) - ret = r; + if (!dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); - } else { - /* Interpret as PID */ - r = show_one_by_pid(args[0], bus, id, &new_line); - if (r != 0) - ret = r; + } else if (streq(field, "BlockIODeviceWeight")) { + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(st)", &sub) || + !dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "(st)", &sub2)) + return log_oom(); + + if (!isempty(eq)) { + const char *path, *weight; + DBusMessageIter sub3; + uint64_t u; + char *e; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + weight = e+1; + } else { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + r = safe_atou64(weight, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, weight); + return -EINVAL; + } + if (!dbus_message_iter_open_container(&sub2, DBUS_TYPE_STRUCT, NULL, &sub3) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &path) || + !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_UINT64, &u) || + !dbus_message_iter_close_container(&sub2, &sub3)) + return log_oom(); } + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + } else { + log_error("Unknown assignment %s.", assignment); + return -EINVAL; } - return ret; + if (!dbus_message_iter_close_container(iter, &sub)) + return log_oom(); + + return 0; } -static int dump(DBusConnection *bus, char **args) { - _cleanup_free_ DBusMessage *reply = NULL; +static int set_property(DBusConnection *bus, char **args) { + + _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; + _cleanup_free_ char *n = NULL; + DBusMessageIter iter, sub; + dbus_bool_t runtime; DBusError error; + char **i; int r; - const char *text; dbus_error_init(&error); - pager_open_if_enabled(); - - r = bus_method_call_with_reply( - bus, + m = dbus_message_new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "Dump", - &reply, - NULL, - DBUS_TYPE_INVALID); - if (r < 0) - return r; + "SetUnitProperties"); + if (!m) + return log_oom(); - if (!dbus_message_get_args(reply, &error, - DBUS_TYPE_STRING, &text, - DBUS_TYPE_INVALID)) { - log_error("Failed to parse reply: %s", bus_error_message(&error)); + dbus_message_iter_init_append(m, &iter); + + runtime = arg_runtime; + + n = unit_name_mangle(args[1]); + if (!n) + return log_oom(); + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n) || + !dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &runtime) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub)) + return log_oom(); + + STRV_FOREACH(i, args + 2) { + DBusMessageIter sub2; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2)) + return log_oom(); + + r = append_assignment(&sub2, *i); + if (r < 0) + return r; + + if (!dbus_message_iter_close_container(&sub, &sub2)) + return log_oom(); + + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + 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; + return -EIO; } - fputs(text, stdout); return 0; } @@ -3690,13 +3903,13 @@ static int snapshot(DBusConnection *bus, char **args) { dbus_error_init(&error); if (strv_length(args) > 1) - n = snapshot_name_mangle(args[1]); + n = unit_name_mangle_with_suffix(args[1], ".snapshot"); else n = strdup(""); if (!n) return log_oom(); - r = bus_method_call_with_reply ( + r = bus_method_call_with_reply( bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -3765,7 +3978,7 @@ static int delete_snapshot(DBusConnection *bus, char **args) { _cleanup_free_ char *n = NULL; int r; - n = snapshot_name_mangle(*name); + n = unit_name_mangle_with_suffix(*name, ".snapshot"); if (!n) return log_oom(); @@ -3825,9 +4038,9 @@ static int daemon_reload(DBusConnection *bus, char **args) { /* There's always a fallback possible for * legacy actions. */ r = -EADDRNOTAVAIL; - else if (r == -ETIMEDOUT && streq(method, "Reexecute")) - /* On reexecution, we expect a disconnect, not - * a reply */ + else if ((r == -ETIMEDOUT || r == -ECONNRESET) && streq(method, "Reexecute")) + /* On reexecution, we expect a disconnect, not a + * reply */ r = 0; else if (r < 0) log_error("Failed to issue method call: %s", bus_error_message(&error)); @@ -4217,24 +4430,30 @@ static int enable_unit(DBusConnection *bus, char **args) { if (!args[1]) return 0; + r = mangle_names(args+1, &mangled_names); + if (r < 0) + goto finish; + if (!bus || avoid_bus()) { if (streq(verb, "enable")) { - r = unit_file_enable(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + r = unit_file_enable(arg_scope, arg_runtime, arg_root, mangled_names, arg_force, &changes, &n_changes); carries_install_info = r; } else if (streq(verb, "disable")) - r = unit_file_disable(arg_scope, arg_runtime, arg_root, args+1, &changes, &n_changes); + r = unit_file_disable(arg_scope, arg_runtime, arg_root, mangled_names, &changes, &n_changes); else if (streq(verb, "reenable")) { - r = unit_file_reenable(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + r = unit_file_reenable(arg_scope, arg_runtime, arg_root, mangled_names, arg_force, &changes, &n_changes); carries_install_info = r; } else if (streq(verb, "link")) - r = unit_file_link(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + r = unit_file_link(arg_scope, arg_runtime, arg_root, mangled_names, arg_force, &changes, &n_changes); else if (streq(verb, "preset")) { - r = unit_file_preset(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + r = unit_file_preset(arg_scope, arg_runtime, arg_root, mangled_names, arg_force, &changes, &n_changes); carries_install_info = r; } else if (streq(verb, "mask")) - r = unit_file_mask(arg_scope, arg_runtime, arg_root, args+1, arg_force, &changes, &n_changes); + r = unit_file_mask(arg_scope, arg_runtime, arg_root, mangled_names, arg_force, &changes, &n_changes); else if (streq(verb, "unmask")) - r = unit_file_unmask(arg_scope, arg_runtime, arg_root, args+1, &changes, &n_changes); + r = unit_file_unmask(arg_scope, arg_runtime, arg_root, mangled_names, &changes, &n_changes); + else if (streq(verb, "set-default")) + r = unit_file_set_default(arg_scope, arg_root, args[1], &changes, &n_changes); else assert_not_reached("Unknown verb"); @@ -4278,6 +4497,8 @@ static int enable_unit(DBusConnection *bus, char **args) { else if (streq(verb, "unmask")) { method = "UnmaskUnitFiles"; send_force = false; + } else if (streq(verb, "set-default")) { + method = "SetDefaultTarget"; } else assert_not_reached("Unknown verb"); @@ -4293,10 +4514,6 @@ static int enable_unit(DBusConnection *bus, char **args) { dbus_message_iter_init_append(m, &iter); - r = mangle_names(args+1, &mangled_names); - if(r < 0) - goto finish; - r = bus_append_strv_iter(&iter, mangled_names); if (r < 0) { log_error("Failed to append unit files."); @@ -4498,19 +4715,20 @@ static int systemctl_help(void) { " -h --help Show this help\n" " --version Show package version\n" " -t --type=TYPE List only units of a particular type\n" + " --state=STATE List only units with particular LOAD or SUB or ACTIVE state\n" " -p --property=NAME Show only properties by this name\n" " -a --all Show all loaded units/properties, including dead/empty\n" " ones. To list all units installed on the system, use\n" " the 'list-unit-files' command instead.\n" " --reverse Show reverse dependencies with 'list-dependencies'\n" - " --failed Show only failed units\n" - " --full Don't ellipsize unit names on output\n" + " -l --full Don't ellipsize unit names on output\n" " --fail When queueing a new job, fail if conflicting jobs are\n" " pending\n" - " --irreversible Create jobs which cannot be implicitly cancelled\n" - " --show-types When showing sockets, explicitly show their type\n" + " --irreversible When queueing a new job, make sure it cannot be implicitly\n" + " cancelled\n" " --ignore-dependencies\n" " When queueing a new job, ignore all its dependencies\n" + " --show-types When showing sockets, explicitly show their type\n" " -i --ignore-inhibitors\n" " When shutting down or sleeping, ignore inhibitors\n" " --kill-who=WHO Who to send signal to\n" @@ -4530,15 +4748,16 @@ static int systemctl_help(void) { " --system Connect to system manager\n" " --user Connect to user service manager\n" " --global Enable/disable unit files globally\n" + " --runtime Enable unit files only temporarily until next reboot\n" " -f --force When enabling unit files, override existing symlinks\n" " When shutting down, execute action immediately\n" " --root=PATH Enable unit files in the specified root directory\n" - " --runtime Enable unit files only temporarily until next reboot\n" - " -n --lines=INTEGER Journal entries to show\n" + " -n --lines=INTEGER Numer of journal entries to show\n" " -o --output=STRING Change journal output mode (short, short-monotonic,\n" " verbose, export, json, json-pretty, json-sse, cat)\n\n" "Unit Commands:\n" " list-units List loaded units\n" + " list-sockets List loaded sockets ordered by address\n" " start [NAME...] Start (activate) one or more units\n" " stop [NAME...] Stop (deactivate) one or more units\n" " reload [NAME...] Reload one or more units\n" @@ -4555,18 +4774,11 @@ static int systemctl_help(void) { " status [NAME...|PID...] Show runtime status of one or more units\n" " show [NAME...|JOB...] Show properties of one or more\n" " units/jobs or the manager\n" + " set-property [NAME] [ASSIGNMENT...]\n" + " Sets one or more properties of a unit\n" " help [NAME...|PID...] Show manual for one or more units\n" " reset-failed [NAME...] Reset failed state for all, one, or more\n" " units\n" - " get-cgroup-attr [NAME] [ATTR] ...\n" - " Get control group attrubute\n" - " set-cgroup-attr [NAME] [ATTR] [VALUE] ...\n" - " Set control group attribute\n" - " unset-cgroup-attr [NAME] [ATTR...]\n" - " Unset control group attribute\n" - " set-cgroup [NAME] [CGROUP...] Add unit to a control group\n" - " unset-cgroup [NAME] [CGROUP...] Remove unit from a control group\n" - " load [NAME...] Load one or more units\n" " list-dependencies [NAME] Recursively show units which are required\n" " or wanted by this unit or by which this\n" " unit is required or wanted\n\n" @@ -4577,16 +4789,16 @@ static int systemctl_help(void) { " reenable [NAME...] Reenable one or more unit files\n" " preset [NAME...] Enable/disable one or more unit files\n" " based on preset configuration\n" + " is-enabled [NAME...] Check whether unit files are enabled\n\n" " mask [NAME...] Mask one or more units\n" " unmask [NAME...] Unmask one or more units\n" " link [PATH...] Link one or more units files into\n" " the search path\n" - " is-enabled [NAME...] Check whether unit files are enabled\n\n" + " get-default Get the name of the default target\n" + " set-default NAME Set the default target\n\n" "Job Commands:\n" " list-jobs List jobs\n" " cancel [JOB...] Cancel all, one, or more jobs\n\n" - "Status Commands:\n" - " dump Dump server status\n" "Snapshot Commands:\n" " snapshot [NAME] Create a snapshot\n" " delete [NAME...] Remove one or more snapshots\n\n" @@ -4691,13 +4903,6 @@ static int help_types(void) { puts(t); } - puts("\nAvailable unit load states: "); - for(i = 0; i < _UNIT_LOAD_STATE_MAX; i++) { - t = unit_load_state_to_string(i); - if (t) - puts(t); - } - return 0; } @@ -4720,53 +4925,54 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_NO_PAGER, ARG_NO_WALL, ARG_ROOT, - ARG_FULL, ARG_NO_RELOAD, ARG_KILL_WHO, ARG_NO_ASK_PASSWORD, ARG_FAILED, ARG_RUNTIME, ARG_FORCE, - ARG_PLAIN + ARG_PLAIN, + ARG_STATE }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "reverse", no_argument, NULL, ARG_REVERSE }, - { "after", no_argument, NULL, ARG_AFTER }, - { "before", no_argument, NULL, ARG_BEFORE }, - { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, - { "failed", no_argument, NULL, ARG_FAILED }, - { "full", no_argument, NULL, ARG_FULL }, - { "fail", no_argument, NULL, ARG_FAIL }, - { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, - { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, - { "ignore-inhibitors", no_argument, NULL, 'i' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "quiet", no_argument, NULL, 'q' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "force", no_argument, NULL, ARG_FORCE }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "kill-who", required_argument, NULL, ARG_KILL_WHO }, - { "signal", required_argument, NULL, 's' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "privileged",no_argument, NULL, 'P' }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "plain", no_argument, NULL, ARG_PLAIN }, - { NULL, 0, NULL, 0 } + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "reverse", no_argument, NULL, ARG_REVERSE }, + { "after", no_argument, NULL, ARG_AFTER }, + { "before", no_argument, NULL, ARG_BEFORE }, + { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, + { "failed", no_argument, NULL, ARG_FAILED }, /* compatibility only */ + { "full", no_argument, NULL, 'l' }, + { "fail", no_argument, NULL, ARG_FAIL }, + { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, + { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, + { "ignore-inhibitors", no_argument, NULL, 'i' }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "global", no_argument, NULL, ARG_GLOBAL }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { "quiet", no_argument, NULL, 'q' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "force", no_argument, NULL, ARG_FORCE }, + { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "host", required_argument, NULL, 'H' }, + { "privileged", no_argument, NULL, 'P' }, + { "runtime", no_argument, NULL, ARG_RUNTIME }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "plain", no_argument, NULL, ARG_PLAIN }, + { "state", required_argument, NULL, ARG_STATE }, + { NULL, 0, NULL, 0 } }; int c; @@ -4774,7 +4980,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:Pn:o:i", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:Pn:o:i", options, NULL)) >= 0) { switch (c) { @@ -4810,8 +5016,12 @@ static int systemctl_parse_argv(int argc, char *argv[]) { continue; } + /* It's much nicer to use --state= for + * load states, but let's support this + * in --types= too for compatibility + * with old versions */ if (unit_load_state_from_string(optarg) >= 0) { - if (strv_push(&arg_load_states, type)) + if (strv_push(&arg_states, type) < 0) return log_oom(); type = NULL; continue; @@ -4843,7 +5053,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { if (!prop) return log_oom(); - if (strv_push(&arg_properties, prop)) { + if (strv_push(&arg_properties, prop) < 0) { free(prop); return log_oom(); } @@ -4922,12 +5132,14 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_root = optarg; break; - case ARG_FULL: + case 'l': arg_full = true; break; case ARG_FAILED: - arg_failed = true; + if (strv_extend(&arg_states, "failed") < 0) + return log_oom(); + break; case 'q': @@ -4967,7 +5179,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { case 'H': arg_transport = TRANSPORT_SSH; - arg_host = optarg; + parse_user_at_host(optarg, &arg_user, &arg_host); break; case ARG_RUNTIME: @@ -4997,6 +5209,25 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_plain = true; break; + case ARG_STATE: { + char *word, *state; + size_t size; + + FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) { + char *s; + + s = strndup(word, size); + if (!s) + return log_oom(); + + if (strv_push(&arg_states, s) < 0) { + free(s); + return log_oom(); + } + } + break; + } + case '?': return -EINVAL; @@ -5447,94 +5678,6 @@ _pure_ static int action_to_runlevel(void) { return table[arg_action]; } -static int talk_upstart(void) { - _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; - _cleanup_dbus_error_free_ DBusError error; - int previous, rl, r; - char - env1_buf[] = "RUNLEVEL=X", - env2_buf[] = "PREVLEVEL=X"; - char *env1 = env1_buf, *env2 = env2_buf; - const char *emit = "runlevel"; - dbus_bool_t b_false = FALSE; - DBusMessageIter iter, sub; - DBusConnection *bus; - - dbus_error_init(&error); - - if (!(rl = action_to_runlevel())) - return 0; - - if (utmp_get_runlevel(&previous, NULL) < 0) - previous = 'N'; - - if (!(bus = dbus_connection_open_private("unix:abstract=/com/ubuntu/upstart", &error))) { - if (dbus_error_has_name(&error, DBUS_ERROR_NO_SERVER)) { - r = 0; - goto finish; - } - - log_error("Failed to connect to Upstart bus: %s", bus_error_message(&error)); - r = -EIO; - goto finish; - } - - if ((r = bus_check_peercred(bus)) < 0) { - log_error("Failed to verify owner of bus."); - goto finish; - } - - if (!(m = dbus_message_new_method_call( - "com.ubuntu.Upstart", - "/com/ubuntu/Upstart", - "com.ubuntu.Upstart0_6", - "EmitEvent"))) { - - log_error("Could not allocate message."); - r = -ENOMEM; - goto finish; - } - - dbus_message_iter_init_append(m, &iter); - - env1_buf[sizeof(env1_buf)-2] = rl; - env2_buf[sizeof(env2_buf)-2] = previous; - - if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &emit) || - !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &env1) || - !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &env2) || - !dbus_message_iter_close_container(&iter, &sub) || - !dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &b_false)) { - log_error("Could not append arguments to message."); - r = -ENOMEM; - goto finish; - } - - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - - if (bus_error_is_no_service(&error)) { - r = -EADDRNOTAVAIL; - goto finish; - } - - log_error("Failed to issue method call: %s", bus_error_message(&error)); - r = -EIO; - goto finish; - } - - r = 1; - -finish: - if (bus) { - dbus_connection_flush(bus); - dbus_connection_close(bus); - dbus_connection_unref(bus); - } - - return r; -} - static int talk_initctl(void) { struct init_request request = {}; int r; @@ -5586,7 +5729,6 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "list-sockets", LESS, 1, list_sockets }, { "list-jobs", EQUAL, 1, list_jobs }, { "clear-jobs", EQUAL, 1, daemon_reload }, - { "load", MORE, 2, load_unit }, { "cancel", MORE, 2, cancel_job }, { "start", MORE, 2, start_unit }, { "stop", MORE, 2, start_unit }, @@ -5600,11 +5742,6 @@ 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, 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 }, @@ -5612,7 +5749,6 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "show", MORE, 1, show }, { "status", MORE, 1, show }, { "help", MORE, 2, show }, - { "dump", EQUAL, 1, dump }, { "snapshot", LESS, 2, snapshot }, { "delete", MORE, 2, delete_snapshot }, { "daemon-reload", EQUAL, 1, daemon_reload }, @@ -5642,6 +5778,9 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "link", MORE, 2, enable_unit }, { "switch-root", MORE, 2, switch_root }, { "list-dependencies", LESS, 2, list_dependencies }, + { "set-default", EQUAL, 2, enable_unit }, + { "get-default", LESS, 1, get_default }, + { "set-property", MORE, 3, set_property }, }; int left; @@ -5713,7 +5852,9 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError !streq(verbs[i].verb, "preset") && !streq(verbs[i].verb, "mask") && !streq(verbs[i].verb, "unmask") && - !streq(verbs[i].verb, "link")) { + !streq(verbs[i].verb, "link") && + !streq(verbs[i].verb, "set-default") && + !streq(verbs[i].verb, "get-default")) { if (running_in_chroot() > 0) { log_info("Running in chroot, ignoring request."); @@ -5808,11 +5949,6 @@ static int start_with_fallback(DBusConnection *bus) { goto done; } - /* Hmm, talking to systemd via D-Bus didn't work. Then - * let's try to talk to Upstart via D-Bus. */ - if (talk_upstart() > 0) - goto done; - /* Nothing else worked, so let's try * /dev/initctl */ if (talk_initctl() > 0) @@ -5954,6 +6090,11 @@ int main(int argc, char*argv[]) { log_parse_environment(); log_open(); + /* Explicitly not on_tty() to avoid setting cached value. + * This becomes relevant for piping output which might be + * ellipsized. */ + original_stdout_is_tty = isatty(STDOUT_FILENO); + r = parse_argv(argc, argv); if (r < 0) goto finish; @@ -5983,7 +6124,7 @@ int main(int argc, char*argv[]) { bus_connect_system_polkit(&bus, &error); private_bus = false; } else if (arg_transport == TRANSPORT_SSH) { - bus_connect_system_ssh(NULL, arg_host, &bus, &error); + bus_connect_system_ssh(arg_user, arg_host, &bus, &error); private_bus = false; } else assert_not_reached("Uh, invalid transport..."); @@ -6052,7 +6193,7 @@ finish: dbus_shutdown(); strv_free(arg_types); - strv_free(arg_load_states); + strv_free(arg_states); strv_free(arg_properties); pager_close(); diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h index c1ec50871f..878001ccb5 100644 --- a/src/systemd/sd-bus.h +++ b/src/systemd/sd-bus.h @@ -27,6 +27,7 @@ #include <sd-id128.h> #include "sd-bus-protocol.h" +#include "sd-memfd.h" #ifdef __cplusplus extern "C" { @@ -40,18 +41,6 @@ extern "C" { # endif #endif -/* TODO: - * - add page donation logic - * - api for appending/reading fixed arrays - * - merge busctl into systemctl or so? - * - default policy (allow uid == 0 and our own uid) - * - * - enforce alignment of pointers passed in - * - negotiation for attach attributes - * - * - for kernel and unix transports allow setting the unix user/access mode for the node - */ - typedef struct sd_bus sd_bus; typedef struct sd_bus_message sd_bus_message; @@ -61,7 +50,7 @@ typedef struct { int need_free; } sd_bus_error; -typedef int (*sd_bus_message_handler_t)(sd_bus *bus, int ret, sd_bus_message *m, void *userdata); +typedef int (*sd_bus_message_handler_t)(sd_bus *bus, sd_bus_message *m, void *userdata); /* Connections */ @@ -75,7 +64,14 @@ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]); int sd_bus_set_bus_client(sd_bus *bus, int b); int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id); int sd_bus_set_anonymous(sd_bus *bus, int b); -int sd_bus_set_negotiate_fds(sd_bus *bus, int b); +int sd_bus_negotiate_fds(sd_bus *bus, int b); +int sd_bus_negotiate_attach_comm(sd_bus *bus, int b); +int sd_bus_negotiate_attach_exe(sd_bus *bus, int b); +int sd_bus_negotiate_attach_cmdline(sd_bus *bus, int b); +int sd_bus_negotiate_attach_cgroup(sd_bus *bus, int b); +int sd_bus_negotiate_attach_caps(sd_bus *bus, int b); +int sd_bus_negotiate_attach_selinux_context(sd_bus *bus, int b); +int sd_bus_negotiate_attach_audit(sd_bus *bus, int b); int sd_bus_start(sd_bus *ret); void sd_bus_close(sd_bus *bus); @@ -163,11 +159,17 @@ int sd_bus_message_set_destination(sd_bus_message *m, const char *destination); int sd_bus_message_append(sd_bus_message *m, const char *types, ...); int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p); +int sd_bus_message_append_array(sd_bus_message *m, char type, const void *ptr, size_t size); +int sd_bus_message_append_array_space(sd_bus_message *m, char type, size_t size, void **ptr); +int sd_bus_message_append_array_memfd(sd_bus_message *m, char type, sd_memfd *memfd); +int sd_bus_message_append_string_space(sd_bus_message *m, size_t size, char **s); +int sd_bus_message_append_string_memfd(sd_bus_message *m, sd_memfd* memfd); int sd_bus_message_open_container(sd_bus_message *m, char type, const char *contents); int sd_bus_message_close_container(sd_bus_message *m); int sd_bus_message_read(sd_bus_message *m, const char *types, ...); int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p); +int sd_bus_message_read_array(sd_bus_message *m, char type, const void **ptr, size_t *size); int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *contents); int sd_bus_message_exit_container(sd_bus_message *m); int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents); @@ -203,6 +205,12 @@ int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); int sd_bus_error_is_set(const sd_bus_error *e); int sd_bus_error_has_name(const sd_bus_error *e, const char *name); +#define SD_BUS_APPEND_ID128(x) 16, \ + (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], \ + (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], \ + (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], \ + (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + #ifdef __cplusplus } #endif diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h index 5375d49963..72ea328b28 100644 --- a/src/systemd/sd-journal.h +++ b/src/systemd/sd-journal.h @@ -86,7 +86,9 @@ typedef struct sd_journal sd_journal; enum { SD_JOURNAL_LOCAL_ONLY = 1, SD_JOURNAL_RUNTIME_ONLY = 2, - SD_JOURNAL_SYSTEM_ONLY = 4 + SD_JOURNAL_SYSTEM = 4, + SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM, /* deprecated */ + SD_JOURNAL_CURRENT_USER = 8, }; /* Wakeup event types */ @@ -98,6 +100,7 @@ enum { int sd_journal_open(sd_journal **ret, int flags); int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); +int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); void sd_journal_close(sd_journal *j); int sd_journal_previous(sd_journal *j); diff --git a/src/systemd/sd-login.h b/src/systemd/sd-login.h index 4855e327a1..c5837f0ca0 100644 --- a/src/systemd/sd-login.h +++ b/src/systemd/sd-login.h @@ -72,9 +72,12 @@ int sd_pid_get_unit(pid_t pid, char **unit); int sd_pid_get_user_unit(pid_t pid, char **unit); /* Get machine name from PID, for processes assigned to VM or - * container. This will return an error for non-service processes. */ + * container. This will return an error for non-machine processes. */ int sd_pid_get_machine_name(pid_t pid, char **name); +/* Get slice name from PID. */ +int sd_pid_get_slice(pid_t pid, char **name); + /* Get state from uid. Possible states: offline, lingering, online, active, closing */ int sd_uid_get_state(uid_t uid, char**state); @@ -121,6 +124,9 @@ int sd_session_get_display(const char *session, char **display); /* Determine the TTY of this session. */ int sd_session_get_tty(const char *session, char **display); +/* Determine the VT number of this session. */ +int sd_session_get_vt(const char *session, unsigned *vtnr); + /* Return active session and user of seat */ int sd_seat_get_active(const char *seat, char **session, uid_t *uid); diff --git a/src/systemd/sd-memfd.h b/src/systemd/sd-memfd.h new file mode 100644 index 0000000000..ee140e48d3 --- /dev/null +++ b/src/systemd/sd-memfd.h @@ -0,0 +1,58 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdmemfdhfoo +#define foosdmemfdhfoo + +/*** + 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 <inttypes.h> +#include <sys/types.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sd_memfd sd_memfd; + +int sd_memfd_new(sd_memfd **m); +int sd_memfd_make(int fd, sd_memfd **m); + +int sd_memfd_new_and_map(sd_memfd **m, size_t sz, void **p); + +void sd_memfd_free(sd_memfd *m); + +int sd_memfd_get_fd(sd_memfd *m); +int sd_memfd_get_file(sd_memfd *m, FILE **f); +int sd_memfd_dup_fd(sd_memfd *n); + +int sd_memfd_map(sd_memfd *m, uint64_t offset, size_t size, void **p); + +int sd_memfd_set_sealed(sd_memfd *m, int b); +int sd_memfd_get_sealed(sd_memfd *m); + +int sd_memfd_get_size(sd_memfd *m, uint64_t *sz); +int sd_memfd_set_size(sd_memfd *m, uint64_t sz); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index c8de331691..c811a064e3 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -31,10 +31,15 @@ extern "C" { /* Hey! If you add a new message here, you *must* also update the * message catalog with an appropriate explanation */ +/* And if you add a new ID here, make sure to generate a random one + * with journalctl --new-id128. Do not use any other IDs, and do not + * count them up manually. */ + #define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) #define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) #define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) #define SD_MESSAGE_JOURNAL_MISSED SD_ID128_MAKE(e9,bf,28,e6,e8,34,48,1b,b6,f4,8f,54,8a,d1,36,06) +#define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) #define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) @@ -42,6 +47,8 @@ extern "C" { #define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) #define SD_MESSAGE_SEAT_START SD_ID128_MAKE(fc,be,fc,5d,a2,3d,42,80,93,f9,7c,82,a9,29,0f,7b) #define SD_MESSAGE_SEAT_STOP SD_ID128_MAKE(e7,85,2b,fe,46,78,4e,d0,ac,cd,e0,4b,c8,64,c2,d5) +#define SD_MESSAGE_MACHINE_START SD_ID128_MAKE(24,d8,d4,45,25,73,40,24,96,06,83,81,a6,31,2d,f2) +#define SD_MESSAGE_MACHINE_STOP SD_ID128_MAKE(58,43,2b,d3,ba,ce,47,7c,b5,14,b5,63,81,b8,a7,58) #define SD_MESSAGE_TIME_CHANGE SD_ID128_MAKE(c7,a7,87,07,9b,35,4e,aa,a9,e7,7b,37,18,93,cd,27) #define SD_MESSAGE_TIMEZONE_CHANGE SD_ID128_MAKE(45,f8,2f,4a,ef,7a,4b,bf,94,2c,e8,61,d1,f2,09,90) diff --git a/src/test/test-boot-timestamps.c b/src/test/test-boot-timestamps.c new file mode 100644 index 0000000000..4ede318e38 --- /dev/null +++ b/src/test/test-boot-timestamps.c @@ -0,0 +1,98 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + Copyright 2013 Kay Sievers + + 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 "log.h" +#include "boot-timestamps.h" +#include "efivars.h" +#include "acpi-fpdt.h" + +static int test_acpi_fpdt(void) { + usec_t loader_start; + usec_t loader_exit; + char ts_start[FORMAT_TIMESPAN_MAX]; + char ts_exit[FORMAT_TIMESPAN_MAX]; + char ts_span[FORMAT_TIMESPAN_MAX]; + int r; + + r = acpi_get_boot_usec(&loader_start, &loader_exit); + if (r < 0) { + if (r != -ENOENT) + log_error("Failed to read ACPI FPDT: %s", strerror(-r)); + return r; + } + + log_info("ACPI FPDT: loader start=%s exit=%s duration=%s", + format_timespan(ts_start, sizeof(ts_start), loader_start, USEC_PER_MSEC), + format_timespan(ts_exit, sizeof(ts_exit), loader_exit, USEC_PER_MSEC), + format_timespan(ts_span, sizeof(ts_span), loader_exit - loader_start, USEC_PER_MSEC)); + + return 0; +} + +static int test_efi_loader(void) { + usec_t loader_start; + usec_t loader_exit; + char ts_start[FORMAT_TIMESPAN_MAX]; + char ts_exit[FORMAT_TIMESPAN_MAX]; + char ts_span[FORMAT_TIMESPAN_MAX]; + int r; + + r = efi_loader_get_boot_usec(&loader_start, &loader_exit); + if (r < 0) { + if (r != -ENOENT) + log_error("Failed to read EFI loader data: %s", strerror(-r)); + return r; + } + + log_info("EFI Loader: start=%s exit=%s duration=%s", + format_timespan(ts_start, sizeof(ts_start), loader_start, USEC_PER_MSEC), + format_timespan(ts_exit, sizeof(ts_exit), loader_exit, USEC_PER_MSEC), + format_timespan(ts_span, sizeof(ts_span), loader_exit - loader_start, USEC_PER_MSEC)); + + return 0; +} + +int main(int argc, char* argv[]) { + char s[MAX(FORMAT_TIMESPAN_MAX, FORMAT_TIMESTAMP_MAX)]; + int r; + dual_timestamp fw, l, k; + + test_acpi_fpdt(); + test_efi_loader(); + + dual_timestamp_from_monotonic(&k, 0); + + r = boot_timestamps(NULL, &fw, &l); + if (r < 0) { + log_error("Failed to read variables: %s", strerror(-r)); + return 1; + } + + log_info("Firmware began %s before kernel.", format_timespan(s, sizeof(s), fw.monotonic, 0)); + log_info("Loader began %s before kernel.", format_timespan(s, sizeof(s), l.monotonic, 0)); + log_info("Firmware began %s.", format_timestamp(s, sizeof(s), fw.realtime)); + log_info("Loader began %s.", format_timestamp(s, sizeof(s), l.realtime)); + log_info("Kernel began %s.", format_timestamp(s, sizeof(s), k.realtime)); + + return 0; +} diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c index c9634d42b0..16bf968340 100644 --- a/src/test/test-cgroup-util.c +++ b/src/test/test-cgroup-util.c @@ -23,6 +23,7 @@ #include "util.h" #include "cgroup-util.h" +#include "test-helper.h" static void check_p_d_u(const char *path, int code, const char *result) { _cleanup_free_ char *unit = NULL; @@ -32,12 +33,15 @@ static void check_p_d_u(const char *path, int code, const char *result) { } static void test_path_decode_unit(void) { - check_p_d_u("getty@.service/getty@tty2.service", 0, "getty@tty2.service"); - check_p_d_u("getty@.service/getty@tty2.service/xxx", 0, "getty@tty2.service"); + check_p_d_u("getty@tty2.service", 0, "getty@tty2.service"); + check_p_d_u("getty@tty2.service/", 0, "getty@tty2.service"); + check_p_d_u("getty@tty2.service/xxx", 0, "getty@tty2.service"); check_p_d_u("getty@.service/", -EINVAL, NULL); check_p_d_u("getty@.service", -EINVAL, NULL); check_p_d_u("getty.service", 0, "getty.service"); check_p_d_u("getty", -EINVAL, NULL); + check_p_d_u("getty/waldo", -EINVAL, NULL); + check_p_d_u("_cpu.service", 0, "cpu.service"); } static void check_p_g_u(const char *path, int code, const char *result) { @@ -47,6 +51,18 @@ static void check_p_g_u(const char *path, int code, const char *result) { assert_se(streq_ptr(unit, result)); } +static void test_path_get_unit(void) { + check_p_g_u("/system.slice/foobar.service/sdfdsaf", 0, "foobar.service"); + check_p_g_u("/system.slice/getty@tty5.service", 0, "getty@tty5.service"); + check_p_g_u("/system.slice/getty@tty5.service/aaa/bbb", 0, "getty@tty5.service"); + check_p_g_u("/system.slice/getty@tty5.service/", 0, "getty@tty5.service"); + check_p_g_u("/system.slice/getty@tty6.service/tty5", 0, "getty@tty6.service"); + check_p_g_u("sadfdsafsda", -EINVAL, NULL); + check_p_g_u("/system.slice/getty####@tty6.service/xxx", -EINVAL, NULL); + check_p_g_u("/system.slice/system-waldo.slice/foobar.service/sdfdsaf", 0, "foobar.service"); + check_p_g_u("/system.slice/system-waldo.slice/_cpu.service/sdfdsaf", 0, "cpu.service"); +} + static void check_p_g_u_u(const char *path, int code, const char *result) { _cleanup_free_ char *unit = NULL; @@ -54,39 +70,65 @@ static void check_p_g_u_u(const char *path, int code, const char *result) { assert_se(streq_ptr(unit, result)); } -static void test_path_get_unit(void) { - check_p_g_u("/system/foobar.service/sdfdsaf", 0, "foobar.service"); - check_p_g_u("/system/getty@.service/getty@tty5.service", 0, "getty@tty5.service"); - check_p_g_u("/system/getty@.service/getty@tty5.service/aaa/bbb", 0, "getty@tty5.service"); - check_p_g_u("/system/getty@.service/getty@tty5.service/", 0, "getty@tty5.service"); - check_p_g_u("/system/getty@tty6.service/tty5", 0, "getty@tty6.service"); - check_p_g_u("sadfdsafsda", -ENOENT, NULL); - check_p_g_u("/system/getty####@tty6.service/tty5", -EINVAL, NULL); +static void test_path_get_user_unit(void) { + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/waldo.slice/foobar.service", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1002.slice/session-2.scope/foobar.service/waldo", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar.service/waldo/uuuux", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/waldo/waldo/uuuux", -EINVAL, NULL); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service"); + check_p_g_u_u("/session-2.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service"); + check_p_g_u_u("/xyz.slice/xyz-waldo.slice/session-77.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service"); + check_p_g_u_u("/meh.service", -ENOENT, NULL); + check_p_g_u_u("/session-3.scope/_cpu.service", 0, "cpu.service"); } -static void test_path_get_user_unit(void) { - check_p_g_u_u("/user/lennart/2/systemd-21548/foobar.service", 0, "foobar.service"); - check_p_g_u_u("/user/lennart/2/systemd-21548/foobar.service/waldo", 0, "foobar.service"); - check_p_g_u_u("/user/lennart/2/systemd-21548/foobar.service/waldo/uuuux", 0, "foobar.service"); - check_p_g_u_u("/user/lennart/2/systemd-21548/waldo/waldo/uuuux", -EINVAL, NULL); - check_p_g_u_u("/user/lennart/2/foobar.service", -ENOENT, NULL); - check_p_g_u_u("/user/lennart/2/systemd-21548/foobar@.service/foobar@pie.service/pa/po", 0, "foobar@pie.service"); +static void check_p_g_s(const char *path, int code, const char *result) { + _cleanup_free_ char *s = NULL; + + assert_se(cg_path_get_session(path, &s) == code); + assert_se(streq_ptr(s, result)); } -static void test_get_paths(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL; +static void test_path_get_session(void) { + check_p_g_s("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, "2"); + check_p_g_s("/session-3.scope", 0, "3"); + check_p_g_s("", -ENOENT, 0); +} - assert_se(cg_get_root_path(&a) >= 0); - log_info("Root = %s", a); +static void check_p_g_o_u(const char *path, int code, uid_t result) { + uid_t uid = 0; + + assert_se(cg_path_get_owner_uid(path, &uid) == code); + assert_se(uid == result); +} + +static void test_path_get_owner_uid(void) { + check_p_g_o_u("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, 1000); + check_p_g_o_u("/user.slice/user-1006.slice", 0, 1006); + check_p_g_o_u("", -ENOENT, 0); +} + +static void check_p_g_m_n(const char *path, int code, const char *result) { + _cleanup_free_ char *m = NULL; - assert_se(cg_get_system_path(&b) >= 0); - log_info("System = %s", b); + assert_se(cg_path_get_machine_name(path, &m) == code); + assert_se(streq_ptr(m, result)); +} - assert_se(cg_get_user_path(&c) >= 0); - log_info("User = %s", c); +static void test_path_get_machine_name(void) { + check_p_g_m_n("/user.slice/machine-foobar.scope", 0, "foobar"); + check_p_g_m_n("/machine-foobar.scope", 0, "foobar"); + check_p_g_m_n("/user.slice/user-kuux.slice/machine-foobar.scope", 0, "foobar"); + check_p_g_m_n("/user.slice/user-kuux.slice/machine-foobar.scope/asjhdkj", 0, "foobar"); + check_p_g_m_n("", -ENOENT, NULL); +} - assert_se(cg_get_machine_path("harley", &d) >= 0); - log_info("Machine = %s", d); +static void test_get_paths(void) { + _cleanup_free_ char *a = NULL; + + assert_se(cg_get_root_path(&a) >= 0); + log_info("Root = %s", a); } static void test_proc(void) { @@ -98,7 +140,7 @@ static void test_proc(void) { assert_se(d); FOREACH_DIRENT(de, d, break) { - _cleanup_free_ char *path = NULL, *path_shifted = NULL, *session = NULL, *unit = NULL, *user_unit = NULL, *machine = NULL, *prefix = NULL; + _cleanup_free_ char *path = NULL, *path_shifted = NULL, *session = NULL, *unit = NULL, *user_unit = NULL, *machine = NULL, *prefix = NULL, *slice = NULL; pid_t pid; uid_t uid = (uid_t) -1; @@ -120,8 +162,9 @@ static void test_proc(void) { cg_pid_get_unit(pid, &unit); cg_pid_get_user_unit(pid, &user_unit); cg_pid_get_machine_name(pid, &machine); + cg_pid_get_slice(pid, &slice); - printf("%lu\t%s\t%s\t%s\t%lu\t%s\t%s\t%s\t%s\n", + printf("%lu\t%s\t%s\t%s\t%lu\t%s\t%s\t%s\t%s\t%s\n", (unsigned long) pid, path, prefix, @@ -130,7 +173,8 @@ static void test_proc(void) { session, unit, user_unit, - machine); + machine, + slice); } } @@ -170,14 +214,37 @@ static void test_controller_is_valid(void) { assert_se(!cg_controller_is_valid("tatü", false)); } +static void test_slice_to_path_one(const char *unit, const char *path, int error) { + _cleanup_free_ char *ret = NULL; + + assert_se(cg_slice_to_path(unit, &ret) == error); + assert_se(streq_ptr(ret, path)); +} + +static void test_slice_to_path(void) { + + test_slice_to_path_one("foobar.slice", "foobar.slice", 0); + test_slice_to_path_one("foobar-waldo.slice", "foobar.slice/foobar-waldo.slice", 0); + test_slice_to_path_one("foobar-waldo.service", NULL, -EINVAL); + test_slice_to_path_one("-.slice", NULL, -EINVAL); + test_slice_to_path_one("-foo-.slice", NULL, -EINVAL); + test_slice_to_path_one("-foo.slice", NULL, -EINVAL); + test_slice_to_path_one("a-b.slice", "a.slice/a-b.slice", 0); + test_slice_to_path_one("a-b-c-d-e.slice", "a.slice/a-b.slice/a-b-c.slice/a-b-c-d.slice/a-b-c-d-e.slice", 0); +} + int main(void) { test_path_decode_unit(); test_path_get_unit(); test_path_get_user_unit(); - test_get_paths(); + test_path_get_session(); + test_path_get_owner_uid(); + test_path_get_machine_name(); + TEST_REQ_RUNNING_SYSTEMD(test_get_paths()); test_proc(); - test_escape(); + TEST_REQ_RUNNING_SYSTEMD(test_escape()); test_controller_is_valid(); + test_slice_to_path(); return 0; } diff --git a/src/test/test-cgroup.c b/src/test/test-cgroup.c index 3a3489d6a2..2a0ce27206 100644 --- a/src/test/test-cgroup.c +++ b/src/test/test-cgroup.c @@ -31,10 +31,10 @@ int main(int argc, char*argv[]) { char *path; char *c, *p; - assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a", NULL) == 0); - assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a", NULL) == 0); - assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b", NULL) == 0); - assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-c", NULL) == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b") == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, "/test-b/test-c") == 0); assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0) == 0); assert_se(cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid(), &path) == 0); diff --git a/src/test/test-device-nodes.c b/src/test/test-device-nodes.c new file mode 100644 index 0000000000..2f3dedb90f --- /dev/null +++ b/src/test/test-device-nodes.c @@ -0,0 +1,55 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Dave Reisner + + 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 <sys/types.h> + +#include "device-nodes.h" +#include "util.h" + +/* helpers for test_encode_devnode_name */ +static char *do_encode_string(const char *in) { + size_t out_len = strlen(in) * 4; + char *out = malloc(out_len); + + assert_se(out); + assert_se(encode_devnode_name(in, out, out_len) >= 0); + puts(out); + + return out; +} + +static bool expect_encoded_as(const char *in, const char *expected) { + _cleanup_free_ char *encoded = do_encode_string(in); + return streq(encoded, expected); +} + +static void test_encode_devnode_name(void) { + assert_se(expect_encoded_as("systemd sucks", "systemd\\x20sucks")); + assert_se(expect_encoded_as("pinkiepie", "pinkiepie")); + assert_se(expect_encoded_as("valÃd\\ųtf8", "valÃd\\x5cųtf8")); + assert_se(expect_encoded_as("s/ash/ng", "s\\x2fash\\x2fng")); +} + +int main(int argc, char *argv[]) { + test_encode_devnode_name(); + + return 0; +} diff --git a/src/test/test-efivars.c b/src/test/test-efivars.c deleted file mode 100644 index 43ea5917b6..0000000000 --- a/src/test/test-efivars.c +++ /dev/null @@ -1,47 +0,0 @@ -/*-*- 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 "log.h" -#include "efivars.h" - -int main(int argc, char* argv[]) { - char s[MAX(FORMAT_TIMESPAN_MAX, FORMAT_TIMESTAMP_MAX)]; - int r; - dual_timestamp fw, l, k; - - dual_timestamp_from_monotonic(&k, 0); - - r = efi_get_boot_timestamps(NULL, &fw, &l); - if (r < 0) { - log_error("Failed to read variables: %s", strerror(-r)); - return 1; - } - - log_info("Firmware began %s before kernel.", format_timespan(s, sizeof(s), fw.monotonic, 0)); - log_info("Loader began %s before kernel.", format_timespan(s, sizeof(s), l.monotonic, 0)); - - log_info("Firmware began %s.", format_timestamp(s, sizeof(s), fw.realtime)); - log_info("Loader began %s.", format_timestamp(s, sizeof(s), l.realtime)); - log_info("Kernel began %s.", format_timestamp(s, sizeof(s), k.realtime)); - - return 0; -} diff --git a/src/test/test-engine.c b/src/test/test-engine.c index 0f3862226a..20ae103a19 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -33,7 +33,7 @@ int main(int argc, char *argv[]) { assert_se(set_unit_path("test") >= 0); - assert_se(manager_new(SYSTEMD_SYSTEM, &m) >= 0); + assert_se(manager_new(SYSTEMD_SYSTEM, false, &m) >= 0); printf("Load1:\n"); assert_se(manager_load_unit(m, "a.service", NULL, NULL, &a) >= 0); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index d56f7cc856..06f3e28288 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -27,9 +27,12 @@ #include "fileio.h" #include "strv.h" #include "env-util.h" +#include "def.h" +#include "ctype.h" static void test_parse_env_file(void) { - char t[] = "/tmp/test-parse-env-file-XXXXXX"; + char t[] = "/tmp/test-fileio-in-XXXXXX", + p[] = "/tmp/test-fileio-out-XXXXXX"; int fd, r; FILE *f; _cleanup_free_ char *one = NULL, *two = NULL, *three = NULL, *four = NULL, *five = NULL, @@ -38,6 +41,8 @@ static void test_parse_env_file(void) { char **i; unsigned k; + assert_se(mktemp(p)); + fd = mkostemp(t, O_CLOEXEC); assert_se(fd >= 0); @@ -83,7 +88,7 @@ static void test_parse_env_file(void) { assert_se(streq(a[9], "ten=")); assert_se(a[10] == NULL); - strv_env_clean_log(a, "/tmp/test-fileio"); + strv_env_clean_log(a, "test"); k = 0; STRV_FOREACH(i, b) { @@ -129,17 +134,167 @@ static void test_parse_env_file(void) { assert_se(streq(nine, "nineval")); assert_se(ten == NULL); - r = write_env_file("/tmp/test-fileio", a); + r = write_env_file(p, a); assert_se(r >= 0); - r = load_env_file("/tmp/test-fileio", NULL, &b); + r = load_env_file(p, NULL, &b); assert_se(r >= 0); unlink(t); - unlink("/tmp/test-fileio"); + unlink(p); +} + +static void test_parse_multiline_env_file(void) { + char t[] = "/tmp/test-fileio-in-XXXXXX", + p[] = "/tmp/test-fileio-out-XXXXXX"; + int fd, r; + FILE *f; + _cleanup_strv_free_ char **a = NULL, **b = NULL; + char **i; + + assert_se(mktemp(p)); + + fd = mkostemp(t, O_CLOEXEC); + assert_se(fd >= 0); + + f = fdopen(fd, "w"); + assert_se(f); + + fputs("one=BAR\\\n" + " VAR\\\n" + "\tGAR\n" + "#comment\n" + "two=\"bar\\\n" + " var\\\n" + "\tgar\"\n" + "#comment\n" + "tri=\"bar \\\n" + " var \\\n" + "\tgar \"\n", f); + + fflush(f); + fclose(f); + + r = load_env_file(t, NULL, &a); + assert_se(r >= 0); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(streq(a[0], "one=BAR VAR\tGAR")); + assert_se(streq(a[1], "two=bar var\tgar")); + assert_se(streq(a[2], "tri=bar var \tgar ")); + assert_se(a[3] == NULL); + + r = write_env_file(p, a); + assert_se(r >= 0); + + r = load_env_file(p, NULL, &b); + assert_se(r >= 0); + + unlink(t); + unlink(p); +} + + +static void test_executable_is_script(void) { + char t[] = "/tmp/test-executable-XXXXXX"; + int fd, r; + FILE *f; + char *command; + + fd = mkostemp(t, O_CLOEXEC); + assert_se(fd >= 0); + + f = fdopen(fd, "w"); + assert_se(f); + + fputs("#! /bin/script -a -b \ngoo goo", f); + fflush(f); + + r = executable_is_script(t, &command); + assert_se(r > 0); + assert_se(streq(command, "/bin/script")); + free(command); + + r = executable_is_script("/bin/sh", &command); + assert_se(r == 0); + + r = executable_is_script("/usr/bin/yum", &command); + assert_se(r > 0 || r == -ENOENT); + if (r > 0) { + assert_se(startswith(command, "/")); + free(command); + } + + fclose(f); + unlink(t); +} + +static void test_status_field(void) { + _cleanup_free_ char *t = NULL, *p = NULL, *s = NULL, *z = NULL; + unsigned long long total = 0, buffers = 0; + int r; + + assert_se(get_status_field("/proc/self/status", "\nThreads:", &t) == 0); + puts(t); + assert_se(streq(t, "1")); + + r = get_status_field("/proc/meminfo", "MemTotal:", &p); + if (r != -ENOENT) { + assert(r == 0); + puts(p); + assert_se(safe_atollu(p, &total) == 0); + } + + r = get_status_field("/proc/meminfo", "\nBuffers:", &s); + if (r != -ENOENT) { + assert(r == 0); + puts(s); + assert_se(safe_atollu(s, &buffers) == 0); + } + + if (p && t) + assert(buffers < total); + + /* Seccomp should be a good test for field full of zeros. */ + r = get_status_field("/proc/meminfo", "\nSeccomp:", &z); + if (r != -ENOENT) { + assert(r == 0); + puts(z); + assert_se(safe_atollu(z, &buffers) == 0); + } +} + +static void test_capeff(void) { + int pid, p; + + for (pid = 0; pid < 2; pid++) { + _cleanup_free_ char *capeff = NULL; + int r; + + r = get_process_capeff(0, &capeff); + log_info("capeff: '%s' (r=%d)", capeff, r); + + if (r == -ENOENT || r == -EPERM) + return; + + assert(r == 0); + assert(*capeff); + p = capeff[strspn(capeff, DIGITS "abcdefABCDEF")]; + assert(!p || isspace(p)); + } } int main(int argc, char *argv[]) { + log_parse_environment(); + log_open(); + test_parse_env_file(); + test_parse_multiline_env_file(); + test_executable_is_script(); + test_status_field(); + test_capeff(); + return 0; } diff --git a/src/test/test-hashmap.c b/src/test/test-hashmap.c index 2aead79bb1..56a9b58c24 100644 --- a/src/test/test-hashmap.c +++ b/src/test/test-hashmap.c @@ -467,10 +467,36 @@ static void test_hashmap_get(void) { hashmap_free_free(m); } +static void test_hashmap_many(void) { + Hashmap *h; + unsigned i; + +#define N_ENTRIES 100000 + + assert_se(h = hashmap_new(NULL, NULL)); + + for (i = 1; i < N_ENTRIES*3; i+=3) { + assert_se(hashmap_put(h, UINT_TO_PTR(i), UINT_TO_PTR(i)) >= 0); + assert_se(PTR_TO_UINT(hashmap_get(h, UINT_TO_PTR(i))) == i); + } + + for (i = 1; i < N_ENTRIES*3; i++) + assert_se(hashmap_contains(h, UINT_TO_PTR(i)) == (i % 3 == 1)); + + log_info("%u <= %u * 0.75 = %g", hashmap_size(h), hashmap_buckets(h), hashmap_buckets(h) * 0.75); + + assert_se(hashmap_size(h) <= hashmap_buckets(h) * 0.75); + assert_se(hashmap_size(h) == N_ENTRIES); + + hashmap_free(h); +} + static void test_uint64_compare_func(void) { - assert_se(uint64_compare_func("a", "a") == 0); - assert_se(uint64_compare_func("a", "b") == -1); - assert_se(uint64_compare_func("b", "a") == 1); + const uint64_t a = 0x100, b = 0x101; + + assert_se(uint64_compare_func(&a, &a) == 0); + assert_se(uint64_compare_func(&a, &b) == -1); + assert_se(uint64_compare_func(&b, &a) == 1); } static void test_trivial_compare_func(void) { @@ -484,8 +510,7 @@ static void test_string_compare_func(void) { assert_se(string_compare_func("fred", "fred") == 0); } -int main(int argc, const char *argv[]) -{ +int main(int argc, const char *argv[]) { test_hashmap_copy(); test_hashmap_get_strv(); test_hashmap_move_one(); @@ -502,6 +527,7 @@ int main(int argc, const char *argv[]) test_hashmap_isempty(); test_hashmap_get(); test_hashmap_size(); + test_hashmap_many(); test_uint64_compare_func(); test_trivial_compare_func(); test_string_compare_func(); diff --git a/src/test/test-helper.h b/src/test/test-helper.h new file mode 100644 index 0000000000..92864edb54 --- /dev/null +++ b/src/test/test-helper.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Holger Hans Peter Freyther + + 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 "sd-daemon.h" + +#define TEST_REQ_RUNNING_SYSTEMD(x) \ + if (sd_booted() > 0) { \ + x; \ + } else { \ + printf("systemd not booted skipping '%s'\n", #x); \ + } diff --git a/src/test/test-id128.c b/src/test/test-id128.c index 2ed8e292e6..7b92758174 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -25,6 +25,7 @@ #include "util.h" #include "macro.h" +#include "sd-daemon.h" #define ID128_WALDI SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10) #define STR_WALDI "0102030405060708090a0b0c0d0e0f10" @@ -41,11 +42,13 @@ int main(int argc, char *argv[]) { assert_se(sd_id128_from_string(t, &id2) == 0); assert_se(sd_id128_equal(id, id2)); - assert_se(sd_id128_get_machine(&id) == 0); - printf("machine: %s\n", sd_id128_to_string(id, t)); + if (sd_booted() > 0) { + assert_se(sd_id128_get_machine(&id) == 0); + printf("machine: %s\n", sd_id128_to_string(id, t)); - assert_se(sd_id128_get_boot(&id) == 0); - printf("boot: %s\n", sd_id128_to_string(id, t)); + assert_se(sd_id128_get_boot(&id) == 0); + printf("boot: %s\n", sd_id128_to_string(id, t)); + } printf("waldi: %s\n", sd_id128_to_string(ID128_WALDI, t)); assert_se(streq(t, STR_WALDI)); diff --git a/src/test/test-libudev.c b/src/test/test-libudev.c index caa3b4d14c..716767ba5f 100644 --- a/src/test/test-libudev.c +++ b/src/test/test-libudev.c @@ -430,7 +430,7 @@ static int test_enumerate(struct udev *udev, const char *subsystem) } static int test_hwdb(struct udev *udev, const char *modalias) { - struct udev_hwdb * hwdb; + struct udev_hwdb *hwdb; struct udev_list_entry *entry; hwdb = udev_hwdb_new(udev); diff --git a/src/test/test-list.c b/src/test/test-list.c new file mode 100644 index 0000000000..2710504765 --- /dev/null +++ b/src/test/test-list.c @@ -0,0 +1,109 @@ +/*** + This file is part of systemd + + Copyright 2013 Jan Janssen + + 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 "list.h" +#include "util.h" + +int main(int argc, const char *argv[]) { + size_t i; + typedef struct list_item { + LIST_FIELDS(struct list_item, item); + } list_item; + LIST_HEAD(list_item, head); + list_item items[4]; + list_item *cursor; + + LIST_HEAD_INIT(list_item, head); + assert_se(head == NULL); + + for (i = 0; i < ELEMENTSOF(items); i++) { + LIST_INIT(list_item, item, &items[i]); + assert_se(LIST_JUST_US(item, &items[i])); + LIST_PREPEND(list_item, item, head, &items[i]); + } + + assert_se(!LIST_JUST_US(item, head)); + + assert_se(items[0].item_next == NULL); + assert_se(items[1].item_next == &items[0]); + assert_se(items[2].item_next == &items[1]); + assert_se(items[3].item_next == &items[2]); + + assert_se(items[0].item_prev == &items[1]); + assert_se(items[1].item_prev == &items[2]); + assert_se(items[2].item_prev == &items[3]); + assert_se(items[3].item_prev == NULL); + + LIST_FIND_HEAD(list_item, item, &items[0], cursor); + assert_se(cursor == &items[3]); + + LIST_FIND_TAIL(list_item, item, &items[3], cursor); + assert_se(cursor == &items[0]); + + LIST_REMOVE(list_item, item, head, &items[1]); + assert_se(LIST_JUST_US(item, &items[1])); + + assert_se(items[0].item_next == NULL); + assert_se(items[2].item_next == &items[0]); + assert_se(items[3].item_next == &items[2]); + + assert_se(items[0].item_prev == &items[2]); + assert_se(items[2].item_prev == &items[3]); + assert_se(items[3].item_prev == NULL); + + LIST_INSERT_AFTER(list_item, item, head, &items[3], &items[1]); + assert_se(items[0].item_next == NULL); + assert_se(items[2].item_next == &items[0]); + assert_se(items[1].item_next == &items[2]); + assert_se(items[3].item_next == &items[1]); + + assert_se(items[0].item_prev == &items[2]); + assert_se(items[2].item_prev == &items[1]); + assert_se(items[1].item_prev == &items[3]); + assert_se(items[3].item_prev == NULL); + + LIST_REMOVE(list_item, item, head, &items[0]); + assert_se(LIST_JUST_US(item, &items[0])); + + assert_se(items[2].item_next == NULL); + assert_se(items[1].item_next == &items[2]); + assert_se(items[3].item_next == &items[1]); + + assert_se(items[2].item_prev == &items[1]); + assert_se(items[1].item_prev == &items[3]); + assert_se(items[3].item_prev == NULL); + + LIST_REMOVE(list_item, item, head, &items[1]); + assert_se(LIST_JUST_US(item, &items[1])); + + assert_se(items[2].item_next == NULL); + assert_se(items[3].item_next == &items[2]); + + assert_se(items[2].item_prev == &items[3]); + assert_se(items[3].item_prev == NULL); + + LIST_REMOVE(list_item, item, head, &items[2]); + assert_se(LIST_JUST_US(item, &items[2])); + assert_se(LIST_JUST_US(item, head)); + + LIST_REMOVE(list_item, item, head, &items[3]); + assert_se(LIST_JUST_US(item, &items[3])); + + return 0; +} diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 127e17803f..ed3b315a61 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -56,7 +56,7 @@ static void test_path(void) { assert_se(streq(path_get_file_name("file.../"), "")); #define test_parent(x, y) { \ - char *z; \ + char _cleanup_free_ *z = NULL; \ int r = path_get_parent(x, &z); \ printf("expected: %s\n", y ? y : "error"); \ printf("actual: %s\n", r<0 ? "error" : z); \ @@ -83,7 +83,84 @@ static void test_path(void) { } } +static void test_find_binary(void) { + char *p; + + assert(find_binary("/bin/sh", &p) == 0); + puts(p); + assert(streq(p, "/bin/sh")); + free(p); + + assert(find_binary("./test-path-util", &p) == 0); + puts(p); + assert(endswith(p, "/test-path-util")); + assert(path_is_absolute(p)); + free(p); + + assert(find_binary("sh", &p) == 0); + puts(p); + assert(endswith(p, "/sh")); + assert(path_is_absolute(p)); + free(p); + + assert(find_binary("xxxx-xxxx", &p) == -ENOENT); +} + +static void test_prefixes(void) { + static const char* values[] = { "/a/b/c/d", "/a/b/c", "/a/b", "/a", "", NULL}; + unsigned i; + char s[PATH_MAX]; + bool b; + + i = 0; + PATH_FOREACH_PREFIX_MORE(s, "/a/b/c/d") { + log_error("---%s---", s); + assert_se(streq(s, values[i++])); + } + assert_se(values[i] == NULL); + + i = 1; + PATH_FOREACH_PREFIX(s, "/a/b/c/d") { + log_error("---%s---", s); + assert_se(streq(s, values[i++])); + } + assert_se(values[i] == NULL); + + i = 0; + PATH_FOREACH_PREFIX_MORE(s, "////a////b////c///d///////") + assert_se(streq(s, values[i++])); + assert_se(values[i] == NULL); + + i = 1; + PATH_FOREACH_PREFIX(s, "////a////b////c///d///////") + assert_se(streq(s, values[i++])); + assert_se(values[i] == NULL); + + PATH_FOREACH_PREFIX(s, "////") + assert_not_reached("Wut?"); + + b = false; + PATH_FOREACH_PREFIX_MORE(s, "////") { + assert_se(!b); + assert_se(streq(s, "")); + b = true; + } + assert_se(b); + + PATH_FOREACH_PREFIX(s, "") + assert_not_reached("wut?"); + + b = false; + PATH_FOREACH_PREFIX_MORE(s, "") { + assert(!b); + assert(streq(s, "")); + b = true; + } +} + int main(void) { test_path(); + test_find_binary(); + test_prefixes(); return 0; } diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c index ba0aacf79d..1bbe867317 100644 --- a/src/test/test-sched-prio.c +++ b/src/test/test-sched-prio.c @@ -34,8 +34,8 @@ int main(int argc, char *argv[]) { /* prepare the test */ assert_se(set_unit_path(TEST_DIR) >= 0); - r = manager_new(SYSTEMD_USER, &m); - if (r == -EPERM) { + r = manager_new(SYSTEMD_USER, false, &m); + if (r == -EPERM || r == -EACCES) { puts("manager_new: Permission denied. Skipping test."); return EXIT_TEST_SKIP; } @@ -88,5 +88,7 @@ int main(int argc, char *argv[]) { assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); assert_se(ser->exec_context.cpu_sched_priority == 99); + manager_free(m); + return EXIT_SUCCESS; } diff --git a/src/test/test-sleep.c b/src/test/test-sleep.c index c3cb9c531d..a1020ad14c 100644 --- a/src/test/test-sleep.c +++ b/src/test/test-sleep.c @@ -29,7 +29,7 @@ #include "sleep-config.h" #include "strv.h" -int main(int argc, char* argv[]) { +static void test_sleep(void) { _cleanup_strv_free_ char **standby = strv_new("standby", NULL), **mem = strv_new("mem", NULL), @@ -40,18 +40,28 @@ int main(int argc, char* argv[]) { **shutdown = strv_new("shutdown", NULL), **freez = strv_new("freeze", NULL); - log_info("Can Standby: %s", yes_no(can_sleep_state(standby) > 0)); - log_info("Can Suspend: %s", yes_no(can_sleep_state(mem) > 0)); - log_info("Can Hibernate: %s", yes_no(can_sleep_state(disk) > 0)); - log_info("Can Hibernate+Suspend (Hybrid-Sleep): %s", yes_no(can_sleep_disk(suspend) > 0)); - log_info("Can Hibernate+Reboot: %s", yes_no(can_sleep_disk(reboot) > 0)); - log_info("Can Hibernate+Platform: %s", yes_no(can_sleep_disk(platform) > 0)); - log_info("Can Hibernate+Shutdown: %s", yes_no(can_sleep_disk(shutdown) > 0)); - log_info("Can Freeze: %s", yes_no(can_sleep_disk(freez) > 0)); + log_info("Standby configured: %s", yes_no(can_sleep_state(standby) > 0)); + log_info("Suspend configured: %s", yes_no(can_sleep_state(mem) > 0)); + log_info("Hibernate configured: %s", yes_no(can_sleep_state(disk) > 0)); + log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(can_sleep_disk(suspend) > 0)); + log_info("Hibernate+Reboot configured: %s", yes_no(can_sleep_disk(reboot) > 0)); + log_info("Hibernate+Platform configured: %s", yes_no(can_sleep_disk(platform) > 0)); + log_info("Hibernate+Shutdown configured: %s", yes_no(can_sleep_disk(shutdown) > 0)); + log_info("Freeze configured: %s", yes_no(can_sleep_state(freez) > 0)); log_info("Suspend configured and possible: %s", yes_no(can_sleep("suspend") > 0)); log_info("Hibernation configured and possible: %s", yes_no(can_sleep("hibernate") > 0)); log_info("Hybrid-sleep configured and possible: %s", yes_no(can_sleep("hybrid-sleep") > 0)); +} + +int main(int argc, char* argv[]) { + log_parse_environment(); + log_open(); + + if (getuid() != 0) + log_warning("This program is unlikely to work for unpriviledged users"); + + test_sleep(); return 0; } diff --git a/src/test/test-strv.c b/src/test/test-strv.c index 074e1bb3d4..c3d536d057 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -27,65 +27,95 @@ #include "strv.h" static void test_specifier_printf(void) { - _cleanup_free_ char *w = NULL; - - const Specifier table[] = { + static const Specifier table[] = { { 'a', specifier_string, (char*) "AAAA" }, { 'b', specifier_string, (char*) "BBBB" }, - { 0, NULL, NULL } + { 'm', specifier_machine_id, NULL }, + { 'B', specifier_boot_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} }; - w = specifier_printf("xxx a=%a b=%b yyy", table, NULL); - puts(w); + _cleanup_free_ char *w = NULL; + int r; + r = specifier_printf("xxx a=%a b=%b yyy", table, NULL, &w); + assert_se(r >= 0); assert_se(w); + + puts(w); assert_se(streq(w, "xxx a=AAAA b=BBBB yyy")); + + free(w); + r = specifier_printf("machine=%m, boot=%B, host=%H, version=%v", table, NULL, &w); + assert_se(r >= 0); + assert_se(w); + puts(w); } -static void test_strv_find(void) { - const char * const input_table[] = { - "one", - "two", - "three", - NULL - }; +static const char* const input_table_multiple[] = { + "one", + "two", + "three", + NULL, +}; + +static const char* const input_table_one[] = { + "one", + NULL, +}; + +static const char* const input_table_none[] = { + NULL, +}; + +static const char* const input_table_quotes[] = { + "\"", + "'", + "\"\"", + "\\", + "\\\\", + NULL, +}; +#define QUOTES_STRING \ + "\"\\\"\" " \ + "\"\\\'\" " \ + "\"\\\"\\\"\" " \ + "\"\\\\\" " \ + "\"\\\\\\\\\"" + +static const char * const input_table_spaces[] = { + " ", + "' '", + "\" ", + " \"", + " \\\\ ", + NULL, +}; +#define SPACES_STRING \ + "\" \" " \ + "\"\\' \\'\" " \ + "\"\\\" \" " \ + "\" \\\"\" " \ + "\" \\\\\\\\ \"" - assert_se(strv_find((char **)input_table, "three")); - assert_se(!strv_find((char **)input_table, "four")); +static void test_strv_find(void) { + assert_se(strv_find((char **)input_table_multiple, "three")); + assert_se(!strv_find((char **)input_table_multiple, "four")); } static void test_strv_find_prefix(void) { - const char * const input_table[] = { - "one", - "two", - "three", - NULL - }; - - assert_se(strv_find_prefix((char **)input_table, "o")); - assert_se(strv_find_prefix((char **)input_table, "one")); - assert_se(strv_find_prefix((char **)input_table, "")); - assert_se(!strv_find_prefix((char **)input_table, "xxx")); - assert_se(!strv_find_prefix((char **)input_table, "onee")); + assert_se(strv_find_prefix((char **)input_table_multiple, "o")); + assert_se(strv_find_prefix((char **)input_table_multiple, "one")); + assert_se(strv_find_prefix((char **)input_table_multiple, "")); + assert_se(!strv_find_prefix((char **)input_table_multiple, "xxx")); + assert_se(!strv_find_prefix((char **)input_table_multiple, "onee")); } static void test_strv_join(void) { _cleanup_free_ char *p = NULL, *q = NULL, *r = NULL, *s = NULL, *t = NULL; - const char * const input_table_multiple[] = { - "one", - "two", - "three", - NULL - }; - const char * const input_table_one[] = { - "one", - NULL - }; - const char * const input_table_none[] = { - NULL - }; - p = strv_join((char **)input_table_multiple, ", "); assert_se(p); assert_se(streq(p, "one, two, three")); @@ -107,6 +137,25 @@ static void test_strv_join(void) { assert_se(streq(t, "")); } +static void test_strv_quote_unquote(const char* const *split, const char *quoted) { + _cleanup_free_ char *p; + _cleanup_strv_free_ char **s; + char **t; + + p = strv_join_quoted((char **)split); + printf("-%s- --- -%s-\n", p, quoted); /* fprintf deals with NULL, puts does not */ + assert_se(p); + assert_se(streq(p, quoted)); + + s = strv_split_quoted(quoted); + assert_se(s); + STRV_FOREACH(t, s) { + assert_se(*t); + assert_se(streq(*t, *split)); + split++; + } +} + static void test_strv_split_nulstr(void) { _cleanup_strv_free_ char **l = NULL; const char nulstr[] = "str0\0str1\0str2\0str3\0"; @@ -253,6 +302,13 @@ int main(int argc, char *argv[]) { test_strv_find(); test_strv_find_prefix(); test_strv_join(); + + test_strv_quote_unquote(input_table_multiple, "\"one\" \"two\" \"three\""); + test_strv_quote_unquote(input_table_one, "\"one\""); + test_strv_quote_unquote(input_table_none, ""); + test_strv_quote_unquote(input_table_quotes, QUOTES_STRING); + test_strv_quote_unquote(input_table_spaces, SPACES_STRING); + test_strv_split_nulstr(); test_strv_parse_nulstr(); test_strv_overlap(); diff --git a/src/test/test-tables.c b/src/test/test-tables.c new file mode 100644 index 0000000000..3b7800cf37 --- /dev/null +++ b/src/test/test-tables.c @@ -0,0 +1,105 @@ +/*** + 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 "automount.h" +#include "cgroup.h" +#include "condition.h" +#include "device.h" +#include "execute.h" +#include "exit-status.h" +#include "install.h" +#include "job.h" +#include "kill.h" +#include "log.h" +#include "logs-show.h" +#include "mount.h" +#include "path-lookup.h" +#include "path.h" +#include "scope.h" +#include "service.h" +#include "slice.h" +#include "snapshot.h" +#include "socket-util.h" +#include "socket.h" +#include "swap.h" +#include "target.h" +#include "timer.h" +#include "unit-name.h" +#include "unit.h" +#include "util.h" +#include "syscall-list.h" + +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table(automount_result, AUTOMOUNT_RESULT); + test_table(automount_state, AUTOMOUNT_STATE); + test_table(cgroup_device_policy, CGROUP_DEVICE_POLICY); + test_table(condition_type, CONDITION_TYPE); + test_table(device_state, DEVICE_STATE); + test_table(exec_input, EXEC_INPUT); + test_table(exec_output, EXEC_OUTPUT); + test_table(job_mode, JOB_MODE); + test_table(job_result, JOB_RESULT); + test_table(job_state, JOB_STATE); + test_table(job_type, JOB_TYPE); + test_table(kill_mode, KILL_MODE); + test_table(kill_who, KILL_WHO); + test_table(log_target, LOG_TARGET); + test_table(mount_exec_command, MOUNT_EXEC_COMMAND); + test_table(mount_result, MOUNT_RESULT); + test_table(mount_state, MOUNT_STATE); + test_table(notify_access, NOTIFY_ACCESS); + test_table(output_mode, OUTPUT_MODE); + test_table(path_result, PATH_RESULT); + test_table(path_state, PATH_STATE); + test_table(path_type, PATH_TYPE); + test_table(scope_result, SCOPE_RESULT); + test_table(scope_state, SCOPE_STATE); + test_table(service_exec_command, SERVICE_EXEC_COMMAND); + test_table(service_restart, SERVICE_RESTART); + test_table(service_result, SERVICE_RESULT); + test_table(service_state, SERVICE_STATE); + test_table(service_type, SERVICE_TYPE); + test_table(slice_state, SLICE_STATE); + test_table(snapshot_state, SNAPSHOT_STATE); + test_table(socket_address_bind_ipv6_only, SOCKET_ADDRESS_BIND_IPV6_ONLY); + test_table(socket_exec_command, SOCKET_EXEC_COMMAND); + test_table(socket_result, SOCKET_RESULT); + test_table(socket_state, SOCKET_STATE); + test_table(start_limit_action, SERVICE_START_LIMIT); + test_table(swap_exec_command, SWAP_EXEC_COMMAND); + test_table(swap_result, SWAP_RESULT); + test_table(swap_state, SWAP_STATE); + test_table(systemd_running_as, SYSTEMD_RUNNING_AS); + test_table(target_state, TARGET_STATE); + test_table(timer_base, TIMER_BASE); + test_table(timer_result, TIMER_RESULT); + test_table(timer_state, TIMER_STATE); + test_table(unit_active_state, UNIT_ACTIVE_STATE); + test_table(unit_dependency, UNIT_DEPENDENCY); + test_table(unit_file_change_type, UNIT_FILE_CHANGE_TYPE); + test_table(unit_file_state, UNIT_FILE_STATE); + test_table(unit_load_state, UNIT_LOAD_STATE); + test_table(unit_type, UNIT_TYPE); + + _test_table("syscall", syscall_to_name, syscall_from_name, syscall_max(), true); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index a7fe77af24..0413ae2117 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -35,8 +35,9 @@ #include "load-fragment.h" #include "strv.h" #include "fileio.h" +#include "test-helper.h" -static void test_unit_file_get_set(void) { +static int test_unit_file_get_set(void) { int r; Hashmap *h; Iterator i; @@ -46,13 +47,17 @@ static void test_unit_file_get_set(void) { assert(h); r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h); - log_info("unit_file_get_list: %s", strerror(-r)); - assert(r >= 0); + log_full(r == 0 ? LOG_INFO : LOG_ERR, + "unit_file_get_list: %s", strerror(-r)); + if (r < 0) + return EXIT_FAILURE; HASHMAP_FOREACH(p, h, i) printf("%s = %s\n", p->path, unit_file_state_to_string(p->state)); unit_file_list_free(h); + + return 0; } static void check_execcommand(ExecCommand *c, @@ -297,17 +302,18 @@ static void test_install_printf(void) { _cleanup_free_ char *mid, *bid, *host; - assert_se((mid = specifier_machine_id('m', NULL, NULL))); - assert_se((bid = specifier_boot_id('b', NULL, NULL))); + assert_se(specifier_machine_id('m', NULL, NULL, &mid) >= 0 && mid); + assert_se(specifier_boot_id('b', NULL, NULL, &bid) >= 0 && bid); assert_se((host = gethostname_malloc())); #define expect(src, pattern, result) \ do { \ - _cleanup_free_ char *t = install_full_printf(&src, pattern); \ + _cleanup_free_ char *t = NULL; \ _cleanup_free_ char \ *d1 = strdup(i.name), \ *d2 = strdup(i.path), \ *d3 = strdup(i.user); \ + assert_se(install_full_printf(&src, pattern, &t) >= 0 || !result); \ memzero(i.name, strlen(i.name)); \ memzero(i.path, strlen(i.path)); \ memzero(i.user, strlen(i.user)); \ @@ -351,17 +357,18 @@ static void test_install_printf(void) { #pragma GCC diagnostic pop int main(int argc, char *argv[]) { + int r; log_parse_environment(); log_open(); - test_unit_file_get_set(); + r = test_unit_file_get_set(); test_config_parse_exec(); test_load_env_file_1(); test_load_env_file_2(); test_load_env_file_3(); test_load_env_file_4(); - test_install_printf(); + TEST_REQ_RUNNING_SYSTEMD(test_install_printf()); - return 0; + return r; } diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index 86cb2b8da6..67ccdd4228 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -34,6 +34,7 @@ #include "specifier.h" #include "util.h" #include "macro.h" +#include "test-helper.h" static void test_replacements(void) { #define expect(pattern, repl, expected) \ @@ -116,15 +117,15 @@ static int test_unit_printf(void) { _cleanup_free_ char *mid, *bid, *host, *root_uid; struct passwd *root; - assert_se((mid = specifier_machine_id('m', NULL, NULL))); - assert_se((bid = specifier_boot_id('b', NULL, NULL))); + assert_se(specifier_machine_id('m', NULL, NULL, &mid) >= 0 && mid); + assert_se(specifier_boot_id('b', NULL, NULL, &bid) >= 0 && bid); assert_se((host = gethostname_malloc())); assert_se((root = getpwnam("root"))); assert_se(asprintf(&root_uid, "%d", (int) root->pw_uid) > 0); - r = manager_new(SYSTEMD_USER, &m); - if (r == -EPERM) { + r = manager_new(SYSTEMD_USER, false, &m); + if (r == -EPERM || r == -EACCES) { puts("manager_new: Permission denied. Skipping test."); return EXIT_TEST_SKIP; } @@ -133,8 +134,8 @@ static int test_unit_printf(void) { #define expect(unit, pattern, expected) \ { \ char *e; \ - _cleanup_free_ char *t = \ - unit_full_printf(unit, pattern); \ + _cleanup_free_ char *t; \ + assert_se(unit_full_printf(unit, pattern, &t) >= 0); \ printf("result: %s\nexpect: %s\n", t, expected); \ if ((e = endswith(expected, "*"))) \ assert(strncmp(t, e, e-expected)); \ @@ -190,10 +191,14 @@ static int test_unit_printf(void) { expect(u2, "%H", host); expect(u2, "%t", "/run/user/*"); + manager_free(m); + return 0; } int main(int argc, char* argv[]) { + int rc = 0; test_replacements(); - return test_unit_printf(); + TEST_REQ_RUNNING_SYSTEMD(rc = test_unit_printf()); + return rc; } diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c new file mode 100644 index 0000000000..7bd0db173a --- /dev/null +++ b/src/test/test-utf8.c @@ -0,0 +1,76 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Dave Reisner + + 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 "utf8.h" +#include "util.h" + +static void test_utf8_is_printable(void) { + assert_se(utf8_is_printable("ascii is valid\tunicode", 22)); + assert_se(utf8_is_printable("\342\204\242", 3)); + assert_se(!utf8_is_printable("\341\204", 2)); +} + +static void test_utf8_is_valid(void) { + assert_se(utf8_is_valid("ascii is valid unicode")); + assert_se(utf8_is_valid("\342\204\242")); + assert_se(!utf8_is_valid("\341\204")); +} + +static void test_ascii_is_valid(void) { + assert_se(ascii_is_valid("alsdjf\t\vbarr\nba z")); + assert_se(!ascii_is_valid("\342\204\242")); + assert_se(!ascii_is_valid("\341\204")); +} + +static void test_ascii_filter(void) { + char *f; + + f = ascii_filter("alsdjf\t\vbarr\nba z"); + assert_se(streq(f, "alsdjf\t\vbarr\nba z")); + free(f); + + f = ascii_filter("\342\204\242"); + assert_se(streq(f, "")); + free(f); + + f = ascii_filter("foo\341\204bar"); + assert_se(streq(f, "foobar")); + free(f); +} + +static void test_utf8_encoded_valid_unichar(void) { + assert_se(utf8_encoded_valid_unichar("\342\204\242") == 3); + assert_se(utf8_encoded_valid_unichar("\302\256") == 2); + assert_se(utf8_encoded_valid_unichar("a") == 1); + assert_se(utf8_encoded_valid_unichar("\341\204") < 0); + assert_se(utf8_encoded_valid_unichar("\341\204\341\204") < 0); + +} + +int main(int argc, char *argv[]) { + test_utf8_is_valid(); + test_utf8_is_printable(); + test_ascii_is_valid(); + test_ascii_filter(); + test_utf8_encoded_valid_unichar(); + + return 0; +} diff --git a/src/test/test-util.c b/src/test/test-util.c index 4c3a8a6b88..c5762ede4b 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -27,6 +27,7 @@ #include <errno.h> #include "util.h" +#include "strv.h" static void test_streq_ptr(void) { assert_se(streq_ptr(NULL, NULL)); @@ -192,41 +193,40 @@ static void test_safe_atod(void) { } static void test_strappend(void) { - _cleanup_free_ char *t1, *t2, *t3, *t4; + _cleanup_free_ char *t1, *t2, *t3, *t4; - t1 = strappend(NULL, NULL); - assert_se(streq(t1, "")); + t1 = strappend(NULL, NULL); + assert_se(streq(t1, "")); - t2 = strappend(NULL, "suf"); - assert_se(streq(t2, "suf")); + t2 = strappend(NULL, "suf"); + assert_se(streq(t2, "suf")); - t3 = strappend("pre", NULL); - assert_se(streq(t3, "pre")); + t3 = strappend("pre", NULL); + assert_se(streq(t3, "pre")); - t4 = strappend("pre", "suf"); - assert_se(streq(t4, "presuf")); + t4 = strappend("pre", "suf"); + assert_se(streq(t4, "presuf")); } static void test_strstrip(void) { - char *r; - char input[] = " hello, waldo. "; - - r = strstrip(input); - assert_se(streq(r, "hello, waldo.")); + 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"; + char *r; + char input[] = " hello, waldo. abc"; - r = delete_chars(input, WHITESPACE); - assert_se(streq(r, "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")); + assert_se(in_charset("dddaaabbbcccc", "abcd")); + assert_se(!in_charset("dddaaabbbcccc", "abc f")); } static void test_hexchar(void) { @@ -260,6 +260,18 @@ static void test_undecchar(void) { assert_se(undecchar('9') == 9); } +static void test_cescape(void) { + _cleanup_free_ char *escaped; + escaped = cescape("abc\\\"\b\f\n\r\t\v\003\177\234\313"); + assert_se(streq(escaped, "abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\003\\177\\234\\313")); +} + +static void test_cunescape(void) { + _cleanup_free_ char *unescaped; + unescaped = cunescape("abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\003\\177\\234\\313"); + assert_se(streq(unescaped, "abc\\\"\b\f\n\r\t\v\003\177\234\313")); +} + static void test_foreach_word(void) { char *w, *state; size_t l; @@ -386,6 +398,7 @@ static void test_u64log2(void) { } static void test_get_process_comm(void) { + struct stat st; _cleanup_free_ char *a = NULL, *c = NULL, *d = NULL, *f = NULL, *i = NULL; unsigned long long b; pid_t e; @@ -394,8 +407,12 @@ static void test_get_process_comm(void) { dev_t h; int r; - assert_se(get_process_comm(1, &a) >= 0); - log_info("pid1 comm: '%s'", a); + if (stat("/proc/1/comm", &st) == 0) { + assert_se(get_process_comm(1, &a) >= 0); + log_info("pid1 comm: '%s'", a); + } else { + log_warning("/proc/1/comm does not exist."); + } assert_se(get_starttime_of_pid(1, &b) >= 0); log_info("pid1 starttime: '%llu'", b); @@ -439,6 +456,141 @@ static void test_protect_errno(void) { assert(errno == 12); } +static void test_parse_bytes(void) { + off_t bytes; + + assert_se(parse_bytes("111", &bytes) == 0); + assert_se(bytes == 111); + + assert_se(parse_bytes(" 112 B", &bytes) == 0); + assert_se(bytes == 112); + + assert_se(parse_bytes("3 K", &bytes) == 0); + assert_se(bytes == 3*1024); + + assert_se(parse_bytes(" 4 M 11K", &bytes) == 0); + assert_se(bytes == 4*1024*1024 + 11 * 1024); + + assert_se(parse_bytes("3B3G", &bytes) == 0); + assert_se(bytes == 3ULL*1024*1024*1024 + 3); + + assert_se(parse_bytes("3B3G4T", &bytes) == 0); + assert_se(bytes == (4ULL*1024 + 3)*1024*1024*1024 + 3); + + assert_se(parse_bytes("12P", &bytes) == 0); + assert_se(bytes == 12ULL * 1024*1024*1024*1024*1024); + + assert_se(parse_bytes("3E 2P", &bytes) == 0); + assert_se(bytes == (3 * 1024 + 2ULL) * 1024*1024*1024*1024*1024); + + assert_se(parse_bytes("12X", &bytes) == -EINVAL); + + assert_se(parse_bytes("1024E", &bytes) == -ERANGE); + assert_se(parse_bytes("-1", &bytes) == -ERANGE); + assert_se(parse_bytes("-1024E", &bytes) == -ERANGE); + + assert_se(parse_bytes("-1024P", &bytes) == -ERANGE); + + assert_se(parse_bytes("-10B 20K", &bytes) == -ERANGE); +} + +static void test_strextend(void) { + _cleanup_free_ char *str = strdup("0123"); + strextend(&str, "456", "78", "9", NULL); + assert_se(streq(str, "0123456789")); +} + +static void test_strrep(void) { + _cleanup_free_ char *one, *three, *zero; + one = strrep("waldo", 1); + three = strrep("waldo", 3); + zero = strrep("waldo", 0); + + assert_se(streq(one, "waldo")); + assert_se(streq(three, "waldowaldowaldo")); + assert_se(streq(zero, "")); +} + +static void test_parse_user_at_host(void) { + _cleanup_free_ char *both = strdup("waldo@waldoscomputer"); + _cleanup_free_ char *onlyhost = strdup("mikescomputer"); + char *user = NULL, *host = NULL; + + parse_user_at_host(both, &user, &host); + assert_se(streq(user, "waldo")); + assert_se(streq(host, "waldoscomputer")); + + user = host = NULL; + parse_user_at_host(onlyhost, &user, &host); + assert_se(user == NULL); + assert_se(streq(host, "mikescomputer")); +} + +static void test_split_pair(void) { + _cleanup_free_ char *a = NULL, *b = NULL; + + assert_se(split_pair("", "", &a, &b) == -EINVAL); + assert_se(split_pair("foo=bar", "", &a, &b) == -EINVAL); + assert_se(split_pair("", "=", &a, &b) == -EINVAL); + assert_se(split_pair("foo=bar", "=", &a, &b) >= 0); + assert_se(streq(a, "foo")); + assert_se(streq(b, "bar")); + free(a); + free(b); + assert_se(split_pair("==", "==", &a, &b) >= 0); + assert_se(streq(a, "")); + assert_se(streq(b, "")); + free(a); + free(b); + + assert_se(split_pair("===", "==", &a, &b) >= 0); + assert_se(streq(a, "")); + assert_se(streq(b, "=")); +} + +static void test_fstab_node_to_udev_node(void) { + char *n; + + n = fstab_node_to_udev_node("LABEL=applé/jack"); + puts(n); + assert_se(streq(n, "/dev/disk/by-label/applé\\x2fjack")); + free(n); + + n = fstab_node_to_udev_node("PARTLABEL=pinkié pie"); + puts(n); + assert_se(streq(n, "/dev/disk/by-partlabel/pinkié\\x20pie")); + free(n); + + n = fstab_node_to_udev_node("UUID=037b9d94-148e-4ee4-8d38-67bfe15bb535"); + puts(n); + assert_se(streq(n, "/dev/disk/by-uuid/037b9d94-148e-4ee4-8d38-67bfe15bb535")); + free(n); + + n = fstab_node_to_udev_node("PARTUUID=037b9d94-148e-4ee4-8d38-67bfe15bb535"); + puts(n); + assert_se(streq(n, "/dev/disk/by-partuuid/037b9d94-148e-4ee4-8d38-67bfe15bb535")); + free(n); + + + n = fstab_node_to_udev_node("PONIES=awesome"); + puts(n); + assert_se(streq(n, "PONIES=awesome")); + free(n); + + n = fstab_node_to_udev_node("/dev/xda1"); + puts(n); + assert_se(streq(n, "/dev/xda1")); + free(n); +} + +static void test_get_files_in_directory(void) { + _cleanup_strv_free_ char **l = NULL, **t = NULL; + + assert_se(get_files_in_directory("/tmp", &l) >= 0); + assert_se(get_files_in_directory(".", &l) >= 0); + assert_se(get_files_in_directory(".", NULL) >= 0); +} + int main(int argc, char *argv[]) { test_streq_ptr(); test_first_word(); @@ -458,6 +610,8 @@ int main(int argc, char *argv[]) { test_unoctchar(); test_decchar(); test_undecchar(); + test_cescape(); + test_cunescape(); test_foreach_word(); test_foreach_word_quoted(); test_default_term_for_tty(); @@ -467,6 +621,13 @@ int main(int argc, char *argv[]) { test_u64log2(); test_get_process_comm(); test_protect_errno(); + test_parse_bytes(); + test_strextend(); + test_strrep(); + test_parse_user_at_host(); + test_split_pair(); + test_fstab_node_to_udev_node(); + test_get_files_in_directory(); return 0; } diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 8d4e560b93..141180c393 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -44,7 +44,8 @@ static enum transport { TRANSPORT_POLKIT } arg_transport = TRANSPORT_NORMAL; static bool arg_ask_password = true; -static const char *arg_host = NULL; +static char *arg_host = NULL; +static char *arg_user = NULL; static void pager_open_if_enabled(void) { @@ -197,7 +198,7 @@ static void print_status_info(StatusInfo *i) { if (i->local_rtc) fputs("\n" ANSI_HIGHLIGHT_ON - "Warning: The RTC is configured to maintain time in the local time zone. This\n" + "Warning: The RTC is configured to maintain time in the local timezone. This\n" " mode is not fully supported and will create various problems with time\n" " zone changes and daylight saving adjustments. If at all possible use\n" " RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout); @@ -304,7 +305,7 @@ static int show_status(DBusConnection *bus, char **args, unsigned n) { static int set_time(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t relative = false, interactive = true; + dbus_bool_t relative = false, interactive = arg_ask_password; usec_t t; dbus_int64_t u; int r; @@ -338,7 +339,7 @@ static int set_time(DBusConnection *bus, char **args, unsigned n) { static int set_timezone(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true; + dbus_bool_t interactive = arg_ask_password; assert(args); assert(n == 2); @@ -360,7 +361,7 @@ static int set_timezone(DBusConnection *bus, char **args, unsigned n) { static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true, b, q; + dbus_bool_t interactive = arg_ask_password, b, q; int r; assert(args); @@ -393,7 +394,7 @@ static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) { static int set_ntp(DBusConnection *bus, char **args, unsigned n) { _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; - dbus_bool_t interactive = true, b; + dbus_bool_t interactive = arg_ask_password, b; int r; assert(args); @@ -501,6 +502,7 @@ static int help(void) { " --adjust-system-clock\n" " Adjust system clock when changing local RTC mode\n" " --no-pager Do not pipe output into a pager\n" + " -P --privileged Acquire privileges before execution\n" " --no-ask-password Do not prompt for password\n" " -H --host=[USER@]HOST Operate on remote host\n\n" "Commands:\n" @@ -540,7 +542,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hH:P", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) { switch (c) { @@ -559,7 +561,11 @@ static int parse_argv(int argc, char *argv[]) { case 'H': arg_transport = TRANSPORT_SSH; - arg_host = optarg; + parse_user_at_host(optarg, &arg_user, &arg_host); + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; break; case ARG_ADJUST_SYSTEM_CLOCK: diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index cdb6e5b16c..525c72e497 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -662,7 +662,7 @@ static DBusHandlerResult timedate_message_handler( return bus_send_error_reply(connection, message, NULL, r); } - /* 2. Tell the kernel our time zone */ + /* 2. Tell the kernel our timezone */ hwclock_set_timezone(NULL); if (tz.local_rtc) { @@ -719,7 +719,7 @@ static DBusHandlerResult timedate_message_handler( return bus_send_error_reply(connection, message, NULL, r); } - /* 2. Tell the kernel our time zone */ + /* 2. Tell the kernel our timezone */ hwclock_set_timezone(NULL); /* 3. Synchronize clocks */ diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index f4885ec942..8122d6af6a 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -51,6 +51,7 @@ #include "set.h" #include "conf-files.h" #include "capability.h" +#include "specifier.h" /* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates * them in the file system. This is intended to be used to create @@ -68,6 +69,7 @@ typedef enum ItemType { CREATE_SYMLINK = 'L', CREATE_CHAR_DEVICE = 'c', CREATE_BLOCK_DEVICE = 'b', + ADJUST_MODE = 'm', /* These ones take globs */ IGNORE_PATH = 'x', @@ -105,7 +107,8 @@ static bool arg_create = false; static bool arg_clean = false; static bool arg_remove = false; -static const char *arg_prefix = NULL; +static char **include_prefixes = NULL; +static char **exclude_prefixes = NULL; static const char conf_file_dirs[] = "/etc/tmpfiles.d\0" @@ -255,8 +258,8 @@ static int dir_cleanup( dev_t rootdev, bool mountpoint, int maxdepth, - bool keep_this_level) -{ + bool keep_this_level) { + struct dirent *dent; struct timespec times[2]; bool deleted = false; @@ -427,12 +430,16 @@ finish: return r; } -static int item_set_perms(Item *i, const char *path) { +static int item_set_perms_full(Item *i, const char *path, bool ignore_enoent) { + int r; + /* not using i->path directly because it may be a glob */ if (i->mode_set) if (chmod(path, i->mode) < 0) { - log_error("chmod(%s) failed: %m", path); - return -errno; + if (errno != ENOENT || !ignore_enoent) { + log_error("chmod(%s) failed: %m", path); + return -errno; + } } if (i->uid_set || i->gid_set) @@ -440,11 +447,18 @@ static int item_set_perms(Item *i, const char *path) { i->uid_set ? i->uid : (uid_t) -1, i->gid_set ? i->gid : (gid_t) -1) < 0) { - log_error("chown(%s) failed: %m", path); - return -errno; + if (errno != ENOENT || !ignore_enoent) { + log_error("chown(%s) failed: %m", path); + return -errno; + } } - return label_fix(path, false, false); + r = label_fix(path, false, false); + return r == -ENOENT && ignore_enoent ? 0 : r; +} + +static int item_set_perms(Item *i, const char *path) { + return item_set_perms_full(i, path, false); } static int write_one_file(Item *i, const char *path) { @@ -640,6 +654,7 @@ static int create_item(Item *i) { if (r < 0) return r; break; + case WRITE_FILE: r = glob_item(i, write_one_file); if (r < 0) @@ -647,6 +662,13 @@ static int create_item(Item *i) { break; + case ADJUST_MODE: + r = item_set_perms_full(i, i->path, true); + if (r < 0) + return r; + + break; + case TRUNCATE_DIRECTORY: case CREATE_DIRECTORY: @@ -783,7 +805,7 @@ static int create_item(Item *i) { r = glob_item(i, item_set_perms); if (r < 0) - return 0; + return r; break; case RECURSIVE_RELABEL_PATH: @@ -817,6 +839,7 @@ static int remove_item_instance(Item *i, const char *instance) { case RELABEL_PATH: case RECURSIVE_RELABEL_PATH: case WRITE_FILE: + case ADJUST_MODE: break; case REMOVE_PATH: @@ -862,6 +885,7 @@ static int remove_item(Item *i) { case RELABEL_PATH: case RECURSIVE_RELABEL_PATH: case WRITE_FILE: + case ADJUST_MODE: break; case REMOVE_PATH: @@ -971,6 +995,12 @@ static void item_free(Item *i) { free(i); } +static inline void item_freep(Item **i) { + if (*i) + item_free(*i); +} +#define _cleanup_item_free_ _cleanup_(item_freep) + static bool item_equal(Item *a, Item *b) { assert(a); assert(b); @@ -1012,11 +1042,38 @@ static bool item_equal(Item *a, Item *b) { return true; } +static bool should_include_path(const char *path) { + char **prefix; + + STRV_FOREACH(prefix, exclude_prefixes) { + if (path_startswith(path, *prefix)) + return false; + } + + STRV_FOREACH(prefix, include_prefixes) { + if (path_startswith(path, *prefix)) + return true; + } + + /* no matches, so we should include this path only if we + * have no whitelist at all */ + return strv_length(include_prefixes) == 0; +} + static int parse_line(const char *fname, unsigned line, const char *buffer) { - _cleanup_free_ Item *i = NULL; + + static const Specifier specifier_table[] = { + { 'm', specifier_machine_id, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} + }; + + _cleanup_item_free_ Item *i = NULL; Item *existing; _cleanup_free_ char - *mode = NULL, *user = NULL, *group = NULL, *age = NULL; + *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; char type; Hashmap *h; int r, n = -1; @@ -1025,14 +1082,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { assert(line >= 1); assert(buffer); - i = new0(Item, 1); - if (!i) - return log_oom(); - r = sscanf(buffer, "%c %ms %ms %ms %ms %ms %n", &type, - &i->path, + &path, &mode, &user, &group, @@ -1043,6 +1096,16 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { return -EIO; } + i = new0(Item, 1); + if (!i) + return log_oom(); + + r = specifier_printf(path, specifier_table, NULL, &i->path); + if (r < 0) { + log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path); + return r; + } + if (n >= 0) { n += strspn(buffer+n, WHITESPACE); if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) { @@ -1065,6 +1128,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { case RECURSIVE_REMOVE_PATH: case RELABEL_PATH: case RECURSIVE_RELABEL_PATH: + case ADJUST_MODE: break; case CREATE_SYMLINK: @@ -1113,7 +1177,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { path_kill_slashes(i->path); - if (arg_prefix && !path_startswith(i->path, arg_prefix)) + if (!should_include_path(i->path)) return 0; if (user && !streq(user, "-")) { @@ -1198,11 +1262,12 @@ static int help(void) { printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" "Creates, deletes and cleans up volatile and temporary files and directories.\n\n" - " -h --help Show this help\n" - " --create Create marked files/directories\n" - " --clean Clean up marked directories\n" - " --remove Remove marked files/directories\n" - " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n", + " -h --help Show this help\n" + " --create Create marked files/directories\n" + " --clean Clean up marked directories\n" + " --remove Remove marked files/directories\n" + " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n" + " --exclude-prefix=PATH Ignore rules that apply to paths with the specified prefix\n", program_invocation_short_name); return 0; @@ -1214,16 +1279,18 @@ static int parse_argv(int argc, char *argv[]) { ARG_CREATE, ARG_CLEAN, ARG_REMOVE, - ARG_PREFIX + ARG_PREFIX, + ARG_EXCLUDE_PREFIX, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "create", no_argument, NULL, ARG_CREATE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "remove", no_argument, NULL, ARG_REMOVE }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { NULL, 0, NULL, 0 } + { "help", no_argument, NULL, 'h' }, + { "create", no_argument, NULL, ARG_CREATE }, + { "clean", no_argument, NULL, ARG_CLEAN }, + { "remove", no_argument, NULL, ARG_REMOVE }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, + { NULL, 0, NULL, 0 } }; int c; @@ -1252,7 +1319,13 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PREFIX: - arg_prefix = optarg; + if (strv_extend(&include_prefixes, optarg) < 0) + return log_oom(); + break; + + case ARG_EXCLUDE_PREFIX: + if (strv_extend(&exclude_prefixes, optarg) < 0) + return log_oom(); break; case '?': @@ -1273,11 +1346,12 @@ static int parse_argv(int argc, char *argv[]) { } static int read_config_file(const char *fn, bool ignore_enoent) { - FILE *f; - unsigned v = 0; - int r; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; Iterator iterator; + unsigned v = 0; Item *i; + int r; assert(fn); @@ -1290,23 +1364,19 @@ static int read_config_file(const char *fn, bool ignore_enoent) { return r; } - log_debug("apply: %s\n", fn); - for (;;) { - char line[LINE_MAX], *l; + FOREACH_LINE(line, f, break) { + char *l; int k; - if (!(fgets(line, sizeof(line), f))) - break; - v++; l = strstrip(line); if (*l == '#' || *l == 0) continue; - if ((k = parse_line(fn, v, l)) < 0) - if (r == 0) - r = k; + k = parse_line(fn, v, l); + if (k < 0 && r == 0) + r = k; } /* we have to determine age parameter for each entry of type X */ @@ -1343,8 +1413,6 @@ static int read_config_file(const char *fn, bool ignore_enoent) { r = -EIO; } - fclose(f); - return r; } @@ -1417,6 +1485,8 @@ finish: hashmap_free(items); hashmap_free(globs); + strv_free(include_prefixes); + set_free_free(unix_sockets); label_finish(); diff --git a/src/udev/.gitignore b/src/udev/.gitignore index 3e375a7726..a229430e36 100644 --- a/src/udev/.gitignore +++ b/src/udev/.gitignore @@ -1 +1,5 @@ /udev.pc +/keyboard-keys-from-name.gperf +/keyboard-keys-from-name.h +/keyboard-keys-to-name.h +/keyboard-keys.txt diff --git a/src/udev/collect/collect.c b/src/udev/collect/collect.c index f95ee23b75..1346f27f91 100644 --- a/src/udev/collect/collect.c +++ b/src/udev/collect/collect.c @@ -442,19 +442,19 @@ int main(int argc, char **argv) if (debug) fprintf(stderr, "ID %s: not in database\n", argv[i]); - him = malloc(sizeof (struct _mate)); + him = new(struct _mate, 1); if (!him) { ret = ENOMEM; goto out; } - him->name = malloc(strlen(argv[i]) + 1); + him->name = strdup(argv[i]); if (!him->name) { + free(him); ret = ENOMEM; goto out; } - strcpy(him->name, argv[i]); him->state = STATE_NONE; udev_list_node_append(&him->node, &bunch); } else { diff --git a/src/udev/keymap/.gitignore b/src/udev/keymap/.gitignore deleted file mode 100644 index 4567584f4e..0000000000 --- a/src/udev/keymap/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -keyboard-force-release.sh -keys-from-name.gperf -keys-from-name.h -keys-to-name.h -keys.txt diff --git a/src/udev/keymap/95-keyboard-force-release.rules b/src/udev/keymap/95-keyboard-force-release.rules deleted file mode 100644 index 3e33e85535..0000000000 --- a/src/udev/keymap/95-keyboard-force-release.rules +++ /dev/null @@ -1,57 +0,0 @@ -# Set model specific atkbd force_release quirk -# -# Several laptops have hotkeys which don't generate release events, -# which can cause problems with software key repeat. -# The atkbd driver has a quirk handler for generating synthetic -# release events, which can be configured via sysfs since 2.6.32. -# Simply add a file with a list of scancodes for your laptop model -# in /usr/lib/udev/keymaps, and add a rule here. -# If the hotkeys also need a keymap assignment you can copy the -# scancodes from the keymap file, otherwise you can run -# /usr/lib/udev/keymap -i /dev/input/eventX -# on a Linux vt to find out. - -ACTION=="remove", GOTO="force_release_end" -SUBSYSTEM!="serio", GOTO="force_release_end" -KERNEL!="serio*", GOTO="force_release_end" -DRIVER!="atkbd", GOTO="force_release_end" - -ENV{DMI_VENDOR}="$attr{[dmi/id]sys_vendor}" - -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", RUN+="keyboard-force-release.sh $devpath samsung-other" -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*90X3A*|*900X3*|*900X4*", RUN+="keyboard-force-release.sh $devpath samsung-series-9" - -ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="Studio 1557|Studio 1558", RUN+="keyboard-force-release.sh $devpath common-volume-keys" -ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="Latitude E*|Latitude *U|Precision M*", RUN+="keyboard-force-release.sh $devpath dell-touchpad" -ENV{DMI_VENDOR}=="Dell Inc.", ATTR{[dmi/id]product_name}=="XPS*", RUN+="keyboard-force-release.sh $devpath dell-xps" - -ENV{DMI_VENDOR}=="FUJITSU SIEMENS", ATTR{[dmi/id]product_name}=="AMILO*", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="FOXCONN", ATTR{[dmi/id]product_name}=="QBOOK", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="MTC", ATTR{[dmi/id]product_version}=="A0", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="Mio Technology", ATTR{[dmi/id]product_name}=="N890", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="PEGATRON CORP.", ATTR{[dmi/id]product_name}=="Spring Peak", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite [uU]300*|Satellite Pro [uU]300*|Satellite [uU]305*|SATELLITE [uU]500*", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="Viooo Corporation", ATTR{[dmi/id]product_name}=="PT17", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -# These are all the HP laptops that setup a touchpad toggle key -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[pP][aA][vV][iI][lL][iI][oO][nN]*", RUN+="keyboard-force-release.sh $devpath hp-other" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][xX]2*", RUN+="keyboard-force-release.sh $devpath hp-other" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*2510p*|*2530p*|HP G60 Notebook PC|HDX9494NR", RUN+="keyboard-force-release.sh $devpath hp-other" - -ENV{DMI_VENDOR}=="Zepto", ATTR{[dmi/id]product_name}=="Znote 6615WD", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="Zepto", ATTR{[dmi/id]product_name}=="Znote", ATTR{[dmi/id]product_version}=="6625WD", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="HANNspree", ATTR{[dmi/id]product_name}=="SN10E100", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="GIGABYTE", ATTR{[dmi/id]product_name}=="i1520M", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -ENV{DMI_VENDOR}=="BenQ", ATTR{[dmi/id]product_name}=="*nScreen*", RUN+="keyboard-force-release.sh $devpath common-volume-keys" - -LABEL="force_release_end" diff --git a/src/udev/keymap/95-keymap.rules b/src/udev/keymap/95-keymap.rules deleted file mode 100644 index 7956092030..0000000000 --- a/src/udev/keymap/95-keymap.rules +++ /dev/null @@ -1,183 +0,0 @@ -# Set model specific hotkey keycodes. -# -# Key map overrides can be specified by either giving scancode/keyname pairs -# directly as keymap arguments (if there are just one or two to change), or as -# a file name (in /usr/lib/udev/keymaps), which has to contain scancode/keyname -# pairs. - -ACTION=="remove", GOTO="keyboard_end" -KERNEL!="event*", GOTO="keyboard_end" -ENV{ID_INPUT_KEY}=="", GOTO="keyboard_end" -SUBSYSTEMS=="bluetooth", GOTO="keyboard_end" - -SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" -SUBSYSTEMS=="usb", GOTO="keyboard_usbcheck" -GOTO="keyboard_modulecheck" - -# -# The following are external USB keyboards -# - -LABEL="keyboard_usbcheck" - -ENV{ID_VENDOR}=="Genius", ENV{ID_MODEL_ID}=="0708", ENV{ID_USB_INTERFACE_NUM}=="01", RUN+="keymap $name genius-slimstar-320" -ENV{ID_VENDOR}=="Logitech*", ATTRS{name}=="Logitech USB Multimedia Keyboard", RUN+="keymap $name logitech-wave" -ENV{ID_VENDOR}=="Logitech*", ATTRS{name}=="Logitech USB Receiver", RUN+="keymap $name logitech-wave-cordless" -# Logitech Cordless Wave Pro looks slightly weird; some hotkeys are coming through the mouse interface -ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="c52[9b]", ATTRS{name}=="Logitech USB Receiver", RUN+="keymap $name logitech-wave-pro-cordless" - -ENV{ID_VENDOR}=="Lite-On_Technology_Corp*", ATTRS{name}=="Lite-On Technology Corp. ThinkPad USB Keyboard with TrackPoint", RUN+="keymap $name lenovo-thinkpad-usb-keyboard-trackpoint" -ENV{ID_VENDOR_ID}=="04b3", ENV{ID_MODEL_ID}=="301[89]", RUN+="keymap $name ibm-thinkpad-usb-keyboard-trackpoint" - -ENV{ID_VENDOR}=="Microsoft", ENV{ID_MODEL_ID}=="00db", RUN+="keymap $name 0xc022d zoomin 0xc022e zoomout" - -GOTO="keyboard_end" - -# -# The following are exposed as separate input devices with low key codes, thus -# we need to check their input device product name -# - -LABEL="keyboard_modulecheck" - -ENV{DMI_VENDOR}="$attr{[dmi/id]sys_vendor}" -ENV{DMI_VENDOR}=="", GOTO="keyboard_end" - -ENV{DMI_VENDOR}=="LENOVO*", KERNELS=="input*", ATTRS{name}=="ThinkPad Extra Buttons", RUN+="keymap $name module-lenovo" -ENV{DMI_VENDOR}=="LENOVO*", KERNELS=="input*", ATTRS{name}=="Lenovo ThinkPad SL Series extra buttons", RUN+="keymap $name 0x0E bluetooth" -ENV{DMI_VENDOR}=="LENOVO*", KERNELS=="input*", ATTRS{name}=="Ideapad extra buttons", RUN+="keymap $name 0x42 f23 0x43 f22" - -ENV{DMI_VENDOR}=="ASUS*", KERNELS=="input*", ATTRS{name}=="Asus Extra Buttons", ATTR{[dmi/id]product_name}=="W3J", RUN+="keymap $name module-asus-w3j" -ENV{DMI_VENDOR}=="ASUS*", KERNELS=="input*", ATTRS{name}=="Eee PC WMI hotkeys|Asus Laptop Support|Asus*WMI*", RUN+="keymap $name 0x6B f21" -ENV{DMI_VENDOR}=="ASUS*", KERNELS=="input*", ATTRS{name}=="Eee PC Hotkey Driver", RUN+="keymap $name 0x37 f21" - -ENV{DMI_VENDOR}=="IBM*", KERNELS=="input*", ATTRS{name}=="ThinkPad Extra Buttons", RUN+="keymap $name module-ibm" -ENV{DMI_VENDOR}=="Sony*", KERNELS=="input*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony" -ENV{DMI_VENDOR}=="Acer*", KERNELS=="input*", ATTRS{name}=="Acer WMI hotkeys", RUN+="keymap $name 0x82 f21" -ENV{DMI_VENDOR}=="MICRO-STAR*|Micro-Star*", KERNELS=="input*", ATTRS{name}=="MSI Laptop hotkeys", RUN+="keymap $name 0x213 f22 0x214 f23" - -# Older Vaios have some different keys -ENV{DMI_VENDOR}=="Sony*", ATTR{[dmi/id]product_name}=="*PCG-C1*|*PCG-K25*|*PCG-F1*|*PCG-F2*|*PCG-F3*|*PCG-F4*|*PCG-F5*|*PCG-F6*|*PCG-FX*|*PCG-FRV*|*PCG-GR*|*PCG-TR*|*PCG-NV*|*PCG-Z*|*VGN-S360*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony-old" - -# Some Sony VGN/VPC models have yet another one -ENV{DMI_VENDOR}=="Sony*", ATTR{[dmi/id]product_name}=="VGN-AR71*|VGN-FW*|VGN-Z21*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony-vgn" -ENV{DMI_VENDOR}=="Sony*", ATTR{[dmi/id]product_name}=="VPC*", ATTRS{name}=="Sony Vaio Keys", RUN+="keymap $name module-sony-vpc" - - -# -# The following rules belong to standard i8042 AT keyboard with high key codes. -# - -DRIVERS=="atkbd", GOTO="keyboard_vendorcheck" -GOTO="keyboard_end" - -LABEL="keyboard_vendorcheck" - -ENV{DMI_VENDOR}=="Dell*", RUN+="keymap $name dell" -ENV{DMI_VENDOR}=="Dell*", ATTR{[dmi/id]product_name}=="Inspiron 910|Inspiron 1010|Inspiron 1011|Inspiron 1012|Inspiron 1110|Inspiron 1210", RUN+="keymap $name 0x84 wlan" -ENV{DMI_VENDOR}=="Dell*", ATTR{[dmi/id]product_name}=="Latitude XT2", RUN+="keymap $name dell-latitude-xt2" - -ENV{DMI_VENDOR}=="Compaq*", ATTR{[dmi/id]product_name}=="*E500*|*Evo N*", RUN+="keymap $name compaq-e_evo" - -ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="*3000*", RUN+="keymap $name lenovo-3000" -ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X6*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x6_tablet" -ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="ThinkPad X2* Tablet*", ATTR{[dmi/id]product_version}=="* Tablet", RUN+="keymap $name lenovo-thinkpad_x200_tablet" -ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_version}=="*IdeaPad*", RUN+="keymap $name lenovo-ideapad" -ENV{DMI_VENDOR}=="LENOVO*", ATTR{[dmi/id]product_name}=="S10-*", RUN+="keymap $name lenovo-ideapad" -ENV{DMI_VENDOR}=="LENOVO", ATTR{[dmi/id]product_version}=="*IdeaPad Y550*", RUN+="keymap $name 0x95 media 0xA3 play" -ENV{DMI_VENDOR}=="LENOVO", ATTR{[dmi/id]product_version}=="*Lenovo V480*", RUN+="keymap $name 0xf1 f21" -# 0xf1 is touchpad toggle, 0xCE is microphone mute in Lenovo U300s -ENV{DMI_VENDOR}=="LENOVO", ATTR{[dmi/id]product_version}=="*IdeaPad U300s*", RUN+="keymap $name 0xf1 f21 0xCE f20" - -ENV{DMI_VENDOR}=="Hewlett-Packard*", RUN+="keymap $name hewlett-packard" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][aA][bB][lL][eE][tT]*", RUN+="keymap $name hewlett-packard-tablet" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[pP][aA][vV][iI][lL][iI][oO][nN]*", RUN+="keymap $name hewlett-packard-pavilion" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*Compaq*|*EliteBook*|*2230s*", RUN+="keymap $name hewlett-packard-compaq_elitebook" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*Presario*CQ*", RUN+="keymap $name 0xD8 f21 0xD9 f21" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*2510p*|*2530p*|HP G60 Notebook PC", RUN+="keymap $name hewlett-packard-2510p_2530p" -ENV{DMI_VENDOR}=="Hewlett-Packard*", ATTR{[dmi/id]product_name}=="*[tT][xX]2*", RUN+="keymap $name hewlett-packard-tx2" -ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="Presario 2100*", RUN+="keymap $name hewlett-packard-presario-2100" -ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP G62 Notebook PC", RUN+="keymap $name 0xB2 www" -ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP ProBook*", RUN+="keymap $name 0xF8 rfkill 0xB2 www" -ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP EliteBook 8440p", RUN+="keymap $name hewlett-packard_elitebook-8440p" -ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HP EliteBook 8460p", RUN+="keymap $name hewlett-packard_elitebook-8460p" -ENV{DMI_VENDOR}=="Hewlett-Packard", ATTR{[dmi/id]product_name}=="HDX9494NR", RUN+="keymap $name hewlett-packard-hdx9494nr" -# HP Pavilion dv6315ea has empty DMI_VENDOR -ATTR{[dmi/id]board_vendor}=="Quanta", ATTR{[dmi/id]board_name}=="30B7", ATTR{[dmi/id]board_version}=="65.2B", RUN+="keymap $name 0x88 media" # "quick play - -# Gateway clone of Acer Aspire One AOA110/AOA150 -ENV{DMI_VENDOR}=="Gateway*", ATTR{[dmi/id]product_name}=="*AOA1*", RUN+="keymap $name acer" - -ENV{DMI_VENDOR}=="Acer*", RUN+="keymap $name acer" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Extensa*", ATTR{[dmi/id]product_name}=="*5210*|*5220*|*5610*|*5620*|*5720*", RUN+="keymap $name 0xEE screenlock" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate*C3[01]0*", RUN+="keymap $name acer-travelmate_c300" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate*6292*|TravelMate*8471*|TravelMate*4720*|TravelMate*7720*|Aspire 1810T*|AO751h|AO531h", RUN+="keymap $name 0xD9 bluetooth" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate*4720*", RUN+="keymap $name 0xB2 www 0xEE screenlock" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="TravelMate 6593|Aspire 1640", RUN+="keymap $name 0xB2 www 0xEE screenlock" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 6920", RUN+="keymap $name acer-aspire_6920" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 5920G", RUN+="keymap $name acer-aspire_5920g" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 5720*", RUN+="keymap $name acer-aspire_5720" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_name}=="Aspire 8930", RUN+="keymap $name acer-aspire_8930" -ENV{DMI_VENDOR}=="Acer*", ATTR{[dmi/id]product_serial}=="ZG8*", RUN+="keymap $name acer-aspire_5720" - -ENV{DMI_VENDOR}=="*BenQ*", ATTR{[dmi/id]product_name}=="*Joybook R22*", RUN+="keymap $name 0x6E wlan" - -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*AMILO Pro V3205*", RUN+="keymap $name fujitsu-amilo_pro_v3205" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*AMILO Pa 2548*", RUN+="keymap $name fujitsu-amilo_pa_2548" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*ESPRIMO Mobile V5*", RUN+="keymap $name fujitsu-esprimo_mobile_v5" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*ESPRIMO Mobile V6*", RUN+="keymap $name fujitsu-esprimo_mobile_v6" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*AMILO Pro Edition V3505*", RUN+="keymap $name fujitsu-amilo_pro_edition_v3505" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="*Amilo Si 1520*", RUN+="keymap $name fujitsu-amilo_si_1520" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="AMILO*M*", RUN+="keymap $name 0x97 prog2 0x9F prog1" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="Amilo Li 1718", RUN+="keymap $name 0xD6 wlan" -ENV{DMI_VENDOR}=="FUJITSU*", ATTR{[dmi/id]product_name}=="AMILO Li 2732", RUN+="keymap $name fujitsu-amilo_li_2732" - -ENV{DMI_VENDOR}=="LG*", ATTR{[dmi/id]product_name}=="*X110*", RUN+="keymap $name lg-x110" - -ENV{DMI_VENDOR}=="MEDION*", ATTR{[dmi/id]product_name}=="*FID2060*", RUN+="keymap $name medion-fid2060" -ENV{DMI_VENDOR}=="MEDIONNB", ATTR{[dmi/id]product_name}=="A555*", RUN+="keymap $name medionnb-a555" - -ENV{DMI_VENDOR}=="MICRO-STAR*|Micro-Star*", RUN+="keymap $name micro-star" - -# some MSI models generate ACPI/input events on the LNXVIDEO input devices, -# plus some extra synthesized ones on atkbd as an echo of actually changing the -# brightness; so ignore those atkbd ones, to avoid loops -ENV{DMI_VENDOR}=="MICRO-STAR*", ATTR{[dmi/id]product_name}=="*U-100*|*U100*|*N033", RUN+="keymap $name 0xF7 reserved 0xF8 reserved" - -# MSI Wind U90/U100 generates separate touchpad on/off keycodes so ignore touchpad toggle keycode -ENV{DMI_VENDOR}=="MICRO-STAR*", ATTR{[dmi/id]product_name}=="U90/U100", RUN+="keymap $name 0xE4 reserved" - -ENV{DMI_VENDOR}=="INVENTEC", ATTR{[dmi/id]product_name}=="SYMPHONY 6.0/7.0", RUN+="keymap $name inventec-symphony_6.0_7.0" - -ENV{DMI_VENDOR}=="MAXDATA", ATTR{[dmi/id]product_name}=="Pro 7000*", RUN+="keymap $name maxdata-pro_7000" - -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", RUN+="keymap $name samsung-other" -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*SX20S*", RUN+="keymap $name samsung-sx20s" -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="SQ1US", RUN+="keymap $name samsung-sq1us" -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*700Z*", RUN+="keymap $name 0xBA ejectcd 0x96 keyboardbrightnessup 0x97 keyboardbrightnessdown" -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*700T*", RUN+="keymap $name 0xAD leftmeta" -ENV{DMI_VENDOR}=="[sS][aA][mM][sS][uU][nN][gG]*", ATTR{[dmi/id]product_name}=="*90X3A*|*900X3*|*900X4*", RUN+="keymap $name samsung-series-9" - -ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="SATELLITE A100", RUN+="keymap $name toshiba-satellite_a100" -ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite A110", RUN+="keymap $name toshiba-satellite_a110" -ENV{DMI_VENDOR}=="TOSHIBA", ATTR{[dmi/id]product_name}=="Satellite M30X", RUN+="keymap $name toshiba-satellite_m30x" - -ENV{DMI_VENDOR}=="OQO Inc.*", ATTR{[dmi/id]product_name}=="OQO Model 2*", RUN+="keymap $name oqo-model2" - -ENV{DMI_VENDOR}=="ONKYO CORPORATION", ATTR{[dmi/id]product_name}=="ONKYOPC", RUN+="keymap $name onkyo" - -ENV{DMI_VENDOR}=="ASUS", RUN+="keymap $name asus" - -ENV{DMI_VENDOR}=="VIA", ATTR{[dmi/id]product_name}=="K8N800", ATTR{[dmi/id]product_version}=="VT8204B", RUN+="keymap $name 0x81 prog1" - -ENV{DMI_VENDOR}=="Zepto", ATTR{[dmi/id]product_name}=="Znote", ATTR{[dmi/id]product_version}=="62*|63*", RUN+="keymap $name zepto-znote" - -ENV{DMI_VENDOR}=="Everex", ATTR{[dmi/id]product_name}=="XT5000*", RUN+="keymap $name everex-xt5000" - -ENV{DMI_VENDOR}=="COMPAL", ATTR{[dmi/id]product_name}=="HEL80I", RUN+="keymap $name 0x84 wlan" - -ENV{DMI_VENDOR}=="OLPC", ATTR{[dmi/id]product_name}=="XO", RUN+="keymap $name olpc-xo" - -ENV{DMI_VENDOR}=="Alienware*", ATTR{[dmi/id]product_name}=="M14xR1", RUN+="keymap $name 0x8A ejectcd" - -LABEL="keyboard_end" diff --git a/src/udev/keymap/README.keymap.txt b/src/udev/keymap/README.keymap.txt deleted file mode 100644 index 2cf2a4e88c..0000000000 --- a/src/udev/keymap/README.keymap.txt +++ /dev/null @@ -1,97 +0,0 @@ -= The udev keymap tool = - -== Introduction == - -This udev extension configures computer model specific key mappings. This is -particularly necessary for the non-standard extra keys found on many laptops, -such as "brightness up", "next song", "www browser", or "suspend". Often these -are accessed with the Fn key. - -Every key produces a "scan code", which is highly vendor/model specific for the -nonstandard keys. This tool maintains mappings for these scan codes to standard -"key codes", which denote the "meaning" of the key. The key codes are defined -in /usr/include/linux/input.h. - -If some of your keys on your keyboard are not working at all, or produce the -wrong effect, then a very likely cause of this is that the scan code -> key -code mapping is incorrect on your computer. - -== Structure == - -udev-keymap consists of the following parts: - - keymaps/*:: mappings of scan codes to key code names - - 95-keymap.rules:: udev rules for mapping system vendor/product names and - input module names to one of the keymaps above - - keymap:: manipulate an evdev input device: - * write a key map file into a device (used by udev rules) - * dump current scan → key code mapping - * interactively display scan and key codes of pressed keys - - findkeyboards:: display evdev input devices which belong to actual keyboards, - i. e. those suitable for the keymap program - -== Fixing broken keys == - -In order to make a broken key work on your system and send it back to upstream -for inclusion you need to do the following steps: - - 1. Find the keyboard device. - - Run /usr/lib/udev/findkeyboards. This should always give you an "AT - keyboard" and possibly a "module". Some laptops (notably Thinkpads, Sonys, and - Acers) have multimedia/function keys on a separate input device instead of the - primary keyboard. The keyboard device should have a name like "input/event3". - In the following commands, the name will be written as "input/eventX" (replace - X with the appropriate number). - - 2. Find broken scan codes: - - sudo /usr/lib/udev/keymap -i input/eventX - - Press all multimedia/function keys and check if the key name that gets printed - out is plausible. If it is unknown or wrong, write down the scan code (looks - like "0x1E") and the intended functionality of this key. Look in - /usr/include/linux/input.h for an available KEY_XXXXX constant which most - closely approximates this functionality and write it down as the new key code. - - For example, you might press a key labeled "web browser" which currently - produces "unknown". Note down this: - - 0x1E www # Fn+F2 web browser - - Repeat that for all other keys. Write the resulting list into a file. Look at - /usr/lib/udev/keymaps/ for existing key map files and make sure that you use the - same structure. - - If the key only ever works once and then your keyboard (or the entire desktop) - gets stuck for a long time, then it is likely that the BIOS fails to send a - corresponding "key release" event after the key press event. Please note down - this case as well, as it can be worked around in - /usr/lib/udev/keymaps/95-keyboard-force-release.rules . - - 3. Find out your system vendor and product: - - cat /sys/class/dmi/id/sys_vendor - cat /sys/class/dmi/id/product_name - - 4. Generate a device dump with "udevadm info --export-db > /tmp/udev-db.txt". - - 6. Send the system vendor/product names, the key mapping from step 2, - and /tmp/udev-db.txt from step 4 to the linux-hotplug@vger.kernel.org mailing - list, so that they can be included in the next release. - -For local testing, copy your map file to /usr/lib/udev/keymaps/ with an appropriate -name, and add an appropriate udev rule to /usr/lib/udev/rules.d/95-keymap.rules: - - * If you selected an "AT keyboard", add the rule to the section after - 'LABEL="keyboard_vendorcheck"'. - - * If you selected a "module", add the rule to the top section where the - "ThinkPad Extra Buttons" are. - -== Author == - -keymap is written and maintained by Martin Pitt <martin.pitt@ubuntu.com>. diff --git a/src/udev/keymap/check-keymaps.sh b/src/udev/keymap/check-keymaps.sh deleted file mode 100755 index c4572745e0..0000000000 --- a/src/udev/keymap/check-keymaps.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# check that all key names in keymaps/* are known in <linux/input.h> -# and that all key maps listed in the rules are valid and present in -# Makefile.am -SRCDIR=${1:-.} -KEYLIST=${2:-src/udev/keymap/keys.txt} -KEYMAPS_DIR=$SRCDIR/keymaps -RULES=$SRCDIR/src/udev/keymap/95-keymap.rules - -[ -e "$KEYLIST" ] || { - echo "need $KEYLIST please build first" >&2 - exit 1 -} - -missing=$(join -v 2 <(awk '{print tolower(substr($1,5))}' $KEYLIST | sort -u) \ - <(grep -hv '^#' ${KEYMAPS_DIR}/*| awk '{print $2}' | sort -u)) -[ -z "$missing" ] || { - echo "ERROR: unknown key names in keymaps/*:" >&2 - echo "$missing" >&2 - exit 1 -} - -# check that all maps referred to in $RULES exist -maps=$(sed -rn '/keymap \$name/ { s/^.*\$name ([^"[:space:]]+).*$/\1/; p }' $RULES) -for m in $maps; do - # ignore inline mappings - [ "$m" = "${m#0x}" ] || continue - - [ -e ${KEYMAPS_DIR}/$m ] || { - echo "ERROR: unknown map name in $RULES: $m" >&2 - exit 1 - } - grep -q "keymaps/$m\>" $SRCDIR/Makefile.am || { - echo "ERROR: map file $m is not added to Makefile.am" >&2 - exit 1 - } -done diff --git a/src/udev/keymap/findkeyboards b/src/udev/keymap/findkeyboards deleted file mode 100755 index c6b50d12d0..0000000000 --- a/src/udev/keymap/findkeyboards +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh -e -# Find "real" keyboard devices and print their device path. -# Author: Martin Pitt <martin.pitt@ubuntu.com> -# -# Copyright (C) 2009, Canonical Ltd. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program 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. - -# returns OK if $1 contains $2 -strstr() { - [ "${1#*$2*}" != "$1" ] -} - -# returns OK if $1 contains $2 at the beginning -str_starts() { - [ "${1#$2*}" != "$1" ] -} - -str_line_starts() { - while read a; do str_starts "$a" "$1" && return 0;done - return 1; -} - -# print a list of input devices which are keyboard-like -keyboard_devices() { - # standard AT keyboard - for dev in `udevadm trigger --dry-run --verbose --property-match=ID_INPUT_KEYBOARD=1`; do - env=`udevadm info --query=env --path=$dev` - # filter out non-event devices, such as the parent input devices which have no devnode - if ! echo "$env" | str_line_starts 'DEVNAME='; then - continue - fi - walk=`udevadm info --attribute-walk --path=$dev` - if strstr "$walk" 'DRIVERS=="atkbd"'; then - echo -n 'AT keyboard: ' - elif echo "$env" | str_line_starts 'ID_USB_DRIVER=usbhid'; then - echo -n 'USB keyboard: ' - else - echo -n 'Unknown type: ' - fi - udevadm info --query=name --path=$dev - done - - # modules - module=$(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*Extra Buttons') - module="$module - $(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='*extra buttons')" - module="$module - $(udevadm trigger --verbose --dry-run --subsystem-match=input --attr-match=name='Sony Vaio Keys')" - for m in $module; do - for evdev in $m/event*/dev; do - if [ -e "$evdev" ]; then - echo -n 'module: ' - udevadm info --query=name --path=${evdev%%/dev} - fi - done - done -} - -keyboard_devices diff --git a/src/udev/keymap/keyboard-force-release.sh.in b/src/udev/keymap/keyboard-force-release.sh.in deleted file mode 100755 index b82674840f..0000000000 --- a/src/udev/keymap/keyboard-force-release.sh.in +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -e -# read list of scancodes, convert hex to decimal and -# append to the atkbd force_release sysfs attribute -# $1 sysfs devpath for serioX -# $2 file with scancode list (hex or dec) - -case "$2" in - /*) scf="$2" ;; - *) scf="@udevlibexecdir@/keymaps/force-release/$2" ;; -esac - -read attr <"/sys/$1/force_release" -while read scancode dummy; do - case "$scancode" in - \#*) ;; - *) - scancode=$(($scancode)) - attr="$attr${attr:+,}$scancode" - ;; - esac -done <"$scf" -echo "$attr" >"/sys/$1/force_release" diff --git a/src/udev/keymap/keymap.c b/src/udev/keymap/keymap.c deleted file mode 100644 index ae0a19d3a3..0000000000 --- a/src/udev/keymap/keymap.c +++ /dev/null @@ -1,453 +0,0 @@ -/* - * keymap - dump keymap of an evdev device or set a new keymap from a file - * - * Based on keyfuzz by Lennart Poettering <mzqrovna@0pointer.net> - * Adapted for udev-extras by Martin Pitt <martin.pitt@ubuntu.com> - * - * Copyright (C) 2006, Lennart Poettering - * Copyright (C) 2009, Canonical Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> -#include <ctype.h> -#include <unistd.h> -#include <errno.h> -#include <limits.h> -#include <fcntl.h> -#include <getopt.h> -#include <sys/ioctl.h> -#include <linux/limits.h> -#include <linux/input.h> - -static const struct key* lookup_key (const char *str, unsigned int len); - -#include "keys-from-name.h" -#include "keys-to-name.h" -#include "macro.h" -#include "util.h" - -#define MAX_SCANCODES 1024 - -static int evdev_open(const char *dev) -{ - int fd; - char fn[PATH_MAX]; - - if (!startswith(dev, "/dev")) { - snprintf(fn, sizeof(fn), "/dev/%s", dev); - dev = fn; - } - - if ((fd = open(dev, O_RDWR)) < 0) { - fprintf(stderr, "error open('%s'): %m\n", dev); - return -1; - } - return fd; -} - -static int evdev_get_keycode(int fd, unsigned scancode, int e) -{ - unsigned codes[2]; - - codes[0] = scancode; - if (ioctl(fd, EVIOCGKEYCODE, codes) < 0) { - if (e && errno == EINVAL) { - return -2; - } else { - fprintf(stderr, "EVIOCGKEYCODE for scan code 0x%x: %m\n", scancode); - return -1; - } - } - return codes[1]; -} - -static int evdev_set_keycode(int fd, unsigned scancode, int keycode) -{ - unsigned codes[2]; - - codes[0] = scancode; - codes[1] = (unsigned) keycode; - - if (ioctl(fd, EVIOCSKEYCODE, codes) < 0) { - fprintf(stderr, "EVIOCSKEYCODE: %m\n"); - return -1; - } - return 0; -} - -static int evdev_driver_version(int fd, char *v, size_t l) -{ - int version; - - if (ioctl(fd, EVIOCGVERSION, &version)) { - fprintf(stderr, "EVIOCGVERSION: %m\n"); - return -1; - } - - snprintf(v, l, "%i.%i.%i.", version >> 16, (version >> 8) & 0xff, version & 0xff); - return 0; -} - -static int evdev_device_name(int fd, char *n, size_t l) -{ - if (ioctl(fd, EVIOCGNAME(l), n) < 0) { - fprintf(stderr, "EVIOCGNAME: %m\n"); - return -1; - } - return 0; -} - -/* Return a lower-case string with KEY_ prefix removed */ -static const char* format_keyname(const char* key) { - static char result[101]; - const char* s; - int len; - - for (s = key+4, len = 0; *s && len < 100; ++len, ++s) - result[len] = tolower(*s); - result[len] = '\0'; - return result; -} - -static int dump_table(int fd) { - char version[256], name[256]; - unsigned scancode; - int r = -1; - - if (evdev_driver_version(fd, version, sizeof(version)) < 0) - goto fail; - - if (evdev_device_name(fd, name, sizeof(name)) < 0) - goto fail; - - printf("### evdev %s, driver '%s'\n", version, name); - - r = 0; - for (scancode = 0; scancode < MAX_SCANCODES; scancode++) { - int keycode; - - if ((keycode = evdev_get_keycode(fd, scancode, 1)) < 0) { - if (keycode == -2) - continue; - r = -1; - break; - } - - if (keycode < KEY_MAX && key_names[keycode]) - printf("0x%03x %s\n", scancode, format_keyname(key_names[keycode])); - else - printf("0x%03x 0x%03x\n", scancode, keycode); - } -fail: - return r; -} - -static void set_key(int fd, const char* scancode_str, const char* keyname) -{ - unsigned scancode; - char *endptr; - char t[105] = "KEY_UNKNOWN"; - const struct key *k; - - scancode = (unsigned) strtol(scancode_str, &endptr, 0); - if (*endptr != '\0') { - fprintf(stderr, "ERROR: Invalid scancode\n"); - exit(1); - } - - snprintf(t, sizeof(t), "KEY_%s", keyname); - - if (!(k = lookup_key(t, strlen(t)))) { - fprintf(stderr, "ERROR: Unknown key name '%s'\n", keyname); - exit(1); - } - - if (evdev_set_keycode(fd, scancode, k->id) < 0) - fprintf(stderr, "setting scancode 0x%2X to key code %i failed\n", - scancode, k->id); - else - printf("setting scancode 0x%2X to key code %i\n", - scancode, k->id); -} - -static int merge_table(int fd, FILE *f) { - int r = 0; - int line = 0; - - while (!feof(f)) { - char s[256], *p; - unsigned scancode; - int new_keycode, old_keycode; - - if (!fgets(s, sizeof(s), f)) - break; - - line++; - p = s+strspn(s, "\t "); - if (*p == '#' || *p == '\n') - continue; - - if (sscanf(p, "%i %i", &scancode, &new_keycode) != 2) { - char t[105] = "KEY_UNKNOWN"; - const struct key *k; - - if (sscanf(p, "%i %100s", &scancode, t+4) != 2) { - fprintf(stderr, "WARNING: Parse failure at line %i, ignoring.\n", line); - r = -1; - continue; - } - - if (!(k = lookup_key(t, strlen(t)))) { - fprintf(stderr, "WARNING: Unknown key '%s' at line %i, ignoring.\n", t, line); - r = -1; - continue; - } - - new_keycode = k->id; - } - - - if ((old_keycode = evdev_get_keycode(fd, scancode, 0)) < 0) { - r = -1; - continue; - } - - if (evdev_set_keycode(fd, scancode, new_keycode) < 0) { - r = -1; - continue; - } - - if (new_keycode != old_keycode) - fprintf(stderr, "Remapped scancode 0x%02x to 0x%02x (prior: 0x%02x)\n", - scancode, new_keycode, old_keycode); - } - - fclose(f); - return r; -} - - -/* read one event; return 1 if valid */ -static int read_event(int fd, struct input_event* ev) -{ - int ret; - ret = read(fd, ev, sizeof(struct input_event)); - - if (ret < 0) { - perror("read"); - return 0; - } - if (ret != sizeof(struct input_event)) { - fprintf(stderr, "did not get enough data for event struct, aborting\n"); - return 0; - } - - return 1; -} - -static void print_key(unsigned scancode, uint16_t keycode, int has_scan, int has_key) -{ - const char *keyname; - - /* ignore key release events */ - if (has_key == 1) - return; - - if (has_key == 0 && has_scan != 0) { - fprintf(stderr, "got scan code event 0x%02X without a key code event\n", - scancode); - return; - } - - if (has_scan != 0) - printf("scan code: 0x%02X ", scancode); - else - printf("(no scan code received) "); - - keyname = key_names[keycode]; - if (keyname != NULL) - printf("key code: %s\n", format_keyname(keyname)); - else - printf("key code: %03X\n", keycode); -} - -static void interactive(int fd) -{ - struct input_event ev; - unsigned last_scan = 0; - uint16_t last_key = 0; - int has_scan; /* boolean */ - int has_key; /* 0: none, 1: release, 2: press */ - - /* grab input device */ - ioctl(fd, EVIOCGRAB, 1); - puts("Press ESC to finish, or Control-C if this device is not your primary keyboard"); - - has_scan = has_key = 0; - while (read_event(fd, &ev)) { - /* Drivers usually send the scan code first, then the key code, - * then a SYN. Some drivers (like thinkpad_acpi) send the key - * code first, and some drivers might not send SYN events, so - * keep a robust state machine which can deal with any of those - */ - - if (ev.type == EV_MSC && ev.code == MSC_SCAN) { - if (has_scan) { - fputs("driver did not send SYN event in between key events; previous event:\n", - stderr); - print_key(last_scan, last_key, has_scan, has_key); - has_key = 0; - } - - last_scan = ev.value; - has_scan = 1; - /*printf("--- got scan %u; has scan %i key %i\n", last_scan, has_scan, has_key); */ - } - else if (ev.type == EV_KEY) { - if (has_key) { - fputs("driver did not send SYN event in between key events; previous event:\n", - stderr); - print_key(last_scan, last_key, has_scan, has_key); - has_scan = 0; - } - - last_key = ev.code; - has_key = 1 + ev.value; - /*printf("--- got key %hu; has scan %i key %i\n", last_key, has_scan, has_key);*/ - - /* Stop on ESC */ - if (ev.code == KEY_ESC && ev.value == 0) - break; - } - else if (ev.type == EV_SYN) { - /*printf("--- got SYN; has scan %i key %i\n", has_scan, has_key);*/ - print_key(last_scan, last_key, has_scan, has_key); - - has_scan = has_key = 0; - } - - } - - /* release input device */ - ioctl(fd, EVIOCGRAB, 0); -} - -_noreturn_ static void help(int error) -{ - const char* h = "Usage: keymap <event device> [<map file>]\n" - " keymap <event device> scancode keyname [...]\n" - " keymap -i <event device>\n"; - if (error) { - fputs(h, stderr); - exit(2); - } else { - fputs(h, stdout); - exit(0); - } -} - -int main(int argc, char **argv) -{ - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "interactive", no_argument, NULL, 'i' }, - {} - }; - int fd = -1; - int opt_interactive = 0; - int i; - - while (1) { - int option; - - option = getopt_long(argc, argv, "hi", options, NULL); - if (option == -1) - break; - - switch (option) { - case 'h': - help(0); - - case 'i': - opt_interactive = 1; - break; - default: - return 1; - } - } - - if (argc < optind+1) - help (1); - - if ((fd = evdev_open(argv[optind])) < 0) - return 3; - - /* one argument (device): dump or interactive */ - if (argc == optind+1) { - if (opt_interactive) - interactive(fd); - else - dump_table(fd); - return 0; - } - - /* two arguments (device, mapfile): set map file */ - if (argc == optind+2) { - const char *filearg = argv[optind+1]; - if (strchr(filearg, '/')) { - /* Keymap file argument is a path */ - FILE *f = fopen(filearg, "re"); - if (f) - merge_table(fd, f); - else - perror(filearg); - } else { - /* Keymap file argument is a filename */ - /* Open override file if present, otherwise default file */ - char keymap_path[PATH_MAX]; - FILE *f; - - snprintf(keymap_path, sizeof(keymap_path), "/etc/udev/keymaps/%s", filearg); - f = fopen(keymap_path, "re"); - if (f) { - merge_table(fd, f); - } else { - snprintf(keymap_path, sizeof(keymap_path), UDEVLIBEXECDIR "/keymaps/%s", filearg); - f = fopen(keymap_path, "re"); - if (f) - merge_table(fd, f); - else - perror(keymap_path); - } - } - return 0; - } - - /* more arguments (device, scancode/keyname pairs): set keys directly */ - if ((argc - optind - 1) % 2 == 0) { - for (i = optind+1; i < argc; i += 2) - set_key(fd, argv[i], argv[i+1]); - return 0; - } - - /* invalid number of arguments */ - help(1); - return 1; /* not reached */ -} diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index bae429344f..b48dccc2fb 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -67,6 +67,9 @@ static void print_property(struct udev_device *dev, bool test, const char *name, } else if (streq(name, "PTTYPE")) { udev_builtin_add_property(dev, test, "ID_PART_TABLE_TYPE", value); + } else if (streq(name, "PTUUID")) { + udev_builtin_add_property(dev, test, "ID_PART_TABLE_UUID", value); + } else if (streq(name, "PART_ENTRY_NAME")) { blkid_encode_string(value, s, sizeof(s)); udev_builtin_add_property(dev, test, "ID_PART_ENTRY_NAME", s); diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 0b35d799fe..d6aa96bb3d 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -23,20 +23,37 @@ #include <inttypes.h> #include <ctype.h> #include <stdlib.h> +#include <fnmatch.h> #include <getopt.h> #include "udev.h" static struct udev_hwdb *hwdb; -int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *modalias, bool test) { +int udev_builtin_hwdb_lookup(struct udev_device *dev, + const char *prefix, const char *modalias, + const char *filter, bool test) { + struct udev_list_entry *list; struct udev_list_entry *entry; int n = 0; if (!hwdb) return -ENOENT; - udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, modalias, 0)) { + if (prefix) { + _cleanup_free_ const char *lookup; + + lookup = strjoin(prefix, modalias, NULL); + if (!lookup) + return -ENOMEM; + list = udev_hwdb_get_properties_list_entry(hwdb, lookup, 0); + } else + list = udev_hwdb_get_properties_list_entry(hwdb, modalias, 0); + + udev_list_entry_foreach(entry, list) { + if (filter && fnmatch(filter, udev_list_entry_get_name(entry), FNM_NOESCAPE) != 0) + continue; + if (udev_builtin_add_property(dev, test, udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)) < 0) @@ -66,12 +83,14 @@ static const char *modalias_usb(struct udev_device *dev, char *s, size_t size) { return s; } -static int udev_builtin_hwdb_search(struct udev_device *dev, const char *subsystem, bool test) { +static int udev_builtin_hwdb_search(struct udev_device *dev, struct udev_device *srcdev, + const char *subsystem, const char *prefix, + const char *filter, bool test) { struct udev_device *d; char s[16]; int n = 0; - for (d = dev; d; d = udev_device_get_parent(d)) { + for (d = srcdev; d; d = udev_device_get_parent(d)) { const char *dsubsys; const char *modalias = NULL; @@ -83,16 +102,16 @@ static int udev_builtin_hwdb_search(struct udev_device *dev, const char *subsyst if (subsystem && !streq(dsubsys, subsystem)) continue; - /* the usb_device does not have a modalias, compose one */ - if (streq(dsubsys, "usb")) - modalias = modalias_usb(dev, s, sizeof(s)); + modalias = udev_device_get_property_value(d, "MODALIAS"); - if (!modalias) - modalias = udev_device_get_property_value(d, "MODALIAS"); + /* the usb_device does not have a modalias, compose one */ + if (!modalias && streq(dsubsys, "usb")) + modalias = modalias_usb(d, s, sizeof(s)); if (!modalias) continue; - n = udev_builtin_hwdb_lookup(dev, modalias, test); + + n = udev_builtin_hwdb_lookup(dev, prefix, modalias, filter, test); if (n > 0) break; } @@ -102,10 +121,17 @@ static int udev_builtin_hwdb_search(struct udev_device *dev, const char *subsyst static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) { static const struct option options[] = { + { "filter", required_argument, NULL, 'f' }, + { "device", required_argument, NULL, 'd' }, { "subsystem", required_argument, NULL, 's' }, + { "lookup-prefix", required_argument, NULL, 'p' }, {} }; + const char *filter = NULL; + const char *device = NULL; const char *subsystem = NULL; + const char *prefix = NULL; + struct udev_device *srcdev; if (!hwdb) return EXIT_FAILURE; @@ -113,20 +139,47 @@ static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool te for (;;) { int option; - option = getopt_long(argc, argv, "s", options, NULL); + option = getopt_long(argc, argv, "f:d:s:p:", options, NULL); if (option == -1) break; switch (option) { + case 'f': + filter = optarg; + break; + + case 'd': + device = optarg; + break; + case 's': subsystem = optarg; break; + + case 'p': + prefix = optarg; + break; } } - if (udev_builtin_hwdb_search(dev, subsystem, test) < 0) + /* query a specific key given as argument */ + if (argv[optind]) { + if (udev_builtin_hwdb_lookup(dev, prefix, argv[optind], filter, test) > 0) + return EXIT_SUCCESS; return EXIT_FAILURE; - return EXIT_SUCCESS; + } + + /* read data from another device than the device we will store the data */ + if (device) { + srcdev = udev_device_new_from_device_id(udev_device_get_udev(dev), device); + if (!srcdev) + return EXIT_FAILURE; + } else + srcdev = dev; + + if (udev_builtin_hwdb_search(dev, srcdev, subsystem, prefix, filter, test) > 0) + return EXIT_SUCCESS; + return EXIT_FAILURE; } /* called at udev startup and reload */ diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c new file mode 100644 index 0000000000..ddd853594e --- /dev/null +++ b/src/udev/udev-builtin-keyboard.c @@ -0,0 +1,163 @@ +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers <kay@vrfy.org> + + 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 <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/limits.h> +#include <linux/input.h> + +#include "udev.h" + +static const struct key *keyboard_lookup_key(const char *str, unsigned int len); +#include "keyboard-keys-from-name.h" +#include "keyboard-keys-to-name.h" + +static int install_force_release(struct udev_device *dev, const unsigned int *release, unsigned int release_count) { + struct udev_device *atkbd; + const char *cur; + char codes[4096]; + char *s; + size_t l; + unsigned int i; + int ret; + + atkbd = udev_device_get_parent_with_subsystem_devtype(dev, "serio", NULL); + if (!atkbd) + return -ENODEV; + + cur = udev_device_get_sysattr_value(atkbd, "force_release"); + if (!cur) + return -ENODEV; + + s = codes; + l = sizeof(codes); + + /* copy current content */ + l = strpcpy(&s, l, cur); + + /* append new codes */ + for (i = 0; i < release_count; i++) + l = strpcpyf(&s, l, ",%d", release[i]); + + log_debug("keyboard: updating force-release list with '%s'\n", codes); + ret = udev_device_set_sysattr_value(atkbd, "force_release", codes); + if (ret < 0) + log_error("Error writing force-release attribute: %s", strerror(-ret)); + return ret; +} + +static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], bool test) { + struct udev_list_entry *entry; + struct { + unsigned int scan; + unsigned int key; + } map[1024]; + unsigned int map_count = 0; + unsigned int release[1024]; + unsigned int release_count = 0; + + udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) { + const char *key; + unsigned int scancode; + char *endptr; + const char *keycode; + const struct key *k; + + key = udev_list_entry_get_name(entry); + if (!startswith(key, "KEYBOARD_KEY_")) + continue; + + /* KEYBOARD_KEY_<hex scan code>=<key identifier string> */ + scancode = strtol(key + 13, &endptr, 16); + if (endptr[0] != '\0') { + log_error("Error, unable to parse scan code from '%s'\n", key); + continue; + } + + keycode = udev_list_entry_get_value(entry); + + /* a leading '!' needs a force-release entry */ + if (keycode[0] == '!') { + keycode++; + + release[release_count] = scancode; + if (release_count < ELEMENTSOF(release)-1) + release_count++; + + if (keycode[0] == '\0') + continue; + } + + /* translate identifier to key code */ + k = keyboard_lookup_key(keycode, strlen(keycode)); + if (!k) { + log_error("Error, unknown key identifier '%s'\n", keycode); + continue; + } + + map[map_count].scan = scancode; + map[map_count].key = k->id; + if (map_count < ELEMENTSOF(map)-1) + map_count++; + } + + if (map_count > 0 || release_count > 0) { + const char *node; + int fd; + unsigned int i; + + node = udev_device_get_devnode(dev); + if (!node) { + log_error("Error, no device node for '%s'\n", udev_device_get_syspath(dev)); + return EXIT_FAILURE; + } + + fd = open(udev_device_get_devnode(dev), O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + log_error("Error, opening device '%s': %m\n", node); + return EXIT_FAILURE; + } + + /* install list of map codes */ + for (i = 0; i < map_count; i++) { + log_debug("keyboard: mapping scan code %d (0x%x) to key code %d (0x%x)\n", + map[i].scan, map[i].scan, map[i].key, map[i].key); + if (ioctl(fd, EVIOCSKEYCODE, &map[i]) < 0) + log_error("Error calling EVIOCSKEYCODE: %m\n"); + } + + /* install list of force-release codes */ + if (release_count > 0) + install_force_release(dev, release, release_count); + + close(fd); + } + + return EXIT_SUCCESS; +} + +const struct udev_builtin udev_builtin_keyboard = { + .name = "keyboard", + .cmd = builtin_keyboard, + .help = "keyboard scan code to key mapping", +}; diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index 5719021e93..9ae8f08ccf 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -35,13 +35,16 @@ * o<index> -- on-board device index number * s<slot>[f<function>][d<dev_id>] -- hotplug slot index number * x<MAC> -- MAC address - * p<bus>s<slot>[f<function>][d<dev_id>] -- PCI geographical location - * p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] + * [P<domain>]p<bus>s<slot>[f<function>][d<dev_id>] + * -- PCI geographical location + * [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] * -- USB port number chain * * All multi-function PCI devices will carry the [f<function>] number in the * device name, including the function 0 device. * + * When using PCI geography, The PCI domain is only prepended when it is not 0. + * * For USB devices the full chain of port numbers of hubs is composed. If the * name gets longer than the maximum number of 15 characters, the name is not * exported. @@ -163,6 +166,7 @@ out: static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { struct udev *udev = udev_device_get_udev(names->pcidev); + unsigned int domain; unsigned int bus; unsigned int slot; unsigned int func; @@ -178,7 +182,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { int hotplug_slot = 0; int err = 0; - if (sscanf(udev_device_get_sysname(names->pcidev), "0000:%x:%x.%d", &bus, &slot, &func) != 3) + if (sscanf(udev_device_get_sysname(names->pcidev), "%x:%x:%x.%d", &domain, &bus, &slot, &func) != 4) return -ENOENT; /* kernel provided multi-device index */ @@ -188,7 +192,10 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { /* compose a name based on the raw kernel's PCI bus, slot numbers */ s = names->pci_path; - l = strpcpyf(&s, sizeof(names->pci_path), "p%ds%d", bus, slot); + l = sizeof(names->pci_path); + if (domain > 0) + l = strpcpyf(&s, l, "P%d", domain); + l = strpcpyf(&s, l, "p%ds%d", bus, slot); if (func > 0 || is_pci_multifunction(names->pcidev)) l = strpcpyf(&s, l, "f%d", func); if (dev_id > 0) @@ -236,7 +243,10 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { if (hotplug_slot > 0) { s = names->pci_slot; - l = strpcpyf(&s, sizeof(names->pci_slot), "s%d", hotplug_slot); + l = sizeof(names->pci_slot); + if (domain > 0) + l = strpcpyf(&s, l, "P%d", domain); + l = strpcpyf(&s, l, "s%d", hotplug_slot); if (func > 0 || is_pci_multifunction(names->pcidev)) l = strpcpyf(&s, l, "f%d", func); if (dev_id > 0) @@ -386,7 +396,7 @@ static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test) snprintf(str, sizeof(str), "OUI:%02X%02X%02X%02X%02X%02X", names->mac[0], names->mac[1], names->mac[2], names->mac[3], names->mac[4], names->mac[5]); - udev_builtin_hwdb_lookup(dev, str, test); + udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test); return 0; } diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index da0273197b..0659967c68 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -531,6 +531,7 @@ static int builtin_path_id(struct udev_device *dev, int argc, char *argv[], bool } else if (streq(subsys, "scm")) { path_prepend(&path, "scm-%s", udev_device_get_sysname(parent)); parent = skip_subsystem(parent, "scm"); + some_transport = true; } parent = udev_device_get_parent(parent); diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index c7d431988d..6b3a518c2e 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -39,6 +39,7 @@ static const struct udev_builtin *builtins[] = { #endif [UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb, [UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id, + [UDEV_BUILTIN_KEYBOARD] = &udev_builtin_keyboard, #ifdef HAVE_KMOD [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod, #endif diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 7a4fb70258..6f8b127872 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -33,6 +33,8 @@ #include "path-util.h" #include "conf-files.h" #include "strbuf.h" +#include "strv.h" +#include "util.h" #define PREALLOC_TOKEN 2048 @@ -152,9 +154,9 @@ enum token_type { TK_A_OWNER_ID, /* uid_t */ TK_A_GROUP_ID, /* gid_t */ TK_A_MODE_ID, /* mode_t */ + TK_A_TAG, /* val */ TK_A_STATIC_NODE, /* val */ TK_A_ENV, /* val, attr */ - TK_A_TAG, /* val */ TK_A_NAME, /* val */ TK_A_DEVLINK, /* val */ TK_A_ATTR, /* val, attr */ @@ -1065,8 +1067,28 @@ static int add_rule(struct udev_rules *rules, char *line, char *value; enum operation_type op; - if (get_key(rules->udev, &linepos, &key, &op, &value) != 0) + if (get_key(rules->udev, &linepos, &key, &op, &value) != 0) { + /* Avoid erroring on trailing whitespace. This is probably rare + * so save the work for the error case instead of always trying + * to strip the trailing whitespace with strstrip(). */ + while (isblank(*linepos)) + linepos++; + + /* If we aren't at the end of the line, this is a parsing error. + * Make a best effort to describe where the problem is. */ + if (*linepos != '\n') { + char buf[2] = {linepos[1]}; + _cleanup_free_ char *tmp; + + tmp = cescape(buf); + log_error("invalid key/value pair in file %s on line %u," + "starting at character %tu ('%s')\n", + filename, lineno, linepos - line + 1, tmp); + if (linepos[1] == '#') + log_info("hint: comments can only start at beginning of line"); + } break; + } if (streq(key, "ACTION")) { if (op > OP_MATCH_MAX) { @@ -1614,7 +1636,7 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) } strv_uniq(rules->dirs); - rules->dirs_ts_usec = calloc(strv_length(rules->dirs), sizeof(long long)); + rules->dirs_ts_usec = calloc(strv_length(rules->dirs), sizeof(usec_t)); if(!rules->dirs_ts_usec) return udev_rules_unref(rules); udev_rules_check_timestamp(rules); @@ -2496,16 +2518,21 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event } } -void udev_rules_apply_static_dev_perms(struct udev_rules *rules) +int udev_rules_apply_static_dev_perms(struct udev_rules *rules) { struct token *cur; struct token *rule; uid_t uid = 0; gid_t gid = 0; mode_t mode = 0; + _cleanup_strv_free_ char **tags = NULL; + char **t; + FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r = 0; if (rules->tokens == NULL) - return; + return 0; cur = &rules->tokens[0]; rule = cur; @@ -2522,6 +2549,8 @@ void udev_rules_apply_static_dev_perms(struct udev_rules *rules) uid = 0; gid = 0; mode = 0; + strv_free(tags); + tags = NULL; break; case TK_A_OWNER_ID: uid = cur->key.uid; @@ -2532,18 +2561,56 @@ void udev_rules_apply_static_dev_perms(struct udev_rules *rules) case TK_A_MODE_ID: mode = cur->key.mode; break; + case TK_A_TAG: + r = strv_extend(&tags, rules_str(rules, cur->key.value_off)); + if (r < 0) + goto finish; + + break; case TK_A_STATIC_NODE: { - char filename[UTIL_PATH_SIZE]; + char device_node[UTIL_PATH_SIZE]; + char tags_dir[UTIL_PATH_SIZE]; + char tag_symlink[UTIL_PATH_SIZE]; struct stat stats; /* we assure, that the permissions tokens are sorted before the static token */ - if (mode == 0 && uid == 0 && gid == 0) + if (mode == 0 && uid == 0 && gid == 0 && tags == NULL) goto next; - strscpyl(filename, sizeof(filename), "/dev/", rules_str(rules, cur->key.value_off), NULL); - if (stat(filename, &stats) != 0) + strscpyl(device_node, sizeof(device_node), "/dev/", rules_str(rules, cur->key.value_off), NULL); + if (stat(device_node, &stats) != 0) goto next; if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode)) goto next; + + if (tags) { + /* Export the tags to a directory as symlinks, allowing otherwise dead nodes to be tagged */ + + STRV_FOREACH(t, tags) { + _cleanup_free_ char *unescaped_filename = NULL; + + strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL); + r = mkdir_p(tags_dir, 0755); + if (r < 0) { + log_error("failed to create %s: %s\n", tags_dir, strerror(-r)); + return r; + } + + unescaped_filename = xescape(rules_str(rules, cur->key.value_off), "/."); + + strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL); + r = symlink(device_node, tag_symlink); + if (r < 0 && errno != EEXIST) { + log_error("failed to create symlink %s -> %s: %s\n", tag_symlink, device_node, strerror(errno)); + return -errno; + } else + r = 0; + } + } + + /* don't touch the permissions if only the tags were set */ + if (mode == 0 && uid == 0 && gid == 0) + goto next; + if (mode == 0) { if (gid > 0) mode = 0660; @@ -2551,20 +2618,28 @@ void udev_rules_apply_static_dev_perms(struct udev_rules *rules) mode = 0600; } if (mode != (stats.st_mode & 01777)) { - chmod(filename, mode); - log_debug("chmod '%s' %#o\n", filename, mode); + r = chmod(device_node, mode); + if (r < 0) { + log_error("failed to chmod '%s' %#o\n", device_node, mode); + return -errno; + } else + log_debug("chmod '%s' %#o\n", device_node, mode); } if ((uid != 0 && uid != stats.st_uid) || (gid != 0 && gid != stats.st_gid)) { - chown(filename, uid, gid); - log_debug("chown '%s' %u %u\n", filename, uid, gid); + r = chown(device_node, uid, gid); + if (r < 0) { + log_error("failed to chown '%s' %u %u \n", device_node, uid, gid); + return -errno; + } else + log_debug("chown '%s' %u %u\n", device_node, uid, gid); } - utimensat(AT_FDCWD, filename, NULL, 0); + utimensat(AT_FDCWD, device_node, NULL, 0); break; } case TK_END: - return; + goto finish; } cur++; @@ -2574,4 +2649,18 @@ next: cur = rule + rule->rule.token_count; continue; } + +finish: + if (f) { + fflush(f); + fchmod(fileno(f), 0644); + if (ferror(f) || rename(path, "/run/udev/static_node-tags") < 0) { + r = -errno; + unlink("/run/udev/static_node-tags"); + unlink(path); + } + fclose(f); + } + + return r; } diff --git a/src/udev/udev.h b/src/udev/udev.h index caec5f0a5d..839592680b 100644 --- a/src/udev/udev.h +++ b/src/udev/udev.h @@ -72,7 +72,7 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names); struct udev_rules *udev_rules_unref(struct udev_rules *rules); bool udev_rules_check_timestamp(struct udev_rules *rules); int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask); -void udev_rules_apply_static_dev_perms(struct udev_rules *rules); +int udev_rules_apply_static_dev_perms(struct udev_rules *rules); /* udev-event.c */ struct udev_event *udev_event_new(struct udev_device *dev); @@ -145,6 +145,7 @@ enum udev_builtin_cmd { #endif UDEV_BUILTIN_HWDB, UDEV_BUILTIN_INPUT_ID, + UDEV_BUILTIN_KEYBOARD, #ifdef HAVE_KMOD UDEV_BUILTIN_KMOD, #endif @@ -174,6 +175,7 @@ extern const struct udev_builtin udev_builtin_firmware; #endif extern const struct udev_builtin udev_builtin_hwdb; extern const struct udev_builtin udev_builtin_input_id; +extern const struct udev_builtin udev_builtin_keyboard; #ifdef HAVE_KMOD extern const struct udev_builtin udev_builtin_kmod; #endif @@ -190,7 +192,8 @@ int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const c void udev_builtin_list(struct udev *udev); bool udev_builtin_validate(struct udev *udev); int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val); -int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *modalias, bool test); +int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *prefix, const char *modalias, + const char *filter, bool test); /* udev logging */ void udev_main_log(struct udev *udev, int priority, diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index 3e849aaed6..d9dc73bfc1 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -21,6 +21,7 @@ #include <unistd.h> #include <getopt.h> #include <string.h> +#include <ctype.h> #include "util.h" #include "strbuf.h" @@ -302,8 +303,10 @@ static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { int64_t child_off; child_off = trie_store_nodes(trie, node->children[i].child); - if (child_off < 0) + if (child_off < 0) { + free(children); return child_off; + } children[i].c = node->children[i].c; children[i].child_off = htole64(child_off); } @@ -402,66 +405,122 @@ out: return err; } -static int import_file(struct trie *trie, const char *filename) { +static int insert_data(struct trie *trie, struct udev_list *match_list, + char *line, const char *filename) { + char *value; + struct udev_list_entry *entry; + + value = strchr(line, '='); + if (!value) { + log_error("Error, key/value pair expected but got '%s' in '%s':\n", line, filename); + return -EINVAL; + } + + value[0] = '\0'; + value++; + + if (line[0] == '\0' || value[0] == '\0') { + log_error("Error, empty key or value '%s' in '%s':\n", line, filename); + return -EINVAL; + } + + udev_list_entry_foreach(entry, udev_list_get_entry(match_list)) + trie_insert(trie, trie->root, udev_list_entry_get_name(entry), line, value); + + return 0; +} + +static int import_file(struct udev *udev, struct trie *trie, const char *filename) { + enum { + HW_MATCH, + HW_DATA, + HW_NONE, + } state = HW_NONE; FILE *f; char line[LINE_MAX]; - char match[LINE_MAX]; - char cond[LINE_MAX]; + struct udev_list match_list; + + udev_list_init(udev, &match_list, false); f = fopen(filename, "re"); if (f == NULL) return -errno; - match[0] = '\0'; - cond[0] = '\0'; while (fgets(line, sizeof(line), f)) { size_t len; + char *pos; + /* comment line */ if (line[0] == '#') continue; - /* new line, new record */ - if (line[0] == '\n') { - match[0] = '\0'; - cond[0] = '\0'; - continue; - } + /* strip trailing comment */ + pos = strchr(line, '#'); + if (pos) + pos[0] = '\0'; - /* remove newline */ + /* strip trailing whitespace */ len = strlen(line); - if (len < 2) - continue; - line[len-1] = '\0'; + while (len > 0 && isspace(line[len-1])) + len--; + line[len] = '\0'; + + switch (state) { + case HW_NONE: + if (len == 0) + break; + + if (line[0] == ' ') { + log_error("Error, MATCH expected but got '%s' in '%s':\n", line, filename); + break; + } - /* start of new record */ - if (match[0] == '\0') { - strcpy(match, line); - cond[0] = '\0'; - continue; - } + /* start of record, first match */ + state = HW_MATCH; + udev_list_entry_add(&match_list, line, NULL); + break; - if (line[0] == '+') { - strcpy(cond, line); - continue; - } + case HW_MATCH: + if (len == 0) { + log_error("Error, DATA expected but got empty line in '%s':\n", filename); + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } - /* TODO: support +; skip the entire record until we support it */ - if (cond[0] != '\0') - continue; + /* another match */ + if (line[0] != ' ') { + udev_list_entry_add(&match_list, line, NULL); + break; + } - /* value lines */ - if (line[0] == ' ') { - char *value; + /* first data */ + state = HW_DATA; + insert_data(trie, &match_list, line, filename); + break; - value = strchr(line, '='); - if (!value) - continue; - value[0] = '\0'; - value++; - trie_insert(trie, trie->root, match, line, value); - } + case HW_DATA: + /* end of record */ + if (len == 0) { + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + if (line[0] != ' ') { + log_error("Error, DATA expected but got '%s' in '%s':\n", line, filename); + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + insert_data(trie, &match_list, line, filename); + break; + }; } + fclose(f); + udev_list_cleanup(&match_list); return 0; } @@ -549,7 +608,7 @@ static int adm_hwdb(struct udev *udev, int argc, char *argv[]) { } STRV_FOREACH(f, files) { log_debug("reading file '%s'", *f); - import_file(trie, *f); + import_file(udev, trie, *f); } strv_free(files); diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 002876594f..2ee59fe075 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -251,6 +251,12 @@ static void cleanup_db(struct udev *udev) closedir(dir); } + dir = opendir("/run/udev/static_node-tags"); + if (dir != NULL) { + cleanup_dir(dir, 0, 2); + closedir(dir); + } + dir = opendir("/run/udev/watch"); if (dir != NULL) { cleanup_dir(dir, 0, 1); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 7d13b4f532..7c6c5d6a87 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -812,77 +812,6 @@ static void handle_signal(struct udev *udev, int signo) } } -static void static_dev_create_from_modules(struct udev *udev) -{ - struct utsname kernel; - char modules[UTIL_PATH_SIZE]; - char buf[4096]; - FILE *f; - - if (uname(&kernel) < 0) { - log_error("uname failed: %m"); - return; - } - - strscpyl(modules, sizeof(modules), ROOTPREFIX "/lib/modules/", kernel.release, "/modules.devname", NULL); - f = fopen(modules, "re"); - if (f == NULL) - return; - - while (fgets(buf, sizeof(buf), f) != NULL) { - char *s; - const char *modname; - const char *devname; - const char *devno; - int maj, min; - char type; - mode_t mode; - char filename[UTIL_PATH_SIZE]; - - if (buf[0] == '#') - continue; - - modname = buf; - s = strchr(modname, ' '); - if (s == NULL) - continue; - s[0] = '\0'; - - devname = &s[1]; - s = strchr(devname, ' '); - if (s == NULL) - continue; - s[0] = '\0'; - - devno = &s[1]; - s = strchr(devno, ' '); - if (s == NULL) - s = strchr(devno, '\n'); - if (s != NULL) - s[0] = '\0'; - if (sscanf(devno, "%c%u:%u", &type, &maj, &min) != 3) - continue; - - mode = 0600; - if (type == 'c') - mode |= S_IFCHR; - else if (type == 'b') - mode |= S_IFBLK; - else - continue; - - strscpyl(filename, sizeof(filename), "/dev/", devname, NULL); - mkdir_parents_label(filename, 0755); - label_context_set(filename, mode); - log_debug("mknod '%s' %c%u:%u\n", filename, type, maj, min); - if (mknod(filename, mode, makedev(maj, min)) < 0 && errno == EEXIST) - utimensat(AT_FDCWD, filename, NULL, 0); - label_context_clear(); - } - - fclose(f); -} - static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink) { int ctrl = -1, netlink = -1; @@ -994,7 +923,10 @@ int main(int argc, char *argv[]) log_set_target(LOG_TARGET_AUTO); log_parse_environment(); log_open(); + udev_set_log_fn(udev, udev_main_log); + log_set_max_level(udev_get_log_priority(udev)); + log_debug("version %s\n", VERSION); label_init("/dev"); @@ -1067,7 +999,6 @@ int main(int argc, char *argv[]) mkdir("/run/udev", 0755); dev_setup(NULL); - static_dev_create_from_modules(udev); /* before opening new files, make sure std{in,out,err} fds are in a sane state */ if (daemonize) { @@ -1269,7 +1200,9 @@ int main(int argc, char *argv[]) } log_debug("set children_max to %u\n", children_max); - udev_rules_apply_static_dev_perms(rules); + rc = udev_rules_apply_static_dev_perms(rules); + if (rc < 0) + log_error("failed to apply permissions on static device nodes - %s\n", strerror(-rc)); udev_list_node_init(&event_list); udev_list_node_init(&worker_list); diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 9184025554..202aa98767 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -104,7 +104,7 @@ static int get_current_runlevel(Context *c) { { '3', SPECIAL_RUNLEVEL3_TARGET }, { '4', SPECIAL_RUNLEVEL4_TARGET }, { '2', SPECIAL_RUNLEVEL2_TARGET }, - { 'S', SPECIAL_RESCUE_TARGET }, + { '1', SPECIAL_RESCUE_TARGET }, }; const char *interface = "org.freedesktop.systemd1.Unit", |