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 
message_set_freep(Set ** set)15 static void message_set_freep(Set **set) {
16         set_free_with_destructor(*set, sd_bus_message_unref);
17 }
18 
get_unit_list_recursive(sd_bus * bus,char ** patterns,UnitInfo ** ret_unit_infos,Set ** ret_replies,char *** ret_machines)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 
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 
output_units_list(const UnitInfo * unit_infos,unsigned c)90 static int output_units_list(const UnitInfo *unit_infos, unsigned c) {
91         _cleanup_(table_unrefp) Table *table = NULL;
92         unsigned job_count = 0;
93         int r;
94 
95         table = table_new("", "unit", "load", "active", "sub", "job", "description");
96         if (!table)
97                 return log_oom();
98 
99         table_set_header(table, arg_legend != 0);
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 
111         for (const UnitInfo *u = unit_infos; unit_infos && (size_t) (u - unit_infos) < c; u++) {
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: "",
158                                    TABLE_SET_BOTH_COLORS, on_underline,
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 
179         if (arg_legend != 0) {
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.");
188                         if (job_count > 0)
189                                 puts("JOB    = Pending job for the unit.\n");
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 
verb_list_units(int argc,char * argv[],void * userdata)212 int verb_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;
216         sd_bus *bus;
217         int r;
218 
219         r = acquire_bus(BUS_MANAGER, &bus);
220         if (r < 0)
221                 return r;
222 
223         pager_open(arg_pager_flags);
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 
get_triggered_units(sd_bus * bus,const char * path,char *** ret)245 static 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 
get_listening(sd_bus * bus,const char * unit_path,char *** listening)271 static 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 
319 struct 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 
socket_info_compare(const struct socket_info * a,const struct socket_info * b)334 static 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 
output_sockets_list(struct socket_info * socket_infos,unsigned cs)351 static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) {
352         _cleanup_(table_unrefp) Table *table = NULL;
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 */
362                 r = table_set_display(table, (size_t) 0, (size_t) 2, (size_t) 3);
363                 if (r < 0)
364                         return log_error_errno(r, "Failed to set columns to display: %m");
365         }
366 
367         table_set_header(table, arg_legend != 0);
368         if (arg_full)
369                 table_set_width(table, 0);
370 
371         (void) table_set_empty_string(table, "-");
372 
373         if (cs) {
374                 for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) {
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 
418         if (arg_legend != 0) {
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 
verb_list_sockets(int argc,char * argv[],void * userdata)427 int verb_list_sockets(int argc, char *argv[], void *userdata) {
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;
433         unsigned cs = 0;
434         int r, n;
435         sd_bus *bus;
436 
437         r = acquire_bus(BUS_MANAGER, &bus);
438         if (r < 0)
439                 return r;
440 
441         pager_open(arg_pager_flags);
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 
452                 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
453                         _cleanup_strv_free_ char **listening = NULL, **triggered = NULL;
454                         int c;
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 
469                         if (!GREEDY_REALLOC(socket_infos, cs + c)) {
470                                 r = log_oom();
471                                 goto cleanup;
472                         }
473 
474                         for (int i = 0; i < c; i++)
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);
497         for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) {
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 
get_next_elapse(sd_bus * bus,const char * path,dual_timestamp * next)507 static 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 
get_last_trigger(sd_bus * bus,const char * path,usec_t * last)548 static 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 
575 struct 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 
timer_info_compare(const struct timer_info * a,const struct timer_info * b)583 static 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 
output_timers_list(struct timer_info * timer_infos,unsigned n)600 static int output_timers_list(struct timer_info *timer_infos, unsigned n) {
601         _cleanup_(table_unrefp) Table *table = NULL;
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 
611         table_set_header(table, arg_legend != 0);
612         if (arg_full)
613                 table_set_width(table, 0);
614 
615         (void) table_set_empty_string(table, "-");
616 
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;
620 
621                 if (t->machine) {
622                         j = strjoin(t->machine, ":", t->id);
623                         if (!j)
624                                 return log_oom();
625                         unit = j;
626                 } else
627                         unit = t->id;
628 
629                 activates = strv_join(t->triggered, ", ");
630                 if (!activates)
631                         return log_oom();
632 
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) {
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 
656         if (arg_legend != 0) {
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 
calc_next_elapse(dual_timestamp * nw,dual_timestamp * next)665 usec_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 
verb_list_timers(int argc,char * argv[],void * userdata)690 int verb_list_timers(int argc, char *argv[], void *userdata) {
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;
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 
705         pager_open(arg_pager_flags);
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 
718                 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
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 
736                         if (!GREEDY_REALLOC(timer_infos, c+1)) {
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:
758         for (struct timer_info *t = timer_infos; t < timer_infos + c; t++)
759                 strv_free(t->triggered);
760 
761         return r;
762 }
763