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