+static int have_multiple_sessions(
+ DBusConnection *connection,
+ Manager *m,
+ DBusMessage *message,
+ DBusError *error) {
+
+ Session *s;
+
+ assert(m);
+
+ if (hashmap_size(m->sessions) > 1)
+ return true;
+
+ /* Hmm, there's only one session, but let's make sure it
+ * actually belongs to the user who is asking. If not, better
+ * be safe than sorry. */
+
+ s = hashmap_first(m->sessions);
+ if (s) {
+ unsigned long ul;
+
+ ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), error);
+ if (ul == (unsigned long) -1)
+ return -EIO;
+
+ return s->user->uid != ul;
+ }
+
+ return false;
+}
+
+static int send_start_unit(DBusConnection *connection, const char *unit_name, DBusError *error) {
+ DBusMessage *message, *reply;
+ const char *mode = "replace";
+
+ assert(connection);
+ assert(unit_name);
+
+ message = dbus_message_new_method_call(
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartUnit");
+ if (!message)
+ return -ENOMEM;
+
+ if (!dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &unit_name,
+ DBUS_TYPE_STRING, &mode,
+ DBUS_TYPE_INVALID)) {
+ dbus_message_unref(message);
+ return -ENOMEM;
+ }
+
+ reply = dbus_connection_send_with_reply_and_block(connection, message, -1, error);
+ dbus_message_unref(message);
+
+ if (!reply)
+ return -EIO;
+
+ dbus_message_unref(reply);
+ return 0;
+}
+
+static int send_prepare_for(Manager *m, InhibitWhat w, bool _active) {
+ static const char * const signal_name[_INHIBIT_WHAT_MAX] = {
+ [INHIBIT_SHUTDOWN] = "PrepareForShutdown",
+ [INHIBIT_SLEEP] = "PrepareForSleep"
+ };
+
+ dbus_bool_t active = _active;
+ DBusMessage *message;
+ int r = 0;
+
+ assert(m);
+ assert(w >= 0);
+ assert(w < _INHIBIT_WHAT_MAX);
+ assert(signal_name[w]);
+
+ message = dbus_message_new_signal("/org/freedesktop/login1", "org.freedesktop.login1.Manager", signal_name[w]);
+ if (!message)
+ return -ENOMEM;
+
+ if (!dbus_message_append_args(message, DBUS_TYPE_BOOLEAN, &active, DBUS_TYPE_INVALID) ||
+ !dbus_connection_send(m->bus, message, NULL))
+ r = -ENOMEM;
+
+ dbus_message_unref(message);
+ return r;
+}
+
+static int delay_shutdown_or_sleep(Manager *m, InhibitWhat w, const char *unit_name) {
+ assert(m);
+ assert(w >= 0);
+ assert(w < _INHIBIT_WHAT_MAX);
+
+ /* Tell everybody to prepare for shutdown/sleep */
+ send_prepare_for(m, w, true);
+
+ /* Update timestamp for timeout */
+ if (!m->delayed_unit)
+ m->delayed_timestamp = now(CLOCK_MONOTONIC);
+
+ /* Remember what we want to do, possibly overriding what kind
+ * of unit we previously queued. */
+ m->delayed_unit = unit_name;
+ m->delayed_what = w;
+
+ return 0;
+}
+
+static int bus_manager_can_shutdown_or_sleep(
+ Manager *m,
+ DBusConnection *connection,
+ DBusMessage *message,
+ InhibitWhat w,
+ const char *action,
+ const char *action_multiple_sessions,
+ const char *action_ignore_inhibit,
+ const char *sleep_type,
+ DBusError *error,
+ DBusMessage **_reply) {
+
+ bool multiple_sessions, challenge, blocked, b;
+ const char *result;
+ DBusMessage *reply = NULL;
+ int r;
+
+ assert(m);
+ assert(connection);
+ assert(message);
+ assert(w >= 0);
+ assert(w <= _INHIBIT_WHAT_MAX);
+ assert(action);
+ assert(action_multiple_sessions);
+ assert(action_ignore_inhibit);
+ assert(error);
+ assert(_reply);
+
+ if (sleep_type) {
+ r = can_sleep(sleep_type);
+ if (r < 0)
+ return r;
+
+ result = "na";
+ goto finish;
+ }
+
+ r = have_multiple_sessions(connection, m, message, error);
+ if (r < 0)
+ return r;
+
+ multiple_sessions = r > 0;
+ blocked = manager_is_inhibited(m, w, INHIBIT_BLOCK, NULL);
+
+ if (multiple_sessions) {
+ r = verify_polkit(connection, message, action_multiple_sessions, false, &challenge, error);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ result = "yes";
+ else if (challenge)
+ result = "challenge";
+ else
+ result = "no";
+ }
+
+ if (blocked) {
+ r = verify_polkit(connection, message, action_ignore_inhibit, false, &challenge, error);
+ if (r < 0)
+ return r;
+
+ if (r > 0 && !result)
+ result = "yes";
+ else if (challenge && (!result || streq(result, "yes")))
+ result = "challenge";
+ else
+ result = "no";
+ }
+
+ if (!multiple_sessions && !blocked) {
+ /* If neither inhibit nor multiple sessions
+ * apply then just check the normal policy */
+
+ r = verify_polkit(connection, message, action, false, &challenge, error);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ result = "yes";
+ else if (challenge)
+ result = "challenge";
+ else
+ result = "no";
+ }
+
+finish:
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ return -ENOMEM;
+
+ b = dbus_message_append_args(
+ reply,
+ DBUS_TYPE_STRING, &result,
+ DBUS_TYPE_INVALID);
+ if (!b) {
+ dbus_message_unref(reply);
+ return -ENOMEM;
+ }
+
+ *_reply = reply;
+ return 0;
+}
+
+static int bus_manager_do_shutdown_or_sleep(
+ Manager *m,
+ DBusConnection *connection,
+ DBusMessage *message,
+ const char *unit_name,
+ InhibitWhat w,
+ const char *action,
+ const char *action_multiple_sessions,
+ const char *action_ignore_inhibit,
+ const char *sleep_type,
+ DBusError *error,
+ DBusMessage **_reply) {
+
+ dbus_bool_t interactive;
+ bool multiple_sessions, blocked, delayed;
+ DBusMessage *reply = NULL;
+ int r;
+
+ assert(m);
+ assert(connection);
+ assert(message);
+ assert(unit_name);
+ assert(w >= 0);
+ assert(w <= _INHIBIT_WHAT_MAX);
+ assert(action);
+ assert(action_multiple_sessions);
+ assert(action_ignore_inhibit);
+ assert(error);
+ assert(_reply);
+
+ if (!dbus_message_get_args(
+ message,
+ error,
+ DBUS_TYPE_BOOLEAN, &interactive,
+ DBUS_TYPE_INVALID))
+ return -EINVAL;
+
+ if (sleep_type) {
+ r = can_sleep(sleep_type);
+ if (r < 0)
+ return r;
+
+ if (r == 0)
+ return -ENOTSUP;
+ }
+
+ r = have_multiple_sessions(connection, m, message, error);
+ if (r < 0)
+ return r;
+
+ multiple_sessions = r > 0;
+ blocked = manager_is_inhibited(m, w, INHIBIT_BLOCK, NULL);
+
+ if (multiple_sessions) {
+ r = verify_polkit(connection, message, action_multiple_sessions, interactive, NULL, error);
+ if (r < 0)
+ return r;
+ }
+
+ if (blocked) {
+ r = verify_polkit(connection, message, action_ignore_inhibit, interactive, NULL, error);
+ if (r < 0)
+ return r;
+ }
+
+ if (!multiple_sessions && !blocked) {
+ r = verify_polkit(connection, message, action, interactive, NULL, error);
+ if (r < 0)
+ return r;
+ }
+
+ delayed =
+ m->inhibit_delay_max > 0 &&
+ manager_is_inhibited(m, w, INHIBIT_DELAY, NULL);
+
+ if (delayed) {
+ /* Shutdown is delayed, keep in mind what we
+ * want to do, and start a timeout */
+ r = delay_shutdown_or_sleep(m, w, unit_name);
+ } else
+ /* Shutdown is not delayed, execute it
+ * immediately */
+ r = send_start_unit(connection, unit_name, error);
+
+ if (r < 0)
+ return r;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply)
+ return -ENOMEM;
+
+ *_reply = reply;
+ return 0;
+}
+
+static const BusProperty bus_login_manager_properties[] = {
+ { "ControlGroupHierarchy", bus_property_append_string, "s", offsetof(Manager, cgroup_path), true },
+ { "Controllers", bus_property_append_strv, "as", offsetof(Manager, controllers), true },
+ { "ResetControllers", bus_property_append_strv, "as", offsetof(Manager, reset_controllers), true },
+ { "NAutoVTs", bus_property_append_unsigned, "u", offsetof(Manager, n_autovts) },
+ { "KillOnlyUsers", bus_property_append_strv, "as", offsetof(Manager, kill_only_users), true },
+ { "KillExcludeUsers", bus_property_append_strv, "as", offsetof(Manager, kill_exclude_users), true },
+ { "KillUserProcesses", bus_property_append_bool, "b", offsetof(Manager, kill_user_processes) },
+ { "IdleHint", bus_manager_append_idle_hint, "b", 0 },
+ { "IdleSinceHint", bus_manager_append_idle_hint_since, "t", 0 },
+ { "IdleSinceHintMonotonic", bus_manager_append_idle_hint_since, "t", 0 },
+ { "BlockInhibited", bus_manager_append_inhibited, "s", 0 },
+ { "DelayInhibited", bus_manager_append_inhibited, "s", 0 },
+ { "InhibitDelayMaxUSec", bus_property_append_usec, "t", offsetof(Manager, inhibit_delay_max) },
+ { NULL, }
+};
+