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 _cleanup_free_
char *id
= NULL
;
131 const char *on_underline
= "", *on_loaded
= "", *on_active
= "", *on_circle
= "";
132 bool circle
= false, underline
= false;
134 if (u
+ 1 < unit_infos
+ c
&&
135 !streq(unit_type_suffix(u
->id
), unit_type_suffix((u
+ 1)->id
))) {
136 on_underline
= ansi_underline();
140 if (STR_IN_SET(u
->load_state
, "error", "not-found", "bad-setting", "masked") && !arg_plain
) {
141 on_circle
= underline
? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
143 on_loaded
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
144 } else if (streq(u
->active_state
, "failed") && !arg_plain
) {
145 on_circle
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
147 on_active
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
149 on_circle
= on_underline
;
150 on_active
= on_underline
;
151 on_loaded
= on_underline
;
154 id
= format_unit_id(u
->id
, u
->machine
);
158 r
= table_add_many(table
,
159 TABLE_STRING
, circle
? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE
) : " ",
160 TABLE_SET_BOTH_COLORS
, on_circle
,
162 TABLE_SET_BOTH_COLORS
, on_active
,
163 TABLE_STRING
, u
->load_state
,
164 TABLE_SET_BOTH_COLORS
, on_loaded
,
165 TABLE_STRING
, u
->active_state
,
166 TABLE_SET_BOTH_COLORS
, on_active
,
167 TABLE_STRING
, u
->sub_state
,
168 TABLE_SET_BOTH_COLORS
, on_active
,
169 TABLE_STRING
, u
->job_id
? u
->job_type
: "",
170 TABLE_SET_BOTH_COLORS
, on_underline
,
171 TABLE_STRING
, u
->description
,
172 TABLE_SET_BOTH_COLORS
, on_underline
);
174 return table_log_add_error(r
);
180 if (job_count
== 0) {
181 /* There's no data in the JOB column, so let's hide it */
182 r
= table_hide_column_from_display(table
, 5);
184 return log_error_errno(r
, "Failed to hide column: %m");
187 r
= output_table(table
);
191 if (arg_legend
!= 0) {
192 const char *on
, *off
;
193 size_t records
= table_get_rows(table
) - 1;
197 "%1$sLegend: LOAD %2$s Reflects whether the unit definition was properly loaded.%3$s\n"
198 "%1$s ACTIVE %2$s The high-level unit activation state, i.e. generalization of SUB.%3$s\n"
199 "%1$s SUB %2$s The low-level unit activation state, values depend on unit type.%3$s\n",
201 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
),
204 printf("%s JOB %s Pending job for the unit.%s\n",
206 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
),
212 on
= records
> 0 ? ansi_highlight() : ansi_highlight_red();
215 if (arg_all
|| strv_contains(arg_states
, "inactive"))
216 printf("%s%zu loaded units listed.%s\n"
217 "To show all installed unit files use 'systemctl list-unit-files'.\n",
219 else if (!arg_states
)
220 printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
221 "To show all installed unit files use 'systemctl list-unit-files'.\n",
224 printf("%zu loaded units listed.\n", records
);
230 int verb_list_units(int argc
, char *argv
[], void *userdata
) {
231 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
232 _cleanup_set_free_ Set
*replies
= NULL
;
236 r
= acquire_bus(BUS_MANAGER
, &bus
);
240 pager_open(arg_pager_flags
);
242 if (arg_with_dependencies
) {
243 _cleanup_strv_free_
char **names
= NULL
;
245 r
= append_unit_dependencies(bus
, strv_skip(argv
, 1), &names
);
249 r
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
);
253 r
= get_unit_list_recursive(bus
, strv_skip(argv
, 1), &unit_infos
, &replies
);
258 typesafe_qsort(unit_infos
, r
, unit_info_compare
);
259 return output_units_list(unit_infos
, r
);
262 static int get_triggered_units(
267 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
274 r
= sd_bus_get_property_strv(
276 "org.freedesktop.systemd1",
278 "org.freedesktop.systemd1.Unit",
283 return log_error_errno(r
, "Failed to determine triggers: %s", bus_error_message(&error
, r
));
288 typedef struct SocketInfo
{
293 char* path
; /* absolute path or socket address */
295 /* Note: triggered is a list here, although it almost certainly will always be one
296 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
300 static void socket_info_array_free(SocketInfo
*sockets
, size_t n_sockets
) {
301 assert(sockets
|| n_sockets
== 0);
303 FOREACH_ARRAY(s
, sockets
, n_sockets
) {
306 strv_free(s
->triggered
);
312 static int socket_info_compare(const SocketInfo
*a
, const SocketInfo
*b
) {
318 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
322 r
= CMP(path_is_absolute(a
->path
), path_is_absolute(b
->path
));
326 r
= path_is_absolute(a
->path
) ? path_compare(a
->path
, b
->path
) : strcmp(a
->path
, b
->path
);
330 return strcmp(a
->type
, b
->type
);
333 static int socket_info_add(
336 SocketInfo
**sockets
,
339 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
340 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
341 _cleanup_strv_free_
char **triggered
= NULL
;
342 const char *type
, *path
;
350 if (!endswith(u
->id
, ".socket"))
353 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
357 r
= sd_bus_get_property(
359 "org.freedesktop.systemd1",
361 "org.freedesktop.systemd1.Socket",
367 return log_error_errno(r
, "Failed to get list of listening sockets: %s", bus_error_message(&error
, r
));
369 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
371 return bus_log_parse_error(r
);
373 while ((r
= sd_bus_message_read(reply
, "(ss)", &type
, &path
)) > 0) {
374 _cleanup_free_
char *type_dup
= NULL
, *path_dup
= NULL
;
375 _cleanup_strv_free_
char **triggered_dup
= NULL
;
377 type_dup
= strdup(type
);
381 path_dup
= strdup(path
);
385 triggered_dup
= strv_copy(triggered
);
389 if (!GREEDY_REALLOC(*sockets
, *n_sockets
+ 1))
392 (*sockets
)[(*n_sockets
)++] = (SocketInfo
) {
393 .machine
= u
->machine
,
395 .type
= TAKE_PTR(type_dup
),
396 .path
= TAKE_PTR(path_dup
),
397 .triggered
= TAKE_PTR(triggered_dup
),
401 return bus_log_parse_error(r
);
403 r
= sd_bus_message_exit_container(reply
);
405 return bus_log_parse_error(r
);
410 static int output_sockets_list(const SocketInfo
*sockets
, size_t n_sockets
) {
411 _cleanup_(table_unrefp
) Table
*table
= NULL
;
414 assert(sockets
|| n_sockets
== 0);
416 table
= table_new("listen", "type", "unit", "activates");
420 if (!arg_show_types
) {
421 /* Hide the second (TYPE) column */
422 r
= table_set_display(table
, (size_t) 0, (size_t) 2, (size_t) 3);
424 return log_error_errno(r
, "Failed to set columns to display: %m");
427 table_set_header(table
, arg_legend
!= 0);
429 table_set_width(table
, 0);
431 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
433 FOREACH_ARRAY(s
, sockets
, n_sockets
) {
434 _cleanup_free_
char *unit
= NULL
;
436 unit
= format_unit_id(s
->id
, s
->machine
);
440 r
= table_add_many(table
,
441 TABLE_STRING
, s
->path
,
442 TABLE_STRING
, s
->type
,
445 return table_log_add_error(r
);
447 r
= table_add_triggered(table
, s
->triggered
);
449 return table_log_add_error(r
);
452 r
= output_table(table
);
457 output_legend("socket", n_sockets
);
462 int verb_list_sockets(int argc
, char *argv
[], void *userdata
) {
463 _cleanup_set_free_ Set
*replies
= NULL
;
464 _cleanup_strv_free_
char **sockets_with_suffix
= NULL
;
465 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
466 SocketInfo
*sockets
= NULL
;
467 size_t n_sockets
= 0;
471 CLEANUP_ARRAY(sockets
, n_sockets
, socket_info_array_free
);
473 r
= acquire_bus(BUS_MANAGER
, &bus
);
477 pager_open(arg_pager_flags
);
479 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".socket", &sockets_with_suffix
, NULL
);
483 if (argc
== 1 || sockets_with_suffix
) {
486 n
= get_unit_list_recursive(bus
, sockets_with_suffix
, &unit_infos
, &replies
);
490 FOREACH_ARRAY(u
, unit_infos
, n
) {
491 r
= socket_info_add(bus
, u
, &sockets
, &n_sockets
);
497 typesafe_qsort(sockets
, n_sockets
, socket_info_compare
);
498 output_sockets_list(sockets
, n_sockets
);
503 static int get_next_elapse(
506 dual_timestamp
*next
) {
508 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
516 r
= sd_bus_get_property_trivial(
518 "org.freedesktop.systemd1",
520 "org.freedesktop.systemd1.Timer",
521 "NextElapseUSecMonotonic",
526 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
528 r
= sd_bus_get_property_trivial(
530 "org.freedesktop.systemd1",
532 "org.freedesktop.systemd1.Timer",
533 "NextElapseUSecRealtime",
538 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
544 static int get_last_trigger(
547 dual_timestamp
*last
) {
549 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
557 r
= sd_bus_get_property_trivial(
559 "org.freedesktop.systemd1",
561 "org.freedesktop.systemd1.Timer",
567 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
569 r
= sd_bus_get_property_trivial(
571 "org.freedesktop.systemd1",
573 "org.freedesktop.systemd1.Timer",
574 "LastTriggerUSecMonotonic",
579 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
585 typedef struct TimerInfo
{
589 dual_timestamp last_trigger
;
593 static void timer_info_array_free(TimerInfo
*timers
, size_t n_timers
) {
594 assert(timers
|| n_timers
== 0);
596 FOREACH_ARRAY(t
, timers
, n_timers
)
597 strv_free(t
->triggered
);
602 static int timer_info_compare(const TimerInfo
*a
, const TimerInfo
*b
) {
608 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
612 r
= CMP(a
->next_elapse
, b
->next_elapse
);
616 return strcmp(a
->id
, b
->id
);
619 static int output_timers_list(const TimerInfo
*timers
, size_t n_timers
) {
620 _cleanup_(table_unrefp
) Table
*table
= NULL
;
623 assert(timers
|| n_timers
== 0);
625 table
= table_new("next", "left", "last", "passed", "unit", "activates");
629 table_set_header(table
, arg_legend
!= 0);
631 table_set_width(table
, 0);
633 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
635 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
636 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
638 FOREACH_ARRAY(t
, timers
, n_timers
) {
639 _cleanup_free_
char *unit
= NULL
;
641 unit
= format_unit_id(t
->id
, t
->machine
);
645 r
= table_add_many(table
,
646 TABLE_TIMESTAMP
, t
->next_elapse
,
647 TABLE_TIMESTAMP_LEFT
, t
->next_elapse
,
648 TABLE_TIMESTAMP
, t
->last_trigger
.realtime
,
649 TABLE_TIMESTAMP_RELATIVE_MONOTONIC
, t
->last_trigger
.monotonic
,
652 return table_log_add_error(r
);
654 r
= table_add_triggered(table
, t
->triggered
);
656 return table_log_add_error(r
);
659 r
= output_table(table
);
664 output_legend("timer", n_timers
);
669 usec_t
calc_next_elapse(const dual_timestamp
*nw
, const dual_timestamp
*next
) {
675 if (timestamp_is_set(next
->monotonic
)) {
678 if (next
->monotonic
> nw
->monotonic
)
679 converted
= nw
->realtime
+ (next
->monotonic
- nw
->monotonic
);
681 converted
= nw
->realtime
- (nw
->monotonic
- next
->monotonic
);
683 if (timestamp_is_set(next
->realtime
))
684 next_elapse
= MIN(converted
, next
->realtime
);
686 next_elapse
= converted
;
689 next_elapse
= next
->realtime
;
694 static int add_timer_info(
697 const dual_timestamp
*nw
,
701 _cleanup_strv_free_
char **triggered
= NULL
;
702 dual_timestamp next
, last
;
712 if (!endswith(u
->id
, ".timer"))
715 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
719 r
= get_next_elapse(bus
, u
->unit_path
, &next
);
723 r
= get_last_trigger(bus
, u
->unit_path
, &last
);
727 m
= calc_next_elapse(nw
, &next
);
729 if (!GREEDY_REALLOC(*timers
, *n_timers
+ 1))
732 (*timers
)[(*n_timers
)++] = (TimerInfo
) {
733 .machine
= u
->machine
,
736 .last_trigger
= last
,
737 .triggered
= TAKE_PTR(triggered
),
743 int verb_list_timers(int argc
, char *argv
[], void *userdata
) {
744 _cleanup_set_free_ Set
*replies
= NULL
;
745 _cleanup_strv_free_
char **timers_with_suffix
= NULL
;
746 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
747 TimerInfo
*timers
= NULL
;
752 CLEANUP_ARRAY(timers
, n_timers
, timer_info_array_free
);
754 r
= acquire_bus(BUS_MANAGER
, &bus
);
758 pager_open(arg_pager_flags
);
760 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".timer", &timers_with_suffix
, NULL
);
764 if (argc
== 1 || timers_with_suffix
) {
768 n
= get_unit_list_recursive(bus
, timers_with_suffix
, &unit_infos
, &replies
);
772 dual_timestamp_now(&nw
);
774 FOREACH_ARRAY(u
, unit_infos
, n
) {
775 r
= add_timer_info(bus
, u
, &nw
, &timers
, &n_timers
);
781 typesafe_qsort(timers
, n_timers
, timer_info_compare
);
782 output_timers_list(timers
, n_timers
);
787 typedef struct AutomountInfo
{
792 usec_t timeout_idle_usec
;
796 static void automount_info_array_free(AutomountInfo
*automounts
, size_t n_automounts
) {
797 assert(automounts
|| n_automounts
== 0);
799 FOREACH_ARRAY(i
, automounts
, n_automounts
) {
807 static int automount_info_compare(const AutomountInfo
*a
, const AutomountInfo
*b
) {
813 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
817 return path_compare(a
->where
, b
->where
);
820 static int automount_info_add(
822 const UnitInfo
*info
,
823 AutomountInfo
**automounts
,
824 size_t *n_automounts
) {
826 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
827 _cleanup_free_
char *mount
= NULL
, *mount_path
= NULL
, *where
= NULL
, *what
= NULL
, *state
= NULL
;
828 uint64_t timeout_idle_usec
;
835 assert(n_automounts
);
837 if (!endswith(info
->id
, ".automount"))
840 locator
= (BusLocator
) {
841 .destination
= "org.freedesktop.systemd1",
842 .path
= info
->unit_path
,
843 .interface
= "org.freedesktop.systemd1.Automount",
846 r
= bus_get_property_string(bus
, &locator
, "Where", &error
, &where
);
848 return log_error_errno(r
, "Failed to get automount target: %s", bus_error_message(&error
, r
));
850 r
= bus_get_property_trivial(bus
, &locator
, "TimeoutIdleUSec", &error
, 't', &timeout_idle_usec
);
852 return log_error_errno(r
, "Failed to get idle timeout: %s", bus_error_message(&error
, r
));
854 r
= unit_name_from_path(where
, ".mount", &mount
);
856 return log_error_errno(r
, "Failed to generate unit name from path: %m");
858 mount_path
= unit_dbus_path_from_name(mount
);
862 locator
.path
= mount_path
;
863 locator
.interface
= "org.freedesktop.systemd1.Mount";
865 r
= bus_get_property_string(bus
, &locator
, "What", &error
, &what
);
867 return log_error_errno(r
, "Failed to get mount source: %s", bus_error_message(&error
, r
));
869 locator
.interface
= "org.freedesktop.systemd1.Unit";
871 r
= bus_get_property_string(bus
, &locator
, "ActiveState", &error
, &state
);
873 return log_error_errno(r
, "Failed to get mount state: %s", bus_error_message(&error
, r
));
875 if (!GREEDY_REALLOC(*automounts
, *n_automounts
+ 1))
878 (*automounts
)[(*n_automounts
)++] = (AutomountInfo
) {
879 .machine
= info
->machine
,
881 .what
= TAKE_PTR(what
),
882 .where
= TAKE_PTR(where
),
883 .timeout_idle_usec
= timeout_idle_usec
,
884 .mounted
= streq_ptr(state
, "active"),
890 static int output_automounts_list(const AutomountInfo
*infos
, size_t n_infos
) {
891 _cleanup_(table_unrefp
) Table
*table
= NULL
;
894 assert(infos
|| n_infos
== 0);
896 table
= table_new("what", "where", "mounted", "idle timeout", "unit");
900 table_set_header(table
, arg_legend
!= 0);
902 table_set_width(table
, 0);
904 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
906 FOREACH_ARRAY(info
, infos
, n_infos
) {
907 _cleanup_free_
char *unit
= NULL
;
909 unit
= format_unit_id(info
->id
, info
->machine
);
913 r
= table_add_many(table
,
914 TABLE_STRING
, info
->what
,
915 TABLE_STRING
, info
->where
,
916 TABLE_BOOLEAN
, info
->mounted
);
918 return table_log_add_error(r
);
920 if (timestamp_is_set(info
->timeout_idle_usec
))
921 r
= table_add_cell(table
, NULL
, TABLE_TIMESPAN_MSEC
, &info
->timeout_idle_usec
);
923 r
= table_add_cell(table
, NULL
, TABLE_EMPTY
, NULL
);
925 return table_log_add_error(r
);
927 r
= table_add_cell(table
, NULL
, TABLE_STRING
, unit
);
929 return table_log_add_error(r
);
932 r
= output_table(table
);
937 output_legend("automount", n_infos
);
942 int verb_list_automounts(int argc
, char *argv
[], void *userdata
) {
943 _cleanup_set_free_ Set
*replies
= NULL
;
944 _cleanup_strv_free_
char **names
= NULL
;
945 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
946 AutomountInfo
*automounts
= NULL
;
947 size_t n_automounts
= 0;
951 CLEANUP_ARRAY(automounts
, n_automounts
, automount_info_array_free
);
953 r
= acquire_bus(BUS_MANAGER
, &bus
);
957 pager_open(arg_pager_flags
);
959 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".automount", &names
, NULL
);
963 if (argc
== 1 || automounts
) {
966 n
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
);
970 FOREACH_ARRAY(u
, unit_infos
, n
) {
971 r
= automount_info_add(bus
, u
, &automounts
, &n_automounts
);
978 typesafe_qsort(automounts
, n_automounts
, automount_info_compare
);
979 output_automounts_list(automounts
, n_automounts
);
984 typedef struct PathInfo
{
991 /* Note: triggered is a list here, although it almost certainly will always be one
992 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
996 static int path_info_compare(const PathInfo
*a
, const PathInfo
*b
) {
1002 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
1006 r
= path_compare(a
->path
, b
->path
);
1010 r
= strcmp(a
->condition
, b
->condition
);
1014 return strcasecmp_ptr(a
->id
, b
->id
);
1017 static void path_info_array_free(PathInfo
*paths
, size_t n_paths
) {
1018 assert(paths
|| n_paths
== 0);
1020 FOREACH_ARRAY(p
, paths
, n_paths
) {
1023 strv_free(p
->triggered
);
1029 static int path_info_add(
1031 const struct UnitInfo
*u
,
1035 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1036 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1037 _cleanup_strv_free_
char **triggered
= NULL
;
1038 const char *condition
, *path
;
1046 if (!endswith(u
->id
, ".path"))
1049 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
1053 r
= sd_bus_get_property(bus
,
1054 "org.freedesktop.systemd1",
1056 "org.freedesktop.systemd1.Path",
1062 return log_error_errno(r
, "Failed to get paths: %s", bus_error_message(&error
, r
));
1064 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
1066 return bus_log_parse_error(r
);
1068 while ((r
= sd_bus_message_read(reply
, "(ss)", &condition
, &path
)) > 0) {
1069 _cleanup_free_
char *condition_dup
= NULL
, *path_dup
= NULL
;
1070 _cleanup_strv_free_
char **triggered_dup
= NULL
;
1072 condition_dup
= strdup(condition
);
1076 path_dup
= strdup(path
);
1080 triggered_dup
= strv_copy(triggered
);
1084 if (!GREEDY_REALLOC(*paths
, *n_paths
+ 1))
1087 (*paths
)[(*n_paths
)++] = (PathInfo
) {
1088 .machine
= u
->machine
,
1090 .condition
= TAKE_PTR(condition_dup
),
1091 .path
= TAKE_PTR(path_dup
),
1092 .triggered
= TAKE_PTR(triggered_dup
),
1096 return bus_log_parse_error(r
);
1098 r
= sd_bus_message_exit_container(reply
);
1100 return bus_log_parse_error(r
);
1105 static int output_paths_list(const PathInfo
*paths
, size_t n_paths
) {
1106 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1109 assert(paths
|| n_paths
== 0);
1111 table
= table_new("path", "condition", "unit", "activates");
1115 table_set_header(table
, arg_legend
!= 0);
1117 table_set_width(table
, 0);
1119 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1121 FOREACH_ARRAY(p
, paths
, n_paths
) {
1122 _cleanup_free_
char *unit
= NULL
;
1124 unit
= format_unit_id(p
->id
, p
->machine
);
1128 r
= table_add_many(table
,
1129 TABLE_STRING
, p
->path
,
1130 TABLE_STRING
, p
->condition
,
1131 TABLE_STRING
, unit
);
1133 return table_log_add_error(r
);
1135 r
= table_add_triggered(table
, p
->triggered
);
1137 return table_log_add_error(r
);
1140 r
= output_table(table
);
1144 if (arg_legend
!= 0)
1145 output_legend("path", n_paths
);
1150 int verb_list_paths(int argc
, char *argv
[], void *userdata
) {
1151 _cleanup_set_free_ Set
*replies
= NULL
;
1152 _cleanup_strv_free_
char **units
= NULL
;
1153 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
1154 PathInfo
*paths
= NULL
;
1159 CLEANUP_ARRAY(paths
, n_paths
, path_info_array_free
);
1161 r
= acquire_bus(BUS_MANAGER
, &bus
);
1165 pager_open(arg_pager_flags
);
1167 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".path", &units
, NULL
);
1171 if (argc
== 1 || units
) {
1174 n
= get_unit_list_recursive(bus
, units
, &unit_infos
, &replies
);
1178 FOREACH_ARRAY(u
, unit_infos
, n
) {
1179 r
= path_info_add(bus
, u
, &paths
, &n_paths
);
1185 typesafe_qsort(paths
, n_paths
, path_info_compare
);
1186 output_paths_list(paths
, n_paths
);