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