1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "analyze.h"
4 #include "analyze-dot.h"
5 #include "bus-error.h"
6 #include "bus-locator.h"
7 #include "bus-unit-util.h"
8 #include "glob-util.h"
9 #include "terminal-util.h"
10 
graph_one_property(sd_bus * bus,const UnitInfo * u,const char * prop,const char * color,char * patterns[],char * from_patterns[],char * to_patterns[])11 static int graph_one_property(
12                 sd_bus *bus,
13                 const UnitInfo *u,
14                 const char *prop,
15                 const char *color,
16                 char *patterns[],
17                 char *from_patterns[],
18                 char *to_patterns[]) {
19 
20         _cleanup_strv_free_ char **units = NULL;
21         bool match_patterns;
22         int r;
23 
24         assert(u);
25         assert(prop);
26         assert(color);
27 
28         match_patterns = strv_fnmatch(patterns, u->id);
29 
30         if (!strv_isempty(from_patterns) && !match_patterns && !strv_fnmatch(from_patterns, u->id))
31                 return 0;
32 
33         r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
34         if (r < 0)
35                 return r;
36 
37         STRV_FOREACH(unit, units) {
38                 bool match_patterns2;
39 
40                 match_patterns2 = strv_fnmatch(patterns, *unit);
41 
42                 if (!strv_isempty(to_patterns) && !match_patterns2 && !strv_fnmatch(to_patterns, *unit))
43                         continue;
44 
45                 if (!strv_isempty(patterns) && !match_patterns && !match_patterns2)
46                         continue;
47 
48                 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
49         }
50 
51         return 0;
52 }
53 
graph_one(sd_bus * bus,const UnitInfo * u,char * patterns[],char * from_patterns[],char * to_patterns[])54 static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) {
55         int r;
56 
57         assert(bus);
58         assert(u);
59 
60         if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) {
61                 r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns);
62                 if (r < 0)
63                         return r;
64         }
65 
66         if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) {
67                 r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns);
68                 if (r < 0)
69                         return r;
70                 r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns);
71                 if (r < 0)
72                         return r;
73                 r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns);
74                 if (r < 0)
75                         return r;
76                 r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns);
77                 if (r < 0)
78                         return r;
79         }
80 
81         return 0;
82 }
83 
expand_patterns(sd_bus * bus,char ** patterns,char *** ret)84 static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) {
85         _cleanup_strv_free_ char **expanded_patterns = NULL;
86         int r;
87 
88         STRV_FOREACH(pattern, patterns) {
89                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
90                 _cleanup_free_ char *unit = NULL, *unit_id = NULL;
91 
92                 if (strv_extend(&expanded_patterns, *pattern) < 0)
93                         return log_oom();
94 
95                 if (string_is_glob(*pattern))
96                         continue;
97 
98                 unit = unit_dbus_path_from_name(*pattern);
99                 if (!unit)
100                         return log_oom();
101 
102                 r = sd_bus_get_property_string(
103                                 bus,
104                                 "org.freedesktop.systemd1",
105                                 unit,
106                                 "org.freedesktop.systemd1.Unit",
107                                 "Id",
108                                 &error,
109                                 &unit_id);
110                 if (r < 0)
111                         return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
112 
113                 if (!streq(*pattern, unit_id)) {
114                         if (strv_extend(&expanded_patterns, unit_id) < 0)
115                                 return log_oom();
116                 }
117         }
118 
119         *ret = TAKE_PTR(expanded_patterns); /* do not free */
120 
121         return 0;
122 }
123 
verb_dot(int argc,char * argv[],void * userdata)124 int verb_dot(int argc, char *argv[], void *userdata) {
125         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
126         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
127         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
128         _cleanup_strv_free_ char **expanded_patterns = NULL;
129         _cleanup_strv_free_ char **expanded_from_patterns = NULL;
130         _cleanup_strv_free_ char **expanded_to_patterns = NULL;
131         int r;
132         UnitInfo u;
133 
134         r = acquire_bus(&bus, NULL);
135         if (r < 0)
136                 return bus_log_connect_error(r, arg_transport);
137 
138         r = expand_patterns(bus, strv_skip(argv, 1), &expanded_patterns);
139         if (r < 0)
140                 return r;
141 
142         r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns);
143         if (r < 0)
144                 return r;
145 
146         r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns);
147         if (r < 0)
148                 return r;
149 
150         r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
151         if (r < 0)
152                 log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
153 
154         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
155         if (r < 0)
156                 return bus_log_parse_error(r);
157 
158         printf("digraph systemd {\n");
159 
160         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
161 
162                 r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns);
163                 if (r < 0)
164                         return r;
165         }
166         if (r < 0)
167                 return bus_log_parse_error(r);
168 
169         printf("}\n");
170 
171         log_info("   Color legend: black     = Requires\n"
172                  "                 dark blue = Requisite\n"
173                  "                 dark grey = Wants\n"
174                  "                 red       = Conflicts\n"
175                  "                 green     = After\n");
176 
177         if (on_tty() && !arg_quiet)
178                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
179                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
180 
181         return 0;
182 }
183