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