diff options
Diffstat (limited to 'src/systemctl')
-rw-r--r-- | src/systemctl/systemctl.c | 3774 |
1 files changed, 2228 insertions, 1546 deletions
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 6db4d6587a..7ed60dbe87 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -20,59 +18,92 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <sys/reboot.h> -#include <linux/reboot.h> -#include <stdio.h> +#include <errno.h> +#include <fcntl.h> #include <getopt.h> +#include <linux/reboot.h> #include <locale.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <string.h> -#include <errno.h> -#include <unistd.h> -#include <fcntl.h> +#include <sys/reboot.h> #include <sys/socket.h> -#include <stddef.h> +#include <unistd.h> +#include "sd-bus.h" #include "sd-daemon.h" #include "sd-login.h" -#include "sd-bus.h" -#include "log.h" -#include "util.h" -#include "macro.h" -#include "set.h" -#include "utmp-wtmp.h" -#include "special.h" -#include "initreq.h" -#include "path-util.h" -#include "strv.h" + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-message.h" +#include "bus-unit-util.h" +#include "bus-util.h" #include "cgroup-show.h" #include "cgroup-util.h" -#include "list.h" -#include "path-lookup.h" -#include "exit-status.h" -#include "build.h" -#include "unit-name.h" -#include "pager.h" -#include "spawn-ask-password-agent.h" -#include "spawn-polkit-agent.h" -#include "install.h" -#include "logs-show.h" -#include "socket-util.h" -#include "fileio.h" #include "copy.h" -#include "env-util.h" -#include "bus-util.h" -#include "bus-message.h" -#include "bus-error.h" -#include "bus-common-errors.h" -#include "mkdir.h" #include "dropin.h" #include "efivars.h" +#include "env-util.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" #include "formats-util.h" -#include "process-util.h" -#include "terminal-util.h" +#include "fs-util.h" +#include "glob-util.h" #include "hostname-util.h" +#include "initreq.h" +#include "install.h" +#include "io-util.h" +#include "list.h" +#include "locale-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "set.h" +#include "sigbus.h" #include "signal-util.h" +#include "socket-util.h" +#include "spawn-ask-password-agent.h" +#include "spawn-polkit-agent.h" +#include "special.h" +#include "stat-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" +#include "verbs.h" +#include "virt.h" + +/* The init script exit status codes + 0 program is running or service is OK + 1 program is dead and /var/run pid file exists + 2 program is dead and /var/lock lock file exists + 3 program is not running + 4 program or service status is unknown + 5-99 reserved for future LSB use + 100-149 reserved for distribution use + 150-199 reserved for application use + 200-254 reserved +*/ +enum { + EXIT_PROGRAM_RUNNING_OR_SERVICE_OK = 0, + EXIT_PROGRAM_DEAD_AND_PID_EXISTS = 1, + EXIT_PROGRAM_DEAD_AND_LOCK_FILE_EXISTS = 2, + EXIT_PROGRAM_NOT_RUNNING = 3, + EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN = 4, +}; static char **arg_types = NULL; static char **arg_states = NULL; @@ -87,12 +118,15 @@ static enum dependency { } arg_dependency = DEPENDENCY_FORWARD; static const char *arg_job_mode = "replace"; static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; +static bool arg_wait = false; static bool arg_no_block = false; static bool arg_no_legend = false; static bool arg_no_pager = false; static bool arg_no_wtmp = false; +static bool arg_no_sync = false; static bool arg_no_wall = false; static bool arg_no_reload = false; +static bool arg_value = false; static bool arg_show_types = false; static bool arg_ignore_inhibitors = false; static bool arg_dry = false; @@ -100,13 +134,13 @@ static bool arg_quiet = false; static bool arg_full = false; static bool arg_recursive = false; static int arg_force = 0; -static bool arg_ask_password = true; +static bool arg_ask_password = false; static bool arg_runtime = false; static UnitFilePresetMode arg_preset_mode = UNIT_FILE_PRESET_FULL; static char **arg_wall = NULL; static const char *arg_kill_who = NULL; static int arg_signal = SIGTERM; -static const char *arg_root = NULL; +static char *arg_root = NULL; static usec_t arg_when = 0; static enum action { _ACTION_INVALID, @@ -133,31 +167,77 @@ static enum action { _ACTION_MAX } arg_action = ACTION_SYSTEMCTL; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; +static const char *arg_host = NULL; static unsigned arg_lines = 10; static OutputMode arg_output = OUTPUT_SHORT; static bool arg_plain = false; static bool arg_firmware_setup = false; static bool arg_now = false; +static int daemon_reload(int argc, char *argv[], void* userdata); +static int trivial_method(int argc, char *argv[], void *userdata); +static int halt_now(enum action a); +static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state); + static bool original_stdout_is_tty; -static int daemon_reload(sd_bus *bus, char **args); -static int halt_now(enum action a); -static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet); +typedef enum BusFocus { + BUS_FULL, /* The full bus indicated via --system or --user */ + BUS_MANAGER, /* The manager itself, possibly directly, possibly via the bus */ + _BUS_FOCUS_MAX +} BusFocus; + +static sd_bus *busses[_BUS_FOCUS_MAX] = {}; + +static int acquire_bus(BusFocus focus, sd_bus **ret) { + int r; + + assert(focus < _BUS_FOCUS_MAX); + assert(ret); + + /* We only go directly to the manager, if we are using a local transport */ + if (arg_transport != BUS_TRANSPORT_LOCAL) + focus = BUS_FULL; -static char** strv_skip_first(char **strv) { - if (strv_length(strv) > 0) - return strv + 1; - return NULL; + if (!busses[focus]) { + bool user; + + user = arg_scope != UNIT_FILE_SYSTEM; + + if (focus == BUS_MANAGER) + r = bus_connect_transport_systemd(arg_transport, arg_host, user, &busses[focus]); + else + r = bus_connect_transport(arg_transport, arg_host, user, &busses[focus]); + if (r < 0) + return log_error_errno(r, "Failed to connect to bus: %m"); + + (void) sd_bus_set_allow_interactive_authorization(busses[focus], arg_ask_password); + } + + *ret = busses[focus]; + return 0; } -static void pager_open_if_enabled(void) { +static void release_busses(void) { + BusFocus w; - if (arg_no_pager) - return; + for (w = 0; w < _BUS_FOCUS_MAX; w++) + busses[w] = sd_bus_flush_close_unref(busses[w]); +} - pager_open(false); +static int map_string_no_copy(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + char *s; + const char **p = userdata; + int r; + + r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &s); + if (r < 0) + return r; + + if (!isempty(s)) + *p = s; + + return 0; } static void ask_password_agent_open_if_enabled(void) { @@ -197,7 +277,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR | + colors_enabled() * OUTPUT_COLOR | !arg_quiet * OUTPUT_WARN_CUTOFF; } @@ -229,42 +309,10 @@ static int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) return EXIT_FAILURE; } -static void warn_wall(enum action a) { - static const char *table[_ACTION_MAX] = { - [ACTION_HALT] = "The system is going down for system halt NOW!", - [ACTION_REBOOT] = "The system is going down for reboot NOW!", - [ACTION_POWEROFF] = "The system is going down for power-off NOW!", - [ACTION_KEXEC] = "The system is going down for kexec reboot NOW!", - [ACTION_RESCUE] = "The system is going down to rescue mode NOW!", - [ACTION_EMERGENCY] = "The system is going down to emergency mode NOW!", - [ACTION_CANCEL_SHUTDOWN] = "The system shutdown has been cancelled NOW!" - }; - - if (arg_no_wall) - return; - - if (arg_wall) { - _cleanup_free_ char *p; +static bool install_client_side(void) { - p = strv_join(arg_wall, " "); - if (!p) { - log_oom(); - return; - } - - if (*p) { - utmp_wall(p, NULL, NULL, NULL, NULL); - return; - } - } - - if (!table[a]) - return; - - utmp_wall(table[a], NULL, NULL, NULL, NULL); -} - -static bool avoid_bus(void) { + /* Decides when to execute enable/disable/... operations + * client-side rather than server-side. */ if (running_in_chroot() > 0) return true; @@ -278,6 +326,10 @@ static bool avoid_bus(void) { if (arg_scope == UNIT_FILE_GLOBAL) return true; + /* Unsupported environment variable, mostly for debugging purposes */ + if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0) + return true; + return false; } @@ -311,6 +363,8 @@ static int compare_unit_info(const void *a, const void *b) { } static bool output_show_unit(const UnitInfo *u, char **patterns) { + assert(u); + if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) return false; @@ -328,10 +382,21 @@ static bool output_show_unit(const UnitInfo *u, char **patterns) { if (arg_all) return true; + /* Note that '--all' is not purely a state filter, but also a + * filter that hides units that "follow" other units (which is + * used for device units that appear under different names). */ + if (!isempty(u->following)) + return false; + + if (!strv_isempty(arg_states)) + return true; + + /* By default show all units except the ones in inactive + * state and with no pending job */ if (u->job_id > 0) return true; - if (streq(u->active_state, "inactive") || u->following[0]) + if (streq(u->active_state, "inactive")) return false; return true; @@ -430,11 +495,11 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { if (STR_IN_SET(u->load_state, "error", "not-found", "masked") && !arg_plain) { on_loaded = ansi_highlight_red(); on_circle = ansi_highlight_yellow(); - off_loaded = off_circle = ansi_highlight_off(); + off_loaded = off_circle = ansi_normal(); circle = true; } else if (streq(u->active_state, "failed") && !arg_plain) { on_circle = on_active = ansi_highlight_red(); - off_circle = off_active = ansi_highlight_off(); + off_circle = off_active = ansi_normal(); circle = true; } @@ -456,7 +521,7 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { } if (circle_len > 0) - printf("%s%s%s ", on_circle, circle ? draw_special_char(DRAW_BLACK_CIRCLE) : " ", off_circle); + printf("%s%s%s ", on_circle, circle ? special_glyph(BLACK_CIRCLE) : " ", off_circle); printf("%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", on_active, id_len, id, off_active, @@ -481,10 +546,10 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) { "SUB = The low-level unit activation state, values depend on unit type."); puts(job_count ? "JOB = Pending job for the unit.\n" : ""); on = ansi_highlight(); - off = ansi_highlight_off(); + off = ansi_normal(); } else { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } if (arg_all) @@ -508,12 +573,13 @@ static int get_unit_list( int c, sd_bus_message **_reply) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; size_t size = c; int r; UnitInfo u; + bool fallback = false; assert(bus); assert(unit_infos); @@ -525,8 +591,7 @@ static int get_unit_list( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "ListUnitsFiltered"); - + "ListUnitsByPatterns"); if (r < 0) return bus_log_create_error(r); @@ -534,11 +599,37 @@ static int get_unit_list( if (r < 0) return bus_log_create_error(r); + r = sd_bus_message_append_strv(m, patterns); + if (r < 0) + return bus_log_create_error(r); + r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, r)); - return r; + if (r < 0 && (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_ACCESS_DENIED))) { + /* Fallback to legacy ListUnitsFiltered method */ + fallback = true; + log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r)); + m = sd_bus_message_unref(m); + sd_bus_error_free(&error); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitsFiltered"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); } + if (r < 0) + return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); if (r < 0) @@ -547,7 +638,7 @@ static int get_unit_list( while ((r = bus_parse_unit_info(reply, &u)) > 0) { u.machine = machine; - if (!output_show_unit(&u, patterns)) + if (!output_show_unit(&u, fallback ? patterns : NULL)) continue; if (!GREEDY_REALLOC(*unit_infos, size, c+1)) @@ -605,7 +696,7 @@ static int get_unit_list_recursive( r = set_put(replies, reply); if (r < 0) { sd_bus_message_unref(reply); - return r; + return log_oom(); } if (arg_recursive) { @@ -614,15 +705,15 @@ static int get_unit_list_recursive( r = sd_get_machine_names(&machines); if (r < 0) - return r; + return log_error_errno(r, "Failed to get machine names: %m"); STRV_FOREACH(i, machines) { - _cleanup_bus_flush_close_unref_ sd_bus *container = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; int k; r = sd_bus_open_system_machine(&container, *i); if (r < 0) { - log_error_errno(r, "Failed to connect to container %s: %m", *i); + log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i); continue; } @@ -635,7 +726,7 @@ static int get_unit_list_recursive( r = set_put(replies, reply); if (r < 0) { sd_bus_message_unref(reply); - return r; + return log_oom(); } } @@ -653,15 +744,20 @@ static int get_unit_list_recursive( return c; } -static int list_units(sd_bus *bus, char **args) { +static int list_units(int argc, char *argv[], void *userdata) { _cleanup_free_ UnitInfo *unit_infos = NULL; _cleanup_(message_set_freep) Set *replies = NULL; _cleanup_strv_free_ char **machines = NULL; + sd_bus *bus; int r; - pager_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + pager_open(arg_no_pager, false); - r = get_unit_list_recursive(bus, strv_skip_first(args), &unit_infos, &replies, &machines); + r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); if (r < 0) return r; @@ -674,9 +770,13 @@ static int get_triggered_units( const char* path, char*** ret) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; + assert(bus); + assert(path); + assert(ret); + r = sd_bus_get_property_strv( bus, "org.freedesktop.systemd1", @@ -685,9 +785,8 @@ static int get_triggered_units( "Triggers", &error, ret); - if (r < 0) - log_error("Failed to determine triggers: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); return 0; } @@ -697,8 +796,8 @@ static int get_listening( const char* unit_path, char*** listening) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *type, *path; int r, n = 0; @@ -711,10 +810,8 @@ static int get_listening( &error, &reply, "a(ss)"); - if (r < 0) { - log_error("Failed to get list of listening sockets: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); if (r < 0) @@ -837,12 +934,12 @@ static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { } on = ansi_highlight(); - off = ansi_highlight_off(); + off = ansi_normal(); if (!arg_no_legend) printf("\n"); } else { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } if (!arg_no_legend) { @@ -854,7 +951,7 @@ static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { return 0; } -static int list_sockets(sd_bus *bus, char **args) { +static int list_sockets(int argc, char *argv[], void *userdata) { _cleanup_(message_set_freep) Set *replies = NULL; _cleanup_strv_free_ char **machines = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -864,10 +961,15 @@ static int list_sockets(sd_bus *bus, char **args) { unsigned cs = 0; size_t size = 0; int r = 0, n; + sd_bus *bus; - pager_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; - n = get_unit_list_recursive(bus, strv_skip_first(args), &unit_infos, &replies, &machines); + pager_open(arg_no_pager, false); + + n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); if (n < 0) return n; @@ -931,7 +1033,7 @@ static int get_next_elapse( const char *path, dual_timestamp *next) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; dual_timestamp t; int r; @@ -948,10 +1050,8 @@ static int get_next_elapse( &error, 't', &t.monotonic); - if (r < 0) { - log_error("Failed to get next elapsation time: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); r = sd_bus_get_property_trivial( bus, @@ -962,10 +1062,8 @@ static int get_next_elapse( &error, 't', &t.realtime); - if (r < 0) { - log_error("Failed to get next elapsation time: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); *next = t; return 0; @@ -976,7 +1074,7 @@ static int get_last_trigger( const char *path, usec_t *last) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(bus); @@ -992,10 +1090,8 @@ static int get_last_trigger( &error, 't', last); - if (r < 0) { - log_error("Failed to get last trigger time: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); return 0; } @@ -1119,12 +1215,12 @@ static int output_timers_list(struct timer_info *timer_infos, unsigned n) { } on = ansi_highlight(); - off = ansi_highlight_off(); + off = ansi_normal(); if (!arg_no_legend) printf("\n"); } else { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } if (!arg_no_legend) { @@ -1161,7 +1257,7 @@ static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { return next_elapse; } -static int list_timers(sd_bus *bus, char **args) { +static int list_timers(int argc, char *argv[], void *userdata) { _cleanup_(message_set_freep) Set *replies = NULL; _cleanup_strv_free_ char **machines = NULL; _cleanup_free_ struct timer_info *timer_infos = NULL; @@ -1171,11 +1267,16 @@ static int list_timers(sd_bus *bus, char **args) { size_t size = 0; int n, c = 0; dual_timestamp nw; + sd_bus *bus; int r = 0; - pager_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + pager_open(arg_no_pager, false); - n = get_unit_list_recursive(bus, strv_skip_first(args), &unit_infos, &replies, &machines); + n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); if (n < 0) return n; @@ -1247,7 +1348,9 @@ static int compare_unit_file_list(const void *a, const void *b) { return strcasecmp(basename(u->path), basename(v->path)); } -static bool output_show_unit_file(const UnitFileList *u, char **patterns) { +static bool output_show_unit_file(const UnitFileList *u, char **states, char **patterns) { + assert(u); + if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE)) return false; @@ -1262,8 +1365,8 @@ static bool output_show_unit_file(const UnitFileList *u, char **patterns) { return false; } - if (!strv_isempty(arg_states) && - !strv_find(arg_states, unit_file_state_to_string(u->state))) + if (!strv_isempty(states) && + !strv_find(states, unit_file_state_to_string(u->state))) return false; return true; @@ -1291,7 +1394,7 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { } else id_cols = max_id_len; - if (!arg_no_legend) + if (!arg_no_legend && c > 0) printf("%-*s %-*s\n", id_cols, "UNIT FILE", state_cols, "STATE"); @@ -1301,15 +1404,16 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { const char *on, *off; const char *id; - if (u->state == UNIT_FILE_MASKED || - u->state == UNIT_FILE_MASKED_RUNTIME || - u->state == UNIT_FILE_DISABLED || - u->state == UNIT_FILE_INVALID) { + if (IN_SET(u->state, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_DISABLED, + UNIT_FILE_BAD)) { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } else if (u->state == UNIT_FILE_ENABLED) { on = ansi_highlight_green(); - off = ansi_highlight_off(); + off = ansi_normal(); } else on = off = ""; @@ -1326,8 +1430,8 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { printf("\n%u unit files listed.\n", c); } -static int list_unit_files(sd_bus *bus, char **args) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; +static int list_unit_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ UnitFileList *units = NULL; UnitFileList *unit; size_t size = 0; @@ -1335,10 +1439,9 @@ static int list_unit_files(sd_bus *bus, char **args) { const char *state; char *path; int r; + bool fallback = false; - pager_open_if_enabled(); - - if (avoid_bus()) { + if (install_client_side()) { Hashmap *h; UnitFileList *u; Iterator i; @@ -1348,23 +1451,22 @@ static int list_unit_files(sd_bus *bus, char **args) { if (!h) return log_oom(); - r = unit_file_get_list(arg_scope, arg_root, h); + r = unit_file_get_list(arg_scope, arg_root, h, arg_states, strv_skip(argv, 1)); if (r < 0) { unit_file_list_free(h); - log_error_errno(r, "Failed to get unit file list: %m"); - return r; + return log_error_errno(r, "Failed to get unit file list: %m"); } n_units = hashmap_size(h); - units = new(UnitFileList, n_units); - if (!units && n_units > 0) { + units = new(UnitFileList, n_units ?: 1); /* avoid malloc(0) */ + if (!units) { unit_file_list_free(h); return log_oom(); } HASHMAP_FOREACH(u, h, i) { - if (!output_show_unit_file(u, strv_skip_first(args))) + if (!output_show_unit_file(u, NULL, NULL)) continue; units[c++] = *u; @@ -1373,22 +1475,57 @@ static int list_unit_files(sd_bus *bus, char **args) { assert(c <= n_units); hashmap_free(h); + + r = 0; } else { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; - r = sd_bus_call_method( + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( bus, + &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "ListUnitFiles", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Failed to list unit files: %s", bus_error_message(&error, r)); - return r; + "ListUnitFilesByPatterns"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + /* Fallback to legacy ListUnitFiles method */ + fallback = true; + log_debug_errno(r, "Failed to list unit files: %s Falling back to ListUnitsFiles method.", bus_error_message(&error, r)); + m = sd_bus_message_unref(m); + sd_bus_error_free(&error); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitFiles"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); } + if (r < 0) + return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); if (r < 0) @@ -1404,8 +1541,10 @@ static int list_unit_files(sd_bus *bus, char **args) { unit_file_state_from_string(state) }; - if (output_show_unit_file(&units[c], strv_skip_first(args))) - c ++; + if (output_show_unit_file(&units[c], + fallback ? arg_states : NULL, + fallback ? strv_skip(argv, 1) : NULL)) + c++; } if (r < 0) @@ -1416,13 +1555,14 @@ static int list_unit_files(sd_bus *bus, char **args) { return bus_log_parse_error(r); } + pager_open(arg_no_pager, false); + qsort_safe(units, c, sizeof(UnitFileList), compare_unit_file_list); output_unit_file_list(units, c); - if (avoid_bus()) { + if (install_client_side()) for (unit = units; unit < units + c; unit++) free(unit->path); - } return 0; } @@ -1441,7 +1581,7 @@ static int list_dependencies_print(const char *name, int level, unsigned int bra printf("%s...\n",max_len % 2 ? "" : " "); return 0; } - printf("%s", draw_special_char(branches & (1 << i) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); + printf("%s", special_glyph(branches & (1 << i) ? TREE_VERTICAL : TREE_SPACE)); } len += 2; @@ -1450,10 +1590,10 @@ static int list_dependencies_print(const char *name, int level, unsigned int bra return 0; } - printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); + printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH)); } - if (arg_full){ + if (arg_full) { printf("%s\n", name); return 0; } @@ -1470,13 +1610,12 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha static const char *dependencies[_DEPENDENCY_MAX] = { [DEPENDENCY_FORWARD] = "Requires\0" - "RequiresOverridable\0" "Requisite\0" - "RequisiteOverridable\0" "Wants\0" + "ConsistsOf\0" "BindsTo\0", [DEPENDENCY_REVERSE] = "RequiredBy\0" - "RequiredByOverridable\0" + "RequisiteOf\0" "WantedBy\0" "PartOf\0" "BoundBy\0", @@ -1484,8 +1623,8 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha [DEPENDENCY_BEFORE] = "Before\0", }; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_strv_free_ char **ret = NULL; _cleanup_free_ char *path = NULL; int r; @@ -1508,10 +1647,8 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha &error, &reply, "s", "org.freedesktop.systemd1.Unit"); - if (r < 0) { - log_error("Failed to get properties of %s: %s", name, bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); if (r < 0) @@ -1610,12 +1747,29 @@ static int list_dependencies_one( if (arg_plain) printf(" "); else { - int state; + UnitActiveState active_state = _UNIT_ACTIVE_STATE_INVALID; const char *on; - state = check_one_unit(bus, *c, "activating\0active\0reloading\0", true); - on = state > 0 ? ansi_highlight_green() : ansi_highlight_red(); - printf("%s%s%s ", on, draw_special_char(DRAW_BLACK_CIRCLE), ansi_highlight_off()); + (void) get_state_one_unit(bus, *c, &active_state); + + switch (active_state) { + case UNIT_ACTIVE: + case UNIT_RELOADING: + case UNIT_ACTIVATING: + on = ansi_highlight_green(); + break; + + case UNIT_INACTIVE: + case UNIT_DEACTIVATING: + on = ansi_normal(); + break; + + default: + on = ansi_highlight_red(); + break; + } + + printf("%s%s%s ", on, special_glyph(BLACK_CIRCLE), ansi_normal()); } r = list_dependencies_print(*c, level, branches, c[1] == NULL); @@ -1635,16 +1789,15 @@ static int list_dependencies_one( return 0; } -static int list_dependencies(sd_bus *bus, char **args) { +static int list_dependencies(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **units = NULL; _cleanup_free_ char *unit = NULL; const char *u; + sd_bus *bus; int r; - assert(bus); - - if (args[1]) { - r = unit_name_mangle(args[1], UNIT_NAME_NOGLOB, &unit); + if (argv[1]) { + r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &unit); if (r < 0) return log_error_errno(r, "Failed to mangle unit name: %m"); @@ -1652,7 +1805,11 @@ static int list_dependencies(sd_bus *bus, char **args) { } else u = SPECIAL_DEFAULT_TARGET; - pager_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + pager_open(arg_no_pager, false); puts(u); @@ -1679,12 +1836,12 @@ static const struct bus_properties_map machine_info_property_map[] = { }; static void machine_info_clear(struct machine_info *info) { - if (info) { - free(info->name); - free(info->state); - free(info->control_group); - zero(*info); - } + assert(info); + + free(info->name); + free(info->state); + free(info->control_group); + zero(*info); } static void free_machines_list(struct machine_info *machine_infos, int n) { @@ -1709,7 +1866,7 @@ static int compare_machine_info(const void *a, const void *b) { } static int get_machine_properties(sd_bus *bus, struct machine_info *mi) { - _cleanup_bus_flush_close_unref_ sd_bus *container = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; int r; assert(mi); @@ -1743,7 +1900,7 @@ static int get_machine_list( _cleanup_free_ char *hn = NULL; size_t sz = 0; char **i; - int c = 0; + int c = 0, r; hn = gethostname_malloc(); if (!hn) @@ -1761,7 +1918,10 @@ static int get_machine_list( c++; } - sd_get_machine_names(&m); + r = sd_get_machine_names(&m); + if (r < 0) + return log_error_errno(r, "Failed to get machine list: %m"); + STRV_FOREACH(i, m) { _cleanup_free_ char *class = NULL; @@ -1831,31 +1991,31 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n) if (streq_ptr(m->state, "degraded")) { on_state = ansi_highlight_red(); - off_state = ansi_highlight_off(); + off_state = ansi_normal(); circle = true; } else if (!streq_ptr(m->state, "running")) { on_state = ansi_highlight_yellow(); - off_state = ansi_highlight_off(); + off_state = ansi_normal(); circle = true; } if (m->n_failed_units > 0) { on_failed = ansi_highlight_red(); - off_failed = ansi_highlight_off(); + off_failed = ansi_normal(); } else on_failed = off_failed = ""; if (circle_len > 0) - printf("%s%s%s ", on_state, circle ? draw_special_char(DRAW_BLACK_CIRCLE) : " ", off_state); + printf("%s%s%s ", on_state, circle ? special_glyph(BLACK_CIRCLE) : " ", off_state); if (m->is_host) - printf("%-*s (host) %s%-*s%s %s%*u%s %*u\n", + printf("%-*s (host) %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n", (int) (namelen - (sizeof(" (host)")-1)), strna(m->name), on_state, statelen, strna(m->state), off_state, on_failed, failedlen, m->n_failed_units, off_failed, jobslen, m->n_jobs); else - printf("%-*s %s%-*s%s %s%*u%s %*u\n", + printf("%-*s %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n", namelen, strna(m->name), on_state, statelen, strna(m->state), off_state, on_failed, failedlen, m->n_failed_units, off_failed, @@ -1866,23 +2026,26 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n) printf("\n%u machines listed.\n", n); } -static int list_machines(sd_bus *bus, char **args) { +static int list_machines(int argc, char *argv[], void *userdata) { struct machine_info *machine_infos = NULL; + sd_bus *bus; int r; - assert(bus); - if (geteuid() != 0) { log_error("Must be root."); return -EPERM; } - pager_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; - r = get_machine_list(bus, &machine_infos, strv_skip_first(args)); + r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1)); if (r < 0) return r; + pager_open(arg_no_pager, false); + qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info); output_machines_list(machine_infos, r); free_machines_list(machine_infos, r); @@ -1890,20 +2053,26 @@ static int list_machines(sd_bus *bus, char **args) { return 0; } -static int get_default(sd_bus *bus, char **args) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; +static int get_default(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *_path = NULL; const char *path; int r; - if (!bus || avoid_bus()) { + if (install_client_side()) { r = unit_file_get_default(arg_scope, arg_root, &_path); if (r < 0) return log_error_errno(r, "Failed to get default target: %m"); path = _path; + r = 0; } else { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; r = sd_bus_call_method( bus, @@ -1914,10 +2083,8 @@ static int get_default(sd_bus *bus, char **args) { &error, &reply, NULL); - if (r < 0) { - log_error("Failed to get default target: %s", bus_error_message(&error, -r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get default target: %s", bus_error_message(&error, r)); r = sd_bus_message_read(reply, "s", &path); if (r < 0) @@ -1930,44 +2097,36 @@ static int get_default(sd_bus *bus, char **args) { return 0; } -static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_changes) { - unsigned i; - - assert(changes || n_changes == 0); - - for (i = 0; i < n_changes; i++) { - if (changes[i].type == UNIT_FILE_SYMLINK) - log_info("Created symlink from %s to %s.", changes[i].path, changes[i].source); - else - log_info("Removed symlink %s.", changes[i].path); - } -} - -static int set_default(sd_bus *bus, char **args) { +static int set_default(int argc, char *argv[], void *userdata) { _cleanup_free_ char *unit = NULL; UnitFileChange *changes = NULL; unsigned n_changes = 0; int r; - r = unit_name_mangle_with_suffix(args[1], UNIT_NAME_NOGLOB, ".target", &unit); + assert(argc >= 2); + assert(argv); + + r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &unit); if (r < 0) return log_error_errno(r, "Failed to mangle unit name: %m"); - if (!bus || avoid_bus()) { + if (install_client_side()) { r = unit_file_set_default(arg_scope, arg_root, unit, true, &changes, &n_changes); - if (r < 0) - return log_error_errno(r, "Failed to set default target: %m"); + unit_file_dump_changes(r, "set default", changes, n_changes, arg_quiet); - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - r = 0; + if (r > 0) + r = 0; } else { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus *bus; polkit_agent_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + r = sd_bus_call_method( bus, "org.freedesktop.systemd1", @@ -1977,22 +2136,21 @@ static int set_default(sd_bus *bus, char **args) { &error, &reply, "sb", unit, 1); - if (r < 0) { - log_error("Failed to set default target: %s", bus_error_message(&error, -r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r)); - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); if (r < 0) - return r; + goto finish; /* Try to reload if enabled */ if (!arg_no_reload) - r = daemon_reload(bus, args); + r = daemon_reload(argc, argv, userdata); else r = 0; } +finish: unit_file_changes_free(changes, n_changes); return r; @@ -2014,14 +2172,14 @@ static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipp if (n == 0) { if (!arg_no_legend) { on = ansi_highlight_green(); - off = ansi_highlight_off(); + off = ansi_normal(); printf("%sNo jobs %s.%s\n", on, skipped ? "listed" : "running", off); } return; } - pager_open_if_enabled(); + pager_open(arg_no_pager, false); id_len = strlen("JOB"); unit_len = strlen("UNIT"); @@ -2055,7 +2213,7 @@ static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipp if (streq(j->state, "running")) { on = ansi_highlight(); - off = ansi_highlight_off(); + off = ansi_normal(); } else on = off = ""; @@ -2069,7 +2227,7 @@ static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipp if (!arg_no_legend) { on = ansi_highlight(); - off = ansi_highlight_off(); + off = ansi_normal(); printf("\n%s%u jobs listed%s.\n", on, n, off); } @@ -2079,17 +2237,22 @@ static bool output_show_job(struct job_info *job, char **patterns) { return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); } -static int list_jobs(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; +static int list_jobs(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *name, *type, *state, *job_path, *unit_path; _cleanup_free_ struct job_info *jobs = NULL; size_t size = 0; unsigned c = 0; + sd_bus *bus; uint32_t id; int r; bool skipped = false; + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + r = sd_bus_call_method( bus, "org.freedesktop.systemd1", @@ -2099,10 +2262,8 @@ static int list_jobs(sd_bus *bus, char **args) { &error, &reply, NULL); - if (r < 0) { - log_error("Failed to list jobs: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to list jobs: %s", bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, 'a', "(usssoo)"); if (r < 0) @@ -2111,7 +2272,7 @@ static int list_jobs(sd_bus *bus, char **args) { while ((r = sd_bus_message_read(reply, "(usssoo)", &id, &name, &type, &state, &job_path, &unit_path)) > 0) { struct job_info job = { id, name, type, state }; - if (!output_show_job(&job, strv_skip_first(args))) { + if (!output_show_job(&job, strv_skip(argv, 1))) { skipped = true; continue; } @@ -2128,23 +2289,28 @@ static int list_jobs(sd_bus *bus, char **args) { if (r < 0) return bus_log_parse_error(r); + pager_open(arg_no_pager, false); + output_jobs_list(jobs, c, skipped); - return r; + return 0; } -static int cancel_job(sd_bus *bus, char **args) { +static int cancel_job(int argc, char *argv[], void *userdata) { + sd_bus *bus; char **name; int r = 0; - assert(args); + if (argc <= 1) + return trivial_method(argc, argv, userdata); - if (strv_length(args) <= 1) - return daemon_reload(bus, args); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); - STRV_FOREACH(name, args+1) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; uint32_t id; int q; @@ -2162,7 +2328,7 @@ static int cancel_job(sd_bus *bus, char **args) { NULL, "u", id); if (q < 0) { - log_error("Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); + log_error_errno(q, "Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); if (r == 0) r = q; } @@ -2172,7 +2338,7 @@ static int cancel_job(sd_bus *bus, char **args) { } static int need_daemon_reload(sd_bus *bus, const char *unit) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *path; int b, r; @@ -2213,9 +2379,11 @@ static int need_daemon_reload(sd_bus *bus, const char *unit) { } static void warn_unit_file_changed(const char *name) { + assert(name); + log_warning("%sWarning:%s %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", ansi_highlight_red(), - ansi_highlight_off(), + ansi_normal(), name, arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); } @@ -2227,7 +2395,7 @@ static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **un assert(unit_name); assert(unit_path); - STRV_FOREACH(p, lp->unit_path) { + STRV_FOREACH(p, lp->search_path) { _cleanup_free_ char *path; path = path_join(arg_root, *p, unit_name); @@ -2247,7 +2415,6 @@ static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **un static int unit_find_paths( sd_bus *bus, const char *unit_name, - bool avoid_bus_cache, LookupPaths *lp, char **fragment_path, char ***dropin_paths) { @@ -2268,44 +2435,14 @@ static int unit_find_paths( assert(fragment_path); assert(lp); - if (!avoid_bus_cache && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *unit_load_error = NULL; + if (!install_client_side() && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *unit = NULL; - char *unit_load_error_name, *unit_load_error_message; unit = unit_dbus_path_from_name(unit_name); if (!unit) return log_oom(); - if (need_daemon_reload(bus, unit_name) > 0) - warn_unit_file_changed(unit_name); - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "LoadError", - &error, - &unit_load_error, - "(ss)"); - if (r < 0) - return log_error_errno(r, "Failed to get LoadError: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read( - unit_load_error, - "(ss)", - &unit_load_error_name, - &unit_load_error_message); - if (r < 0) - return bus_log_parse_error(r); - - if (!isempty(unit_load_error_name)) { - log_error("Unit %s is not loaded: %s", unit_name, unit_load_error_message); - return 0; - } - r = sd_bus_get_property_string( bus, "org.freedesktop.systemd1", @@ -2358,7 +2495,7 @@ static int unit_find_paths( } if (dropin_paths) { - r = unit_file_find_dropin_paths(lp->unit_path, NULL, names, &dropins); + r = unit_file_find_dropin_paths(lp->search_path, NULL, names, &dropins); if (r < 0) return r; } @@ -2378,74 +2515,78 @@ static int unit_find_paths( r = 1; } - if (r == 0) + if (r == 0 && !arg_force) log_error("No files found for %s.", unit_name); return r; } -static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_free_ char *n = NULL, *state = NULL; +static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *buf = NULL; + UnitActiveState state; const char *path; int r; assert(name); + assert(active_state); - r = unit_name_mangle(name, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - /* We don't use unit_dbus_path_from_name() directly since we - * don't want to load the unit if it isn't loaded. */ - + /* We don't use unit_dbus_path_from_name() directly since we don't want to load the unit unnecessarily, if it + * isn't loaded. */ r = sd_bus_call_method( bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "GetUnit", - NULL, + &error, &reply, - "s", n); + "s", name); if (r < 0) { - if (!quiet) - puts("unknown"); - return 0; - } + if (!sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) + return log_error_errno(r, "Failed to retrieve unit: %s", bus_error_message(&error, r)); - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); + /* The unit is currently not loaded, hence say it's "inactive", since all units that aren't loaded are + * considered inactive. */ + state = UNIT_INACTIVE; - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - NULL, - &state); - if (r < 0) { - if (!quiet) - puts("unknown"); - return 0; - } + } else { + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); - if (!quiet) - puts(state); + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &buf); + if (r < 0) + return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); - return nulstr_contains(good_states, state); + state = unit_active_state_from_string(buf); + if (state == _UNIT_ACTIVE_STATE_INVALID) { + log_error("Invalid unit state '%s' for: %s", buf, name); + return -EINVAL; + } + } + + *active_state = state; + return 0; } static int check_triggering_units( sd_bus *bus, const char *name) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL, *n = NULL, *state = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL, *n = NULL, *load_state = NULL; _cleanup_strv_free_ char **triggered_by = NULL; bool print_warning_label = true; + UnitActiveState active_state; char **i; int r; @@ -2464,13 +2605,11 @@ static int check_triggering_units( "org.freedesktop.systemd1.Unit", "LoadState", &error, - &state); - if (r < 0) { - log_error("Failed to get load state of %s: %s", n, bus_error_message(&error, r)); - return r; - } + &load_state); + if (r < 0) + return log_error_errno(r, "Failed to get load state of %s: %s", n, bus_error_message(&error, r)); - if (streq(state, "masked")) + if (streq(load_state, "masked")) return 0; r = sd_bus_get_property_strv( @@ -2481,17 +2620,15 @@ static int check_triggering_units( "TriggeredBy", &error, &triggered_by); - if (r < 0) { - log_error("Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); STRV_FOREACH(i, triggered_by) { - r = check_one_unit(bus, *i, "active\0reloading\0", true); + r = get_state_one_unit(bus, *i, &active_state); if (r < 0) - return log_error_errno(r, "Failed to check unit: %m"); + return r; - if (r == 0) + if (!IN_SET(active_state, UNIT_ACTIVE, UNIT_RELOADING)) continue; if (print_warning_label) { @@ -2517,6 +2654,7 @@ static const struct { { "try-restart", "TryRestartUnit" }, { "condrestart", "TryRestartUnit" }, { "reload-or-restart", "ReloadOrRestartUnit" }, + { "try-reload-or-restart", "ReloadOrTryRestartUnit" }, { "reload-or-try-restart", "ReloadOrTryRestartUnit" }, { "condreload", "ReloadOrTryRestartUnit" }, { "force-reload", "ReloadOrTryRestartUnit" } @@ -2542,15 +2680,88 @@ static const char *method_to_verb(const char *method) { return "n/a"; } +typedef struct { + sd_bus_slot *match; + sd_event *event; + Set *unit_paths; + bool any_failed; +} WaitContext; + +static void wait_context_free(WaitContext *c) { + c->match = sd_bus_slot_unref(c->match); + c->event = sd_event_unref(c->event); + c->unit_paths = set_free(c->unit_paths); +} + +static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + WaitContext *c = userdata; + const char *path; + int r; + + path = sd_bus_message_get_path(m); + if (!set_contains(c->unit_paths, path)) + return 0; + + /* Check if ActiveState changed to inactive/failed */ + /* (s interface, a{sv} changed_properties, as invalidated_properties) */ + r = sd_bus_message_skip(m, "s"); + if (r < 0) + return bus_log_parse_error(r); + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *s; + bool is_failed; + + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + if (streq(s, "ActiveState")) { + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "s"); + if (r < 0) + return bus_log_parse_error(r); + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + is_failed = streq(s, "failed"); + if (streq(s, "inactive") || is_failed) { + log_debug("%s became %s, dropping from --wait tracking", path, s); + set_remove(c->unit_paths, path); + c->any_failed |= is_failed; + } else + log_debug("ActiveState on %s changed to %s", path, s); + break; /* no need to dissect the rest of the message */ + } else { + /* other property */ + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error(r); + } + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + } + if (r < 0) + return bus_log_parse_error(r); + + if (set_isempty(c->unit_paths)) + sd_event_exit(c->event, EXIT_SUCCESS); + + return 0; +} + static int start_unit_one( sd_bus *bus, const char *method, const char *name, const char *mode, sd_bus_error *error, - BusWaitForJobs *w) { + BusWaitForJobs *w, + WaitContext *wait_context) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *path; int r; @@ -2559,6 +2770,40 @@ static int start_unit_one( assert(mode); assert(error); + if (wait_context) { + _cleanup_free_ char *unit_path = NULL; + const char* mt; + + log_debug("Watching for property changes of %s", name); + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "RefUnit", + error, + NULL, + "s", name); + if (r < 0) + return log_error_errno(r, "Failed to RefUnit %s: %s", name, bus_error_message(error, r)); + + unit_path = unit_dbus_path_from_name(name); + if (!unit_path) + return log_oom(); + + r = set_put_strdup(wait_context->unit_paths, unit_path); + if (r < 0) + return log_error_errno(r, "Failed to add unit path %s to set: %m", unit_path); + + mt = strjoina("type='signal'," + "interface='org.freedesktop.DBus.Properties'," + "path='", unit_path, "'," + "member='PropertiesChanged'"); + r = sd_bus_add_match(bus, &wait_context->match, mt, on_properties_changed, wait_context); + if (r < 0) + return log_error_errno(r, "Failed to add match for PropertiesChanged signal: %m"); + } + log_debug("Calling manager for %s on %s, %s", method, name, mode); r = sd_bus_call_method( @@ -2573,14 +2818,22 @@ static int start_unit_one( if (r < 0) { const char *verb; - if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) - /* There's always a fallback possible for - * legacy actions. */ - return -EADDRNOTAVAIL; + /* There's always a fallback possible for legacy actions. */ + if (arg_action != ACTION_SYSTEMCTL) + return r; verb = method_to_verb(method); log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r)); + + if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && + !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) + log_error("See %s logs and 'systemctl%s status%s %s' for details.", + arg_scope == UNIT_FILE_SYSTEM ? "system" : "user", + arg_scope == UNIT_FILE_SYSTEM ? "" : " --user", + name[0] == '-' ? " --" : "", + name); + return r; } @@ -2602,11 +2855,13 @@ static int start_unit_one( } static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***ret) { - _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; char **name; int r, i; + assert(bus); + assert(ret); + STRV_FOREACH(name, names) { char *t; @@ -2628,19 +2883,27 @@ static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***r /* Query the manager only if any of the names are a glob, since * this is fairly expensive */ if (!strv_isempty(globs)) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; - - if (!bus) - return log_error_errno(EOPNOTSUPP, "Unit name globbing without bus is not implemented."); + size_t allocated, n; r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); if (r < 0) return r; - for (i = 0; i < r; i++) - if (strv_extend(&mangled, unit_infos[i].id) < 0) + n = strv_length(mangled); + allocated = n + 1; + + for (i = 0; i < r; i++) { + if (!GREEDY_REALLOC(mangled, allocated, n+2)) return log_oom(); + + mangled[n] = strdup(unit_infos[i].id); + if (!mangled[n]) + return log_oom(); + + mangled[++n] = NULL; + } } *ret = mangled; @@ -2681,24 +2944,36 @@ static enum action verb_to_action(const char *verb) { return _ACTION_INVALID; } -static int start_unit(sd_bus *bus, char **args) { +static int start_unit(int argc, char *argv[], void *userdata) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; const char *method, *mode, *one_name, *suffix = NULL; _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; + _cleanup_(wait_context_free) WaitContext wait_context = {}; char **name; int r = 0; - assert(bus); + if (arg_wait && !strstr(argv[0], "start")) { + log_error("--wait may only be used with a command that starts units."); + return -EINVAL; + } + + /* we cannot do sender tracking on the private bus, so we need the full + * one for RefUnit to implement --wait */ + r = acquire_bus(arg_wait ? BUS_FULL : BUS_MANAGER, &bus); + if (r < 0) + return r; ask_password_agent_open_if_enabled(); polkit_agent_open_if_enabled(); if (arg_action == ACTION_SYSTEMCTL) { enum action action; - method = verb_to_method(args[0]); - action = verb_to_action(args[0]); - if (streq(args[0], "isolate")) { + method = verb_to_method(argv[0]); + action = verb_to_action(argv[0]); + + if (streq(argv[0], "isolate")) { mode = "isolate"; suffix = ".target"; } else @@ -2718,9 +2993,9 @@ static int start_unit(sd_bus *bus, char **args) { if (one_name) names = strv_new(one_name, NULL); else { - r = expand_names(bus, args + 1, suffix, &names); + r = expand_names(bus, strv_skip(argv, 1), suffix, &names); if (r < 0) - log_error_errno(r, "Failed to expand names: %m"); + return log_error_errno(r, "Failed to expand names: %m"); } if (!arg_no_block) { @@ -2729,19 +3004,57 @@ static int start_unit(sd_bus *bus, char **args) { return log_error_errno(r, "Could not watch jobs: %m"); } + if (arg_wait) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + wait_context.unit_paths = set_new(&string_hash_ops); + if (!wait_context.unit_paths) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + &error, + NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to enable subscription: %s", bus_error_message(&error, r)); + r = sd_event_default(&wait_context.event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + r = sd_bus_attach_event(bus, wait_context.event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + } + STRV_FOREACH(name, names) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int q; - q = start_unit_one(bus, method, *name, mode, &error, w); + q = start_unit_one(bus, method, *name, mode, &error, w, arg_wait ? &wait_context : NULL); if (r >= 0 && q < 0) r = translate_bus_error_to_exit_status(q, &error); } if (!arg_no_block) { - int q; + int q, arg_count = 0; + const char* extra_args[4] = {}; + + if (arg_scope != UNIT_FILE_SYSTEM) + extra_args[arg_count++] = "--user"; + + assert(IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE)); + if (arg_transport == BUS_TRANSPORT_REMOTE) { + extra_args[arg_count++] = "-H"; + extra_args[arg_count++] = arg_host; + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { + extra_args[arg_count++] = "-M"; + extra_args[arg_count++] = arg_host; + } - q = bus_wait_for_jobs(w, arg_quiet); + q = bus_wait_for_jobs(w, arg_quiet, extra_args); if (q < 0) return q; @@ -2752,48 +3065,99 @@ static int start_unit(sd_bus *bus, char **args) { check_triggering_units(bus, *name); } + if (r >= 0 && arg_wait) { + int q; + q = sd_event_loop(wait_context.event); + if (q < 0) + return log_error_errno(q, "Failed to run event loop: %m"); + if (wait_context.any_failed) + r = EXIT_FAILURE; + } + return r; } +static int logind_set_wall_message(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + _cleanup_free_ char *m = NULL; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + m = strv_join(arg_wall, " "); + if (!m) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetWallMessage", + &error, + NULL, + "sb", + m, + !arg_no_wall); + + if (r < 0) + return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r)); + +#endif + return 0; +} + /* Ask systemd-logind, which might grant access to unprivileged users * through PolicyKit */ -static int reboot_with_logind(sd_bus *bus, enum action a) { +static int logind_reboot(enum action a) { #ifdef HAVE_LOGIND - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - const char *method; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *method, *description; + sd_bus *bus; int r; - if (!bus) - return -EIO; - - polkit_agent_open_if_enabled(); + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; switch (a) { case ACTION_REBOOT: method = "Reboot"; + description = "reboot system"; break; case ACTION_POWEROFF: method = "PowerOff"; + description = "power off system"; break; case ACTION_SUSPEND: method = "Suspend"; + description = "suspend system"; break; case ACTION_HIBERNATE: method = "Hibernate"; + description = "hibernate system"; break; case ACTION_HYBRID_SLEEP: method = "HybridSleep"; + description = "put system into hybrid sleep"; break; default: return -EINVAL; } + polkit_agent_open_if_enabled(); + (void) logind_set_wall_message(); + r = sd_bus_call_method( bus, "org.freedesktop.login1", @@ -2804,27 +3168,25 @@ static int reboot_with_logind(sd_bus *bus, enum action a) { NULL, "b", arg_ask_password); if (r < 0) - log_error("Failed to execute operation: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to %s via logind: %s", description, bus_error_message(&error, r)); - return r; + return 0; #else return -ENOSYS; #endif } -static int check_inhibitors(sd_bus *bus, enum action a) { +static int logind_check_inhibitors(enum action a) { #ifdef HAVE_LOGIND - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_strv_free_ char **sessions = NULL; const char *what, *who, *why, *mode; uint32_t uid, pid; + sd_bus *bus; unsigned c = 0; char **s; int r; - if (!bus) - return 0; - if (arg_ignore_inhibitors || arg_force > 0) return 0; @@ -2837,6 +3199,13 @@ static int check_inhibitors(sd_bus *bus, enum action a) { if (!on_tty()) return 0; + if (arg_transport != BUS_TRANSPORT_LOCAL) + return 0; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + r = sd_bus_call_method( bus, "org.freedesktop.login1", @@ -2869,10 +3238,11 @@ static int check_inhibitors(sd_bus *bus, enum action a) { return log_error_errno(ERANGE, "Bad PID %"PRIu32": %m", pid); if (!strv_contains(sv, - a == ACTION_HALT || - a == ACTION_POWEROFF || - a == ACTION_REBOOT || - a == ACTION_KEXEC ? "shutdown" : "sleep")) + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC) ? "shutdown" : "sleep")) continue; get_process_comm(pid, &comm); @@ -2901,7 +3271,7 @@ static int check_inhibitors(sd_bus *bus, enum action a) { if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user")) continue; - if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty"))) + if (sd_session_get_type(*s, &type) < 0 || !STR_IN_SET(type, "x11", "tty")) continue; sd_session_get_tty(*s, &tty); @@ -2925,10 +3295,36 @@ static int check_inhibitors(sd_bus *bus, enum action a) { #endif } -static int prepare_firmware_setup(sd_bus *bus) { +static int logind_prepare_firmware_setup(void) { #ifdef HAVE_LOGIND - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetRebootToFirmwareSetup", + &error, + NULL, + "b", true); + if (r < 0) + return log_error_errno(r, "Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r)); + + return 0; +#else + log_error("Cannot remotely indicate to EFI to boot into setup mode."); + return -ENOSYS; #endif +} + +static int prepare_firmware_setup(void) { int r; if (!arg_firmware_setup) @@ -2943,37 +3339,42 @@ static int prepare_firmware_setup(sd_bus *bus) { return r; } -#ifdef HAVE_LOGIND + return logind_prepare_firmware_setup(); +} + +static int set_exit_code(uint8_t code) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + r = sd_bus_call_method( bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "SetRebootToFirmwareSetup", + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetExitCode", &error, NULL, - "b", true); - if (r < 0) { - log_error("Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r)); - return r; - } + "y", code); + if (r < 0) + return log_error_errno(r, "Failed to set exit code: %s", bus_error_message(&error, r)); return 0; -#else - log_error("Cannot remotely indicate to EFI to boot into setup mode."); - return -EINVAL; -#endif } -static int start_special(sd_bus *bus, char **args) { +static int start_special(int argc, char *argv[], void *userdata) { enum action a; int r; - assert(args); + assert(argv); - a = verb_to_action(args[0]); + a = verb_to_action(argv[0]); - r = check_inhibitors(bus, a); + r = logind_check_inhibitors(a); if (r < 0) return r; @@ -2982,102 +3383,149 @@ static int start_special(sd_bus *bus, char **args) { return -EPERM; } - r = prepare_firmware_setup(bus); + r = prepare_firmware_setup(); if (r < 0) return r; - if (a == ACTION_REBOOT && args[1]) { - r = update_reboot_param_file(args[1]); + if (a == ACTION_REBOOT && argc > 1) { + r = update_reboot_parameter_and_warn(argv[1]); + if (r < 0) + return r; + + } else if (a == ACTION_EXIT && argc > 1) { + uint8_t code; + + /* If the exit code is not given on the command line, + * don't reset it to zero: just keep it as it might + * have been set previously. */ + + r = safe_atou8(argv[1], &code); + if (r < 0) + return log_error_errno(r, "Invalid exit code."); + + r = set_exit_code(code); if (r < 0) return r; } if (arg_force >= 2 && - (a == ACTION_HALT || - a == ACTION_POWEROFF || - a == ACTION_REBOOT)) + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT)) return halt_now(a); if (arg_force >= 1 && - (a == ACTION_HALT || - a == ACTION_POWEROFF || - a == ACTION_REBOOT || - a == ACTION_KEXEC || - a == ACTION_EXIT)) - return daemon_reload(bus, args); - - /* first try logind, to allow authentication with polkit */ - if (geteuid() != 0 && - (a == ACTION_POWEROFF || - a == ACTION_REBOOT || - a == ACTION_SUSPEND || - a == ACTION_HIBERNATE || - a == ACTION_HYBRID_SLEEP)) { - r = reboot_with_logind(bus, a); - if (r >= 0 || IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC, + ACTION_EXIT)) + return trivial_method(argc, argv, userdata); + + /* First try logind, to allow authentication with polkit */ + if (IN_SET(a, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_SUSPEND, + ACTION_HIBERNATE, + ACTION_HYBRID_SLEEP)) { + r = logind_reboot(a); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not supported or already in progress */ return r; + + /* On all other errors, try low-level operation */ } - r = start_unit(bus, args); - if (r == EXIT_SUCCESS) - warn_wall(a); + return start_unit(argc, argv, userdata); +} - return r; +static int start_system_special(int argc, char *argv[], void *userdata) { + /* Like start_special above, but raises an error when running in user mode */ + + if (arg_scope != UNIT_FILE_SYSTEM) { + log_error("Bad action for %s mode.", + arg_scope == UNIT_FILE_GLOBAL ? "--global" : "--user"); + return -EINVAL; + } + + return start_special(argc, argv, userdata); } -static int check_unit_generic(sd_bus *bus, int code, const char *good_states, char **args) { +static int check_unit_generic(int code, const UnitActiveState good_states[], int nb_states, char **args) { _cleanup_strv_free_ char **names = NULL; + UnitActiveState active_state; + sd_bus *bus; char **name; - int r; + int r, i; + bool found = false; - assert(bus); - assert(args); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; r = expand_names(bus, args, NULL, &names); if (r < 0) return log_error_errno(r, "Failed to expand names: %m"); STRV_FOREACH(name, names) { - int state; + r = get_state_one_unit(bus, *name, &active_state); + if (r < 0) + return r; - state = check_one_unit(bus, *name, good_states, arg_quiet); - if (state < 0) - return state; - if (state == 0) - r = code; + if (!arg_quiet) + puts(unit_active_state_to_string(active_state)); + + for (i = 0; i < nb_states; ++i) + if (good_states[i] == active_state) + found = true; } - return r; + /* use the given return code for the case that we won't find + * any unit which matches the list */ + return found ? 0 : code; } -static int check_unit_active(sd_bus *bus, char **args) { +static int check_unit_active(int argc, char *argv[], void *userdata) { + const UnitActiveState states[] = { UNIT_ACTIVE, UNIT_RELOADING }; /* According to LSB: 3, "program is not running" */ - return check_unit_generic(bus, 3, "active\0reloading\0", args + 1); + return check_unit_generic(EXIT_PROGRAM_NOT_RUNNING, states, ELEMENTSOF(states), strv_skip(argv, 1)); } -static int check_unit_failed(sd_bus *bus, char **args) { - return check_unit_generic(bus, 1, "failed\0", args + 1); +static int check_unit_failed(int argc, char *argv[], void *userdata) { + const UnitActiveState states[] = { UNIT_FAILED }; + return check_unit_generic(EXIT_PROGRAM_DEAD_AND_PID_EXISTS, states, ELEMENTSOF(states), strv_skip(argv, 1)); } -static int kill_unit(sd_bus *bus, char **args) { +static int kill_unit(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; - char **name; + char *kill_who = NULL, **name; + sd_bus *bus; int r, q; - assert(bus); - assert(args); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); if (!arg_kill_who) arg_kill_who = "all"; - r = expand_names(bus, args + 1, NULL, &names); + /* --fail was specified */ + if (streq(arg_job_mode, "fail")) + kill_who = strjoina(arg_kill_who, "-fail"); + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); if (r < 0) - log_error_errno(r, "Failed to expand names: %m"); + return log_error_errno(r, "Failed to expand names: %m"); STRV_FOREACH(name, names) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; q = sd_bus_call_method( bus, @@ -3087,9 +3535,9 @@ static int kill_unit(sd_bus *bus, char **args) { "KillUnit", &error, NULL, - "ssi", *names, arg_kill_who, arg_signal); + "ssi", *name, kill_who ? kill_who : arg_kill_who, arg_signal); if (q < 0) { - log_error("Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); + log_error_errno(q, "Failed to kill unit %s: %s", *name, bus_error_message(&error, q)); if (r == 0) r = q; } @@ -3176,6 +3624,27 @@ static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) { return 1; } +typedef struct UnitCondition { + char *name; + char *param; + bool trigger; + bool negate; + int tristate; + + LIST_FIELDS(struct UnitCondition, conditions); +} UnitCondition; + +static void unit_condition_free(UnitCondition *c) { + if (!c) + return; + + free(c->name); + free(c->param); + free(c); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(UnitCondition*, unit_condition_free); + typedef struct UnitStatusInfo { const char *id; const char *load_state; @@ -3205,6 +3674,7 @@ typedef struct UnitStatusInfo { usec_t inactive_enter_timestamp; bool need_daemon_reload; + bool transient; /* Service */ pid_t main_pid; @@ -3221,10 +3691,7 @@ typedef struct UnitStatusInfo { usec_t condition_timestamp; bool condition_result; - bool failed_condition_trigger; - bool failed_condition_negate; - const char *failed_condition; - const char *failed_condition_parameter; + LIST_HEAD(UnitCondition, conditions); usec_t assert_timestamp; bool assert_result; @@ -3252,13 +3719,39 @@ typedef struct UnitStatusInfo { /* CGroup */ uint64_t memory_current; + uint64_t memory_low; + uint64_t memory_high; + uint64_t memory_max; + uint64_t memory_swap_max; uint64_t memory_limit; uint64_t cpu_usage_nsec; + uint64_t tasks_current; + uint64_t tasks_max; LIST_HEAD(ExecStatusInfo, exec); } UnitStatusInfo; +static void unit_status_info_free(UnitStatusInfo *info) { + ExecStatusInfo *p; + UnitCondition *c; + + strv_free(info->documentation); + strv_free(info->dropin_paths); + strv_free(info->listen); + + while ((c = info->conditions)) { + LIST_REMOVE(conditions, info->conditions, c); + unit_condition_free(c); + } + + while ((p = info->exec)) { + LIST_REMOVE(exec, info->exec, p); + exec_status_info_free(p); + } +} + static void print_status_info( + sd_bus *bus, UnitStatusInfo *i, bool *ellipsized) { @@ -3269,6 +3762,7 @@ static void print_status_info( char since2[FORMAT_TIMESTAMP_MAX], *s2; const char *path; char **t, **t2; + int r; assert(i); @@ -3277,14 +3771,14 @@ static void print_status_info( if (streq_ptr(i->active_state, "failed")) { active_on = ansi_highlight_red(); - active_off = ansi_highlight_off(); - } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { + active_off = ansi_normal(); + } else if (STRPTR_IN_SET(i->active_state, "active", "reloading")) { active_on = ansi_highlight_green(); - active_off = ansi_highlight_off(); + active_off = ansi_normal(); } else active_on = active_off = ""; - printf("%s%s%s %s", active_on, draw_special_char(DRAW_BLACK_CIRCLE), active_off, strna(i->id)); + printf("%s%s%s %s", active_on, special_glyph(BLACK_CIRCLE), active_off, strna(i->id)); if (i->description && !streq_ptr(i->id, i->description)) printf(" - %s", i->description); @@ -3296,13 +3790,13 @@ static void print_status_info( if (streq_ptr(i->load_state, "error")) { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } else on = off = ""; path = i->source_path ? i->source_path : i->fragment_path; - if (i->load_error) + if (i->load_error != 0) printf(" Loaded: %s%s%s (Reason: %s)\n", on, strna(i->load_state), off, i->load_error); else if (path && !isempty(i->unit_file_state) && !isempty(i->unit_file_preset)) @@ -3318,6 +3812,9 @@ static void print_status_info( printf(" Loaded: %s%s%s\n", on, strna(i->load_state), off); + if (i->transient) + printf("Transient: yes\n"); + if (!strv_isempty(i->dropin_paths)) { _cleanup_free_ char *dir = NULL; bool last = false; @@ -3327,16 +3824,16 @@ static void print_status_info( if (! dir || last) { printf(dir ? " " : " Drop-In: "); - free(dir); - dir = NULL; + dir = mfree(dir); - if (path_get_parent(*dropin, &dir) < 0) { + dir = dirname_malloc(*dropin); + if (!dir) { log_oom(); return; } printf("%s\n %s", dir, - draw_special_char(DRAW_TREE_RIGHT)); + special_glyph(TREE_RIGHT)); } last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir)); @@ -3356,12 +3853,10 @@ static void print_status_info( if (!isempty(i->result) && !streq(i->result, "success")) printf(" (Result: %s)", i->result); - timestamp = (streq_ptr(i->active_state, "active") || - streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : - (streq_ptr(i->active_state, "inactive") || - streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : - streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : - i->active_exit_timestamp; + timestamp = STRPTR_IN_SET(i->active_state, "active", "reloading") ? i->active_enter_timestamp : + STRPTR_IN_SET(i->active_state, "inactive", "failed") ? i->inactive_enter_timestamp : + STRPTR_IN_SET(i->active_state, "activating") ? i->inactive_exit_timestamp : + i->active_exit_timestamp; s1 = format_timestamp_relative(since1, sizeof(since1), timestamp); s2 = format_timestamp(since2, sizeof(since2), timestamp); @@ -3374,19 +3869,28 @@ static void print_status_info( printf("\n"); if (!i->condition_result && i->condition_timestamp > 0) { + UnitCondition *c; + int n = 0; + s1 = format_timestamp_relative(since1, sizeof(since1), i->condition_timestamp); s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp); printf("Condition: start %scondition failed%s at %s%s%s\n", - ansi_highlight_yellow(), ansi_highlight_off(), - s2, s1 ? "; " : "", s1 ? s1 : ""); - if (i->failed_condition_trigger) - printf(" none of the trigger conditions were met\n"); - else if (i->failed_condition) - printf(" %s=%s%s was not met\n", - i->failed_condition, - i->failed_condition_negate ? "!" : "", - i->failed_condition_parameter); + ansi_highlight_yellow(), ansi_normal(), + s2, s1 ? "; " : "", strempty(s1)); + + LIST_FOREACH(conditions, c, i->conditions) + if (c->tristate < 0) + n++; + + LIST_FOREACH(conditions, c, i->conditions) + if (c->tristate < 0) + printf(" %s %s=%s%s%s was not met\n", + --n ? special_glyph(TREE_BRANCH) : special_glyph(TREE_RIGHT), + c->name, + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->param); } if (!i->assert_result && i->assert_timestamp > 0) { @@ -3394,8 +3898,8 @@ static void print_status_info( s2 = format_timestamp(since2, sizeof(since2), i->assert_timestamp); printf(" Assert: start %sassertion failed%s at %s%s%s\n", - ansi_highlight_red(), ansi_highlight_off(), - s2, s1 ? "; " : "", s1 ? s1 : ""); + ansi_highlight_red(), ansi_normal(), + s2, s1 ? "; " : "", strempty(s1)); if (i->failed_assert_trigger) printf(" none of the trigger assertions were met\n"); else if (i->failed_assert) @@ -3432,10 +3936,10 @@ static void print_status_info( argv = strv_join(p->argv, " "); printf(" Process: "PID_FMT" %s=%s ", p->pid, p->name, strna(argv)); - good = is_clean_exit_lsb(p->code, p->status, NULL); + good = is_clean_exit(p->code, p->status, EXIT_CLEAN_DAEMON, NULL); if (!good) { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } else on = off = ""; @@ -3471,7 +3975,7 @@ static void print_status_info( if (i->running) { _cleanup_free_ char *comm = NULL; - get_process_comm(i->main_pid, &comm); + (void) get_process_comm(i->main_pid, &comm); if (comm) printf(" (%s)", comm); } else if (i->exit_code > 0) { @@ -3490,17 +3994,19 @@ static void print_status_info( printf("signal=%s", signal_to_string(i->exit_status)); printf(")"); } - - if (i->control_pid > 0) - printf(";"); } if (i->control_pid > 0) { _cleanup_free_ char *c = NULL; - printf(" %8s: "PID_FMT, i->main_pid ? "" : " Control", i->control_pid); + if (i->main_pid > 0) + fputs("; Control PID: ", stdout); + else + fputs("Cntrl PID: ", stdout); /* if first in column, abbreviated so it fits alignment */ + + printf(PID_FMT, i->control_pid); - get_process_comm(i->control_pid, &c); + (void) get_process_comm(i->control_pid, &c); if (c) printf(" (%s)", c); } @@ -3513,15 +4019,49 @@ static void print_status_info( if (i->status_errno > 0) printf(" Error: %i (%s)\n", i->status_errno, strerror(i->status_errno)); + if (i->tasks_current != (uint64_t) -1) { + printf(" Tasks: %" PRIu64, i->tasks_current); + + if (i->tasks_max != (uint64_t) -1) + printf(" (limit: %" PRIu64 ")\n", i->tasks_max); + else + printf("\n"); + } + if (i->memory_current != (uint64_t) -1) { char buf[FORMAT_BYTES_MAX]; printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); - if (i->memory_limit != (uint64_t) -1) - printf(" (limit: %s)\n", format_bytes(buf, sizeof(buf), i->memory_limit)); - else - printf("\n"); + if (i->memory_low > 0 || i->memory_high != CGROUP_LIMIT_MAX || + i->memory_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX || + i->memory_limit != CGROUP_LIMIT_MAX) { + const char *prefix = ""; + + printf(" ("); + if (i->memory_low > 0) { + printf("%slow: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_low)); + prefix = " "; + } + if (i->memory_high != CGROUP_LIMIT_MAX) { + printf("%shigh: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_high)); + prefix = " "; + } + if (i->memory_max != CGROUP_LIMIT_MAX) { + printf("%smax: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_max)); + prefix = " "; + } + if (i->memory_swap_max != CGROUP_LIMIT_MAX) { + printf("%sswap max: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_swap_max)); + prefix = " "; + } + if (i->memory_limit != CGROUP_LIMIT_MAX) { + printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit)); + prefix = " "; + } + printf(")"); + } + printf("\n"); } if (i->cpu_usage_nsec != (uint64_t) -1) { @@ -3529,23 +4069,25 @@ static void print_status_info( printf(" CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC)); } - if (i->control_group && - (i->main_pid > 0 || i->control_pid > 0 || - ((arg_transport != BUS_TRANSPORT_LOCAL && arg_transport != BUS_TRANSPORT_MACHINE) || cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, i->control_group, false) == 0))) { + if (i->control_group) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + static const char prefix[] = " "; unsigned c; printf(" CGroup: %s\n", i->control_group); - if (arg_transport == BUS_TRANSPORT_LOCAL || arg_transport == BUS_TRANSPORT_MACHINE) { + c = columns(); + if (c > sizeof(prefix) - 1) + c -= sizeof(prefix) - 1; + else + c = 0; + + r = unit_show_processes(bus, i->id, i->control_group, prefix, c, get_output_flags(), &error); + if (r == -EBADR) { unsigned k = 0; pid_t extra[2]; - static const char prefix[] = " "; - c = columns(); - if (c > sizeof(prefix) - 1) - c -= sizeof(prefix) - 1; - else - c = 0; + /* Fallback for older systemd versions where the GetUnitProcesses() call is not yet available */ if (i->main_pid > 0) extra[k++] = i->main_pid; @@ -3553,11 +4095,12 @@ static void print_status_info( if (i->control_pid > 0) extra[k++] = i->control_pid; - show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, false, extra, k, get_output_flags()); - } + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, extra, k, get_output_flags()); + } else if (r < 0) + log_warning_errno(r, "Failed to dump process list, ignoring: %s", bus_error_message(&error, r)); } - if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) { + if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) show_journal_by_unit( stdout, i->id, @@ -3570,7 +4113,6 @@ static void print_status_info( SD_JOURNAL_LOCAL_ONLY, arg_scope == UNIT_FILE_SYSTEM, ellipsized); - } if (i->need_daemon_reload) warn_unit_file_changed(i->id); @@ -3672,6 +4214,8 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo * i->condition_result = b; else if (streq(name, "AssertResult")) i->assert_result = b; + else if (streq(name, "Transient")) + i->transient = b; break; } @@ -3745,8 +4289,20 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo * i->assert_timestamp = (usec_t) u; else if (streq(name, "MemoryCurrent")) i->memory_current = u; + else if (streq(name, "MemoryLow")) + i->memory_low = u; + else if (streq(name, "MemoryHigh")) + i->memory_high = u; + else if (streq(name, "MemoryMax")) + i->memory_max = u; + else if (streq(name, "MemorySwapMax")) + i->memory_swap_max = u; else if (streq(name, "MemoryLimit")) i->memory_limit = u; + else if (streq(name, "TasksCurrent")) + i->tasks_current = u; + else if (streq(name, "TasksMax")) + i->tasks_max = u; else if (streq(name, "CPUUsageNSec")) i->cpu_usage_nsec = u; @@ -3770,13 +4326,13 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo * info->name = strdup(name); if (!info->name) - log_oom(); + return log_oom(); LIST_PREPEND(exec, i->exec, info); info = new0(ExecStatusInfo, 1); if (!info) - log_oom(); + return log_oom(); } if (r < 0) @@ -3836,13 +4392,25 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo * return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { - log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); - if (state < 0 && (!trigger || !i->failed_condition)) { - i->failed_condition = cond; - i->failed_condition_trigger = trigger; - i->failed_condition_negate = negate; - i->failed_condition_parameter = param; - } + _cleanup_(unit_condition_freep) UnitCondition *c = NULL; + + log_debug("%s trigger=%d negate=%d %s →%d", cond, trigger, negate, param, state); + + c = new0(UnitCondition, 1); + if (!c) + return log_oom(); + + c->name = strdup(cond); + c->param = strdup(param); + if (!c->name || !c->param) + return log_oom(); + + c->trigger = trigger; + c->negate = negate; + c->tristate = state; + + LIST_PREPEND(conditions, i->conditions, c); + c = NULL; } if (r < 0) return bus_log_parse_error(r); @@ -3911,6 +4479,14 @@ skip: return 0; } +#define print_prop(name, fmt, ...) \ + do { \ + if (arg_value) \ + printf(fmt "\n", __VA_ARGS__); \ + else \ + printf("%s=" fmt "\n", name, __VA_ARGS__); \ + } while(0) + static int print_property(const char *name, sd_bus_message *m, const char *contents) { int r; @@ -3938,9 +4514,9 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); if (u > 0) - printf("%s=%"PRIu32"\n", name, u); + print_prop(name, "%"PRIu32, u); else if (arg_all) - printf("%s=\n", name); + print_prop(name, "%s", ""); return 0; @@ -3952,7 +4528,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); if (arg_all || !isempty(s)) - printf("%s=%s\n", name, s); + print_prop(name, "%s", s); return 0; @@ -3964,7 +4540,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); if (arg_all || !isempty(a) || !isempty(b)) - printf("%s=%s \"%s\"\n", name, strempty(a), strempty(b)); + print_prop(name, "%s \"%s\"", strempty(a), strempty(b)); return 0; } else if (streq_ptr(name, "SystemCallFilter")) { @@ -3991,8 +4567,10 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte bool first = true; char **i; - fputs(name, stdout); - fputc('=', stdout); + if (!arg_value) { + fputs(name, stdout); + fputc('=', stdout); + } if (!whitelist) fputc('~', stdout); @@ -4024,7 +4602,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(sb)", &path, &ignore)) > 0) - printf("EnvironmentFile=%s (ignore_errors=%s)\n", path, yes_no(ignore)); + print_prop("EnvironmentFile", "%s (ignore_errors=%s)", path, yes_no(ignore)); if (r < 0) return bus_log_parse_error(r); @@ -4043,7 +4621,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) - printf("%s=%s\n", type, path); + print_prop(type, "%s", path); if (r < 0) return bus_log_parse_error(r); @@ -4061,7 +4639,10 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) - printf("Listen%s=%s\n", type, path); + if (arg_value) + puts(path); + else + printf("Listen%s=%s\n", type, path); if (r < 0) return bus_log_parse_error(r); @@ -4082,10 +4663,9 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte while ((r = sd_bus_message_read(m, "(stt)", &base, &value, &next_elapse)) > 0) { char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX]; - printf("%s={ value=%s ; next_elapse=%s }\n", - base, - format_timespan(timespan1, sizeof(timespan1), value, 0), - format_timespan(timespan2, sizeof(timespan2), next_elapse, 0)); + print_prop(base, "{ value=%s ; next_elapse=%s }", + format_timespan(timespan1, sizeof(timespan1), value, 0), + format_timespan(timespan2, sizeof(timespan2), next_elapse, 0)); } if (r < 0) return bus_log_parse_error(r); @@ -4109,18 +4689,18 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte tt = strv_join(info.argv, " "); - printf("%s={ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }\n", - name, - strna(info.path), - strna(tt), - yes_no(info.ignore), - strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), - strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), - info.pid, - sigchld_code_to_string(info.code), - info.status, - info.code == CLD_EXITED ? "" : "/", - strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); + print_prop(name, + "{ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }", + strna(info.path), + strna(tt), + yes_no(info.ignore), + strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), + strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), + info.pid, + sigchld_code_to_string(info.code), + info.status, + info.code == CLD_EXITED ? "" : "/", + strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); free(info.path); strv_free(info.argv); @@ -4141,7 +4721,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(ss)", &path, &rwm)) > 0) - printf("%s=%s %s\n", name, strna(path), strna(rwm)); + print_prop(name, "%s %s", strna(path), strna(rwm)); if (r < 0) return bus_log_parse_error(r); @@ -4151,7 +4731,8 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return 0; - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "BlockIODeviceWeight")) { + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && + STR_IN_SET(name, "IODeviceWeight", "BlockIODeviceWeight")) { const char *path; uint64_t weight; @@ -4160,7 +4741,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(st)", &path, &weight)) > 0) - printf("%s=%s %" PRIu64 "\n", name, strna(path), weight); + print_prop(name, "%s %"PRIu64, strna(path), weight); if (r < 0) return bus_log_parse_error(r); @@ -4170,7 +4751,9 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return 0; - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && + (cgroup_io_limit_type_from_string(name) >= 0 || + STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth"))) { const char *path; uint64_t bandwidth; @@ -4179,7 +4762,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte return bus_log_parse_error(r); while ((r = sd_bus_message_read(m, "(st)", &path, &bandwidth)) > 0) - printf("%s=%s %" PRIu64 "\n", name, strna(path), bandwidth); + print_prop(name, "%s %"PRIu64, strna(path), bandwidth); if (r < 0) return bus_log_parse_error(r); @@ -4193,7 +4776,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte break; } - r = bus_print_property(name, m, arg_all); + r = bus_print_property(name, m, arg_value, arg_all); if (r < 0) return bus_log_parse_error(r); @@ -4213,18 +4796,30 @@ static int show_one( const char *verb, sd_bus *bus, const char *path, + const char *unit, bool show_properties, bool *new_line, bool *ellipsized) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - UnitStatusInfo info = { + static const struct bus_properties_map property_map[] = { + { "LoadState", "s", map_string_no_copy, offsetof(UnitStatusInfo, load_state) }, + { "ActiveState", "s", map_string_no_copy, offsetof(UnitStatusInfo, active_state) }, + {} + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_set_free_ Set *found_properties = NULL; + _cleanup_(unit_status_info_free) UnitStatusInfo info = { .memory_current = (uint64_t) -1, + .memory_high = CGROUP_LIMIT_MAX, + .memory_max = CGROUP_LIMIT_MAX, + .memory_swap_max = CGROUP_LIMIT_MAX, .memory_limit = (uint64_t) -1, .cpu_usage_nsec = (uint64_t) -1, + .tasks_current = (uint64_t) -1, + .tasks_max = (uint64_t) -1, }; - ExecStatusInfo *p; int r; assert(path); @@ -4241,9 +4836,28 @@ static int show_one( &error, &reply, "s", ""); - if (r < 0) { - log_error("Failed to get properties: %s", bus_error_message(&error, r)); - return r; + if (r < 0) + return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r)); + + if (unit) { + r = bus_message_map_all_properties(reply, property_map, &info); + if (r < 0) + return log_error_errno(r, "Failed to map properties: %s", bus_error_message(&error, r)); + + if (streq_ptr(info.load_state, "not-found") && streq_ptr(info.active_state, "inactive")) { + log_full(streq(verb, "status") ? LOG_ERR : LOG_DEBUG, + "Unit %s could not be found.", unit); + + if (streq(verb, "status")) + return EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN; + + if (!streq(verb, "show")) + return -ENOENT; + } + + r = sd_bus_message_rewind(reply, true); + if (r < 0) + return log_error_errno(r, "Failed to rewind: %s", bus_error_message(&error, r)); } r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); @@ -4270,9 +4884,17 @@ static int show_one( if (r < 0) return bus_log_parse_error(r); - if (show_properties) + if (show_properties) { + r = set_ensure_allocated(&found_properties, &string_hash_ops); + if (r < 0) + return log_oom(); + + r = set_put(found_properties, name); + if (r < 0 && r != EEXIST) + return log_oom(); + r = print_property(name, reply, contents); - else + } else r = status_property(name, reply, &info, contents); if (r < 0) return r; @@ -4293,37 +4915,25 @@ static int show_one( return bus_log_parse_error(r); r = 0; + if (show_properties) { + char **pp; + int not_found_level = streq(verb, "show") ? LOG_DEBUG : LOG_WARNING; + + STRV_FOREACH(pp, arg_properties) + if (!set_contains(found_properties, *pp)) { + log_full(not_found_level, "Property %s does not exist.", *pp); + r = -ENXIO; + } - if (!show_properties) { - if (streq(verb, "help")) - show_unit_help(&info); - else - print_status_info(&info, ellipsized); - } - - strv_free(info.documentation); - strv_free(info.dropin_paths); - strv_free(info.listen); - - if (!streq_ptr(info.active_state, "active") && - !streq_ptr(info.active_state, "reloading") && - streq(verb, "status")) { - /* According to LSB: "program not running" */ - /* 0: program is running or service is OK - * 1: program is dead and /run PID file exists - * 2: program is dead and /run/lock lock file exists - * 3: program is not running - * 4: program or service status is unknown - */ - if (info.pid_file && access(info.pid_file, F_OK) == 0) - r = 1; - else - r = 3; - } + } else if (streq(verb, "help")) + show_unit_help(&info); + else if (streq(verb, "status")) { + print_status_info(bus, &info, ellipsized); - while ((p = info.exec)) { - LIST_REMOVE(exec, info.exec, p); - exec_status_info_free(p); + if (info.active_state && !STR_IN_SET(info.active_state, "active", "reloading")) + r = EXIT_PROGRAM_NOT_RUNNING; + else + r = EXIT_PROGRAM_RUNNING_OR_SERVICE_OK; } return r; @@ -4334,8 +4944,8 @@ static int get_unit_dbus_path_by_pid( uint32_t pid, char **unit) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; char *u; int r; @@ -4348,10 +4958,8 @@ static int get_unit_dbus_path_by_pid( &error, &reply, "u", pid); - if (r < 0) { - log_error("Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); r = sd_bus_message_read(reply, "o", &u); if (r < 0) @@ -4372,7 +4980,7 @@ static int show_all( bool *new_line, bool *ellipsized) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; const UnitInfo *u; unsigned c; @@ -4382,7 +4990,7 @@ static int show_all( if (r < 0) return r; - pager_open_if_enabled(); + pager_open(arg_no_pager, false); c = (unsigned) r; @@ -4395,7 +5003,7 @@ static int show_all( if (!p) return log_oom(); - r = show_one(verb, bus, p, show_properties, new_line, ellipsized); + r = show_one(verb, bus, p, u->id, show_properties, new_line, ellipsized); if (r < 0) return r; else if (r > 0 && ret == 0) @@ -4422,27 +5030,29 @@ static int show_system_status(sd_bus *bus) { if (streq_ptr(mi.state, "degraded")) { on = ansi_highlight_red(); - off = ansi_highlight_off(); + off = ansi_normal(); } else if (!streq_ptr(mi.state, "running")) { on = ansi_highlight_yellow(); - off = ansi_highlight_off(); + off = ansi_normal(); } else on = off = ""; - printf("%s%s%s %s\n", on, draw_special_char(DRAW_BLACK_CIRCLE), off, arg_host ? arg_host : hn); + printf("%s%s%s %s\n", on, special_glyph(BLACK_CIRCLE), off, arg_host ? arg_host : hn); printf(" State: %s%s%s\n", on, strna(mi.state), off); - printf(" Jobs: %u queued\n", mi.n_jobs); - printf(" Failed: %u units\n", mi.n_failed_units); + printf(" Jobs: %" PRIu32 " queued\n", mi.n_jobs); + printf(" Failed: %" PRIu32 " units\n", mi.n_failed_units); printf(" Since: %s; %s\n", format_timestamp(since2, sizeof(since2), mi.timestamp), format_timestamp_relative(since1, sizeof(since1), mi.timestamp)); printf(" CGroup: %s\n", mi.control_group ?: "/"); - if (arg_transport == BUS_TRANSPORT_LOCAL || arg_transport == BUS_TRANSPORT_MACHINE) { + if (IN_SET(arg_transport, + BUS_TRANSPORT_LOCAL, + BUS_TRANSPORT_MACHINE)) { static const char prefix[] = " "; unsigned c; @@ -4452,25 +5062,34 @@ static int show_system_status(sd_bus *bus) { else c = 0; - show_cgroup(SYSTEMD_CGROUP_CONTROLLER, strempty(mi.control_group), prefix, c, false, get_output_flags()); + show_cgroup(SYSTEMD_CGROUP_CONTROLLER, strempty(mi.control_group), prefix, c, get_output_flags()); } return 0; } -static int show(sd_bus *bus, char **args) { - bool show_properties, show_status, new_line = false; +static int show(int argc, char *argv[], void *userdata) { + bool show_properties, show_status, show_help, new_line = false; bool ellipsized = false; int r, ret = 0; + sd_bus *bus; - assert(bus); - assert(args); + assert(argv); - show_properties = streq(args[0], "show"); - show_status = streq(args[0], "status"); + show_properties = streq(argv[0], "show"); + show_status = streq(argv[0], "status"); + show_help = streq(argv[0], "help"); - if (show_properties) - pager_open_if_enabled(); + if (show_help && argc <= 1) { + log_error("This command expects one or more unit names. Did you mean --help?"); + return -EINVAL; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + pager_open(arg_no_pager, false); if (show_status) /* Increase max number of open files to 16K if we can, we @@ -4479,24 +5098,22 @@ static int show(sd_bus *bus, char **args) { setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384)); /* If no argument is specified inspect the manager itself */ + if (show_properties && argc <= 1) + return show_one(argv[0], bus, "/org/freedesktop/systemd1", NULL, show_properties, &new_line, &ellipsized); - if (show_properties && strv_length(args) <= 1) - return show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line, &ellipsized); + if (show_status && argc <= 1) { - if (show_status && strv_length(args) <= 1) { - - pager_open_if_enabled(); show_system_status(bus); new_line = true; if (arg_all) - ret = show_all(args[0], bus, false, &new_line, &ellipsized); + ret = show_all(argv[0], bus, false, &new_line, &ellipsized); } else { _cleanup_free_ char **patterns = NULL; char **name; - STRV_FOREACH(name, args + 1) { - _cleanup_free_ char *unit = NULL; + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *path = NULL, *unit = NULL; uint32_t id; if (safe_atou32(*name, &id) < 0) { @@ -4506,20 +5123,23 @@ static int show(sd_bus *bus, char **args) { continue; } else if (show_properties) { /* Interpret as job id */ - if (asprintf(&unit, "/org/freedesktop/systemd1/job/%u", id) < 0) + if (asprintf(&path, "/org/freedesktop/systemd1/job/%u", id) < 0) return log_oom(); } else { /* Interpret as PID */ - r = get_unit_dbus_path_by_pid(bus, id, &unit); + r = get_unit_dbus_path_by_pid(bus, id, &path); if (r < 0) { ret = r; continue; } + + r = unit_name_from_dbus_path(path, &unit); + if (r < 0) + return log_oom(); } - r = show_one(args[0], bus, unit, show_properties, - &new_line, &ellipsized); + r = show_one(argv[0], bus, path, unit, show_properties, &new_line, &ellipsized); if (r < 0) return r; else if (r > 0 && ret == 0) @@ -4531,20 +5151,19 @@ static int show(sd_bus *bus, char **args) { r = expand_names(bus, patterns, NULL, &names); if (r < 0) - log_error_errno(r, "Failed to expand names: %m"); + return log_error_errno(r, "Failed to expand names: %m"); STRV_FOREACH(name, names) { - _cleanup_free_ char *unit; + _cleanup_free_ char *path; - unit = unit_dbus_path_from_name(*name); - if (!unit) + path = unit_dbus_path_from_name(*name); + if (!path) return log_oom(); - r = show_one(args[0], bus, unit, show_properties, - &new_line, &ellipsized); + r = show_one(argv[0], bus, path, *name, show_properties, &new_line, &ellipsized); if (r < 0) return r; - else if (r > 0 && ret == 0) + if (r > 0 && ret == 0) ret = r; } } @@ -4556,34 +5175,6 @@ static int show(sd_bus *bus, char **args) { return ret; } -static int init_home_and_lookup_paths(char **user_home, char **user_runtime, LookupPaths *lp) { - int r; - - assert(user_home); - assert(user_runtime); - assert(lp); - - if (arg_scope == UNIT_FILE_USER) { - r = user_config_home(user_home); - if (r < 0) - return log_error_errno(r, "Failed to query XDG_CONFIG_HOME: %m"); - else if (r == 0) - return log_error_errno(ENOTDIR, "Cannot find units: $XDG_CONFIG_HOME and $HOME are not set."); - - r = user_runtime_dir(user_runtime); - if (r < 0) - return log_error_errno(r, "Failed to query XDG_CONFIG_HOME: %m"); - else if (r == 0) - return log_error_errno(ENOTDIR, "Cannot find units: $XDG_RUNTIME_DIR is not set."); - } - - r = lookup_paths_init_from_scope(lp, arg_scope, arg_root); - if (r < 0) - return log_error_errno(r, "Failed to query unit lookup paths: %m"); - - return 0; -} - static int cat_file(const char *filename, bool newline) { _cleanup_close_ int fd; @@ -4595,46 +5186,45 @@ static int cat_file(const char *filename, bool newline) { newline ? "\n" : "", ansi_highlight_blue(), filename, - ansi_highlight_off()); + ansi_normal()); fflush(stdout); - return copy_bytes(fd, STDOUT_FILENO, (off_t) -1, false); + return copy_bytes(fd, STDOUT_FILENO, (uint64_t) -1, false); } -static int cat(sd_bus *bus, char **args) { - _cleanup_free_ char *user_home = NULL; - _cleanup_free_ char *user_runtime = NULL; +static int cat(int argc, char *argv[], void *userdata) { _cleanup_lookup_paths_free_ LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL; char **name; - bool first = true, avoid_bus_cache; + sd_bus *bus; + bool first = true; int r; - assert(args); - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot remotely cat units"); + log_error("Cannot remotely cat units."); return -EINVAL; } - r = init_home_and_lookup_paths(&user_home, &user_runtime, &lp); + r = lookup_paths_init(&lp, arg_scope, 0, arg_root); + if (r < 0) + return log_error_errno(r, "Failed to determine unit paths: %m"); + + r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) return r; - r = expand_names(bus, args + 1, NULL, &names); + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); if (r < 0) return log_error_errno(r, "Failed to expand names: %m"); - avoid_bus_cache = !bus || avoid_bus(); - - pager_open_if_enabled(); + pager_open(arg_no_pager, false); STRV_FOREACH(name, names) { _cleanup_free_ char *fragment_path = NULL; _cleanup_strv_free_ char **dropin_paths = NULL; char **path; - r = unit_find_paths(bus, *name, avoid_bus_cache, &lp, &fragment_path, &dropin_paths); + r = unit_find_paths(bus, *name, &lp, &fragment_path, &dropin_paths); if (r < 0) return r; else if (r == 0) @@ -4661,13 +5251,17 @@ static int cat(sd_bus *bus, char **args) { return 0; } -static int set_property(sd_bus *bus, char **args) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; +static int set_property(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *n = NULL; - char **i; + sd_bus *bus; int r; + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + polkit_agent_open_if_enabled(); r = sd_bus_message_new_method_call( @@ -4680,7 +5274,7 @@ static int set_property(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = unit_name_mangle(args[1], UNIT_NAME_NOGLOB, &n); + r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &n); if (r < 0) return log_error_errno(r, "Failed to mangle unit name: %m"); @@ -4692,151 +5286,103 @@ static int set_property(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - STRV_FOREACH(i, args + 2) { - r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); - if (r < 0) - return bus_log_create_error(r); - - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - } + r = bus_append_unit_property_assignment_many(m, strv_skip(argv, 2)); + if (r < 0) + return r; r = sd_bus_message_close_container(m); if (r < 0) return bus_log_create_error(r); r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) { - log_error("Failed to set unit properties on %s: %s", n, bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to set unit properties on %s: %s", n, bus_error_message(&error, r)); return 0; } -static int snapshot(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_free_ char *n = NULL, *id = NULL; - const char *path; +static int daemon_reload(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + const char *method; + sd_bus *bus; int r; + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + polkit_agent_open_if_enabled(); - if (strv_length(args) > 1) { - r = unit_name_mangle_with_suffix(args[1], UNIT_NAME_NOGLOB, ".snapshot", &n); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - } else { - n = strdup(""); - if (!n) - return log_oom(); + switch (arg_action) { + + case ACTION_RELOAD: + method = "Reload"; + break; + + case ACTION_REEXEC: + method = "Reexecute"; + break; + + case ACTION_SYSTEMCTL: + method = streq(argv[0], "daemon-reexec") ? "Reexecute" : + /* "daemon-reload" */ "Reload"; + break; + + default: + assert_not_reached("Unexpected action"); } - r = sd_bus_call_method( + r = sd_bus_message_new_method_call( bus, + &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "CreateSnapshot", - &error, - &reply, - "sb", n, false); - if (r < 0) { - log_error("Failed to create snapshot: %s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); + method); if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &id); - if (r < 0) { - log_error("Failed to get ID of snapshot: %s", bus_error_message(&error, r)); - return r; - } - - if (!arg_quiet) - puts(id); - - return 0; -} - -static int delete_snapshot(sd_bus *bus, char **args) { - _cleanup_strv_free_ char **names = NULL; - char **name; - int r; + return bus_log_create_error(r); - assert(args); + /* Note we use an extra-long timeout here. This is because a reload or reexec means generators are rerun which + * are timed out after DEFAULT_TIMEOUT_USEC. Let's use twice that time here, so that the generators can have + * their timeout, and for everything else there's the same time budget in place. */ - polkit_agent_open_if_enabled(); + r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL); - r = expand_names(bus, args + 1, ".snapshot", &names); - if (r < 0) - log_error_errno(r, "Failed to expand names: %m"); + /* On reexecution, we expect a disconnect, not a reply */ + if (IN_SET(r, -ETIMEDOUT, -ECONNRESET) && streq(method, "Reexecute")) + r = 0; - STRV_FOREACH(name, names) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - int q; + if (r < 0 && arg_action == ACTION_SYSTEMCTL) + return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r)); - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "RemoveSnapshot", - &error, - NULL, - "s", *name); - if (q < 0) { - log_error("Failed to remove snapshot %s: %s", *name, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } + /* Note that for the legacy commands (i.e. those with action != ACTION_SYSTEMCTL) we support fallbacks to the + * old ways of doing things, hence don't log any error in that case here. */ - return r; + return r < 0 ? r : 0; } -static int daemon_reload(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; +static int trivial_method(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *method; + sd_bus *bus; int r; - polkit_agent_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; - if (arg_action == ACTION_RELOAD) - method = "Reload"; - else if (arg_action == ACTION_REEXEC) - method = "Reexecute"; - else { - assert(arg_action == ACTION_SYSTEMCTL); + polkit_agent_open_if_enabled(); - method = - streq(args[0], "clear-jobs") || - streq(args[0], "cancel") ? "ClearJobs" : - streq(args[0], "daemon-reexec") ? "Reexecute" : - streq(args[0], "reset-failed") ? "ResetFailed" : - streq(args[0], "halt") ? "Halt" : - streq(args[0], "poweroff") ? "PowerOff" : - streq(args[0], "reboot") ? "Reboot" : - streq(args[0], "kexec") ? "KExec" : - streq(args[0], "exit") ? "Exit" : - /* "daemon-reload" */ "Reload"; - } + method = + streq(argv[0], "clear-jobs") || + streq(argv[0], "cancel") ? "ClearJobs" : + streq(argv[0], "reset-failed") ? "ResetFailed" : + streq(argv[0], "halt") ? "Halt" : + streq(argv[0], "reboot") ? "Reboot" : + streq(argv[0], "kexec") ? "KExec" : + streq(argv[0], "exit") ? "Exit" : + /* poweroff */ "PowerOff"; r = sd_bus_call_method( bus, @@ -4847,36 +5393,36 @@ static int daemon_reload(sd_bus *bus, char **args) { &error, NULL, NULL); - if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) - /* There's always a fallback possible for - * legacy actions. */ - r = -EADDRNOTAVAIL; - else if ((r == -ETIMEDOUT || r == -ECONNRESET) && streq(method, "Reexecute")) - /* On reexecution, we expect a disconnect, not a - * reply */ - r = 0; - else if (r < 0) - log_error("Failed to execute operation: %s", bus_error_message(&error, r)); + if (r < 0 && arg_action == ACTION_SYSTEMCTL) + return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); + + /* Note that for the legacy commands (i.e. those with action != ACTION_SYSTEMCTL) we support fallbacks to the + * old ways of doing things, hence don't log any error in that case here. */ return r < 0 ? r : 0; } -static int reset_failed(sd_bus *bus, char **args) { +static int reset_failed(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; char **name; int r, q; - if (strv_length(args) <= 1) - return daemon_reload(bus, args); + if (argc <= 1) + return trivial_method(argc, argv, userdata); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); - r = expand_names(bus, args + 1, NULL, &names); + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); if (r < 0) - log_error_errno(r, "Failed to expand names: %m"); + return log_error_errno(r, "Failed to expand names: %m"); STRV_FOREACH(name, names) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; q = sd_bus_call_method( bus, @@ -4888,7 +5434,7 @@ static int reset_failed(sd_bus *bus, char **args) { NULL, "s", *name); if (q < 0) { - log_error("Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); + log_error_errno(q, "Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); if (r == 0) r = q; } @@ -4897,13 +5443,18 @@ static int reset_failed(sd_bus *bus, char **args) { return r; } -static int show_environment(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; +static int show_environment(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *text; + sd_bus *bus; int r; - pager_open_if_enabled(); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + pager_open(arg_no_pager, false); r = sd_bus_get_property( bus, @@ -4914,10 +5465,8 @@ static int show_environment(sd_bus *bus, char **args) { &error, &reply, "as"); - if (r < 0) { - log_error("Failed to get environment: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get environment: %s", bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s"); if (r < 0) @@ -4935,23 +5484,27 @@ static int show_environment(sd_bus *bus, char **args) { return 0; } -static int switch_root(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; +static int switch_root(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *cmdline_init = NULL; const char *root, *init; - unsigned l; + sd_bus *bus; int r; - l = strv_length(args); - if (l < 2 || l > 3) { + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot switch root remotely."); + return -EINVAL; + } + + if (argc < 2 || argc > 3) { log_error("Wrong number of arguments."); return -EINVAL; } - root = args[1]; + root = argv[1]; - if (l >= 3) - init = args[2]; + if (argc >= 3) + init = argv[2]; else { r = parse_env_file("/proc/cmdline", WHITESPACE, "init", &cmdline_init, @@ -4962,9 +5515,7 @@ static int switch_root(sd_bus *bus, char **args) { init = cmdline_init; } - if (isempty(init)) - init = NULL; - + init = empty_to_null(init); if (init) { const char *root_systemd_path = NULL, *root_init_path = NULL; @@ -4977,6 +5528,10 @@ static int switch_root(sd_bus *bus, char **args) { init = NULL; } + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + log_debug("Switching root - root: %s; init: %s", root, strna(init)); r = sd_bus_call_method( @@ -4988,26 +5543,29 @@ static int switch_root(sd_bus *bus, char **args) { &error, NULL, "ss", root, init); - if (r < 0) { - log_error("Failed to switch root: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r)); return 0; } -static int set_environment(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; +static int set_environment(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *method; + sd_bus *bus; int r; - assert(bus); - assert(args); + assert(argc > 1); + assert(argv); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); - method = streq(args[0], "set-environment") + method = streq(argv[0], "set-environment") ? "SetEnvironment" : "UnsetEnvironment"; @@ -5021,26 +5579,26 @@ static int set_environment(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append_strv(m, args + 1); + r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); if (r < 0) return bus_log_create_error(r); r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) { - log_error("Failed to set environment: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to set environment: %s", bus_error_message(&error, r)); return 0; } -static int import_environment(sd_bus *bus, char **args) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; +static int import_environment(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + sd_bus *bus; int r; - assert(bus); - assert(args); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); @@ -5054,7 +5612,7 @@ static int import_environment(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - if (strv_isempty(args + 1)) + if (argc < 2) r = sd_bus_message_append_strv(m, environ); else { char **a, **b; @@ -5063,7 +5621,7 @@ static int import_environment(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - STRV_FOREACH(a, args + 1) { + STRV_FOREACH(a, strv_skip(argv, 1)) { if (!env_name_is_valid(*a)) { log_error("Not a valid environment variable name: %s", *a); @@ -5091,10 +5649,8 @@ static int import_environment(sd_bus *bus, char **args) { return bus_log_create_error(r); r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) { - log_error("Failed to import environment: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to import environment: %s", bus_error_message(&error, r)); return 0; } @@ -5103,35 +5659,45 @@ static int enable_sysv_units(const char *verb, char **args) { int r = 0; #if defined(HAVE_SYSV_COMPAT) - unsigned f = 0; _cleanup_lookup_paths_free_ LookupPaths paths = {}; + unsigned f = 0; + + /* Processes all SysV units, and reshuffles the array so that afterwards only the native units remain */ if (arg_scope != UNIT_FILE_SYSTEM) return 0; - if (!streq(verb, "enable") && - !streq(verb, "disable") && - !streq(verb, "is-enabled")) + if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0) return 0; - /* Processes all SysV units, and reshuffles the array so that - * afterwards only the native units remain */ + if (!STR_IN_SET(verb, + "enable", + "disable", + "is-enabled")) + return 0; - r = lookup_paths_init(&paths, MANAGER_SYSTEM, false, arg_root, NULL, NULL, NULL); + r = lookup_paths_init(&paths, arg_scope, LOOKUP_PATHS_EXCLUDE_GENERATED, arg_root); if (r < 0) return r; r = 0; while (args[f]) { - const char *name; + + const char *argv[] = { + ROOTLIBEXECDIR "/systemd-sysv-install", + NULL, + NULL, + NULL, + NULL, + }; + _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL; bool found_native = false, found_sysv; + siginfo_t status; + const char *name; unsigned c = 1; - const char *argv[6] = { ROOTLIBEXECDIR "/systemd-sysv-install", NULL, NULL, NULL, NULL }; - char **k; - int j; pid_t pid; - siginfo_t status; + int j; name = args[f++]; @@ -5141,21 +5707,13 @@ static int enable_sysv_units(const char *verb, char **args) { if (path_is_absolute(name)) continue; - STRV_FOREACH(k, paths.unit_path) { - _cleanup_free_ char *path = NULL; - - path = path_join(arg_root, *k, name); - if (!path) - return log_oom(); + j = unit_file_exists(arg_scope, &paths, name); + if (j < 0 && !IN_SET(j, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) + return log_error_errno(j, "Failed to lookup unit file state: %m"); + found_native = j != 0; - found_native = access(path, F_OK) >= 0; - if (found_native) - break; - } - - /* If we have both a native unit and a SysV script, - * enable/disable them both (below); for is-enabled, prefer the - * native unit */ + /* If we have both a native unit and a SysV script, enable/disable them both (below); for is-enabled, + * prefer the native unit */ if (found_native && streq(verb, "is-enabled")) continue; @@ -5168,10 +5726,12 @@ static int enable_sysv_units(const char *verb, char **args) { if (!found_sysv) continue; - if (found_native) - log_info("Synchronizing state of %s with SysV init with %s...", name, argv[0]); - else - log_info("%s is not a native service, redirecting to systemd-sysv-install", name); + if (!arg_quiet) { + if (found_native) + log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]); + else + log_info("%s is not a native service, redirecting to systemd-sysv-install.", name); + } if (!isempty(arg_root)) argv[c++] = q = strappend("--root=", arg_root); @@ -5184,7 +5744,7 @@ static int enable_sysv_units(const char *verb, char **args) { if (!l) return log_oom(); - log_info("Executing %s", l); + log_info("Executing: %s", l); pid = fork(); if (pid < 0) @@ -5196,15 +5756,13 @@ static int enable_sysv_units(const char *verb, char **args) { (void) reset_signal_mask(); execv(argv[0], (char**) argv); - log_error("Failed to execute %s: %m", argv[0]); + log_error_errno(errno, "Failed to execute %s: %m", argv[0]); _exit(EXIT_FAILURE); } j = wait_for_terminate(pid, &status); - if (j < 0) { - log_error_errno(r, "Failed to wait for child: %m"); - return j; - } + if (j < 0) + return log_error_errno(j, "Failed to wait for child: %m"); if (status.si_code == CLD_EXITED) { if (streq(verb, "is-enabled")) { @@ -5218,9 +5776,11 @@ static int enable_sysv_units(const char *verb, char **args) { } } else if (status.si_status != 0) - return -EINVAL; - } else + return -EBADE; /* We don't warn here, under the assumption the script already showed an explanation */ + } else { + log_error("Unexpected waitid() result."); return -EPROTO; + } if (found_native) continue; @@ -5272,18 +5832,82 @@ static int mangle_names(char **original_names, char ***mangled_names) { return 0; } -static int enable_unit(sd_bus *bus, char **args) { +static int normalize_names(char **names, bool warn_if_path) { + char **u; + bool was_path = false; + + STRV_FOREACH(u, names) { + int r; + + if (!is_path(*u)) + continue; + + r = free_and_strdup(u, basename(*u)); + if (r < 0) + return log_error_errno(r, "Failed to normalize unit file path: %m"); + + was_path = true; + } + + if (warn_if_path && was_path) + log_warning("Warning: Can't execute disable on the unit file path. Proceeding with the unit name."); + + return 0; +} + +static int unit_exists(const char *unit) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL; + static const struct bus_properties_map property_map[] = { + { "LoadState", "s", map_string_no_copy, offsetof(UnitStatusInfo, load_state) }, + { "ActiveState", "s", map_string_no_copy, offsetof(UnitStatusInfo, active_state)}, + {}, + }; + UnitStatusInfo info = {}; + sd_bus *bus; + int r; + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &reply, + "s", ""); + if (r < 0) + return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r)); + + r = bus_message_map_all_properties(reply, property_map, &info); + if (r < 0) + return log_error_errno(r, "Failed to map properties: %s", bus_error_message(&error, r)); + + return !streq_ptr(info.load_state, "not-found") || !streq_ptr(info.active_state, "inactive"); +} + +static int enable_unit(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; - const char *verb = args[0]; + const char *verb = argv[0]; UnitFileChange *changes = NULL; unsigned n_changes = 0; int carries_install_info = -1; + bool ignore_carries_install_info = arg_quiet; int r; - if (!args[1]) + if (!argv[1]) return 0; - r = mangle_names(args+1, &names); + r = mangle_names(strv_skip(argv, 1), &names); if (r < 0) return r; @@ -5291,12 +5915,20 @@ static int enable_unit(sd_bus *bus, char **args) { if (r < 0) return r; - /* If the operation was fully executed by the SysV compat, - * let's finish early */ - if (strv_isempty(names)) - return 0; + /* If the operation was fully executed by the SysV compat, let's finish early */ + if (strv_isempty(names)) { + if (arg_no_reload || install_client_side()) + return 0; + return daemon_reload(argc, argv, userdata); + } + + if (streq(verb, "disable")) { + r = normalize_names(names, true); + if (r < 0) + return r; + } - if (!bus || avoid_bus()) { + if (install_client_side()) { if (streq(verb, "enable")) { r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); carries_install_info = r; @@ -5309,29 +5941,38 @@ static int enable_unit(sd_bus *bus, char **args) { r = unit_file_link(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); else if (streq(verb, "preset")) { r = unit_file_preset(arg_scope, arg_runtime, arg_root, names, arg_preset_mode, arg_force, &changes, &n_changes); - carries_install_info = r; } else if (streq(verb, "mask")) r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); else if (streq(verb, "unmask")) r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + else if (streq(verb, "revert")) + r = unit_file_revert(arg_scope, arg_root, names, &changes, &n_changes); else assert_not_reached("Unknown verb"); - if (r < 0) { - log_error_errno(r, "Operation failed: %m"); + unit_file_dump_changes(r, verb, changes, n_changes, arg_quiet); + if (r < 0) goto finish; - } - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - r = 0; } else { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *m = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - int expect_carries_install_info = false; - bool send_force = true, send_preset_mode = false; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool expect_carries_install_info = false; + bool send_runtime = true, send_force = true, send_preset_mode = false; const char *method; + sd_bus *bus; + + if (STR_IN_SET(verb, "mask", "unmask")) { + r = unit_exists(*names); + if (r < 0) + return r; + if (r == 0) + log_notice("Unit %s does not exist, proceeding anyway.", *names); + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); @@ -5355,11 +5996,15 @@ static int enable_unit(sd_bus *bus, char **args) { method = "PresetUnitFiles"; expect_carries_install_info = true; + ignore_carries_install_info = true; } else if (streq(verb, "mask")) method = "MaskUnitFiles"; else if (streq(verb, "unmask")) { method = "UnmaskUnitFiles"; send_force = false; + } else if (streq(verb, "revert")) { + method = "RevertUnitFiles"; + send_runtime = send_force = false; } else assert_not_reached("Unknown verb"); @@ -5383,9 +6028,11 @@ static int enable_unit(sd_bus *bus, char **args) { return bus_log_create_error(r); } - r = sd_bus_message_append(m, "b", arg_runtime); - if (r < 0) - return bus_log_create_error(r); + if (send_runtime) { + r = sd_bus_message_append(m, "b", arg_runtime); + if (r < 0) + return bus_log_create_error(r); + } if (send_force) { r = sd_bus_message_append(m, "b", arg_force); @@ -5394,10 +6041,8 @@ static int enable_unit(sd_bus *bus, char **args) { } r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to execute operation: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to %s unit: %s", verb, bus_error_message(&error, r)); if (expect_carries_install_info) { r = sd_bus_message_read(reply, "b", &carries_install_info); @@ -5407,36 +6052,44 @@ static int enable_unit(sd_bus *bus, char **args) { r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); if (r < 0) - return r; + goto finish; /* Try to reload if enabled */ if (!arg_no_reload) - r = daemon_reload(bus, args); + r = daemon_reload(argc, argv, userdata); else r = 0; } - if (carries_install_info == 0) - log_warning("The unit files have no [Install] section. They are not meant to be enabled\n" - "using systemctl.\n" + if (carries_install_info == 0 && !ignore_carries_install_info) + log_warning("The unit files have no installation config (WantedBy, RequiredBy, Also, Alias\n" + "settings in the [Install] section, and DefaultInstance for template units).\n" + "This means they are not meant to be enabled using systemctl.\n" "Possible reasons for having this kind of units are:\n" "1) A unit may be statically enabled by being symlinked from another unit's\n" " .wants/ or .requires/ directory.\n" "2) A unit's purpose may be to act as a helper for some other unit which has\n" " a requirement dependency on it.\n" "3) A unit may be started when needed via activation (socket, path, timer,\n" - " D-Bus, udev, scripted systemctl call, ...).\n"); + " D-Bus, udev, scripted systemctl call, ...).\n" + "4) In case of template units, the unit is meant to be enabled with some\n" + " instance name specified."); - if (arg_now && n_changes > 0 && STR_IN_SET(args[0], "enable", "disable", "mask")) { + if (arg_now && n_changes > 0 && STR_IN_SET(argv[0], "enable", "disable", "mask")) { char *new_args[n_changes + 2]; + sd_bus *bus; unsigned i; - new_args[0] = streq(args[0], "enable") ? (char *)"start" : (char *)"stop"; + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + goto finish; + + new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop"); for (i = 0; i < n_changes; i++) new_args[i + 1] = basename(changes[i].path); new_args[i + 1] = NULL; - r = start_unit(bus, new_args); + r = start_unit(strv_length(new_args), new_args, userdata); } finish: @@ -5445,21 +6098,23 @@ finish: return r; } -static int add_dependency(sd_bus *bus, char **args) { +static int add_dependency(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; _cleanup_free_ char *target = NULL; - const char *verb = args[0]; + const char *verb = argv[0]; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; UnitDependency dep; int r = 0; - if (!args[1]) + if (!argv[1]) return 0; - r = unit_name_mangle_with_suffix(args[1], UNIT_NAME_NOGLOB, ".target", &target); + r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &target); if (r < 0) return log_error_errno(r, "Failed to mangle unit name: %m"); - r = mangle_names(args+2, &names); + r = mangle_names(strv_skip(argv, 2), &names); if (r < 0) return r; @@ -5470,23 +6125,20 @@ static int add_dependency(sd_bus *bus, char **args) { else assert_not_reached("Unknown verb"); - if (!bus || avoid_bus()) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - + if (install_client_side()) { r = unit_file_add_dependency(arg_scope, arg_runtime, arg_root, names, target, dep, arg_force, &changes, &n_changes); + unit_file_dump_changes(r, "add dependency on", changes, n_changes, arg_quiet); - if (r < 0) - return log_error_errno(r, "Can't add dependency: %m"); - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - unit_file_changes_free(changes, n_changes); - + if (r > 0) + r = 0; } else { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *m = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); @@ -5509,45 +6161,46 @@ static int add_dependency(sd_bus *bus, char **args) { return bus_log_create_error(r); r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to execute operation: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r)); - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); if (r < 0) - return r; + goto finish; - if (!arg_no_reload) - r = daemon_reload(bus, args); - else + if (arg_no_reload) { r = 0; + goto finish; + } + + r = daemon_reload(argc, argv, userdata); } +finish: + unit_file_changes_free(changes, n_changes); + return r; } -static int preset_all(sd_bus *bus, char **args) { +static int preset_all(int argc, char *argv[], void *userdata) { UnitFileChange *changes = NULL; unsigned n_changes = 0; int r; - if (!bus || avoid_bus()) { - + if (install_client_side()) { r = unit_file_preset_all(arg_scope, arg_runtime, arg_root, arg_preset_mode, arg_force, &changes, &n_changes); - if (r < 0) { - log_error_errno(r, "Operation failed: %m"); - goto finish; - } - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - r = 0; + unit_file_dump_changes(r, "preset", changes, n_changes, arg_quiet); + if (r > 0) + r = 0; } else { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; polkit_agent_open_if_enabled(); @@ -5563,19 +6216,19 @@ static int preset_all(sd_bus *bus, char **args) { unit_file_preset_mode_to_string(arg_preset_mode), arg_runtime, arg_force); - if (r < 0) { - log_error("Failed to execute operation: %s", bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r)); - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); if (r < 0) - return r; + goto finish; - if (!arg_no_reload) - r = daemon_reload(bus, args); - else + if (arg_no_reload) { r = 0; + goto finish; + } + + r = daemon_reload(argc, argv, userdata); } finish: @@ -5584,46 +6237,55 @@ finish: return r; } -static int unit_is_enabled(sd_bus *bus, char **args) { +static int unit_is_enabled(int argc, char *argv[], void *userdata) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_strv_free_ char **names = NULL; bool enabled; char **name; int r; - r = mangle_names(args+1, &names); + r = mangle_names(strv_skip(argv, 1), &names); if (r < 0) return r; - r = enable_sysv_units(args[0], names); + r = enable_sysv_units(argv[0], names); if (r < 0) return r; enabled = r > 0; - if (!bus || avoid_bus()) { + if (install_client_side()) { STRV_FOREACH(name, names) { UnitFileState state; - state = unit_file_get_state(arg_scope, arg_root, *name); - if (state < 0) + r = unit_file_get_state(arg_scope, arg_root, *name, &state); + if (r < 0) return log_error_errno(state, "Failed to get unit file state for %s: %m", *name); - if (state == UNIT_FILE_ENABLED || - state == UNIT_FILE_ENABLED_RUNTIME || - state == UNIT_FILE_STATIC || - state == UNIT_FILE_INDIRECT) + if (IN_SET(state, + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_INDIRECT, + UNIT_FILE_GENERATED)) enabled = true; if (!arg_quiet) puts(unit_file_state_to_string(state)); } + r = 0; } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + STRV_FOREACH(name, names) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *s; r = sd_bus_call_method( @@ -5635,16 +6297,14 @@ static int unit_is_enabled(sd_bus *bus, char **args) { &error, &reply, "s", *name); - if (r < 0) { - log_error("Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r)); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r)); r = sd_bus_message_read(reply, "s", &s); if (r < 0) return bus_log_parse_error(r); - if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect")) + if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect", "generated")) enabled = true; if (!arg_quiet) @@ -5652,13 +6312,24 @@ static int unit_is_enabled(sd_bus *bus, char **args) { } } - return !enabled; + return enabled ? EXIT_SUCCESS : EXIT_FAILURE; } -static int is_system_running(sd_bus *bus, char **args) { +static int is_system_running(int argc, char *argv[], void *userdata) { _cleanup_free_ char *state = NULL; + sd_bus *bus; int r; + if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) { + if (!arg_quiet) + puts("offline"); + return EXIT_FAILURE; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + r = sd_bus_get_property_string( bus, "org.freedesktop.systemd1", @@ -5680,7 +6351,7 @@ static int is_system_running(sd_bus *bus, char **args) { } static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) { - char *t; + _cleanup_free_ char *t = NULL; int r; assert(new_path); @@ -5692,70 +6363,51 @@ static int create_edit_temp_file(const char *new_path, const char *original_path return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); r = mkdir_parents(new_path, 0755); - if (r < 0) { - log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); - free(t); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); r = copy_file(original_path, t, 0, 0644, 0); if (r == -ENOENT) { + r = touch(t); - if (r < 0) { - log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); - free(t); - return r; - } - } else if (r < 0) { - log_error_errno(r, "Failed to copy \"%s\" to \"%s\": %m", original_path, t); - free(t); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); + + } else if (r < 0) + return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path); *ret_tmp_fn = t; + t = NULL; return 0; } -static int get_file_to_edit(const char *name, const char *user_home, const char *user_runtime, char **ret_path) { - _cleanup_free_ char *path = NULL, *path2 = NULL, *run = NULL; +static int get_file_to_edit( + const LookupPaths *paths, + const char *name, + char **ret_path) { - switch (arg_scope) { - case UNIT_FILE_SYSTEM: - path = path_join(arg_root, SYSTEM_CONFIG_UNIT_PATH, name); - if (arg_runtime) - run = path_join(arg_root, "/run/systemd/system/", name); - break; - case UNIT_FILE_GLOBAL: - path = path_join(arg_root, USER_CONFIG_UNIT_PATH, name); - if (arg_runtime) - run = path_join(arg_root, "/run/systemd/user/", name); - break; - case UNIT_FILE_USER: - assert(user_home); - assert(user_runtime); - - path = path_join(arg_root, user_home, name); - if (arg_runtime) { - path2 = path_join(arg_root, USER_CONFIG_UNIT_PATH, name); - if (!path2) - return log_oom(); - run = path_join(arg_root, user_runtime, name); - } - break; - default: - assert_not_reached("Invalid scope"); - } - if (!path || (arg_runtime && !run)) + _cleanup_free_ char *path = NULL, *run = NULL; + + assert(name); + assert(ret_path); + + path = strjoin(paths->persistent_config, "/", name, NULL); + if (!path) return log_oom(); if (arg_runtime) { - if (access(path, F_OK) >= 0) - return log_error_errno(EEXIST, "Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", - run, path); - if (path2 && access(path2, F_OK) >= 0) - return log_error_errno(EEXIST, "Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", - run, path2); + run = strjoin(paths->runtime_config, "/", name, NULL); + if (!run) + return log_oom(); + } + + if (arg_runtime) { + if (access(path, F_OK) >= 0) { + log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path); + return -EEXIST; + } + *ret_path = run; run = NULL; } else { @@ -5766,17 +6418,22 @@ static int get_file_to_edit(const char *name, const char *user_home, const char return 0; } -static int unit_file_create_dropin(const char *unit_name, const char *user_home, const char *user_runtime, char **ret_new_path, char **ret_tmp_path) { - char *tmp_new_path, *ending; - char *tmp_tmp_path; +static int unit_file_create_new( + const LookupPaths *paths, + const char *unit_name, + const char *suffix, + char **ret_new_path, + char **ret_tmp_path) { + + char *tmp_new_path, *tmp_tmp_path, *ending; int r; assert(unit_name); assert(ret_new_path); assert(ret_tmp_path); - ending = strjoina(unit_name, ".d/override.conf"); - r = get_file_to_edit(ending, user_home, user_runtime, &tmp_new_path); + ending = strjoina(unit_name, suffix); + r = get_file_to_edit(paths, ending, &tmp_new_path); if (r < 0) return r; @@ -5793,15 +6450,13 @@ static int unit_file_create_dropin(const char *unit_name, const char *user_home, } static int unit_file_create_copy( + const LookupPaths *paths, const char *unit_name, const char *fragment_path, - const char *user_home, - const char *user_runtime, char **ret_new_path, char **ret_tmp_path) { - char *tmp_new_path; - char *tmp_tmp_path; + char *tmp_new_path, *tmp_tmp_path; int r; assert(fragment_path); @@ -5809,7 +6464,7 @@ static int unit_file_create_copy( assert(ret_new_path); assert(ret_tmp_path); - r = get_file_to_edit(unit_name, user_home, user_runtime, &tmp_new_path); + r = get_file_to_edit(paths, unit_name, &tmp_new_path); if (r < 0) return r; @@ -5824,13 +6479,12 @@ static int unit_file_create_copy( if (response != 'y') { log_warning("%s ignored", unit_name); free(tmp_new_path); - return -1; + return -EKEYREJECTED; } } r = create_edit_temp_file(tmp_new_path, fragment_path, &tmp_tmp_path); if (r < 0) { - log_error_errno(r, "Failed to create temporary file for \"%s\": %m", tmp_new_path); free(tmp_new_path); return r; } @@ -5848,10 +6502,8 @@ static int run_editor(char **paths) { assert(paths); pid = fork(); - if (pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - return -errno; - } + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); if (pid == 0) { const char **args; @@ -5909,7 +6561,7 @@ static int run_editor(char **paths) { * failing. */ if (errno != ENOENT) { - log_error("Failed to execute %s: %m", editor); + log_error_errno(errno, "Failed to execute %s: %m", editor); _exit(EXIT_FAILURE); } } @@ -5922,75 +6574,79 @@ static int run_editor(char **paths) { if (r < 0) return log_error_errno(r, "Failed to wait for child: %m"); - return r; + return 0; } static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { - _cleanup_free_ char *user_home = NULL; - _cleanup_free_ char *user_runtime = NULL; _cleanup_lookup_paths_free_ LookupPaths lp = {}; - bool avoid_bus_cache; char **name; int r; assert(names); assert(paths); - r = init_home_and_lookup_paths(&user_home, &user_runtime, &lp); + r = lookup_paths_init(&lp, arg_scope, 0, arg_root); if (r < 0) return r; - avoid_bus_cache = !bus || avoid_bus(); - STRV_FOREACH(name, names) { - _cleanup_free_ char *path = NULL; - char *new_path, *tmp_path; + _cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL; - r = unit_find_paths(bus, *name, avoid_bus_cache, &lp, &path, NULL); + r = unit_find_paths(bus, *name, &lp, &path, NULL); if (r < 0) return r; - else if (r == 0) - return -ENOENT; - else if (!path) { - // FIXME: support units with path==NULL (no FragmentPath) - log_error("No fragment exists for %s.", *name); - return -ENOENT; + else if (!arg_force) { + if (r == 0) { + log_error("Run 'systemctl edit --force %s' to create a new unit.", *name); + return -ENOENT; + } else if (!path) { + // FIXME: support units with path==NULL (no FragmentPath) + log_error("No fragment exists for %s.", *name); + return -ENOENT; + } } - if (arg_full) - r = unit_file_create_copy(*name, path, user_home, user_runtime, &new_path, &tmp_path); - else - r = unit_file_create_dropin(*name, user_home, user_runtime, &new_path, &tmp_path); + if (path) { + if (arg_full) + r = unit_file_create_copy(&lp, *name, path, &new_path, &tmp_path); + else + r = unit_file_create_new(&lp, *name, ".d/override.conf", &new_path, &tmp_path); + } else + r = unit_file_create_new(&lp, *name, NULL, &new_path, &tmp_path); if (r < 0) return r; r = strv_push_pair(paths, new_path, tmp_path); if (r < 0) return log_oom(); + new_path = tmp_path = NULL; } return 0; } -static int edit(sd_bus *bus, char **args) { +static int edit(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **names = NULL; _cleanup_strv_free_ char **paths = NULL; char **original, **tmp; + sd_bus *bus; int r; - assert(args); - if (!on_tty()) { - log_error("Cannot edit units if not on a tty"); + log_error("Cannot edit units if not on a tty."); return -EINVAL; } if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot remotely edit units"); + log_error("Cannot edit units remotely."); return -EINVAL; } - r = expand_names(bus, args + 1, NULL, &names); + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); if (r < 0) return log_error_errno(r, "Failed to expand names: %m"); @@ -6006,13 +6662,14 @@ static int edit(sd_bus *bus, char **args) { goto end; STRV_FOREACH_PAIR(original, tmp, paths) { - /* If the temporary file is empty we ignore it. - * It's useful if the user wants to cancel its modification + /* If the temporary file is empty we ignore it. It's + * useful if the user wants to cancel its modification */ if (null_or_empty_path(*tmp)) { - log_warning("Editing \"%s\" canceled: temporary file is empty", *original); + log_warning("Editing \"%s\" canceled: temporary file is empty.", *original); continue; } + r = rename(*tmp, *original); if (r < 0) { r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original); @@ -6020,19 +6677,36 @@ static int edit(sd_bus *bus, char **args) { } } - if (!arg_no_reload && bus && !avoid_bus()) - r = daemon_reload(bus, args); + r = 0; + + if (!arg_no_reload && !install_client_side()) + r = daemon_reload(argc, argv, userdata); end: - STRV_FOREACH_PAIR(original, tmp, paths) - unlink_noerrno(*tmp); + STRV_FOREACH_PAIR(original, tmp, paths) { + (void) unlink(*tmp); + + /* Removing empty dropin dirs */ + if (!arg_full) { + _cleanup_free_ char *dir; + + dir = dirname_malloc(*original); + if (!dir) + return log_oom(); + + /* no need to check if the dir is empty, rmdir + * does nothing if it is not the case. + */ + (void) rmdir(dir); + } + } return r; } static void systemctl_help(void) { - pager_open_if_enabled(); + pager_open(arg_no_pager, false); printf("%s [OPTIONS...] {COMMAND} ...\n\n" "Query or send control commands to the systemd manager.\n\n" @@ -6047,21 +6721,23 @@ static void systemctl_help(void) { " -t --type=TYPE List units of a particular type\n" " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" " -p --property=NAME Show only properties by this name\n" - " -a --all Show all loaded units/properties, including dead/empty\n" - " ones. To list all units installed on the system, use\n" - " the 'list-unit-files' command instead.\n" + " -a --all Show all properties/all units currently in memory,\n" + " including dead/empty ones. To list all units installed on\n" + " the system, use the 'list-unit-files' command instead.\n" " -l --full Don't ellipsize unit names on output\n" " -r --recursive Show unit list of host and local containers\n" " --reverse Show reverse dependencies with 'list-dependencies'\n" " --job-mode=MODE Specify how to deal with already queued jobs, when\n" " queueing a new job\n" " --show-types When showing sockets, explicitly show their type\n" + " --value When showing properties, only print the value\n" " -i --ignore-inhibitors\n" " When shutting down or sleeping, ignore inhibitors\n" " --kill-who=WHO Who to send signal to\n" " -s --signal=SIGNAL Which signal to send\n" " --now Start or stop unit in addition to enabling or disabling it\n" " -q --quiet Suppress output\n" + " --wait For (re)start, wait until service stopped again\n" " --no-block Do not wait until operation finished\n" " --no-wall Don't send wall message before halt/power-off/reboot\n" " --no-reload Don't reload daemon after en-/dis-abling unit files\n" @@ -6076,15 +6752,17 @@ static void systemctl_help(void) { " --preset-mode= Apply only enable, only disable, or all presets\n" " --root=PATH Enable unit files in the specified root directory\n" " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-iso,\n" - " short-precise, short-monotonic, verbose,\n" - " export, json, json-pretty, json-sse, cat)\n" + " -o --output=STRING Change journal output mode (short, short-precise,\n" + " short-iso, short-full, short-monotonic, short-unix,\n" + " verbose, export, json, json-pretty, json-sse, cat)\n" " --firmware-setup Tell the firmware to show the setup menu on next boot\n" " --plain Print unit dependencies as a list instead of a tree\n\n" "Unit Commands:\n" - " list-units [PATTERN...] List loaded units\n" - " list-sockets [PATTERN...] List loaded sockets ordered by address\n" - " list-timers [PATTERN...] List loaded timers ordered by next elapse\n" + " list-units [PATTERN...] List units currently in memory\n" + " list-sockets [PATTERN...] List socket units currently in memory, ordered\n" + " by address\n" + " list-timers [PATTERN...] List timer units currently in memory, ordered\n" + " by next elapse\n" " start NAME... Start (activate) one or more units\n" " stop NAME... Stop (deactivate) one or more units\n" " reload NAME... Reload one or more units\n" @@ -6092,8 +6770,8 @@ static void systemctl_help(void) { " try-restart NAME... Restart one or more units if active\n" " reload-or-restart NAME... Reload one or more units if possible,\n" " otherwise start or restart\n" - " reload-or-try-restart NAME... Reload one or more units if possible,\n" - " otherwise restart if active\n" + " try-reload-or-restart NAME... If active, reload one or more units,\n" + " if supported, otherwise restart\n" " isolate NAME Start one unit and stop all others\n" " kill NAME... Send signal to processes of a unit\n" " is-active PATTERN... Check whether units are active\n" @@ -6111,7 +6789,7 @@ static void systemctl_help(void) { " unit is required or wanted\n\n" "Unit File Commands:\n" " list-unit-files [PATTERN...] List installed unit files\n" - " enable NAME... Enable one or more unit files\n" + " enable [NAME...|PATH...] Enable one or more unit files\n" " disable NAME... Disable one or more unit files\n" " reenable NAME... Reenable one or more unit files\n" " preset NAME... Enable/disable one or more unit files\n" @@ -6123,6 +6801,8 @@ static void systemctl_help(void) { " unmask NAME... Unmask one or more units\n" " link PATH... Link one or more units files into\n" " the search path\n" + " revert NAME... Revert one or more unit files to vendor\n" + " version\n" " add-wants TARGET NAME... Add 'Wants' dependency for the target\n" " on specified one or more units\n" " add-requires TARGET NAME... Add 'Requires' dependency for the target\n" @@ -6135,9 +6815,6 @@ static void systemctl_help(void) { "Job Commands:\n" " list-jobs [PATTERN...] List jobs\n" " cancel [JOB...] Cancel all, one, or more jobs\n\n" - "Snapshot Commands:\n" - " snapshot [NAME] Create a snapshot\n" - " delete NAME... Remove one or more snapshots\n\n" "Environment Commands:\n" " show-environment Dump environment\n" " set-environment NAME=VALUE... Set one or more environment variables\n" @@ -6155,7 +6832,7 @@ static void systemctl_help(void) { " poweroff Shut down and power-off the system\n" " reboot [ARG] Shut down and reboot the system\n" " kexec Shut down and reboot the system with kexec\n" - " exit Request user instance exit\n" + " exit [EXIT_CODE] Request user instance or container exit\n" " switch-root ROOT [INIT] Change to a different root file system\n" " suspend Suspend the system\n" " hibernate Hibernate the system\n" @@ -6219,15 +6896,85 @@ static void runlevel_help(void) { static void help_types(void) { int i; - const char *t; if (!arg_no_legend) puts("Available unit types:"); - for (i = 0; i < _UNIT_TYPE_MAX; i++) { - t = unit_type_to_string(i); - if (t) - puts(t); - } + for (i = 0; i < _UNIT_TYPE_MAX; i++) + puts(unit_type_to_string(i)); +} + +static void help_states(void) { + int i; + + if (!arg_no_legend) + puts("Available unit load states:"); + for (i = 0; i < _UNIT_LOAD_STATE_MAX; i++) + puts(unit_load_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable unit active states:"); + for (i = 0; i < _UNIT_ACTIVE_STATE_MAX; i++) + puts(unit_active_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable automount unit substates:"); + for (i = 0; i < _AUTOMOUNT_STATE_MAX; i++) + puts(automount_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable busname unit substates:"); + for (i = 0; i < _BUSNAME_STATE_MAX; i++) + puts(busname_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable device unit substates:"); + for (i = 0; i < _DEVICE_STATE_MAX; i++) + puts(device_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable mount unit substates:"); + for (i = 0; i < _MOUNT_STATE_MAX; i++) + puts(mount_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable path unit substates:"); + for (i = 0; i < _PATH_STATE_MAX; i++) + puts(path_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable scope unit substates:"); + for (i = 0; i < _SCOPE_STATE_MAX; i++) + puts(scope_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable service unit substates:"); + for (i = 0; i < _SERVICE_STATE_MAX; i++) + puts(service_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable slice unit substates:"); + for (i = 0; i < _SLICE_STATE_MAX; i++) + puts(slice_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable socket unit substates:"); + for (i = 0; i < _SOCKET_STATE_MAX; i++) + puts(socket_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable swap unit substates:"); + for (i = 0; i < _SWAP_STATE_MAX; i++) + puts(swap_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable target unit substates:"); + for (i = 0; i < _TARGET_STATE_MAX; i++) + puts(target_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable timer unit substates:"); + for (i = 0; i < _TIMER_STATE_MAX; i++) + puts(timer_state_to_string(i)); } static int systemctl_parse_argv(int argc, char *argv[]) { @@ -6240,6 +6987,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_SHOW_TYPES, ARG_IRREVERSIBLE, ARG_IGNORE_DEPENDENCIES, + ARG_VALUE, ARG_VERSION, ARG_USER, ARG_SYSTEM, @@ -6261,6 +7009,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_PRESET_MODE, ARG_FIRMWARE_SETUP, ARG_NOW, + ARG_MESSAGE, + ARG_WAIT, }; static const struct option options[] = { @@ -6280,9 +7030,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ { "ignore-inhibitors", no_argument, NULL, 'i' }, + { "value", no_argument, NULL, ARG_VALUE }, { "user", no_argument, NULL, ARG_USER }, { "system", no_argument, NULL, ARG_SYSTEM }, { "global", no_argument, NULL, ARG_GLOBAL }, + { "wait", no_argument, NULL, ARG_WAIT }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, @@ -6305,14 +7057,19 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, { "now", no_argument, NULL, ARG_NOW }, + { "message", required_argument, NULL, ARG_MESSAGE }, {} }; - int c; + const char *p; + int c, r; assert(argc >= 0); assert(argv); + /* we default to allowing interactive authorization only in systemctl (not in the legacy commands) */ + arg_ask_password = true; + while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:ir", options, NULL)) >= 0) switch (c) { @@ -6322,20 +7079,24 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return 0; case ARG_VERSION: - puts(PACKAGE_STRING); - puts(SYSTEMD_FEATURES); - return 0; + return version(); case 't': { - const char *word, *state; - size_t size; + if (isempty(optarg)) { + log_error("--type requires arguments."); + return -EINVAL; + } - FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) { - _cleanup_free_ char *type; + p = optarg; + for (;;) { + _cleanup_free_ char *type = NULL; - type = strndup(word, size); - if (!type) - return -ENOMEM; + r = extract_first_word(&p, &type, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse type: %s", optarg); + + if (r == 0) + break; if (streq(type, "help")) { help_types(); @@ -6343,7 +7104,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } if (unit_type_from_string(type) >= 0) { - if (strv_push(&arg_types, type)) + if (strv_push(&arg_types, type) < 0) return log_oom(); type = NULL; continue; @@ -6353,7 +7114,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { * load states, but let's support this * in --types= too for compatibility * with old versions */ - if (unit_load_state_from_string(optarg) >= 0) { + if (unit_load_state_from_string(type) >= 0) { if (strv_push(&arg_states, type) < 0) return log_oom(); type = NULL; @@ -6376,18 +7137,21 @@ static int systemctl_parse_argv(int argc, char *argv[]) { if (!arg_properties) return log_oom(); } else { - const char *word, *state; - size_t size; + p = optarg; + for (;;) { + _cleanup_free_ char *prop = NULL; - FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) { - char *prop; + r = extract_first_word(&p, &prop, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse property: %s", optarg); - prop = strndup(word, size); - if (!prop) - return log_oom(); + if (r == 0) + break; - if (strv_consume(&arg_properties, prop) < 0) + if (strv_push(&arg_properties, prop) < 0) return log_oom(); + + prop = NULL; } } @@ -6419,6 +7183,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_show_types = true; break; + case ARG_VALUE: + arg_value = true; + break; + case ARG_JOB_MODE: arg_job_mode = optarg; break; @@ -6447,6 +7215,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_scope = UNIT_FILE_GLOBAL; break; + case ARG_WAIT: + arg_wait = true; + break; + case ARG_NO_BLOCK: arg_no_block = true; break; @@ -6464,7 +7236,9 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case ARG_ROOT: - arg_root = optarg; + r = parse_path_argument_and_warn(optarg, false, &arg_root); + if (r < 0) + return r; break; case 'l': @@ -6482,11 +7256,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case ARG_FORCE: - arg_force ++; + arg_force++; break; case 'f': - arg_force ++; + arg_force++; break; case ARG_NO_RELOAD: @@ -6498,7 +7272,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case 's': - if ((arg_signal = signal_from_string_try_harder(optarg)) < 0) { + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { log_error("Failed to parse signal string %s.", optarg); return -EINVAL; } @@ -6550,18 +7325,31 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case ARG_STATE: { - const char *word, *state; - size_t size; + if (isempty(optarg)) { + log_error("--signal requires arguments."); + return -EINVAL; + } - FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) { - char *s; + p = optarg; + for (;;) { + _cleanup_free_ char *s = NULL; - s = strndup(word, size); - if (!s) - return log_oom(); + r = extract_first_word(&p, &s, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse signal: %s", optarg); - if (strv_consume(&arg_states, s) < 0) + if (r == 0) + break; + + if (streq(s, "help")) { + help_states(); + return 0; + } + + if (strv_push(&arg_states, s) < 0) return log_oom(); + + s = NULL; } break; } @@ -6589,6 +7377,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_now = true; break; + case ARG_MESSAGE: + if (strv_extend(&arg_wall, optarg) < 0) + return log_oom(); + break; + case '?': return -EINVAL; @@ -6601,6 +7394,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (arg_wait && arg_no_block) { + log_error("--wait may not be combined with --no-block."); + return -EINVAL; + } + return 1; } @@ -6621,6 +7419,7 @@ static int halt_parse_argv(int argc, char *argv[]) { { "force", no_argument, NULL, 'f' }, { "wtmp-only", no_argument, NULL, 'w' }, { "no-wtmp", no_argument, NULL, 'd' }, + { "no-sync", no_argument, NULL, 'n' }, { "no-wall", no_argument, NULL, ARG_NO_WALL }, {} }; @@ -6666,13 +7465,16 @@ static int halt_parse_argv(int argc, char *argv[]) { arg_no_wtmp = true; break; + case 'n': + arg_no_sync = true; + break; + case ARG_NO_WALL: arg_no_wall = true; break; case 'i': case 'h': - case 'n': /* Compatibility nops */ break; @@ -6684,7 +7486,7 @@ static int halt_parse_argv(int argc, char *argv[]) { } if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { - r = update_reboot_param_file(argc == optind + 1 ? argv[optind] : NULL); + r = update_reboot_parameter_and_warn(argc == optind + 1 ? argv[optind] : NULL); if (r < 0) return r; } else if (optind < argc) { @@ -6695,7 +7497,7 @@ static int halt_parse_argv(int argc, char *argv[]) { return 1; } -static int parse_time_spec(const char *t, usec_t *_u) { +static int parse_shutdown_time_spec(const char *t, usec_t *_u) { assert(t); assert(_u); @@ -6761,12 +7563,13 @@ static int shutdown_parse_argv(int argc, char *argv[]) { {} }; + char **wall = NULL; int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "HPrhkKt:afFc", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "HPrhkKtafFc", options, NULL)) >= 0) switch (c) { case ARG_HELP: @@ -6824,7 +7627,7 @@ static int shutdown_parse_argv(int argc, char *argv[]) { } if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { - r = parse_time_spec(argv[optind], &arg_when); + r = parse_shutdown_time_spec(argv[optind], &arg_when); if (r < 0) { log_error("Failed to parse time specification: %s", argv[optind]); return r; @@ -6834,10 +7637,16 @@ static int shutdown_parse_argv(int argc, char *argv[]) { if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) /* No time argument for shutdown cancel */ - arg_wall = argv + optind; + wall = argv + optind; else if (argc > optind + 1) /* We skip the time argument */ - arg_wall = argv + optind + 1; + wall = argv + optind + 1; + + if (wall) { + arg_wall = strv_copy(wall); + if (!arg_wall) + return log_oom(); + } optind = argc; @@ -6901,8 +7710,7 @@ static int telinit_parse_argv(int argc, char *argv[]) { } if (optind >= argc) { - log_error("%s: required argument missing.", - program_invocation_short_name); + log_error("%s: required argument missing.", program_invocation_short_name); return -EINVAL; } @@ -6927,7 +7735,7 @@ static int telinit_parse_argv(int argc, char *argv[]) { arg_action = table[i].to; - optind ++; + optind++; return 1; } @@ -7018,6 +7826,7 @@ static int parse_argv(int argc, char *argv[]) { return systemctl_parse_argv(argc, argv); } +#ifdef HAVE_SYSV_COMPAT _pure_ static int action_to_runlevel(void) { static const char table[_ACTION_MAX] = { @@ -7035,9 +7844,10 @@ _pure_ static int action_to_runlevel(void) { return table[arg_action]; } +#endif static int talk_initctl(void) { - +#ifdef HAVE_SYSV_COMPAT struct init_request request = { .magic = INIT_MAGIC, .sleeptime = 0, @@ -7059,8 +7869,7 @@ static int talk_initctl(void) { if (errno == ENOENT) return 0; - log_error_errno(errno, "Failed to open "INIT_FIFO": %m"); - return -errno; + return log_error_errno(errno, "Failed to open "INIT_FIFO": %m"); } r = loop_write(fd, &request, sizeof(request), false); @@ -7068,180 +7877,94 @@ static int talk_initctl(void) { return log_error_errno(r, "Failed to write to "INIT_FIFO": %m"); return 1; +#else + return 0; +#endif } -static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { - - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(sd_bus *bus, char **args); - const enum { - NOBUS = 1, - FORCE, - } bus; - } verbs[] = { - { "list-units", MORE, 0, list_units }, - { "list-unit-files", MORE, 1, list_unit_files, NOBUS }, - { "list-sockets", MORE, 1, list_sockets }, - { "list-timers", MORE, 1, list_timers }, - { "list-jobs", MORE, 1, list_jobs }, - { "list-machines", MORE, 1, list_machines }, - { "clear-jobs", EQUAL, 1, daemon_reload }, - { "cancel", MORE, 2, cancel_job }, - { "start", MORE, 2, start_unit }, - { "stop", MORE, 2, start_unit }, - { "condstop", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ - { "reload", MORE, 2, start_unit }, - { "restart", MORE, 2, start_unit }, - { "try-restart", MORE, 2, start_unit }, - { "reload-or-restart", MORE, 2, start_unit }, - { "reload-or-try-restart", MORE, 2, start_unit }, - { "force-reload", MORE, 2, start_unit }, /* For compatibility with SysV */ - { "condreload", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ - { "condrestart", MORE, 2, start_unit }, /* For compatibility with RH */ - { "isolate", EQUAL, 2, start_unit }, - { "kill", MORE, 2, kill_unit }, - { "is-active", MORE, 2, check_unit_active }, - { "check", MORE, 2, check_unit_active }, - { "is-failed", MORE, 2, check_unit_failed }, - { "show", MORE, 1, show }, - { "cat", MORE, 2, cat, NOBUS }, - { "status", MORE, 1, show }, - { "help", MORE, 2, show }, - { "snapshot", LESS, 2, snapshot }, - { "delete", MORE, 2, delete_snapshot }, - { "daemon-reload", EQUAL, 1, daemon_reload }, - { "daemon-reexec", EQUAL, 1, daemon_reload }, - { "show-environment", EQUAL, 1, show_environment }, - { "set-environment", MORE, 2, set_environment }, - { "unset-environment", MORE, 2, set_environment }, - { "import-environment", MORE, 1, import_environment}, - { "halt", EQUAL, 1, start_special, FORCE }, - { "poweroff", EQUAL, 1, start_special, FORCE }, - { "reboot", MORE, 1, start_special, FORCE }, - { "kexec", EQUAL, 1, start_special }, - { "suspend", EQUAL, 1, start_special }, - { "hibernate", EQUAL, 1, start_special }, - { "hybrid-sleep", EQUAL, 1, start_special }, - { "default", EQUAL, 1, start_special }, - { "rescue", EQUAL, 1, start_special }, - { "emergency", EQUAL, 1, start_special }, - { "exit", EQUAL, 1, start_special }, - { "reset-failed", MORE, 1, reset_failed }, - { "enable", MORE, 2, enable_unit, NOBUS }, - { "disable", MORE, 2, enable_unit, NOBUS }, - { "is-enabled", MORE, 2, unit_is_enabled, NOBUS }, - { "reenable", MORE, 2, enable_unit, NOBUS }, - { "preset", MORE, 2, enable_unit, NOBUS }, - { "preset-all", EQUAL, 1, preset_all, NOBUS }, - { "mask", MORE, 2, enable_unit, NOBUS }, - { "unmask", MORE, 2, enable_unit, NOBUS }, - { "link", MORE, 2, enable_unit, NOBUS }, - { "switch-root", MORE, 2, switch_root }, - { "list-dependencies", LESS, 2, list_dependencies }, - { "set-default", EQUAL, 2, set_default, NOBUS }, - { "get-default", EQUAL, 1, get_default, NOBUS }, - { "set-property", MORE, 3, set_property }, - { "is-system-running", EQUAL, 1, is_system_running }, - { "add-wants", MORE, 3, add_dependency, NOBUS }, - { "add-requires", MORE, 3, add_dependency, NOBUS }, - { "edit", MORE, 2, edit, NOBUS }, +static int systemctl_main(int argc, char *argv[]) { + + static const Verb verbs[] = { + { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_NOCHROOT, list_units }, + { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files }, + { "list-sockets", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_sockets }, + { "list-timers", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_timers }, + { "list-jobs", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_jobs }, + { "list-machines", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_machines }, + { "clear-jobs", VERB_ANY, 1, VERB_NOCHROOT, trivial_method }, + { "cancel", VERB_ANY, VERB_ANY, VERB_NOCHROOT, cancel_job }, + { "start", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "stop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "condstop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ + { "reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "reload-or-try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatbility with old systemctl <= 228 */ + { "try-reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "force-reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with SysV */ + { "condreload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ + { "condrestart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with RH */ + { "isolate", 2, 2, VERB_NOCHROOT, start_unit }, + { "kill", 2, VERB_ANY, VERB_NOCHROOT, kill_unit }, + { "is-active", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, + { "check", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, + { "is-failed", 2, VERB_ANY, VERB_NOCHROOT, check_unit_failed }, + { "show", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "cat", 2, VERB_ANY, VERB_NOCHROOT, cat }, + { "status", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "help", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "daemon-reload", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "daemon-reexec", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "show-environment", VERB_ANY, 1, VERB_NOCHROOT, show_environment }, + { "set-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, + { "unset-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, + { "import-environment", VERB_ANY, VERB_ANY, VERB_NOCHROOT, import_environment }, + { "halt", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "poweroff", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "reboot", VERB_ANY, 2, VERB_NOCHROOT, start_system_special }, + { "kexec", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "suspend", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "hibernate", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "hybrid-sleep", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "default", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "rescue", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "emergency", VERB_ANY, 1, VERB_NOCHROOT, start_system_special }, + { "exit", VERB_ANY, 2, VERB_NOCHROOT, start_special }, + { "reset-failed", VERB_ANY, VERB_ANY, VERB_NOCHROOT, reset_failed }, + { "enable", 2, VERB_ANY, 0, enable_unit }, + { "disable", 2, VERB_ANY, 0, enable_unit }, + { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled }, + { "reenable", 2, VERB_ANY, 0, enable_unit }, + { "preset", 2, VERB_ANY, 0, enable_unit }, + { "preset-all", VERB_ANY, 1, 0, preset_all }, + { "mask", 2, VERB_ANY, 0, enable_unit }, + { "unmask", 2, VERB_ANY, 0, enable_unit }, + { "link", 2, VERB_ANY, 0, enable_unit }, + { "revert", 2, VERB_ANY, 0, enable_unit }, + { "switch-root", 2, VERB_ANY, VERB_NOCHROOT, switch_root }, + { "list-dependencies", VERB_ANY, 2, VERB_NOCHROOT, list_dependencies }, + { "set-default", 2, 2, 0, set_default }, + { "get-default", VERB_ANY, 1, 0, get_default }, + { "set-property", 3, VERB_ANY, VERB_NOCHROOT, set_property }, + { "is-system-running", VERB_ANY, 1, 0, is_system_running }, + { "add-wants", 3, VERB_ANY, 0, add_dependency }, + { "add-requires", 3, VERB_ANY, 0, add_dependency }, + { "edit", 2, VERB_ANY, VERB_NOCHROOT, edit }, {} - }, *verb = verbs; - - int left; - - assert(argc >= 0); - assert(argv); - - left = argc - optind; - - /* Special rule: no arguments (left == 0) means "list-units" */ - if (left > 0) { - if (streq(argv[optind], "help") && !argv[optind+1]) { - log_error("This command expects one or more " - "unit names. Did you mean --help?"); - return -EINVAL; - } - - for (; verb->verb; verb++) - if (streq(argv[optind], verb->verb)) - goto found; - - log_error("Unknown operation '%s'.", argv[optind]); - return -EINVAL; - } -found: - - switch (verb->argc_cmp) { - - case EQUAL: - if (left != verb->argc) { - log_error("Invalid number of arguments."); - return -EINVAL; - } - - break; - - case MORE: - if (left < verb->argc) { - log_error("Too few arguments."); - return -EINVAL; - } - - break; - - case LESS: - if (left > verb->argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Unknown comparison operator."); - } - - /* Require a bus connection for all operations but - * enable/disable */ - if (verb->bus == NOBUS) { - if (!bus && !avoid_bus()) { - log_error_errno(bus_error, "Failed to get D-Bus connection: %m"); - return -EIO; - } - - } else { - if (running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); - return 0; - } - - if ((verb->bus != FORCE || arg_force <= 0) && !bus) { - log_error_errno(bus_error, "Failed to get D-Bus connection: %m"); - return -EIO; - } - } + }; - return verb->dispatch(bus, argv + optind); + return dispatch_verb(argc, argv, verbs, NULL); } -static int reload_with_fallback(sd_bus *bus) { +static int reload_with_fallback(void) { - if (bus) { - /* First, try systemd via D-Bus. */ - if (daemon_reload(bus, NULL) >= 0) - return 0; - } + /* First, try systemd via D-Bus. */ + if (daemon_reload(0, NULL, NULL) >= 0) + return 0; /* Nothing else worked, so let's try signals */ - assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC); + assert(IN_SET(arg_action, ACTION_RELOAD, ACTION_REEXEC)); if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) return log_error_errno(errno, "kill() failed: %m"); @@ -7249,61 +7972,61 @@ static int reload_with_fallback(sd_bus *bus) { return 0; } -static int start_with_fallback(sd_bus *bus) { +static int start_with_fallback(void) { - if (bus) { - /* First, try systemd via D-Bus. */ - if (start_unit(bus, NULL) >= 0) - goto done; - } + /* First, try systemd via D-Bus. */ + if (start_unit(0, NULL, NULL) >= 0) + return 0; - /* Nothing else worked, so let's try - * /dev/initctl */ + /* Nothing else worked, so let's try /dev/initctl */ if (talk_initctl() > 0) - goto done; + return 0; log_error("Failed to talk to init daemon."); return -EIO; - -done: - warn_wall(arg_action); - return 0; } static int halt_now(enum action a) { + int r; /* The kernel will automaticall flush ATA disks and suchlike * on reboot(), but the file systems need to be synce'd * explicitly in advance. */ - sync(); + if (!arg_no_sync) + (void) sync(); /* Make sure C-A-D is handled by the kernel from this point * on... */ - reboot(RB_ENABLE_CAD); + (void) reboot(RB_ENABLE_CAD); switch (a) { case ACTION_HALT: log_info("Halting."); - reboot(RB_HALT_SYSTEM); + (void) reboot(RB_HALT_SYSTEM); return -errno; case ACTION_POWEROFF: log_info("Powering off."); - reboot(RB_POWER_OFF); + (void) reboot(RB_POWER_OFF); return -errno; + case ACTION_KEXEC: case ACTION_REBOOT: { _cleanup_free_ char *param = NULL; - if (read_one_line_file(REBOOT_PARAM_FILE, ¶m) >= 0) { + r = read_one_line_file("/run/systemd/reboot-param", ¶m); + if (r < 0) + log_warning_errno(r, "Failed to read reboot parameter file: %m"); + + if (!isempty(param)) { log_info("Rebooting with argument '%s'.", param); - syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, - LINUX_REBOOT_CMD_RESTART2, param); + (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); + log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); } log_info("Rebooting."); - reboot(RB_AUTOBOOT); + (void) reboot(RB_AUTOBOOT); return -errno; } @@ -7312,106 +8035,101 @@ static int halt_now(enum action a) { } } -static int halt_main(sd_bus *bus) { +static int logind_schedule_shutdown(void) { + +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char date[FORMAT_TIMESTAMP_MAX]; + const char *action; + sd_bus *bus; int r; - r = check_inhibitors(bus, arg_action); + r = acquire_bus(BUS_FULL, &bus); if (r < 0) return r; - if (geteuid() != 0) { - /* Try logind if we are a normal user and no special - * mode applies. Maybe PolicyKit allows us to shutdown - * the machine. */ + switch (arg_action) { + case ACTION_HALT: + action = "halt"; + break; + case ACTION_POWEROFF: + action = "poweroff"; + break; + case ACTION_KEXEC: + action = "kexec"; + break; + case ACTION_EXIT: + action = "exit"; + break; + case ACTION_REBOOT: + default: + action = "reboot"; + break; + } - if (arg_when <= 0 && - !arg_dry && - arg_force <= 0 && - (arg_action == ACTION_POWEROFF || - arg_action == ACTION_REBOOT)) { - r = reboot_with_logind(bus, arg_action); - if (r >= 0) - return r; - } + if (arg_dry) + action = strjoina("dry-", action); - log_error("Must be root."); - return -EPERM; - } + (void) logind_set_wall_message(); - if (arg_when > 0) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_flush_close_unref_ sd_bus *b = NULL; - _cleanup_free_ char *m = NULL; + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ScheduleShutdown", + &error, + NULL, + "st", + action, + arg_when); + if (r < 0) + return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r)); - if (avoid_bus()) { - log_error("Unable to perform operation without bus connection."); - return -ENOSYS; - } + log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", format_timestamp(date, sizeof(date), arg_when)); + return 0; +#else + log_error("Cannot schedule shutdown without logind support, proceeding with immediate shutdown."); + return -ENOSYS; +#endif +} - r = sd_bus_open_system(&b); - if (r < 0) - return log_error_errno(r, "Unable to open system bus: %m"); +static int halt_main(void) { + int r; - m = strv_join(arg_wall, " "); - if (!m) - return log_oom(); + r = logind_check_inhibitors(arg_action); + if (r < 0) + return r; - r = sd_bus_set_property( - b, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "WallMessage", - &error, - "s", m); - if (r < 0) { - log_warning_errno(r, "Failed to set WallMessage property in logind: %s", - bus_error_message(&error, r)); - sd_bus_error_free(&error); - } + if (arg_when > 0) + return logind_schedule_shutdown(); - r = sd_bus_set_property( - b, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "EnableWallMessages", - &error, - "b", !arg_no_wall); - if (r < 0) { - log_warning_errno(r, "Failed to set EnableWallMessages property in logind: %s", - bus_error_message(&error, r)); - sd_bus_error_free(&error); + if (geteuid() != 0) { + if (arg_dry || arg_force > 0) { + log_error("Must be root."); + return -EPERM; } - r = sd_bus_call_method( - b, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ScheduleShutdown", - &error, - NULL, - "st", - arg_action == ACTION_HALT ? "halt" : - arg_action == ACTION_POWEROFF ? "poweroff" : - arg_action == ACTION_KEXEC ? "kexec" : - "reboot", - arg_when); - if (r < 0) - log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", - bus_error_message(&error, r)); - else { - char date[FORMAT_TIMESTAMP_MAX]; - - log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", - format_timestamp(date, sizeof(date), arg_when)); - return 0; + /* Try logind if we are a normal user and no special + * mode applies. Maybe PolicyKit allows us to shutdown + * the machine. */ + if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT)) { + r = logind_reboot(arg_action); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not + * supported on the local system or + * already in progress */ + return r; + /* on all other errors, try low-level operation */ } } if (!arg_dry && !arg_force) - return start_with_fallback(bus); + return start_with_fallback(); + + assert(geteuid() == 0); if (!arg_no_wtmp) { if (sd_booted() > 0) @@ -7427,9 +8145,7 @@ static int halt_main(sd_bus *bus) { return 0; r = halt_now(arg_action); - log_error_errno(r, "Failed to reboot: %m"); - - return r; + return log_error_errno(r, "Failed to reboot: %m"); } static int runlevel_main(void) { @@ -7448,13 +8164,43 @@ static int runlevel_main(void) { return 0; } +static int logind_cancel_shutdown(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + (void) logind_set_wall_message(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "CancelScheduledShutdown", + &error, + NULL, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", bus_error_message(&error, r)); + + return 0; +#else + log_error("Not compiled with logind support, cannot cancel scheduled shutdowns."); + return -ENOSYS; +#endif +} + int main(int argc, char*argv[]) { - _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; int r; setlocale(LC_ALL, ""); log_parse_environment(); log_open(); + sigbus_install(); /* Explicitly not on_tty() to avoid setting cached value. * This becomes relevant for piping output which might be @@ -7465,39 +8211,26 @@ int main(int argc, char*argv[]) { if (r <= 0) goto finish; - /* /sbin/runlevel doesn't need to communicate via D-Bus, so - * let's shortcut this */ - if (arg_action == ACTION_RUNLEVEL) { - r = runlevel_main(); - goto finish; - } - - if (running_in_chroot() > 0 && arg_action != ACTION_SYSTEMCTL) { + if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) { log_info("Running in chroot, ignoring request."); r = 0; goto finish; } - if (!avoid_bus()) - r = bus_open_transport_systemd(arg_transport, arg_host, arg_scope != UNIT_FILE_SYSTEM, &bus); - - if (bus) - sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - /* systemctl_main() will print an error message for the bus * connection, but only if it needs to */ switch (arg_action) { case ACTION_SYSTEMCTL: - r = systemctl_main(bus, argc, argv, r); + r = systemctl_main(argc, argv); break; case ACTION_HALT: case ACTION_POWEROFF: case ACTION_REBOOT: case ACTION_KEXEC: - r = halt_main(bus); + r = halt_main(); break; case ACTION_RUNLEVEL2: @@ -7507,85 +8240,30 @@ int main(int argc, char*argv[]) { case ACTION_RESCUE: case ACTION_EMERGENCY: case ACTION_DEFAULT: - r = start_with_fallback(bus); + r = start_with_fallback(); break; case ACTION_RELOAD: case ACTION_REEXEC: - r = reload_with_fallback(bus); + r = reload_with_fallback(); break; - case ACTION_CANCEL_SHUTDOWN: { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_flush_close_unref_ sd_bus *b = NULL; - _cleanup_free_ char *m = NULL; - - if (avoid_bus()) { - log_error("Unable to perform operation without bus connection."); - return -ENOSYS; - } - - r = sd_bus_open_system(&b); - if (r < 0) - return log_error_errno(r, "Unable to open system bus: %m"); - - if (arg_wall) { - m = strv_join(arg_wall, " "); - if (!m) { - r = log_oom(); - goto finish; - } - } - - r = sd_bus_set_property( - b, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "WallMessage", - &error, - "s", arg_wall); - if (r < 0) { - log_warning_errno(r, "Failed to set WallMessage property in logind: %s", - bus_error_message(&error, r)); - sd_bus_error_free(&error); - } - - r = sd_bus_set_property( - b, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "EnableWallMessages", - &error, - "b", !arg_no_wall); - if (r < 0) { - log_warning_errno(r, "Failed to set EnableWallMessages property in logind: %s", - bus_error_message(&error, r)); - sd_bus_error_free(&error); - } - - r = sd_bus_call_method( - b, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "CancelScheduledShutdown", - &error, - NULL, NULL); - if (r < 0) - log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", - bus_error_message(&error, r)); + case ACTION_CANCEL_SHUTDOWN: + r = logind_cancel_shutdown(); break; - } case ACTION_RUNLEVEL: + r = runlevel_main(); + break; + case _ACTION_INVALID: default: assert_not_reached("Unknown action"); } finish: + release_busses(); + pager_close(); ask_password_agent_close(); polkit_agent_close(); @@ -7594,5 +8272,9 @@ finish: strv_free(arg_states); strv_free(arg_properties); + strv_free(arg_wall); + free(arg_root); + + /* Note that we return r here, not EXIT_SUCCESS, so that we can implement the LSB-like return codes */ return r < 0 ? EXIT_FAILURE : r; } |