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