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 void message_set_freep(Set
**set
) {
18 set_free_with_destructor(*set
, sd_bus_message_unref
);
21 static int get_unit_list_recursive(
24 UnitInfo
**ret_unit_infos
,
26 char ***ret_machines
) {
28 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
29 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
30 sd_bus_message
*reply
;
35 assert(ret_unit_infos
);
38 replies
= set_new(NULL
);
42 c
= get_unit_list(bus
, NULL
, patterns
, &unit_infos
, 0, &reply
);
46 r
= set_put(replies
, reply
);
48 sd_bus_message_unref(reply
);
53 _cleanup_strv_free_
char **machines
= NULL
;
55 r
= sd_get_machine_names(&machines
);
57 return log_error_errno(r
, "Failed to get machine names: %m");
59 STRV_FOREACH(i
, machines
) {
60 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*container
= NULL
;
63 r
= sd_bus_open_system_machine(&container
, *i
);
65 log_warning_errno(r
, "Failed to connect to container %s, ignoring: %m", *i
);
69 k
= get_unit_list(container
, *i
, patterns
, &unit_infos
, c
, &reply
);
75 r
= set_put(replies
, reply
);
77 sd_bus_message_unref(reply
);
82 *ret_machines
= TAKE_PTR(machines
);
86 *ret_unit_infos
= TAKE_PTR(unit_infos
);
87 *ret_replies
= TAKE_PTR(replies
);
92 static void output_legend(const char *type
, size_t n_items
) {
97 on
= n_items
> 0 ? ansi_highlight() : ansi_highlight_red();
100 printf("\n%s%zu %ss listed.%s\n", on
, n_items
, type
, off
);
102 printf("Pass --all to see loaded but inactive %ss, too.\n", type
);
105 static int table_add_triggered(Table
*table
, char **triggered
) {
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]);
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
116 return table_add_cell(table
, NULL
, TABLE_STRV
, triggered
);
119 static char *format_unit_id(const char *unit
, const char *machine
) {
122 return machine
? strjoin(machine
, ":", unit
) : strdup(unit
);
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;
130 table
= table_new("", "unit", "load", "active", "sub", "job", "description");
134 table_set_header(table
, arg_legend
!= 0);
136 /* Hide the 'glyph' column when --plain is requested */
137 r
= table_hide_column_from_display(table
, 0);
139 return log_error_errno(r
, "Failed to hide column: %m");
142 table_set_width(table
, 0);
144 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
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;
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();
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();
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();
164 on_active
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
166 on_circle
= on_underline
;
167 on_active
= on_underline
;
168 on_loaded
= on_underline
;
171 id
= format_unit_id(u
->id
, u
->machine
);
175 r
= table_add_many(table
,
176 TABLE_STRING
, circle
? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE
) : " ",
177 TABLE_SET_BOTH_COLORS
, on_circle
,
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
);
191 return table_log_add_error(r
);
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);
201 return log_error_errno(r
, "Failed to hide column: %m");
204 r
= output_table(table
);
208 if (arg_legend
!= 0) {
209 const char *on
, *off
;
210 size_t records
= table_get_rows(table
) - 1;
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.");
218 puts("JOB = Pending job for the unit.\n");
221 on
= records
> 0 ? ansi_highlight() : ansi_highlight_red();
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",
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",
233 printf("%zu loaded units listed.\n", records
);
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
;
246 r
= acquire_bus(BUS_MANAGER
, &bus
);
250 pager_open(arg_pager_flags
);
252 if (arg_with_dependencies
) {
253 _cleanup_strv_free_
char **names
= NULL
;
255 r
= append_unit_dependencies(bus
, strv_skip(argv
, 1), &names
);
259 r
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
, &machines
);
263 r
= get_unit_list_recursive(bus
, strv_skip(argv
, 1), &unit_infos
, &replies
, &machines
);
268 typesafe_qsort(unit_infos
, r
, unit_info_compare
);
269 return output_units_list(unit_infos
, r
);
272 static int get_triggered_units(
277 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
284 r
= sd_bus_get_property_strv(
286 "org.freedesktop.systemd1",
288 "org.freedesktop.systemd1.Unit",
293 return log_error_errno(r
, "Failed to determine triggers: %s", bus_error_message(&error
, r
));
298 static int get_listening(
300 const char* unit_path
,
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
;
308 r
= sd_bus_get_property(
310 "org.freedesktop.systemd1",
312 "org.freedesktop.systemd1.Socket",
318 return log_error_errno(r
, "Failed to get list of listening sockets: %s", bus_error_message(&error
, r
));
320 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
322 return bus_log_parse_error(r
);
324 while ((r
= sd_bus_message_read(reply
, "(ss)", &type
, &path
)) > 0) {
326 r
= strv_extend(listening
, type
);
330 r
= strv_extend(listening
, path
);
337 return bus_log_parse_error(r
);
339 r
= sd_bus_message_exit_container(reply
);
341 return bus_log_parse_error(r
);
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. */
357 /* The strv above is shared. free is set only in the first one. */
361 static int socket_info_compare(const struct socket_info
*a
, const struct socket_info
*b
) {
367 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
371 r
= strcmp(a
->path
, b
->path
);
375 return strcmp(a
->type
, b
->type
);
378 static int output_sockets_list(struct socket_info
*socket_infos
, size_t cs
) {
379 _cleanup_(table_unrefp
) Table
*table
= NULL
;
382 assert(socket_infos
|| cs
== 0);
384 table
= table_new("listen", "type", "unit", "activates");
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);
392 return log_error_errno(r
, "Failed to set columns to display: %m");
395 table_set_header(table
, arg_legend
!= 0);
397 table_set_width(table
, 0);
399 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
401 for (struct socket_info
*s
= socket_infos
; s
< socket_infos
+ cs
; s
++) {
402 _cleanup_free_
char *unit
= NULL
;
404 unit
= format_unit_id(s
->id
, s
->machine
);
408 r
= table_add_many(table
,
409 TABLE_STRING
, s
->path
,
410 TABLE_STRING
, s
->type
,
413 return table_log_add_error(r
);
415 r
= table_add_triggered(table
, s
->triggered
);
417 return table_log_add_error(r
);
420 r
= output_table(table
);
425 output_legend("socket", cs
);
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
;
440 r
= acquire_bus(BUS_MANAGER
, &bus
);
444 pager_open(arg_pager_flags
);
446 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".socket", &sockets_with_suffix
, NULL
);
450 if (argc
== 1 || sockets_with_suffix
) {
451 n
= get_unit_list_recursive(bus
, sockets_with_suffix
, &unit_infos
, &replies
, &machines
);
455 for (const UnitInfo
*u
= unit_infos
; u
< unit_infos
+ n
; u
++) {
456 _cleanup_strv_free_
char **listening
= NULL
, **triggered
= NULL
;
459 if (!endswith(u
->id
, ".socket"))
462 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
466 c
= get_listening(bus
, u
->unit_path
, &listening
);
472 if (!GREEDY_REALLOC(socket_infos
, cs
+ c
)) {
477 for (int i
= 0; i
< c
; i
++)
478 socket_infos
[cs
+ i
] = (struct socket_info
) {
479 .machine
= u
->machine
,
481 .type
= listening
[i
*2],
482 .path
= listening
[i
*2 + 1],
483 .triggered
= triggered
,
484 .own_triggered
= i
==0,
487 /* from this point on we will cleanup those socket_infos */
490 listening
= triggered
= NULL
; /* avoid cleanup */
493 typesafe_qsort(socket_infos
, cs
, socket_info_compare
);
496 output_sockets_list(socket_infos
, cs
);
499 assert(cs
== 0 || socket_infos
);
500 for (struct socket_info
*s
= socket_infos
; s
< socket_infos
+ cs
; s
++) {
503 if (s
->own_triggered
)
504 strv_free(s
->triggered
);
510 static int get_next_elapse(
513 dual_timestamp
*next
) {
515 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
523 r
= sd_bus_get_property_trivial(
525 "org.freedesktop.systemd1",
527 "org.freedesktop.systemd1.Timer",
528 "NextElapseUSecMonotonic",
533 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
535 r
= sd_bus_get_property_trivial(
537 "org.freedesktop.systemd1",
539 "org.freedesktop.systemd1.Timer",
540 "NextElapseUSecRealtime",
545 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
551 static int get_last_trigger(
556 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
563 r
= sd_bus_get_property_trivial(
565 "org.freedesktop.systemd1",
567 "org.freedesktop.systemd1.Timer",
573 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
586 static int timer_info_compare(const struct timer_info
*a
, const struct timer_info
*b
) {
592 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
596 r
= CMP(a
->next_elapse
, b
->next_elapse
);
600 return strcmp(a
->id
, b
->id
);
603 static int output_timers_list(struct timer_info
*timer_infos
, size_t n
) {
604 _cleanup_(table_unrefp
) Table
*table
= NULL
;
607 assert(timer_infos
|| n
== 0);
609 table
= table_new("next", "left", "last", "passed", "unit", "activates");
613 table_set_header(table
, arg_legend
!= 0);
615 table_set_width(table
, 0);
617 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
619 for (struct timer_info
*t
= timer_infos
; t
< timer_infos
+ n
; t
++) {
620 _cleanup_free_
char *unit
= NULL
;
622 unit
= format_unit_id(t
->id
, t
->machine
);
626 r
= table_add_many(table
,
627 TABLE_TIMESTAMP
, t
->next_elapse
,
628 TABLE_TIMESTAMP_RELATIVE
, t
->next_elapse
,
629 TABLE_TIMESTAMP
, t
->last_trigger
,
630 TABLE_TIMESTAMP_RELATIVE
, t
->last_trigger
,
633 return table_log_add_error(r
);
635 r
= table_add_triggered(table
, t
->triggered
);
637 return table_log_add_error(r
);
640 r
= output_table(table
);
645 output_legend("timer", n
);
650 usec_t
calc_next_elapse(dual_timestamp
*nw
, dual_timestamp
*next
) {
656 if (timestamp_is_set(next
->monotonic
)) {
659 if (next
->monotonic
> nw
->monotonic
)
660 converted
= nw
->realtime
+ (next
->monotonic
- nw
->monotonic
);
662 converted
= nw
->realtime
- (nw
->monotonic
- next
->monotonic
);
664 if (timestamp_is_set(next
->realtime
))
665 next_elapse
= MIN(converted
, next
->realtime
);
667 next_elapse
= converted
;
670 next_elapse
= next
->realtime
;
675 int verb_list_timers(int argc
, char *argv
[], void *userdata
) {
676 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
677 _cleanup_strv_free_
char **machines
= NULL
;
678 _cleanup_strv_free_
char **timers_with_suffix
= NULL
;
679 _cleanup_free_
struct timer_info
*timer_infos
= NULL
;
680 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
686 r
= acquire_bus(BUS_MANAGER
, &bus
);
690 pager_open(arg_pager_flags
);
692 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".timer", &timers_with_suffix
, NULL
);
696 if (argc
== 1 || timers_with_suffix
) {
697 n
= get_unit_list_recursive(bus
, timers_with_suffix
, &unit_infos
, &replies
, &machines
);
701 dual_timestamp_get(&nw
);
703 for (const UnitInfo
*u
= unit_infos
; u
< unit_infos
+ n
; u
++) {
704 _cleanup_strv_free_
char **triggered
= NULL
;
705 dual_timestamp next
= DUAL_TIMESTAMP_NULL
;
708 if (!endswith(u
->id
, ".timer"))
711 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
715 r
= get_next_elapse(bus
, u
->unit_path
, &next
);
719 get_last_trigger(bus
, u
->unit_path
, &last
);
721 if (!GREEDY_REALLOC(timer_infos
, c
+1)) {
726 m
= calc_next_elapse(&nw
, &next
);
728 timer_infos
[c
++] = (struct timer_info
) {
729 .machine
= u
->machine
,
732 .last_trigger
= last
,
733 .triggered
= TAKE_PTR(triggered
),
737 typesafe_qsort(timer_infos
, c
, timer_info_compare
);
740 output_timers_list(timer_infos
, c
);
743 for (struct timer_info
*t
= timer_infos
; t
< timer_infos
+ c
; t
++)
744 strv_free(t
->triggered
);
749 struct automount_info
{
754 usec_t timeout_idle_usec
;
758 static int automount_info_compare(const struct automount_info
*a
, const struct automount_info
*b
) {
764 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
768 return strcmp(a
->where
, b
->where
);
771 static int collect_automount_info(sd_bus
* bus
, const UnitInfo
* info
, struct automount_info
*ret_info
) {
772 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
773 _cleanup_free_
char *mount
= NULL
, *mount_path
= NULL
, *where
= NULL
, *what
= NULL
, *state
= NULL
;
774 usec_t timeout_idle_usec
;
782 locator
= (BusLocator
) {
783 .destination
= "org.freedesktop.systemd1",
784 .path
= info
->unit_path
,
785 .interface
= "org.freedesktop.systemd1.Automount",
788 r
= bus_get_property_string(bus
, &locator
, "Where", &error
, &where
);
790 return log_error_errno(r
, "Failed to get automount target: %s", bus_error_message(&error
, r
));
792 r
= bus_get_property_trivial(bus
, &locator
, "TimeoutIdleUSec", &error
, 't', &timeout_idle_usec
);
794 return log_error_errno(r
, "Failed to get idle timeout: %s", bus_error_message(&error
, r
));
796 r
= unit_name_from_path(where
, ".mount", &mount
);
798 return log_error_errno(r
, "Failed to generate unit name from path: %m");
800 mount_path
= unit_dbus_path_from_name(mount
);
804 locator
.path
= mount_path
;
805 locator
.interface
= "org.freedesktop.systemd1.Mount";
807 r
= bus_get_property_string(bus
, &locator
, "What", &error
, &what
);
809 return log_error_errno(r
, "Failed to get mount source: %s", bus_error_message(&error
, r
));
811 locator
.interface
= "org.freedesktop.systemd1.Unit";
813 r
= bus_get_property_string(bus
, &locator
, "ActiveState", &error
, &state
);
815 return log_error_errno(r
, "Failed to get mount state: %s", bus_error_message(&error
, r
));
817 *ret_info
= (struct automount_info
) {
818 .machine
= info
->machine
,
820 .what
= TAKE_PTR(what
),
821 .where
= TAKE_PTR(where
),
822 .timeout_idle_usec
= timeout_idle_usec
,
823 .mounted
= streq_ptr(state
, "active"),
829 static int output_automounts_list(struct automount_info
*infos
, size_t n_infos
) {
830 _cleanup_(table_unrefp
) Table
*table
= NULL
;
833 assert(infos
|| n_infos
== 0);
835 table
= table_new("what", "where", "mounted", "idle timeout", "unit");
839 table_set_header(table
, arg_legend
!= 0);
841 table_set_width(table
, 0);
843 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
845 for (struct automount_info
*info
= infos
; info
< infos
+ n_infos
; info
++) {
846 _cleanup_free_
char *unit
= NULL
;
848 unit
= format_unit_id(info
->id
, info
->machine
);
852 r
= table_add_many(table
,
853 TABLE_STRING
, info
->what
,
854 TABLE_STRING
, info
->where
,
855 TABLE_BOOLEAN
, info
->mounted
,
856 TABLE_TIMESPAN_MSEC
, info
->timeout_idle_usec
,
859 return table_log_add_error(r
);
862 r
= output_table(table
);
867 output_legend("automount", n_infos
);
872 int verb_list_automounts(int argc
, char *argv
[], void *userdata
) {
873 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
874 _cleanup_strv_free_
char **machines
= NULL
, **automounts
= NULL
;
875 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
876 _cleanup_free_
struct automount_info
*automount_infos
= NULL
;
881 r
= acquire_bus(BUS_MANAGER
, &bus
);
885 pager_open(arg_pager_flags
);
887 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".automount", &automounts
, NULL
);
891 if (argc
== 1 || automounts
) {
892 n
= get_unit_list_recursive(bus
, automounts
, &unit_infos
, &replies
, &machines
);
896 for (const UnitInfo
*u
= unit_infos
; u
< unit_infos
+ n
; u
++) {
897 if (!endswith(u
->id
, ".automount"))
900 if (!GREEDY_REALLOC(automount_infos
, c
+ 1)) {
905 r
= collect_automount_info(bus
, u
, &automount_infos
[c
]);
912 typesafe_qsort(automount_infos
, c
, automount_info_compare
);
915 output_automounts_list(automount_infos
, c
);
918 assert(c
== 0 || automount_infos
);
919 for (struct automount_info
*info
= automount_infos
; info
< automount_infos
+ c
; info
++) {
934 /* Note: triggered is a list here, although it almost certainly will always be one
935 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
941 struct path_info
*items
;
944 static int path_info_compare(const struct path_info
*a
, const struct path_info
*b
) {
950 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
954 r
= path_compare(a
->path
, b
->path
);
958 r
= strcmp(a
->condition
, b
->condition
);
962 return strcasecmp_ptr(a
->id
, b
->id
);
965 static void path_infos_done(struct path_infos
*ps
) {
967 assert(ps
->items
|| ps
->count
== 0);
969 for (struct path_info
*p
= ps
->items
; p
< ps
->items
+ ps
->count
; p
++) {
972 strv_free(p
->triggered
);
978 static int get_paths(sd_bus
*bus
, const char *unit_path
, char ***ret_conditions
, char ***ret_paths
) {
979 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
980 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
981 _cleanup_strv_free_
char **conditions
= NULL
, **paths
= NULL
;
982 const char *condition
, *path
;
987 assert(ret_conditions
);
990 r
= sd_bus_get_property(bus
,
991 "org.freedesktop.systemd1",
993 "org.freedesktop.systemd1.Path",
999 return log_error_errno(r
, "Failed to get paths: %s", bus_error_message(&error
, r
));
1001 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
1003 return bus_log_parse_error(r
);
1005 while ((r
= sd_bus_message_read(reply
, "(ss)", &condition
, &path
)) > 0) {
1006 if (strv_extend(&conditions
, condition
) < 0)
1009 if (strv_extend(&paths
, path
) < 0)
1015 return bus_log_parse_error(r
);
1017 r
= sd_bus_message_exit_container(reply
);
1019 return bus_log_parse_error(r
);
1021 *ret_conditions
= TAKE_PTR(conditions
);
1022 *ret_paths
= TAKE_PTR(paths
);
1027 static int output_paths_list(struct path_infos
*ps
) {
1028 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1032 assert(ps
->items
|| ps
->count
== 0);
1034 table
= table_new("path", "condition", "unit", "activates");
1038 table_set_header(table
, arg_legend
!= 0);
1040 table_set_width(table
, 0);
1042 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1044 for (struct path_info
*p
= ps
->items
; p
< ps
->items
+ ps
->count
; p
++) {
1045 _cleanup_free_
char *unit
= NULL
;
1047 unit
= format_unit_id(p
->id
, p
->machine
);
1051 r
= table_add_many(table
,
1052 TABLE_STRING
, p
->path
,
1053 TABLE_STRING
, p
->condition
,
1054 TABLE_STRING
, unit
);
1056 return table_log_add_error(r
);
1058 r
= table_add_triggered(table
, p
->triggered
);
1060 return table_log_add_error(r
);
1063 r
= output_table(table
);
1067 if (arg_legend
!= 0)
1068 output_legend("path", ps
->count
);
1073 int verb_list_paths(int argc
, char *argv
[], void *userdata
) {
1074 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
1075 _cleanup_strv_free_
char **machines
= NULL
, **units
= NULL
;
1076 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
1077 _cleanup_(path_infos_done
) struct path_infos path_infos
= {};
1081 r
= acquire_bus(BUS_MANAGER
, &bus
);
1085 pager_open(arg_pager_flags
);
1087 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".path", &units
, NULL
);
1091 if (argc
== 1 || units
) {
1092 n
= get_unit_list_recursive(bus
, units
, &unit_infos
, &replies
, &machines
);
1096 for (const UnitInfo
*u
= unit_infos
; u
< unit_infos
+ n
; u
++) {
1097 _cleanup_strv_free_
char **conditions
= NULL
, **paths
= NULL
, **triggered
= NULL
;
1100 if (!endswith(u
->id
, ".path"))
1103 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
1107 c
= get_paths(bus
, u
->unit_path
, &conditions
, &paths
);
1111 if (!GREEDY_REALLOC(path_infos
.items
, path_infos
.count
+ c
))
1114 for (int i
= c
- 1; i
>= 0; i
--) {
1117 t
= strv_copy(triggered
);
1121 path_infos
.items
[path_infos
.count
++] = (struct path_info
) {
1122 .machine
= u
->machine
,
1124 .condition
= TAKE_PTR(conditions
[i
]),
1125 .path
= TAKE_PTR(paths
[i
]),
1131 typesafe_qsort(path_infos
.items
, path_infos
.count
, path_info_compare
);
1134 output_paths_list(&path_infos
);