1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
7 #include "ansi-color.h"
9 #include "bus-locator.h"
10 #include "bus-message-util.h"
11 #include "bus-unit-util.h"
13 #include "format-table.h"
14 #include "glyph-util.h"
15 #include "path-util.h"
17 #include "sort-util.h"
18 #include "string-util.h"
20 #include "systemctl.h"
21 #include "systemctl-list-units.h"
22 #include "systemctl-util.h"
24 #include "unit-name.h"
26 static int get_unit_list_recursive(
29 UnitInfo
**ret_unit_infos
,
32 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
33 _cleanup_set_free_ Set
*replies
= NULL
;
34 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
39 assert(ret_unit_infos
);
41 c
= get_unit_list(bus
, NULL
, patterns
, &unit_infos
, 0, &reply
);
45 r
= set_ensure_consume(&replies
, &bus_message_hash_ops
, TAKE_PTR(reply
));
50 _cleanup_strv_free_
char **machines
= NULL
;
52 r
= sd_get_machine_names(&machines
);
54 return log_error_errno(r
, "Failed to get machine names: %m");
56 STRV_FOREACH(i
, machines
) {
57 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*container
= NULL
;
60 r
= sd_bus_open_system_machine(&container
, *i
);
62 log_warning_errno(r
, "Failed to connect to container %s, ignoring: %m", *i
);
66 k
= get_unit_list(container
, *i
, patterns
, &unit_infos
, c
, &reply
);
72 r
= set_consume(replies
, TAKE_PTR(reply
));
78 *ret_unit_infos
= TAKE_PTR(unit_infos
);
79 *ret_replies
= TAKE_PTR(replies
);
84 static void output_legend(const char *type
, size_t n_items
) {
89 on
= n_items
> 0 ? ansi_highlight() : ansi_highlight_red();
92 printf("\n%s%zu %ss listed.%s\n", on
, n_items
, type
, off
);
94 printf("Pass --all to see loaded but inactive %ss, too.\n", type
);
97 static int table_add_triggered(Table
*table
, char **triggered
) {
100 if (strv_isempty(triggered
))
101 return table_add_cell(table
, NULL
, TABLE_EMPTY
, NULL
);
102 else if (strv_length(triggered
) == 1)
103 return table_add_cell(table
, NULL
, TABLE_STRING
, triggered
[0]);
105 /* This should never happen, currently our socket units can only trigger a
106 * single unit. But let's handle this anyway, who knows what the future
108 return table_add_cell(table
, NULL
, TABLE_STRV
, triggered
);
111 static char *format_unit_id(const char *unit
, const char *machine
) {
114 return machine
? strjoin(machine
, ":", unit
) : strdup(unit
);
117 static int output_units_list(const UnitInfo
*unit_infos
, size_t c
) {
118 _cleanup_(table_unrefp
) Table
*table
= NULL
;
119 size_t job_count
= 0;
122 table
= table_new("", "unit", "load", "active", "sub", "job", "description");
126 table_set_header(table
, arg_legend
!= 0);
128 /* Hide the 'glyph' column when --plain is requested */
129 r
= table_hide_column_from_display(table
, 0);
131 return log_error_errno(r
, "Failed to hide column: %m");
134 table_set_width(table
, 0);
136 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
138 FOREACH_ARRAY(u
, unit_infos
, c
) {
139 const char *on_loaded
= NULL
, *on_active
= NULL
, *on_sub
= NULL
, *on_circle
= NULL
;
140 _cleanup_free_
char *id
= NULL
;
141 bool circle
= false, underline
;
143 underline
= u
+ 1 < unit_infos
+ c
&& !streq(unit_type_suffix(u
->id
), unit_type_suffix((u
+ 1)->id
));
145 if (streq(u
->load_state
, "not-found")) {
146 on_circle
= on_loaded
= ansi_highlight_yellow();
148 } else if (STR_IN_SET(u
->load_state
, "bad-setting", "error", "masked")) {
149 on_loaded
= ansi_highlight_red();
150 on_circle
= ansi_highlight_yellow();
154 if (streq(u
->active_state
, "failed")) {
155 on_sub
= on_active
= ansi_highlight_red();
157 /* Here override any load_state highlighting */
158 on_circle
= ansi_highlight_red();
160 } else if (STR_IN_SET(u
->active_state
, "reloading", "activating", "maintenance", "refreshing", "deactivating")) {
161 on_sub
= on_active
= ansi_highlight();
163 if (!circle
) { /* Here we let load_state highlighting win */
164 on_circle
= ansi_highlight();
167 } else if (streq(u
->active_state
, "inactive"))
168 on_sub
= on_active
= ansi_grey();
170 /* As a special case, when this is a service which has not process running, let's grey out
171 * its state, to highlight that a bit */
172 if (!on_sub
&& endswith(u
->id
, ".service") && streq(u
->sub_state
, "exited"))
173 on_sub
= ansi_grey();
178 id
= format_unit_id(u
->id
, u
->machine
);
182 r
= table_add_many(table
,
183 TABLE_STRING
, circle
? glyph(GLYPH_BLACK_CIRCLE
) : " ",
184 TABLE_SET_COLOR
, on_circle
,
185 TABLE_SET_BOTH_UNDERLINES
, underline
,
187 TABLE_SET_COLOR
, on_active
,
188 TABLE_SET_BOTH_UNDERLINES
, underline
,
189 TABLE_STRING
, u
->load_state
,
190 TABLE_SET_COLOR
, on_loaded
,
191 TABLE_SET_BOTH_UNDERLINES
, underline
,
192 TABLE_STRING
, u
->active_state
,
193 TABLE_SET_COLOR
, on_active
,
194 TABLE_SET_BOTH_UNDERLINES
, underline
,
195 TABLE_STRING
, u
->sub_state
,
196 TABLE_SET_COLOR
, on_sub
,
197 TABLE_SET_BOTH_UNDERLINES
, underline
,
198 TABLE_STRING
, u
->job_id
? u
->job_type
: "",
199 TABLE_SET_BOTH_UNDERLINES
, underline
,
200 TABLE_STRING
, u
->description
,
201 TABLE_SET_BOTH_UNDERLINES
, underline
);
203 return table_log_add_error(r
);
209 if (job_count
== 0) {
210 /* There's no data in the JOB column, so let's hide it */
211 r
= table_hide_column_from_display(table
, 5);
213 return log_error_errno(r
, "Failed to hide column: %m");
216 r
= output_table(table
);
220 if (arg_legend
!= 0) {
221 const char *on
, *off
;
222 size_t records
= table_get_rows(table
) - 1;
226 "%1$sLegend: LOAD %2$s Reflects whether the unit definition was properly loaded.%3$s\n"
227 "%1$s ACTIVE %2$s The high-level unit activation state, i.e. generalization of SUB.%3$s\n"
228 "%1$s SUB %2$s The low-level unit activation state, values depend on unit type.%3$s\n",
230 glyph(GLYPH_ARROW_RIGHT
),
233 printf("%s JOB %s Pending job for the unit.%s\n",
235 glyph(GLYPH_ARROW_RIGHT
),
241 on
= records
> 0 ? ansi_highlight() : ansi_highlight_red();
244 if (arg_all
|| strv_contains(arg_states
, "inactive"))
245 printf("%s%zu loaded units listed.%s\n"
246 "%sTo show all installed unit files use 'systemctl list-unit-files'.%s\n",
248 ansi_grey(), ansi_normal());
249 else if (!arg_states
)
250 printf("%s%zu loaded units listed.%s %sPass --all to see loaded but inactive units, too.%s\n"
251 "%sTo show all installed unit files use 'systemctl list-unit-files'.%s\n",
253 ansi_grey(), ansi_normal(), ansi_grey(), ansi_normal());
255 printf("%zu loaded units listed.\n", records
);
261 int verb_list_units(int argc
, char *argv
[], void *userdata
) {
262 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
263 _cleanup_set_free_ Set
*replies
= NULL
;
267 r
= acquire_bus(BUS_MANAGER
, &bus
);
271 pager_open(arg_pager_flags
);
273 if (arg_with_dependencies
) {
274 _cleanup_strv_free_
char **names
= NULL
;
276 r
= append_unit_dependencies(bus
, strv_skip(argv
, 1), &names
);
280 r
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
);
284 r
= get_unit_list_recursive(bus
, strv_skip(argv
, 1), &unit_infos
, &replies
);
289 typesafe_qsort(unit_infos
, r
, unit_info_compare
);
290 return output_units_list(unit_infos
, r
);
293 static int get_triggered_units(
298 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
305 r
= sd_bus_get_property_strv(
307 "org.freedesktop.systemd1",
309 "org.freedesktop.systemd1.Unit",
314 return log_error_errno(r
, "Failed to determine triggers: %s", bus_error_message(&error
, r
));
319 typedef struct SocketInfo
{
324 char* path
; /* absolute path or socket address */
326 /* Note: triggered is a list here, although it almost certainly will always be one
327 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
331 static void socket_info_array_free(SocketInfo
*sockets
, size_t n_sockets
) {
332 assert(sockets
|| n_sockets
== 0);
334 FOREACH_ARRAY(s
, sockets
, n_sockets
) {
337 strv_free(s
->triggered
);
343 static int socket_info_compare(const SocketInfo
*a
, const SocketInfo
*b
) {
349 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
353 r
= CMP(path_is_absolute(a
->path
), path_is_absolute(b
->path
));
357 r
= path_is_absolute(a
->path
) ? path_compare(a
->path
, b
->path
) : strcmp(a
->path
, b
->path
);
361 return strcmp(a
->type
, b
->type
);
364 static int socket_info_add(
367 SocketInfo
**sockets
,
370 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
371 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
372 _cleanup_strv_free_
char **triggered
= NULL
;
373 const char *type
, *path
;
381 if (!endswith(u
->id
, ".socket"))
384 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
388 r
= sd_bus_get_property(
390 "org.freedesktop.systemd1",
392 "org.freedesktop.systemd1.Socket",
398 return log_error_errno(r
, "Failed to get list of listening sockets: %s", bus_error_message(&error
, r
));
400 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
402 return bus_log_parse_error(r
);
404 while ((r
= sd_bus_message_read(reply
, "(ss)", &type
, &path
)) > 0) {
405 _cleanup_free_
char *type_dup
= NULL
, *path_dup
= NULL
;
406 _cleanup_strv_free_
char **triggered_dup
= NULL
;
408 type_dup
= strdup(type
);
412 path_dup
= strdup(path
);
416 triggered_dup
= strv_copy(triggered
);
420 if (!GREEDY_REALLOC(*sockets
, *n_sockets
+ 1))
423 (*sockets
)[(*n_sockets
)++] = (SocketInfo
) {
424 .machine
= u
->machine
,
426 .type
= TAKE_PTR(type_dup
),
427 .path
= TAKE_PTR(path_dup
),
428 .triggered
= TAKE_PTR(triggered_dup
),
432 return bus_log_parse_error(r
);
434 r
= sd_bus_message_exit_container(reply
);
436 return bus_log_parse_error(r
);
441 static int output_sockets_list(const SocketInfo
*sockets
, size_t n_sockets
) {
442 _cleanup_(table_unrefp
) Table
*table
= NULL
;
445 assert(sockets
|| n_sockets
== 0);
447 table
= table_new("listen", "type", "unit", "activates");
451 if (!arg_show_types
) {
452 /* Hide the second (TYPE) column */
453 r
= table_set_display(table
, (size_t) 0, (size_t) 2, (size_t) 3);
455 return log_error_errno(r
, "Failed to set columns to display: %m");
458 table_set_header(table
, arg_legend
!= 0);
460 table_set_width(table
, 0);
462 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
464 FOREACH_ARRAY(s
, sockets
, n_sockets
) {
465 _cleanup_free_
char *unit
= NULL
;
467 unit
= format_unit_id(s
->id
, s
->machine
);
471 r
= table_add_many(table
,
472 TABLE_STRING
, s
->path
,
473 TABLE_STRING
, s
->type
,
476 return table_log_add_error(r
);
478 r
= table_add_triggered(table
, s
->triggered
);
480 return table_log_add_error(r
);
483 r
= output_table(table
);
488 output_legend("socket", n_sockets
);
493 int verb_list_sockets(int argc
, char *argv
[], void *userdata
) {
494 _cleanup_set_free_ Set
*replies
= NULL
;
495 _cleanup_strv_free_
char **sockets_with_suffix
= NULL
;
496 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
497 SocketInfo
*sockets
= NULL
;
498 size_t n_sockets
= 0;
502 CLEANUP_ARRAY(sockets
, n_sockets
, socket_info_array_free
);
504 r
= acquire_bus(BUS_MANAGER
, &bus
);
508 pager_open(arg_pager_flags
);
510 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".socket", &sockets_with_suffix
, NULL
);
514 if (argc
== 1 || sockets_with_suffix
) {
517 n
= get_unit_list_recursive(bus
, sockets_with_suffix
, &unit_infos
, &replies
);
521 FOREACH_ARRAY(u
, unit_infos
, n
) {
522 r
= socket_info_add(bus
, u
, &sockets
, &n_sockets
);
528 typesafe_qsort(sockets
, n_sockets
, socket_info_compare
);
529 output_sockets_list(sockets
, n_sockets
);
534 static int get_next_elapse(
537 dual_timestamp
*next
) {
539 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
547 r
= sd_bus_get_property_trivial(
549 "org.freedesktop.systemd1",
551 "org.freedesktop.systemd1.Timer",
552 "NextElapseUSecMonotonic",
557 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
559 r
= sd_bus_get_property_trivial(
561 "org.freedesktop.systemd1",
563 "org.freedesktop.systemd1.Timer",
564 "NextElapseUSecRealtime",
569 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
575 static int get_last_trigger(
578 dual_timestamp
*last
) {
580 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
588 r
= sd_bus_get_property_trivial(
590 "org.freedesktop.systemd1",
592 "org.freedesktop.systemd1.Timer",
598 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
600 r
= sd_bus_get_property_trivial(
602 "org.freedesktop.systemd1",
604 "org.freedesktop.systemd1.Timer",
605 "LastTriggerUSecMonotonic",
610 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
616 typedef struct TimerInfo
{
620 dual_timestamp last_trigger
;
624 static void timer_info_array_free(TimerInfo
*timers
, size_t n_timers
) {
625 assert(timers
|| n_timers
== 0);
627 FOREACH_ARRAY(t
, timers
, n_timers
)
628 strv_free(t
->triggered
);
633 static int timer_info_compare(const TimerInfo
*a
, const TimerInfo
*b
) {
639 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
643 r
= CMP(a
->next_elapse
, b
->next_elapse
);
647 return strcmp(a
->id
, b
->id
);
650 static int output_timers_list(const TimerInfo
*timers
, size_t n_timers
) {
651 _cleanup_(table_unrefp
) Table
*table
= NULL
;
654 assert(timers
|| n_timers
== 0);
656 table
= table_new("next", "left", "last", "passed", "unit", "activates");
660 table_set_header(table
, arg_legend
!= 0);
662 table_set_width(table
, 0);
664 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
666 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
667 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
669 FOREACH_ARRAY(t
, timers
, n_timers
) {
670 _cleanup_free_
char *unit
= NULL
;
672 unit
= format_unit_id(t
->id
, t
->machine
);
676 r
= table_add_many(table
,
677 TABLE_TIMESTAMP
, t
->next_elapse
,
678 TABLE_TIMESTAMP_LEFT
, t
->next_elapse
,
679 TABLE_TIMESTAMP
, t
->last_trigger
.realtime
,
680 TABLE_TIMESTAMP_RELATIVE_MONOTONIC
, t
->last_trigger
.monotonic
,
683 return table_log_add_error(r
);
685 r
= table_add_triggered(table
, t
->triggered
);
687 return table_log_add_error(r
);
690 r
= output_table(table
);
695 output_legend("timer", n_timers
);
700 usec_t
calc_next_elapse(const dual_timestamp
*nw
, const dual_timestamp
*next
) {
706 if (timestamp_is_set(next
->monotonic
)) {
709 if (next
->monotonic
> nw
->monotonic
)
710 converted
= nw
->realtime
+ (next
->monotonic
- nw
->monotonic
);
712 converted
= nw
->realtime
- (nw
->monotonic
- next
->monotonic
);
714 if (timestamp_is_set(next
->realtime
))
715 next_elapse
= MIN(converted
, next
->realtime
);
717 next_elapse
= converted
;
720 next_elapse
= next
->realtime
;
725 static int add_timer_info(
728 const dual_timestamp
*nw
,
732 _cleanup_strv_free_
char **triggered
= NULL
;
733 dual_timestamp next
, last
;
743 if (!endswith(u
->id
, ".timer"))
746 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
750 r
= get_next_elapse(bus
, u
->unit_path
, &next
);
754 r
= get_last_trigger(bus
, u
->unit_path
, &last
);
758 m
= calc_next_elapse(nw
, &next
);
760 if (!GREEDY_REALLOC(*timers
, *n_timers
+ 1))
763 (*timers
)[(*n_timers
)++] = (TimerInfo
) {
764 .machine
= u
->machine
,
767 .last_trigger
= last
,
768 .triggered
= TAKE_PTR(triggered
),
774 int verb_list_timers(int argc
, char *argv
[], void *userdata
) {
775 _cleanup_set_free_ Set
*replies
= NULL
;
776 _cleanup_strv_free_
char **timers_with_suffix
= NULL
;
777 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
778 TimerInfo
*timers
= NULL
;
783 CLEANUP_ARRAY(timers
, n_timers
, timer_info_array_free
);
785 r
= acquire_bus(BUS_MANAGER
, &bus
);
789 pager_open(arg_pager_flags
);
791 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".timer", &timers_with_suffix
, NULL
);
795 if (argc
== 1 || timers_with_suffix
) {
799 n
= get_unit_list_recursive(bus
, timers_with_suffix
, &unit_infos
, &replies
);
803 dual_timestamp_now(&nw
);
805 FOREACH_ARRAY(u
, unit_infos
, n
) {
806 r
= add_timer_info(bus
, u
, &nw
, &timers
, &n_timers
);
812 typesafe_qsort(timers
, n_timers
, timer_info_compare
);
813 output_timers_list(timers
, n_timers
);
818 typedef struct AutomountInfo
{
823 usec_t timeout_idle_usec
;
827 static void automount_info_array_free(AutomountInfo
*automounts
, size_t n_automounts
) {
828 assert(automounts
|| n_automounts
== 0);
830 FOREACH_ARRAY(i
, automounts
, n_automounts
) {
838 static int automount_info_compare(const AutomountInfo
*a
, const AutomountInfo
*b
) {
844 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
848 return path_compare(a
->where
, b
->where
);
851 static int automount_info_add(
853 const UnitInfo
*info
,
854 AutomountInfo
**automounts
,
855 size_t *n_automounts
) {
857 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
858 _cleanup_free_
char *mount
= NULL
, *mount_path
= NULL
, *where
= NULL
, *what
= NULL
, *state
= NULL
;
859 uint64_t timeout_idle_usec
;
866 assert(n_automounts
);
868 if (!endswith(info
->id
, ".automount"))
871 locator
= (BusLocator
) {
872 .destination
= "org.freedesktop.systemd1",
873 .path
= info
->unit_path
,
874 .interface
= "org.freedesktop.systemd1.Automount",
877 r
= bus_get_property_string(bus
, &locator
, "Where", &error
, &where
);
879 return log_error_errno(r
, "Failed to get automount target: %s", bus_error_message(&error
, r
));
881 r
= bus_get_property_trivial(bus
, &locator
, "TimeoutIdleUSec", &error
, 't', &timeout_idle_usec
);
883 return log_error_errno(r
, "Failed to get idle timeout: %s", bus_error_message(&error
, r
));
885 r
= unit_name_from_path(where
, ".mount", &mount
);
887 return log_error_errno(r
, "Failed to generate unit name from path: %m");
889 mount_path
= unit_dbus_path_from_name(mount
);
893 locator
.path
= mount_path
;
894 locator
.interface
= "org.freedesktop.systemd1.Mount";
896 r
= bus_get_property_string(bus
, &locator
, "What", &error
, &what
);
898 return log_error_errno(r
, "Failed to get mount source: %s", bus_error_message(&error
, r
));
900 locator
.interface
= "org.freedesktop.systemd1.Unit";
902 r
= bus_get_property_string(bus
, &locator
, "ActiveState", &error
, &state
);
904 return log_error_errno(r
, "Failed to get mount state: %s", bus_error_message(&error
, r
));
906 if (!GREEDY_REALLOC(*automounts
, *n_automounts
+ 1))
909 (*automounts
)[(*n_automounts
)++] = (AutomountInfo
) {
910 .machine
= info
->machine
,
912 .what
= TAKE_PTR(what
),
913 .where
= TAKE_PTR(where
),
914 .timeout_idle_usec
= timeout_idle_usec
,
915 .mounted
= streq_ptr(state
, "active"),
921 static int output_automounts_list(const AutomountInfo
*infos
, size_t n_infos
) {
922 _cleanup_(table_unrefp
) Table
*table
= NULL
;
925 assert(infos
|| n_infos
== 0);
927 table
= table_new("what", "where", "mounted", "idle timeout", "unit");
931 table_set_header(table
, arg_legend
!= 0);
933 table_set_width(table
, 0);
935 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
937 FOREACH_ARRAY(info
, infos
, n_infos
) {
938 _cleanup_free_
char *unit
= NULL
;
940 unit
= format_unit_id(info
->id
, info
->machine
);
944 r
= table_add_many(table
,
945 TABLE_STRING
, info
->what
,
946 TABLE_STRING
, info
->where
,
947 TABLE_BOOLEAN
, info
->mounted
);
949 return table_log_add_error(r
);
951 if (timestamp_is_set(info
->timeout_idle_usec
))
952 r
= table_add_cell(table
, NULL
, TABLE_TIMESPAN_MSEC
, &info
->timeout_idle_usec
);
954 r
= table_add_cell(table
, NULL
, TABLE_EMPTY
, NULL
);
956 return table_log_add_error(r
);
958 r
= table_add_cell(table
, NULL
, TABLE_STRING
, unit
);
960 return table_log_add_error(r
);
963 r
= output_table(table
);
968 output_legend("automount", n_infos
);
973 int verb_list_automounts(int argc
, char *argv
[], void *userdata
) {
974 _cleanup_set_free_ Set
*replies
= NULL
;
975 _cleanup_strv_free_
char **names
= NULL
;
976 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
977 AutomountInfo
*automounts
= NULL
;
978 size_t n_automounts
= 0;
982 CLEANUP_ARRAY(automounts
, n_automounts
, automount_info_array_free
);
984 r
= acquire_bus(BUS_MANAGER
, &bus
);
988 pager_open(arg_pager_flags
);
990 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".automount", &names
, NULL
);
994 if (argc
== 1 || automounts
) {
997 n
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
);
1001 FOREACH_ARRAY(u
, unit_infos
, n
) {
1002 r
= automount_info_add(bus
, u
, &automounts
, &n_automounts
);
1009 typesafe_qsort(automounts
, n_automounts
, automount_info_compare
);
1010 output_automounts_list(automounts
, n_automounts
);
1015 typedef struct PathInfo
{
1016 const char *machine
;
1022 /* Note: triggered is a list here, although it almost certainly will always be one
1023 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
1027 static int path_info_compare(const PathInfo
*a
, const PathInfo
*b
) {
1033 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
1037 r
= path_compare(a
->path
, b
->path
);
1041 r
= strcmp(a
->condition
, b
->condition
);
1045 return strcasecmp_ptr(a
->id
, b
->id
);
1048 static void path_info_array_free(PathInfo
*paths
, size_t n_paths
) {
1049 assert(paths
|| n_paths
== 0);
1051 FOREACH_ARRAY(p
, paths
, n_paths
) {
1054 strv_free(p
->triggered
);
1060 static int path_info_add(
1062 const struct UnitInfo
*u
,
1066 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1067 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1068 _cleanup_strv_free_
char **triggered
= NULL
;
1069 const char *condition
, *path
;
1077 if (!endswith(u
->id
, ".path"))
1080 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
1084 r
= sd_bus_get_property(bus
,
1085 "org.freedesktop.systemd1",
1087 "org.freedesktop.systemd1.Path",
1093 return log_error_errno(r
, "Failed to get paths: %s", bus_error_message(&error
, r
));
1095 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
1097 return bus_log_parse_error(r
);
1099 while ((r
= sd_bus_message_read(reply
, "(ss)", &condition
, &path
)) > 0) {
1100 _cleanup_free_
char *condition_dup
= NULL
, *path_dup
= NULL
;
1101 _cleanup_strv_free_
char **triggered_dup
= NULL
;
1103 condition_dup
= strdup(condition
);
1107 path_dup
= strdup(path
);
1111 triggered_dup
= strv_copy(triggered
);
1115 if (!GREEDY_REALLOC(*paths
, *n_paths
+ 1))
1118 (*paths
)[(*n_paths
)++] = (PathInfo
) {
1119 .machine
= u
->machine
,
1121 .condition
= TAKE_PTR(condition_dup
),
1122 .path
= TAKE_PTR(path_dup
),
1123 .triggered
= TAKE_PTR(triggered_dup
),
1127 return bus_log_parse_error(r
);
1129 r
= sd_bus_message_exit_container(reply
);
1131 return bus_log_parse_error(r
);
1136 static int output_paths_list(const PathInfo
*paths
, size_t n_paths
) {
1137 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1140 assert(paths
|| n_paths
== 0);
1142 table
= table_new("path", "condition", "unit", "activates");
1146 table_set_header(table
, arg_legend
!= 0);
1148 table_set_width(table
, 0);
1150 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1152 FOREACH_ARRAY(p
, paths
, n_paths
) {
1153 _cleanup_free_
char *unit
= NULL
;
1155 unit
= format_unit_id(p
->id
, p
->machine
);
1159 r
= table_add_many(table
,
1160 TABLE_STRING
, p
->path
,
1161 TABLE_STRING
, p
->condition
,
1162 TABLE_STRING
, unit
);
1164 return table_log_add_error(r
);
1166 r
= table_add_triggered(table
, p
->triggered
);
1168 return table_log_add_error(r
);
1171 r
= output_table(table
);
1175 if (arg_legend
!= 0)
1176 output_legend("path", n_paths
);
1181 int verb_list_paths(int argc
, char *argv
[], void *userdata
) {
1182 _cleanup_set_free_ Set
*replies
= NULL
;
1183 _cleanup_strv_free_
char **units
= NULL
;
1184 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
1185 PathInfo
*paths
= NULL
;
1190 CLEANUP_ARRAY(paths
, n_paths
, path_info_array_free
);
1192 r
= acquire_bus(BUS_MANAGER
, &bus
);
1196 pager_open(arg_pager_flags
);
1198 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".path", &units
, NULL
);
1202 if (argc
== 1 || units
) {
1205 n
= get_unit_list_recursive(bus
, units
, &unit_infos
, &replies
);
1209 FOREACH_ARRAY(u
, unit_infos
, n
) {
1210 r
= path_info_add(bus
, u
, &paths
, &n_paths
);
1216 typesafe_qsort(paths
, n_paths
, path_info_compare
);
1217 output_paths_list(paths
, n_paths
);