]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
daf71ef6 LP |
2 | |
3 | #include "sd-login.h" | |
4 | ||
5 | #include "bus-error.h" | |
ed462ea3 | 6 | #include "bus-locator.h" |
daf71ef6 LP |
7 | #include "format-table.h" |
8 | #include "locale-util.h" | |
5fb5f49b | 9 | #include "path-util.h" |
daf71ef6 LP |
10 | #include "set.h" |
11 | #include "sort-util.h" | |
12 | #include "systemctl-list-units.h" | |
13 | #include "systemctl-util.h" | |
14 | #include "systemctl.h" | |
15 | #include "terminal-util.h" | |
16 | ||
17 | static void message_set_freep(Set **set) { | |
18 | set_free_with_destructor(*set, sd_bus_message_unref); | |
19 | } | |
20 | ||
21 | static int get_unit_list_recursive( | |
22 | sd_bus *bus, | |
23 | char **patterns, | |
24 | UnitInfo **ret_unit_infos, | |
f2ccc0d3 | 25 | Set **ret_replies) { |
daf71ef6 LP |
26 | |
27 | _cleanup_free_ UnitInfo *unit_infos = NULL; | |
c2b2df60 | 28 | _cleanup_(message_set_freep) Set *replies = NULL; |
eef96911 | 29 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; |
daf71ef6 LP |
30 | int c, r; |
31 | ||
32 | assert(bus); | |
33 | assert(ret_replies); | |
34 | assert(ret_unit_infos); | |
daf71ef6 LP |
35 | |
36 | replies = set_new(NULL); | |
37 | if (!replies) | |
38 | return log_oom(); | |
39 | ||
40 | c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply); | |
41 | if (c < 0) | |
42 | return c; | |
43 | ||
44 | r = set_put(replies, reply); | |
eef96911 | 45 | if (r < 0) |
daf71ef6 | 46 | return log_oom(); |
eef96911 | 47 | TAKE_PTR(reply); |
daf71ef6 LP |
48 | |
49 | if (arg_recursive) { | |
50 | _cleanup_strv_free_ char **machines = NULL; | |
daf71ef6 LP |
51 | |
52 | r = sd_get_machine_names(&machines); | |
53 | if (r < 0) | |
54 | return log_error_errno(r, "Failed to get machine names: %m"); | |
55 | ||
56 | STRV_FOREACH(i, machines) { | |
57 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; | |
58 | int k; | |
59 | ||
60 | r = sd_bus_open_system_machine(&container, *i); | |
61 | if (r < 0) { | |
62 | log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i); | |
63 | continue; | |
64 | } | |
65 | ||
66 | k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply); | |
67 | if (k < 0) | |
68 | return k; | |
69 | ||
70 | c = k; | |
71 | ||
72 | r = set_put(replies, reply); | |
eef96911 | 73 | if (r < 0) |
daf71ef6 | 74 | return log_oom(); |
eef96911 | 75 | TAKE_PTR(reply); |
daf71ef6 | 76 | } |
f2ccc0d3 | 77 | } |
daf71ef6 LP |
78 | |
79 | *ret_unit_infos = TAKE_PTR(unit_infos); | |
80 | *ret_replies = TAKE_PTR(replies); | |
81 | ||
82 | return c; | |
83 | } | |
84 | ||
11d6270b DT |
85 | static void output_legend(const char *type, size_t n_items) { |
86 | const char *on, *off; | |
87 | ||
88 | assert(type); | |
89 | ||
90 | on = n_items > 0 ? ansi_highlight() : ansi_highlight_red(); | |
91 | off = ansi_normal(); | |
92 | ||
93 | printf("\n%s%zu %ss listed.%s\n", on, n_items, type, off); | |
94 | if (!arg_all) | |
95 | printf("Pass --all to see loaded but inactive %ss, too.\n", type); | |
96 | } | |
97 | ||
531a45f3 DT |
98 | static int table_add_triggered(Table *table, char **triggered) { |
99 | assert(table); | |
100 | ||
101 | if (strv_isempty(triggered)) | |
102 | return table_add_cell(table, NULL, TABLE_EMPTY, NULL); | |
103 | else if (strv_length(triggered) == 1) | |
104 | return table_add_cell(table, NULL, TABLE_STRING, triggered[0]); | |
105 | else | |
106 | /* This should never happen, currently our socket units can only trigger a | |
107 | * single unit. But let's handle this anyway, who knows what the future | |
108 | * brings? */ | |
109 | return table_add_cell(table, NULL, TABLE_STRV, triggered); | |
110 | } | |
111 | ||
ac140596 DT |
112 | static char *format_unit_id(const char *unit, const char *machine) { |
113 | assert(unit); | |
114 | ||
115 | return machine ? strjoin(machine, ":", unit) : strdup(unit); | |
116 | } | |
117 | ||
46e6449a | 118 | static int output_units_list(const UnitInfo *unit_infos, size_t c) { |
daf71ef6 | 119 | _cleanup_(table_unrefp) Table *table = NULL; |
46e6449a | 120 | size_t job_count = 0; |
daf71ef6 LP |
121 | int r; |
122 | ||
123 | table = table_new("", "unit", "load", "active", "sub", "job", "description"); | |
124 | if (!table) | |
125 | return log_oom(); | |
126 | ||
6906da26 | 127 | table_set_header(table, arg_legend != 0); |
daf71ef6 LP |
128 | if (arg_plain) { |
129 | /* Hide the 'glyph' column when --plain is requested */ | |
130 | r = table_hide_column_from_display(table, 0); | |
131 | if (r < 0) | |
132 | return log_error_errno(r, "Failed to hide column: %m"); | |
133 | } | |
134 | if (arg_full) | |
135 | table_set_width(table, 0); | |
136 | ||
c8b62cf6 | 137 | table_set_ersatz_string(table, TABLE_ERSATZ_DASH); |
daf71ef6 | 138 | |
7ed7d3e6 | 139 | for (const UnitInfo *u = unit_infos; unit_infos && (size_t) (u - unit_infos) < c; u++) { |
ac140596 DT |
140 | _cleanup_free_ char *id = NULL; |
141 | const char *on_underline = "", *on_loaded = "", *on_active = "", *on_circle = ""; | |
daf71ef6 LP |
142 | bool circle = false, underline = false; |
143 | ||
144 | if (u + 1 < unit_infos + c && | |
145 | !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id))) { | |
146 | on_underline = ansi_underline(); | |
147 | underline = true; | |
148 | } | |
149 | ||
150 | if (STR_IN_SET(u->load_state, "error", "not-found", "bad-setting", "masked") && !arg_plain) { | |
151 | on_circle = underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow(); | |
152 | circle = true; | |
153 | on_loaded = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); | |
154 | } else if (streq(u->active_state, "failed") && !arg_plain) { | |
155 | on_circle = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); | |
156 | circle = true; | |
157 | on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); | |
158 | } else { | |
159 | on_circle = on_underline; | |
160 | on_active = on_underline; | |
161 | on_loaded = on_underline; | |
162 | } | |
163 | ||
ac140596 DT |
164 | id = format_unit_id(u->id, u->machine); |
165 | if (!id) | |
166 | return log_oom(); | |
daf71ef6 LP |
167 | |
168 | r = table_add_many(table, | |
169 | TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ", | |
170 | TABLE_SET_BOTH_COLORS, on_circle, | |
171 | TABLE_STRING, id, | |
172 | TABLE_SET_BOTH_COLORS, on_active, | |
173 | TABLE_STRING, u->load_state, | |
174 | TABLE_SET_BOTH_COLORS, on_loaded, | |
175 | TABLE_STRING, u->active_state, | |
176 | TABLE_SET_BOTH_COLORS, on_active, | |
177 | TABLE_STRING, u->sub_state, | |
178 | TABLE_SET_BOTH_COLORS, on_active, | |
179 | TABLE_STRING, u->job_id ? u->job_type: "", | |
81039448 | 180 | TABLE_SET_BOTH_COLORS, on_underline, |
daf71ef6 LP |
181 | TABLE_STRING, u->description, |
182 | TABLE_SET_BOTH_COLORS, on_underline); | |
183 | if (r < 0) | |
184 | return table_log_add_error(r); | |
185 | ||
186 | if (u->job_id != 0) | |
187 | job_count++; | |
188 | } | |
189 | ||
190 | if (job_count == 0) { | |
191 | /* There's no data in the JOB column, so let's hide it */ | |
192 | r = table_hide_column_from_display(table, 5); | |
193 | if (r < 0) | |
194 | return log_error_errno(r, "Failed to hide column: %m"); | |
195 | } | |
196 | ||
197 | r = output_table(table); | |
198 | if (r < 0) | |
199 | return r; | |
200 | ||
6906da26 | 201 | if (arg_legend != 0) { |
daf71ef6 LP |
202 | const char *on, *off; |
203 | size_t records = table_get_rows(table) - 1; | |
204 | ||
205 | if (records > 0) { | |
206 | puts("\n" | |
207 | "LOAD = Reflects whether the unit definition was properly loaded.\n" | |
208 | "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n" | |
209 | "SUB = The low-level unit activation state, values depend on unit type."); | |
5ba97fc0 YW |
210 | if (job_count > 0) |
211 | puts("JOB = Pending job for the unit.\n"); | |
daf71ef6 LP |
212 | } |
213 | ||
0f787940 DT |
214 | on = records > 0 ? ansi_highlight() : ansi_highlight_red(); |
215 | off = ansi_normal(); | |
216 | ||
daf71ef6 LP |
217 | if (arg_all || strv_contains(arg_states, "inactive")) |
218 | printf("%s%zu loaded units listed.%s\n" | |
219 | "To show all installed unit files use 'systemctl list-unit-files'.\n", | |
220 | on, records, off); | |
221 | else if (!arg_states) | |
222 | printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" | |
223 | "To show all installed unit files use 'systemctl list-unit-files'.\n", | |
224 | on, records, off); | |
225 | else | |
226 | printf("%zu loaded units listed.\n", records); | |
227 | } | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
32baf64d | 232 | int verb_list_units(int argc, char *argv[], void *userdata) { |
daf71ef6 LP |
233 | _cleanup_free_ UnitInfo *unit_infos = NULL; |
234 | _cleanup_(message_set_freep) Set *replies = NULL; | |
daf71ef6 LP |
235 | sd_bus *bus; |
236 | int r; | |
237 | ||
238 | r = acquire_bus(BUS_MANAGER, &bus); | |
239 | if (r < 0) | |
240 | return r; | |
241 | ||
384c2c32 | 242 | pager_open(arg_pager_flags); |
daf71ef6 LP |
243 | |
244 | if (arg_with_dependencies) { | |
245 | _cleanup_strv_free_ char **names = NULL; | |
246 | ||
247 | r = append_unit_dependencies(bus, strv_skip(argv, 1), &names); | |
248 | if (r < 0) | |
249 | return r; | |
250 | ||
f2ccc0d3 | 251 | r = get_unit_list_recursive(bus, names, &unit_infos, &replies); |
daf71ef6 LP |
252 | if (r < 0) |
253 | return r; | |
254 | } else { | |
f2ccc0d3 | 255 | r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies); |
daf71ef6 LP |
256 | if (r < 0) |
257 | return r; | |
258 | } | |
259 | ||
260 | typesafe_qsort(unit_infos, r, unit_info_compare); | |
261 | return output_units_list(unit_infos, r); | |
262 | } | |
263 | ||
264 | static int get_triggered_units( | |
265 | sd_bus *bus, | |
266 | const char* path, | |
267 | char*** ret) { | |
268 | ||
269 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
270 | int r; | |
271 | ||
272 | assert(bus); | |
273 | assert(path); | |
274 | assert(ret); | |
275 | ||
276 | r = sd_bus_get_property_strv( | |
277 | bus, | |
278 | "org.freedesktop.systemd1", | |
279 | path, | |
280 | "org.freedesktop.systemd1.Unit", | |
281 | "Triggers", | |
282 | &error, | |
283 | ret); | |
284 | if (r < 0) | |
285 | return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
1b876066 YW |
290 | typedef struct SocketInfo { |
291 | const char *machine; | |
292 | const char* id; | |
293 | ||
294 | char* type; | |
295 | char* path; /* absolute path or socket address */ | |
296 | ||
297 | /* Note: triggered is a list here, although it almost certainly will always be one | |
298 | * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */ | |
299 | char** triggered; | |
300 | } SocketInfo; | |
301 | ||
302 | static void socket_info_array_free(SocketInfo *sockets, size_t n_sockets) { | |
303 | assert(sockets || n_sockets == 0); | |
304 | ||
305 | for (SocketInfo *s = sockets; s < sockets + n_sockets; s++) { | |
306 | free(s->type); | |
307 | free(s->path); | |
308 | strv_free(s->triggered); | |
309 | } | |
310 | ||
311 | free(sockets); | |
312 | } | |
313 | ||
314 | static int socket_info_compare(const SocketInfo *a, const SocketInfo *b) { | |
315 | int r; | |
316 | ||
317 | assert(a); | |
318 | assert(b); | |
319 | ||
320 | r = strcasecmp_ptr(a->machine, b->machine); | |
321 | if (r != 0) | |
322 | return r; | |
323 | ||
324 | r = CMP(path_is_absolute(a->path), path_is_absolute(b->path)); | |
325 | if (r != 0) | |
326 | return r; | |
327 | ||
328 | r = path_is_absolute(a->path) ? path_compare(a->path, b->path) : strcmp(a->path, b->path); | |
329 | if (r != 0) | |
330 | return r; | |
331 | ||
332 | return strcmp(a->type, b->type); | |
333 | } | |
334 | ||
335 | static int socket_info_add( | |
daf71ef6 | 336 | sd_bus *bus, |
1b876066 YW |
337 | const UnitInfo *u, |
338 | SocketInfo **sockets, | |
339 | size_t *n_sockets) { | |
daf71ef6 LP |
340 | |
341 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
342 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
1b876066 | 343 | _cleanup_strv_free_ char **triggered = NULL; |
daf71ef6 | 344 | const char *type, *path; |
1b876066 YW |
345 | int r; |
346 | ||
347 | assert(bus); | |
348 | assert(u); | |
349 | assert(sockets); | |
350 | assert(n_sockets); | |
351 | ||
352 | if (!endswith(u->id, ".socket")) | |
353 | return 0; | |
354 | ||
355 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
356 | if (r < 0) | |
357 | return r; | |
daf71ef6 LP |
358 | |
359 | r = sd_bus_get_property( | |
360 | bus, | |
361 | "org.freedesktop.systemd1", | |
1b876066 | 362 | u->unit_path, |
daf71ef6 LP |
363 | "org.freedesktop.systemd1.Socket", |
364 | "Listen", | |
365 | &error, | |
366 | &reply, | |
367 | "a(ss)"); | |
368 | if (r < 0) | |
369 | return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); | |
370 | ||
371 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); | |
372 | if (r < 0) | |
373 | return bus_log_parse_error(r); | |
374 | ||
375 | while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) { | |
1b876066 YW |
376 | _cleanup_free_ char *type_dup = NULL, *path_dup = NULL; |
377 | _cleanup_strv_free_ char **triggered_dup = NULL; | |
daf71ef6 | 378 | |
1b876066 YW |
379 | type_dup = strdup(type); |
380 | if (!type_dup) | |
daf71ef6 LP |
381 | return log_oom(); |
382 | ||
1b876066 YW |
383 | path_dup = strdup(path); |
384 | if (!path_dup) | |
385 | return log_oom(); | |
386 | ||
387 | triggered_dup = strv_copy(triggered); | |
388 | if (!triggered_dup) | |
daf71ef6 LP |
389 | return log_oom(); |
390 | ||
1b876066 YW |
391 | if (!GREEDY_REALLOC(*sockets, *n_sockets + 1)) |
392 | return log_oom(); | |
393 | ||
394 | (*sockets)[(*n_sockets)++] = (SocketInfo) { | |
395 | .machine = u->machine, | |
396 | .id = u->id, | |
397 | .type = TAKE_PTR(type_dup), | |
398 | .path = TAKE_PTR(path_dup), | |
399 | .triggered = TAKE_PTR(triggered_dup), | |
400 | }; | |
daf71ef6 LP |
401 | } |
402 | if (r < 0) | |
403 | return bus_log_parse_error(r); | |
404 | ||
405 | r = sd_bus_message_exit_container(reply); | |
406 | if (r < 0) | |
407 | return bus_log_parse_error(r); | |
408 | ||
1b876066 | 409 | return 0; |
daf71ef6 LP |
410 | } |
411 | ||
1b876066 | 412 | static int output_sockets_list(const SocketInfo *sockets, size_t n_sockets) { |
daf71ef6 | 413 | _cleanup_(table_unrefp) Table *table = NULL; |
daf71ef6 LP |
414 | int r; |
415 | ||
1b876066 | 416 | assert(sockets || n_sockets == 0); |
25facd03 | 417 | |
daf71ef6 LP |
418 | table = table_new("listen", "type", "unit", "activates"); |
419 | if (!table) | |
420 | return log_oom(); | |
421 | ||
422 | if (!arg_show_types) { | |
423 | /* Hide the second (TYPE) column */ | |
ef1e0b9a | 424 | r = table_set_display(table, (size_t) 0, (size_t) 2, (size_t) 3); |
daf71ef6 LP |
425 | if (r < 0) |
426 | return log_error_errno(r, "Failed to set columns to display: %m"); | |
427 | } | |
428 | ||
6906da26 | 429 | table_set_header(table, arg_legend != 0); |
daf71ef6 LP |
430 | if (arg_full) |
431 | table_set_width(table, 0); | |
432 | ||
c8b62cf6 | 433 | table_set_ersatz_string(table, TABLE_ERSATZ_DASH); |
daf71ef6 | 434 | |
1b876066 | 435 | for (const SocketInfo *s = sockets; s < sockets + n_sockets; s++) { |
f748b2d0 | 436 | _cleanup_free_ char *unit = NULL; |
25facd03 | 437 | |
f748b2d0 DT |
438 | unit = format_unit_id(s->id, s->machine); |
439 | if (!unit) | |
440 | return log_oom(); | |
25facd03 DT |
441 | |
442 | r = table_add_many(table, | |
1b876066 YW |
443 | TABLE_STRING, s->path, |
444 | TABLE_STRING, s->type, | |
445 | TABLE_STRING, unit); | |
25facd03 DT |
446 | if (r < 0) |
447 | return table_log_add_error(r); | |
448 | ||
531a45f3 | 449 | r = table_add_triggered(table, s->triggered); |
25facd03 DT |
450 | if (r < 0) |
451 | return table_log_add_error(r); | |
daf71ef6 LP |
452 | } |
453 | ||
454 | r = output_table(table); | |
455 | if (r < 0) | |
456 | return r; | |
457 | ||
11d6270b | 458 | if (arg_legend != 0) |
1b876066 | 459 | output_legend("socket", n_sockets); |
daf71ef6 LP |
460 | |
461 | return 0; | |
462 | } | |
463 | ||
32baf64d | 464 | int verb_list_sockets(int argc, char *argv[], void *userdata) { |
daf71ef6 | 465 | _cleanup_(message_set_freep) Set *replies = NULL; |
daf71ef6 LP |
466 | _cleanup_strv_free_ char **sockets_with_suffix = NULL; |
467 | _cleanup_free_ UnitInfo *unit_infos = NULL; | |
1b876066 YW |
468 | SocketInfo *sockets = NULL; |
469 | size_t n_sockets = 0; | |
daf71ef6 | 470 | sd_bus *bus; |
1b876066 YW |
471 | int r; |
472 | ||
473 | CLEANUP_ARRAY(sockets, n_sockets, socket_info_array_free); | |
daf71ef6 LP |
474 | |
475 | r = acquire_bus(BUS_MANAGER, &bus); | |
476 | if (r < 0) | |
477 | return r; | |
478 | ||
384c2c32 | 479 | pager_open(arg_pager_flags); |
daf71ef6 LP |
480 | |
481 | r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL); | |
482 | if (r < 0) | |
483 | return r; | |
484 | ||
485 | if (argc == 1 || sockets_with_suffix) { | |
1b876066 YW |
486 | int n; |
487 | ||
f2ccc0d3 | 488 | n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies); |
daf71ef6 LP |
489 | if (n < 0) |
490 | return n; | |
491 | ||
deaf4b86 | 492 | for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { |
1b876066 | 493 | r = socket_info_add(bus, u, &sockets, &n_sockets); |
daf71ef6 | 494 | if (r < 0) |
1b876066 | 495 | return r; |
daf71ef6 | 496 | } |
daf71ef6 LP |
497 | } |
498 | ||
1b876066 YW |
499 | typesafe_qsort(sockets, n_sockets, socket_info_compare); |
500 | output_sockets_list(sockets, n_sockets); | |
daf71ef6 | 501 | |
1b876066 | 502 | return 0; |
daf71ef6 LP |
503 | } |
504 | ||
505 | static int get_next_elapse( | |
506 | sd_bus *bus, | |
507 | const char *path, | |
508 | dual_timestamp *next) { | |
509 | ||
510 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
511 | dual_timestamp t; | |
512 | int r; | |
513 | ||
514 | assert(bus); | |
515 | assert(path); | |
516 | assert(next); | |
517 | ||
518 | r = sd_bus_get_property_trivial( | |
519 | bus, | |
520 | "org.freedesktop.systemd1", | |
521 | path, | |
522 | "org.freedesktop.systemd1.Timer", | |
523 | "NextElapseUSecMonotonic", | |
524 | &error, | |
525 | 't', | |
526 | &t.monotonic); | |
527 | if (r < 0) | |
528 | return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r)); | |
529 | ||
530 | r = sd_bus_get_property_trivial( | |
531 | bus, | |
532 | "org.freedesktop.systemd1", | |
533 | path, | |
534 | "org.freedesktop.systemd1.Timer", | |
535 | "NextElapseUSecRealtime", | |
536 | &error, | |
537 | 't', | |
538 | &t.realtime); | |
539 | if (r < 0) | |
540 | return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r)); | |
541 | ||
542 | *next = t; | |
543 | return 0; | |
544 | } | |
545 | ||
546 | static int get_last_trigger( | |
547 | sd_bus *bus, | |
548 | const char *path, | |
549 | usec_t *last) { | |
550 | ||
551 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
552 | int r; | |
553 | ||
554 | assert(bus); | |
555 | assert(path); | |
556 | assert(last); | |
557 | ||
558 | r = sd_bus_get_property_trivial( | |
559 | bus, | |
560 | "org.freedesktop.systemd1", | |
561 | path, | |
562 | "org.freedesktop.systemd1.Timer", | |
563 | "LastTriggerUSec", | |
564 | &error, | |
565 | 't', | |
566 | last); | |
567 | if (r < 0) | |
568 | return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); | |
569 | ||
570 | return 0; | |
571 | } | |
572 | ||
0e351e9a | 573 | typedef struct TimerInfo { |
daf71ef6 LP |
574 | const char* machine; |
575 | const char* id; | |
576 | usec_t next_elapse; | |
577 | usec_t last_trigger; | |
0e351e9a YW |
578 | char **triggered; |
579 | } TimerInfo; | |
580 | ||
581 | static void timer_info_array_free(TimerInfo *timers, size_t n_timers) { | |
582 | assert(timers || n_timers == 0); | |
583 | ||
584 | for (TimerInfo *t = timers; t < timers + n_timers; t++) | |
585 | strv_free(t->triggered); | |
586 | ||
587 | free(timers); | |
588 | } | |
daf71ef6 | 589 | |
0e351e9a | 590 | static int timer_info_compare(const TimerInfo *a, const TimerInfo *b) { |
daf71ef6 LP |
591 | int r; |
592 | ||
593 | assert(a); | |
594 | assert(b); | |
595 | ||
596 | r = strcasecmp_ptr(a->machine, b->machine); | |
597 | if (r != 0) | |
598 | return r; | |
599 | ||
600 | r = CMP(a->next_elapse, b->next_elapse); | |
601 | if (r != 0) | |
602 | return r; | |
603 | ||
604 | return strcmp(a->id, b->id); | |
605 | } | |
606 | ||
0e351e9a | 607 | static int output_timers_list(const TimerInfo *timers, size_t n_timers) { |
daf71ef6 | 608 | _cleanup_(table_unrefp) Table *table = NULL; |
daf71ef6 LP |
609 | int r; |
610 | ||
0e351e9a | 611 | assert(timers || n_timers == 0); |
daf71ef6 LP |
612 | |
613 | table = table_new("next", "left", "last", "passed", "unit", "activates"); | |
614 | if (!table) | |
615 | return log_oom(); | |
616 | ||
6906da26 | 617 | table_set_header(table, arg_legend != 0); |
daf71ef6 LP |
618 | if (arg_full) |
619 | table_set_width(table, 0); | |
620 | ||
c8b62cf6 | 621 | table_set_ersatz_string(table, TABLE_ERSATZ_DASH); |
daf71ef6 | 622 | |
f168919d LP |
623 | (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100); |
624 | (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100); | |
625 | ||
0e351e9a | 626 | for (const TimerInfo *t = timers; t < timers + n_timers; t++) { |
ac140596 | 627 | _cleanup_free_ char *unit = NULL; |
daf71ef6 | 628 | |
ac140596 DT |
629 | unit = format_unit_id(t->id, t->machine); |
630 | if (!unit) | |
631 | return log_oom(); | |
daf71ef6 | 632 | |
deaf4b86 ZJS |
633 | r = table_add_many(table, |
634 | TABLE_TIMESTAMP, t->next_elapse, | |
635 | TABLE_TIMESTAMP_RELATIVE, t->next_elapse, | |
636 | TABLE_TIMESTAMP, t->last_trigger, | |
637 | TABLE_TIMESTAMP_RELATIVE, t->last_trigger, | |
f4384e19 DT |
638 | TABLE_STRING, unit); |
639 | if (r < 0) | |
640 | return table_log_add_error(r); | |
641 | ||
642 | r = table_add_triggered(table, t->triggered); | |
deaf4b86 ZJS |
643 | if (r < 0) |
644 | return table_log_add_error(r); | |
645 | } | |
646 | ||
daf71ef6 LP |
647 | r = output_table(table); |
648 | if (r < 0) | |
649 | return r; | |
650 | ||
11d6270b | 651 | if (arg_legend != 0) |
0e351e9a | 652 | output_legend("timer", n_timers); |
daf71ef6 LP |
653 | |
654 | return 0; | |
655 | } | |
656 | ||
0e351e9a | 657 | usec_t calc_next_elapse(const dual_timestamp *nw, const dual_timestamp *next) { |
daf71ef6 LP |
658 | usec_t next_elapse; |
659 | ||
660 | assert(nw); | |
661 | assert(next); | |
662 | ||
663 | if (timestamp_is_set(next->monotonic)) { | |
664 | usec_t converted; | |
665 | ||
666 | if (next->monotonic > nw->monotonic) | |
667 | converted = nw->realtime + (next->monotonic - nw->monotonic); | |
668 | else | |
669 | converted = nw->realtime - (nw->monotonic - next->monotonic); | |
670 | ||
671 | if (timestamp_is_set(next->realtime)) | |
672 | next_elapse = MIN(converted, next->realtime); | |
673 | else | |
674 | next_elapse = converted; | |
675 | ||
676 | } else | |
677 | next_elapse = next->realtime; | |
678 | ||
679 | return next_elapse; | |
680 | } | |
681 | ||
0e351e9a YW |
682 | static int add_timer_info( |
683 | sd_bus *bus, | |
684 | const UnitInfo *u, | |
685 | const dual_timestamp *nw, | |
686 | TimerInfo **timers, | |
687 | size_t *n_timers) { | |
688 | ||
689 | _cleanup_strv_free_ char **triggered = NULL; | |
690 | dual_timestamp next = DUAL_TIMESTAMP_NULL; | |
691 | usec_t m, last = 0; | |
692 | int r; | |
693 | ||
694 | assert(bus); | |
695 | assert(u); | |
696 | assert(nw); | |
697 | assert(timers); | |
698 | assert(n_timers); | |
699 | ||
700 | if (!endswith(u->id, ".timer")) | |
701 | return 0; | |
702 | ||
703 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
704 | if (r < 0) | |
705 | return r; | |
706 | ||
707 | r = get_next_elapse(bus, u->unit_path, &next); | |
708 | if (r < 0) | |
709 | return r; | |
710 | ||
711 | r = get_last_trigger(bus, u->unit_path, &last); | |
712 | if (r < 0) | |
713 | return r; | |
714 | ||
715 | m = calc_next_elapse(nw, &next); | |
716 | ||
717 | if (!GREEDY_REALLOC(*timers, *n_timers + 1)) | |
718 | return log_oom(); | |
719 | ||
720 | (*timers)[(*n_timers)++] = (TimerInfo) { | |
721 | .machine = u->machine, | |
722 | .id = u->id, | |
723 | .next_elapse = m, | |
724 | .last_trigger = last, | |
725 | .triggered = TAKE_PTR(triggered), | |
726 | }; | |
727 | ||
728 | return 0; | |
729 | } | |
730 | ||
32baf64d | 731 | int verb_list_timers(int argc, char *argv[], void *userdata) { |
daf71ef6 | 732 | _cleanup_(message_set_freep) Set *replies = NULL; |
daf71ef6 | 733 | _cleanup_strv_free_ char **timers_with_suffix = NULL; |
daf71ef6 | 734 | _cleanup_free_ UnitInfo *unit_infos = NULL; |
0e351e9a YW |
735 | TimerInfo *timers = NULL; |
736 | size_t n_timers = 0; | |
daf71ef6 | 737 | sd_bus *bus; |
0e351e9a YW |
738 | int r; |
739 | ||
740 | CLEANUP_ARRAY(timers, n_timers, timer_info_array_free); | |
daf71ef6 LP |
741 | |
742 | r = acquire_bus(BUS_MANAGER, &bus); | |
743 | if (r < 0) | |
744 | return r; | |
745 | ||
384c2c32 | 746 | pager_open(arg_pager_flags); |
daf71ef6 LP |
747 | |
748 | r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL); | |
749 | if (r < 0) | |
750 | return r; | |
751 | ||
752 | if (argc == 1 || timers_with_suffix) { | |
0e351e9a YW |
753 | dual_timestamp nw; |
754 | int n; | |
755 | ||
f2ccc0d3 | 756 | n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies); |
daf71ef6 LP |
757 | if (n < 0) |
758 | return n; | |
759 | ||
760 | dual_timestamp_get(&nw); | |
761 | ||
deaf4b86 | 762 | for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { |
0e351e9a | 763 | r = add_timer_info(bus, u, &nw, &timers, &n_timers); |
daf71ef6 | 764 | if (r < 0) |
0e351e9a | 765 | return r; |
daf71ef6 | 766 | } |
daf71ef6 LP |
767 | } |
768 | ||
0e351e9a YW |
769 | typesafe_qsort(timers, n_timers, timer_info_compare); |
770 | output_timers_list(timers, n_timers); | |
daf71ef6 | 771 | |
0e351e9a | 772 | return 0; |
daf71ef6 | 773 | } |
ed462ea3 DT |
774 | |
775 | struct automount_info { | |
776 | const char *machine; | |
777 | const char *id; | |
778 | char *what; | |
779 | char *where; | |
780 | usec_t timeout_idle_usec; | |
781 | bool mounted; | |
782 | }; | |
783 | ||
784 | static int automount_info_compare(const struct automount_info *a, const struct automount_info *b) { | |
785 | int r; | |
786 | ||
787 | assert(a); | |
788 | assert(b); | |
789 | ||
790 | r = strcasecmp_ptr(a->machine, b->machine); | |
791 | if (r != 0) | |
792 | return r; | |
793 | ||
794 | return strcmp(a->where, b->where); | |
795 | } | |
796 | ||
797 | static int collect_automount_info(sd_bus* bus, const UnitInfo* info, struct automount_info *ret_info) { | |
798 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
799 | _cleanup_free_ char *mount = NULL, *mount_path = NULL, *where = NULL, *what = NULL, *state = NULL; | |
976baf26 | 800 | uint64_t timeout_idle_usec; |
ed462ea3 DT |
801 | BusLocator locator; |
802 | int r; | |
803 | ||
804 | assert(bus); | |
805 | assert(info); | |
806 | assert(ret_info); | |
807 | ||
808 | locator = (BusLocator) { | |
809 | .destination = "org.freedesktop.systemd1", | |
810 | .path = info->unit_path, | |
811 | .interface = "org.freedesktop.systemd1.Automount", | |
812 | }; | |
813 | ||
814 | r = bus_get_property_string(bus, &locator, "Where", &error, &where); | |
815 | if (r < 0) | |
816 | return log_error_errno(r, "Failed to get automount target: %s", bus_error_message(&error, r)); | |
817 | ||
818 | r = bus_get_property_trivial(bus, &locator, "TimeoutIdleUSec", &error, 't', &timeout_idle_usec); | |
819 | if (r < 0) | |
820 | return log_error_errno(r, "Failed to get idle timeout: %s", bus_error_message(&error, r)); | |
821 | ||
822 | r = unit_name_from_path(where, ".mount", &mount); | |
823 | if (r < 0) | |
824 | return log_error_errno(r, "Failed to generate unit name from path: %m"); | |
825 | ||
826 | mount_path = unit_dbus_path_from_name(mount); | |
827 | if (!mount_path) | |
828 | return log_oom(); | |
829 | ||
830 | locator.path = mount_path; | |
831 | locator.interface = "org.freedesktop.systemd1.Mount"; | |
832 | ||
833 | r = bus_get_property_string(bus, &locator, "What", &error, &what); | |
834 | if (r < 0) | |
835 | return log_error_errno(r, "Failed to get mount source: %s", bus_error_message(&error, r)); | |
836 | ||
837 | locator.interface = "org.freedesktop.systemd1.Unit"; | |
838 | ||
839 | r = bus_get_property_string(bus, &locator, "ActiveState", &error, &state); | |
840 | if (r < 0) | |
841 | return log_error_errno(r, "Failed to get mount state: %s", bus_error_message(&error, r)); | |
842 | ||
843 | *ret_info = (struct automount_info) { | |
844 | .machine = info->machine, | |
845 | .id = info->id, | |
846 | .what = TAKE_PTR(what), | |
847 | .where = TAKE_PTR(where), | |
848 | .timeout_idle_usec = timeout_idle_usec, | |
849 | .mounted = streq_ptr(state, "active"), | |
850 | }; | |
851 | ||
852 | return 0; | |
853 | } | |
854 | ||
855 | static int output_automounts_list(struct automount_info *infos, size_t n_infos) { | |
856 | _cleanup_(table_unrefp) Table *table = NULL; | |
857 | int r; | |
858 | ||
859 | assert(infos || n_infos == 0); | |
860 | ||
861 | table = table_new("what", "where", "mounted", "idle timeout", "unit"); | |
862 | if (!table) | |
863 | return log_oom(); | |
864 | ||
865 | table_set_header(table, arg_legend != 0); | |
866 | if (arg_full) | |
867 | table_set_width(table, 0); | |
868 | ||
c8b62cf6 | 869 | table_set_ersatz_string(table, TABLE_ERSATZ_DASH); |
ed462ea3 DT |
870 | |
871 | for (struct automount_info *info = infos; info < infos + n_infos; info++) { | |
ac140596 | 872 | _cleanup_free_ char *unit = NULL; |
ed462ea3 | 873 | |
ac140596 DT |
874 | unit = format_unit_id(info->id, info->machine); |
875 | if (!unit) | |
876 | return log_oom(); | |
ed462ea3 DT |
877 | |
878 | r = table_add_many(table, | |
879 | TABLE_STRING, info->what, | |
880 | TABLE_STRING, info->where, | |
21ae8c17 LP |
881 | TABLE_BOOLEAN, info->mounted); |
882 | if (r < 0) | |
883 | return table_log_add_error(r); | |
884 | ||
885 | if (timestamp_is_set(info->timeout_idle_usec)) | |
886 | r = table_add_cell(table, NULL, TABLE_TIMESPAN_MSEC, &info->timeout_idle_usec); | |
887 | else | |
888 | r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); | |
889 | if (r < 0) | |
890 | return table_log_add_error(r); | |
891 | ||
892 | r = table_add_cell(table, NULL, TABLE_STRING, unit); | |
ed462ea3 DT |
893 | if (r < 0) |
894 | return table_log_add_error(r); | |
895 | } | |
896 | ||
897 | r = output_table(table); | |
898 | if (r < 0) | |
899 | return r; | |
900 | ||
901 | if (arg_legend != 0) | |
902 | output_legend("automount", n_infos); | |
903 | ||
904 | return 0; | |
905 | } | |
906 | ||
907 | int verb_list_automounts(int argc, char *argv[], void *userdata) { | |
908 | _cleanup_(message_set_freep) Set *replies = NULL; | |
f2ccc0d3 | 909 | _cleanup_strv_free_ char **automounts = NULL; |
ed462ea3 DT |
910 | _cleanup_free_ UnitInfo *unit_infos = NULL; |
911 | _cleanup_free_ struct automount_info *automount_infos = NULL; | |
912 | size_t c = 0; | |
913 | int r, n; | |
914 | sd_bus *bus; | |
915 | ||
916 | r = acquire_bus(BUS_MANAGER, &bus); | |
917 | if (r < 0) | |
918 | return r; | |
919 | ||
920 | pager_open(arg_pager_flags); | |
921 | ||
922 | r = expand_unit_names(bus, strv_skip(argv, 1), ".automount", &automounts, NULL); | |
923 | if (r < 0) | |
924 | return r; | |
925 | ||
926 | if (argc == 1 || automounts) { | |
f2ccc0d3 | 927 | n = get_unit_list_recursive(bus, automounts, &unit_infos, &replies); |
ed462ea3 DT |
928 | if (n < 0) |
929 | return n; | |
930 | ||
931 | for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { | |
932 | if (!endswith(u->id, ".automount")) | |
933 | continue; | |
934 | ||
935 | if (!GREEDY_REALLOC(automount_infos, c + 1)) { | |
936 | r = log_oom(); | |
937 | goto cleanup; | |
938 | } | |
939 | ||
940 | r = collect_automount_info(bus, u, &automount_infos[c]); | |
941 | if (r < 0) | |
942 | goto cleanup; | |
943 | ||
944 | c++; | |
945 | } | |
946 | ||
947 | typesafe_qsort(automount_infos, c, automount_info_compare); | |
948 | } | |
949 | ||
950 | output_automounts_list(automount_infos, c); | |
951 | ||
952 | cleanup: | |
953 | assert(c == 0 || automount_infos); | |
954 | for (struct automount_info *info = automount_infos; info < automount_infos + c; info++) { | |
955 | free(info->what); | |
956 | free(info->where); | |
957 | } | |
958 | ||
959 | return r; | |
960 | } | |
5fb5f49b DT |
961 | |
962 | struct path_info { | |
963 | const char *machine; | |
964 | const char *id; | |
965 | ||
966 | char *path; | |
967 | char *condition; | |
968 | ||
969 | /* Note: triggered is a list here, although it almost certainly will always be one | |
970 | * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */ | |
971 | char** triggered; | |
972 | }; | |
973 | ||
974 | struct path_infos { | |
975 | size_t count; | |
976 | struct path_info *items; | |
977 | }; | |
978 | ||
979 | static int path_info_compare(const struct path_info *a, const struct path_info *b) { | |
980 | int r; | |
981 | ||
982 | assert(a); | |
983 | assert(b); | |
984 | ||
985 | r = strcasecmp_ptr(a->machine, b->machine); | |
986 | if (r != 0) | |
987 | return r; | |
988 | ||
989 | r = path_compare(a->path, b->path); | |
990 | if (r != 0) | |
991 | return r; | |
992 | ||
993 | r = strcmp(a->condition, b->condition); | |
994 | if (r != 0) | |
995 | return r; | |
996 | ||
997 | return strcasecmp_ptr(a->id, b->id); | |
998 | } | |
999 | ||
1000 | static void path_infos_done(struct path_infos *ps) { | |
1001 | assert(ps); | |
1002 | assert(ps->items || ps->count == 0); | |
1003 | ||
1004 | for (struct path_info *p = ps->items; p < ps->items + ps->count; p++) { | |
1005 | free(p->condition); | |
1006 | free(p->path); | |
1007 | strv_free(p->triggered); | |
1008 | } | |
1009 | ||
1010 | free(ps->items); | |
1011 | } | |
1012 | ||
1013 | static int get_paths(sd_bus *bus, const char *unit_path, char ***ret_conditions, char ***ret_paths) { | |
1014 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
1015 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
1016 | _cleanup_strv_free_ char **conditions = NULL, **paths = NULL; | |
1017 | const char *condition, *path; | |
1018 | int r, n = 0; | |
1019 | ||
1020 | assert(bus); | |
1021 | assert(unit_path); | |
1022 | assert(ret_conditions); | |
1023 | assert(ret_paths); | |
1024 | ||
1025 | r = sd_bus_get_property(bus, | |
1026 | "org.freedesktop.systemd1", | |
1027 | unit_path, | |
1028 | "org.freedesktop.systemd1.Path", | |
1029 | "Paths", | |
1030 | &error, | |
1031 | &reply, | |
1032 | "a(ss)"); | |
1033 | if (r < 0) | |
1034 | return log_error_errno(r, "Failed to get paths: %s", bus_error_message(&error, r)); | |
1035 | ||
1036 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); | |
1037 | if (r < 0) | |
1038 | return bus_log_parse_error(r); | |
1039 | ||
1040 | while ((r = sd_bus_message_read(reply, "(ss)", &condition, &path)) > 0) { | |
1041 | if (strv_extend(&conditions, condition) < 0) | |
1042 | return log_oom(); | |
1043 | ||
1044 | if (strv_extend(&paths, path) < 0) | |
1045 | return log_oom(); | |
1046 | ||
1047 | n++; | |
1048 | } | |
1049 | if (r < 0) | |
1050 | return bus_log_parse_error(r); | |
1051 | ||
1052 | r = sd_bus_message_exit_container(reply); | |
1053 | if (r < 0) | |
1054 | return bus_log_parse_error(r); | |
1055 | ||
1056 | *ret_conditions = TAKE_PTR(conditions); | |
1057 | *ret_paths = TAKE_PTR(paths); | |
1058 | ||
1059 | return n; | |
1060 | } | |
1061 | ||
1062 | static int output_paths_list(struct path_infos *ps) { | |
1063 | _cleanup_(table_unrefp) Table *table = NULL; | |
1064 | int r; | |
1065 | ||
1066 | assert(ps); | |
1067 | assert(ps->items || ps->count == 0); | |
1068 | ||
1069 | table = table_new("path", "condition", "unit", "activates"); | |
1070 | if (!table) | |
1071 | return log_oom(); | |
1072 | ||
1073 | table_set_header(table, arg_legend != 0); | |
1074 | if (arg_full) | |
1075 | table_set_width(table, 0); | |
1076 | ||
1077 | table_set_ersatz_string(table, TABLE_ERSATZ_DASH); | |
1078 | ||
1079 | for (struct path_info *p = ps->items; p < ps->items + ps->count; p++) { | |
1080 | _cleanup_free_ char *unit = NULL; | |
1081 | ||
1082 | unit = format_unit_id(p->id, p->machine); | |
1083 | if (!unit) | |
1084 | return log_oom(); | |
1085 | ||
1086 | r = table_add_many(table, | |
1087 | TABLE_STRING, p->path, | |
1088 | TABLE_STRING, p->condition, | |
1089 | TABLE_STRING, unit); | |
1090 | if (r < 0) | |
1091 | return table_log_add_error(r); | |
1092 | ||
1093 | r = table_add_triggered(table, p->triggered); | |
1094 | if (r < 0) | |
1095 | return table_log_add_error(r); | |
1096 | } | |
1097 | ||
1098 | r = output_table(table); | |
1099 | if (r < 0) | |
1100 | return r; | |
1101 | ||
1102 | if (arg_legend != 0) | |
1103 | output_legend("path", ps->count); | |
1104 | ||
1105 | return 0; | |
1106 | } | |
1107 | ||
1108 | int verb_list_paths(int argc, char *argv[], void *userdata) { | |
1109 | _cleanup_(message_set_freep) Set *replies = NULL; | |
f2ccc0d3 | 1110 | _cleanup_strv_free_ char **units = NULL; |
5fb5f49b DT |
1111 | _cleanup_free_ UnitInfo *unit_infos = NULL; |
1112 | _cleanup_(path_infos_done) struct path_infos path_infos = {}; | |
1113 | int r, n; | |
1114 | sd_bus *bus; | |
1115 | ||
1116 | r = acquire_bus(BUS_MANAGER, &bus); | |
1117 | if (r < 0) | |
1118 | return r; | |
1119 | ||
1120 | pager_open(arg_pager_flags); | |
1121 | ||
1122 | r = expand_unit_names(bus, strv_skip(argv, 1), ".path", &units, NULL); | |
1123 | if (r < 0) | |
1124 | return r; | |
1125 | ||
1126 | if (argc == 1 || units) { | |
f2ccc0d3 | 1127 | n = get_unit_list_recursive(bus, units, &unit_infos, &replies); |
5fb5f49b DT |
1128 | if (n < 0) |
1129 | return n; | |
1130 | ||
1131 | for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { | |
1132 | _cleanup_strv_free_ char **conditions = NULL, **paths = NULL, **triggered = NULL; | |
1133 | int c; | |
1134 | ||
1135 | if (!endswith(u->id, ".path")) | |
1136 | continue; | |
1137 | ||
1138 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
1139 | if (r < 0) | |
1140 | return r; | |
1141 | ||
1142 | c = get_paths(bus, u->unit_path, &conditions, &paths); | |
1143 | if (c < 0) | |
1144 | return c; | |
1145 | ||
1146 | if (!GREEDY_REALLOC(path_infos.items, path_infos.count + c)) | |
1147 | return log_oom(); | |
1148 | ||
1149 | for (int i = c - 1; i >= 0; i--) { | |
1150 | char **t; | |
1151 | ||
1152 | t = strv_copy(triggered); | |
1153 | if (!t) | |
1154 | return log_oom(); | |
1155 | ||
1156 | path_infos.items[path_infos.count++] = (struct path_info) { | |
1157 | .machine = u->machine, | |
1158 | .id = u->id, | |
1159 | .condition = TAKE_PTR(conditions[i]), | |
1160 | .path = TAKE_PTR(paths[i]), | |
1161 | .triggered = t, | |
1162 | }; | |
1163 | } | |
1164 | } | |
1165 | ||
1166 | typesafe_qsort(path_infos.items, path_infos.count, path_info_compare); | |
1167 | } | |
1168 | ||
1169 | output_paths_list(&path_infos); | |
1170 | ||
1171 | return 0; | |
1172 | } |