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