chiark / gitweb /
systemctl: honour inhibitors only when running unprivileged
[elogind.git] / src / systemctl / systemctl.c
index 2ebfff8daf94e3ef71491401eb3aeb3cdc585df0..91467cc0855835551f0ed48cc2a072a51fbcae04 100644 (file)
@@ -78,6 +78,7 @@ static bool arg_no_pager = false;
 static bool arg_no_wtmp = false;
 static bool arg_no_wall = false;
 static bool arg_no_reload = false;
+static bool arg_ignore_inhibitors = false;
 static bool arg_dry = false;
 static bool arg_quiet = false;
 static bool arg_full = false;
@@ -1032,12 +1033,14 @@ finish:
 }
 
 static int load_unit(DBusConnection *bus, char **args) {
-        int r = 0;
-        char **name, *n;
+        char **name;
 
         assert(args);
 
         STRV_FOREACH(name, args+1) {
+                _cleanup_free_ char *n = NULL;
+                int r;
+
                 n = unit_name_mangle(*name);
                 r = bus_method_call_with_reply (
                                 bus,
@@ -1049,18 +1052,14 @@ static int load_unit(DBusConnection *bus, char **args) {
                                 NULL,
                                 DBUS_TYPE_STRING, n ? &n : name,
                                 DBUS_TYPE_INVALID);
-                free(n);
-                if (r)
-                        goto finish;
+                if (r < 0)
+                        return r;
         }
 
-finish:
-        return r;
+        return 0;
 }
 
 static int cancel_job(DBusConnection *bus, char **args) {
-        DBusMessage *reply = NULL;
-        int r = 0;
         char **name;
 
         assert(args);
@@ -1069,54 +1068,30 @@ static int cancel_job(DBusConnection *bus, char **args) {
                 return daemon_reload(bus, args);
 
         STRV_FOREACH(name, args+1) {
-                unsigned id;
-                const char *path;
+                uint32_t id;
+                int r;
 
-                r = safe_atou(*name, &id);
+                r = safe_atou32(*name, &id);
                 if (r < 0) {
                         log_error("Failed to parse job id: %s", strerror(-r));
-                        goto finish;
+                        return r;
                 }
-                assert_cc(sizeof(uint32_t) == sizeof(id));
 
-                r = bus_method_call_with_reply (
+                r = bus_method_call_with_reply(
                                 bus,
                                 "org.freedesktop.systemd1",
                                 "/org/freedesktop/systemd1",
                                 "org.freedesktop.systemd1.Manager",
-                                "GetJob",
-                                &reply,
-                                NULL,
-                                DBUS_TYPE_UINT32, &id,
-                                DBUS_TYPE_INVALID);
-                if (r)
-                        goto finish;
-
-                if (!dbus_message_get_args(reply, NULL,
-                                           DBUS_TYPE_OBJECT_PATH, &path,
-                                           DBUS_TYPE_INVALID)) {
-                        log_error("Failed to parse reply");
-                        dbus_message_unref(reply);
-                        r = -EIO;
-                        goto finish;
-                }
-                dbus_message_unref(reply);
-
-                r = bus_method_call_with_reply (
-                                bus,
-                                "org.freedesktop.systemd1",
-                                path,
-                                "org.freedesktop.systemd1.Job",
-                                "Cancel",
+                                "CancelJob",
                                 NULL,
                                 NULL,
+                                DBUS_TYPE_UINT32, &id,
                                 DBUS_TYPE_INVALID);
-                if (r)
-                        goto finish;
+                if (r < 0)
+                        return r;
         }
 
-finish:
-        return r;
+        return 0;
 }
 
 static bool need_daemon_reload(DBusConnection *bus, const char *unit) {
@@ -1366,20 +1341,25 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) {
         return r;
 }
 
-static int check_one_unit(DBusConnection *bus, char *name, char **check_states, bool quiet) {
-        DBusMessage *reply = NULL;
+static int check_one_unit(DBusConnection *bus, const char *name, char **check_states, bool quiet) {
+        _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
         DBusMessageIter iter, sub;
         const char
                 *interface = "org.freedesktop.systemd1.Unit",
                 *property = "ActiveState";
-        const char *path = NULL;
-        const char *state;
+        const char *state, *path;
+        _cleanup_free_ char *n = NULL;
+        DBusError error;
         int r;
-        char *n;
 
         assert(name);
 
+        dbus_error_init(&error);
+
         n = unit_name_mangle(name);
+        if (!n)
+                return log_oom();
+
         r = bus_method_call_with_reply (
                         bus,
                         "org.freedesktop.systemd1",
@@ -1387,26 +1367,28 @@ static int check_one_unit(DBusConnection *bus, char *name, char **check_states,
                         "org.freedesktop.systemd1.Manager",
                         "GetUnit",
                         &reply,
-                        NULL,
-                        DBUS_TYPE_STRING, n ? &n : &name,
+                        &error,
+                        DBUS_TYPE_STRING, &n,
                         DBUS_TYPE_INVALID);
-        free(n);
-        if (r) {
-                if ((r != -ENOMEM) && (!quiet))
+        if (r < 0) {
+                dbus_error_free(&error);
+
+                if (!quiet)
                         puts("unknown");
-                goto finish;
+                return 0;
         }
 
         if (!dbus_message_get_args(reply, NULL,
                                    DBUS_TYPE_OBJECT_PATH, &path,
                                    DBUS_TYPE_INVALID)) {
                 log_error("Failed to parse reply.");
-                r = -EIO;
-                goto finish;
+                return -EIO;
         }
 
         dbus_message_unref(reply);
-        r = bus_method_call_with_reply (
+        reply = NULL;
+
+        r = bus_method_call_with_reply(
                         bus,
                         "org.freedesktop.systemd1",
                         path,
@@ -1417,22 +1399,23 @@ static int check_one_unit(DBusConnection *bus, char *name, char **check_states,
                         DBUS_TYPE_STRING, &interface,
                         DBUS_TYPE_STRING, &property,
                         DBUS_TYPE_INVALID);
-        if (r)
-                goto finish;
+        if (r < 0) {
+                if (!quiet)
+                        puts("unknown");
+                return 0;
+        }
 
         if (!dbus_message_iter_init(reply, &iter) ||
             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
                 log_error("Failed to parse reply.");
-                r = -EIO;
-                goto finish;
+                return r;
         }
 
         dbus_message_iter_recurse(&iter, &sub);
 
         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
                 log_error("Failed to parse reply.");
-                r = -EIO;
-                goto finish;
+                return r;
         }
 
         dbus_message_iter_get_basic(&sub, &state);
@@ -1440,16 +1423,7 @@ static int check_one_unit(DBusConnection *bus, char *name, char **check_states,
         if (!quiet)
                 puts(state);
 
-        if (strv_find(check_states, state))
-                r = 0;
-        else
-                r = 3; /* According to LSB: "program is not running" */
-
-finish:
-        if (reply)
-                dbus_message_unref(reply);
-
-        return r;
+        return strv_find(check_states, state) ? 1 : 0;
 }
 
 static void check_triggering_units(
@@ -1458,7 +1432,6 @@ static void check_triggering_units(
 
         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
         DBusMessageIter iter, sub;
-        char *service_trigger = NULL;
         const char *interface = "org.freedesktop.systemd1.Unit",
                    *triggered_by_property = "TriggeredBy";
 
@@ -1478,7 +1451,7 @@ static void check_triggering_units(
                 return;
         }
 
-        r = bus_method_call_with_reply (
+        r = bus_method_call_with_reply(
                         bus,
                         "org.freedesktop.systemd1",
                         unit_path,
@@ -1489,7 +1462,7 @@ static void check_triggering_units(
                         DBUS_TYPE_STRING, &interface,
                         DBUS_TYPE_STRING, &triggered_by_property,
                         DBUS_TYPE_INVALID);
-        if (r)
+        if (r < 0)
                 return;
 
         if (!dbus_message_iter_init(reply, &iter) ||
@@ -1503,7 +1476,12 @@ static void check_triggering_units(
         sub = iter;
 
         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
-                char **check_states = NULL;
+                const char * const check_states[] = {
+                        "active",
+                        "reloading",
+                        NULL
+                };
+                const char *service_trigger;
 
                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
                         log_error("Failed to parse reply.");
@@ -1512,16 +1490,15 @@ static void check_triggering_units(
 
                 dbus_message_iter_get_basic(&sub, &service_trigger);
 
-                check_states = strv_new("active", "reloading", NULL);
-                r = check_one_unit(bus, service_trigger, check_states, true);
-                strv_free(check_states);
+                r = check_one_unit(bus, service_trigger, (char**) check_states, true);
                 if (r < 0)
                         return;
-                if (r == 0) {
+                if (r > 0) {
                         if (print_warning_label) {
                                 log_warning("Warning: Stopping %s, but it can still be activated by:", unit_name);
                                 print_warning_label = false;
                         }
+
                         log_warning("  %s", service_trigger);
                 }
 
@@ -1761,6 +1738,9 @@ static int reboot_with_logind(DBusConnection *bus, enum action a) {
         const char *method;
         dbus_bool_t interactive = true;
 
+        if (!bus)
+                return -EIO;
+
         polkit_agent_open_if_enabled();
 
         switch (a) {
@@ -1804,6 +1784,113 @@ static int reboot_with_logind(DBusConnection *bus, enum action a) {
 #endif
 }
 
+static int check_inhibitors(DBusConnection *bus, enum action a) {
+#ifdef HAVE_LOGIND
+        _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
+        DBusMessageIter iter, sub, sub2;
+        int r;
+        unsigned c = 0;
+
+        if (!bus)
+                return 0;
+
+        if (arg_ignore_inhibitors || arg_force > 0)
+                return 0;
+
+        if (arg_when > 0)
+                return 0;
+
+        if (geteuid() == 0)
+                return 0;
+
+        if (!on_tty())
+                return 0;
+
+        r = bus_method_call_with_reply(
+                        bus,
+                        "org.freedesktop.login1",
+                        "/org/freedesktop/login1",
+                        "org.freedesktop.login1.Manager",
+                        "ListInhibitors",
+                        &reply,
+                        NULL,
+                        DBUS_TYPE_INVALID);
+        if (r < 0)
+                /* If logind is not around, then there are no inhibitors... */
+                return 0;
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
+                log_error("Failed to parse reply.");
+                return -EIO;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *what, *who, *why, *mode;
+                uint32_t uid, pid;
+                _cleanup_strv_free_ char **sv = NULL;
+                _cleanup_free_ char *comm = NULL;
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+                        log_error("Failed to parse reply.");
+                        return -EIO;
+                }
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &what, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &who, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &why, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &mode, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &pid, false) < 0) {
+                        log_error("Failed to parse reply.");
+                        return -EIO;
+                }
+
+                if (!streq(mode, "block"))
+                        goto next;
+
+                sv = strv_split(what, ":");
+                if (!sv)
+                        return log_oom();
+
+                if (!strv_contains(sv,
+                                  a == ACTION_HALT ||
+                                  a == ACTION_POWEROFF ||
+                                  a == ACTION_REBOOT ||
+                                  a == ACTION_KEXEC ? "shutdown" : "sleep"))
+                        goto next;
+
+                get_process_comm(pid, &comm);
+                log_warning("Operation inhibited by \"%s\" (PID %lu \"%s\", UID %lu), reason is \"%s\".", who, (unsigned long) pid, strna(comm), (unsigned long) uid, why);
+                c++;
+
+        next:
+                dbus_message_iter_next(&sub);
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (c <= 0)
+                return 0;
+
+        log_error("Please try again after closing inhibitors or ignore them with 'systemctl %s -i'.",
+                  a == ACTION_HALT ? "halt" :
+                  a == ACTION_POWEROFF ? "poweroff" :
+                  a == ACTION_REBOOT ? "reboot" :
+                  a == ACTION_KEXEC ? "kexec" :
+                  a == ACTION_SUSPEND ? "suspend" :
+                  a == ACTION_HIBERNATE ? "hibernate" : "hybrid-sleep");
+
+        return -EPERM;
+#else
+        return 0;
+#endif
+}
+
 static int start_special(DBusConnection *bus, char **args) {
         enum action a;
         int r;
@@ -1812,6 +1899,10 @@ static int start_special(DBusConnection *bus, char **args) {
 
         a = verb_to_action(args[0]);
 
+        r = check_inhibitors(bus, a);
+        if (r < 0)
+                return r;
+
         if (arg_force >= 2 && geteuid() != 0) {
                 log_error("Must be root.");
                 return -EPERM;
@@ -1851,6 +1942,12 @@ static int start_special(DBusConnection *bus, char **args) {
 }
 
 static int check_unit_active(DBusConnection *bus, char **args) {
+        const char * const check_states[] = {
+                "active",
+                "reloading",
+                NULL
+        };
+
         char **name;
         int r = 3; /* According to LSB: "program is not running" */
 
@@ -1858,12 +1955,12 @@ static int check_unit_active(DBusConnection *bus, char **args) {
         assert(args);
 
         STRV_FOREACH(name, args+1) {
-                char **check_states = strv_new("active", "reloading", NULL);
-                int state = check_one_unit(bus, *name, check_states, arg_quiet);
-                strv_free(check_states);
+                int state;
+
+                state = check_one_unit(bus, *name, (char**) check_states, arg_quiet);
                 if (state < 0)
                         return state;
-                if (state == 0)
+                if (state > 0)
                         r = 0;
         }
 
@@ -1871,6 +1968,11 @@ static int check_unit_active(DBusConnection *bus, char **args) {
 }
 
 static int check_unit_failed(DBusConnection *bus, char **args) {
+        const char * const check_states[] = {
+                "failed",
+                NULL
+        };
+
         char **name;
         int r = 1;
 
@@ -1878,12 +1980,12 @@ static int check_unit_failed(DBusConnection *bus, char **args) {
         assert(args);
 
         STRV_FOREACH(name, args+1) {
-                char **check_states = strv_new("failed", NULL);
-                int state = check_one_unit(bus, *name, check_states, arg_quiet);
-                strv_free(check_states);
+                int state;
+
+                state = check_one_unit(bus, *name, (char**) check_states, arg_quiet);
                 if (state < 0)
                         return state;
-                if (state == 0)
+                if (state > 0)
                         r = 0;
         }
 
@@ -1891,17 +1993,21 @@ static int check_unit_failed(DBusConnection *bus, char **args) {
 }
 
 static int kill_unit(DBusConnection *bus, char **args) {
+        char **name;
         int r = 0;
-        char **name, *n;
 
+        assert(bus);
         assert(args);
 
         if (!arg_kill_who)
                 arg_kill_who = "all";
 
         STRV_FOREACH(name, args+1) {
+                _cleanup_free_ char *n = NULL;
+
                 n = unit_name_mangle(*name);
-                r = bus_method_call_with_reply (
+
+                r = bus_method_call_with_reply(
                                 bus,
                                 "org.freedesktop.systemd1",
                                 "/org/freedesktop/systemd1",
@@ -1913,8 +2019,7 @@ static int kill_unit(DBusConnection *bus, char **args) {
                                 DBUS_TYPE_STRING, &arg_kill_who,
                                 DBUS_TYPE_INT32, &arg_signal,
                                 DBUS_TYPE_INVALID);
-                free(n);
-                if (r)
+                if (r < 0)
                         return r;
         }
         return 0;
@@ -3062,7 +3167,7 @@ finish:
 }
 
 static int snapshot(DBusConnection *bus, char **args) {
-        DBusMessage *reply = NULL;
+        _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
         DBusError error;
         int r;
         dbus_bool_t cleanup = FALSE;
@@ -3071,14 +3176,15 @@ static int snapshot(DBusConnection *bus, char **args) {
                 *name = "", *path, *id,
                 *interface = "org.freedesktop.systemd1.Unit",
                 *property = "Id";
-        char *n;
+        _cleanup_free_ char *n = NULL;
 
         dbus_error_init(&error);
 
-        if (strv_length(args) > 1)
+        if (strv_length(args) > 1) {
                 name = args[1];
+                n = unit_name_mangle(name);
+        }
 
-        n = unit_name_mangle(name);
         r = bus_method_call_with_reply (
                         bus,
                         "org.freedesktop.systemd1",
@@ -3090,8 +3196,7 @@ static int snapshot(DBusConnection *bus, char **args) {
                         DBUS_TYPE_STRING, n ? (const char**) &n : &name,
                         DBUS_TYPE_BOOLEAN, &cleanup,
                         DBUS_TYPE_INVALID);
-        free(n);
-        if (r)
+        if (r < 0)
                 goto finish;
 
         if (!dbus_message_get_args(reply, &error,
@@ -3103,6 +3208,8 @@ static int snapshot(DBusConnection *bus, char **args) {
         }
 
         dbus_message_unref(reply);
+        reply = NULL;
+
         r = bus_method_call_with_reply (
                         bus,
                         "org.freedesktop.systemd1",
@@ -3114,7 +3221,7 @@ static int snapshot(DBusConnection *bus, char **args) {
                         DBUS_TYPE_STRING, &interface,
                         DBUS_TYPE_STRING, &property,
                         DBUS_TYPE_INVALID);
-        if (r)
+        if (r < 0)
                 goto finish;
 
         if (!dbus_message_iter_init(reply, &iter) ||
@@ -3138,69 +3245,36 @@ static int snapshot(DBusConnection *bus, char **args) {
                 puts(id);
 
 finish:
-        if (reply)
-                dbus_message_unref(reply);
-
         dbus_error_free(&error);
 
         return r;
 }
 
 static int delete_snapshot(DBusConnection *bus, char **args) {
-        DBusMessage *reply = NULL;
-        int r = 0;
-        DBusError error;
         char **name;
 
         assert(args);
 
-        dbus_error_init(&error);
-
         STRV_FOREACH(name, args+1) {
-                const char *path = NULL;
-                char *n;
+                _cleanup_free_ char *n = NULL;
+                int r;
 
                 n = unit_name_mangle(*name);
-                r = bus_method_call_with_reply (
+                r = bus_method_call_with_reply(
                                 bus,
                                 "org.freedesktop.systemd1",
                                 "/org/freedesktop/systemd1",
                                 "org.freedesktop.systemd1.Manager",
-                                "GetUnit",
-                                &reply,
-                                NULL,
-                                DBUS_TYPE_STRING, n ? &n : name,
-                                DBUS_TYPE_INVALID);
-                free(n);
-                if (r)
-                        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;
-                        dbus_message_unref(reply);
-                        dbus_error_free(&error);
-                        goto finish;
-                }
-                dbus_message_unref(reply);
-
-                r = bus_method_call_with_reply (
-                                bus,
-                                "org.freedesktop.systemd1",
-                                path,
-                                "org.freedesktop.systemd1.Snapshot",
-                                "Remove",
+                                "RemoveSnapshot",
                                 NULL,
                                 NULL,
+                                DBUS_TYPE_STRING, n ? &n : name,
                                 DBUS_TYPE_INVALID);
-                if (r)
-                        goto finish;
+                if (r < 0)
+                        return r;
         }
 
-finish:
-        return r;
+        return 0;
 }
 
 static int daemon_reload(DBusConnection *bus, char **args) {
@@ -3398,6 +3472,7 @@ static int set_environment(DBusConnection *bus, char **args) {
         int r;
 
         assert(bus);
+        assert(args);
 
         dbus_error_init(&error);
 
@@ -3952,6 +4027,8 @@ static int systemctl_help(void) {
                "                      pending\n"
                "     --ignore-dependencies\n"
                "                      When queueing a new job, ignore all its dependencies\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"
                "  -H --host=[USER@]HOST\n"
@@ -4162,6 +4239,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "full",      no_argument,       NULL, ARG_FULL      },
                 { "fail",      no_argument,       NULL, ARG_FAIL      },
                 { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES },
+                { "ignore-inhibitors", no_argument, NULL, 'i'         },
                 { "user",      no_argument,       NULL, ARG_USER      },
                 { "system",    no_argument,       NULL, ARG_SYSTEM    },
                 { "global",    no_argument,       NULL, ARG_GLOBAL    },
@@ -4191,7 +4269,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:Pn:o:", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:Pn:o:i", options, NULL)) >= 0) {
 
                 switch (c) {
 
@@ -4357,6 +4435,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         }
                         break;
 
+                case 'i':
+                        arg_ignore_inhibitors = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -5229,6 +5311,10 @@ static _noreturn_ void halt_now(enum action a) {
 static int halt_main(DBusConnection *bus) {
         int r;
 
+        r = check_inhibitors(bus, arg_action);
+        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
@@ -5236,7 +5322,7 @@ static int halt_main(DBusConnection *bus) {
 
                 if (arg_when <= 0 &&
                     !arg_dry &&
-                    !arg_force &&
+                    arg_force <= 0 &&
                     (arg_action == ACTION_POWEROFF ||
                      arg_action == ACTION_REBOOT)) {
                         r = reboot_with_logind(bus, arg_action);