]>
Commit | Line | Data |
---|---|---|
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 | ||
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; | |
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 | 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_no_legend); | |
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 | int job_count = 0; | |
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; | |
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: "", | |
81039448 | 159 | TABLE_SET_BOTH_COLORS, on_underline, |
daf71ef6 LP |
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_no_legend) { | |
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 | puts(job_count ? "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 | ||
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; | |
216 | sd_bus *bus; | |
217 | int r; | |
218 | ||
219 | r = acquire_bus(BUS_MANAGER, &bus); | |
220 | if (r < 0) | |
221 | return r; | |
222 | ||
223 | (void) 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 | ||
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 | ||
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 | ||
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 | ||
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; | |
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, (size_t) -1); | |
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_no_legend); | |
369 | if (arg_full) | |
370 | table_set_width(table, 0); | |
371 | ||
372 | (void) table_set_empty_string(table, "-"); | |
373 | ||
374 | if (cs) { | |
375 | for (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_no_legend) { | |
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 | const UnitInfo *u; | |
435 | struct socket_info *s; | |
436 | unsigned cs = 0; | |
437 | size_t size = 0; | |
438 | int r, n; | |
439 | sd_bus *bus; | |
440 | ||
441 | r = acquire_bus(BUS_MANAGER, &bus); | |
442 | if (r < 0) | |
443 | return r; | |
444 | ||
445 | (void) pager_open(arg_pager_flags); | |
446 | ||
447 | r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL); | |
448 | if (r < 0) | |
449 | return r; | |
450 | ||
451 | if (argc == 1 || sockets_with_suffix) { | |
452 | n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies, &machines); | |
453 | if (n < 0) | |
454 | return n; | |
455 | ||
456 | for (u = unit_infos; u < unit_infos + n; u++) { | |
457 | _cleanup_strv_free_ char **listening = NULL, **triggered = NULL; | |
458 | int i, c; | |
459 | ||
460 | if (!endswith(u->id, ".socket")) | |
461 | continue; | |
462 | ||
463 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
464 | if (r < 0) | |
465 | goto cleanup; | |
466 | ||
467 | c = get_listening(bus, u->unit_path, &listening); | |
468 | if (c < 0) { | |
469 | r = c; | |
470 | goto cleanup; | |
471 | } | |
472 | ||
473 | if (!GREEDY_REALLOC(socket_infos, size, cs + c)) { | |
474 | r = log_oom(); | |
475 | goto cleanup; | |
476 | } | |
477 | ||
478 | for (i = 0; i < c; i++) | |
479 | socket_infos[cs + i] = (struct socket_info) { | |
480 | .machine = u->machine, | |
481 | .id = u->id, | |
482 | .type = listening[i*2], | |
483 | .path = listening[i*2 + 1], | |
484 | .triggered = triggered, | |
485 | .own_triggered = i==0, | |
486 | }; | |
487 | ||
488 | /* from this point on we will cleanup those socket_infos */ | |
489 | cs += c; | |
490 | free(listening); | |
491 | listening = triggered = NULL; /* avoid cleanup */ | |
492 | } | |
493 | ||
494 | typesafe_qsort(socket_infos, cs, socket_info_compare); | |
495 | } | |
496 | ||
497 | output_sockets_list(socket_infos, cs); | |
498 | ||
499 | cleanup: | |
500 | assert(cs == 0 || socket_infos); | |
501 | for (s = socket_infos; s < socket_infos + cs; s++) { | |
502 | free(s->type); | |
503 | free(s->path); | |
504 | if (s->own_triggered) | |
505 | strv_free(s->triggered); | |
506 | } | |
507 | ||
508 | return r; | |
509 | } | |
510 | ||
511 | static int get_next_elapse( | |
512 | sd_bus *bus, | |
513 | const char *path, | |
514 | dual_timestamp *next) { | |
515 | ||
516 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
517 | dual_timestamp t; | |
518 | int r; | |
519 | ||
520 | assert(bus); | |
521 | assert(path); | |
522 | assert(next); | |
523 | ||
524 | r = sd_bus_get_property_trivial( | |
525 | bus, | |
526 | "org.freedesktop.systemd1", | |
527 | path, | |
528 | "org.freedesktop.systemd1.Timer", | |
529 | "NextElapseUSecMonotonic", | |
530 | &error, | |
531 | 't', | |
532 | &t.monotonic); | |
533 | if (r < 0) | |
534 | return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r)); | |
535 | ||
536 | r = sd_bus_get_property_trivial( | |
537 | bus, | |
538 | "org.freedesktop.systemd1", | |
539 | path, | |
540 | "org.freedesktop.systemd1.Timer", | |
541 | "NextElapseUSecRealtime", | |
542 | &error, | |
543 | 't', | |
544 | &t.realtime); | |
545 | if (r < 0) | |
546 | return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r)); | |
547 | ||
548 | *next = t; | |
549 | return 0; | |
550 | } | |
551 | ||
552 | static int get_last_trigger( | |
553 | sd_bus *bus, | |
554 | const char *path, | |
555 | usec_t *last) { | |
556 | ||
557 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
558 | int r; | |
559 | ||
560 | assert(bus); | |
561 | assert(path); | |
562 | assert(last); | |
563 | ||
564 | r = sd_bus_get_property_trivial( | |
565 | bus, | |
566 | "org.freedesktop.systemd1", | |
567 | path, | |
568 | "org.freedesktop.systemd1.Timer", | |
569 | "LastTriggerUSec", | |
570 | &error, | |
571 | 't', | |
572 | last); | |
573 | if (r < 0) | |
574 | return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); | |
575 | ||
576 | return 0; | |
577 | } | |
578 | ||
579 | struct timer_info { | |
580 | const char* machine; | |
581 | const char* id; | |
582 | usec_t next_elapse; | |
583 | usec_t last_trigger; | |
584 | char** triggered; | |
585 | }; | |
586 | ||
587 | static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) { | |
588 | int r; | |
589 | ||
590 | assert(a); | |
591 | assert(b); | |
592 | ||
593 | r = strcasecmp_ptr(a->machine, b->machine); | |
594 | if (r != 0) | |
595 | return r; | |
596 | ||
597 | r = CMP(a->next_elapse, b->next_elapse); | |
598 | if (r != 0) | |
599 | return r; | |
600 | ||
601 | return strcmp(a->id, b->id); | |
602 | } | |
603 | ||
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; | |
608 | int r; | |
609 | ||
610 | assert(timer_infos || n == 0); | |
611 | ||
612 | table = table_new("next", "left", "last", "passed", "unit", "activates"); | |
613 | if (!table) | |
614 | return log_oom(); | |
615 | ||
616 | table_set_header(table, !arg_no_legend); | |
617 | if (arg_full) | |
618 | table_set_width(table, 0); | |
619 | ||
620 | (void) table_set_empty_string(table, "-"); | |
621 | ||
622 | if (n > 0) { | |
623 | for (t = timer_infos; t < timer_infos + n; t++) { | |
624 | _cleanup_free_ char *j = NULL, *activates = NULL; | |
625 | const char *unit; | |
626 | ||
627 | if (t->machine) { | |
628 | j = strjoin(t->machine, ":", t->id); | |
629 | if (!j) | |
630 | return log_oom(); | |
631 | unit = j; | |
632 | } else | |
633 | unit = t->id; | |
634 | ||
635 | activates = strv_join(t->triggered, ", "); | |
636 | if (!activates) | |
637 | return log_oom(); | |
638 | ||
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, | |
644 | TABLE_STRING, unit, | |
645 | TABLE_STRING, activates); | |
646 | if (r < 0) | |
647 | return table_log_add_error(r); | |
648 | } | |
649 | ||
650 | on = ansi_highlight(); | |
651 | off = ansi_normal(); | |
652 | } else { | |
653 | on = ansi_highlight_red(); | |
654 | off = ansi_normal(); | |
655 | } | |
656 | ||
657 | r = output_table(table); | |
658 | if (r < 0) | |
659 | return r; | |
660 | ||
661 | if (!arg_no_legend) { | |
662 | printf("\n%s%u timers listed.%s\n", on, n, off); | |
663 | if (!arg_all) | |
664 | printf("Pass --all to see loaded but inactive timers, too.\n"); | |
665 | } | |
666 | ||
667 | return 0; | |
668 | } | |
669 | ||
670 | usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { | |
671 | usec_t next_elapse; | |
672 | ||
673 | assert(nw); | |
674 | assert(next); | |
675 | ||
676 | if (timestamp_is_set(next->monotonic)) { | |
677 | usec_t converted; | |
678 | ||
679 | if (next->monotonic > nw->monotonic) | |
680 | converted = nw->realtime + (next->monotonic - nw->monotonic); | |
681 | else | |
682 | converted = nw->realtime - (nw->monotonic - next->monotonic); | |
683 | ||
684 | if (timestamp_is_set(next->realtime)) | |
685 | next_elapse = MIN(converted, next->realtime); | |
686 | else | |
687 | next_elapse = converted; | |
688 | ||
689 | } else | |
690 | next_elapse = next->realtime; | |
691 | ||
692 | return next_elapse; | |
693 | } | |
694 | ||
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; | |
702 | const UnitInfo *u; | |
703 | size_t size = 0; | |
704 | int n, c = 0; | |
705 | dual_timestamp nw; | |
706 | sd_bus *bus; | |
707 | int r; | |
708 | ||
709 | r = acquire_bus(BUS_MANAGER, &bus); | |
710 | if (r < 0) | |
711 | return r; | |
712 | ||
713 | (void) pager_open(arg_pager_flags); | |
714 | ||
715 | r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL); | |
716 | if (r < 0) | |
717 | return r; | |
718 | ||
719 | if (argc == 1 || timers_with_suffix) { | |
720 | n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies, &machines); | |
721 | if (n < 0) | |
722 | return n; | |
723 | ||
724 | dual_timestamp_get(&nw); | |
725 | ||
726 | for (u = unit_infos; u < unit_infos + n; u++) { | |
727 | _cleanup_strv_free_ char **triggered = NULL; | |
728 | dual_timestamp next = DUAL_TIMESTAMP_NULL; | |
729 | usec_t m, last = 0; | |
730 | ||
731 | if (!endswith(u->id, ".timer")) | |
732 | continue; | |
733 | ||
734 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
735 | if (r < 0) | |
736 | goto cleanup; | |
737 | ||
738 | r = get_next_elapse(bus, u->unit_path, &next); | |
739 | if (r < 0) | |
740 | goto cleanup; | |
741 | ||
742 | get_last_trigger(bus, u->unit_path, &last); | |
743 | ||
744 | if (!GREEDY_REALLOC(timer_infos, size, c+1)) { | |
745 | r = log_oom(); | |
746 | goto cleanup; | |
747 | } | |
748 | ||
749 | m = calc_next_elapse(&nw, &next); | |
750 | ||
751 | timer_infos[c++] = (struct timer_info) { | |
752 | .machine = u->machine, | |
753 | .id = u->id, | |
754 | .next_elapse = m, | |
755 | .last_trigger = last, | |
756 | .triggered = TAKE_PTR(triggered), | |
757 | }; | |
758 | } | |
759 | ||
760 | typesafe_qsort(timer_infos, c, timer_info_compare); | |
761 | } | |
762 | ||
763 | output_timers_list(timer_infos, c); | |
764 | ||
765 | cleanup: | |
766 | for (t = timer_infos; t < timer_infos + c; t++) | |
767 | strv_free(t->triggered); | |
768 | ||
769 | return r; | |
770 | } |