diff options
Diffstat (limited to 'src/grp-system/grp-utils')
33 files changed, 7613 insertions, 0 deletions
diff --git a/src/grp-system/grp-utils/GNUmakefile b/src/grp-system/grp-utils/GNUmakefile new file mode 120000 index 0000000000..95e5924740 --- /dev/null +++ b/src/grp-system/grp-utils/GNUmakefile @@ -0,0 +1 @@ +../../../GNUmakefile
\ No newline at end of file diff --git a/src/grp-system/grp-utils/Makefile b/src/grp-system/grp-utils/Makefile new file mode 100644 index 0000000000..2c8cc75a7c --- /dev/null +++ b/src/grp-system/grp-utils/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +nested.subdirs += systemd-analyze +nested.subdirs += systemd-delta +nested.subdirs += systemd-fstab-generator +nested.subdirs += systemd-run +nested.subdirs += systemd-sysv-generator + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-analyze/.gitignore b/src/grp-system/grp-utils/systemd-analyze/.gitignore new file mode 100644 index 0000000000..752ea236c8 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/.gitignore @@ -0,0 +1 @@ +/systemd-analyze diff --git a/src/grp-system/grp-utils/systemd-analyze/GNUmakefile b/src/grp-system/grp-utils/systemd-analyze/GNUmakefile new file mode 120000 index 0000000000..13308a50cd --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/GNUmakefile @@ -0,0 +1 @@ +../../../../GNUmakefile
\ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-analyze/Makefile b/src/grp-system/grp-utils/systemd-analyze/Makefile new file mode 100644 index 0000000000..cb1b72e77d --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/Makefile @@ -0,0 +1,39 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +bin_PROGRAMS += systemd-analyze +systemd_analyze_SOURCES = \ + src/analyze/analyze.c \ + src/analyze/analyze-verify.c \ + src/analyze/analyze-verify.h + +systemd_analyze_CFLAGS = \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_analyze_LDADD = \ + libcore.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c new file mode 100644 index 0000000000..22d210a14b --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c @@ -0,0 +1,316 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 <stdlib.h> + +#include "core/manager.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-util.h" +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/log.h" +#include "systemd-basic/path-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/unit-name.h" +#include "systemd-shared/pager.h" + +#include "analyze-verify.h" + +static int prepare_filename(const char *filename, char **ret) { + int r; + const char *name; + _cleanup_free_ char *abspath = NULL; + _cleanup_free_ char *dir = NULL; + _cleanup_free_ char *with_instance = NULL; + char *c; + + assert(filename); + assert(ret); + + r = path_make_absolute_cwd(filename, &abspath); + if (r < 0) + return r; + + name = basename(abspath); + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + r = unit_name_replace_instance(name, "i", &with_instance); + if (r < 0) + return r; + } + + dir = dirname_malloc(abspath); + if (!dir) + return -ENOMEM; + + if (with_instance) + c = path_join(NULL, dir, with_instance); + else + c = path_join(NULL, dir, name); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} + +static int generate_path(char **var, char **filenames) { + const char *old; + char **filename; + + _cleanup_strv_free_ char **ans = NULL; + int r; + + STRV_FOREACH(filename, filenames) { + char *t; + + t = dirname_malloc(*filename); + if (!t) + return -ENOMEM; + + r = strv_consume(&ans, t); + if (r < 0) + return r; + } + + assert_se(strv_uniq(ans)); + + /* First, prepend our directories. Second, if some path was specified, use that, and + * otherwise use the defaults. Any duplicates will be filtered out in path-lookup.c. + * Treat explicit empty path to mean that nothing should be appended. + */ + old = getenv("SYSTEMD_UNIT_PATH"); + if (!streq_ptr(old, "")) { + if (!old) + old = ":"; + + r = strv_extend(&ans, old); + if (r < 0) + return r; + } + + *var = strv_join(ans, ":"); + if (!*var) + return -ENOMEM; + + return 0; +} + +static int verify_socket(Unit *u) { + int r; + + assert(u); + + if (u->type != UNIT_SOCKET) + return 0; + + /* Cannot run this without the service being around */ + + /* This makes sure instance is created if necessary. */ + r = socket_instantiate_service(SOCKET(u)); + if (r < 0) { + log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); + return r; + } + + /* This checks both type of sockets */ + if (UNIT_ISSET(SOCKET(u)->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); + log_unit_debug(u, "Using %s", UNIT(service)->id); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); + return -ENOENT; + } + } + + return 0; +} + +static int verify_executable(Unit *u, ExecCommand *exec) { + if (exec == NULL) + return 0; + + if (access(exec->path, X_OK) < 0) + return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); + + return 0; +} + +static int verify_executables(Unit *u) { + ExecCommand *exec; + int r = 0, k; + unsigned i; + + assert(u); + + exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : + u->type == UNIT_MOUNT ? MOUNT(u)->control_command : + u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; + k = verify_executable(u, exec); + if (k < 0 && r == 0) + r = k; + + if (u->type == UNIT_SERVICE) + for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { + k = verify_executable(u, SERVICE(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + if (u->type == UNIT_SOCKET) + for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { + k = verify_executable(u, SOCKET(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static int verify_documentation(Unit *u, bool check_man) { + char **p; + int r = 0, k; + + STRV_FOREACH(p, u->documentation) { + log_unit_debug(u, "Found documentation item: %s", *p); + + if (check_man && startswith(*p, "man:")) { + k = show_man_page(*p + 4, true); + if (k != 0) { + if (k < 0) + log_unit_error_errno(u, r, "Can't show %s: %m", *p); + else { + log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); + k = -ENOEXEC; + } + if (r == 0) + r = k; + } + } + } + + /* Check remote URLs? */ + + return r; +} + +static int verify_unit(Unit *u, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + int r, k; + + assert(u); + + if (log_get_max_level() >= LOG_DEBUG) + unit_dump(u, stdout, "\t"); + + log_unit_debug(u, "Creating %s/start job", u->id); + r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); + if (r < 0) + log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); + + k = verify_socket(u); + if (k < 0 && r == 0) + r = k; + + k = verify_executables(u); + if (k < 0 && r == 0) + r = k; + + k = verify_documentation(u, check_man); + if (k < 0 && r == 0) + r = k; + + return r; +} + +int verify_units(char **filenames, UnitFileScope scope, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_free_ char *var = NULL; + Manager *m = NULL; + FILE *serial = NULL; + FDSet *fdset = NULL; + char **filename; + int r = 0, k; + + Unit *units[strv_length(filenames)]; + int i, count = 0; + + if (strv_isempty(filenames)) + return 0; + + /* set the path */ + r = generate_path(&var, filenames); + if (r < 0) + return log_error_errno(r, "Failed to generate unit load path: %m"); + + assert_se(set_unit_path(var) >= 0); + + r = manager_new(scope, true, &m); + if (r < 0) + return log_error_errno(r, "Failed to initialize manager: %m"); + + log_debug("Starting manager..."); + + r = manager_startup(m, serial, fdset); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + goto finish; + } + + manager_clear_jobs(m); + + log_debug("Loading remaining units from the command line..."); + + STRV_FOREACH(filename, filenames) { + _cleanup_free_ char *prepared = NULL; + + log_debug("Handling %s...", *filename); + + k = prepare_filename(*filename, &prepared); + if (k < 0) { + log_error_errno(k, "Failed to prepare filename %s: %m", *filename); + if (r == 0) + r = k; + continue; + } + + k = manager_load_unit(m, NULL, prepared, &err, &units[count]); + if (k < 0) { + log_error_errno(k, "Failed to load %s: %m", *filename); + if (r == 0) + r = k; + } else + count++; + } + + for (i = 0; i < count; i++) { + k = verify_unit(units[i], check_man); + if (k < 0 && r == 0) + r = k; + } + +finish: + manager_free(m); + + return r; +} diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h new file mode 100644 index 0000000000..33b809997a --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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> + +#include "systemd-shared/path-lookup.h" + +int verify_units(char **filenames, UnitFileScope scope, bool check_man); diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze.c b/src/grp-system/grp-utils/systemd-analyze/analyze.c new file mode 100644 index 0000000000..ea775c8dd1 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/analyze.c @@ -0,0 +1,1486 @@ +/*** + This file is part of systemd. + + Copyright 2010-2013 Lennart Poettering + Copyright 2013 Simon Peeters + + 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 <getopt.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> + +#include <systemd/sd-bus.h> + +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-util.h" +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/glob-util.h" +#include "systemd-basic/hashmap.h" +#include "systemd-basic/locale-util.h" +#include "systemd-basic/log.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/special.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/strxcpyx.h" +#include "systemd-basic/terminal-util.h" +#include "systemd-basic/unit-name.h" +#include "systemd-basic/util.h" +#include "systemd-shared/bus-unit-util.h" +#include "systemd-shared/pager.h" + +#include "analyze-verify.h" + +#define SCALE_X (0.1 / 1000.0) /* pixels per us */ +#define SCALE_Y (20.0) + +#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) + +#define svg(...) printf(__VA_ARGS__) + +#define svg_bar(class, x1, x2, y) \ + svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \ + (class), \ + SCALE_X * (x1), SCALE_Y * (y), \ + SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) + +#define svg_text(b, x, y, format, ...) \ + do { \ + svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ + svg(format, ## __VA_ARGS__); \ + svg("</text>\n"); \ + } while (false) + +static enum dot { + DEP_ALL, + DEP_ORDER, + DEP_REQUIRE +} arg_dot = DEP_ALL; +static char** arg_dot_from_patterns = NULL; +static char** arg_dot_to_patterns = NULL; +static usec_t arg_fuzz = 0; +static bool arg_no_pager = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; +static bool arg_man = true; + +struct boot_times { + usec_t firmware_time; + usec_t loader_time; + usec_t kernel_time; + usec_t kernel_done_time; + usec_t initrd_time; + usec_t userspace_time; + usec_t finish_time; + usec_t security_start_time; + usec_t security_finish_time; + usec_t generators_start_time; + usec_t generators_finish_time; + usec_t unitsload_start_time; + usec_t unitsload_finish_time; + + /* + * If we're analyzing the user instance, all timestamps will be offset + * by its own start-up timestamp, which may be arbitrarily big. + * With "plot", this causes arbitrarily wide output SVG files which almost + * completely consist of empty space. Thus we cancel out this offset. + * + * This offset is subtracted from times above by acquire_boot_times(), + * but it still needs to be subtracted from unit-specific timestamps + * (so it is stored here for reference). + */ + usec_t reverse_offset; +}; + +struct unit_times { + char *name; + usec_t activating; + usec_t activated; + usec_t deactivated; + usec_t deactivating; + usec_t time; +}; + +struct host_info { + char *hostname; + char *kernel_name; + char *kernel_release; + char *kernel_version; + char *os_pretty_name; + char *virtualization; + char *architecture; +}; + +static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(property); + assert(val); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + interface, + property, + &error, + 't', val); + + if (r < 0) { + log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(property); + assert(strv); + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + property, + &error, + strv); + if (r < 0) { + log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int compare_unit_time(const void *a, const void *b) { + return compare(((struct unit_times *)b)->time, + ((struct unit_times *)a)->time); +} + +static int compare_unit_start(const void *a, const void *b) { + return compare(((struct unit_times *)a)->activating, + ((struct unit_times *)b)->activating); +} + +static void free_unit_times(struct unit_times *t, unsigned n) { + struct unit_times *p; + + for (p = t; p < t + n; p++) + free(p->name); + + free(t); +} + +static void subtract_timestamp(usec_t *a, usec_t b) { + assert(a); + + if (*a > 0) { + assert(*a >= b); + *a -= b; + } +} + +static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { + static struct boot_times times; + static bool cached = false; + + if (cached) + goto finish; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FirmwareTimestampMonotonic", + ×.firmware_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LoaderTimestampMonotonic", + ×.loader_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KernelTimestamp", + ×.kernel_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "InitRDTimestampMonotonic", + ×.initrd_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UserspaceTimestampMonotonic", + ×.userspace_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FinishTimestampMonotonic", + ×.finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityStartTimestampMonotonic", + ×.security_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityFinishTimestampMonotonic", + ×.security_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) { + log_error("Bootup is not yet finished. Please try again later."); + return -EINPROGRESS; + } + + if (arg_user) { + /* + * User-instance-specific timestamps processing + * (see comment to reverse_offset in struct boot_times). + */ + times.reverse_offset = times.userspace_time; + + times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; + subtract_timestamp(×.finish_time, times.reverse_offset); + + subtract_timestamp(×.security_start_time, times.reverse_offset); + subtract_timestamp(×.security_finish_time, times.reverse_offset); + + subtract_timestamp(×.generators_start_time, times.reverse_offset); + subtract_timestamp(×.generators_finish_time, times.reverse_offset); + + subtract_timestamp(×.unitsload_start_time, times.reverse_offset); + subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); + } else { + if (times.initrd_time) + times.kernel_done_time = times.initrd_time; + else + times.kernel_done_time = times.userspace_time; + } + + cached = true; + +finish: + *bt = × + return 0; +} + +static void free_host_info(struct host_info *hi) { + + if (!hi) + return; + + free(hi->hostname); + free(hi->kernel_name); + free(hi->kernel_release); + free(hi->kernel_version); + free(hi->os_pretty_name); + free(hi->virtualization); + free(hi->architecture); + free(hi); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); + +static int acquire_time_data(sd_bus *bus, struct unit_times **out) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, c = 0; + struct boot_times *boot_times = NULL; + struct unit_times *unit_times = NULL; + size_t size = 0; + UnitInfo u; + + r = acquire_boot_times(bus, &boot_times); + if (r < 0) + goto fail; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, &reply, + NULL); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + goto fail; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + struct unit_times *t; + + if (!GREEDY_REALLOC(unit_times, size, c+1)) { + r = log_oom(); + goto fail; + } + + t = unit_times+c; + t->name = NULL; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveExitTimestampMonotonic", + &t->activating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveEnterTimestampMonotonic", + &t->activated) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveExitTimestampMonotonic", + &t->deactivating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveEnterTimestampMonotonic", + &t->deactivated) < 0) { + r = -EIO; + goto fail; + } + + subtract_timestamp(&t->activating, boot_times->reverse_offset); + subtract_timestamp(&t->activated, boot_times->reverse_offset); + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + + if (t->activated >= t->activating) + t->time = t->activated - t->activating; + else if (t->deactivated >= t->activating) + t->time = t->deactivated - t->activating; + else + t->time = 0; + + if (t->activating == 0) + continue; + + t->name = strdup(u.id); + if (t->name == NULL) { + r = log_oom(); + goto fail; + } + c++; + } + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + *out = unit_times; + return c; + +fail: + if (unit_times) + free_unit_times(unit_times, (unsigned) c); + return r; +} + +static int acquire_host_info(sd_bus *bus, struct host_info **hi) { + static const struct bus_properties_map hostname_map[] = { + { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, + { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, + { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, + { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, + { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, + {} + }; + + static const struct bus_properties_map manager_map[] = { + { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, + { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(free_host_infop) struct host_info *host; + int r; + + host = new0(struct host_info, 1); + if (!host) + return log_oom(); + + r = bus_map_all_properties(bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + hostname_map, + host); + if (r < 0) + log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); + + r = bus_map_all_properties(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + manager_map, + host); + if (r < 0) + return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); + + *hi = host; + host = NULL; + + return 0; +} + +static int pretty_boot_time(sd_bus *bus, char **_buf) { + char ts[FORMAT_TIMESPAN_MAX]; + struct boot_times *t; + static char buf[4096]; + size_t size; + char *ptr; + int r; + + r = acquire_boot_times(bus, &t); + if (r < 0) + return r; + + ptr = buf; + size = sizeof(buf); + + size = strpcpyf(&ptr, size, "Startup finished in "); + if (t->firmware_time) + size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); + if (t->loader_time) + size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); + if (t->kernel_time) + size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); + if (t->initrd_time > 0) + size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); + + size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); + strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); + + ptr = strdup(buf); + if (!ptr) + return log_oom(); + + *_buf = ptr; + return 0; +} + +static void svg_graph_box(double height, double begin, double end) { + long long i; + + /* outside box, fill */ + svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n", + SCALE_X * (end - begin), SCALE_Y * height); + + for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { + /* lines for each second */ + if (i % 5000000 == 0) + svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n" + " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else if (i % 1000000 == 0) + svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n" + " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else + svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height); + } +} + +static int analyze_plot(sd_bus *bus) { + _cleanup_(free_host_infop) struct host_info *host = NULL; + struct unit_times *times; + struct boot_times *boot; + int n, m = 1, y=0; + double width; + _cleanup_free_ char *pretty_times = NULL; + struct unit_times *u; + + n = acquire_boot_times(bus, &boot); + if (n < 0) + return n; + + n = pretty_boot_time(bus, &pretty_times); + if (n < 0) + return n; + + n = acquire_host_info(bus, &host); + if (n < 0) + return n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_start); + + width = SCALE_X * (boot->firmware_time + boot->finish_time); + if (width < 800.0) + width = 800.0; + + if (boot->firmware_time > boot->loader_time) + m++; + if (boot->loader_time) { + m++; + if (width < 1000.0) + width = 1000.0; + } + if (boot->initrd_time) + m++; + if (boot->kernel_time) + m++; + + for (u = times; u < times + n; u++) { + double text_start, text_width; + + if (u->activating < boot->userspace_time || + u->activating > boot->finish_time) { + u->name = mfree(u->name); + continue; + } + + /* If the text cannot fit on the left side then + * increase the svg width so it fits on the right. + * TODO: calculate the text width more accurately */ + text_width = 8.0 * strlen(u->name); + text_start = (boot->firmware_time + u->activating) * SCALE_X; + if (text_width > text_start && text_width + text_start > width) + width = text_width + text_start; + + if (u->deactivated > u->activating && u->deactivated <= boot->finish_time + && u->activated == 0 && u->deactivating == 0) + u->activated = u->deactivating = u->deactivated; + if (u->activated < u->activating || u->activated > boot->finish_time) + u->activated = boot->finish_time; + if (u->deactivating < u->activated || u->activated > boot->finish_time) + u->deactivating = boot->finish_time; + if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) + u->deactivated = boot->finish_time; + m++; + } + + svg("<?xml version=\"1.0\" standalone=\"no\"?>\n" + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" " + "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); + + 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) + + 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" + "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n" + "<!-- that render these files properly but much slower are ImageMagick, -->\n" + "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n" + "<!-- point your browser to this file. -->\n\n" + "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION); + + /* style sheet */ + svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n" + " rect { stroke-width: 1; stroke-opacity: 0; }\n" + " rect.background { fill: rgb(255,255,255); }\n" + " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n" + " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n" + " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n" + " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n" + " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n" + " 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.security { fill: rgb(144,238,144); 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: 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("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n"); + svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times); + svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>", + isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, + strempty(host->hostname), + strempty(host->kernel_name), + strempty(host->kernel_release), + strempty(host->kernel_version), + strempty(host->architecture), + strempty(host->virtualization)); + + svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time)); + svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); + + if (boot->firmware_time) { + svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); + svg_text(true, -(double) boot->firmware_time, y, "firmware"); + y++; + } + if (boot->loader_time) { + svg_bar("loader", -(double) boot->loader_time, 0, y); + svg_text(true, -(double) boot->loader_time, y, "loader"); + y++; + } + if (boot->kernel_time) { + svg_bar("kernel", 0, boot->kernel_done_time, y); + svg_text(true, 0, y, "kernel"); + y++; + } + if (boot->initrd_time) { + svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); + svg_text(true, boot->initrd_time, y, "initrd"); + y++; + } + svg_bar("active", boot->userspace_time, boot->finish_time, y); + svg_bar("security", boot->security_start_time, boot->security_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(true, boot->userspace_time, y, "systemd"); + y++; + + for (u = times; u < times + n; u++) { + char ts[FORMAT_TIMESPAN_MAX]; + bool b; + + if (!u->name) + continue; + + svg_bar("activating", u->activating, u->activated, y); + svg_bar("active", u->activated, u->deactivating, y); + svg_bar("deactivating", u->deactivating, u->deactivated, y); + + /* place the text on the left if we have passed the half of the svg width */ + b = u->activating * SCALE_X < width / 2; + if (u->time) + svg_text(b, u->activating, y, "%s (%s)", + u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); + else + svg_text(b, u->activating, y, "%s", u->name); + y++; + } + + svg("</g>\n"); + + /* Legend */ + svg("<g transform=\"translate(20,100)\">\n"); + y++; + svg_bar("activating", 0, 300000, y); + svg_text(true, 400000, y, "Activating"); + y++; + svg_bar("active", 0, 300000, y); + svg_text(true, 400000, y, "Active"); + y++; + svg_bar("deactivating", 0, 300000, y); + svg_text(true, 400000, y, "Deactivating"); + y++; + svg_bar("security", 0, 300000, y); + svg_text(true, 400000, y, "Setting up security module"); + y++; + svg_bar("generators", 0, 300000, y); + svg_text(true, 400000, y, "Generators"); + y++; + svg_bar("unitsload", 0, 300000, y); + svg_text(true, 400000, y, "Loading unit files"); + y++; + + svg("</g>\n\n"); + + svg("</svg>\n"); + + free_unit_times(times, (unsigned) n); + + n = 0; + return n; +} + +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; + char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; + + for (i = level; i != 0; i--) + printf("%s", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE)); + + printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH)); + + if (times) { + if (times->time) + printf("%s%s @%s +%s%s", ansi_highlight_red(), name, + format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), + format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ansi_normal()); + else if (times->activated > boot->userspace_time) + printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s", name); + } else + printf("%s", name); + printf("\n"); + + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + _cleanup_free_ char *path = NULL; + + assert(bus); + assert(name); + assert(deps); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + return bus_get_unit_property_strv(bus, path, "After", deps); +} + +static Hashmap *unit_times_hashmap; + +static int list_dependencies_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + usec_t usa = 0, usb = 0; + struct unit_times *times; + + times = hashmap_get(unit_times_hashmap, *a); + if (times) + usa = times->activated; + times = hashmap_get(unit_times_hashmap, *b); + if (times) + usb = times->activated; + + return usb - usa; +} + +static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, + unsigned int branches) { + _cleanup_strv_free_ char **deps = NULL; + char **c; + int r = 0; + usec_t service_longest = 0; + int to_print = 0; + struct unit_times *times; + struct boot_times *boot; + + if (strv_extend(units, name)) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times + && times->activated + && times->activated <= boot->finish_time + && (times->activated >= service_longest + || service_longest == 0)) { + service_longest = times->activated; + break; + } + } + + if (service_longest == 0 ) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) + to_print++; + } + + if (!to_print) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (!times + || !times->activated + || times->activated > boot->finish_time + || service_longest - times->activated > arg_fuzz) + continue; + + to_print--; + + r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); + if (r < 0) + return r; + + if (strv_contains(*units, *c)) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), + true, NULL, boot); + if (r < 0) + return r; + continue; + } + + r = list_dependencies_one(bus, *c, level + 1, units, + (branches << 1) | (to_print ? 1 : 0)); + if (r < 0) + return r; + + if (!to_print) + break; + } + return 0; +} + +static int list_dependencies(sd_bus *bus, const char *name) { + _cleanup_strv_free_ char **units = NULL; + char ts[FORMAT_TIMESPAN_MAX]; + struct unit_times *times; + int r; + const char *id; + _cleanup_free_ char *path = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct boot_times *boot; + + assert(bus); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to get ID: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &id); + if (r < 0) + return bus_log_parse_error(r); + + times = hashmap_get(unit_times_hashmap, id); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + if (times) { + if (times->time) + printf("%s%s +%s%s\n", ansi_highlight_red(), id, + format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ansi_normal()); + else if (times->activated > boot->userspace_time) + printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s\n", id); + } + + return list_dependencies_one(bus, name, 0, &units, 0); +} + +static int analyze_critical_chain(sd_bus *bus, char *names[]) { + struct unit_times *times; + unsigned int i; + Hashmap *h; + int n, r; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + + for (i = 0; i < (unsigned)n; i++) { + r = hashmap_put(h, times[i].name, ×[i]); + if (r < 0) + return r; + } + unit_times_hashmap = h; + + pager_open(arg_no_pager, false); + + 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"); + + 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); + return 0; +} + +static int analyze_blame(sd_bus *bus) { + struct unit_times *times; + unsigned i; + int n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_time); + + pager_open(arg_no_pager, false); + + for (i = 0; i < (unsigned) n; i++) { + char ts[FORMAT_TIMESPAN_MAX]; + + if (times[i].time > 0) + printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); + } + + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_time(sd_bus *bus) { + _cleanup_free_ char *buf = NULL; + int r; + + r = pretty_boot_time(bus, &buf); + if (r < 0) + return r; + + puts(buf); + return 0; +} + +static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { + _cleanup_strv_free_ char **units = NULL; + char **unit; + int r; + bool match_patterns; + + assert(u); + assert(prop); + assert(color); + + match_patterns = strv_fnmatch(patterns, u->id, 0); + + if (!strv_isempty(from_patterns) && + !match_patterns && + !strv_fnmatch(from_patterns, u->id, 0)) + return 0; + + r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); + if (r < 0) + return r; + + STRV_FOREACH(unit, units) { + bool match_patterns2; + + match_patterns2 = strv_fnmatch(patterns, *unit, 0); + + if (!strv_isempty(to_patterns) && + !match_patterns2 && + !strv_fnmatch(to_patterns, *unit, 0)) + continue; + + if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) + continue; + + printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); + } + + return 0; +} + +static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { + int r; + + assert(bus); + assert(u); + + if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { + r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { + r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + return 0; +} + +static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { + _cleanup_strv_free_ char **expanded_patterns = NULL; + char **pattern; + int r; + + STRV_FOREACH(pattern, patterns) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *unit = NULL, *unit_id = NULL; + + if (strv_extend(&expanded_patterns, *pattern) < 0) + return log_oom(); + + if (string_is_glob(*pattern)) + continue; + + unit = unit_dbus_path_from_name(*pattern); + if (!unit) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &unit_id); + if (r < 0) + return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); + + if (!streq(*pattern, unit_id)) { + if (strv_extend(&expanded_patterns, unit_id) < 0) + return log_oom(); + } + } + + *ret = expanded_patterns; + expanded_patterns = NULL; /* do not free */ + + return 0; +} + +static int dot(sd_bus *bus, char* patterns[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_strv_free_ char **expanded_patterns = NULL; + _cleanup_strv_free_ char **expanded_from_patterns = NULL; + _cleanup_strv_free_ char **expanded_to_patterns = NULL; + int r; + UnitInfo u; + + r = expand_patterns(bus, patterns, &expanded_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, + &reply, + ""); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + printf("digraph systemd {\n"); + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + + r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + printf("}\n"); + + log_info(" Color legend: black = Requires\n" + " dark blue = Requisite\n" + " dark grey = Wants\n" + " red = Conflicts\n" + " green = After\n"); + + if (on_tty()) + log_notice("-- You probably want to process this output with graphviz' dot tool.\n" + "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); + + return 0; +} + +static int dump(sd_bus *bus, char **args) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *text = NULL; + int r; + + if (!strv_isempty(args)) { + log_error("Too many arguments."); + return -E2BIG; + } + + pager_open(arg_no_pager, false); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Dump", + &error, + &reply, + ""); + if (r < 0) + return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &text); + if (r < 0) + return bus_log_parse_error(r); + + fputs(text, stdout); + return 0; +} + +static int set_log_level(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogLevel", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static int set_log_target(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogTarget", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help(void) { + + pager_open(arg_no_pager, false); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Profile systemd, show unit dependencies, check unit files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --system Operate on system systemd instance\n" + " --user Operate on user systemd instance\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --order Show only order in the graph\n" + " --require Show only requirement in the graph\n" + " --from-pattern=GLOB Show only origins in the graph\n" + " --to-pattern=GLOB Show only destinations in the graph\n" + " --fuzz=SECONDS Also print also services which finished SECONDS\n" + " earlier than the latest in the branch\n" + " --man[=BOOL] Do [not] check for existence of man pages\n\n" + "Commands:\n" + " time Print time spent in the kernel\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 Output dependency graph in dot(1) format\n" + " set-log-level LEVEL Set logging threshold for manager\n" + " set-log-target TARGET Set logging target for manager\n" + " dump Output state serialization of service manager\n" + " verify FILE... Check unit files for correctness\n" + , program_invocation_short_name); + + /* When updating this list, including descriptions, apply + * changes to shell-completion/bash/systemd-analyze and + * shell-completion/zsh/_systemd-analyze too. */ +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_ORDER, + ARG_REQUIRE, + ARG_USER, + ARG_SYSTEM, + ARG_DOT_FROM_PATTERN, + ARG_DOT_TO_PATTERN, + ARG_FUZZ, + ARG_NO_PAGER, + ARG_MAN, + }; + + 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 }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "man", optional_argument, NULL, ARG_MAN }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ORDER: + arg_dot = DEP_ORDER; + break; + + case ARG_REQUIRE: + arg_dot = DEP_REQUIRE; + break; + + case ARG_DOT_FROM_PATTERN: + if (strv_extend(&arg_dot_from_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_DOT_TO_PATTERN: + if (strv_extend(&arg_dot_to_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_FUZZ: + r = parse_sec(optarg, &arg_fuzz); + if (r < 0) + return r; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_MAN: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --man= argument."); + return -EINVAL; + } + + arg_man = !!r; + } else + arg_man = true; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + return 1; /* work to do */ +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (streq_ptr(argv[optind], "verify")) + r = verify_units(argv+optind+1, + arg_user ? UNIT_FILE_USER : UNIT_FILE_SYSTEM, + arg_man); + else { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + 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, 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 if (streq(argv[optind], "set-log-target")) + r = set_log_target(bus, argv+optind+1); + else + log_error("Unknown operation '%s'.", argv[optind]); + } + +finish: + pager_close(); + + strv_free(arg_dot_from_patterns); + strv_free(arg_dot_to_patterns); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash new file mode 100644 index 0000000000..7a5f46ba1d --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash @@ -0,0 +1,117 @@ +# systemd-analyze(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2010 Ran Benita +# Copyright 2013 Harald Hoyer +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +__get_machines() { + local a b + machinectl list --no-legend --no-pager | { while read a b; do echo " $a"; done; }; +} + +_systemd_analyze() { + local i verb comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + + local -A OPTS=( + [STANDALONE]='--help --version --system --user --order --require --no-pager --man' + [ARG]='-H --host -M --machine --fuzz --from-pattern --to-pattern ' + ) + + local -A VERBS=( + [STANDALONE]='time blame plot dump' + [CRITICAL_CHAIN]='critical-chain' + [DOT]='dot' + [LOG_LEVEL]='set-log-level' + [VERIFY]='verify' + ) + + _init_completion || return + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --host|-H) + comps=$(compgen -A hostname) + ;; + --machine|-M) + comps=$( __get_machines ) + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ -z $verb && $cur = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + if [[ -z $verb ]]; then + comps=${VERBS[*]} + + elif __contains_word "$verb" ${VERBS[STANDALONE]}; then + if [[ $cur = -* ]]; then + comps='--help --version --system --user' + fi + + elif __contains_word "$verb" ${VERBS[CRITICAL_CHAIN]}; then + if [[ $cur = -* ]]; then + comps='--help --version --system --user --fuzz' + fi + + elif __contains_word "$verb" ${VERBS[DOT]}; then + if [[ $cur = -* ]]; then + comps='--help --version --system --user --from-pattern --to-pattern --order --require' + fi + + elif __contains_word "$verb" ${VERBS[LOG_LEVEL]}; then + if [[ $cur = -* ]]; then + comps='--help --version --system --user' + else + comps='debug info notice warning err crit alert emerg' + fi + + elif __contains_word "$verb" ${VERBS[VERIFY]}; then + if [[ $cur = -* ]]; then + comps='--help --version --system --user --man' + else + comps=$( compgen -A file -- "$cur" ) + compopt -o filenames + fi + + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _systemd_analyze systemd-analyze diff --git a/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh new file mode 100644 index 0000000000..efafddc686 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh @@ -0,0 +1,58 @@ +#compdef systemd-analyze + +_systemd_analyze_set-log-level() { + local -a _levels + _levels=(debug info notice warning err crit alert emerg) + _describe -t level 'logging level' _levels || compadd "$@" +} + +_systemd_analyze_verify() { + _sd_unit_files +} + +_systemd_analyze_command(){ + local -a _systemd_analyze_cmds + # Descriptions taken from systemd-analyze --help. + _systemd_analyze_cmds=( + 'time:Print time spent in the kernel before reaching userspace' + 'blame:Print list of running units ordered by time to init' + 'critical-chain:Print a tree of the time critical chain of units' + 'plot:Output SVG graphic showing service initialization' + 'dot:Dump dependency graph (in dot(1) format)' + 'dump:Dump server status' + 'set-log-level:Set systemd log threshold' + 'verify:Check unit files for correctness' + ) + + if (( CURRENT == 1 )); then + _describe "options" _systemd_analyze_cmds + else + local curcontext="$curcontext" + cmd="${${_systemd_analyze_cmds[(r)$words[1]:*]%%:*}}" + if (( $#cmd )); then + if (( $+functions[_systemd_analyze_$cmd] )) && (( CURRENT == 2 )); then + _systemd_analyze_$cmd + else + _message "no more options" + fi + else + _message "unknown systemd-analyze command: $words[1]" + fi + fi +} + +_arguments \ + {-h,--help}'[Show help text]' \ + '--version[Show package version]' \ + '--system[Operate on system systemd instance]' \ + '--user[Operate on user systemd instance]' \ + '--no-pager[Do not pipe output into a pager]' \ + '--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \ + '--order[When generating graph for dot, show only order]' \ + '--require[When generating graph for dot, show only requirement]' \ + '--fuzz=[When printing the tree of the critical chain, print also services, which finished TIMESPAN earlier, than the latest in the branch]:TIMESPAN' \ + '--from-pattern=[When generating a dependency graph, filter only origins]:GLOB' \ + '--to-pattern=[When generating a dependency graph, filter only destinations]:GLOB' \ + {-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \ + {-M+,--machine=}'[Operate on local container]:machine:_sd_machines' \ + '*::systemd-analyze commands:_systemd_analyze_command' diff --git a/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml new file mode 100644 index 0000000000..8fa7cd3329 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml @@ -0,0 +1,389 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + 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/>. +--> + +<refentry id="systemd-analyze" + xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>systemd-analyze</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + <author> + <contrib>Developer</contrib> + <firstname>Harald</firstname> + <surname>Hoyer</surname> + <email>harald@redhat.com</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-analyze</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-analyze</refname> + <refpurpose>Analyze system boot-up performance</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg>time</arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">blame</arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">critical-chain</arg> + <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">plot</arg> + <arg choice="opt">> file.svg</arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">dot</arg> + <arg choice="opt" rep="repeat"><replaceable>PATTERN</replaceable></arg> + <arg choice="opt">> file.dot</arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">dump</arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">set-log-level</arg> + <arg choice="plain"><replaceable>LEVEL</replaceable></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">set-log-target</arg> + <arg choice="plain"><replaceable>TARGET</replaceable></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-analyze</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain">verify</arg> + <arg choice="opt" rep="repeat"><replaceable>FILES</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-analyze</command> may be used to determine + system boot-up performance statistics and retrieve other state and + tracing information from the system and service manager, and to + verify the correctness of unit files.</para> + + <para><command>systemd-analyze time</command> prints the time + spent in the kernel before userspace has been reached, the time + spent in the initial RAM disk (initrd) before normal system + userspace has been reached, and the time normal system userspace + took to initialize. Note that these measurements simply measure + the time passed up to the point where all system services have + been spawned, but not necessarily until they fully finished + initialization or the disk is idle.</para> + + <para><command>systemd-analyze blame</command> prints a list of + all running units, ordered by the time they took to initialize. + This information may be used to optimize boot-up times. Note that + the output might be misleading as the initialization of one + service might be slow simply because it waits for the + initialization of another service to complete.</para> + + <para><command>systemd-analyze critical-chain + [<replaceable>UNIT...</replaceable>]</command> prints a tree of + the time-critical chain of units (for each of the specified + <replaceable>UNIT</replaceable>s or for the default target + otherwise). The time after the unit is active or started is + printed after the "@" character. The time the unit takes to start + is printed after the "+" character. Note that the output might be + misleading as the initialization of one service might depend on + socket activation and because of the parallel execution of + units.</para> + + <para><command>systemd-analyze plot</command> prints an SVG + graphic detailing which system services have been started at what + time, highlighting the time they spent on initialization.</para> + + <para><command>systemd-analyze dot</command> generates textual + dependency graph description in dot format for further processing + with the GraphViz + <citerefentry project='die-net'><refentrytitle>dot</refentrytitle><manvolnum>1</manvolnum></citerefentry> + tool. Use a command line like <command>systemd-analyze dot | dot + -Tsvg > systemd.svg</command> to generate a graphical dependency + tree. Unless <option>--order</option> or + <option>--require</option> is passed, the generated graph will + show both ordering and requirement dependencies. Optional pattern + globbing style specifications (e.g. <filename>*.target</filename>) + may be given at the end. A unit dependency is included in the + graph if any of these patterns match either the origin or + destination node.</para> + + <para><command>systemd-analyze dump</command> outputs a (usually + very long) human-readable serialization of the complete server + state. Its format is subject to change without notice and should + not be parsed by applications.</para> + + <para><command>systemd-analyze set-log-level + <replaceable>LEVEL</replaceable></command> changes the current log + level of the <command>systemd</command> daemon to + <replaceable>LEVEL</replaceable> (accepts the same values as + <option>--log-level=</option> described in + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para> + + <para><command>systemd-analyze set-log-target + <replaceable>TARGET</replaceable></command> changes the current log + target of the <command>systemd</command> daemon to + <replaceable>TARGET</replaceable> (accepts the same values as + <option>--log-target=</option>, described in + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para> + + <para><command>systemd-analyze verify</command> will load unit files and print + warnings if any errors are detected. Files specified on the command line will be + loaded, but also any other units referenced by them. The full unit search path is + formed by combining the directories for all command line arguments, and the usual unit + load paths (variable <varname>$SYSTEMD_UNIT_PATH</varname> is supported, and may be + used to replace or augment the compiled in set of unit load paths; see + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>). + All units files present in the directories containing the command line arguments will + be used in preference to the other paths.</para> + + <para>If no command is passed, <command>systemd-analyze + time</command> is implied.</para> + + </refsect1> + + <refsect1> + <title>Options</title> + + <para>The following options are understood:</para> + + <variablelist> + <varlistentry> + <term><option>--user</option></term> + + <listitem><para>Operates on the user systemd + instance.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--system</option></term> + + <listitem><para>Operates on the system systemd instance. This + is the implied default.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--order</option></term> + <term><option>--require</option></term> + + <listitem><para>When used in conjunction with the + <command>dot</command> command (see above), selects which + dependencies are shown in the dependency graph. If + <option>--order</option> is passed, only dependencies of type + <varname>After=</varname> or <varname>Before=</varname> are + shown. If <option>--require</option> is passed, only + dependencies of type <varname>Requires=</varname>, + <varname>Requisite=</varname>, + <varname>Wants=</varname> and <varname>Conflicts=</varname> + are shown. If neither is passed, this shows dependencies of + all these types.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--from-pattern=</option></term> + <term><option>--to-pattern=</option></term> + + <listitem><para>When used in conjunction with the + <command>dot</command> command (see above), this selects which + relationships are shown in the dependency graph. Both options + require a + <citerefentry project='die-net'><refentrytitle>glob</refentrytitle><manvolnum>7</manvolnum></citerefentry> + pattern as an argument, which will be matched against the + left-hand and the right-hand, respectively, nodes of a + relationship.</para> + + <para>Each of these can be used more than once, in which case + the unit name must match one of the values. When tests for + both sides of the relation are present, a relation must pass + both tests to be shown. When patterns are also specified as + positional arguments, they must match at least one side of the + relation. In other words, patterns specified with those two + options will trim the list of edges matched by the positional + arguments, if any are given, and fully determine the list of + edges shown otherwise.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--fuzz=</option><replaceable>timespan</replaceable></term> + + <listitem><para>When used in conjunction with the + <command>critical-chain</command> command (see above), also + show units, which finished <replaceable>timespan</replaceable> + earlier, than the latest unit in the same level. The unit of + <replaceable>timespan</replaceable> is seconds unless + specified with a different unit, e.g. + "50ms".</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--no-man</option></term> + + <listitem><para>Do not invoke man to verify the existence of + man pages listed in <varname>Documentation=</varname>. + </para></listitem> + </varlistentry> + + <xi:include href="user-system-options.xml" xpointer="host" /> + <xi:include href="user-system-options.xml" xpointer="machine" /> + + <xi:include href="standard-options.xml" xpointer="help" /> + <xi:include href="standard-options.xml" xpointer="version" /> + <xi:include href="standard-options.xml" xpointer="no-pager" /> + </variablelist> + + </refsect1> + + <refsect1> + <title>Exit status</title> + + <para>On success, 0 is returned, a non-zero failure code + otherwise.</para> + </refsect1> + + <refsect1> + <title>Examples for <command>dot</command></title> + + <example> + <title>Plots all dependencies of any unit whose name starts with + <literal>avahi-daemon</literal></title> + + <programlisting>$ systemd-analyze dot 'avahi-daemon.*' | dot -Tsvg > avahi.svg + $ eog avahi.svg</programlisting> + </example> + + <example> + <title>Plots the dependencies between all known target units</title> + + <programlisting>systemd-analyze dot --to-pattern='*.target' --from-pattern='*.target' | dot -Tsvg > targets.svg +$ eog targets.svg</programlisting> + </example> + </refsect1> + + <refsect1> + <title>Examples for <command>verify</command></title> + + <para>The following errors are currently detected:</para> + <itemizedlist> + <listitem><para>unknown sections and directives, + </para></listitem> + + <listitem><para>missing dependencies which are required to start + the given unit, </para></listitem> + + <listitem><para>man pages listed in + <varname>Documentation=</varname> which are not found in the + system,</para></listitem> + + <listitem><para>commands listed in <varname>ExecStart=</varname> + and similar which are not found in the system or not + executable.</para></listitem> + </itemizedlist> + + <example> + <title>Misspelt directives</title> + + <programlisting>$ cat ./user.slice +[Unit] +WhatIsThis=11 +Documentation=man:nosuchfile(1) +Requires=different.service + +[Service] +Desription=x + +$ systemd-analyze verify ./user.slice +[./user.slice:9] Unknown lvalue 'WhatIsThis' in section 'Unit' +[./user.slice:13] Unknown section 'Service'. Ignoring. +Error: org.freedesktop.systemd1.LoadFailed: + Unit different.service failed to load: + No such file or directory. +Failed to create user.slice/start: Invalid argument +user.slice: man nosuchfile(1) command failed with code 16 + </programlisting> + </example> + + <example> + <title>Missing service units</title> + + <programlisting>$ tail ./a.socket ./b.socket +==> ./a.socket <== +[Socket] +ListenStream=100 + +==> ./b.socket <== +[Socket] +ListenStream=100 +Accept=yes + +$ systemd-analyze verify ./a.socket ./b.socket +Service a.service not loaded, a.socket cannot be started. +Service b@0.service not loaded, b.socket cannot be started. + </programlisting> + </example> + </refsect1> + + <xi:include href="less-variables.xml" /> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/src/grp-system/grp-utils/systemd-delta/GNUmakefile b/src/grp-system/grp-utils/systemd-delta/GNUmakefile new file mode 120000 index 0000000000..13308a50cd --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/GNUmakefile @@ -0,0 +1 @@ +../../../../GNUmakefile
\ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-delta/Makefile b/src/grp-system/grp-utils/systemd-delta/Makefile new file mode 100644 index 0000000000..7273647c52 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +bin_PROGRAMS += systemd-delta +systemd_delta_SOURCES = \ + src/delta/delta.c + +systemd_delta_LDADD = \ + libsystemd-shared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-delta/delta.c b/src/grp-system/grp-utils/systemd-delta/delta.c new file mode 100644 index 0000000000..0142803b46 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/delta.c @@ -0,0 +1,635 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 + 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 <getopt.h> +#include <string.h> +#include <sys/prctl.h> +#include <unistd.h> + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/dirent-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/fs-util.h" +#include "systemd-basic/hashmap.h" +#include "systemd-basic/locale-util.h" +#include "systemd-basic/log.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/path-util.h" +#include "systemd-basic/process-util.h" +#include "systemd-basic/signal-util.h" +#include "systemd-basic/stat-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/terminal-util.h" +#include "systemd-basic/util.h" +#include "systemd-shared/pager.h" + +static const char prefixes[] = + "/etc\0" + "/run\0" + "/usr/local/lib\0" + "/usr/local/share\0" + "/usr/lib\0" + "/usr/share\0" +#ifdef HAVE_SPLIT_USR + "/lib\0" +#endif + ; + +static const char suffixes[] = + "sysctl.d\0" + "tmpfiles.d\0" + "modules-load.d\0" + "binfmt.d\0" + "systemd/system\0" + "systemd/user\0" + "systemd/system-preset\0" + "systemd/user-preset\0" + "udev/rules.d\0" + "modprobe.d\0"; + +static const char have_dropins[] = + "systemd/system\0" + "systemd/user\0"; + +static bool arg_no_pager = false; +static int arg_diff = -1; + +static enum { + SHOW_MASKED = 1 << 0, + SHOW_EQUIVALENT = 1 << 1, + 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_EXTENDED) +} arg_flags = 0; + +static int equivalent(const char *a, const char *b) { + _cleanup_free_ char *x = NULL, *y = NULL; + + x = canonicalize_file_name(a); + if (!x) + return -errno; + + y = canonicalize_file_name(b); + if (!y) + return -errno; + + return path_equal(x, y); +} + +static int notify_override_masked(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_MASKED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight_red(), "[MASKED]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_equivalent(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_EQUIVALENT)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_redirected(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_REDIRECTED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[REDIRECTED]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_overridden(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_OVERRIDDEN)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[OVERRIDDEN]", ansi_normal(), + top, special_glyph(ARROW), 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 %s\n", + ansi_highlight(), "[EXTENDED]", ansi_normal(), + top, special_glyph(ARROW), bottom); + return 1; +} + +static int notify_override_unchanged(const char *f) { + if (!(arg_flags & SHOW_UNCHANGED)) + return 0; + + printf("[UNCHANGED] %s\n", f); + return 1; +} + +static int found_override(const char *top, const char *bottom) { + _cleanup_free_ char *dest = NULL; + int k; + pid_t pid; + + assert(top); + assert(bottom); + + 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) + return notify_override_equivalent(top, bottom); + else + return notify_override_redirected(top, bottom); + } + + k = notify_override_overridden(top, bottom); + if (!arg_diff) + return k; + + putchar('\n'); + + fflush(stdout); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off diff: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execlp("diff", "diff", "-us", "--", bottom, top, NULL); + log_error_errno(errno, "Failed to execute diff: %m"); + _exit(EXIT_FAILURE); + } + + wait_for_terminate_and_warn("diff", pid, false); + putchar('\n'); + + return k; +} + +static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) { + _cleanup_free_ char *unit = NULL; + _cleanup_free_ char *path = NULL; + _cleanup_strv_free_ char **list = NULL; + char **file; + char *c; + int r; + + assert(!endswith(drop, "/")); + + path = strjoin(toppath, "/", drop, NULL); + if (!path) + return -ENOMEM; + + log_debug("Looking at %s", path); + + unit = strdup(drop); + if (!unit) + return -ENOMEM; + + c = strrchr(unit, '.'); + if (!c) + return -EINVAL; + *c = 0; + + r = get_files_in_directory(path, &list); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s: %m", path); + + 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; + d = p + strlen(toppath) + 1; + + log_debug("Adding at top: %s %s %s", d, special_glyph(ARROW), p); + k = hashmap_put(top, d, p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + d = p + strlen(toppath) + 1; + } else if (k != -EEXIST) { + free(p); + return k; + } + + log_debug("Adding at bottom: %s %s %s", d, special_glyph(ARROW), p); + free(hashmap_remove(bottom, d)); + k = hashmap_put(bottom, d, p); + if (k < 0) { + free(p); + return k; + } + + h = hashmap_get(drops, unit); + if (!h) { + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + hashmap_put(drops, unit, h); + unit = strdup(unit); + if (!unit) + return -ENOMEM; + } + + p = strdup(p); + if (!p) + return -ENOMEM; + + log_debug("Adding to drops: %s %s %s %s %s", + unit, special_glyph(ARROW), basename(p), special_glyph(ARROW), p); + k = hashmap_put(h, basename(p), p); + if (k < 0) { + free(p); + if (k != -EEXIST) + return k; + } + } + return 0; +} + +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); + + log_debug("Looking at %s", path); + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + for (;;) { + struct dirent *de; + int k; + char *p; + + errno = 0; + de = readdir(d); + if (!de) + return -errno; + + dirent_ensure_type(d, de); + + 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; + + p = strjoin(path, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + log_debug("Adding at top: %s %s %s", basename(p), special_glyph(ARROW), p); + k = hashmap_put(top, basename(p), p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + } else if (k != -EEXIST) { + free(p); + return k; + } + + log_debug("Adding at bottom: %s %s %s", basename(p), special_glyph(ARROW), p); + free(hashmap_remove(bottom, basename(p))); + k = hashmap_put(bottom, basename(p), p); + if (k < 0) { + free(p); + return k; + } + } +} + +static int process_suffix(const char *suffix, const char *onlyprefix) { + const char *p; + char *f; + Hashmap *top, *bottom, *drops; + Hashmap *h; + char *key; + int r = 0, k; + Iterator i, j; + int n_found = 0; + bool dropins; + + assert(suffix); + assert(!startswith(suffix, "/")); + assert(!strstr(suffix, "//")); + + dropins = nulstr_contains(have_dropins, suffix); + + top = hashmap_new(&string_hash_ops); + bottom = hashmap_new(&string_hash_ops); + drops = hashmap_new(&string_hash_ops); + if (!top || !bottom || !drops) { + r = -ENOMEM; + goto finish; + } + + NULSTR_FOREACH(p, prefixes) { + _cleanup_free_ char *t = NULL; + + t = strjoin(p, "/", suffix, NULL); + if (!t) { + r = -ENOMEM; + goto finish; + } + + k = enumerate_dir(top, bottom, drops, t, dropins); + if (r == 0) + r = k; + } + + HASHMAP_FOREACH_KEY(f, key, top, i) { + char *o; + + o = hashmap_get(bottom, key); + assert(o); + + if (!onlyprefix || startswith(o, onlyprefix)) { + if (path_equal(o, f)) { + notify_override_unchanged(f); + } else { + k = found_override(f, o); + if (k < 0) + r = k; + else + n_found += k; + } + } + + h = hashmap_get(drops, key); + if (h) + HASHMAP_FOREACH(o, h, j) + if (!onlyprefix || startswith(o, onlyprefix)) + n_found += notify_override_extended(f, o); + } + +finish: + hashmap_free_free(top); + hashmap_free_free(bottom); + + 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_suffixes(const char *onlyprefix) { + const char *n; + int n_found = 0, r; + + NULSTR_FOREACH(n, suffixes) { + r = process_suffix(n, onlyprefix); + if (r < 0) + return r; + + n_found += r; + } + + return n_found; +} + +static int process_suffix_chop(const char *arg) { + const char *p; + + assert(arg); + + if (!path_is_absolute(arg)) + return process_suffix(arg, NULL); + + /* Strip prefix from the suffix */ + NULSTR_FOREACH(p, prefixes) { + const char *suffix; + + suffix = startswith(arg, p); + if (suffix) { + suffix += strspn(suffix, "/"); + if (*suffix) + return process_suffix(suffix, NULL); + else + return process_suffixes(arg); + } + } + + log_error("Invalid suffix specification %s.", arg); + return -EINVAL; +} + +static void help(void) { + printf("%s [OPTIONS...] [SUFFIX...]\n\n" + "Find overridden configuration files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --diff[=1|0] Show a diff when overridden files differ\n" + " -t --type=LIST... Only display a selected set of override types\n" + , program_invocation_short_name); +} + +static int parse_flags(const char *flag_str, int flags) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) { + if (strneq("masked", word, l)) + flags |= SHOW_MASKED; + else if (strneq ("equivalent", word, l)) + flags |= SHOW_EQUIVALENT; + else if (strneq("redirected", word, l)) + flags |= SHOW_REDIRECTED; + else if (strneq("overridden", word, l)) + flags |= SHOW_OVERRIDDEN; + else if (strneq("unchanged", word, l)) + flags |= SHOW_UNCHANGED; + else if (strneq("extended", word, l)) + flags |= SHOW_EXTENDED; + else if (strneq("default", word, l)) + flags |= SHOW_DEFAULTS; + else + return -EINVAL; + } + return flags; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NO_PAGER = 0x100, + ARG_DIFF, + ARG_VERSION + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "diff", optional_argument, NULL, ARG_DIFF }, + { "type", required_argument, NULL, 't' }, + {} + }; + + int c; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 't': { + int f; + f = parse_flags(optarg, arg_flags); + if (f < 0) { + log_error("Failed to parse flags field."); + return -EINVAL; + } + arg_flags = f; + break; + } + + case ARG_DIFF: + if (!optarg) + arg_diff = 1; + else { + int b; + + b = parse_boolean(optarg); + if (b < 0) { + log_error("Failed to parse diff boolean."); + return -EINVAL; + } + + arg_diff = b; + } + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, k, n_found = 0; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_flags == 0) + arg_flags = SHOW_DEFAULTS; + + if (arg_diff < 0) + arg_diff = !!(arg_flags & SHOW_OVERRIDDEN); + else if (arg_diff) + arg_flags |= SHOW_OVERRIDDEN; + + pager_open(arg_no_pager, false); + + if (optind < argc) { + int i; + + for (i = optind; i < argc; i++) { + path_kill_slashes(argv[i]); + + k = process_suffix_chop(argv[i]); + if (k < 0) + r = k; + else + n_found += k; + } + + } else { + k = process_suffixes(NULL); + if (k < 0) + r = k; + else + n_found += k; + } + + if (r >= 0) + printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-system/grp-utils/systemd-delta/systemd-delta.completion.bash b/src/grp-system/grp-utils/systemd-delta/systemd-delta.completion.bash new file mode 100644 index 0000000000..cb1732895f --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/systemd-delta.completion.bash @@ -0,0 +1,61 @@ +# systemd-delta(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2014 Thomas H.P. Andersen +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +__contains_word() { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +_systemd-delta() { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local comps + + local -A OPTS=( + [STANDALONE]='--help -h --no-pager --version' + [ARG]='--diff --type -t' + ) + + _init_completion || return + + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --diff) + comps='yes no' + ;; + --type|-t) + comps='masked equivalent redirected overridden unchanged extended default' + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _systemd-delta systemd-delta diff --git a/src/grp-system/grp-utils/systemd-delta/systemd-delta.completion.zsh b/src/grp-system/grp-utils/systemd-delta/systemd-delta.completion.zsh new file mode 100644 index 0000000000..757f1b66fb --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/systemd-delta.completion.zsh @@ -0,0 +1,15 @@ +#compdef systemd-delta + +_delta_type() { + local -a _delta_types + _delta_types=(masked equivalent redirected overridden unchanged) + _values -s , "${_delta_types[@]}" +} + +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' \ + '--no-pager[Do not pipe output into a pager]' \ + '--diff=[Show a diff when overridden files differ]:boolean:(1 0)' \ + {-t+,--type=}'[Only display a selected set of override types]:types:_delta_type' \ + ':SUFFIX:(tmpfiles.d sysctl.d systemd/system)' diff --git a/src/grp-system/grp-utils/systemd-delta/systemd-delta.xml b/src/grp-system/grp-utils/systemd-delta/systemd-delta.xml new file mode 100644 index 0000000000..99709604aa --- /dev/null +++ b/src/grp-system/grp-utils/systemd-delta/systemd-delta.xml @@ -0,0 +1,205 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + 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/>. +--> + +<refentry id="systemd-delta" + xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>systemd-delta</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-delta</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-delta</refname> + <refpurpose>Find overridden configuration files</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>systemd-delta</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="opt" rep="repeat"><replaceable>PREFIX</replaceable><optional>/<replaceable>SUFFIX</replaceable></optional>|<replaceable>SUFFIX</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-delta</command> may be used to identify and + compare configuration files that override other configuration + files. Files in <filename>/etc</filename> have highest priority, + files in <filename>/run</filename> have the second highest + priority, ..., files in <filename>/lib</filename> have lowest + priority. Files in a directory with higher priority override files + with the same name in directories of lower priority. In addition, + certain configuration files can have <literal>.d</literal> + directories which contain "drop-in" files with configuration + snippets which augment the main configuration file. "Drop-in" + files can be overridden in the same way by placing files with the + same name in a directory of higher priority (except that, in case + of "drop-in" files, both the "drop-in" file name and the name of + the containing directory, which corresponds to the name of the + main configuration file, must match). For a fuller explanation, + see + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + </para> + + <para>The command line argument will be split into a prefix and a + suffix. Either is optional. The prefix must be one of the + directories containing configuration files + (<filename>/etc</filename>, <filename>/run</filename>, + <filename>/usr/lib</filename>, ...). If it is given, only + overriding files contained in this directory will be shown. + Otherwise, all overriding files will be shown. The suffix must be + a name of a subdirectory containing configuration files like + <filename>tmpfiles.d</filename>, <filename>sysctl.d</filename> or + <filename>systemd/system</filename>. If it is given, only + configuration files in this subdirectory (across all configuration + paths) will be analyzed. Otherwise, all configuration files will + be analyzed. If the command line argument is not given at all, all + configuration files will be analyzed. See below for some + examples.</para> + </refsect1> + + <refsect1> + <title>Options</title> + + <para>The following options are understood:</para> + + <variablelist> + <varlistentry> + <term><option>-t</option></term> + <term><option>--type=</option></term> + + <listitem><para>When listing the differences, only list those + that are asked for. The list itself is a comma-separated list + of desired difference types.</para> + + <para>Recognized types are: + + <variablelist> + <varlistentry> + <term><varname>masked</varname></term> + + <listitem><para>Show masked files</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>equivalent</varname></term> + + <listitem><para>Show overridden files that while + overridden, do not differ in content.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>redirected</varname></term> + + <listitem><para>Show files that are redirected to + another.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>overridden</varname></term> + + <listitem><para>Show overridden, and changed + files.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>extended</varname></term> + + <listitem><para>Show <filename>*.conf</filename> files + in drop-in directories for units.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>unchanged</varname></term> + + <listitem><para>Show unmodified files + too.</para></listitem> + </varlistentry> + </variablelist> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--diff=</option></term> + + <listitem><para>When showing modified files, when a file is + overridden show a diff as well. This option takes a boolean + argument. If omitted, it defaults to + <option>true</option>.</para></listitem> + </varlistentry> + + <xi:include href="standard-options.xml" xpointer="help" /> + <xi:include href="standard-options.xml" xpointer="version" /> + <xi:include href="standard-options.xml" xpointer="no-pager" /> + </variablelist> + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>To see all local configuration:</para> + <programlisting>systemd-delta</programlisting> + + <para>To see all runtime configuration:</para> + <programlisting>systemd-delta /run</programlisting> + + <para>To see all system unit configuration changes:</para> + <programlisting>systemd-delta systemd/system</programlisting> + + <para>To see all runtime "drop-in" changes for system units:</para> + <programlisting>systemd-delta --type=extended /run/systemd/system</programlisting> + </refsect1> + + <refsect1> + <title>Exit status</title> + + <para>On success, 0 is returned, a non-zero failure code + otherwise.</para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/src/grp-system/grp-utils/systemd-fstab-generator/GNUmakefile b/src/grp-system/grp-utils/systemd-fstab-generator/GNUmakefile new file mode 120000 index 0000000000..13308a50cd --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/GNUmakefile @@ -0,0 +1 @@ +../../../../GNUmakefile
\ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-fstab-generator/Makefile b/src/grp-system/grp-utils/systemd-fstab-generator/Makefile new file mode 100644 index 0000000000..43475f69e1 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/Makefile @@ -0,0 +1,34 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemgenerator_PROGRAMS += systemd-fstab-generator +systemd_fstab_generator_SOURCES = \ + src/fstab-generator/fstab-generator.c \ + src/core/mount-setup.c + +systemd_fstab_generator_LDADD = \ + libsystemd-shared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c b/src/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c new file mode 100644 index 0000000000..aceac5151c --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/fstab-generator.c @@ -0,0 +1,723 @@ +/*** + 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/>. +***/ + +#include <errno.h> +#include <mntent.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "core/mount-setup.h" +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/fileio.h" +#include "systemd-basic/log.h" +#include "systemd-basic/mkdir.h" +#include "systemd-basic/mount-util.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/path-util.h" +#include "systemd-basic/proc-cmdline.h" +#include "systemd-basic/special.h" +#include "systemd-basic/stat-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/unit-name.h" +#include "systemd-basic/util.h" +#include "systemd-basic/virt.h" +#include "systemd-shared/fstab-util.h" +#include "systemd-shared/generator.h" + +static const char *arg_dest = "/tmp"; +static bool arg_fstab_enabled = true; +static char *arg_root_what = NULL; +static char *arg_root_fstype = NULL; +static char *arg_root_options = NULL; +static int arg_root_rw = -1; +static char *arg_usr_what = NULL; +static char *arg_usr_fstype = NULL; +static char *arg_usr_options = NULL; + +static int add_swap( + const char *what, + struct mntent *me, + bool noauto, + bool nofail) { + + _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(what); + assert(me); + + if (access("/proc/swaps", F_OK) < 0) { + log_info("Swap not supported, ignoring fstab swap entry for %s.", what); + return 0; + } + + if (detect_container() > 0) { + log_info("Running in a container, ignoring fstab swap entry for %s.", what); + return 0; + } + + r = unit_name_from_path(what, ".swap", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, + errno == EEXIST ? + "Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?" : + "Failed to create unit file %s: %m", + unit); + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=/etc/fstab\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n" + "[Swap]\n" + "What=%s\n", + what); + + if (!isempty(me->mnt_opts) && !streq(me->mnt_opts, "defaults")) + fprintf(f, "Options=%s\n", me->mnt_opts); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + /* use what as where, to have a nicer error message */ + r = generator_write_timeouts(arg_dest, what, what, me->mnt_opts, NULL); + if (r < 0) + return r; + + if (!noauto) { + lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET, + nofail ? ".wants/" : ".requires/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static bool mount_is_network(struct mntent *me) { + assert(me); + + return fstab_test_option(me->mnt_opts, "_netdev\0") || + fstype_is_network(me->mnt_type); +} + +static bool mount_in_initrd(struct mntent *me) { + assert(me); + + return fstab_test_option(me->mnt_opts, "x-initrd.mount\0") || + streq(me->mnt_dir, "/usr"); +} + +static int write_idle_timeout(FILE *f, const char *where, const char *opts) { + _cleanup_free_ char *timeout = NULL; + char timespan[FORMAT_TIMESPAN_MAX]; + usec_t u; + int r; + + r = fstab_filter_options(opts, "x-systemd.idle-timeout\0", NULL, &timeout, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + r = parse_sec(timeout, &u); + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + return 0; + } + + fprintf(f, "TimeoutIdleSec=%s\n", format_timespan(timespan, sizeof(timespan), u, 0)); + + return 0; +} + +static int write_requires_after(FILE *f, const char *opts) { + _cleanup_strv_free_ char **names = NULL, **units = NULL; + _cleanup_free_ char *res = NULL; + char **s; + int r; + + assert(f); + assert(opts); + + r = fstab_extract_values(opts, "x-systemd.requires", &names); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + STRV_FOREACH(s, names) { + char *x; + + r = unit_name_mangle_with_suffix(*s, UNIT_NAME_NOGLOB, ".mount", &x); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + r = strv_consume(&units, x); + if (r < 0) + return log_oom(); + } + + if (units) { + res = strv_join(units, " "); + if (!res) + return log_oom(); + fprintf(f, "After=%1$s\nRequires=%1$s\n", res); + } + + return 0; +} + +static int write_requires_mounts_for(FILE *f, const char *opts) { + _cleanup_strv_free_ char **paths = NULL; + _cleanup_free_ char *res = NULL; + int r; + + assert(f); + assert(opts); + + r = fstab_extract_values(opts, "x-systemd.requires-mounts-for", &paths); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + res = strv_join(paths, " "); + if (!res) + return log_oom(); + + fprintf(f, "RequiresMountsFor=%s\n", res); + + return 0; +} + +static int add_mount( + const char *what, + const char *where, + const char *fstype, + const char *opts, + int passno, + bool noauto, + bool nofail, + bool automount, + const char *post, + const char *source) { + + _cleanup_free_ char + *name = NULL, *unit = NULL, *lnk = NULL, + *automount_name = NULL, *automount_unit = NULL, + *filtered = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(what); + assert(where); + assert(opts); + assert(post); + assert(source); + + if (streq_ptr(fstype, "autofs")) + return 0; + + if (!is_path(where)) { + log_warning("Mount point %s is not a valid path, ignoring.", where); + return 0; + } + + if (mount_point_is_api(where) || + mount_point_ignore(where)) + return 0; + + if (path_equal(where, "/")) { + if (noauto) + log_warning("Ignoring \"noauto\" for root device"); + if (nofail) + log_warning("Ignoring \"nofail\" for root device"); + if (automount) + log_warning("Ignoring automount option for root device"); + + noauto = nofail = automount = false; + } + + r = unit_name_from_path(where, ".mount", &name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + unit = strjoin(arg_dest, "/", name, NULL); + if (!unit) + return log_oom(); + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, + errno == EEXIST ? + "Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?" : + "Failed to create unit file %s: %m", + unit); + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + source); + + if (!noauto && !nofail && !automount) + fprintf(f, "Before=%s\n", post); + + if (!automount && opts) { + r = write_requires_after(f, opts); + if (r < 0) + return r; + r = write_requires_mounts_for(f, opts); + if (r < 0) + return r; + } + + if (passno != 0) { + r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); + if (r < 0) + return r; + } + + fprintf(f, + "\n" + "[Mount]\n" + "What=%s\n" + "Where=%s\n", + what, + where); + + if (!isempty(fstype) && !streq(fstype, "auto")) + fprintf(f, "Type=%s\n", fstype); + + r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); + if (r < 0) + return r; + + if (!isempty(filtered) && !streq(filtered, "defaults")) + fprintf(f, "Options=%s\n", filtered); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + if (!noauto && !automount) { + lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + if (automount) { + r = unit_name_from_path(where, ".automount", &automount_name); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + automount_unit = strjoin(arg_dest, "/", automount_name, NULL); + if (!automount_unit) + return log_oom(); + + fclose(f); + f = fopen(automount_unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", automount_unit); + + fprintf(f, + "# Automatically generated by systemd-fstab-generator\n\n" + "[Unit]\n" + "SourcePath=%s\n" + "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", + source); + + fprintf(f, "Before=%s\n", post); + + if (opts) { + r = write_requires_after(f, opts); + if (r < 0) + return r; + r = write_requires_mounts_for(f, opts); + if (r < 0) + return r; + } + + fprintf(f, + "\n" + "[Automount]\n" + "Where=%s\n", + where); + + r = write_idle_timeout(f, where, opts); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); + + free(lnk); + lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL); + if (!lnk) + return log_oom(); + + mkdir_parents_label(lnk, 0755); + if (symlink(automount_unit, lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + } + + return 0; +} + +static int parse_fstab(bool initrd) { + _cleanup_endmntent_ FILE *f = NULL; + const char *fstab_path; + struct mntent *me; + int r = 0; + + fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab"; + f = setmntent(fstab_path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", fstab_path); + } + + while ((me = getmntent(f))) { + _cleanup_free_ char *where = NULL, *what = NULL; + bool noauto, nofail; + int k; + + if (initrd && !mount_in_initrd(me)) + continue; + + what = fstab_node_to_udev_node(me->mnt_fsname); + if (!what) + return log_oom(); + + if (is_device_path(what) && path_is_read_only_fs("sys") > 0) { + log_info("Running in a container, ignoring fstab device entry for %s.", what); + continue; + } + + where = initrd ? strappend("/sysroot/", me->mnt_dir) : strdup(me->mnt_dir); + if (!where) + return log_oom(); + + if (is_path(where)) + path_kill_slashes(where); + + noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0"); + nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0"); + log_debug("Found entry what=%s where=%s type=%s nofail=%s noauto=%s", + what, where, me->mnt_type, + yes_no(noauto), yes_no(nofail)); + + if (streq(me->mnt_type, "swap")) + k = add_swap(what, me, noauto, nofail); + else { + bool automount; + const char *post; + + automount = fstab_test_option(me->mnt_opts, + "comment=systemd.automount\0" + "x-systemd.automount\0"); + if (initrd) + post = SPECIAL_INITRD_FS_TARGET; + else if (mount_is_network(me)) + post = SPECIAL_REMOTE_FS_TARGET; + else + post = SPECIAL_LOCAL_FS_TARGET; + + k = add_mount(what, + where, + me->mnt_type, + me->mnt_opts, + me->mnt_passno, + noauto, + nofail, + automount, + post, + fstab_path); + } + + if (k < 0) + r = k; + } + + return r; +} + +static int add_sysroot_mount(void) { + _cleanup_free_ char *what = NULL; + const char *opts; + int r; + + if (isempty(arg_root_what)) { + log_debug("Could not find a root= entry on the kernel command line."); + return 0; + } + + if (streq(arg_root_what, "gpt-auto")) { + /* This is handled by the gpt-auto generator */ + log_debug("Skipping root directory handling, as gpt-auto was requested."); + return 0; + } + + if (path_equal(arg_root_what, "/dev/nfs")) { + /* This is handled by the kernel or the initrd */ + log_debug("Skipping root directory handling, as /dev/nfs was requested."); + return 0; + } + + what = fstab_node_to_udev_node(arg_root_what); + if (!what) + return log_oom(); + + if (!arg_root_options) + opts = arg_root_rw > 0 ? "rw" : "ro"; + else if (arg_root_rw >= 0 || + !fstab_test_option(arg_root_options, "ro\0" "rw\0")) + opts = strjoina(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro"); + else + opts = arg_root_options; + + log_debug("Found entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype)); + + if (is_device_path(what)) { + r = generator_write_initrd_root_device_deps(arg_dest, what); + if (r < 0) + return r; + } + + return add_mount(what, + "/sysroot", + arg_root_fstype, + opts, + is_device_path(what) ? 1 : 0, /* passno */ + false, /* noauto off */ + false, /* nofail off */ + false, /* automount off */ + SPECIAL_INITRD_ROOT_FS_TARGET, + "/proc/cmdline"); +} + +static int add_sysroot_usr_mount(void) { + _cleanup_free_ char *what = NULL; + const char *opts; + + if (!arg_usr_what && !arg_usr_fstype && !arg_usr_options) + return 0; + + if (arg_root_what && !arg_usr_what) { + /* Copy over the root device, in case the /usr mount just differs in a mount option (consider btrfs subvolumes) */ + arg_usr_what = strdup(arg_root_what); + if (!arg_usr_what) + return log_oom(); + } + + if (arg_root_fstype && !arg_usr_fstype) { + arg_usr_fstype = strdup(arg_root_fstype); + if (!arg_usr_fstype) + return log_oom(); + } + + if (arg_root_options && !arg_usr_options) { + arg_usr_options = strdup(arg_root_options); + if (!arg_usr_options) + return log_oom(); + } + + if (!arg_usr_what) + return 0; + + what = fstab_node_to_udev_node(arg_usr_what); + if (!what) + return log_oom(); + + if (!arg_usr_options) + opts = arg_root_rw > 0 ? "rw" : "ro"; + else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0")) + opts = strjoina(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro"); + else + opts = arg_usr_options; + + log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); + return add_mount(what, + "/sysroot/usr", + arg_usr_fstype, + opts, + is_device_path(what) ? 1 : 0, /* passno */ + false, /* noauto off */ + false, /* nofail off */ + false, /* automount off */ + SPECIAL_INITRD_FS_TARGET, + "/proc/cmdline"); +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + /* root=, usr=, usrfstype= and roofstype= may occur more than once, the last + * instance should take precedence. In the case of multiple rootflags= + * or usrflags= the arguments should be concatenated */ + + if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse fstab switch %s. Ignoring.", value); + else + arg_fstab_enabled = r; + + } else if (streq(key, "root") && value) { + + if (free_and_strdup(&arg_root_what, value) < 0) + return log_oom(); + + } else if (streq(key, "rootfstype") && value) { + + if (free_and_strdup(&arg_root_fstype, value) < 0) + return log_oom(); + + } else if (streq(key, "rootflags") && value) { + char *o; + + o = arg_root_options ? + strjoin(arg_root_options, ",", value, NULL) : + strdup(value); + if (!o) + return log_oom(); + + free(arg_root_options); + arg_root_options = o; + + } else if (streq(key, "mount.usr") && value) { + + if (free_and_strdup(&arg_usr_what, value) < 0) + return log_oom(); + + } else if (streq(key, "mount.usrfstype") && value) { + + if (free_and_strdup(&arg_usr_fstype, value) < 0) + return log_oom(); + + } else if (streq(key, "mount.usrflags") && value) { + char *o; + + o = arg_usr_options ? + strjoin(arg_usr_options, ",", value, NULL) : + strdup(value); + if (!o) + return log_oom(); + + free(arg_usr_options); + arg_usr_options = o; + + } else if (streq(key, "rw") && !value) + arg_root_rw = true; + else if (streq(key, "ro") && !value) + arg_root_rw = false; + + return 0; +} + +int main(int argc, char *argv[]) { + int r = 0; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + /* Always honour root= and usr= in the kernel command line if we are in an initrd */ + if (in_initrd()) { + int k; + + r = add_sysroot_mount(); + + k = add_sysroot_usr_mount(); + if (k < 0) + r = k; + } else + r = 0; + + /* Honour /etc/fstab only when that's enabled */ + if (arg_fstab_enabled) { + int k; + + log_debug("Parsing /etc/fstab"); + + /* Parse the local /etc/fstab, possibly from the initrd */ + k = parse_fstab(false); + if (k < 0) + r = k; + + /* If running in the initrd also parse the /etc/fstab from the host */ + if (in_initrd()) { + log_debug("Parsing /sysroot/etc/fstab"); + + k = parse_fstab(true); + if (k < 0) + r = k; + } + } + + free(arg_root_what); + free(arg_root_fstype); + free(arg_root_options); + + free(arg_usr_what); + free(arg_usr_fstype); + free(arg_usr_options); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-system/grp-utils/systemd-fstab-generator/systemd-fstab-generator.xml b/src/grp-system/grp-utils/systemd-fstab-generator/systemd-fstab-generator.xml new file mode 100644 index 0000000000..a971cb3675 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-fstab-generator/systemd-fstab-generator.xml @@ -0,0 +1,183 @@ +<?xml version="1.0"?> +<!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<!-- + 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/>. +--> +<refentry id="systemd-fstab-generator"> + + <refentryinfo> + <title>systemd-fstab-generator</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-fstab-generator</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-fstab-generator</refname> + <refpurpose>Unit generator for /etc/fstab</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/usr/lib/systemd/system-generators/systemd-fstab-generator</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><filename>systemd-fstab-generator</filename> is a generator + that translates <filename>/etc/fstab</filename> (see + <citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for details) into native systemd units early at boot and when + configuration of the system manager is reloaded. This will + instantiate mount and swap units as necessary.</para> + + <para>The <varname>passno</varname> field is treated like a simple + boolean, and the ordering information is discarded. However, if + the root file system is checked, it is checked before all the + other file systems.</para> + + <para>See + <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry> + and + <citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for more information about special <filename>/etc/fstab</filename> + mount options this generator understands.</para> + + <para><filename>systemd-fstab-generator</filename> implements + <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>Kernel Command Line</title> + + <para><filename>systemd-fstab-generator</filename> understands the + following kernel command line parameters:</para> + + <variablelist class='kernel-commandline-options'> + + <varlistentry> + <term><varname>fstab=</varname></term> + <term><varname>rd.fstab=</varname></term> + + <listitem><para>Takes a boolean argument. Defaults to + <literal>yes</literal>. If <literal>no</literal>, causes the + generator to ignore any mounts or swaps configured in + <filename>/etc/fstab</filename>. <varname>rd.fstab=</varname> + is honored only by initial RAM disk (initrd) while + <varname>fstab=</varname> is honored by both the main system + and the initrd.</para></listitem> + </varlistentry> + <varlistentry> + <term><varname>root=</varname></term> + + <listitem><para>Takes the root filesystem to mount in the + initrd. <varname>root=</varname> is honored by the + initrd.</para></listitem> + </varlistentry> + <varlistentry> + <term><varname>rootfstype=</varname></term> + + <listitem><para>Takes the root filesystem type that will be + passed to the mount command. <varname>rootfstype=</varname> is + honored by the initrd.</para></listitem> + </varlistentry> + <varlistentry> + <term><varname>rootflags=</varname></term> + + <listitem><para>Takes the root filesystem mount options to + use. <varname>rootflags=</varname> is honored by the + initrd.</para></listitem> + </varlistentry> + <varlistentry> + <term><varname>mount.usr=</varname></term> + + <listitem><para>Takes the <filename>/usr</filename> filesystem + to be mounted by the initrd. If + <varname>mount.usrfstype=</varname> or + <varname>mount.usrflags=</varname> is set, then + <varname>mount.usr=</varname> will default to the value set in + <varname>root=</varname>.</para> + + <para>Otherwise, this parameter defaults to the + <filename>/usr</filename> entry found in + <filename>/etc/fstab</filename> on the root filesystem.</para> + + <para><varname>mount.usr=</varname> is honored by the initrd. + </para></listitem> + </varlistentry> + <varlistentry> + <term><varname>mount.usrfstype=</varname></term> + + <listitem><para>Takes the <filename>/usr</filename> filesystem + type that will be passed to the mount command. If + <varname>mount.usr=</varname> or + <varname>mount.usrflags=</varname> is set, then + <varname>mount.usrfstype=</varname> will default to the value + set in <varname>rootfstype=</varname>.</para> + + <para>Otherwise, this value will be read from the + <filename>/usr</filename> entry in + <filename>/etc/fstab</filename> on the root filesystem.</para> + + <para><varname>mount.usrfstype=</varname> is honored by the + initrd.</para></listitem> + </varlistentry> + <varlistentry> + <term><varname>mount.usrflags=</varname></term> + + <listitem><para>Takes the <filename>/usr</filename> filesystem + mount options to use. If <varname>mount.usr=</varname> or + <varname>mount.usrfstype=</varname> is set, then + <varname>mount.usrflags=</varname> will default to the value + set in <varname>rootflags=</varname>.</para> + + <para>Otherwise, this value will be read from the + <filename>/usr</filename> entry in + <filename>/etc/fstab</filename> on the root filesystem.</para> + + <para><varname>mount.usrflags=</varname> is honored by the + initrd.</para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.swap</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-cryptsetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/src/grp-system/grp-utils/systemd-run/GNUmakefile b/src/grp-system/grp-utils/systemd-run/GNUmakefile new file mode 120000 index 0000000000..13308a50cd --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/GNUmakefile @@ -0,0 +1 @@ +../../../../GNUmakefile
\ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-run/Makefile b/src/grp-system/grp-utils/systemd-run/Makefile new file mode 100644 index 0000000000..9664591eb6 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +bin_PROGRAMS += systemd-run +systemd_run_SOURCES = \ + src/run/run.c + +systemd_run_LDADD = \ + libsystemd-shared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-run/run.c b/src/grp-system/grp-utils/systemd-run/run.c new file mode 100644 index 0000000000..274edaf2f0 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/run.c @@ -0,0 +1,1441 @@ +/*** + 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 <getopt.h> +#include <stdio.h> + +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> + +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-util.h" +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/calendarspec.h" +#include "systemd-basic/env-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/formats-util.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/path-util.h" +#include "systemd-basic/process-util.h" +#include "systemd-basic/signal-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/terminal-util.h" +#include "systemd-basic/unit-name.h" +#include "systemd-basic/user-util.h" +#include "systemd-shared/bus-unit-util.h" +#include "systemd-shared/ptyfwd.h" +#include "systemd-shared/spawn-polkit-agent.h" + +static bool arg_ask_password = true; +static bool arg_scope = false; +static bool arg_remain_after_exit = false; +static bool arg_no_block = false; +static bool arg_wait = 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 BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static bool arg_user = false; +static const char *arg_service_type = NULL; +static const char *arg_exec_user = NULL; +static const char *arg_exec_group = NULL; +static int arg_nice = 0; +static bool arg_nice_set = false; +static char **arg_environment = NULL; +static char **arg_property = NULL; +static bool arg_pty = false; +static usec_t arg_on_active = 0; +static usec_t arg_on_boot = 0; +static usec_t arg_on_startup = 0; +static usec_t arg_on_unit_active = 0; +static usec_t arg_on_unit_inactive = 0; +static const char *arg_on_calendar = NULL; +static char **arg_timer_property = NULL; +static bool arg_quiet = false; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static void help(void) { + printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" + "Run the specified command in a transient scope or service.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-ask-password Do not prompt for password\n" + " --user Run as user unit\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --scope Run this as scope rather than service\n" + " --unit=UNIT Run under the specified unit name\n" + " -p --property=NAME=VALUE Set service or scope unit property\n" + " --description=TEXT Description for unit\n" + " --slice=SLICE Run in the specified slice\n" + " --no-block Do not wait until operation finished\n" + " -r --remain-after-exit Leave service around until explicitly stopped\n" + " --wait Wait until service stopped again\n" + " --send-sighup Send SIGHUP when terminating\n" + " --service-type=TYPE Service type\n" + " --uid=USER Run as system user\n" + " --gid=GROUP Run as system group\n" + " --nice=NICE Nice level\n" + " -E --setenv=NAME=VALUE Set environment\n" + " -t --pty Run service on pseudo tty\n" + " -q --quiet Suppress information messages during runtime\n\n" + "Timer options:\n" + " --on-active=SECONDS Run after SECONDS delay\n" + " --on-boot=SECONDS Run SECONDS after machine was booted up\n" + " --on-startup=SECONDS Run SECONDS after systemd activation\n" + " --on-unit-active=SECONDS Run SECONDS after the last activation\n" + " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" + " --on-calendar=SPEC Realtime timer\n" + " --timer-property=NAME=VALUE Set timer unit property\n" + , program_invocation_short_name); +} + +static bool with_timer(void) { + return arg_on_active || arg_on_boot || arg_on_startup || arg_on_unit_active || arg_on_unit_inactive || arg_on_calendar; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_USER, + ARG_SYSTEM, + ARG_SCOPE, + ARG_UNIT, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SEND_SIGHUP, + ARG_SERVICE_TYPE, + ARG_EXEC_USER, + ARG_EXEC_GROUP, + ARG_NICE, + ARG_ON_ACTIVE, + ARG_ON_BOOT, + ARG_ON_STARTUP, + ARG_ON_UNIT_ACTIVE, + ARG_ON_UNIT_INACTIVE, + ARG_ON_CALENDAR, + ARG_TIMER_PROPERTY, + ARG_NO_BLOCK, + ARG_NO_ASK_PASSWORD, + ARG_WAIT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "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 }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, + { "wait", no_argument, NULL, ARG_WAIT }, + { "uid", required_argument, NULL, ARG_EXEC_USER }, + { "gid", required_argument, NULL, ARG_EXEC_GROUP }, + { "nice", required_argument, NULL, ARG_NICE }, + { "setenv", required_argument, NULL, 'E' }, + { "property", required_argument, NULL, 'p' }, + { "tty", no_argument, NULL, 't' }, /* deprecated */ + { "pty", no_argument, NULL, 't' }, + { "quiet", no_argument, NULL, 'q' }, + { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, + { "on-boot", required_argument, NULL, ARG_ON_BOOT }, + { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, + { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, + { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, + { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, + { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tq", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + 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 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_SERVICE_TYPE: + arg_service_type = optarg; + break; + + case ARG_EXEC_USER: + arg_exec_user = optarg; + break; + + case ARG_EXEC_GROUP: + arg_exec_group = optarg; + break; + + case ARG_NICE: + r = parse_nice(optarg, &arg_nice); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", optarg); + + arg_nice_set = true; + break; + + case 'E': + if (strv_extend(&arg_environment, optarg) < 0) + return log_oom(); + + break; + + case 'p': + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case 't': + arg_pty = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_ON_ACTIVE: + + r = parse_sec(optarg, &arg_on_active); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_BOOT: + + r = parse_sec(optarg, &arg_on_boot); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_STARTUP: + + r = parse_sec(optarg, &arg_on_startup); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_UNIT_ACTIVE: + + r = parse_sec(optarg, &arg_on_unit_active); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_UNIT_INACTIVE: + + r = parse_sec(optarg, &arg_on_unit_inactive); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_CALENDAR: { + CalendarSpec *spec = NULL; + + r = calendar_spec_from_string(optarg, &spec); + if (r < 0) { + log_error("Invalid calendar spec: %s", optarg); + return r; + } + + calendar_spec_free(spec); + arg_on_calendar = optarg; + break; + } + + case ARG_TIMER_PROPERTY: + + if (strv_extend(&arg_timer_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case ARG_WAIT: + arg_wait = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if ((optind >= argc) && (!arg_unit || !with_timer())) { + log_error("Command line to execute required."); + return -EINVAL; + } + + if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Execution in user context is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Scope execution is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_scope && (arg_remain_after_exit || arg_service_type)) { + log_error("--remain-after-exit and --service-type= are not supported in --scope mode."); + return -EINVAL; + } + + if (arg_pty && (with_timer() || arg_scope)) { + log_error("--pty is not compatible in timer or --scope mode."); + return -EINVAL; + } + + if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) { + log_error("--pty is only supported when connecting to the local system or containers."); + return -EINVAL; + } + + if (arg_scope && with_timer()) { + log_error("Timer options are not supported in --scope mode."); + return -EINVAL; + } + + if (arg_timer_property && !with_timer()) { + log_error("--timer-property= has no effect without any other timer options."); + return -EINVAL; + } + + if (arg_wait) { + if (arg_no_block) { + log_error("--wait may not be combined with --no-block."); + return -EINVAL; + } + + if (with_timer()) { + log_error("--wait may not be combined with timer operations."); + return -EINVAL; + } + + if (arg_scope) { + log_error("--wait may not be combined with --scope."); + return -EINVAL; + } + } + + return 1; +} + +static int transient_unit_set_properties(sd_bus_message *m, char **properties) { + int r; + + r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); + if (r < 0) + return r; + + r = bus_append_unit_property_assignment_many(m, properties); + if (r < 0) + return r; + + return 0; +} + +static int transient_cgroup_set_properties(sd_bus_message *m) { + int r; + assert(m); + + if (!isempty(arg_slice)) { + _cleanup_free_ char *slice; + + r = unit_name_mangle_with_suffix(arg_slice, UNIT_NAME_NOGLOB, ".slice", &slice); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_kill_set_properties(sd_bus_message *m) { + assert(m); + + if (arg_send_sighup) + return sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup); + else + return 0; +} + +static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + r = transient_kill_set_properties(m); + if (r < 0) + return r; + + r = transient_cgroup_set_properties(m); + if (r < 0) + return r; + + if (arg_wait) { + r = sd_bus_message_append(m, "(sv)", "AddRef", "b", 1); + if (r < 0) + return r; + } + + if (arg_remain_after_exit) { + r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); + if (r < 0) + return r; + } + + if (arg_service_type) { + r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_service_type); + if (r < 0) + return r; + } + + if (arg_exec_user) { + r = sd_bus_message_append(m, "(sv)", "User", "s", arg_exec_user); + if (r < 0) + return r; + } + + if (arg_exec_group) { + r = sd_bus_message_append(m, "(sv)", "Group", "s", arg_exec_group); + if (r < 0) + return r; + } + + if (arg_nice_set) { + r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice); + if (r < 0) + return r; + } + + if (pty_path) { + const char *e; + + r = sd_bus_message_append(m, + "(sv)(sv)(sv)(sv)", + "StandardInput", "s", "tty", + "StandardOutput", "s", "tty", + "StandardError", "s", "tty", + "TTYPath", "s", pty_path); + if (r < 0) + return r; + + e = getenv("TERM"); + if (e) { + char *n; + + n = strjoina("TERM=", e); + r = sd_bus_message_append(m, + "(sv)", + "Environment", "as", 1, n); + if (r < 0) + return r; + } + } + + if (!strv_isempty(arg_environment)) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, arg_environment); + 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; + } + + /* Exec container */ + { + 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_append_strv(m, argv); + 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 0; +} + +static int transient_scope_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + r = transient_kill_set_properties(m); + if (r < 0) + return r; + + r = transient_cgroup_set_properties(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid()); + if (r < 0) + return r; + + return 0; +} + +static int transient_timer_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_timer_property); + if (r < 0) + return r; + + /* Automatically clean up our transient timers */ + r = sd_bus_message_append(m, "(sv)", "RemainAfterElapse", "b", false); + if (r < 0) + return r; + + if (arg_on_active) { + r = sd_bus_message_append(m, "(sv)", "OnActiveSec", "t", arg_on_active); + if (r < 0) + return r; + } + + if (arg_on_boot) { + r = sd_bus_message_append(m, "(sv)", "OnBootSec", "t", arg_on_boot); + if (r < 0) + return r; + } + + if (arg_on_startup) { + r = sd_bus_message_append(m, "(sv)", "OnStartupSec", "t", arg_on_startup); + if (r < 0) + return r; + } + + if (arg_on_unit_active) { + r = sd_bus_message_append(m, "(sv)", "OnUnitActiveSec", "t", arg_on_unit_active); + if (r < 0) + return r; + } + + if (arg_on_unit_inactive) { + r = sd_bus_message_append(m, "(sv)", "OnUnitInactiveSec", "t", arg_on_unit_inactive); + if (r < 0) + return r; + } + + if (arg_on_calendar) { + r = sd_bus_message_append(m, "(sv)", "OnCalendar", "s", arg_on_calendar); + if (r < 0) + return r; + } + + return 0; +} + +static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { + const char *unique, *id; + char *p; + int r; + + assert(bus); + assert(t >= 0); + assert(t < _UNIT_TYPE_MAX); + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) { + sd_id128_t rnd; + + /* We couldn't get the unique name, which is a pretty + * common case if we are connected to systemd + * directly. In that case, just pick a random uuid as + * name */ + + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_error_errno(r, "Failed to generate random run unit name: %m"); + + if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) + return log_oom(); + + return 0; + } + + /* We managed to get the unique name, then let's use that to + * name our transient units. */ + + id = startswith(unique, ":1."); + if (!id) { + log_error("Unique name %s has unexpected format.", unique); + return -EINVAL; + } + + p = strjoin("run-u", id, ".", unit_type_to_string(t), NULL); + if (!p) + return log_oom(); + + *ret = p; + return 0; +} + +typedef struct RunContext { + sd_bus *bus; + sd_event *event; + PTYForward *forward; + sd_bus_slot *match; + + /* The exit data of the unit */ + char *active_state; + uint64_t inactive_exit_usec; + uint64_t inactive_enter_usec; + char *result; + uint64_t cpu_usage_nsec; + uint32_t exit_code; + uint32_t exit_status; +} RunContext; + +static void run_context_free(RunContext *c) { + assert(c); + + c->forward = pty_forward_free(c->forward); + c->match = sd_bus_slot_unref(c->match); + c->bus = sd_bus_unref(c->bus); + c->event = sd_event_unref(c->event); + + free(c->active_state); + free(c->result); +} + +static void run_context_check_done(RunContext *c) { + bool done = true; + + assert(c); + + if (c->match) + done = done && (c->active_state && STR_IN_SET(c->active_state, "inactive", "failed")); + + if (c->forward) + done = done && pty_forward_is_done(c->forward); + + if (done) + sd_event_exit(c->event, EXIT_SUCCESS); +} + +static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + + static const struct bus_properties_map map[] = { + { "ActiveState", "s", NULL, offsetof(RunContext, active_state) }, + { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(RunContext, inactive_exit_usec) }, + { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(RunContext, inactive_enter_usec) }, + { "Result", "s", NULL, offsetof(RunContext, result) }, + { "ExecMainCode", "i", NULL, offsetof(RunContext, exit_code) }, + { "ExecMainStatus", "i", NULL, offsetof(RunContext, exit_status) }, + { "CPUUsageNSec", "t", NULL, offsetof(RunContext, cpu_usage_nsec) }, + {} + }; + + RunContext *c = userdata; + int r; + + r = bus_map_all_properties(c->bus, + "org.freedesktop.systemd1", + sd_bus_message_get_path(m), + map, + c); + if (r < 0) { + sd_event_exit(c->event, EXIT_FAILURE); + return log_error_errno(r, "Failed to query unit state: %m"); + } + + run_context_check_done(c); + return 0; +} + +static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) { + RunContext *c = userdata; + + assert(f); + + if (rcode < 0) { + sd_event_exit(c->event, EXIT_FAILURE); + return log_error_errno(rcode, "Error on PTY forwarding logic: %m"); + } + + run_context_check_done(c); + return 0; +} + +static int start_transient_service( + sd_bus *bus, + char **argv, + int *retval) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *service = NULL, *pty_path = NULL; + _cleanup_close_ int master = -1; + int r; + + assert(bus); + assert(argv); + assert(retval); + + if (arg_pty) { + + if (arg_transport == BUS_TRANSPORT_LOCAL) { + master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); + if (master < 0) + return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); + + r = ptsname_malloc(master, &pty_path); + if (r < 0) + return log_error_errno(r, "Failed to determine tty name: %m"); + + if (unlockpt(master) < 0) + return log_error_errno(errno, "Failed to unlock tty: %m"); + + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { + _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = NULL; + const char *s; + + r = sd_bus_default_system(&system_bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_call_method(system_bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachinePTY", + &error, + &pty_reply, + "s", arg_host); + if (r < 0) { + log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(pty_reply, "hs", &master, &s); + if (r < 0) + return bus_log_parse_error(r); + + master = fcntl(master, F_DUPFD_CLOEXEC, 3); + if (master < 0) + return log_error_errno(errno, "Failed to duplicate master fd: %m"); + + pty_path = strdup(s); + if (!pty_path) + return log_oom(); + } else + assert_not_reached("Can't allocate tty via ssh"); + } + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + if (arg_unit) { + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + } else { + r = make_unit_name(bus, UNIT_SERVICE, &service); + if (r < 0) + return r; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", service, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_service_set_properties(m, argv, pty_path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (!arg_quiet) + log_info("Running as unit: %s", service); + + if (arg_wait || master >= 0) { + _cleanup_(run_context_free) RunContext c = {}; + + c.bus = sd_bus_ref(bus); + + r = sd_event_default(&c.event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + if (master >= 0) { + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + (void) sd_event_add_signal(c.event, NULL, SIGINT, NULL, NULL); + (void) sd_event_add_signal(c.event, NULL, SIGTERM, NULL, NULL); + + if (!arg_quiet) + log_info("Press ^] three times within 1s to disconnect TTY."); + + r = pty_forward_new(c.event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &c.forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + pty_forward_set_handler(c.forward, pty_forward_handler, &c); + } + + if (arg_wait) { + _cleanup_free_ char *path = NULL; + const char *mt; + + path = unit_dbus_path_from_name(service); + if (!path) + return log_oom(); + + mt = strjoina("type='signal'," + "sender='org.freedesktop.systemd1'," + "path='", path, "'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'"); + r = sd_bus_add_match(bus, &c.match, mt, on_properties_changed, &c); + if (r < 0) + return log_error_errno(r, "Failed to add properties changed signal."); + + r = sd_bus_attach_event(bus, c.event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop."); + } + + r = sd_event_loop(c.event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + if (c.forward) { + char last_char = 0; + + r = pty_forward_get_last_char(c.forward, &last_char); + if (r >= 0 && !arg_quiet && last_char != '\n') + fputc('\n', stdout); + } + + if (!arg_quiet) { + if (!isempty(c.result)) + log_info("Finished with result: %s", strna(c.result)); + + if (c.exit_code == CLD_EXITED) + log_info("Main processes terminated with: code=%s/status=%i", sigchld_code_to_string(c.exit_code), c.exit_status); + else if (c.exit_code > 0) + log_info("Main processes terminated with: code=%s/status=%s", sigchld_code_to_string(c.exit_code), signal_to_string(c.exit_status)); + + if (c.inactive_enter_usec > 0 && c.inactive_enter_usec != USEC_INFINITY && + c.inactive_exit_usec > 0 && c.inactive_exit_usec != USEC_INFINITY && + c.inactive_enter_usec > c.inactive_exit_usec) { + char ts[FORMAT_TIMESPAN_MAX]; + log_info("Service runtime: %s", format_timespan(ts, sizeof(ts), c.inactive_enter_usec - c.inactive_exit_usec, USEC_PER_MSEC)); + } + + if (c.cpu_usage_nsec > 0 && c.cpu_usage_nsec != NSEC_INFINITY) { + char ts[FORMAT_TIMESPAN_MAX]; + log_info("CPU time consumed: %s", format_timespan(ts, sizeof(ts), (c.cpu_usage_nsec + NSEC_PER_USEC - 1) / NSEC_PER_USEC, USEC_PER_MSEC)); + } + } + + /* Try to propagate the service's return value */ + if (c.result && STR_IN_SET(c.result, "success", "exit-code") && c.exit_code == CLD_EXITED) + *retval = c.exit_status; + else + *retval = EXIT_FAILURE; + } + + return 0; +} + +static int start_transient_scope( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_strv_free_ char **env = NULL, **user_env = NULL; + _cleanup_free_ char *scope = NULL; + const char *object = NULL; + int r; + + assert(bus); + assert(argv); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + if (arg_unit) { + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".scope", &scope); + if (r < 0) + return log_error_errno(r, "Failed to mangle scope name: %m"); + } else { + r = make_unit_name(bus, UNIT_SCOPE, &scope); + if (r < 0) + return r; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and Mode */ + r = sd_bus_message_append(m, "ss", scope, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_scope_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (arg_nice_set) { + if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0) + return log_error_errno(errno, "Failed to set nice level: %m"); + } + + if (arg_exec_group) { + gid_t gid; + + r = get_group_creds(&arg_exec_group, &gid); + if (r < 0) + return log_error_errno(r, "Failed to resolve group %s: %m", arg_exec_group); + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); + } + + if (arg_exec_user) { + const char *home, *shell; + uid_t uid; + gid_t gid; + + r = get_user_creds_clean(&arg_exec_user, &uid, &gid, &home, &shell); + if (r < 0) + return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); + + if (home) { + r = strv_extendf(&user_env, "HOME=%s", home); + if (r < 0) + return log_oom(); + } + + if (shell) { + r = strv_extendf(&user_env, "SHELL=%s", shell); + if (r < 0) + return log_oom(); + } + + r = strv_extendf(&user_env, "USER=%s", arg_exec_user); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "LOGNAME=%s", arg_exec_user); + if (r < 0) + return log_oom(); + + if (!arg_exec_group) { + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); + } + + if (setresuid(uid, uid, uid) < 0) + return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); + } + + env = strv_env_merge(3, environ, user_env, arg_environment); + if (!env) + return log_oom(); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + + if (!arg_quiet) + log_info("Running scope as unit: %s", scope); + + execvpe(argv[0], argv, env); + + return log_error_errno(errno, "Failed to execute: %m"); +} + +static int start_transient_timer( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *timer = NULL, *service = NULL; + const char *object = NULL; + int r; + + assert(bus); + assert(argv); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + if (arg_unit) { + switch (unit_name_to_type(arg_unit)) { + + case UNIT_SERVICE: + service = strdup(arg_unit); + if (!service) + return log_oom(); + + r = unit_name_change_suffix(service, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + break; + + case UNIT_TIMER: + timer = strdup(arg_unit); + if (!timer) + return log_oom(); + + r = unit_name_change_suffix(timer, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + break; + + default: + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + break; + } + } else { + r = make_unit_name(bus, UNIT_SERVICE, &service); + if (r < 0) + return r; + + r = unit_name_change_suffix(service, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and Mode */ + r = sd_bus_message_append(m, "ss", timer, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_timer_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + if (!strv_isempty(argv)) { + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", service); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_service_set_properties(m, argv, NULL); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + + if (!arg_quiet) { + log_info("Running timer as unit: %s", timer); + if (argv[0]) + log_info("Will run service as unit: %s", service); + } + + return 0; +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *description = NULL, *command = NULL; + int r, retval = EXIT_SUCCESS; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (argc > optind && arg_transport == BUS_TRANSPORT_LOCAL) { + /* Patch in an absolute path */ + + r = find_binary(argv[optind], &command); + if (r < 0) { + log_error_errno(r, "Failed to find executable %s: %m", argv[optind]); + goto finish; + } + + argv[optind] = command; + } + + if (!arg_description) { + description = strv_join(argv + optind, " "); + if (!description) { + r = log_oom(); + goto finish; + } + + if (arg_unit && isempty(description)) { + r = free_and_strdup(&description, arg_unit); + if (r < 0) + goto finish; + } + + arg_description = description; + } + + /* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the limited direct + * connection */ + if (arg_wait) + r = bus_connect_transport(arg_transport, arg_host, arg_user, &bus); + else + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + if (arg_scope) + r = start_transient_scope(bus, argv + optind); + else if (with_timer()) + r = start_transient_timer(bus, argv + optind); + else + r = start_transient_service(bus, argv + optind, &retval); + +finish: + strv_free(arg_environment); + strv_free(arg_property); + strv_free(arg_timer_property); + + return r < 0 ? EXIT_FAILURE : retval; +} diff --git a/src/grp-system/grp-utils/systemd-run/systemd-run.completion.bash b/src/grp-system/grp-utils/systemd-run/systemd-run.completion.bash new file mode 100644 index 0000000000..4116ba7eca --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/systemd-run.completion.bash @@ -0,0 +1,118 @@ +# systemd-run(1) completion -*- shell-script -*- +# +# 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 +# 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/>. + +__systemctl() { + local mode=$1; shift 1 + systemctl $mode --full --no-legend "$@" +} + +__get_slice_units () { __systemctl $1 list-units --all -t slice \ + | { while read -r a b c d; do echo " $a"; done; }; } + +__get_machines() { + local a b + machinectl list --no-legend --no-pager | { while read a b; do echo " $a"; done; }; +} + +_systemd_run() { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local OPTS='-h --help --version --user --system --scope --unit --description --slice + -r --remain-after-exit --send-sighup -H --host -M --machine --service-type + --on-active --on-boot --on-startup --on-unit-active --on-unit-inactive + --on-calendar --timer-property -t --pty -q --quiet --no-block + --uid --gid --nice --setenv -p --property --no-ask-password + --wait' + + local mode=--system + local i + local opts_with_values=( + --unit --description --slice --service-type -H --host -M --machine -p --property --on-active + --on-boot --on-startup --on-unit-active --on-unit-inactive --on-calendar --timer-property + ) + for (( i=1; i <= COMP_CWORD; i++ )); do + if [[ ${COMP_WORDS[i]} != -* ]]; then + local root_command=${COMP_WORDS[i]} + _command_offset $i + return + fi + + [[ ${COMP_WORDS[i]} == "--user" ]] && mode=--user + + [[ $i -lt $COMP_CWORD && " ${opts_with_values[@]} " =~ " ${COMP_WORDS[i]} " ]] && ((i++)) + done + + case "$prev" in + --unit|--description|--on-active|--on-boot|--on-startup|--on-unit-active|--on-unit-inactive|--on-calendar) + # argument required but no completions available + return + ;; + --slice) + local comps=$(__get_slice_units $mode) + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + ;; + --service-type) + local comps='simple forking oneshot dbus notify idle' + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + ;; + -p|--property) + local comps='CPUAccounting= MemoryAccounting= BlockIOAccounting= SendSIGHUP= + SendSIGKILL= MemoryLimit= CPUShares= BlockIOWeight= User= Group= + DevicePolicy= KillMode= DeviceAllow= BlockIOReadBandwidth= + BlockIOWriteBandwidth= BlockIODeviceWeight= Nice= Environment= + KillSignal= LimitCPU= LimitFSIZE= LimitDATA= LimitSTACK= + LimitCORE= LimitRSS= LimitNOFILE= LimitAS= LimitNPROC= + LimitMEMLOCK= LimitLOCKS= LimitSIGPENDING= LimitMSGQUEUE= + LimitNICE= LimitRTPRIO= LimitRTTIME= PrivateTmp= PrivateDevices= + PrivateNetwork= NoNewPrivileges= WorkingDirectory= RootDirectory= + TTYPath= SyslogIdentifier= SyslogLevelPrefix= SyslogLevel= + SyslogFacility= TimerSlackNSec= OOMScoreAdjust= ReadWritePaths= + ReadOnlyPaths= InaccessiblePaths= EnvironmentFile= + ProtectSystem= ProtectHome= RuntimeDirectory= PassEnvironment=' + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + ;; + -H|--host) + local comps=$(compgen -A hostname) + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + ;; + -M|--machine) + local comps=$( __get_machines ) + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + ;; + --timer-property) + local comps='AccuracySec= WakeSystem=' + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + ;; + esac + + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 +} + +complete -F _systemd_run systemd-run diff --git a/src/grp-system/grp-utils/systemd-run/systemd-run.completion.zsh b/src/grp-system/grp-utils/systemd-run/systemd-run.completion.zsh new file mode 100644 index 0000000000..da9f73a6d0 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/systemd-run.completion.zsh @@ -0,0 +1,61 @@ +#compdef systemd-run + +__systemctl() { + local -a _modes + _modes=("--user" "--system") + systemctl ${words:*_modes} --full --no-legend --no-pager "$@" 2>/dev/null +} + +__get_slices () { + __systemctl list-units --all -t slice \ + | { while read -r a b; do echo $a; done; }; +} + +__slices () { + local -a _slices + _slices=(${(fo)"$(__get_slices)"}) + typeset -U _slices + _describe 'slices' _slices +} + +_arguments \ + {-h,--help}'[Show help message]' \ + '--version[Show package version]' \ + '--user[Run as user unit]' \ + {-H+,--host=}'[Operate on remote host]:[user@]host:_sd_hosts_or_user_at_host' \ + {-M+,--machine=}'[Operate on local container]:machines:_sd_machines' \ + '--scope[Run this as scope rather than service]' \ + '--unit=[Run under the specified unit name]:unit name' \ + {-p+,--property=}'[Set unit property]:NAME=VALUE:(( \ + CPUAccounting= MemoryAccounting= BlockIOAccounting= SendSIGHUP= \ + SendSIGKILL= MemoryLimit= CPUShares= BlockIOWeight= User= Group= \ + DevicePolicy= KillMode= DeviceAllow= BlockIOReadBandwidth= \ + BlockIOWriteBandwidth= BlockIODeviceWeight= Nice= Environment= \ + KillSignal= LimitCPU= LimitFSIZE= LimitDATA= LimitSTACK= \ + LimitCORE= LimitRSS= LimitNOFILE= LimitAS= LimitNPROC= \ + LimitMEMLOCK= LimitLOCKS= LimitSIGPENDING= LimitMSGQUEUE= \ + LimitNICE= LimitRTPRIO= LimitRTTIME= PrivateTmp= PrivateDevices= \ + PrivateNetwork= NoNewPrivileges= WorkingDirectory= RootDirectory= \ + TTYPath= SyslogIdentifier= SyslogLevelPrefix= SyslogLevel= \ + SyslogFacility= TimerSlackNSec= OOMScoreAdjust= ReadWritePaths= \ + ReadOnlyPaths= InaccessiblePaths= EnvironmentFile= \ + ProtectSystem= ProtectHome= RuntimeDirectory= PassEnvironment= \ + ))' \ + '--description=[Description for unit]:description' \ + '--slice=[Run in the specified slice]:slices:__slices' \ + {-r,--remain-after-exit}'[Leave service around until explicitly stopped]' \ + '--send-sighup[Send SIGHUP when terminating]' \ + '--service-type=[Service type]:type:(simple forking oneshot dbus notify idle)' \ + '--uid=[Run as system user]:user:_users' \ + '--gid=[Run as system group]:group:_groups' \ + '--nice=[Nice level]:nice level' \ + '--setenv=[Set environment]:NAME=VALUE' \ + '--on-active=[Run after SEC seconds]:SEC' \ + '--on-boot=[Run after SEC seconds from machine was booted up]:SEC' \ + '--on-statup=[Run after SEC seconds from systemd was first started]:SEC' \ + '--on-unit-active=[Run after SEC seconds from the last activation]:SEC' \ + '--on-unit-inactive=[Run after SEC seconds from the last deactivation]:SEC' \ + '--on-calendar=[Realtime timer]:SPEC' \ + '--timer-property=[Set timer unit property]:NAME=VALUE' \ + '--wait=[Wait until service stopped again]' \ + '*::command:_command' diff --git a/src/grp-system/grp-utils/systemd-run/systemd-run.xml b/src/grp-system/grp-utils/systemd-run/systemd-run.xml new file mode 100644 index 0000000000..2ad8cb0835 --- /dev/null +++ b/src/grp-system/grp-utils/systemd-run/systemd-run.xml @@ -0,0 +1,437 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + 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/>. +--> + +<refentry id="systemd-run" + xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>systemd-run</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-run</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-run</refname> + <refpurpose>Run programs in transient scope units, service units, or timer-scheduled service units</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>systemd-run</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain"><replaceable>COMMAND</replaceable> + <arg choice="opt" rep="repeat">ARGS</arg> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>systemd-run</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="opt" rep="repeat">TIMER OPTIONS</arg> + <arg choice="req"><replaceable>COMMAND</replaceable></arg> + <arg choice="opt" rep="repeat">ARGS</arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-run</command> may be used to create and start a transient <filename>.service</filename> or + <filename>.scope</filename> unit and run the specified <replaceable>COMMAND</replaceable> in it. It may also be + used to create and start a transient <filename>.timer</filename> unit, that activates a + <filename>.service</filename> unit when elapsing.</para> + + <para>If a command is run as transient service unit, it will be started and managed by the service manager like any + other service, and thus shows up in the output of <command>systemctl list-units</command> like any other unit. It + will run in a clean and detached execution environment, with the service manager as its parent process. In this + mode, <command>systemd-run</command> will start the service asynchronously in the background and return after the + command has begun execution (unless <option>--no-block</option> or <option>--watch</option> are specified, see + below).</para> + + <para>If a command is run as transient scope unit, it will be executed by <command>systemd-run</command> itself as + parent process and will thus inherit the execution environment of the caller. However, the processes of the command + are managed by the service manager similar to normal services, and will show up in the output of <command>systemctl + list-units</command>. Execution in this case is synchronous, and will return only when the command finishes. This + mode is enabled via the <option>--scope</option> switch (see below). </para> + + <para>If a command is run with timer options such as <option>--on-calendar=</option> (see below), a transient timer + unit is created alongside the service unit for the specified command. Only the transient timer unit is started + immediately, the transient service unit will be started when the timer elapses. If the <option>--unit=</option> + option is specified, the <replaceable>COMMAND</replaceable> may be omitted. In this case, + <command>systemd-run</command> creates only a <filename>.timer</filename> unit that invokes the specified unit when + elapsing.</para> + </refsect1> + + <refsect1> + <title>Options</title> + + <para>The following options are understood:</para> + + <variablelist> + <varlistentry> + <term><option>--no-ask-password</option></term> + + <listitem><para>Do not query the user for authentication for + privileged operations.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--scope</option></term> + + <listitem> + <para>Create a transient <filename>.scope</filename> unit instead of the default transient + <filename>.service</filename> unit (see above). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--unit=</option></term> + + <listitem><para>Use this unit name instead of an automatically + generated one.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--property=</option></term> + <term><option>-p</option></term> + + <listitem><para>Sets a property on the scope or service unit that is created. This option takes an assignment + in the same format as + <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s + <command>set-property</command> command.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--description=</option></term> + + <listitem><para>Provide a description for the service, scope or timer unit. If not specified, the command + itself will be used as a description. See <varname>Description=</varname> in + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--slice=</option></term> + + <listitem><para>Make the new <filename>.service</filename> or <filename>.scope</filename> unit part of the + specified slice, instead of <filename>system.slice</filename>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--remain-after-exit</option></term> + + <listitem><para>After the service process has terminated, keep the service around until it is explicitly + stopped. This is useful to collect runtime information about the service after it finished running. Also see + <varname>RemainAfterExit=</varname> in + <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--send-sighup</option></term> + + <listitem><para>When terminating the scope or service unit, send a SIGHUP immediately after SIGTERM. This is + useful to indicate to shells and shell-like processes that the connection has been severed. Also see + <varname>SendSIGHUP=</varname> in + <citerefentry><refentrytitle>systemd.kill</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--service-type=</option></term> + + <listitem><para>Sets the service type. Also see + <varname>Type=</varname> in + <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This + option has no effect in conjunction with + <option>--scope</option>. Defaults to + <constant>simple</constant>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--uid=</option></term> + <term><option>--gid=</option></term> + + <listitem><para>Runs the service process under the specified UNIX user and group. Also see + <varname>User=</varname> and <varname>Group=</varname> in + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--nice=</option></term> + + <listitem><para>Runs the service process with the specified + nice level. Also see <varname>Nice=</varname> in + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-E <replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term> + <term><option>--setenv=<replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term> + + <listitem><para>Runs the service process with the specified environment variable set. + Also see <varname>Environment=</varname> in + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--pty</option></term> + <term><option>-t</option></term> + + <listitem><para>When invoking the command, the transient service connects its standard input and output to the + terminal <command>systemd-run</command> is invoked on, via a pseudo TTY device. This allows running binaries + that expect interactive user input as services, such as interactive command shells.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--quiet</option></term> + <term><option>-q</option></term> + + <listitem><para>Suppresses additional informational output + while running. This is particularly useful in combination with + <option>--pty</option> when it will suppress the initial + message explaining how to terminate the TTY connection.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--on-active=</option></term> + <term><option>--on-boot=</option></term> + <term><option>--on-startup=</option></term> + <term><option>--on-unit-active=</option></term> + <term><option>--on-unit-inactive=</option></term> + + <listitem><para>Defines a monotonic timer relative to different starting points for starting the specified + command. See <varname>OnActiveSec=</varname>, <varname>OnBootSec=</varname>, <varname>OnStartupSec=</varname>, + <varname>OnUnitActiveSec=</varname> and <varname>OnUnitInactiveSec=</varname> in + <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry> for + details. These options may not be combined with <option>--scope</option>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--on-calendar=</option></term> + + <listitem><para>Defines a calendar timer for starting the specified command. See <varname>OnCalendar=</varname> + in <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This + option may not be combined with <option>--scope</option>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--timer-property=</option></term> + + <listitem><para>Sets a property on the timer unit that is created. This option is similar to + <option>--property=</option> but applies to the transient timer unit rather than the transient service unit + created. This option only has an effect in conjunction with <option>--on-active=</option>, + <option>--on-boot=</option>, <option>--on-startup=</option>, <option>--on-unit-active=</option>, + <option>--on-unit-inactive=</option> or <option>--on-calendar=</option>. This option takes an assignment in the + same format as <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s + <command>set-property</command> command.</para> </listitem> + </varlistentry> + + <varlistentry> + <term><option>--no-block</option></term> + + <listitem> + <para>Do not synchronously wait for the unit start operation to finish. If this option is not specified, the + start request for the transient unit will be verified, enqueued and <command>systemd-run</command> will wait + until the unit's start-up is completed. By passing this argument, it is only verified and enqueued. This + option may not be combined with <option>--wait</option>.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--wait</option></term> + + <listitem><para>Synchronously wait for the transient service to terminate. If this option is specified, the + start request for the transient unit is verified, enqueued, and waited for. Subsequently the invoked unit is + monitored, and it is waited until it is deactivated again (most likely because the specified command + completed). On exit, terse information about the unit's runtime is shown, including total runtime (as well as + CPU usage, if <option>--property=CPUAccounting=1</option> was set) and the exit code and status of the main + process. This output may be suppressed with <option>--quiet</option>. This option may not be combined with + <option>--no-block</option>, <option>--scope</option> or the various timer options.</para></listitem> + </varlistentry> + + <xi:include href="user-system-options.xml" xpointer="user" /> + <xi:include href="user-system-options.xml" xpointer="system" /> + <xi:include href="user-system-options.xml" xpointer="host" /> + <xi:include href="user-system-options.xml" xpointer="machine" /> + + <xi:include href="standard-options.xml" xpointer="help" /> + <xi:include href="standard-options.xml" xpointer="version" /> + </variablelist> + + <para>All command line arguments after the first non-option + argument become part of the command line of the launched + process. If a command is run as service unit, its first argument + needs to be an absolute binary path.</para> + </refsect1> + + <refsect1> + <title>Exit status</title> + + <para>On success, 0 is returned, a non-zero failure + code otherwise.</para> + </refsect1> + + <refsect1> + <title>Examples</title> + + <example> + <title>Logging environment variables provided by systemd to services</title> + + <programlisting># systemd-run env +Running as unit: run-19945.service +# journalctl -u run-19945.service +Sep 08 07:37:21 bupkis systemd[1]: Starting /usr/bin/env... +Sep 08 07:37:21 bupkis systemd[1]: Started /usr/bin/env. +Sep 08 07:37:21 bupkis env[19948]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +Sep 08 07:37:21 bupkis env[19948]: LANG=en_US.UTF-8 +Sep 08 07:37:21 bupkis env[19948]: BOOT_IMAGE=/vmlinuz-3.11.0-0.rc5.git6.2.fc20.x86_64</programlisting> + </example> + + <example> + <title>Limiting resources available to a command</title> + + <programlisting># systemd-run -p BlockIOWeight=10 updatedb</programlisting> + + <para>This command invokes the + <citerefentry project='man-pages'><refentrytitle>updatedb</refentrytitle><manvolnum>8</manvolnum></citerefentry> + tool, but lowers the block I/O weight for it to 10. See + <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for more information on the <varname>BlockIOWeight=</varname> + property.</para> + </example> + + <example> + <title>Running commands at a specified time</title> + + <para>The following command will touch a file after 30 seconds.</para> + + <programlisting># date; systemd-run --on-active=30 --timer-property=AccuracySec=100ms /bin/touch /tmp/foo +Mon Dec 8 20:44:24 KST 2014 +Running as unit: run-71.timer +Will run service as unit: run-71.service +# journalctl -b -u run-71.timer +-- Logs begin at Fri 2014-12-05 19:09:21 KST, end at Mon 2014-12-08 20:44:54 KST. -- +Dec 08 20:44:38 container systemd[1]: Starting /bin/touch /tmp/foo. +Dec 08 20:44:38 container systemd[1]: Started /bin/touch /tmp/foo. +# journalctl -b -u run-71.service +-- Logs begin at Fri 2014-12-05 19:09:21 KST, end at Mon 2014-12-08 20:44:54 KST. -- +Dec 08 20:44:48 container systemd[1]: Starting /bin/touch /tmp/foo... +Dec 08 20:44:48 container systemd[1]: Started /bin/touch /tmp/foo.</programlisting> + </example> + + <example> + <title>Allowing access to the tty</title> + + <para>The following command invokes <filename>/bin/bash</filename> as a service + passing its standard input, output and error to the calling TTY.</para> + + <programlisting># systemd-run -t --send-sighup /bin/bash</programlisting> + </example> + + <example> + <title>Start <command>screen</command> as a user service</title> + + <programlisting>$ systemd-run --scope --user screen +Running scope as unit run-r14b0047ab6df45bfb45e7786cc839e76.scope. + +$ screen -ls +There is a screen on: + 492..laptop (Detached) +1 Socket in /var/run/screen/S-fatima. +</programlisting> + + <para>This starts the <command>screen</command> process as a child of the + <command>systemd --user</command> process that was started by + <filename>user@.service</filename>, in a scope unit. A + <citerefentry><refentrytitle>systemd.scope</refentrytitle><manvolnum>5</manvolnum></citerefentry> + unit is used instead of a + <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> + unit, because <command>screen</command> will exit when detaching from the terminal, + and a service unit would be terminated. Running <command>screen</command> + as a user unit has the advantage that it is not part of the session scope. + If <varname>KillUserProcesses=yes</varname> is configured in + <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + the default, the session scope will be terminated when the user logs + out of that session.</para> + + <para>The <filename>user@.service</filename> is started automatically + when the user first logs in, and stays around as long as at least one + login session is open. After the user logs out of the last session, + <filename>user@.service</filename> and all services underneath it + are terminated. This behavior is the default, when "lingering" is + not enabled for that user. Enabling lingering means that + <filename>user@.service</filename> is started automatically during + boot, even if the user is not logged in, and that the service is + not terminated when the user logs out.</para> + + <para>Enabling lingering allows the user to run processes without being logged in, + for example to allow <command>screen</command> to persist after the user logs out, + even if the session scope is terminated. In the default configuration, users can + enable lingering for themselves:</para> + + <programlisting>$ loginctl enable-linger</programlisting> + </example> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.scope</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.slice</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-mount</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/src/grp-system/grp-utils/systemd-sysv-generator/.gitignore b/src/grp-system/grp-utils/systemd-sysv-generator/.gitignore new file mode 100644 index 0000000000..c3fea7424f --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/.gitignore @@ -0,0 +1 @@ +/README diff --git a/src/grp-system/grp-utils/systemd-sysv-generator/GNUmakefile b/src/grp-system/grp-utils/systemd-sysv-generator/GNUmakefile new file mode 120000 index 0000000000..13308a50cd --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/GNUmakefile @@ -0,0 +1 @@ +../../../../GNUmakefile
\ No newline at end of file diff --git a/src/grp-system/grp-utils/systemd-sysv-generator/Makefile b/src/grp-system/grp-utils/systemd-sysv-generator/Makefile new file mode 100644 index 0000000000..9dec62efdc --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sysv_generator_SOURCES = \ + src/sysv-generator/sysv-generator.c + +systemd_sysv_generator_LDADD = \ + libcore.la + +ifneq ($(HAVE_SYSV_COMPAT),) +sysvinit_DATA = \ + docs/sysvinit/README + +$(outdir)/README: docs/sysvinit/README.in + $(SED_PROCESS) + +CLEANFILES += \ + docs/sysvinit/README +endif # HAVE_SYSV_COMPAT + +EXTRA_DIST += \ + docs/sysvinit/README.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-system/grp-utils/systemd-sysv-generator/README.in b/src/grp-system/grp-utils/systemd-sysv-generator/README.in new file mode 100644 index 0000000000..996402d06b --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/README.in @@ -0,0 +1,27 @@ +You are looking for the traditional init scripts in @SYSTEM_SYSVINIT_PATH@, +and they are gone? + +Here's an explanation on what's going on: + +You are running a systemd-based OS where traditional init scripts have +been replaced by native systemd services files. Service files provide +very similar functionality to init scripts. To make use of service +files simply invoke "systemctl", which will output a list of all +currently running services (and other units). Use "systemctl +list-unit-files" to get a listing of all known unit files, including +stopped, disabled and masked ones. Use "systemctl start +foobar.service" and "systemctl stop foobar.service" to start or stop a +service, respectively. For further details, please refer to +systemctl(1). + +Note that traditional init scripts continue to function on a systemd +system. An init script @SYSTEM_SYSVINIT_PATH@/foobar is implicitly mapped +into a service unit foobar.service during system initialization. + +Thank you! + +Further reading: + man:systemctl(1) + man:systemd(1) + http://0pointer.de/blog/projects/systemd-for-admins-3.html + http://www.freedesktop.org/wiki/Software/systemd/Incompatibilities diff --git a/src/grp-system/grp-utils/systemd-sysv-generator/systemd-sysv-generator.xml b/src/grp-system/grp-utils/systemd-sysv-generator/systemd-sysv-generator.xml new file mode 100644 index 0000000000..2353eb3efe --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/systemd-sysv-generator.xml @@ -0,0 +1,97 @@ +<?xml version="1.0"?> +<!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<!-- + This file is part of systemd. + + Copyright 2014 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/>. +--> +<refentry id="systemd-sysv-generator" conditional="HAVE_SYSV_COMPAT"> + + <refentryinfo> + <title>systemd-sysv-generator</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Documentation</contrib> + <firstname>Zbigniew</firstname> + <surname>Jędrzejewski-Szmek</surname> + <email>zbyszek@in.waw.pl</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-sysv-generator</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-sysv-generator</refname> + <refpurpose>Unit generator for SysV init scripts</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/usr/lib/systemd/system-generators/systemd-sysv-generator</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><filename>systemd-sysv-generator</filename> is a generator + that creates wrapper .service units for + <ulink url="https://savannah.nongnu.org/projects/sysvinit">SysV init</ulink> + scripts in <filename>/etc/init.d/*</filename> at boot and when + configuration of the system manager is reloaded. This will allow + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> + to support them similarly to native units.</para> + + <para><ulink url="http://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html">LSB headers</ulink> + in SysV init scripts are interpreted, and the ordering specified + in the header is turned into dependencies between the generated + unit and other units. The LSB facilities + <literal>$remote_fs</literal>, <literal>$network</literal>, + <literal>$named</literal>, <literal>$portmap</literal>, + <literal>$time</literal> are supported and will be turned into + dependencies on specific native systemd targets. See + <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for more details.</para> + + <para>SysV runlevels have corresponding systemd targets + (<filename>runlevel<replaceable>X</replaceable>.target</filename>). + The wrapper unit that is generated will be wanted by those targets + which correspond to runlevels for which the script is + enabled.</para> + + <para><command>systemd</command> does not support SysV scripts as + part of early boot, so all wrapper units are ordered after + <filename>basic.target</filename>.</para> + + <para><filename>systemd-sysv-generator</filename> implements + <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/src/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c b/src/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c new file mode 100644 index 0000000000..42dc76c20e --- /dev/null +++ b/src/grp-system/grp-utils/systemd-sysv-generator/sysv-generator.c @@ -0,0 +1,993 @@ +/*** + This file is part of systemd. + + Copyright 2014 Thomas H.P. Andersen + Copyright 2010 Lennart Poettering + Copyright 2011 Michal Schmidt + + 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 <stdio.h> +#include <unistd.h> + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/dirent-util.h" +#include "systemd-basic/exit-status.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/fileio.h" +#include "systemd-basic/hashmap.h" +#include "systemd-basic/hexdecoct.h" +#include "systemd-basic/log.h" +#include "systemd-basic/mkdir.h" +#include "systemd-basic/path-util.h" +#include "systemd-basic/set.h" +#include "systemd-basic/special.h" +#include "systemd-basic/stat-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/unit-name.h" +#include "systemd-basic/util.h" +#include "systemd-shared/install.h" +#include "systemd-shared/path-lookup.h" + +static const struct { + const char *path; + const char *target; +} rcnd_table[] = { + /* Standard SysV runlevels for start-up */ + { "rc1.d", SPECIAL_RESCUE_TARGET }, + { "rc2.d", SPECIAL_MULTI_USER_TARGET }, + { "rc3.d", SPECIAL_MULTI_USER_TARGET }, + { "rc4.d", SPECIAL_MULTI_USER_TARGET }, + { "rc5.d", SPECIAL_GRAPHICAL_TARGET }, + + /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that + * means they are shut down anyway at system power off if running. */ +}; + +static const char *arg_dest = "/tmp"; + +typedef struct SysvStub { + char *name; + char *path; + char *description; + int sysv_start_priority; + char *pid_file; + char **before; + char **after; + char **wants; + char **wanted_by; + bool has_lsb; + bool reload; + bool loaded; +} SysvStub; + +static void free_sysvstub(SysvStub *s) { + if (!s) + return; + + free(s->name); + free(s->path); + free(s->description); + free(s->pid_file); + strv_free(s->before); + strv_free(s->after); + strv_free(s->wants); + strv_free(s->wanted_by); + free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, free_sysvstub); + +static void free_sysvstub_hashmapp(Hashmap **h) { + SysvStub *stub; + + while ((stub = hashmap_steal_first(*h))) + free_sysvstub(stub); + + hashmap_free(*h); +} + +static int add_symlink(const char *service, const char *where) { + const char *from, *to; + int r; + + assert(service); + assert(where); + + from = strjoina(arg_dest, "/", service); + to = strjoina(arg_dest, "/", where, ".wants/", service); + + mkdir_parents_label(to, 0755); + + r = symlink(from, to); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +static int add_alias(const char *service, const char *alias) { + const char *link; + int r; + + assert(service); + assert(alias); + + link = strjoina(arg_dest, "/", alias); + + r = symlink(service, link); + if (r < 0) { + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +static int generate_unit_file(SysvStub *s) { + _cleanup_fclose_ FILE *f = NULL; + const char *unit; + char **p; + int r; + + assert(s); + + if (!s->loaded) + return 0; + + unit = strjoina(arg_dest, "/", s->name); + + /* We might already have a symlink with the same name from a Provides:, + * or from backup files like /etc/init.d/foo.bak. Real scripts always win, + * so remove an existing link */ + if (is_symlink(unit) > 0) { + log_warning("Overwriting existing symlink %s with real service.", unit); + (void) unlink(unit); + } + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by systemd-sysv-generator\n\n" + "[Unit]\n" + "Documentation=man:systemd-sysv-generator(8)\n" + "SourcePath=%s\n", + s->path); + + if (s->description) + fprintf(f, "Description=%s\n", s->description); + + STRV_FOREACH(p, s->before) + fprintf(f, "Before=%s\n", *p); + STRV_FOREACH(p, s->after) + fprintf(f, "After=%s\n", *p); + STRV_FOREACH(p, s->wants) + fprintf(f, "Wants=%s\n", *p); + + fprintf(f, + "\n[Service]\n" + "Type=forking\n" + "Restart=no\n" + "TimeoutSec=5min\n" + "IgnoreSIGPIPE=no\n" + "KillMode=process\n" + "GuessMainPID=no\n" + "RemainAfterExit=%s\n", + yes_no(!s->pid_file)); + + if (s->pid_file) + fprintf(f, "PIDFile=%s\n", s->pid_file); + + /* Consider two special LSB exit codes a clean exit */ + if (s->has_lsb) + fprintf(f, + "SuccessExitStatus=%i %i\n", + EXIT_NOTINSTALLED, + EXIT_NOTCONFIGURED); + + fprintf(f, + "ExecStart=%s start\n" + "ExecStop=%s stop\n", + s->path, s->path); + + if (s->reload) + fprintf(f, "ExecReload=%s reload\n", s->path); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", unit); + + STRV_FOREACH(p, s->wanted_by) { + r = add_symlink(s->name, *p); + if (r < 0) + log_warning_errno(r, "Failed to create 'Wants' symlink to %s, ignoring: %m", *p); + } + + return 1; +} + +static bool usage_contains_reload(const char *line) { + return (strcasestr(line, "{reload|") || + strcasestr(line, "{reload}") || + strcasestr(line, "{reload\"") || + strcasestr(line, "|reload|") || + strcasestr(line, "|reload}") || + strcasestr(line, "|reload\"")); +} + +static char *sysv_translate_name(const char *name) { + _cleanup_free_ char *c = NULL; + char *res; + + c = strdup(name); + if (!c) + return NULL; + + res = endswith(c, ".sh"); + if (res) + *res = 0; + + if (unit_name_mangle(c, UNIT_NAME_NOGLOB, &res) < 0) + return NULL; + + return res; +} + +static int sysv_translate_facility(SysvStub *s, unsigned line, const char *name, char **ret) { + + /* We silently ignore the $ prefix here. According to the LSB + * spec it simply indicates whether something is a + * standardized name or a distribution-specific one. Since we + * just follow what already exists and do not introduce new + * uses or names we don't care who introduced a new name. */ + + static const char * const table[] = { + /* LSB defined facilities */ + "local_fs", NULL, + "network", SPECIAL_NETWORK_ONLINE_TARGET, + "named", SPECIAL_NSS_LOOKUP_TARGET, + "portmap", SPECIAL_RPCBIND_TARGET, + "remote_fs", SPECIAL_REMOTE_FS_TARGET, + "syslog", NULL, + "time", SPECIAL_TIME_SYNC_TARGET, + }; + + const char *filename; + char *filename_no_sh, *e, *m; + const char *n; + unsigned i; + int r; + + assert(name); + assert(s); + assert(ret); + + filename = basename(s->path); + + n = *name == '$' ? name + 1 : name; + + for (i = 0; i < ELEMENTSOF(table); i += 2) { + if (!streq(table[i], n)) + continue; + + if (!table[i+1]) + return 0; + + m = strdup(table[i+1]); + if (!m) + return log_oom(); + + *ret = m; + return 1; + } + + /* If we don't know this name, fallback heuristics to figure + * out whether something is a target or a service alias. */ + + /* Facilities starting with $ are most likely targets */ + if (*name == '$') { + r = unit_name_build(n, NULL, ".target", ret); + if (r < 0) + return log_error_errno(r, "[%s:%u] Could not build name for facility %s: %m", s->path, line, name); + + return r; + } + + /* Strip ".sh" suffix from file name for comparison */ + filename_no_sh = strdupa(filename); + e = endswith(filename_no_sh, ".sh"); + if (e) { + *e = '\0'; + filename = filename_no_sh; + } + + /* Names equaling the file name of the services are redundant */ + if (streq_ptr(n, filename)) + return 0; + + /* Everything else we assume to be normal service names */ + m = sysv_translate_name(n); + if (!m) + return log_oom(); + + *ret = m; + return 1; +} + +static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) { + int r; + + assert(s); + assert(full_text); + assert(text); + + for (;;) { + _cleanup_free_ char *word = NULL, *m = NULL; + + r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line); + if (r == 0) + break; + + r = sysv_translate_facility(s, line, word, &m); + if (r <= 0) /* continue on error */ + continue; + + switch (unit_name_to_type(m)) { + + case UNIT_SERVICE: + log_debug("Adding Provides: alias '%s' for '%s'", m, s->name); + r = add_alias(s->name, m); + if (r < 0) + log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m); + break; + + case UNIT_TARGET: + + /* NB: SysV targets which are provided by a + * service are pulled in by the services, as + * an indication that the generic service is + * now available. This is strictly one-way. + * The targets do NOT pull in SysV services! */ + + r = strv_extend(&s->before, m); + if (r < 0) + return log_oom(); + + r = strv_extend(&s->wants, m); + if (r < 0) + return log_oom(); + + if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) { + r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET); + if (r < 0) + return log_oom(); + } + + break; + + case _UNIT_TYPE_INVALID: + log_warning("Unit name '%s' is invalid", m); + break; + + default: + log_warning("Unknown unit type for unit '%s'", m); + } + } + + return 0; +} + +static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) { + int r; + + assert(s); + assert(full_text); + assert(text); + + for (;;) { + _cleanup_free_ char *word = NULL, *m = NULL; + bool is_before; + + r = extract_first_word(&text, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line); + if (r == 0) + break; + + r = sysv_translate_facility(s, line, word, &m); + if (r <= 0) /* continue on error */ + continue; + + is_before = startswith_no_case(full_text, "X-Start-Before:"); + + if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) { + /* the network-online target is special, as it needs to be actively pulled in */ + r = strv_extend(&s->after, m); + if (r < 0) + return log_oom(); + + r = strv_extend(&s->wants, m); + } else + r = strv_extend(is_before ? &s->before : &s->after, m); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int load_sysv(SysvStub *s) { + _cleanup_fclose_ FILE *f; + unsigned line = 0; + int r; + enum { + NORMAL, + DESCRIPTION, + LSB, + LSB_DESCRIPTION, + USAGE_CONTINUATION + } state = NORMAL; + _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL; + char *description; + bool supports_reload = false; + char l[LINE_MAX]; + + assert(s); + + f = fopen(s->path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", s->path); + } + + log_debug("Loading SysV script %s", s->path); + + FOREACH_LINE(l, f, goto fail) { + char *t; + + line++; + + t = strstrip(l); + if (*t != '#') { + /* Try to figure out whether this init script supports + * the reload operation. This heuristic looks for + * "Usage" lines which include the reload option. */ + if ( state == USAGE_CONTINUATION || + (state == NORMAL && strcasestr(t, "usage"))) { + if (usage_contains_reload(t)) { + supports_reload = true; + state = NORMAL; + } else if (t[strlen(t)-1] == '\\') + state = USAGE_CONTINUATION; + else + state = NORMAL; + } + + continue; + } + + if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { + state = LSB; + s->has_lsb = true; + continue; + } + + if ((state == LSB_DESCRIPTION || state == LSB) && streq(t, "### END INIT INFO")) { + state = NORMAL; + continue; + } + + t++; + t += strspn(t, WHITESPACE); + + if (state == NORMAL) { + + /* Try to parse Red Hat style description */ + + if (startswith_no_case(t, "description:")) { + + size_t k; + const char *j; + + k = strlen(t); + if (k > 0 && t[k-1] == '\\') { + state = DESCRIPTION; + t[k-1] = 0; + } + + j = empty_to_null(strstrip(t+12)); + + r = free_and_strdup(&chkconfig_description, j); + if (r < 0) + return log_oom(); + + } else if (startswith_no_case(t, "pidfile:")) { + const char *fn; + + state = NORMAL; + + fn = strstrip(t+8); + if (!path_is_absolute(fn)) { + log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line); + continue; + } + + r = free_and_strdup(&s->pid_file, fn); + if (r < 0) + return log_oom(); + } + + } else if (state == DESCRIPTION) { + + /* Try to parse Red Hat style description + * continuation */ + + size_t k; + char *j; + + k = strlen(t); + if (k > 0 && t[k-1] == '\\') + t[k-1] = 0; + else + state = NORMAL; + + j = strstrip(t); + if (!isempty(j)) { + char *d = NULL; + + if (chkconfig_description) + d = strjoin(chkconfig_description, " ", j, NULL); + else + d = strdup(j); + if (!d) + return log_oom(); + + free(chkconfig_description); + chkconfig_description = d; + } + + } else if (state == LSB || state == LSB_DESCRIPTION) { + + if (startswith_no_case(t, "Provides:")) { + state = LSB; + + r = handle_provides(s, line, t, t + 9); + if (r < 0) + return r; + + } else if (startswith_no_case(t, "Required-Start:") || + startswith_no_case(t, "Should-Start:") || + startswith_no_case(t, "X-Start-Before:") || + startswith_no_case(t, "X-Start-After:")) { + + state = LSB; + + r = handle_dependencies(s, line, t, strchr(t, ':') + 1); + if (r < 0) + return r; + + } else if (startswith_no_case(t, "Description:")) { + const char *j; + + state = LSB_DESCRIPTION; + + j = empty_to_null(strstrip(t+12)); + + r = free_and_strdup(&long_description, j); + if (r < 0) + return log_oom(); + + } else if (startswith_no_case(t, "Short-Description:")) { + const char *j; + + state = LSB; + + j = empty_to_null(strstrip(t+18)); + + r = free_and_strdup(&short_description, j); + if (r < 0) + return log_oom(); + + } else if (state == LSB_DESCRIPTION) { + + if (startswith(l, "#\t") || startswith(l, "# ")) { + const char *j; + + j = strstrip(t); + if (!isempty(j)) { + char *d = NULL; + + if (long_description) + d = strjoin(long_description, " ", t, NULL); + else + d = strdup(j); + if (!d) + return log_oom(); + + free(long_description); + long_description = d; + } + + } else + state = LSB; + } + } + } + + s->reload = supports_reload; + + /* We use the long description only if + * no short description is set. */ + + if (short_description) + description = short_description; + else if (chkconfig_description) + description = chkconfig_description; + else if (long_description) + description = long_description; + else + description = NULL; + + if (description) { + char *d; + + d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description); + if (!d) + return log_oom(); + + s->description = d; + } + + s->loaded = true; + return 0; + +fail: + return log_error_errno(errno, "Failed to read configuration file '%s': %m", s->path); +} + +static int fix_order(SysvStub *s, Hashmap *all_services) { + SysvStub *other; + Iterator j; + int r; + + assert(s); + + if (!s->loaded) + return 0; + + if (s->sysv_start_priority < 0) + return 0; + + HASHMAP_FOREACH(other, all_services, j) { + if (s == other) + continue; + + if (!other->loaded) + continue; + + if (other->sysv_start_priority < 0) + continue; + + /* If both units have modern headers we don't care + * about the priorities */ + if (s->has_lsb && other->has_lsb) + continue; + + if (other->sysv_start_priority < s->sysv_start_priority) { + r = strv_extend(&s->after, other->name); + if (r < 0) + return log_oom(); + + } else if (other->sysv_start_priority > s->sysv_start_priority) { + r = strv_extend(&s->before, other->name); + if (r < 0) + return log_oom(); + } else + continue; + + /* FIXME: Maybe we should compare the name here lexicographically? */ + } + + return 0; +} + +static int acquire_search_path(const char *def, const char *envvar, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + const char *e; + int r; + + assert(def); + assert(envvar); + + e = getenv(envvar); + if (e) { + r = path_split_and_make_absolute(e, &l); + if (r < 0) + return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar); + } + + if (strv_isempty(l)) { + strv_free(l); + + l = strv_new(def, NULL); + if (!l) + return log_oom(); + } + + if (!path_strv_resolve_uniq(l, NULL)) + return log_oom(); + + *ret = l; + l = NULL; + + return 0; +} + +static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { + _cleanup_strv_free_ char **sysvinit_path = NULL; + char **path; + int r; + + assert(lp); + + r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path); + if (r < 0) + return r; + + STRV_FOREACH(path, sysvinit_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(*path); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path); + continue; + } + + FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) { + _cleanup_free_ char *fpath = NULL, *name = NULL; + _cleanup_(free_sysvstubp) SysvStub *service = NULL; + struct stat st; + + if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { + log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name); + continue; + } + + if (!(st.st_mode & S_IXUSR)) + continue; + + if (!S_ISREG(st.st_mode)) + continue; + + name = sysv_translate_name(de->d_name); + if (!name) + return log_oom(); + + if (hashmap_contains(all_services, name)) + continue; + + r = unit_file_exists(UNIT_FILE_SYSTEM, lp, name); + if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) { + log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name); + continue; + } else if (r != 0) { + log_debug("Native unit for %s already exists, skipping.", name); + continue; + } + + fpath = strjoin(*path, "/", de->d_name, NULL); + if (!fpath) + return log_oom(); + + service = new0(SysvStub, 1); + if (!service) + return log_oom(); + + service->sysv_start_priority = -1; + service->name = name; + service->path = fpath; + name = fpath = NULL; + + r = hashmap_put(all_services, service->name, service); + if (r < 0) + return log_oom(); + + service = NULL; + } + } + + return 0; +} + +static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) { + Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {}; + _cleanup_strv_free_ char **sysvrcnd_path = NULL; + SysvStub *service; + unsigned i; + Iterator j; + char **p; + int r; + + assert(lp); + + r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path); + if (r < 0) + return r; + + STRV_FOREACH(p, sysvrcnd_path) { + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) { + + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *path = NULL; + struct dirent *de; + + path = strjoin(*p, "/", rcnd_table[i].path, NULL); + if (!path) { + r = log_oom(); + goto finish; + } + + d = opendir(path); + if (!d) { + if (errno != ENOENT) + log_warning_errno(errno, "Opening %s failed, ignoring: %m", path); + + continue; + } + + FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { + _cleanup_free_ char *name = NULL, *fpath = NULL; + int a, b; + + if (de->d_name[0] != 'S') + continue; + + if (strlen(de->d_name) < 4) + continue; + + a = undecchar(de->d_name[1]); + b = undecchar(de->d_name[2]); + + if (a < 0 || b < 0) + continue; + + fpath = strjoin(*p, "/", de->d_name, NULL); + if (!fpath) { + r = log_oom(); + goto finish; + } + + name = sysv_translate_name(de->d_name + 3); + if (!name) { + r = log_oom(); + goto finish; + } + + service = hashmap_get(all_services, name); + if (!service) { + log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name); + continue; + } + + service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority); + + r = set_ensure_allocated(&runlevel_services[i], NULL); + if (r < 0) { + log_oom(); + goto finish; + } + + r = set_put(runlevel_services[i], service); + if (r < 0) { + log_oom(); + goto finish; + } + } + } + } + + for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) + SET_FOREACH(service, runlevel_services[i], j) { + r = strv_extend(&service->before, rcnd_table[i].target); + if (r < 0) { + log_oom(); + goto finish; + } + r = strv_extend(&service->wanted_by, rcnd_table[i].target); + if (r < 0) { + log_oom(); + goto finish; + } + } + + r = 0; + +finish: + for (i = 0; i < ELEMENTSOF(rcnd_table); i++) + set_free(runlevel_services[i]); + + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL; + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + SysvStub *service; + Iterator j; + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[3]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = lookup_paths_init(&lp, UNIT_FILE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL); + if (r < 0) { + log_error_errno(r, "Failed to find lookup paths: %m"); + goto finish; + } + + all_services = hashmap_new(&string_hash_ops); + if (!all_services) { + r = log_oom(); + goto finish; + } + + r = enumerate_sysv(&lp, all_services); + if (r < 0) + goto finish; + + r = set_dependencies_from_rcnd(&lp, all_services); + if (r < 0) + goto finish; + + HASHMAP_FOREACH(service, all_services, j) + (void) load_sysv(service); + + HASHMAP_FOREACH(service, all_services, j) { + (void) fix_order(service, all_services); + (void) generate_unit_file(service); + } + + r = 0; + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} |