]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/systemctl/systemctl-list-units.c
Merge pull request #20030 from keszybz/exec_fd-event-source
[thirdparty/systemd.git] / src / systemctl / systemctl-list-units.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "sd-login.h"
4
5 #include "bus-error.h"
6 #include "format-table.h"
7 #include "locale-util.h"
8 #include "set.h"
9 #include "sort-util.h"
10 #include "systemctl-list-units.h"
11 #include "systemctl-util.h"
12 #include "systemctl.h"
13 #include "terminal-util.h"
14
15 static void message_set_freep(Set **set) {
16 set_free_with_destructor(*set, sd_bus_message_unref);
17 }
18
19 static int get_unit_list_recursive(
20 sd_bus *bus,
21 char **patterns,
22 UnitInfo **ret_unit_infos,
23 Set **ret_replies,
24 char ***ret_machines) {
25
26 _cleanup_free_ UnitInfo *unit_infos = NULL;
27 _cleanup_(message_set_freep) Set *replies = NULL;
28 sd_bus_message *reply;
29 int c, r;
30
31 assert(bus);
32 assert(ret_replies);
33 assert(ret_unit_infos);
34 assert(ret_machines);
35
36 replies = set_new(NULL);
37 if (!replies)
38 return log_oom();
39
40 c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply);
41 if (c < 0)
42 return c;
43
44 r = set_put(replies, reply);
45 if (r < 0) {
46 sd_bus_message_unref(reply);
47 return log_oom();
48 }
49
50 if (arg_recursive) {
51 _cleanup_strv_free_ char **machines = NULL;
52 char **i;
53
54 r = sd_get_machine_names(&machines);
55 if (r < 0)
56 return log_error_errno(r, "Failed to get machine names: %m");
57
58 STRV_FOREACH(i, machines) {
59 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL;
60 int k;
61
62 r = sd_bus_open_system_machine(&container, *i);
63 if (r < 0) {
64 log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i);
65 continue;
66 }
67
68 k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply);
69 if (k < 0)
70 return k;
71
72 c = k;
73
74 r = set_put(replies, reply);
75 if (r < 0) {
76 sd_bus_message_unref(reply);
77 return log_oom();
78 }
79 }
80
81 *ret_machines = TAKE_PTR(machines);
82 } else
83 *ret_machines = NULL;
84
85 *ret_unit_infos = TAKE_PTR(unit_infos);
86 *ret_replies = TAKE_PTR(replies);
87
88 return c;
89 }
90
91 static int output_units_list(const UnitInfo *unit_infos, unsigned c) {
92 _cleanup_(table_unrefp) Table *table = NULL;
93 unsigned job_count = 0;
94 int r;
95
96 table = table_new("", "unit", "load", "active", "sub", "job", "description");
97 if (!table)
98 return log_oom();
99
100 table_set_header(table, arg_legend != 0);
101 if (arg_plain) {
102 /* Hide the 'glyph' column when --plain is requested */
103 r = table_hide_column_from_display(table, 0);
104 if (r < 0)
105 return log_error_errno(r, "Failed to hide column: %m");
106 }
107 if (arg_full)
108 table_set_width(table, 0);
109
110 (void) table_set_empty_string(table, "-");
111
112 for (const UnitInfo *u = unit_infos; unit_infos && (size_t) (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;
117
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();
121 underline = true;
122 }
123
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();
126 circle = true;
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();
130 circle = true;
131 on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
132 } else {
133 on_circle = on_underline;
134 on_active = on_underline;
135 on_loaded = on_underline;
136 }
137
138 if (u->machine) {
139 j = strjoin(u->machine, ":", u->id);
140 if (!j)
141 return log_oom();
142
143 id = j;
144 } else
145 id = u->id;
146
147 r = table_add_many(table,
148 TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ",
149 TABLE_SET_BOTH_COLORS, on_circle,
150 TABLE_STRING, id,
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, on_underline,
160 TABLE_STRING, u->description,
161 TABLE_SET_BOTH_COLORS, on_underline);
162 if (r < 0)
163 return table_log_add_error(r);
164
165 if (u->job_id != 0)
166 job_count++;
167 }
168
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);
172 if (r < 0)
173 return log_error_errno(r, "Failed to hide column: %m");
174 }
175
176 r = output_table(table);
177 if (r < 0)
178 return r;
179
180 if (arg_legend != 0) {
181 const char *on, *off;
182 size_t records = table_get_rows(table) - 1;
183
184 if (records > 0) {
185 puts("\n"
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 if (job_count > 0)
190 puts("JOB = Pending job for the unit.\n");
191 on = ansi_highlight();
192 off = ansi_normal();
193 } else {
194 on = ansi_highlight_red();
195 off = ansi_normal();
196 }
197
198 if (arg_all || strv_contains(arg_states, "inactive"))
199 printf("%s%zu loaded units listed.%s\n"
200 "To show all installed unit files use 'systemctl list-unit-files'.\n",
201 on, records, off);
202 else if (!arg_states)
203 printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
204 "To show all installed unit files use 'systemctl list-unit-files'.\n",
205 on, records, off);
206 else
207 printf("%zu loaded units listed.\n", records);
208 }
209
210 return 0;
211 }
212
213 int list_units(int argc, char *argv[], void *userdata) {
214 _cleanup_free_ UnitInfo *unit_infos = NULL;
215 _cleanup_(message_set_freep) Set *replies = NULL;
216 _cleanup_strv_free_ char **machines = NULL;
217 sd_bus *bus;
218 int r;
219
220 r = acquire_bus(BUS_MANAGER, &bus);
221 if (r < 0)
222 return r;
223
224 (void) pager_open(arg_pager_flags);
225
226 if (arg_with_dependencies) {
227 _cleanup_strv_free_ char **names = NULL;
228
229 r = append_unit_dependencies(bus, strv_skip(argv, 1), &names);
230 if (r < 0)
231 return r;
232
233 r = get_unit_list_recursive(bus, names, &unit_infos, &replies, &machines);
234 if (r < 0)
235 return r;
236 } else {
237 r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
238 if (r < 0)
239 return r;
240 }
241
242 typesafe_qsort(unit_infos, r, unit_info_compare);
243 return output_units_list(unit_infos, r);
244 }
245
246 static int get_triggered_units(
247 sd_bus *bus,
248 const char* path,
249 char*** ret) {
250
251 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
252 int r;
253
254 assert(bus);
255 assert(path);
256 assert(ret);
257
258 r = sd_bus_get_property_strv(
259 bus,
260 "org.freedesktop.systemd1",
261 path,
262 "org.freedesktop.systemd1.Unit",
263 "Triggers",
264 &error,
265 ret);
266 if (r < 0)
267 return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r));
268
269 return 0;
270 }
271
272 static int get_listening(
273 sd_bus *bus,
274 const char* unit_path,
275 char*** listening) {
276
277 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
278 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
279 const char *type, *path;
280 int r, n = 0;
281
282 r = sd_bus_get_property(
283 bus,
284 "org.freedesktop.systemd1",
285 unit_path,
286 "org.freedesktop.systemd1.Socket",
287 "Listen",
288 &error,
289 &reply,
290 "a(ss)");
291 if (r < 0)
292 return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r));
293
294 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
295 if (r < 0)
296 return bus_log_parse_error(r);
297
298 while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) {
299
300 r = strv_extend(listening, type);
301 if (r < 0)
302 return log_oom();
303
304 r = strv_extend(listening, path);
305 if (r < 0)
306 return log_oom();
307
308 n++;
309 }
310 if (r < 0)
311 return bus_log_parse_error(r);
312
313 r = sd_bus_message_exit_container(reply);
314 if (r < 0)
315 return bus_log_parse_error(r);
316
317 return n;
318 }
319
320 struct socket_info {
321 const char *machine;
322 const char* id;
323
324 char* type;
325 char* path;
326
327 /* Note: triggered is a list here, although it almost certainly will always be one
328 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
329 char** triggered;
330
331 /* The strv above is shared. free is set only in the first one. */
332 bool own_triggered;
333 };
334
335 static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) {
336 int r;
337
338 assert(a);
339 assert(b);
340
341 r = strcasecmp_ptr(a->machine, b->machine);
342 if (r != 0)
343 return r;
344
345 r = strcmp(a->path, b->path);
346 if (r != 0)
347 return r;
348
349 return strcmp(a->type, b->type);
350 }
351
352 static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) {
353 _cleanup_(table_unrefp) Table *table = NULL;
354 const char *on, *off;
355 int r;
356
357 table = table_new("listen", "type", "unit", "activates");
358 if (!table)
359 return log_oom();
360
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);
364 if (r < 0)
365 return log_error_errno(r, "Failed to set columns to display: %m");
366 }
367
368 table_set_header(table, arg_legend != 0);
369 if (arg_full)
370 table_set_width(table, 0);
371
372 (void) table_set_empty_string(table, "-");
373
374 if (cs) {
375 for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) {
376 _cleanup_free_ char *j = NULL;
377 const char *path;
378
379 if (s->machine) {
380 j = strjoin(s->machine, ":", s->path);
381 if (!j)
382 return log_oom();
383 path = j;
384 } else
385 path = s->path;
386
387 r = table_add_many(table,
388 TABLE_STRING, path,
389 TABLE_STRING, s->type,
390 TABLE_STRING, s->id);
391 if (r < 0)
392 return table_log_add_error(r);
393
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]);
398 else
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
401 * brings? */
402 r = table_add_cell(table, NULL, TABLE_STRV, s->triggered);
403 if (r < 0)
404 return table_log_add_error(r);
405
406 }
407
408 on = ansi_highlight();
409 off = ansi_normal();
410 } else {
411 on = ansi_highlight_red();
412 off = ansi_normal();
413 }
414
415 r = output_table(table);
416 if (r < 0)
417 return r;
418
419 if (arg_legend != 0) {
420 printf("\n%s%u sockets listed.%s\n", on, cs, off);
421 if (!arg_all)
422 printf("Pass --all to see loaded but inactive sockets, too.\n");
423 }
424
425 return 0;
426 }
427
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;
434 unsigned cs = 0;
435 int r, n;
436 sd_bus *bus;
437
438 r = acquire_bus(BUS_MANAGER, &bus);
439 if (r < 0)
440 return r;
441
442 (void) pager_open(arg_pager_flags);
443
444 r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL);
445 if (r < 0)
446 return r;
447
448 if (argc == 1 || sockets_with_suffix) {
449 n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies, &machines);
450 if (n < 0)
451 return n;
452
453 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
454 _cleanup_strv_free_ char **listening = NULL, **triggered = NULL;
455 int c;
456
457 if (!endswith(u->id, ".socket"))
458 continue;
459
460 r = get_triggered_units(bus, u->unit_path, &triggered);
461 if (r < 0)
462 goto cleanup;
463
464 c = get_listening(bus, u->unit_path, &listening);
465 if (c < 0) {
466 r = c;
467 goto cleanup;
468 }
469
470 if (!GREEDY_REALLOC(socket_infos, cs + c)) {
471 r = log_oom();
472 goto cleanup;
473 }
474
475 for (int i = 0; i < c; i++)
476 socket_infos[cs + i] = (struct socket_info) {
477 .machine = u->machine,
478 .id = u->id,
479 .type = listening[i*2],
480 .path = listening[i*2 + 1],
481 .triggered = triggered,
482 .own_triggered = i==0,
483 };
484
485 /* from this point on we will cleanup those socket_infos */
486 cs += c;
487 free(listening);
488 listening = triggered = NULL; /* avoid cleanup */
489 }
490
491 typesafe_qsort(socket_infos, cs, socket_info_compare);
492 }
493
494 output_sockets_list(socket_infos, cs);
495
496 cleanup:
497 assert(cs == 0 || socket_infos);
498 for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) {
499 free(s->type);
500 free(s->path);
501 if (s->own_triggered)
502 strv_free(s->triggered);
503 }
504
505 return r;
506 }
507
508 static int get_next_elapse(
509 sd_bus *bus,
510 const char *path,
511 dual_timestamp *next) {
512
513 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
514 dual_timestamp t;
515 int r;
516
517 assert(bus);
518 assert(path);
519 assert(next);
520
521 r = sd_bus_get_property_trivial(
522 bus,
523 "org.freedesktop.systemd1",
524 path,
525 "org.freedesktop.systemd1.Timer",
526 "NextElapseUSecMonotonic",
527 &error,
528 't',
529 &t.monotonic);
530 if (r < 0)
531 return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
532
533 r = sd_bus_get_property_trivial(
534 bus,
535 "org.freedesktop.systemd1",
536 path,
537 "org.freedesktop.systemd1.Timer",
538 "NextElapseUSecRealtime",
539 &error,
540 't',
541 &t.realtime);
542 if (r < 0)
543 return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
544
545 *next = t;
546 return 0;
547 }
548
549 static int get_last_trigger(
550 sd_bus *bus,
551 const char *path,
552 usec_t *last) {
553
554 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
555 int r;
556
557 assert(bus);
558 assert(path);
559 assert(last);
560
561 r = sd_bus_get_property_trivial(
562 bus,
563 "org.freedesktop.systemd1",
564 path,
565 "org.freedesktop.systemd1.Timer",
566 "LastTriggerUSec",
567 &error,
568 't',
569 last);
570 if (r < 0)
571 return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r));
572
573 return 0;
574 }
575
576 struct timer_info {
577 const char* machine;
578 const char* id;
579 usec_t next_elapse;
580 usec_t last_trigger;
581 char** triggered;
582 };
583
584 static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) {
585 int r;
586
587 assert(a);
588 assert(b);
589
590 r = strcasecmp_ptr(a->machine, b->machine);
591 if (r != 0)
592 return r;
593
594 r = CMP(a->next_elapse, b->next_elapse);
595 if (r != 0)
596 return r;
597
598 return strcmp(a->id, b->id);
599 }
600
601 static int output_timers_list(struct timer_info *timer_infos, unsigned n) {
602 _cleanup_(table_unrefp) Table *table = NULL;
603 const char *on, *off;
604 int r;
605
606 assert(timer_infos || n == 0);
607
608 table = table_new("next", "left", "last", "passed", "unit", "activates");
609 if (!table)
610 return log_oom();
611
612 table_set_header(table, arg_legend != 0);
613 if (arg_full)
614 table_set_width(table, 0);
615
616 (void) table_set_empty_string(table, "-");
617
618 for (struct timer_info *t = timer_infos; t < timer_infos + n; t++) {
619 _cleanup_free_ char *j = NULL, *activates = NULL;
620 const char *unit;
621
622 if (t->machine) {
623 j = strjoin(t->machine, ":", t->id);
624 if (!j)
625 return log_oom();
626 unit = j;
627 } else
628 unit = t->id;
629
630 activates = strv_join(t->triggered, ", ");
631 if (!activates)
632 return log_oom();
633
634 r = table_add_many(table,
635 TABLE_TIMESTAMP, t->next_elapse,
636 TABLE_TIMESTAMP_RELATIVE, t->next_elapse,
637 TABLE_TIMESTAMP, t->last_trigger,
638 TABLE_TIMESTAMP_RELATIVE, t->last_trigger,
639 TABLE_STRING, unit,
640 TABLE_STRING, activates);
641 if (r < 0)
642 return table_log_add_error(r);
643 }
644
645 if (n > 0) {
646 on = ansi_highlight();
647 off = ansi_normal();
648 } else {
649 on = ansi_highlight_red();
650 off = ansi_normal();
651 }
652
653 r = output_table(table);
654 if (r < 0)
655 return r;
656
657 if (arg_legend != 0) {
658 printf("\n%s%u timers listed.%s\n", on, n, off);
659 if (!arg_all)
660 printf("Pass --all to see loaded but inactive timers, too.\n");
661 }
662
663 return 0;
664 }
665
666 usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) {
667 usec_t next_elapse;
668
669 assert(nw);
670 assert(next);
671
672 if (timestamp_is_set(next->monotonic)) {
673 usec_t converted;
674
675 if (next->monotonic > nw->monotonic)
676 converted = nw->realtime + (next->monotonic - nw->monotonic);
677 else
678 converted = nw->realtime - (nw->monotonic - next->monotonic);
679
680 if (timestamp_is_set(next->realtime))
681 next_elapse = MIN(converted, next->realtime);
682 else
683 next_elapse = converted;
684
685 } else
686 next_elapse = next->realtime;
687
688 return next_elapse;
689 }
690
691 int list_timers(int argc, char *argv[], void *userdata) {
692 _cleanup_(message_set_freep) Set *replies = NULL;
693 _cleanup_strv_free_ char **machines = NULL;
694 _cleanup_strv_free_ char **timers_with_suffix = NULL;
695 _cleanup_free_ struct timer_info *timer_infos = NULL;
696 _cleanup_free_ UnitInfo *unit_infos = NULL;
697 int n, c = 0;
698 dual_timestamp nw;
699 sd_bus *bus;
700 int r;
701
702 r = acquire_bus(BUS_MANAGER, &bus);
703 if (r < 0)
704 return r;
705
706 (void) pager_open(arg_pager_flags);
707
708 r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL);
709 if (r < 0)
710 return r;
711
712 if (argc == 1 || timers_with_suffix) {
713 n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies, &machines);
714 if (n < 0)
715 return n;
716
717 dual_timestamp_get(&nw);
718
719 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
720 _cleanup_strv_free_ char **triggered = NULL;
721 dual_timestamp next = DUAL_TIMESTAMP_NULL;
722 usec_t m, last = 0;
723
724 if (!endswith(u->id, ".timer"))
725 continue;
726
727 r = get_triggered_units(bus, u->unit_path, &triggered);
728 if (r < 0)
729 goto cleanup;
730
731 r = get_next_elapse(bus, u->unit_path, &next);
732 if (r < 0)
733 goto cleanup;
734
735 get_last_trigger(bus, u->unit_path, &last);
736
737 if (!GREEDY_REALLOC(timer_infos, c+1)) {
738 r = log_oom();
739 goto cleanup;
740 }
741
742 m = calc_next_elapse(&nw, &next);
743
744 timer_infos[c++] = (struct timer_info) {
745 .machine = u->machine,
746 .id = u->id,
747 .next_elapse = m,
748 .last_trigger = last,
749 .triggered = TAKE_PTR(triggered),
750 };
751 }
752
753 typesafe_qsort(timer_infos, c, timer_info_compare);
754 }
755
756 output_timers_list(timer_infos, c);
757
758 cleanup:
759 for (struct timer_info *t = timer_infos; t < timer_infos + c; t++)
760 strv_free(t->triggered);
761
762 return r;
763 }