chiark / gitweb /
systemctl: add new "get-cgroup-attr" to query current cgroup attribute value
[elogind.git] / src / systemctl / systemctl.c
index 76c67f20c8bdf8df4dcaf2bc4b5c970e768077e2..f3d661d2b53cd963dc8ec5fdff42ad439068a70e 100644 (file)
@@ -38,6 +38,7 @@
 
 #include <systemd/sd-daemon.h>
 #include <systemd/sd-shutdown.h>
+#include <systemd/sd-login.h>
 
 #include "log.h"
 #include "util.h"
@@ -671,7 +672,7 @@ static int list_unit_files(DBusConnection *bus, char **args) {
                         UnitFileList *u;
                         const char *state;
 
-                        assert(dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT);
+                        assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT);
 
                         if (c >= n_units) {
                                 UnitFileList *w;
@@ -709,6 +710,216 @@ static int list_unit_files(DBusConnection *bus, char **args) {
         return 0;
 }
 
+static int list_dependencies_print(const char *name, int level, unsigned int branches, bool last) {
+        int i;
+        _cleanup_free_ char *n = NULL;
+        size_t len = 0;
+        size_t max_len = MAX(columns(),20);
+
+        for (i = level - 1; i >= 0; i--) {
+                len += 2;
+                if(len > max_len - 3 && !arg_full) {
+                        printf("%s...\n",max_len % 2 ? "" : " ");
+                        return 0;
+                }
+                printf("%s", draw_special_char(branches & (1 << i) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
+        }
+        len += 2;
+        if(len > max_len - 3 && !arg_full) {
+                printf("%s...\n",max_len % 2 ? "" : " ");
+                return 0;
+        }
+        printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
+
+        if(arg_full){
+                printf("%s\n", name);
+                return 0;
+        }
+
+        n = ellipsize(name, max_len-len, 100);
+        if(!n)
+                return log_oom();
+
+        printf("%s\n", n);
+        return 0;
+}
+
+static int list_dependencies_get_dependencies(DBusConnection *bus, const char *name, char ***deps) {
+        static const char dependencies[] =
+                "Requires\0"
+                "RequiresOverridable\0"
+                "Requisite\0"
+                "RequisiteOverridable\0"
+                "Wants\0";
+
+        _cleanup_free_ char *path;
+        const char *interface = "org.freedesktop.systemd1.Unit";
+
+        _cleanup_dbus_message_unref_  DBusMessage *reply = NULL;
+        DBusMessageIter iter, sub, sub2, sub3;
+
+        int r = 0;
+        char **ret = NULL;
+
+        assert(bus);
+        assert(name);
+        assert(deps);
+
+        path = unit_dbus_path_from_name(name);
+        if (path == NULL) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        r = bus_method_call_with_reply(
+                bus,
+                "org.freedesktop.systemd1",
+                path,
+                "org.freedesktop.DBus.Properties",
+                "GetAll",
+                &reply,
+                NULL,
+                DBUS_TYPE_STRING, &interface,
+                DBUS_TYPE_INVALID);
+        if (r < 0)
+                goto finish;
+
+        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_DICT_ENTRY) {
+                log_error("Failed to parse reply.");
+                r = -EIO;
+                goto finish;
+        }
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+                const char *prop;
+
+                assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
+                        log_error("Failed to parse reply.");
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_iter_recurse(&sub2, &sub3);
+                dbus_message_iter_next(&sub);
+
+                if (!nulstr_contains(dependencies, prop))
+                        continue;
+
+                if (dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_ARRAY) {
+                        if (dbus_message_iter_get_element_type(&sub3) == DBUS_TYPE_STRING) {
+                                DBusMessageIter sub4;
+                                dbus_message_iter_recurse(&sub3, &sub4);
+
+                                while (dbus_message_iter_get_arg_type(&sub4) != DBUS_TYPE_INVALID) {
+                                        const char *s;
+
+                                        assert(dbus_message_iter_get_arg_type(&sub4) == DBUS_TYPE_STRING);
+                                        dbus_message_iter_get_basic(&sub4, &s);
+
+                                        r = strv_extend(&ret, s);
+                                        if (r < 0) {
+                                                log_oom();
+                                                goto finish;
+                                        }
+
+                                        dbus_message_iter_next(&sub4);
+                                }
+                        }
+                }
+        }
+finish:
+        if (r < 0)
+                strv_free(ret);
+        else
+                *deps = ret;
+        return r;
+}
+
+static int list_dependencies_compare(const void *_a, const void *_b) {
+        const char **a = (const char**) _a, **b = (const char**) _b;
+        if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET)
+                return 1;
+        if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET)
+                return -1;
+        return strcasecmp(*a, *b);
+}
+
+static int list_dependencies_one(DBusConnection *bus, const char *name, int level, char **units, unsigned int branches) {
+        char **deps = NULL;
+        char **c;
+        char **u = NULL;
+        int r = 0;
+
+        u = strv_append(units, name);
+        if(!u)
+                return log_oom();
+
+        r = list_dependencies_get_dependencies(bus, name, &deps);
+        if (r < 0)
+                goto finish;
+
+        qsort(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
+
+        STRV_FOREACH(c, deps) {
+                if (strv_contains(u, *c)) {
+                        r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1);
+                        if(r < 0)
+                                goto finish;
+                        continue;
+                }
+
+                r = list_dependencies_print(*c, level, branches, c[1] == NULL);
+                if(r < 0)
+                        goto finish;
+
+                if (arg_all || unit_name_to_type(*c) == UNIT_TARGET) {
+                       r = list_dependencies_one(bus, *c, level + 1, u, (branches << 1) | (c[1] == NULL ? 0 : 1));
+                       if(r < 0)
+                                goto finish;
+                }
+        }
+        r = 0;
+finish:
+        strv_free(deps);
+        strv_free(u);
+
+        return r;
+}
+
+static int list_dependencies(DBusConnection *bus, char **args) {
+        _cleanup_free_ char *unit = NULL;
+        const char *u;
+
+        assert(bus);
+
+        if (args[1]) {
+                unit = unit_name_mangle(args[1]);
+                if (!unit)
+                        return log_oom();
+                u = unit;
+        } else
+                u = SPECIAL_DEFAULT_TARGET;
+
+        pager_open_if_enabled();
+
+        puts(u);
+
+        return list_dependencies_one(bus, u, 0, NULL, 0);
+}
+
 static int dot_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
 
         static const char * const colors[] = {
@@ -1041,7 +1252,7 @@ static bool need_daemon_reload(DBusConnection *bus, const char *unit) {
         /* We ignore all errors here, since this is used to show a warning only */
 
         n = unit_name_mangle(unit);
-        if (n)
+        if (!n)
                 return log_oom();
 
         r = bus_method_call_with_reply (
@@ -1120,7 +1331,6 @@ 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, *unit;
-                dbus_bool_t success = true;
 
                 if (dbus_message_get_args(message, &error,
                                           DBUS_TYPE_UINT32, &id,
@@ -1128,10 +1338,8 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me
                                           DBUS_TYPE_STRING, &unit,
                                           DBUS_TYPE_STRING, &result,
                                           DBUS_TYPE_INVALID)) {
-                        char *p;
 
-                        p = set_remove(d->set, (char*) path);
-                        free(p);
+                        free(set_remove(d->set, (char*) path));
 
                         if (!isempty(result))
                                 d->result = strdup(result);
@@ -1148,40 +1356,16 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me
                                           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);
+                        free(set_remove(d->set, (char*) path));
 
                         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,
-                                          DBUS_TYPE_BOOLEAN, &success,
-                                          DBUS_TYPE_INVALID)) {
-                        char *p;
-
-                        /* Compatibility with older systemd versions <
-                         * 19 during upgrades. This should be dropped
-                         * one day */
-
-                        p = set_remove(d->set, (char*) path);
-                        free(p);
-
-                        if (!success)
-                                d->result = strdup("failed");
-
-                        goto finish;
-                }
 #endif
 
                 log_error("Failed to parse message: %s", bus_error_message(&error));
@@ -1721,6 +1905,8 @@ static int check_inhibitors(DBusConnection *bus, enum action a) {
         DBusMessageIter iter, sub, sub2;
         int r;
         unsigned c = 0;
+        _cleanup_strv_free_ char **sessions = NULL;
+        char **s;
 
         if (!bus)
                 return 0;
@@ -1762,7 +1948,7 @@ static int check_inhibitors(DBusConnection *bus, enum action a) {
                 const char *what, *who, *why, *mode;
                 uint32_t uid, pid;
                 _cleanup_strv_free_ char **sv = NULL;
-                _cleanup_free_ char *comm = NULL;
+                _cleanup_free_ char *comm = NULL, *user = NULL;
 
                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
                         log_error("Failed to parse reply.");
@@ -1796,7 +1982,9 @@ static int check_inhibitors(DBusConnection *bus, enum action a) {
                         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);
+                user = uid_to_name(uid);
+                log_warning("Operation inhibited by \"%s\" (PID %lu \"%s\", user %s), reason is \"%s\".",
+                            who, (unsigned long) pid, strna(comm), strna(user), why);
                 c++;
 
         next:
@@ -1805,10 +1993,34 @@ static int check_inhibitors(DBusConnection *bus, enum action a) {
 
         dbus_message_iter_recurse(&iter, &sub);
 
+        /* Check for current sessions */
+        sd_get_sessions(&sessions);
+        STRV_FOREACH(s, sessions) {
+                uid_t uid;
+                _cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL;
+
+                if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid())
+                        continue;
+
+                if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user"))
+                        continue;
+
+                if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty")))
+                        continue;
+
+                sd_session_get_tty(*s, &tty);
+                sd_session_get_seat(*s, &seat);
+                sd_session_get_service(*s, &service);
+                user = uid_to_name(uid);
+
+                log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty);
+                c++;
+        }
+
         if (c <= 0)
                 return 0;
 
-        log_error("Please try again after closing inhibitors or ignore them with 'systemctl %s -i'.",
+        log_error("Please retry operation after closing inhibitors and logging out other users.\nAlternatively, ignore inhibitors and users with 'systemctl %s -i'.",
                   a == ACTION_HALT ? "halt" :
                   a == ACTION_POWEROFF ? "poweroff" :
                   a == ACTION_REBOOT ? "reboot" :
@@ -2061,6 +2273,64 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) {
         return 0;
 }
 
+static int get_cgroup_attr(DBusConnection *bus, char **args) {
+        _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
+        DBusError error;
+        DBusMessageIter iter;
+        int r;
+        _cleanup_free_ char *n = NULL;
+        _cleanup_strv_free_ char **list = NULL;
+        char **a;
+
+        assert(bus);
+        assert(args);
+
+        dbus_error_init(&error);
+
+        n = unit_name_mangle(args[1]);
+        if (!n)
+                return log_oom();
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "GetUnitControlGroupAttributes");
+        if (!m)
+                return log_oom();
+
+        dbus_message_iter_init_append(m, &iter);
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n))
+                return log_oom();
+
+        r = bus_append_strv_iter(&iter, args + 2);
+        if (r < 0)
+                return log_oom();
+
+        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));
+                dbus_error_free(&error);
+                return -EIO;
+        }
+
+        dbus_message_iter_init(reply, &iter);
+        r = bus_parse_strv_iter(&iter, &list);
+        if (r < 0) {
+                log_error("Failed to parse value list.");
+                return r;
+        }
+
+        STRV_FOREACH(a, list) {
+                if (endswith(*a, "\n"))
+                        fputs(*a, stdout);
+                else
+                        puts(*a);
+        }
+
+        return 0;
+}
+
 typedef struct ExecStatusInfo {
         char *name;
 
@@ -2229,6 +2499,12 @@ static void print_status_info(UnitStatusInfo *i) {
         char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
         char since2[FORMAT_TIMESTAMP_MAX], *s2;
         const char *path;
+        int flags =
+                arg_all * OUTPUT_SHOW_ALL |
+                (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
+                on_tty() * OUTPUT_COLOR |
+                !arg_quiet * OUTPUT_WARN_CUTOFF |
+                arg_full * OUTPUT_FULL_WIDTH;
 
         assert(i);
 
@@ -2452,17 +2728,11 @@ static void print_status_info(UnitStatusInfo *i) {
                         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);
+                        show_cgroup_and_extra_by_spec(i->default_control_group, "\t\t  ", c, false, extra, k, flags);
                 }
         }
 
         if (i->id && arg_transport != TRANSPORT_SSH) {
-                int flags =
-                        arg_all * OUTPUT_SHOW_ALL |
-                        (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
-                        on_tty() * OUTPUT_COLOR |
-                        !arg_quiet * OUTPUT_WARN_CUTOFF;
-
                 printf("\n");
                 show_journal_by_unit(stdout,
                                      i->id,
@@ -4061,16 +4331,20 @@ 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"
+               "  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"
-               "  set-cgroup [NAME] [CGROUP...]   Add unit to a control group\n"
-               "  unset-cgroup [NAME] [CGROUP...] Remove unit from a control group\n"
+               "  get-cgroup-attr [NAME] [ATTR] ...\n"
+               "                                  Get control group attrubute\n"
                "  set-cgroup-attr [NAME] [ATTR] [VALUE] ...\n"
                "                                  Set control group attribute\n"
                "  unset-cgroup-attr [NAME] [ATTR...]\n"
                "                                  Unset control group attribute\n"
-               "  load [NAME...]                  Load one or more units\n\n"
+               "  set-cgroup [NAME] [CGROUP...]   Add unit to a control group\n"
+               "  unset-cgroup [NAME] [CGROUP...] Remove unit from a control group\n"
+               "  load [NAME...]                  Load one or more units\n"
+               "  list-dependencies [NAME]        Recursively show units which are required\n"
+               "                                  or wanted by this unit\n\n"
                "Unit File Commands:\n"
                "  list-unit-files                 List installed unit files\n"
                "  enable [NAME...]                Enable one or more unit files\n"
@@ -5047,6 +5321,7 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError
                 { "isolate",               EQUAL, 2, start_unit        },
                 { "set-cgroup",            MORE,  2, set_cgroup        },
                 { "unset-cgroup",          MORE,  2, set_cgroup        },
+                { "get-cgroup-attr",       MORE,  2, get_cgroup_attr   },
                 { "set-cgroup-attr",       MORE,  2, set_cgroup_attr   },
                 { "unset-cgroup-attr",     MORE,  2, set_cgroup        },
                 { "kill",                  MORE,  2, kill_unit         },
@@ -5086,6 +5361,7 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError
                 { "unmask",                MORE,  2, enable_unit       },
                 { "link",                  MORE,  2, enable_unit       },
                 { "switch-root",           MORE,  2, switch_root       },
+                { "list-dependencies",     LESS,  2, list_dependencies },
         };
 
         int left;