1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "bus-locator.h"
7 #include "format-table.h"
8 #include "locale-util.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"
17 static int get_unit_list_recursive(
20 UnitInfo
**ret_unit_infos
,
23 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
24 _cleanup_set_free_ Set
*replies
= NULL
;
25 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
30 assert(ret_unit_infos
);
32 c
= get_unit_list(bus
, NULL
, patterns
, &unit_infos
, 0, &reply
);
36 r
= set_ensure_consume(&replies
, &bus_message_hash_ops
, TAKE_PTR(reply
));
41 _cleanup_strv_free_
char **machines
= NULL
;
43 r
= sd_get_machine_names(&machines
);
45 return log_error_errno(r
, "Failed to get machine names: %m");
47 STRV_FOREACH(i
, machines
) {
48 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*container
= NULL
;
51 r
= sd_bus_open_system_machine(&container
, *i
);
53 log_warning_errno(r
, "Failed to connect to container %s, ignoring: %m", *i
);
57 k
= get_unit_list(container
, *i
, patterns
, &unit_infos
, c
, &reply
);
63 r
= set_consume(replies
, TAKE_PTR(reply
));
69 *ret_unit_infos
= TAKE_PTR(unit_infos
);
70 *ret_replies
= TAKE_PTR(replies
);
75 static void output_legend(const char *type
, size_t n_items
) {
80 on
= n_items
> 0 ? ansi_highlight() : ansi_highlight_red();
83 printf("\n%s%zu %ss listed.%s\n", on
, n_items
, type
, off
);
85 printf("Pass --all to see loaded but inactive %ss, too.\n", type
);
88 static int table_add_triggered(Table
*table
, char **triggered
) {
91 if (strv_isempty(triggered
))
92 return table_add_cell(table
, NULL
, TABLE_EMPTY
, NULL
);
93 else if (strv_length(triggered
) == 1)
94 return table_add_cell(table
, NULL
, TABLE_STRING
, triggered
[0]);
96 /* This should never happen, currently our socket units can only trigger a
97 * single unit. But let's handle this anyway, who knows what the future
99 return table_add_cell(table
, NULL
, TABLE_STRV
, triggered
);
102 static char *format_unit_id(const char *unit
, const char *machine
) {
105 return machine
? strjoin(machine
, ":", unit
) : strdup(unit
);
108 static int output_units_list(const UnitInfo
*unit_infos
, size_t c
) {
109 _cleanup_(table_unrefp
) Table
*table
= NULL
;
110 size_t job_count
= 0;
113 table
= table_new("", "unit", "load", "active", "sub", "job", "description");
117 table_set_header(table
, arg_legend
!= 0);
119 /* Hide the 'glyph' column when --plain is requested */
120 r
= table_hide_column_from_display(table
, 0);
122 return log_error_errno(r
, "Failed to hide column: %m");
125 table_set_width(table
, 0);
127 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
129 FOREACH_ARRAY(u
, unit_infos
, c
) {
130 const char *on_loaded
= NULL
, *on_active
= NULL
, *on_sub
= NULL
, *on_circle
= NULL
;
131 _cleanup_free_
char *id
= NULL
;
132 bool circle
= false, underline
;
134 underline
= u
+ 1 < unit_infos
+ c
&& !streq(unit_type_suffix(u
->id
), unit_type_suffix((u
+ 1)->id
));
136 if (streq(u
->load_state
, "not-found")) {
137 on_circle
= on_loaded
= ansi_highlight_yellow();
138 on_circle
= ansi_highlight_yellow();
140 } else if (STR_IN_SET(u
->load_state
, "bad-setting", "error", "masked")) {
141 on_loaded
= ansi_highlight_red();
142 on_circle
= ansi_highlight_yellow();
146 if (streq(u
->active_state
, "failed")) {
147 on_sub
= on_active
= ansi_highlight_red();
149 /* Here override any load_state highlighting */
150 on_circle
= ansi_highlight_red();
152 } else if (STR_IN_SET(u
->active_state
, "reloading", "activating", "maintenance", "deactivating")) {
153 on_sub
= on_active
= ansi_highlight();
155 if (!circle
) { /* Here we let load_state highlighting win */
156 on_circle
= ansi_highlight();
159 } else if (streq(u
->active_state
, "inactive"))
160 on_sub
= on_active
= ansi_grey();
162 /* As a special case, when this is a service which has not process running, let's grey out
163 * its state, to highlight that a bit */
164 if (!on_sub
&& endswith(u
->id
, ".service") && streq(u
->sub_state
, "exited"))
165 on_sub
= ansi_grey();
170 id
= format_unit_id(u
->id
, u
->machine
);
174 r
= table_add_many(table
,
175 TABLE_STRING
, circle
? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE
) : " ",
176 TABLE_SET_COLOR
, on_circle
,
177 TABLE_SET_BOTH_UNDERLINES
, underline
,
179 TABLE_SET_COLOR
, on_active
,
180 TABLE_SET_BOTH_UNDERLINES
, underline
,
181 TABLE_STRING
, u
->load_state
,
182 TABLE_SET_COLOR
, on_loaded
,
183 TABLE_SET_BOTH_UNDERLINES
, underline
,
184 TABLE_STRING
, u
->active_state
,
185 TABLE_SET_COLOR
, on_active
,
186 TABLE_SET_BOTH_UNDERLINES
, underline
,
187 TABLE_STRING
, u
->sub_state
,
188 TABLE_SET_COLOR
, on_sub
,
189 TABLE_SET_BOTH_UNDERLINES
, underline
,
190 TABLE_STRING
, u
->job_id
? u
->job_type
: "",
191 TABLE_SET_BOTH_UNDERLINES
, underline
,
192 TABLE_STRING
, u
->description
,
193 TABLE_SET_BOTH_UNDERLINES
, underline
);
195 return table_log_add_error(r
);
201 if (job_count
== 0) {
202 /* There's no data in the JOB column, so let's hide it */
203 r
= table_hide_column_from_display(table
, 5);
205 return log_error_errno(r
, "Failed to hide column: %m");
208 r
= output_table(table
);
212 if (arg_legend
!= 0) {
213 const char *on
, *off
;
214 size_t records
= table_get_rows(table
) - 1;
218 "%1$sLegend: LOAD %2$s Reflects whether the unit definition was properly loaded.%3$s\n"
219 "%1$s ACTIVE %2$s The high-level unit activation state, i.e. generalization of SUB.%3$s\n"
220 "%1$s SUB %2$s The low-level unit activation state, values depend on unit type.%3$s\n",
222 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
),
225 printf("%s JOB %s Pending job for the unit.%s\n",
227 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
),
233 on
= records
> 0 ? ansi_highlight() : ansi_highlight_red();
236 if (arg_all
|| strv_contains(arg_states
, "inactive"))
237 printf("%s%zu loaded units listed.%s\n"
238 "To show all installed unit files use 'systemctl list-unit-files'.\n",
240 else if (!arg_states
)
241 printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
242 "To show all installed unit files use 'systemctl list-unit-files'.\n",
245 printf("%zu loaded units listed.\n", records
);
251 int verb_list_units(int argc
, char *argv
[], void *userdata
) {
252 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
253 _cleanup_set_free_ Set
*replies
= NULL
;
257 r
= acquire_bus(BUS_MANAGER
, &bus
);
261 pager_open(arg_pager_flags
);
263 if (arg_with_dependencies
) {
264 _cleanup_strv_free_
char **names
= NULL
;
266 r
= append_unit_dependencies(bus
, strv_skip(argv
, 1), &names
);
270 r
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
);
274 r
= get_unit_list_recursive(bus
, strv_skip(argv
, 1), &unit_infos
, &replies
);
279 typesafe_qsort(unit_infos
, r
, unit_info_compare
);
280 return output_units_list(unit_infos
, r
);
283 static int get_triggered_units(
288 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
295 r
= sd_bus_get_property_strv(
297 "org.freedesktop.systemd1",
299 "org.freedesktop.systemd1.Unit",
304 return log_error_errno(r
, "Failed to determine triggers: %s", bus_error_message(&error
, r
));
309 typedef struct SocketInfo
{
314 char* path
; /* absolute path or socket address */
316 /* Note: triggered is a list here, although it almost certainly will always be one
317 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
321 static void socket_info_array_free(SocketInfo
*sockets
, size_t n_sockets
) {
322 assert(sockets
|| n_sockets
== 0);
324 FOREACH_ARRAY(s
, sockets
, n_sockets
) {
327 strv_free(s
->triggered
);
333 static int socket_info_compare(const SocketInfo
*a
, const SocketInfo
*b
) {
339 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
343 r
= CMP(path_is_absolute(a
->path
), path_is_absolute(b
->path
));
347 r
= path_is_absolute(a
->path
) ? path_compare(a
->path
, b
->path
) : strcmp(a
->path
, b
->path
);
351 return strcmp(a
->type
, b
->type
);
354 static int socket_info_add(
357 SocketInfo
**sockets
,
360 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
361 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
362 _cleanup_strv_free_
char **triggered
= NULL
;
363 const char *type
, *path
;
371 if (!endswith(u
->id
, ".socket"))
374 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
378 r
= sd_bus_get_property(
380 "org.freedesktop.systemd1",
382 "org.freedesktop.systemd1.Socket",
388 return log_error_errno(r
, "Failed to get list of listening sockets: %s", bus_error_message(&error
, r
));
390 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
392 return bus_log_parse_error(r
);
394 while ((r
= sd_bus_message_read(reply
, "(ss)", &type
, &path
)) > 0) {
395 _cleanup_free_
char *type_dup
= NULL
, *path_dup
= NULL
;
396 _cleanup_strv_free_
char **triggered_dup
= NULL
;
398 type_dup
= strdup(type
);
402 path_dup
= strdup(path
);
406 triggered_dup
= strv_copy(triggered
);
410 if (!GREEDY_REALLOC(*sockets
, *n_sockets
+ 1))
413 (*sockets
)[(*n_sockets
)++] = (SocketInfo
) {
414 .machine
= u
->machine
,
416 .type
= TAKE_PTR(type_dup
),
417 .path
= TAKE_PTR(path_dup
),
418 .triggered
= TAKE_PTR(triggered_dup
),
422 return bus_log_parse_error(r
);
424 r
= sd_bus_message_exit_container(reply
);
426 return bus_log_parse_error(r
);
431 static int output_sockets_list(const SocketInfo
*sockets
, size_t n_sockets
) {
432 _cleanup_(table_unrefp
) Table
*table
= NULL
;
435 assert(sockets
|| n_sockets
== 0);
437 table
= table_new("listen", "type", "unit", "activates");
441 if (!arg_show_types
) {
442 /* Hide the second (TYPE) column */
443 r
= table_set_display(table
, (size_t) 0, (size_t) 2, (size_t) 3);
445 return log_error_errno(r
, "Failed to set columns to display: %m");
448 table_set_header(table
, arg_legend
!= 0);
450 table_set_width(table
, 0);
452 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
454 FOREACH_ARRAY(s
, sockets
, n_sockets
) {
455 _cleanup_free_
char *unit
= NULL
;
457 unit
= format_unit_id(s
->id
, s
->machine
);
461 r
= table_add_many(table
,
462 TABLE_STRING
, s
->path
,
463 TABLE_STRING
, s
->type
,
466 return table_log_add_error(r
);
468 r
= table_add_triggered(table
, s
->triggered
);
470 return table_log_add_error(r
);
473 r
= output_table(table
);
478 output_legend("socket", n_sockets
);
483 int verb_list_sockets(int argc
, char *argv
[], void *userdata
) {
484 _cleanup_set_free_ Set
*replies
= NULL
;
485 _cleanup_strv_free_
char **sockets_with_suffix
= NULL
;
486 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
487 SocketInfo
*sockets
= NULL
;
488 size_t n_sockets
= 0;
492 CLEANUP_ARRAY(sockets
, n_sockets
, socket_info_array_free
);
494 r
= acquire_bus(BUS_MANAGER
, &bus
);
498 pager_open(arg_pager_flags
);
500 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".socket", &sockets_with_suffix
, NULL
);
504 if (argc
== 1 || sockets_with_suffix
) {
507 n
= get_unit_list_recursive(bus
, sockets_with_suffix
, &unit_infos
, &replies
);
511 FOREACH_ARRAY(u
, unit_infos
, n
) {
512 r
= socket_info_add(bus
, u
, &sockets
, &n_sockets
);
518 typesafe_qsort(sockets
, n_sockets
, socket_info_compare
);
519 output_sockets_list(sockets
, n_sockets
);
524 static int get_next_elapse(
527 dual_timestamp
*next
) {
529 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
537 r
= sd_bus_get_property_trivial(
539 "org.freedesktop.systemd1",
541 "org.freedesktop.systemd1.Timer",
542 "NextElapseUSecMonotonic",
547 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
549 r
= sd_bus_get_property_trivial(
551 "org.freedesktop.systemd1",
553 "org.freedesktop.systemd1.Timer",
554 "NextElapseUSecRealtime",
559 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
565 static int get_last_trigger(
568 dual_timestamp
*last
) {
570 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
578 r
= sd_bus_get_property_trivial(
580 "org.freedesktop.systemd1",
582 "org.freedesktop.systemd1.Timer",
588 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
590 r
= sd_bus_get_property_trivial(
592 "org.freedesktop.systemd1",
594 "org.freedesktop.systemd1.Timer",
595 "LastTriggerUSecMonotonic",
600 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
606 typedef struct TimerInfo
{
610 dual_timestamp last_trigger
;
614 static void timer_info_array_free(TimerInfo
*timers
, size_t n_timers
) {
615 assert(timers
|| n_timers
== 0);
617 FOREACH_ARRAY(t
, timers
, n_timers
)
618 strv_free(t
->triggered
);
623 static int timer_info_compare(const TimerInfo
*a
, const TimerInfo
*b
) {
629 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
633 r
= CMP(a
->next_elapse
, b
->next_elapse
);
637 return strcmp(a
->id
, b
->id
);
640 static int output_timers_list(const TimerInfo
*timers
, size_t n_timers
) {
641 _cleanup_(table_unrefp
) Table
*table
= NULL
;
644 assert(timers
|| n_timers
== 0);
646 table
= table_new("next", "left", "last", "passed", "unit", "activates");
650 table_set_header(table
, arg_legend
!= 0);
652 table_set_width(table
, 0);
654 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
656 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
657 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
659 FOREACH_ARRAY(t
, timers
, n_timers
) {
660 _cleanup_free_
char *unit
= NULL
;
662 unit
= format_unit_id(t
->id
, t
->machine
);
666 r
= table_add_many(table
,
667 TABLE_TIMESTAMP
, t
->next_elapse
,
668 TABLE_TIMESTAMP_LEFT
, t
->next_elapse
,
669 TABLE_TIMESTAMP
, t
->last_trigger
.realtime
,
670 TABLE_TIMESTAMP_RELATIVE_MONOTONIC
, t
->last_trigger
.monotonic
,
673 return table_log_add_error(r
);
675 r
= table_add_triggered(table
, t
->triggered
);
677 return table_log_add_error(r
);
680 r
= output_table(table
);
685 output_legend("timer", n_timers
);
690 usec_t
calc_next_elapse(const dual_timestamp
*nw
, const dual_timestamp
*next
) {
696 if (timestamp_is_set(next
->monotonic
)) {
699 if (next
->monotonic
> nw
->monotonic
)
700 converted
= nw
->realtime
+ (next
->monotonic
- nw
->monotonic
);
702 converted
= nw
->realtime
- (nw
->monotonic
- next
->monotonic
);
704 if (timestamp_is_set(next
->realtime
))
705 next_elapse
= MIN(converted
, next
->realtime
);
707 next_elapse
= converted
;
710 next_elapse
= next
->realtime
;
715 static int add_timer_info(
718 const dual_timestamp
*nw
,
722 _cleanup_strv_free_
char **triggered
= NULL
;
723 dual_timestamp next
, last
;
733 if (!endswith(u
->id
, ".timer"))
736 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
740 r
= get_next_elapse(bus
, u
->unit_path
, &next
);
744 r
= get_last_trigger(bus
, u
->unit_path
, &last
);
748 m
= calc_next_elapse(nw
, &next
);
750 if (!GREEDY_REALLOC(*timers
, *n_timers
+ 1))
753 (*timers
)[(*n_timers
)++] = (TimerInfo
) {
754 .machine
= u
->machine
,
757 .last_trigger
= last
,
758 .triggered
= TAKE_PTR(triggered
),
764 int verb_list_timers(int argc
, char *argv
[], void *userdata
) {
765 _cleanup_set_free_ Set
*replies
= NULL
;
766 _cleanup_strv_free_
char **timers_with_suffix
= NULL
;
767 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
768 TimerInfo
*timers
= NULL
;
773 CLEANUP_ARRAY(timers
, n_timers
, timer_info_array_free
);
775 r
= acquire_bus(BUS_MANAGER
, &bus
);
779 pager_open(arg_pager_flags
);
781 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".timer", &timers_with_suffix
, NULL
);
785 if (argc
== 1 || timers_with_suffix
) {
789 n
= get_unit_list_recursive(bus
, timers_with_suffix
, &unit_infos
, &replies
);
793 dual_timestamp_now(&nw
);
795 FOREACH_ARRAY(u
, unit_infos
, n
) {
796 r
= add_timer_info(bus
, u
, &nw
, &timers
, &n_timers
);
802 typesafe_qsort(timers
, n_timers
, timer_info_compare
);
803 output_timers_list(timers
, n_timers
);
808 typedef struct AutomountInfo
{
813 usec_t timeout_idle_usec
;
817 static void automount_info_array_free(AutomountInfo
*automounts
, size_t n_automounts
) {
818 assert(automounts
|| n_automounts
== 0);
820 FOREACH_ARRAY(i
, automounts
, n_automounts
) {
828 static int automount_info_compare(const AutomountInfo
*a
, const AutomountInfo
*b
) {
834 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
838 return path_compare(a
->where
, b
->where
);
841 static int automount_info_add(
843 const UnitInfo
*info
,
844 AutomountInfo
**automounts
,
845 size_t *n_automounts
) {
847 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
848 _cleanup_free_
char *mount
= NULL
, *mount_path
= NULL
, *where
= NULL
, *what
= NULL
, *state
= NULL
;
849 uint64_t timeout_idle_usec
;
856 assert(n_automounts
);
858 if (!endswith(info
->id
, ".automount"))
861 locator
= (BusLocator
) {
862 .destination
= "org.freedesktop.systemd1",
863 .path
= info
->unit_path
,
864 .interface
= "org.freedesktop.systemd1.Automount",
867 r
= bus_get_property_string(bus
, &locator
, "Where", &error
, &where
);
869 return log_error_errno(r
, "Failed to get automount target: %s", bus_error_message(&error
, r
));
871 r
= bus_get_property_trivial(bus
, &locator
, "TimeoutIdleUSec", &error
, 't', &timeout_idle_usec
);
873 return log_error_errno(r
, "Failed to get idle timeout: %s", bus_error_message(&error
, r
));
875 r
= unit_name_from_path(where
, ".mount", &mount
);
877 return log_error_errno(r
, "Failed to generate unit name from path: %m");
879 mount_path
= unit_dbus_path_from_name(mount
);
883 locator
.path
= mount_path
;
884 locator
.interface
= "org.freedesktop.systemd1.Mount";
886 r
= bus_get_property_string(bus
, &locator
, "What", &error
, &what
);
888 return log_error_errno(r
, "Failed to get mount source: %s", bus_error_message(&error
, r
));
890 locator
.interface
= "org.freedesktop.systemd1.Unit";
892 r
= bus_get_property_string(bus
, &locator
, "ActiveState", &error
, &state
);
894 return log_error_errno(r
, "Failed to get mount state: %s", bus_error_message(&error
, r
));
896 if (!GREEDY_REALLOC(*automounts
, *n_automounts
+ 1))
899 (*automounts
)[(*n_automounts
)++] = (AutomountInfo
) {
900 .machine
= info
->machine
,
902 .what
= TAKE_PTR(what
),
903 .where
= TAKE_PTR(where
),
904 .timeout_idle_usec
= timeout_idle_usec
,
905 .mounted
= streq_ptr(state
, "active"),
911 static int output_automounts_list(const AutomountInfo
*infos
, size_t n_infos
) {
912 _cleanup_(table_unrefp
) Table
*table
= NULL
;
915 assert(infos
|| n_infos
== 0);
917 table
= table_new("what", "where", "mounted", "idle timeout", "unit");
921 table_set_header(table
, arg_legend
!= 0);
923 table_set_width(table
, 0);
925 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
927 FOREACH_ARRAY(info
, infos
, n_infos
) {
928 _cleanup_free_
char *unit
= NULL
;
930 unit
= format_unit_id(info
->id
, info
->machine
);
934 r
= table_add_many(table
,
935 TABLE_STRING
, info
->what
,
936 TABLE_STRING
, info
->where
,
937 TABLE_BOOLEAN
, info
->mounted
);
939 return table_log_add_error(r
);
941 if (timestamp_is_set(info
->timeout_idle_usec
))
942 r
= table_add_cell(table
, NULL
, TABLE_TIMESPAN_MSEC
, &info
->timeout_idle_usec
);
944 r
= table_add_cell(table
, NULL
, TABLE_EMPTY
, NULL
);
946 return table_log_add_error(r
);
948 r
= table_add_cell(table
, NULL
, TABLE_STRING
, unit
);
950 return table_log_add_error(r
);
953 r
= output_table(table
);
958 output_legend("automount", n_infos
);
963 int verb_list_automounts(int argc
, char *argv
[], void *userdata
) {
964 _cleanup_set_free_ Set
*replies
= NULL
;
965 _cleanup_strv_free_
char **names
= NULL
;
966 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
967 AutomountInfo
*automounts
= NULL
;
968 size_t n_automounts
= 0;
972 CLEANUP_ARRAY(automounts
, n_automounts
, automount_info_array_free
);
974 r
= acquire_bus(BUS_MANAGER
, &bus
);
978 pager_open(arg_pager_flags
);
980 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".automount", &names
, NULL
);
984 if (argc
== 1 || automounts
) {
987 n
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
);
991 FOREACH_ARRAY(u
, unit_infos
, n
) {
992 r
= automount_info_add(bus
, u
, &automounts
, &n_automounts
);
999 typesafe_qsort(automounts
, n_automounts
, automount_info_compare
);
1000 output_automounts_list(automounts
, n_automounts
);
1005 typedef struct PathInfo
{
1006 const char *machine
;
1012 /* Note: triggered is a list here, although it almost certainly will always be one
1013 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
1017 static int path_info_compare(const PathInfo
*a
, const PathInfo
*b
) {
1023 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
1027 r
= path_compare(a
->path
, b
->path
);
1031 r
= strcmp(a
->condition
, b
->condition
);
1035 return strcasecmp_ptr(a
->id
, b
->id
);
1038 static void path_info_array_free(PathInfo
*paths
, size_t n_paths
) {
1039 assert(paths
|| n_paths
== 0);
1041 FOREACH_ARRAY(p
, paths
, n_paths
) {
1044 strv_free(p
->triggered
);
1050 static int path_info_add(
1052 const struct UnitInfo
*u
,
1056 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1057 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1058 _cleanup_strv_free_
char **triggered
= NULL
;
1059 const char *condition
, *path
;
1067 if (!endswith(u
->id
, ".path"))
1070 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
1074 r
= sd_bus_get_property(bus
,
1075 "org.freedesktop.systemd1",
1077 "org.freedesktop.systemd1.Path",
1083 return log_error_errno(r
, "Failed to get paths: %s", bus_error_message(&error
, r
));
1085 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
1087 return bus_log_parse_error(r
);
1089 while ((r
= sd_bus_message_read(reply
, "(ss)", &condition
, &path
)) > 0) {
1090 _cleanup_free_
char *condition_dup
= NULL
, *path_dup
= NULL
;
1091 _cleanup_strv_free_
char **triggered_dup
= NULL
;
1093 condition_dup
= strdup(condition
);
1097 path_dup
= strdup(path
);
1101 triggered_dup
= strv_copy(triggered
);
1105 if (!GREEDY_REALLOC(*paths
, *n_paths
+ 1))
1108 (*paths
)[(*n_paths
)++] = (PathInfo
) {
1109 .machine
= u
->machine
,
1111 .condition
= TAKE_PTR(condition_dup
),
1112 .path
= TAKE_PTR(path_dup
),
1113 .triggered
= TAKE_PTR(triggered_dup
),
1117 return bus_log_parse_error(r
);
1119 r
= sd_bus_message_exit_container(reply
);
1121 return bus_log_parse_error(r
);
1126 static int output_paths_list(const PathInfo
*paths
, size_t n_paths
) {
1127 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1130 assert(paths
|| n_paths
== 0);
1132 table
= table_new("path", "condition", "unit", "activates");
1136 table_set_header(table
, arg_legend
!= 0);
1138 table_set_width(table
, 0);
1140 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1142 FOREACH_ARRAY(p
, paths
, n_paths
) {
1143 _cleanup_free_
char *unit
= NULL
;
1145 unit
= format_unit_id(p
->id
, p
->machine
);
1149 r
= table_add_many(table
,
1150 TABLE_STRING
, p
->path
,
1151 TABLE_STRING
, p
->condition
,
1152 TABLE_STRING
, unit
);
1154 return table_log_add_error(r
);
1156 r
= table_add_triggered(table
, p
->triggered
);
1158 return table_log_add_error(r
);
1161 r
= output_table(table
);
1165 if (arg_legend
!= 0)
1166 output_legend("path", n_paths
);
1171 int verb_list_paths(int argc
, char *argv
[], void *userdata
) {
1172 _cleanup_set_free_ Set
*replies
= NULL
;
1173 _cleanup_strv_free_
char **units
= NULL
;
1174 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
1175 PathInfo
*paths
= NULL
;
1180 CLEANUP_ARRAY(paths
, n_paths
, path_info_array_free
);
1182 r
= acquire_bus(BUS_MANAGER
, &bus
);
1186 pager_open(arg_pager_flags
);
1188 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".path", &units
, NULL
);
1192 if (argc
== 1 || units
) {
1195 n
= get_unit_list_recursive(bus
, units
, &unit_infos
, &replies
);
1199 FOREACH_ARRAY(u
, unit_infos
, n
) {
1200 r
= path_info_add(bus
, u
, &paths
, &n_paths
);
1206 typesafe_qsort(paths
, n_paths
, path_info_compare
);
1207 output_paths_list(paths
, n_paths
);