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