]>
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; | |
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 | ||
90 | static 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 | 212 | int 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 | ||
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; | |
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 | 427 | int 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 | ||
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 | ||
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 | ||
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 | ||
600 | static 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 | ||
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 | ||
32baf64d | 690 | int 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 | } |