]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/systemctl/systemctl-list-units.c
Merge pull request #26540 from poettering/systemctl-list-tweaks
[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 (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
620 (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
621
622 for (struct timer_info *t = timer_infos; t < timer_infos + n; t++) {
623 _cleanup_free_ char *unit = NULL;
624
625 unit = format_unit_id(t->id, t->machine);
626 if (!unit)
627 return log_oom();
628
629 r = table_add_many(table,
630 TABLE_TIMESTAMP, t->next_elapse,
631 TABLE_TIMESTAMP_RELATIVE, t->next_elapse,
632 TABLE_TIMESTAMP, t->last_trigger,
633 TABLE_TIMESTAMP_RELATIVE, t->last_trigger,
634 TABLE_STRING, unit);
635 if (r < 0)
636 return table_log_add_error(r);
637
638 r = table_add_triggered(table, t->triggered);
639 if (r < 0)
640 return table_log_add_error(r);
641 }
642
643 r = output_table(table);
644 if (r < 0)
645 return r;
646
647 if (arg_legend != 0)
648 output_legend("timer", n);
649
650 return 0;
651 }
652
653 usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) {
654 usec_t next_elapse;
655
656 assert(nw);
657 assert(next);
658
659 if (timestamp_is_set(next->monotonic)) {
660 usec_t converted;
661
662 if (next->monotonic > nw->monotonic)
663 converted = nw->realtime + (next->monotonic - nw->monotonic);
664 else
665 converted = nw->realtime - (nw->monotonic - next->monotonic);
666
667 if (timestamp_is_set(next->realtime))
668 next_elapse = MIN(converted, next->realtime);
669 else
670 next_elapse = converted;
671
672 } else
673 next_elapse = next->realtime;
674
675 return next_elapse;
676 }
677
678 int verb_list_timers(int argc, char *argv[], void *userdata) {
679 _cleanup_(message_set_freep) Set *replies = NULL;
680 _cleanup_strv_free_ char **machines = NULL;
681 _cleanup_strv_free_ char **timers_with_suffix = NULL;
682 _cleanup_free_ struct timer_info *timer_infos = NULL;
683 _cleanup_free_ UnitInfo *unit_infos = NULL;
684 dual_timestamp nw;
685 size_t c = 0;
686 sd_bus *bus;
687 int n, r;
688
689 r = acquire_bus(BUS_MANAGER, &bus);
690 if (r < 0)
691 return r;
692
693 pager_open(arg_pager_flags);
694
695 r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL);
696 if (r < 0)
697 return r;
698
699 if (argc == 1 || timers_with_suffix) {
700 n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies, &machines);
701 if (n < 0)
702 return n;
703
704 dual_timestamp_get(&nw);
705
706 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
707 _cleanup_strv_free_ char **triggered = NULL;
708 dual_timestamp next = DUAL_TIMESTAMP_NULL;
709 usec_t m, last = 0;
710
711 if (!endswith(u->id, ".timer"))
712 continue;
713
714 r = get_triggered_units(bus, u->unit_path, &triggered);
715 if (r < 0)
716 goto cleanup;
717
718 r = get_next_elapse(bus, u->unit_path, &next);
719 if (r < 0)
720 goto cleanup;
721
722 get_last_trigger(bus, u->unit_path, &last);
723
724 if (!GREEDY_REALLOC(timer_infos, c+1)) {
725 r = log_oom();
726 goto cleanup;
727 }
728
729 m = calc_next_elapse(&nw, &next);
730
731 timer_infos[c++] = (struct timer_info) {
732 .machine = u->machine,
733 .id = u->id,
734 .next_elapse = m,
735 .last_trigger = last,
736 .triggered = TAKE_PTR(triggered),
737 };
738 }
739
740 typesafe_qsort(timer_infos, c, timer_info_compare);
741 }
742
743 output_timers_list(timer_infos, c);
744
745 cleanup:
746 for (struct timer_info *t = timer_infos; t < timer_infos + c; t++)
747 strv_free(t->triggered);
748
749 return r;
750 }
751
752 struct automount_info {
753 const char *machine;
754 const char *id;
755 char *what;
756 char *where;
757 usec_t timeout_idle_usec;
758 bool mounted;
759 };
760
761 static int automount_info_compare(const struct automount_info *a, const struct automount_info *b) {
762 int r;
763
764 assert(a);
765 assert(b);
766
767 r = strcasecmp_ptr(a->machine, b->machine);
768 if (r != 0)
769 return r;
770
771 return strcmp(a->where, b->where);
772 }
773
774 static int collect_automount_info(sd_bus* bus, const UnitInfo* info, struct automount_info *ret_info) {
775 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
776 _cleanup_free_ char *mount = NULL, *mount_path = NULL, *where = NULL, *what = NULL, *state = NULL;
777 uint64_t timeout_idle_usec;
778 BusLocator locator;
779 int r;
780
781 assert(bus);
782 assert(info);
783 assert(ret_info);
784
785 locator = (BusLocator) {
786 .destination = "org.freedesktop.systemd1",
787 .path = info->unit_path,
788 .interface = "org.freedesktop.systemd1.Automount",
789 };
790
791 r = bus_get_property_string(bus, &locator, "Where", &error, &where);
792 if (r < 0)
793 return log_error_errno(r, "Failed to get automount target: %s", bus_error_message(&error, r));
794
795 r = bus_get_property_trivial(bus, &locator, "TimeoutIdleUSec", &error, 't', &timeout_idle_usec);
796 if (r < 0)
797 return log_error_errno(r, "Failed to get idle timeout: %s", bus_error_message(&error, r));
798
799 r = unit_name_from_path(where, ".mount", &mount);
800 if (r < 0)
801 return log_error_errno(r, "Failed to generate unit name from path: %m");
802
803 mount_path = unit_dbus_path_from_name(mount);
804 if (!mount_path)
805 return log_oom();
806
807 locator.path = mount_path;
808 locator.interface = "org.freedesktop.systemd1.Mount";
809
810 r = bus_get_property_string(bus, &locator, "What", &error, &what);
811 if (r < 0)
812 return log_error_errno(r, "Failed to get mount source: %s", bus_error_message(&error, r));
813
814 locator.interface = "org.freedesktop.systemd1.Unit";
815
816 r = bus_get_property_string(bus, &locator, "ActiveState", &error, &state);
817 if (r < 0)
818 return log_error_errno(r, "Failed to get mount state: %s", bus_error_message(&error, r));
819
820 *ret_info = (struct automount_info) {
821 .machine = info->machine,
822 .id = info->id,
823 .what = TAKE_PTR(what),
824 .where = TAKE_PTR(where),
825 .timeout_idle_usec = timeout_idle_usec,
826 .mounted = streq_ptr(state, "active"),
827 };
828
829 return 0;
830 }
831
832 static int output_automounts_list(struct automount_info *infos, size_t n_infos) {
833 _cleanup_(table_unrefp) Table *table = NULL;
834 int r;
835
836 assert(infos || n_infos == 0);
837
838 table = table_new("what", "where", "mounted", "idle timeout", "unit");
839 if (!table)
840 return log_oom();
841
842 table_set_header(table, arg_legend != 0);
843 if (arg_full)
844 table_set_width(table, 0);
845
846 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
847
848 for (struct automount_info *info = infos; info < infos + n_infos; info++) {
849 _cleanup_free_ char *unit = NULL;
850
851 unit = format_unit_id(info->id, info->machine);
852 if (!unit)
853 return log_oom();
854
855 r = table_add_many(table,
856 TABLE_STRING, info->what,
857 TABLE_STRING, info->where,
858 TABLE_BOOLEAN, info->mounted);
859 if (r < 0)
860 return table_log_add_error(r);
861
862 if (timestamp_is_set(info->timeout_idle_usec))
863 r = table_add_cell(table, NULL, TABLE_TIMESPAN_MSEC, &info->timeout_idle_usec);
864 else
865 r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
866 if (r < 0)
867 return table_log_add_error(r);
868
869 r = table_add_cell(table, NULL, TABLE_STRING, unit);
870 if (r < 0)
871 return table_log_add_error(r);
872 }
873
874 r = output_table(table);
875 if (r < 0)
876 return r;
877
878 if (arg_legend != 0)
879 output_legend("automount", n_infos);
880
881 return 0;
882 }
883
884 int verb_list_automounts(int argc, char *argv[], void *userdata) {
885 _cleanup_(message_set_freep) Set *replies = NULL;
886 _cleanup_strv_free_ char **machines = NULL, **automounts = NULL;
887 _cleanup_free_ UnitInfo *unit_infos = NULL;
888 _cleanup_free_ struct automount_info *automount_infos = NULL;
889 size_t c = 0;
890 int r, n;
891 sd_bus *bus;
892
893 r = acquire_bus(BUS_MANAGER, &bus);
894 if (r < 0)
895 return r;
896
897 pager_open(arg_pager_flags);
898
899 r = expand_unit_names(bus, strv_skip(argv, 1), ".automount", &automounts, NULL);
900 if (r < 0)
901 return r;
902
903 if (argc == 1 || automounts) {
904 n = get_unit_list_recursive(bus, automounts, &unit_infos, &replies, &machines);
905 if (n < 0)
906 return n;
907
908 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
909 if (!endswith(u->id, ".automount"))
910 continue;
911
912 if (!GREEDY_REALLOC(automount_infos, c + 1)) {
913 r = log_oom();
914 goto cleanup;
915 }
916
917 r = collect_automount_info(bus, u, &automount_infos[c]);
918 if (r < 0)
919 goto cleanup;
920
921 c++;
922 }
923
924 typesafe_qsort(automount_infos, c, automount_info_compare);
925 }
926
927 output_automounts_list(automount_infos, c);
928
929 cleanup:
930 assert(c == 0 || automount_infos);
931 for (struct automount_info *info = automount_infos; info < automount_infos + c; info++) {
932 free(info->what);
933 free(info->where);
934 }
935
936 return r;
937 }
938
939 struct path_info {
940 const char *machine;
941 const char *id;
942
943 char *path;
944 char *condition;
945
946 /* Note: triggered is a list here, although it almost certainly will always be one
947 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
948 char** triggered;
949 };
950
951 struct path_infos {
952 size_t count;
953 struct path_info *items;
954 };
955
956 static int path_info_compare(const struct path_info *a, const struct path_info *b) {
957 int r;
958
959 assert(a);
960 assert(b);
961
962 r = strcasecmp_ptr(a->machine, b->machine);
963 if (r != 0)
964 return r;
965
966 r = path_compare(a->path, b->path);
967 if (r != 0)
968 return r;
969
970 r = strcmp(a->condition, b->condition);
971 if (r != 0)
972 return r;
973
974 return strcasecmp_ptr(a->id, b->id);
975 }
976
977 static void path_infos_done(struct path_infos *ps) {
978 assert(ps);
979 assert(ps->items || ps->count == 0);
980
981 for (struct path_info *p = ps->items; p < ps->items + ps->count; p++) {
982 free(p->condition);
983 free(p->path);
984 strv_free(p->triggered);
985 }
986
987 free(ps->items);
988 }
989
990 static int get_paths(sd_bus *bus, const char *unit_path, char ***ret_conditions, char ***ret_paths) {
991 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
992 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
993 _cleanup_strv_free_ char **conditions = NULL, **paths = NULL;
994 const char *condition, *path;
995 int r, n = 0;
996
997 assert(bus);
998 assert(unit_path);
999 assert(ret_conditions);
1000 assert(ret_paths);
1001
1002 r = sd_bus_get_property(bus,
1003 "org.freedesktop.systemd1",
1004 unit_path,
1005 "org.freedesktop.systemd1.Path",
1006 "Paths",
1007 &error,
1008 &reply,
1009 "a(ss)");
1010 if (r < 0)
1011 return log_error_errno(r, "Failed to get paths: %s", bus_error_message(&error, r));
1012
1013 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
1014 if (r < 0)
1015 return bus_log_parse_error(r);
1016
1017 while ((r = sd_bus_message_read(reply, "(ss)", &condition, &path)) > 0) {
1018 if (strv_extend(&conditions, condition) < 0)
1019 return log_oom();
1020
1021 if (strv_extend(&paths, path) < 0)
1022 return log_oom();
1023
1024 n++;
1025 }
1026 if (r < 0)
1027 return bus_log_parse_error(r);
1028
1029 r = sd_bus_message_exit_container(reply);
1030 if (r < 0)
1031 return bus_log_parse_error(r);
1032
1033 *ret_conditions = TAKE_PTR(conditions);
1034 *ret_paths = TAKE_PTR(paths);
1035
1036 return n;
1037 }
1038
1039 static int output_paths_list(struct path_infos *ps) {
1040 _cleanup_(table_unrefp) Table *table = NULL;
1041 int r;
1042
1043 assert(ps);
1044 assert(ps->items || ps->count == 0);
1045
1046 table = table_new("path", "condition", "unit", "activates");
1047 if (!table)
1048 return log_oom();
1049
1050 table_set_header(table, arg_legend != 0);
1051 if (arg_full)
1052 table_set_width(table, 0);
1053
1054 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
1055
1056 for (struct path_info *p = ps->items; p < ps->items + ps->count; p++) {
1057 _cleanup_free_ char *unit = NULL;
1058
1059 unit = format_unit_id(p->id, p->machine);
1060 if (!unit)
1061 return log_oom();
1062
1063 r = table_add_many(table,
1064 TABLE_STRING, p->path,
1065 TABLE_STRING, p->condition,
1066 TABLE_STRING, unit);
1067 if (r < 0)
1068 return table_log_add_error(r);
1069
1070 r = table_add_triggered(table, p->triggered);
1071 if (r < 0)
1072 return table_log_add_error(r);
1073 }
1074
1075 r = output_table(table);
1076 if (r < 0)
1077 return r;
1078
1079 if (arg_legend != 0)
1080 output_legend("path", ps->count);
1081
1082 return 0;
1083 }
1084
1085 int verb_list_paths(int argc, char *argv[], void *userdata) {
1086 _cleanup_(message_set_freep) Set *replies = NULL;
1087 _cleanup_strv_free_ char **machines = NULL, **units = NULL;
1088 _cleanup_free_ UnitInfo *unit_infos = NULL;
1089 _cleanup_(path_infos_done) struct path_infos path_infos = {};
1090 int r, n;
1091 sd_bus *bus;
1092
1093 r = acquire_bus(BUS_MANAGER, &bus);
1094 if (r < 0)
1095 return r;
1096
1097 pager_open(arg_pager_flags);
1098
1099 r = expand_unit_names(bus, strv_skip(argv, 1), ".path", &units, NULL);
1100 if (r < 0)
1101 return r;
1102
1103 if (argc == 1 || units) {
1104 n = get_unit_list_recursive(bus, units, &unit_infos, &replies, &machines);
1105 if (n < 0)
1106 return n;
1107
1108 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
1109 _cleanup_strv_free_ char **conditions = NULL, **paths = NULL, **triggered = NULL;
1110 int c;
1111
1112 if (!endswith(u->id, ".path"))
1113 continue;
1114
1115 r = get_triggered_units(bus, u->unit_path, &triggered);
1116 if (r < 0)
1117 return r;
1118
1119 c = get_paths(bus, u->unit_path, &conditions, &paths);
1120 if (c < 0)
1121 return c;
1122
1123 if (!GREEDY_REALLOC(path_infos.items, path_infos.count + c))
1124 return log_oom();
1125
1126 for (int i = c - 1; i >= 0; i--) {
1127 char **t;
1128
1129 t = strv_copy(triggered);
1130 if (!t)
1131 return log_oom();
1132
1133 path_infos.items[path_infos.count++] = (struct path_info) {
1134 .machine = u->machine,
1135 .id = u->id,
1136 .condition = TAKE_PTR(conditions[i]),
1137 .path = TAKE_PTR(paths[i]),
1138 .triggered = t,
1139 };
1140 }
1141 }
1142
1143 typesafe_qsort(path_infos.items, path_infos.count, path_info_compare);
1144 }
1145
1146 output_paths_list(&path_infos);
1147
1148 return 0;
1149 }