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