X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fsystemctl%2Fsystemctl.c;h=2fbfd7eb9bb3d0eb28d5415ad1af4bcd8f49f17a;hp=e8b8b51d7634a40ce9c1723a218bfdbb5b521665;hb=b43f208f78666fd27e274daa946ae57becd59dd6;hpb=c3175a7f40a2d2fabc3a2de63033a6810d45221a diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index e8b8b51d7..2fbfd7eb9 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -45,6 +45,7 @@ #include "utmp-wtmp.h" #include "special.h" #include "initreq.h" +#include "path-util.h" #include "strv.h" #include "dbus-common.h" #include "cgroup-show.h" @@ -61,13 +62,13 @@ #include "spawn-polkit-agent.h" #include "install.h" #include "logs-show.h" +#include "path-util.h" static const char *arg_type = NULL; static char **arg_property = NULL; static bool arg_all = false; static const char *arg_job_mode = "replace"; static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; -static bool arg_immediate = false; static bool arg_no_block = false; static bool arg_no_legend = false; static bool arg_no_pager = false; @@ -96,6 +97,8 @@ static enum action { ACTION_REBOOT, ACTION_KEXEC, ACTION_EXIT, + ACTION_SUSPEND, + ACTION_HIBERNATE, ACTION_RUNLEVEL2, ACTION_RUNLEVEL3, ACTION_RUNLEVEL4, @@ -168,6 +171,7 @@ static void ask_password_agent_open_if_enabled(void) { ask_password_agent_open(); } +#ifdef HAVE_LOGIND static void polkit_agent_open_if_enabled(void) { /* Open the polkit agent as a child process if necessary */ @@ -180,6 +184,7 @@ static void polkit_agent_open_if_enabled(void) { polkit_agent_open(); } +#endif static const char *ansi_highlight_red(bool b) { @@ -561,7 +566,7 @@ static int compare_unit_file_list(const void *a, const void *b) { return r; } - return strcasecmp(file_name_from_path(u->path), file_name_from_path(v->path)); + return strcasecmp(path_get_file_name(u->path), path_get_file_name(v->path)); } static bool output_show_unit_file(const UnitFileList *u) { @@ -580,7 +585,7 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { if (!output_show_unit_file(u)) continue; - max_id_len = MAX(max_id_len, strlen(file_name_from_path(u->path))); + max_id_len = MAX(max_id_len, strlen(path_get_file_name(u->path))); state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state))); } @@ -617,7 +622,7 @@ static void output_unit_file_list(const UnitFileList *units, unsigned c) { } else on = off = ""; - id = file_name_from_path(u->path); + id = path_get_file_name(u->path); e = arg_full ? NULL : ellipsize(id, id_cols, 33); @@ -1350,18 +1355,19 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { uint32_t id; - const char *path, *result; + const char *path, *result, *unit; dbus_bool_t success = true; if (dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &id, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &unit, DBUS_TYPE_STRING, &result, DBUS_TYPE_INVALID)) { char *p; - if ((p = set_remove(d->set, (char*) path))) - free(p); + p = set_remove(d->set, (char*) path); + free(p); if (*result) d->result = strdup(result); @@ -1370,7 +1376,26 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me } #ifndef LEGACY dbus_error_free(&error); + if (dbus_message_get_args(message, &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &result, + DBUS_TYPE_INVALID)) { + char *p; + + /* Compatibility with older systemd versions < + * 183 during upgrades. This should be dropped + * one day. */ + p = set_remove(d->set, (char*) path); + free(p); + + if (*result) + d->result = strdup(result); + + goto finish; + } + dbus_error_free(&error); if (dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &id, DBUS_TYPE_OBJECT_PATH, &path, @@ -1382,8 +1407,8 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me * 19 during upgrades. This should be dropped * one day */ - if ((p = set_remove(d->set, (char*) path))) - free(p); + p = set_remove(d->set, (char*) path); + free(p); if (!success) d->result = strdup("failed"); @@ -1584,6 +1609,10 @@ static enum action verb_to_action(const char *verb) { return ACTION_DEFAULT; else if (streq(verb, "exit")) return ACTION_EXIT; + else if (streq(verb, "suspend")) + return ACTION_SUSPEND; + else if (streq(verb, "hibernate")) + return ACTION_HIBERNATE; else return ACTION_INVALID; } @@ -1602,7 +1631,9 @@ static int start_unit(DBusConnection *bus, char **args) { [ACTION_RESCUE] = SPECIAL_RESCUE_TARGET, [ACTION_EMERGENCY] = SPECIAL_EMERGENCY_TARGET, [ACTION_DEFAULT] = SPECIAL_DEFAULT_TARGET, - [ACTION_EXIT] = SPECIAL_EXIT_TARGET + [ACTION_EXIT] = SPECIAL_EXIT_TARGET, + [ACTION_SUSPEND] = SPECIAL_SUSPEND_TARGET, + [ACTION_HIBERNATE] = SPECIAL_HIBERNATE_TARGET }; int r, ret = 0; @@ -1721,6 +1752,14 @@ static int reboot_with_logind(DBusConnection *bus, enum action a) { method = "PowerOff"; break; + case ACTION_SUSPEND: + method = "Suspend"; + break; + + case ACTION_HIBERNATE: + method = "Hibernate"; + break; + default: return -EINVAL; } @@ -1784,11 +1823,15 @@ static int start_special(DBusConnection *bus, char **args) { enum action a; int r; - assert(bus); assert(args); a = verb_to_action(args[0]); + if (arg_force >= 2 && geteuid() != 0) { + log_error("Must be root."); + return -EPERM; + } + if (arg_force >= 2 && (a == ACTION_HALT || a == ACTION_POWEROFF || @@ -1806,7 +1849,9 @@ static int start_special(DBusConnection *bus, char **args) { /* first try logind, to allow authentication with polkit */ if (geteuid() != 0 && (a == ACTION_POWEROFF || - a == ACTION_REBOOT)) { + a == ACTION_REBOOT || + a == ACTION_SUSPEND || + a == ACTION_HIBERNATE)) { r = reboot_with_logind(bus, a); if (r >= 0) return r; @@ -2115,7 +2160,10 @@ typedef struct UnitStatusInfo { const char *description; const char *following; - const char *path; + char **documentation; + + const char *fragment_path; + const char *source_path; const char *default_control_group; const char *load_error; @@ -2134,9 +2182,6 @@ typedef struct UnitStatusInfo { pid_t control_pid; const char *status_text; bool running:1; -#ifdef HAVE_SYSV_COMPAT - bool is_sysv:1; -#endif usec_t start_timestamp; usec_t exit_timestamp; @@ -2169,6 +2214,7 @@ static void print_status_info(UnitStatusInfo *i) { usec_t timestamp; char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1; char since2[FORMAT_TIMESTAMP_MAX], *s2; + const char *path; assert(i); @@ -2191,12 +2237,14 @@ static void print_status_info(UnitStatusInfo *i) { } else on = off = ""; + path = i->source_path ? i->source_path : i->fragment_path; + if (i->load_error) printf("\t Loaded: %s%s%s (Reason: %s)\n", on, strna(i->load_state), off, i->load_error); - else if (i->path && i->unit_file_state) - printf("\t Loaded: %s%s%s (%s; %s)\n", on, strna(i->load_state), off, i->path, i->unit_file_state); - else if (i->path) - printf("\t Loaded: %s%s%s (%s)\n", on, strna(i->load_state), off, i->path); + else if (path && i->unit_file_state) + printf("\t Loaded: %s%s%s (%s; %s)\n", on, strna(i->load_state), off, path, i->unit_file_state); + else if (path) + printf("\t Loaded: %s%s%s (%s)\n", on, strna(i->load_state), off, path); else printf("\t Loaded: %s%s%s\n", on, strna(i->load_state), off); @@ -2260,6 +2308,19 @@ static void print_status_info(UnitStatusInfo *i) { if (i->what) printf("\t What: %s\n", i->what); + if (!strv_isempty(i->documentation)) { + char **t; + bool first = true; + + STRV_FOREACH(t, i->documentation) { + if (first) { + printf("\t Docs: %s\n", *t); + first = false; + } else + printf("\t %s\n", *t); + } + } + if (i->accept) printf("\tAccepted: %u; Connected: %u\n", i->n_accepted, i->n_connections); @@ -2275,13 +2336,7 @@ static void print_status_info(UnitStatusInfo *i) { printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t)); free(t); -#ifdef HAVE_SYSV_COMPAT - if (i->is_sysv) - good = is_clean_exit_lsb(p->code, p->status); - else -#endif - good = is_clean_exit(p->code, p->status); - + good = is_clean_exit_lsb(p->code, p->status); if (!good) { on = ansi_highlight_red(true); off = ansi_highlight_red(false); @@ -2295,11 +2350,8 @@ static void print_status_info(UnitStatusInfo *i) { printf("status=%i", p->status); -#ifdef HAVE_SYSV_COMPAT - if ((c = exit_status_to_string(p->status, i->is_sysv ? EXIT_STATUS_LSB : EXIT_STATUS_SYSTEMD))) -#else - if ((c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD))) -#endif + c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD); + if (c) printf("/%s", c); } else @@ -2338,11 +2390,8 @@ static void print_status_info(UnitStatusInfo *i) { printf("status=%i", i->exit_status); -#ifdef HAVE_SYSV_COMPAT - if ((c = exit_status_to_string(i->exit_status, i->is_sysv ? EXIT_STATUS_LSB : EXIT_STATUS_SYSTEMD))) -#else - if ((c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD))) -#endif + c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD); + if (c) printf("/%s", c); } else @@ -2378,12 +2427,22 @@ static void print_status_info(UnitStatusInfo *i) { printf("\t CGroup: %s\n", i->default_control_group); if (arg_transport != TRANSPORT_SSH) { - if ((c = columns()) > 18) + unsigned k = 0; + pid_t extra[2]; + + c = columns(); + if (c > 18) c -= 18; else c = 0; - show_cgroup_by_path(i->default_control_group, "\t\t ", c, false, arg_all); + if (i->main_pid > 0) + extra[k++] = i->main_pid; + + if (i->control_pid > 0) + extra[k++] = i->control_pid; + + show_cgroup_and_extra_by_spec(i->default_control_group, "\t\t ", c, false, arg_all, extra, k); } } @@ -2399,6 +2458,73 @@ static void print_status_info(UnitStatusInfo *i) { arg_scope == UNIT_FILE_SYSTEM ? "--system" : "--user"); } +static void show_unit_help(UnitStatusInfo *i) { + char **p; + + assert(i); + + if (!i->documentation) { + log_info("Documentation for %s not known.", i->id); + return; + } + + STRV_FOREACH(p, i->documentation) { + + if (startswith(*p, "man:")) { + size_t k; + char *e = NULL; + char *page = NULL, *section = NULL; + const char *args[4] = { "man", NULL, NULL, NULL }; + pid_t pid; + + k = strlen(*p); + + if ((*p)[k-1] == ')') + e = strrchr(*p, '('); + + if (e) { + page = strndup((*p) + 4, e - *p - 4); + if (!page) { + log_error("Out of memory."); + return; + } + + section = strndup(e + 1, *p + k - e - 2); + if (!section) { + free(page); + log_error("Out of memory"); + return; + } + + args[1] = section; + args[2] = page; + } else + args[1] = *p + 4; + + pid = fork(); + if (pid < 0) { + log_error("Failed to fork: %m"); + free(page); + free(section); + continue; + } + + if (pid == 0) { + /* Child */ + execvp(args[0], (char**) args); + log_error("Failed to execute man: %m"); + _exit(EXIT_FAILURE); + } + + free(page); + free(section); + + wait_for_terminate(pid, NULL); + } else + log_info("Can't show %s.", *p); + } +} + static int status_property(const char *name, DBusMessageIter *iter, UnitStatusInfo *i) { assert(name); @@ -2424,13 +2550,9 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn else if (streq(name, "Description")) i->description = s; else if (streq(name, "FragmentPath")) - i->path = s; -#ifdef HAVE_SYSV_COMPAT - else if (streq(name, "SysVPath")) { - i->is_sysv = true; - i->path = s; - } -#endif + i->fragment_path = s; + else if (streq(name, "SourcePath")) + i->source_path = s; else if (streq(name, "DefaultControlGroup")) i->default_control_group = s; else if (streq(name, "StatusText")) @@ -2554,6 +2676,27 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn LIST_PREPEND(ExecStatusInfo, exec, i->exec, info); + dbus_message_iter_next(&sub); + } + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING && + streq(name, "Documentation")) { + + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + const char *s; + char **l; + + dbus_message_iter_get_basic(&sub, &s); + + l = strv_append(i->documentation, s); + if (!l) + return -ENOMEM; + + strv_free(i->documentation); + i->documentation = l; + dbus_message_iter_next(&sub); } } @@ -2876,8 +3019,14 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo r = 0; - if (!show_properties) - print_status_info(&info); + if (!show_properties) { + if (streq(verb, "help")) + show_unit_help(&info); + else + print_status_info(&info); + } + + strv_free(info.documentation); if (!streq_ptr(info.active_state, "active") && !streq_ptr(info.active_state, "reloading") && @@ -2902,19 +3051,71 @@ finish: return r; } -static int show(DBusConnection *bus, char **args) { +static int show_one_by_pid(const char *verb, DBusConnection *bus, uint32_t pid, bool *new_line) { DBusMessage *m = NULL, *reply = NULL; - int r, ret = 0; + const char *path = NULL; DBusError error; + int r; + + dbus_error_init(&error); + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitByPID"); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + r = show_one(verb, bus, path, false, new_line); + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int show(DBusConnection *bus, char **args) { + int r, ret = 0; bool show_properties, new_line = false; char **name; assert(bus); assert(args); - dbus_error_init(&error); - - show_properties = !streq(args[0], "status"); + show_properties = streq(args[0], "show"); if (show_properties) pager_open_if_enabled(); @@ -2923,157 +3124,55 @@ static int show(DBusConnection *bus, char **args) { /* If not argument is specified inspect the manager * itself */ - ret = show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line); - goto finish; + return show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line); } STRV_FOREACH(name, args+1) { - const char *path = NULL; uint32_t id; if (safe_atou32(*name, &id) < 0) { /* Interpret as unit name */ - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LoadUnit"))) { - log_error("Could not allocate message."); - ret = -ENOMEM; - goto finish; - } - - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, name, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - ret = -ENOMEM; - goto finish; - } - - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - - if (!dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED)) { - log_error("Failed to issue method call: %s", bus_error_message(&error)); - ret = -EIO; - goto finish; - } - - dbus_error_free(&error); - - dbus_message_unref(m); - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnit"))) { - log_error("Could not allocate message."); - ret = -ENOMEM; - goto finish; - } - - if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, name, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - ret = -ENOMEM; - goto finish; - } + char *e, *p; + e = bus_path_escape(*name); + if (!e) + return -ENOMEM; + p = strappend("/org/freedesktop/systemd1/unit/", e); + free(e); + if (!p) + return -ENOMEM; - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = show_one(args[0], bus, p, show_properties, &new_line); + free(p); - if (dbus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) - ret = 4; /* According to LSB: "program or service status is unknown" */ - else - ret = -EIO; - goto finish; - } - } + if (r != 0) + ret = r; } else if (show_properties) { /* Interpret as job id */ - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetJob"))) { - log_error("Could not allocate message."); - ret = -ENOMEM; - goto finish; - } + char *p; + if (asprintf(&p, "/org/freedesktop/systemd1/job/%u", id) < 0) + return -ENOMEM; - if (!dbus_message_append_args(m, - DBUS_TYPE_UINT32, &id, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - ret = -ENOMEM; - goto finish; - } + r = show_one(args[0], bus, p, show_properties, &new_line); + free(p); + + if (r != 0) + ret = r; - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to issue method call: %s", bus_error_message(&error)); - ret = -EIO; - goto finish; - } } else { /* Interpret as PID */ - if (!(m = dbus_message_new_method_call( - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitByPID"))) { - log_error("Could not allocate message."); - ret = -ENOMEM; - goto finish; - } - - if (!dbus_message_append_args(m, - DBUS_TYPE_UINT32, &id, - DBUS_TYPE_INVALID)) { - log_error("Could not append arguments to message."); - ret = -ENOMEM; - goto finish; - } - - if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - log_error("Failed to issue method call: %s", bus_error_message(&error)); - ret = -EIO; - goto finish; - } - } - - if (!dbus_message_get_args(reply, &error, - DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID)) { - log_error("Failed to parse reply: %s", bus_error_message(&error)); - ret = -EIO; - goto finish; + r = show_one_by_pid(args[0], bus, id, &new_line); + if (r != 0) + ret = r; } - - if ((r = show_one(args[0], bus, path, show_properties, &new_line)) != 0) - ret = r; - - dbus_message_unref(m); - dbus_message_unref(reply); - m = reply = NULL; } -finish: - if (m) - dbus_message_unref(m); - - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - return ret; } @@ -3527,6 +3626,65 @@ finish: return r; } +static int switch_root(DBusConnection *bus, char **args) { + DBusMessage *m = NULL, *reply = NULL; + unsigned l; + const char *root, *init; + DBusError error; + int r; + + dbus_error_init(&error); + + l = strv_length(args); + if (l < 2 || l > 3) { + log_error("Wrong number of arguments."); + return -EINVAL; + } + + root = args[1]; + init = l >= 3 ? args[2] : ""; + + m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SwitchRoot"); + if (!m) { + log_error("Could not allocate message."); + return -ENOMEM; + } + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &root, + DBUS_TYPE_STRING, &init, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + static int set_environment(DBusConnection *bus, char **args) { DBusMessage *m = NULL, *reply = NULL; DBusError error; @@ -3612,12 +3770,11 @@ static int enable_sysv_units(char **args) { * afterwards only the native units remain */ zero(paths); - r = lookup_paths_init(&paths, MANAGER_SYSTEM, false); + r = lookup_paths_init(&paths, MANAGER_SYSTEM, false, NULL, NULL, NULL); if (r < 0) return r; r = 0; - for (f = 1; args[f]; f++) { const char *name; char *p; @@ -3688,7 +3845,7 @@ static int enable_sysv_units(char **args) { if (!isempty(arg_root)) argv[c++] = q = strappend("--root=", arg_root); - argv[c++] = file_name_from_path(p); + argv[c++] = path_get_file_name(p); argv[c++] = streq(verb, "enable") ? "on" : streq(verb, "disable") ? "off" : "--level=5"; @@ -4127,6 +4284,7 @@ static int systemctl_help(void) { " status [NAME...|PID...] Show runtime status of one or more units\n" " show [NAME...|JOB...] Show properties of one or more\n" " units/jobs or the manager\n" + " help [NAME...|PID...] Show manual for one or more units\n" " reset-failed [NAME...] Reset failed state for all, one, or more\n" " units\n" " load [NAME...] Load one or more units\n\n" @@ -4166,7 +4324,10 @@ static int systemctl_help(void) { " poweroff Shut down and power-off the system\n" " reboot Shut down and reboot the system\n" " kexec Shut down and reboot the system with kexec\n" - " exit Ask for user instance termination\n", + " exit Request user instance exit\n" + " switch-root [ROOT] [INIT] Change to a different root file system\n" + " suspend Suspend the system\n" + " hibernate Hibernate the system\n", program_invocation_short_name); return 0; @@ -4201,7 +4362,7 @@ static int shutdown_help(void) { " -H --halt Halt the machine\n" " -P --poweroff Power-off the machine\n" " -r --reboot Reboot the machine\n" - " -h Equivalent to --poweroff, overriden by --halt\n" + " -h Equivalent to --poweroff, overridden by --halt\n" " -k Don't halt/power-off/reboot, just send warnings\n" " --no-wall Don't send wall message before halt/power-off/reboot\n" " -c Cancel a pending shutdown\n", @@ -4515,7 +4676,7 @@ static int halt_parse_argv(int argc, char *argv[]) { if (utmp_get_runlevel(&runlevel, NULL) >= 0) if (runlevel == '0' || runlevel == '6') - arg_immediate = true; + arg_force = 2; while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) { switch (c) { @@ -4538,7 +4699,7 @@ static int halt_parse_argv(int argc, char *argv[]) { break; case 'f': - arg_immediate = true; + arg_force = 2; break; case 'w': @@ -5087,6 +5248,7 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "check", MORE, 2, check_unit }, { "show", MORE, 1, show }, { "status", MORE, 2, show }, + { "help", MORE, 2, show }, { "dump", EQUAL, 1, dump }, { "dot", EQUAL, 1, dot }, { "snapshot", LESS, 2, snapshot }, @@ -5100,6 +5262,8 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "poweroff", EQUAL, 1, start_special }, { "reboot", EQUAL, 1, start_special }, { "kexec", EQUAL, 1, start_special }, + { "suspend", EQUAL, 1, start_special }, + { "hibernate", EQUAL, 1, start_special }, { "default", EQUAL, 1, start_special }, { "rescue", EQUAL, 1, start_special }, { "emergency", EQUAL, 1, start_special }, @@ -5112,7 +5276,8 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "preset", MORE, 2, enable_unit }, { "mask", MORE, 2, enable_unit }, { "unmask", MORE, 2, enable_unit }, - { "link", MORE, 2, enable_unit } + { "link", MORE, 2, enable_unit }, + { "switch-root", MORE, 2, switch_root }, }; int left; @@ -5128,9 +5293,10 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError /* Special rule: no arguments means "list-units" */ i = 0; else { - if (streq(argv[optind], "help")) { - systemctl_help(); - return 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 (i = 0; i < ELEMENTSOF(verbs); i++) @@ -5190,7 +5356,9 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError return 0; } - if (!bus) { + if (((!streq(verbs[i].verb, "reboot") && + !streq(verbs[i].verb, "halt") && + !streq(verbs[i].verb, "poweroff")) || arg_force <= 0) && !bus) { log_error("Failed to get D-Bus connection: %s", dbus_error_is_set(error) ? error->message : "No connection to service manager."); return -EIO; @@ -5340,7 +5508,7 @@ static int halt_main(DBusConnection *bus) { if (arg_when <= 0 && !arg_dry && - !arg_immediate && + !arg_force && (arg_action == ACTION_POWEROFF || arg_action == ACTION_REBOOT)) { r = reboot_with_logind(bus, arg_action); @@ -5377,7 +5545,7 @@ static int halt_main(DBusConnection *bus) { } } - if (!arg_dry && !arg_immediate) + if (!arg_dry && !arg_force) return start_with_fallback(bus); if (!arg_no_wtmp) {