chiark / gitweb /
systemctl: properly handle job results
[elogind.git] / src / systemctl.c
index 372b3d0ca662adeeaba24d5cbc4b541bf4c89f43..b33a89f63d76d9bd3ae05d28db2f5b35a75aca4d 100644 (file)
 static const char *arg_type = NULL;
 static char **arg_property = NULL;
 static bool arg_all = false;
-static bool arg_fail = false;
-static bool arg_session = false;
+static const char *arg_job_mode = "replace";
+static bool arg_user = false;
 static bool arg_global = false;
 static bool arg_immediate = false;
 static bool arg_no_block = 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;
@@ -75,6 +76,7 @@ static bool arg_full = false;
 static bool arg_force = false;
 static bool arg_defaults = false;
 static bool arg_ask_password = false;
+static bool arg_failed = false;
 static char **arg_wall = NULL;
 static const char *arg_kill_who = NULL;
 static const char *arg_kill_mode = NULL;
@@ -109,11 +111,20 @@ static enum dot {
 
 static bool private_bus = false;
 
+static pid_t pager_pid = 0;
+
 static int daemon_reload(DBusConnection *bus, char **args, unsigned n);
+static void pager_open(void);
 
 static bool on_tty(void) {
         static int t = -1;
 
+        /* Note that this is invoked relatively early, before we start
+         * the pager. That means the value we return reflects whether
+         * we originally were started on a tty, not if we currently
+         * are. But this is intended, since we want colour and so on
+         * when run in our own pager. */
+
         if (_unlikely_(t < 0))
                 t = isatty(STDOUT_FILENO) > 0;
 
@@ -131,6 +142,9 @@ static void spawn_ask_password_agent(void) {
         if (!arg_ask_password)
                 return;
 
+        if (arg_user)
+                return;
+
         parent = getpid();
 
         /* Spawns a temporary TTY agent, making sure it goes away when
@@ -141,13 +155,16 @@ static void spawn_ask_password_agent(void) {
 
         if (child == 0) {
                 /* In the child */
-
                 const char * const args[] = {
                         SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH,
                         "--watch",
                         NULL
                 };
 
+                int fd;
+                bool stdout_is_tty, stderr_is_tty;
+
+                /* Make sure the agent goes away when the parent dies */
                 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
                         _exit(EXIT_FAILURE);
 
@@ -156,6 +173,35 @@ static void spawn_ask_password_agent(void) {
                 if (getppid() != parent)
                         _exit(EXIT_SUCCESS);
 
+                /* Don't leak fds to the agent */
+                close_all_fds(NULL, 0);
+
+                stdout_is_tty = isatty(STDOUT_FILENO);
+                stderr_is_tty = isatty(STDERR_FILENO);
+
+                if (!stdout_is_tty || !stderr_is_tty) {
+                        /* Detach from stdout/stderr. and reopen
+                         * /dev/tty for them. This is important to
+                         * ensure that when systemctl is started via
+                         * popen() or a similar call that expects to
+                         * read EOF we actually do generate EOF and
+                         * not delay this indefinitely by because we
+                         * keep an unused copy of stdin around. */
+                        if ((fd = open("/dev/tty", O_WRONLY)) < 0) {
+                                log_error("Failed to open /dev/tty: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        if (!stdout_is_tty)
+                                dup2(fd, STDOUT_FILENO);
+
+                        if (!stderr_is_tty)
+                                dup2(fd, STDERR_FILENO);
+
+                        if (fd > 2)
+                                close(fd);
+                }
+
                 execv(args[0], (char **) args);
                 _exit(EXIT_FAILURE);
         }
@@ -302,9 +348,12 @@ static int compare_unit_info(const void *a, const void *b) {
         return strcasecmp(u->id, v->id);
 }
 
-static bool output_show_job(const struct unit_info *u) {
+static bool output_show_unit(const struct unit_info *u) {
         const char *dot;
 
+        if (arg_failed)
+                return streq(u->active_state, "failed");
+
         return (!arg_type || ((dot = strrchr(u->id, '.')) &&
                               streq(dot+1, arg_type))) &&
                 (arg_all || !(streq(u->active_state, "inactive") || u->following[0]) || u->job_id > 0);
@@ -319,7 +368,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) {
         job_len = sizeof("JOB")-1;
 
         for (u = unit_infos; u < unit_infos + c; u++) {
-                if (!output_show_job(u))
+                if (!output_show_unit(u))
                         continue;
 
                 active_len = MAX(active_len, strlen(u->active_state));
@@ -343,7 +392,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) {
                 const char *on_loaded, *off_loaded;
                 const char *on_active, *off_active;
 
-                if (!output_show_job(u))
+                if (!output_show_unit(u))
                         continue;
 
                 n_shown++;
@@ -418,6 +467,8 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
 
         assert(bus);
 
+        pager_open();
+
         if (!(m = dbus_message_new_method_call(
                               "org.freedesktop.systemd1",
                               "/org/freedesktop/systemd1",
@@ -490,8 +541,10 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                 c++;
         }
 
-        qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
-        output_units_list(unit_infos, c);
+        if (c > 0) {
+                qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
+                output_units_list(unit_infos, c);
+        }
 
         r = 0;
 
@@ -764,6 +817,8 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) {
 
         assert(bus);
 
+        pager_open();
+
         if (!(m = dbus_message_new_method_call(
                               "org.freedesktop.systemd1",
                               "/org/freedesktop/systemd1",
@@ -1065,7 +1120,7 @@ finish:
 
 typedef struct WaitData {
         Set *set;
-        bool failed;
+        char *result;
 } WaitData;
 
 static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) {
@@ -1089,26 +1144,52 @@ 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;
+                const char *path, *result;
                 dbus_bool_t success = true;
 
-                if (!dbus_message_get_args(message, &error,
-                                           DBUS_TYPE_UINT32, &id,
-                                           DBUS_TYPE_OBJECT_PATH, &path,
-                                           DBUS_TYPE_BOOLEAN, &success,
-                                           DBUS_TYPE_INVALID))
-                        log_error("Failed to parse message: %s", bus_error_message(&error));
-                else {
+                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;
 
+                        if ((p = set_remove(d->set, (char*) path)))
+                                free(p);
+
+                        if (*result)
+                                d->result = strdup(result);
+
+                        goto finish;
+                }
+#ifndef LEGACY
+                dbus_error_free(&error);
+
+                if (dbus_message_get_args(message, &error,
+                                          DBUS_TYPE_UINT32, &id,
+                                          DBUS_TYPE_OBJECT_PATH, &path,
+                                          DBUS_TYPE_BOOLEAN, &success,
+                                          DBUS_TYPE_INVALID)) {
+                        char *p;
+
+                        /* Compatibility with older systemd versions <
+                         * 19 during upgrades. This should be dropped
+                         * one day */
+
                         if ((p = set_remove(d->set, (char*) path)))
                                 free(p);
 
                         if (!success)
-                                d->failed = true;
+                                d->result = strdup("failed");
+
+                        goto finish;
                 }
+#endif
+
+                log_error("Failed to parse message: %s", bus_error_message(&error));
         }
 
+finish:
         dbus_error_free(&error);
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
@@ -1149,7 +1230,6 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) {
 
         zero(d);
         d.set = s;
-        d.failed = false;
 
         if (!dbus_connection_add_filter(bus, wait_filter, &d, NULL)) {
                 log_error("Failed to add filter.");
@@ -1161,10 +1241,27 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) {
                dbus_connection_read_write_dispatch(bus, -1))
                 ;
 
-        if (!arg_quiet && d.failed)
-                log_error("Job failed. See system logs and 'systemctl status' for details.");
+        if (!arg_quiet && d.result) {
+                if (streq(d.result, "timeout"))
+                        log_error("Job timed out.");
+                else if (streq(d.result, "canceled"))
+                        log_error("Job canceled.");
+                else if (streq(d.result, "dependency"))
+                        log_error("A dependency job failed. See system logs for details.");
+                else if (!streq(d.result, "done"))
+                        log_error("Job failed. See system logs and 'systemctl status' for details.");
+        }
+
+        if (streq_ptr(d.result, "timeout"))
+                r = -ETIME;
+        else if (streq_ptr(d.result, "canceled"))
+                r = -ECANCELED;
+        else if (!streq_ptr(d.result, "done"))
+                r = -EIO;
+        else
+                r = 0;
 
-        r = d.failed ? -EIO : 0;
+        free(d.result);
 
 finish:
         /* This is slightly dirty, since we don't undo the filter registration. */
@@ -1234,7 +1331,7 @@ static int start_unit_one(
 
         if (need_daemon_reload(bus, name))
                 log_warning("Unit file of created job changed on disk, 'systemctl %s daemon-reload' recommended.",
-                            arg_session ? "--session" : "--system");
+                            arg_user ? "--user" : "--system");
 
         if (!arg_no_block) {
                 char *p;
@@ -1329,9 +1426,7 @@ static int start_unit(DBusConnection *bus, char **args, unsigned n) {
                 mode =
                         (streq(args[0], "isolate") ||
                          streq(args[0], "rescue")  ||
-                         streq(args[0], "emergency")) ? "isolate" :
-                                             arg_fail ? "fail" :
-                                                        "replace";
+                         streq(args[0], "emergency")) ? "isolate" : arg_job_mode;
 
                 one_name = table[verb_to_action(args[0])];
 
@@ -1601,6 +1696,8 @@ finish:
 }
 
 typedef struct ExecStatusInfo {
+        char *name;
+
         char *path;
         char **argv;
 
@@ -1618,6 +1715,7 @@ typedef struct ExecStatusInfo {
 static void exec_status_info_free(ExecStatusInfo *i) {
         assert(i);
 
+        free(i->name);
         free(i->path);
         strv_free(i->argv);
         free(i);
@@ -1830,15 +1928,31 @@ static void print_status_info(UnitStatusInfo *i) {
 
         LIST_FOREACH(exec, p, i->exec) {
                 char *t;
+                bool good;
 
                 /* Only show exited processes here */
                 if (p->code == 0)
                         continue;
 
                 t = strv_join(p->argv, " ");
-                printf("\t Process: %u (%s, code=%s, ", p->pid, strna(t), sigchld_code_to_string(p->code));
+                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);
+
+                if (!good) {
+                        on = ansi_highlight(true);
+                        off = ansi_highlight(false);
+                } else
+                        on = off = "";
+
+                printf("%s(code=%s, ", on, sigchld_code_to_string(p->code));
+
                 if (p->code == CLD_EXITED) {
                         const char *c;
 
@@ -1853,7 +1967,10 @@ static void print_status_info(UnitStatusInfo *i) {
 
                 } else
                         printf("signal=%s", signal_to_string(p->status));
-                printf(")\n");
+
+                printf(")%s\n", off);
+
+                on = off = NULL;
 
                 if (i->main_pid == p->pid &&
                     i->start_timestamp == p->start_timestamp &&
@@ -1937,7 +2054,7 @@ static void print_status_info(UnitStatusInfo *i) {
                 printf("\n%sWarning:%s Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n",
                        ansi_highlight(true),
                        ansi_highlight(false),
-                       arg_session ? "--session" : "--system");
+                       arg_user ? "--user" : "--system");
 }
 
 static int status_property(const char *name, DBusMessageIter *iter, UnitStatusInfo *i) {
@@ -2069,6 +2186,11 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn
                                 if (!(info = new0(ExecStatusInfo, 1)))
                                         return -ENOMEM;
 
+                                if (!(info->name = strdup(name))) {
+                                        free(info);
+                                        return -ENOMEM;
+                                }
+
                                 if ((r = exec_status_info_deserialize(&sub, info)) < 0) {
                                         free(info);
                                         return r;
@@ -2338,7 +2460,7 @@ static int print_property(const char *name, DBusMessageIter *iter) {
         return 0;
 }
 
-static int show_one(DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
+static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
         DBusMessage *m = NULL, *reply = NULL;
         const char *interface = "";
         int r;
@@ -2438,7 +2560,8 @@ static int show_one(DBusConnection *bus, const char *path, bool show_properties,
                 print_status_info(&info);
 
         if (!streq_ptr(info.active_state, "active") &&
-            !streq_ptr(info.active_state, "reloading"))
+            !streq_ptr(info.active_state, "reloading") &&
+            streq(verb, "status"))
                 /* According to LSB: "program not running" */
                 r = 3;
 
@@ -2473,11 +2596,14 @@ static int show(DBusConnection *bus, char **args, unsigned n) {
 
         show_properties = !streq(args[0], "status");
 
+        if (show_properties)
+                pager_open();
+
         if (show_properties && n <= 1) {
                 /* If not argument is specified inspect the manager
                  * itself */
 
-                ret = show_one(bus, "/org/freedesktop/systemd1", show_properties, &new_line);
+                ret = show_one(args[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line);
                 goto finish;
         }
 
@@ -2611,7 +2737,7 @@ static int show(DBusConnection *bus, char **args, unsigned n) {
                         goto finish;
                 }
 
-                if ((r = show_one(bus, path, show_properties, &new_line)) != 0)
+                if ((r = show_one(args[0], bus, path, show_properties, &new_line)) != 0)
                         ret = r;
 
                 dbus_message_unref(m);
@@ -2666,11 +2792,12 @@ static DBusHandlerResult monitor_filter(DBusConnection *connection, DBusMessage
         } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobNew") ||
                    dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
                 uint32_t id;
-                const char *path;
+                const char *path, *result;
 
                 if (!dbus_message_get_args(message, &error,
                                            DBUS_TYPE_UINT32, &id,
                                            DBUS_TYPE_OBJECT_PATH, &path,
+                                           DBUS_TYPE_STRING, &result,
                                            DBUS_TYPE_INVALID))
                         log_error("Failed to parse message: %s", bus_error_message(&error));
                 else if (streq(dbus_message_get_member(message), "JobNew"))
@@ -2856,6 +2983,8 @@ static int dump(DBusConnection *bus, char **args, unsigned n) {
 
         dbus_error_init(&error);
 
+        pager_open();
+
         if (!(m = dbus_message_new_method_call(
                               "org.freedesktop.systemd1",
                               "/org/freedesktop/systemd1",
@@ -3218,6 +3347,8 @@ static int show_enviroment(DBusConnection *bus, char **args, unsigned n) {
 
         dbus_error_init(&error);
 
+        pager_open();
+
         if (!(m = dbus_message_new_method_call(
                               "org.freedesktop.systemd1",
                               "/org/freedesktop/systemd1",
@@ -3886,6 +4017,68 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo
         }
 
         if (!f) {
+#if defined(TARGET_FEDORA) && defined (HAVE_SYSV_COMPAT)
+
+                if (endswith(i->name, ".service")) {
+                        char *sysv;
+                        bool exists;
+
+                        if (asprintf(&sysv, SYSTEM_SYSVINIT_PATH "/%s", i->name) < 0) {
+                                log_error("Out of memory");
+                                return -ENOMEM;
+                        }
+
+                        sysv[strlen(sysv) - sizeof(".service") + 1] = 0;
+                        exists = access(sysv, F_OK) >= 0;
+
+                        if (exists) {
+                                pid_t pid;
+                                siginfo_t status;
+
+                                const char *argv[] = {
+                                        "/sbin/chkconfig",
+                                        NULL,
+                                        NULL,
+                                        NULL
+                                };
+
+                                log_info("%s is not a native service, redirecting to /sbin/chkconfig.", i->name);
+
+                                argv[1] = file_name_from_path(sysv);
+                                argv[2] =
+                                        streq(verb, "enable") ? "on" :
+                                        streq(verb, "disable") ? "off" : NULL;
+
+                                log_info("Executing %s %s %s", argv[0], argv[1], strempty(argv[2]));
+
+                                if ((pid = fork()) < 0) {
+                                        log_error("Failed to fork: %m");
+                                        free(sysv);
+                                        return -errno;
+                                } else if (pid == 0) {
+                                        execv(argv[0], (char**) argv);
+                                        _exit(EXIT_FAILURE);
+                                }
+
+                                free(sysv);
+
+                                if ((r = wait_for_terminate(pid, &status)) < 0)
+                                        return r;
+
+                                if (status.si_code == CLD_EXITED) {
+                                        if (status.si_status == 0 && (streq(verb, "enable") || streq(verb, "disable")))
+                                                n_symlinks ++;
+
+                                        return status.si_status == 0 ? 0 : -EINVAL;
+                                } else
+                                        return -EPROTO;
+                        }
+
+                        free(sysv);
+                }
+
+#endif
+
                 log_error("Couldn't find %s.", i->name);
                 return -ENOENT;
         }
@@ -3919,13 +4112,13 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo
 
 static char *get_config_path(void) {
 
-        if (arg_session && arg_global)
-                return strdup(SESSION_CONFIG_UNIT_PATH);
+        if (arg_user && arg_global)
+                return strdup(USER_CONFIG_UNIT_PATH);
 
-        if (arg_session) {
+        if (arg_user) {
                 char *p;
 
-                if (session_config_home(&p) < 0)
+                if (user_config_home(&p) < 0)
                         return NULL;
 
                 return p;
@@ -3946,7 +4139,7 @@ static int enable_unit(DBusConnection *bus, char **args, unsigned n) {
         dbus_error_init(&error);
 
         zero(paths);
-        if ((r = lookup_paths_init(&paths, arg_session ? MANAGER_SESSION : MANAGER_SYSTEM)) < 0) {
+        if ((r = lookup_paths_init(&paths, arg_user ? MANAGER_USER : MANAGER_SYSTEM)) < 0) {
                 log_error("Failed to determine lookup paths: %s", strerror(-r));
                 goto finish;
         }
@@ -4010,9 +4203,9 @@ static int enable_unit(DBusConnection *bus, char **args, unsigned n) {
                     /* Don't try to reload anything when updating a unit globally */
                     !arg_global &&
                     /* Don't try to reload anything if we are called for system changes but the system wasn't booted with systemd */
-                    (arg_session || sd_booted() > 0) &&
+                    (arg_user || sd_booted() > 0) &&
                     /* Don't try to reload anything if we are running in a chroot environment */
-                    (arg_session || running_in_chroot() <= 0) ) {
+                    (arg_user || running_in_chroot() <= 0) ) {
                         int q;
 
                         if ((q = daemon_reload(bus, args, n)) < 0)
@@ -4042,13 +4235,17 @@ static int systemctl_help(void) {
                "  -t --type=TYPE      List only units of a particular type\n"
                "  -p --property=NAME  Show only properties by this name\n"
                "  -a --all            Show all units/properties, including dead/empty ones\n"
+               "     --failed         Show only failed units\n"
                "     --full           Don't ellipsize unit names on output\n"
                "     --fail           When queueing a new job, fail if conflicting jobs are\n"
                "                      pending\n"
+               "     --ignore-dependencies\n"
+               "                      When queueing a new job, ignore all its dependencies\n"
                "  -q --quiet          Suppress output\n"
                "     --no-block       Do not wait until operation finished\n"
-               "     --system         Connect to system bus\n"
-               "     --session        Connect to session bus\n"
+               "     --no-pager       Do not pipe output into a pager.\n"
+               "     --system         Connect to system manager\n"
+               "     --user           Connect to user service manager\n"
                "     --order          When generating graph for dot, show only order\n"
                "     --require        When generating graph for dot, show only requirement\n"
                "     --no-wall        Don't send wall message before halt/power-off/reboot\n"
@@ -4105,7 +4302,7 @@ 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 session termination\n",
+               "  exit                            Ask for user instance termination\n",
                program_invocation_short_name);
 
         return 0;
@@ -4181,11 +4378,13 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
 
         enum {
                 ARG_FAIL = 0x100,
+                ARG_IGNORE_DEPENDENCIES,
                 ARG_VERSION,
-                ARG_SESSION,
+                ARG_USER,
                 ARG_SYSTEM,
                 ARG_GLOBAL,
                 ARG_NO_BLOCK,
+                ARG_NO_PAGER,
                 ARG_NO_WALL,
                 ARG_ORDER,
                 ARG_REQUIRE,
@@ -4194,7 +4393,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 ARG_DEFAULTS,
                 ARG_KILL_MODE,
                 ARG_KILL_WHO,
-                ARG_NO_ASK_PASSWORD
+                ARG_NO_ASK_PASSWORD,
+                ARG_FAILED
         };
 
         static const struct option options[] = {
@@ -4203,12 +4403,15 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "type",      required_argument, NULL, 't'           },
                 { "property",  required_argument, NULL, 'p'           },
                 { "all",       no_argument,       NULL, 'a'           },
+                { "failed",    no_argument,       NULL, ARG_FAILED    },
                 { "full",      no_argument,       NULL, ARG_FULL      },
                 { "fail",      no_argument,       NULL, ARG_FAIL      },
-                { "session",   no_argument,       NULL, ARG_SESSION   },
+                { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES },
+                { "user",      no_argument,       NULL, ARG_USER      },
                 { "system",    no_argument,       NULL, ARG_SYSTEM    },
                 { "global",    no_argument,       NULL, ARG_GLOBAL    },
                 { "no-block",  no_argument,       NULL, ARG_NO_BLOCK  },
+                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
                 { "no-wall",   no_argument,       NULL, ARG_NO_WALL   },
                 { "quiet",     no_argument,       NULL, 'q'           },
                 { "order",     no_argument,       NULL, ARG_ORDER     },
@@ -4270,21 +4473,29 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_FAIL:
-                        arg_fail = true;
+                        arg_job_mode = "fail";
                         break;
 
-                case ARG_SESSION:
-                        arg_session = true;
+                case ARG_IGNORE_DEPENDENCIES:
+                        arg_job_mode = "ignore-dependencies";
+                        break;
+
+                case ARG_USER:
+                        arg_user = true;
                         break;
 
                 case ARG_SYSTEM:
-                        arg_session = false;
+                        arg_user = false;
                         break;
 
                 case ARG_NO_BLOCK:
                         arg_no_block = true;
                         break;
 
+                case ARG_NO_PAGER:
+                        arg_no_pager = true;
+                        break;
+
                 case ARG_NO_WALL:
                         arg_no_wall = true;
                         break;
@@ -4301,6 +4512,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_full = true;
                         break;
 
+                case ARG_FAILED:
+                        arg_failed = true;
+                        break;
+
                 case 'q':
                         arg_quiet = true;
                         break;
@@ -4315,7 +4530,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
 
                 case ARG_GLOBAL:
                         arg_global = true;
-                        arg_session = true;
+                        arg_user = true;
                         break;
 
                 case ARG_DEFAULTS:
@@ -4496,6 +4711,18 @@ static int parse_time_spec(const char *t, usec_t *_u) {
         return 0;
 }
 
+static bool kexec_loaded(void) {
+       bool loaded = false;
+       char *s;
+
+       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) {
+               if (s[0] == '1')
+                       loaded = true;
+               free(s);
+       }
+       return loaded;
+}
+
 static int shutdown_parse_argv(int argc, char *argv[]) {
 
         enum {
@@ -4533,7 +4760,10 @@ static int shutdown_parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'r':
-                        arg_action = ACTION_REBOOT;
+                        if (kexec_loaded())
+                                arg_action = ACTION_KEXEC;
+                        else
+                                arg_action = ACTION_REBOOT;
                         break;
 
                 case 'h':
@@ -4726,7 +4956,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_action = ACTION_POWEROFF;
                         return halt_parse_argv(argc, argv);
                 } else if (strstr(program_invocation_short_name, "reboot")) {
-                        arg_action = ACTION_REBOOT;
+                        if (kexec_loaded())
+                                arg_action = ACTION_KEXEC;
+                        else
+                                arg_action = ACTION_REBOOT;
                         return halt_parse_argv(argc, argv);
                 } else if (strstr(program_invocation_short_name, "shutdown")) {
                         arg_action = ACTION_POWEROFF;
@@ -5211,6 +5444,85 @@ static int runlevel_main(void) {
         return 0;
 }
 
+static void pager_open(void) {
+        int fd[2];
+        const char *pager;
+
+        if (pager_pid > 0)
+                return;
+
+        if (!on_tty() || arg_no_pager)
+                return;
+
+        if ((pager = getenv("PAGER")))
+                if (!*pager || streq(pager, "cat"))
+                        return;
+
+        /* Determine and cache number of columns before we spawn the
+         * pager so that we get the value from the actual tty */
+        columns();
+
+        if (pipe(fd) < 0) {
+                log_error("Failed to create pager pipe: %m");
+                return;
+        }
+
+        pager_pid = fork();
+        if (pager_pid < 0) {
+                log_error("Failed to fork pager: %m");
+                close_pipe(fd);
+                return;
+        }
+
+        /* In the child start the pager */
+        if (pager_pid == 0) {
+
+                dup2(fd[0], STDIN_FILENO);
+                close_pipe(fd);
+
+                setenv("LESS", "FRSX", 0);
+
+                prctl(PR_SET_PDEATHSIG, SIGTERM);
+
+                if (pager) {
+                        execlp(pager, pager, NULL);
+                        execl("/bin/sh", "sh", "-c", pager, NULL);
+                } else {
+                        /* Debian's alternatives command for pagers is
+                         * called 'pager'. Note that we do not call
+                         * sensible-pagers here, since that is just a
+                         * shell script that implements a logic that
+                         * is similar to this one anyway, but is
+                         * Debian-specific. */
+                        execlp("pager", "pager", NULL);
+
+                        execlp("less", "less", NULL);
+                        execlp("more", "more", NULL);
+                }
+
+                log_error("Unable to execute pager: %m");
+                _exit(EXIT_FAILURE);
+        }
+
+        /* Return in the parent */
+        if (dup2(fd[1], STDOUT_FILENO) < 0)
+                log_error("Failed to duplicate pager pipe: %m");
+
+        close_pipe(fd);
+}
+
+static void pager_close(void) {
+        siginfo_t dummy;
+
+        if (pager_pid <= 0)
+                return;
+
+        /* Inform pager that we are done */
+        fclose(stdout);
+        wait_for_terminate(pager_pid, &dummy);
+        pager_pid = 0;
+}
+
 int main(int argc, char*argv[]) {
         int r, retval = EXIT_FAILURE;
         DBusConnection *bus = NULL;
@@ -5236,7 +5548,7 @@ int main(int argc, char*argv[]) {
                 goto finish;
         }
 
-        bus_connect(arg_session ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error);
+        bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error);
 
         switch (arg_action) {
 
@@ -5247,6 +5559,7 @@ int main(int argc, char*argv[]) {
         case ACTION_HALT:
         case ACTION_POWEROFF:
         case ACTION_REBOOT:
+        case ACTION_KEXEC:
                 r = halt_main(bus);
                 break;
 
@@ -5291,5 +5604,7 @@ finish:
 
         strv_free(arg_property);
 
+        pager_close();
+
         return retval;
 }