1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "format-table.h"
7 #include "locale-util.h"
10 #include "systemctl-list-units.h"
11 #include "systemctl-util.h"
12 #include "systemctl.h"
13 #include "terminal-util.h"
15 static void message_set_freep(Set
**set
) {
16 set_free_with_destructor(*set
, sd_bus_message_unref
);
19 static int get_unit_list_recursive(
22 UnitInfo
**ret_unit_infos
,
24 char ***ret_machines
) {
26 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
27 _cleanup_(message_set_freep
) Set
*replies
;
28 sd_bus_message
*reply
;
33 assert(ret_unit_infos
);
36 replies
= set_new(NULL
);
40 c
= get_unit_list(bus
, NULL
, patterns
, &unit_infos
, 0, &reply
);
44 r
= set_put(replies
, reply
);
46 sd_bus_message_unref(reply
);
51 _cleanup_strv_free_
char **machines
= NULL
;
54 r
= sd_get_machine_names(&machines
);
56 return log_error_errno(r
, "Failed to get machine names: %m");
58 STRV_FOREACH(i
, machines
) {
59 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*container
= NULL
;
62 r
= sd_bus_open_system_machine(&container
, *i
);
64 log_warning_errno(r
, "Failed to connect to container %s, ignoring: %m", *i
);
68 k
= get_unit_list(container
, *i
, patterns
, &unit_infos
, c
, &reply
);
74 r
= set_put(replies
, reply
);
76 sd_bus_message_unref(reply
);
81 *ret_machines
= TAKE_PTR(machines
);
85 *ret_unit_infos
= TAKE_PTR(unit_infos
);
86 *ret_replies
= TAKE_PTR(replies
);
91 static int output_units_list(const UnitInfo
*unit_infos
, unsigned c
) {
92 _cleanup_(table_unrefp
) Table
*table
= NULL
;
95 table
= table_new("", "unit", "load", "active", "sub", "job", "description");
99 table_set_header(table
, !arg_no_legend
);
101 /* Hide the 'glyph' column when --plain is requested */
102 r
= table_hide_column_from_display(table
, 0);
104 return log_error_errno(r
, "Failed to hide column: %m");
107 table_set_width(table
, 0);
109 (void) table_set_empty_string(table
, "-");
112 for (const UnitInfo
*u
= unit_infos
; unit_infos
&& u
< unit_infos
+ c
; u
++) {
113 _cleanup_free_
char *j
= NULL
;
114 const char *on_underline
= "", *on_loaded
= "", *on_active
= "";
115 const char *on_circle
= "", *id
;
116 bool circle
= false, underline
= false;
118 if (u
+ 1 < unit_infos
+ c
&&
119 !streq(unit_type_suffix(u
->id
), unit_type_suffix((u
+ 1)->id
))) {
120 on_underline
= ansi_underline();
124 if (STR_IN_SET(u
->load_state
, "error", "not-found", "bad-setting", "masked") && !arg_plain
) {
125 on_circle
= underline
? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
127 on_loaded
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
128 } else if (streq(u
->active_state
, "failed") && !arg_plain
) {
129 on_circle
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
131 on_active
= underline
? ansi_highlight_red_underline() : ansi_highlight_red();
133 on_circle
= on_underline
;
134 on_active
= on_underline
;
135 on_loaded
= on_underline
;
139 j
= strjoin(u
->machine
, ":", u
->id
);
147 r
= table_add_many(table
,
148 TABLE_STRING
, circle
? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE
) : " ",
149 TABLE_SET_BOTH_COLORS
, on_circle
,
151 TABLE_SET_BOTH_COLORS
, on_active
,
152 TABLE_STRING
, u
->load_state
,
153 TABLE_SET_BOTH_COLORS
, on_loaded
,
154 TABLE_STRING
, u
->active_state
,
155 TABLE_SET_BOTH_COLORS
, on_active
,
156 TABLE_STRING
, u
->sub_state
,
157 TABLE_SET_BOTH_COLORS
, on_active
,
158 TABLE_STRING
, u
->job_id
? u
->job_type
: "",
159 TABLE_SET_BOTH_COLORS
, u
->job_id
? on_underline
: "",
160 TABLE_STRING
, u
->description
,
161 TABLE_SET_BOTH_COLORS
, on_underline
);
163 return table_log_add_error(r
);
169 if (job_count
== 0) {
170 /* There's no data in the JOB column, so let's hide it */
171 r
= table_hide_column_from_display(table
, 5);
173 return log_error_errno(r
, "Failed to hide column: %m");
176 r
= output_table(table
);
180 if (!arg_no_legend
) {
181 const char *on
, *off
;
182 size_t records
= table_get_rows(table
) - 1;
186 "LOAD = Reflects whether the unit definition was properly loaded.\n"
187 "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n"
188 "SUB = The low-level unit activation state, values depend on unit type.");
189 puts(job_count
? "JOB = Pending job for the unit.\n" : "");
190 on
= ansi_highlight();
193 on
= ansi_highlight_red();
197 if (arg_all
|| strv_contains(arg_states
, "inactive"))
198 printf("%s%zu loaded units listed.%s\n"
199 "To show all installed unit files use 'systemctl list-unit-files'.\n",
201 else if (!arg_states
)
202 printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
203 "To show all installed unit files use 'systemctl list-unit-files'.\n",
206 printf("%zu loaded units listed.\n", records
);
212 int list_units(int argc
, char *argv
[], void *userdata
) {
213 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
214 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
215 _cleanup_strv_free_
char **machines
= NULL
;
219 r
= acquire_bus(BUS_MANAGER
, &bus
);
223 (void) pager_open(arg_pager_flags
);
225 if (arg_with_dependencies
) {
226 _cleanup_strv_free_
char **names
= NULL
;
228 r
= append_unit_dependencies(bus
, strv_skip(argv
, 1), &names
);
232 r
= get_unit_list_recursive(bus
, names
, &unit_infos
, &replies
, &machines
);
236 r
= get_unit_list_recursive(bus
, strv_skip(argv
, 1), &unit_infos
, &replies
, &machines
);
241 typesafe_qsort(unit_infos
, r
, unit_info_compare
);
242 return output_units_list(unit_infos
, r
);
245 static int get_triggered_units(
250 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
257 r
= sd_bus_get_property_strv(
259 "org.freedesktop.systemd1",
261 "org.freedesktop.systemd1.Unit",
266 return log_error_errno(r
, "Failed to determine triggers: %s", bus_error_message(&error
, r
));
271 static int get_listening(
273 const char* unit_path
,
276 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
277 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
278 const char *type
, *path
;
281 r
= sd_bus_get_property(
283 "org.freedesktop.systemd1",
285 "org.freedesktop.systemd1.Socket",
291 return log_error_errno(r
, "Failed to get list of listening sockets: %s", bus_error_message(&error
, r
));
293 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ss)");
295 return bus_log_parse_error(r
);
297 while ((r
= sd_bus_message_read(reply
, "(ss)", &type
, &path
)) > 0) {
299 r
= strv_extend(listening
, type
);
303 r
= strv_extend(listening
, path
);
310 return bus_log_parse_error(r
);
312 r
= sd_bus_message_exit_container(reply
);
314 return bus_log_parse_error(r
);
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. */
330 /* The strv above is shared. free is set only in the first one. */
334 static int socket_info_compare(const struct socket_info
*a
, const struct socket_info
*b
) {
340 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
344 r
= strcmp(a
->path
, b
->path
);
348 return strcmp(a
->type
, b
->type
);
351 static int output_sockets_list(struct socket_info
*socket_infos
, unsigned cs
) {
352 _cleanup_(table_unrefp
) Table
*table
= NULL
;
353 struct socket_info
*s
;
354 const char *on
, *off
;
357 table
= table_new("listen", "type", "unit", "activates");
361 if (!arg_show_types
) {
362 /* Hide the second (TYPE) column */
363 r
= table_set_display(table
, (size_t) 0, (size_t) 2, (size_t) 3, (size_t) -1);
365 return log_error_errno(r
, "Failed to set columns to display: %m");
368 table_set_header(table
, !arg_no_legend
);
370 table_set_width(table
, 0);
372 (void) table_set_empty_string(table
, "-");
375 for (s
= socket_infos
; s
< socket_infos
+ cs
; s
++) {
376 _cleanup_free_
char *j
= NULL
;
380 j
= strjoin(s
->machine
, ":", s
->path
);
387 r
= table_add_many(table
,
389 TABLE_STRING
, s
->type
,
390 TABLE_STRING
, s
->id
);
392 return table_log_add_error(r
);
394 if (strv_isempty(s
->triggered
))
395 r
= table_add_cell(table
, NULL
, TABLE_EMPTY
, NULL
);
396 else if (strv_length(s
->triggered
) == 1)
397 r
= table_add_cell(table
, NULL
, TABLE_STRING
, s
->triggered
[0]);
399 /* This should never happen, currently our socket units can only trigger a
400 * single unit. But let's handle this anyway, who knows what the future
402 r
= table_add_cell(table
, NULL
, TABLE_STRV
, s
->triggered
);
404 return table_log_add_error(r
);
408 on
= ansi_highlight();
411 on
= ansi_highlight_red();
415 r
= output_table(table
);
419 if (!arg_no_legend
) {
420 printf("\n%s%u sockets listed.%s\n", on
, cs
, off
);
422 printf("Pass --all to see loaded but inactive sockets, too.\n");
428 int list_sockets(int argc
, char *argv
[], void *userdata
) {
429 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
430 _cleanup_strv_free_
char **machines
= NULL
;
431 _cleanup_strv_free_
char **sockets_with_suffix
= NULL
;
432 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
433 _cleanup_free_
struct socket_info
*socket_infos
= NULL
;
435 struct socket_info
*s
;
441 r
= acquire_bus(BUS_MANAGER
, &bus
);
445 (void) pager_open(arg_pager_flags
);
447 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".socket", &sockets_with_suffix
, NULL
);
451 if (argc
== 1 || sockets_with_suffix
) {
452 n
= get_unit_list_recursive(bus
, sockets_with_suffix
, &unit_infos
, &replies
, &machines
);
456 for (u
= unit_infos
; u
< unit_infos
+ n
; u
++) {
457 _cleanup_strv_free_
char **listening
= NULL
, **triggered
= NULL
;
460 if (!endswith(u
->id
, ".socket"))
463 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
467 c
= get_listening(bus
, u
->unit_path
, &listening
);
473 if (!GREEDY_REALLOC(socket_infos
, size
, cs
+ c
)) {
478 for (i
= 0; i
< c
; i
++)
479 socket_infos
[cs
+ i
] = (struct socket_info
) {
480 .machine
= u
->machine
,
482 .type
= listening
[i
*2],
483 .path
= listening
[i
*2 + 1],
484 .triggered
= triggered
,
485 .own_triggered
= i
==0,
488 /* from this point on we will cleanup those socket_infos */
491 listening
= triggered
= NULL
; /* avoid cleanup */
494 typesafe_qsort(socket_infos
, cs
, socket_info_compare
);
497 output_sockets_list(socket_infos
, cs
);
500 assert(cs
== 0 || socket_infos
);
501 for (s
= socket_infos
; s
< socket_infos
+ cs
; s
++) {
504 if (s
->own_triggered
)
505 strv_free(s
->triggered
);
511 static int get_next_elapse(
514 dual_timestamp
*next
) {
516 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
524 r
= sd_bus_get_property_trivial(
526 "org.freedesktop.systemd1",
528 "org.freedesktop.systemd1.Timer",
529 "NextElapseUSecMonotonic",
534 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
536 r
= sd_bus_get_property_trivial(
538 "org.freedesktop.systemd1",
540 "org.freedesktop.systemd1.Timer",
541 "NextElapseUSecRealtime",
546 return log_error_errno(r
, "Failed to get next elapse time: %s", bus_error_message(&error
, r
));
552 static int get_last_trigger(
557 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
564 r
= sd_bus_get_property_trivial(
566 "org.freedesktop.systemd1",
568 "org.freedesktop.systemd1.Timer",
574 return log_error_errno(r
, "Failed to get last trigger time: %s", bus_error_message(&error
, r
));
587 static int timer_info_compare(const struct timer_info
*a
, const struct timer_info
*b
) {
593 r
= strcasecmp_ptr(a
->machine
, b
->machine
);
597 r
= CMP(a
->next_elapse
, b
->next_elapse
);
601 return strcmp(a
->id
, b
->id
);
604 static int output_timers_list(struct timer_info
*timer_infos
, unsigned n
) {
605 _cleanup_(table_unrefp
) Table
*table
= NULL
;
606 struct timer_info
*t
;
607 const char *on
, *off
;
610 assert(timer_infos
|| n
== 0);
612 table
= table_new("next", "left", "last", "passed", "unit", "activates");
616 table_set_header(table
, !arg_no_legend
);
618 table_set_width(table
, 0);
620 (void) table_set_empty_string(table
, "-");
623 for (t
= timer_infos
; t
< timer_infos
+ n
; t
++) {
624 _cleanup_free_
char *j
= NULL
, *activates
= NULL
;
628 j
= strjoin(t
->machine
, ":", t
->id
);
635 activates
= strv_join(t
->triggered
, ", ");
639 r
= table_add_many(table
,
640 TABLE_TIMESTAMP
, t
->next_elapse
,
641 TABLE_TIMESTAMP_RELATIVE
, t
->next_elapse
,
642 TABLE_TIMESTAMP
, t
->last_trigger
,
643 TABLE_TIMESTAMP_RELATIVE
, t
->last_trigger
,
645 TABLE_STRING
, activates
);
647 return table_log_add_error(r
);
650 on
= ansi_highlight();
653 on
= ansi_highlight_red();
657 r
= output_table(table
);
661 if (!arg_no_legend
) {
662 printf("\n%s%u timers listed.%s\n", on
, n
, off
);
664 printf("Pass --all to see loaded but inactive timers, too.\n");
670 usec_t
calc_next_elapse(dual_timestamp
*nw
, dual_timestamp
*next
) {
676 if (timestamp_is_set(next
->monotonic
)) {
679 if (next
->monotonic
> nw
->monotonic
)
680 converted
= nw
->realtime
+ (next
->monotonic
- nw
->monotonic
);
682 converted
= nw
->realtime
- (nw
->monotonic
- next
->monotonic
);
684 if (timestamp_is_set(next
->realtime
))
685 next_elapse
= MIN(converted
, next
->realtime
);
687 next_elapse
= converted
;
690 next_elapse
= next
->realtime
;
695 int list_timers(int argc
, char *argv
[], void *userdata
) {
696 _cleanup_(message_set_freep
) Set
*replies
= NULL
;
697 _cleanup_strv_free_
char **machines
= NULL
;
698 _cleanup_strv_free_
char **timers_with_suffix
= NULL
;
699 _cleanup_free_
struct timer_info
*timer_infos
= NULL
;
700 _cleanup_free_ UnitInfo
*unit_infos
= NULL
;
701 struct timer_info
*t
;
709 r
= acquire_bus(BUS_MANAGER
, &bus
);
713 (void) pager_open(arg_pager_flags
);
715 r
= expand_unit_names(bus
, strv_skip(argv
, 1), ".timer", &timers_with_suffix
, NULL
);
719 if (argc
== 1 || timers_with_suffix
) {
720 n
= get_unit_list_recursive(bus
, timers_with_suffix
, &unit_infos
, &replies
, &machines
);
724 dual_timestamp_get(&nw
);
726 for (u
= unit_infos
; u
< unit_infos
+ n
; u
++) {
727 _cleanup_strv_free_
char **triggered
= NULL
;
728 dual_timestamp next
= DUAL_TIMESTAMP_NULL
;
731 if (!endswith(u
->id
, ".timer"))
734 r
= get_triggered_units(bus
, u
->unit_path
, &triggered
);
738 r
= get_next_elapse(bus
, u
->unit_path
, &next
);
742 get_last_trigger(bus
, u
->unit_path
, &last
);
744 if (!GREEDY_REALLOC(timer_infos
, size
, c
+1)) {
749 m
= calc_next_elapse(&nw
, &next
);
751 timer_infos
[c
++] = (struct timer_info
) {
752 .machine
= u
->machine
,
755 .last_trigger
= last
,
756 .triggered
= TAKE_PTR(triggered
),
760 typesafe_qsort(timer_infos
, c
, timer_info_compare
);
763 output_timers_list(timer_infos
, c
);
766 for (t
= timer_infos
; t
< timer_infos
+ c
; t
++)
767 strv_free(t
->triggered
);