From e9c387c8293c57d1c773fc80d23239350eb3b370 Mon Sep 17 00:00:00 2001
From: Kevin Kuehler <keur@xcf.berkeley.edu>
Date: Sun, 8 Dec 2019 15:23:27 -0800
Subject: [PATCH] systemctl: Add --with-dependencies flag

Will print a unit and all of its dependencies. Works with cat, status,
list-units, and list-unit-files. This flag can also be used in conjunction
with --reverse, --before, and --after.

We also vastly simplify the list_dependencies_get_dependencies logic.
Instead of using 5 strvs and merging them into one, use one strv and
have the bus append all the map values to it.

Fixes #9273
---
 src/systemctl/systemctl.c | 227 +++++++++++++++++++++++++-------------
 1 file changed, 148 insertions(+), 79 deletions(-)

diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 6a0e59a4d7..0873045173 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -117,6 +117,7 @@ static bool arg_dry_run = false;
 static bool arg_quiet = false;
 static bool arg_full = false;
 static bool arg_recursive = false;
+static bool arg_with_dependencies = false;
 static bool arg_show_transaction = false;
 static int arg_force = 0;
 static bool arg_ask_password = false;
@@ -799,6 +800,107 @@ static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***r
         return c;
 }
 
+static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***ret) {
+        _cleanup_strv_free_ char **deps = NULL;
+
+        static const struct bus_properties_map map[_DEPENDENCY_MAX][6] = {
+                [DEPENDENCY_FORWARD] = {
+                        { "Requires",    "as", NULL, 0 },
+                        { "Requisite",   "as", NULL, 0 },
+                        { "Wants",       "as", NULL, 0 },
+                        { "ConsistsOf",  "as", NULL, 0 },
+                        { "BindsTo",     "as", NULL, 0 },
+                        {}
+                },
+                [DEPENDENCY_REVERSE] = {
+                        { "RequiredBy",  "as", NULL, 0 },
+                        { "RequisiteOf", "as", NULL, 0 },
+                        { "WantedBy",    "as", NULL, 0 },
+                        { "PartOf",      "as", NULL, 0 },
+                        { "BoundBy",     "as", NULL, 0 },
+                        {}
+                },
+                [DEPENDENCY_AFTER] = {
+                        { "After",       "as", NULL, 0 },
+                        {}
+                },
+                [DEPENDENCY_BEFORE] = {
+                        { "Before",      "as", NULL, 0 },
+                        {}
+                },
+        };
+
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_free_ char *dbus_path = NULL;
+        int r;
+
+        assert(bus);
+        assert(name);
+        assert(ret);
+
+        dbus_path = unit_dbus_path_from_name(name);
+        if (!dbus_path)
+                return log_oom();
+
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.systemd1",
+                                   dbus_path,
+                                   map[arg_dependency],
+                                   BUS_MAP_STRDUP,
+                                   &error,
+                                   NULL,
+                                   &deps);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r));
+
+        *ret = TAKE_PTR(deps);
+
+        return 0;
+}
+
+static int append_unit_dependencies(sd_bus *bus, char **names, char ***ret) {
+        _cleanup_strv_free_ char **with_deps = NULL;
+        char **name;
+
+        assert(bus);
+        assert(ret);
+
+        STRV_FOREACH(name, names) {
+                _cleanup_strv_free_ char **deps = NULL;
+
+                if (strv_extend(&with_deps, *name) < 0)
+                        return log_oom();
+
+                (void) list_dependencies_get_dependencies(bus, *name, &deps);
+
+                if (strv_extend_strv(&with_deps, deps, true) < 0)
+                        return log_oom();
+        }
+
+        *ret = TAKE_PTR(with_deps);
+
+        return 0;
+}
+
+static int maybe_extend_with_unit_dependencies(sd_bus *bus, char ***list) {
+        assert(bus);
+        assert(list);
+
+        if (arg_with_dependencies) {
+                int r;
+                _cleanup_strv_free_ char **list_with_deps = NULL;
+
+                r = append_unit_dependencies(bus, *list, &list_with_deps);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to append unit dependencies: %m");
+
+                strv_free(*list);
+                *list = TAKE_PTR(list_with_deps);
+        }
+
+        return 0;
+}
+
 static int list_units(int argc, char *argv[], void *userdata) {
         _cleanup_free_ UnitInfo *unit_infos = NULL;
         _cleanup_(message_set_freep) Set *replies = NULL;
@@ -812,9 +914,21 @@ static int list_units(int argc, char *argv[], void *userdata) {
 
         (void) pager_open(arg_pager_flags);
 
-        r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
-        if (r < 0)
-                return r;
+        if (arg_with_dependencies) {
+                _cleanup_strv_free_ char **names = NULL;
+
+                r = append_unit_dependencies(bus, strv_skip(argv, 1), &names);
+                if (r < 0)
+                        return r;
+
+                r = get_unit_list_recursive(bus, names, &unit_infos, &replies, &machines);
+                if (r < 0)
+                        return r;
+        } else {
+                r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
+                if (r < 0)
+                        return r;
+        }
 
         typesafe_qsort(unit_infos, r, compare_unit_info);
         return output_units_list(unit_infos, r);
@@ -1571,9 +1685,21 @@ static int list_unit_files(int argc, char *argv[], void *userdata) {
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_append_strv(m, strv_skip(argv, 1));
-                if (r < 0)
-                        return bus_log_create_error(r);
+                if (arg_with_dependencies) {
+                        _cleanup_strv_free_ char **names_with_deps = NULL;
+
+                        r = append_unit_dependencies(bus, strv_skip(argv, 1), &names_with_deps);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to append unit dependencies: %m");
+
+                        r = sd_bus_message_append_strv(m, names_with_deps);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                } else {
+                        r = sd_bus_message_append_strv(m, strv_skip(argv, 1));
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                }
 
                 r = sd_bus_call(bus, m, 0, &error, &reply);
                 if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
@@ -1677,79 +1803,6 @@ static int list_dependencies_print(const char *name, int level, unsigned branche
         return 0;
 }
 
-static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
-        struct DependencyStatusInfo {
-                char **dep[5];
-        } info = {};
-
-        static const struct bus_properties_map map[_DEPENDENCY_MAX][6] = {
-                [DEPENDENCY_FORWARD] = {
-                        { "Requires",    "as", NULL, offsetof(struct DependencyStatusInfo, dep[0]) },
-                        { "Requisite",   "as", NULL, offsetof(struct DependencyStatusInfo, dep[1]) },
-                        { "Wants",       "as", NULL, offsetof(struct DependencyStatusInfo, dep[2]) },
-                        { "ConsistsOf",  "as", NULL, offsetof(struct DependencyStatusInfo, dep[3]) },
-                        { "BindsTo",     "as", NULL, offsetof(struct DependencyStatusInfo, dep[4]) },
-                        {}
-                },
-                [DEPENDENCY_REVERSE] = {
-                        { "RequiredBy",  "as", NULL, offsetof(struct DependencyStatusInfo, dep[0]) },
-                        { "RequisiteOf", "as", NULL, offsetof(struct DependencyStatusInfo, dep[1]) },
-                        { "WantedBy",    "as", NULL, offsetof(struct DependencyStatusInfo, dep[2]) },
-                        { "PartOf",      "as", NULL, offsetof(struct DependencyStatusInfo, dep[3]) },
-                        { "BoundBy",     "as", NULL, offsetof(struct DependencyStatusInfo, dep[4]) },
-                        {}
-                },
-                [DEPENDENCY_AFTER] = {
-                        { "After",       "as", NULL, offsetof(struct DependencyStatusInfo, dep[0]) },
-                        {}
-                },
-                [DEPENDENCY_BEFORE] = {
-                        { "Before",      "as", NULL, offsetof(struct DependencyStatusInfo, dep[0]) },
-                        {}
-                },
-        };
-
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_strv_free_ char **ret = NULL;
-        _cleanup_free_ char *dbus_path = NULL;
-        int i, r;
-
-        assert(bus);
-        assert(name);
-        assert(deps);
-
-        dbus_path = unit_dbus_path_from_name(name);
-        if (!dbus_path)
-                return log_oom();
-
-        r = bus_map_all_properties(bus,
-                                   "org.freedesktop.systemd1",
-                                   dbus_path,
-                                   map[arg_dependency],
-                                   BUS_MAP_STRDUP,
-                                   &error,
-                                   NULL,
-                                   &info);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r));
-
-        if (IN_SET(arg_dependency, DEPENDENCY_AFTER, DEPENDENCY_BEFORE)) {
-                *deps = info.dep[0];
-                return 0;
-        }
-
-        for (i = 0; i < 5; i++) {
-                r = strv_extend_strv(&ret, info.dep[i], true);
-                if (r < 0)
-                        return log_oom();
-                info.dep[i] = strv_free(info.dep[i]);
-        }
-
-        *deps = TAKE_PTR(ret);
-
-        return 0;
-}
-
 static int list_dependencies_compare(char * const *a, char * const *b) {
         if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET)
                 return 1;
@@ -5909,6 +5962,10 @@ static int show(int argc, char *argv[], void *userdata) {
                         if (r < 0)
                                 return log_error_errno(r, "Failed to expand names: %m");
 
+                        r = maybe_extend_with_unit_dependencies(bus, &names);
+                        if (r < 0)
+                                return r;
+
                         STRV_FOREACH(name, names) {
                                 _cleanup_free_ char *path;
 
@@ -5959,6 +6016,10 @@ static int cat(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to expand names: %m");
 
+        r = maybe_extend_with_unit_dependencies(bus, &names);
+        if (r < 0)
+                return r;
+
         (void) pager_open(arg_pager_flags);
 
         STRV_FOREACH(name, names) {
@@ -7945,6 +8006,9 @@ static int systemctl_help(void) {
                "  -l --full           Don't ellipsize unit names on output\n"
                "  -r --recursive      Show unit list of host and local containers\n"
                "     --reverse        Show reverse dependencies with 'list-dependencies'\n"
+               "     --with-dependencies\n"
+               "                      Show unit dependencies with 'status', 'cat',\n"
+               "                      'list-units', and 'list-unit-files'.\n"
                "     --job-mode=MODE  Specify how to deal with already queued jobs, when\n"
                "                      queueing a new job\n"
                "  -T --show-transaction\n"
@@ -8235,6 +8298,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 ARG_BOOT_LOADER_ENTRY,
                 ARG_NOW,
                 ARG_MESSAGE,
+                ARG_WITH_DEPENDENCIES,
                 ARG_WAIT,
                 ARG_WHAT,
         };
@@ -8281,6 +8345,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "plain",               no_argument,       NULL, ARG_PLAIN               },
                 { "state",               required_argument, NULL, ARG_STATE               },
                 { "recursive",           no_argument,       NULL, 'r'                     },
+                { "with-dependencies",   no_argument,       NULL, ARG_WITH_DEPENDENCIES   },
                 { "preset-mode",         required_argument, NULL, ARG_PRESET_MODE         },
                 { "firmware-setup",      no_argument,       NULL, ARG_FIRMWARE_SETUP      },
                 { "boot-loader-menu",    required_argument, NULL, ARG_BOOT_LOADER_MENU    },
@@ -8641,6 +8706,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_show_transaction = true;
                         break;
 
+                case ARG_WITH_DEPENDENCIES:
+                        arg_with_dependencies = true;
+                        break;
+
                 case ARG_WHAT: {
                         const char *p;
 
-- 
2.23.0

