]>
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; | |
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; | |
ca7b9e1e | 93 | unsigned job_count = 0; |
daf71ef6 LP |
94 | int r; |
95 | ||
96 | table = table_new("", "unit", "load", "active", "sub", "job", "description"); | |
97 | if (!table) | |
98 | return log_oom(); | |
99 | ||
6906da26 | 100 | table_set_header(table, arg_legend != 0); |
daf71ef6 LP |
101 | if (arg_plain) { |
102 | /* Hide the 'glyph' column when --plain is requested */ | |
103 | r = table_hide_column_from_display(table, 0); | |
104 | if (r < 0) | |
105 | return log_error_errno(r, "Failed to hide column: %m"); | |
106 | } | |
107 | if (arg_full) | |
108 | table_set_width(table, 0); | |
109 | ||
110 | (void) table_set_empty_string(table, "-"); | |
111 | ||
7ed7d3e6 | 112 | for (const UnitInfo *u = unit_infos; unit_infos && (size_t) (u - unit_infos) < c; u++) { |
daf71ef6 LP |
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 | ||
6906da26 | 180 | if (arg_legend != 0) { |
daf71ef6 LP |
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."); | |
5ba97fc0 YW |
189 | if (job_count > 0) |
190 | puts("JOB = Pending job for the unit.\n"); | |
daf71ef6 LP |
191 | on = ansi_highlight(); |
192 | off = ansi_normal(); | |
193 | } else { | |
194 | on = ansi_highlight_red(); | |
195 | off = ansi_normal(); | |
196 | } | |
197 | ||
198 | if (arg_all || strv_contains(arg_states, "inactive")) | |
199 | printf("%s%zu loaded units listed.%s\n" | |
200 | "To show all installed unit files use 'systemctl list-unit-files'.\n", | |
201 | on, records, off); | |
202 | else if (!arg_states) | |
203 | printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" | |
204 | "To show all installed unit files use 'systemctl list-unit-files'.\n", | |
205 | on, records, off); | |
206 | else | |
207 | printf("%zu loaded units listed.\n", records); | |
208 | } | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | int list_units(int argc, char *argv[], void *userdata) { | |
214 | _cleanup_free_ UnitInfo *unit_infos = NULL; | |
215 | _cleanup_(message_set_freep) Set *replies = NULL; | |
216 | _cleanup_strv_free_ char **machines = NULL; | |
217 | sd_bus *bus; | |
218 | int r; | |
219 | ||
220 | r = acquire_bus(BUS_MANAGER, &bus); | |
221 | if (r < 0) | |
222 | return r; | |
223 | ||
384c2c32 | 224 | pager_open(arg_pager_flags); |
daf71ef6 LP |
225 | |
226 | if (arg_with_dependencies) { | |
227 | _cleanup_strv_free_ char **names = NULL; | |
228 | ||
229 | r = append_unit_dependencies(bus, strv_skip(argv, 1), &names); | |
230 | if (r < 0) | |
231 | return r; | |
232 | ||
233 | r = get_unit_list_recursive(bus, names, &unit_infos, &replies, &machines); | |
234 | if (r < 0) | |
235 | return r; | |
236 | } else { | |
237 | r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); | |
238 | if (r < 0) | |
239 | return r; | |
240 | } | |
241 | ||
242 | typesafe_qsort(unit_infos, r, unit_info_compare); | |
243 | return output_units_list(unit_infos, r); | |
244 | } | |
245 | ||
246 | static int get_triggered_units( | |
247 | sd_bus *bus, | |
248 | const char* path, | |
249 | char*** ret) { | |
250 | ||
251 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
252 | int r; | |
253 | ||
254 | assert(bus); | |
255 | assert(path); | |
256 | assert(ret); | |
257 | ||
258 | r = sd_bus_get_property_strv( | |
259 | bus, | |
260 | "org.freedesktop.systemd1", | |
261 | path, | |
262 | "org.freedesktop.systemd1.Unit", | |
263 | "Triggers", | |
264 | &error, | |
265 | ret); | |
266 | if (r < 0) | |
267 | return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
272 | static int get_listening( | |
273 | sd_bus *bus, | |
274 | const char* unit_path, | |
275 | char*** listening) { | |
276 | ||
277 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
278 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
279 | const char *type, *path; | |
280 | int r, n = 0; | |
281 | ||
282 | r = sd_bus_get_property( | |
283 | bus, | |
284 | "org.freedesktop.systemd1", | |
285 | unit_path, | |
286 | "org.freedesktop.systemd1.Socket", | |
287 | "Listen", | |
288 | &error, | |
289 | &reply, | |
290 | "a(ss)"); | |
291 | if (r < 0) | |
292 | return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); | |
293 | ||
294 | r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); | |
295 | if (r < 0) | |
296 | return bus_log_parse_error(r); | |
297 | ||
298 | while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) { | |
299 | ||
300 | r = strv_extend(listening, type); | |
301 | if (r < 0) | |
302 | return log_oom(); | |
303 | ||
304 | r = strv_extend(listening, path); | |
305 | if (r < 0) | |
306 | return log_oom(); | |
307 | ||
308 | n++; | |
309 | } | |
310 | if (r < 0) | |
311 | return bus_log_parse_error(r); | |
312 | ||
313 | r = sd_bus_message_exit_container(reply); | |
314 | if (r < 0) | |
315 | return bus_log_parse_error(r); | |
316 | ||
317 | return n; | |
318 | } | |
319 | ||
320 | struct socket_info { | |
321 | const char *machine; | |
322 | const char* id; | |
323 | ||
324 | char* type; | |
325 | char* path; | |
326 | ||
327 | /* Note: triggered is a list here, although it almost certainly will always be one | |
328 | * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */ | |
329 | char** triggered; | |
330 | ||
331 | /* The strv above is shared. free is set only in the first one. */ | |
332 | bool own_triggered; | |
333 | }; | |
334 | ||
335 | static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) { | |
336 | int r; | |
337 | ||
338 | assert(a); | |
339 | assert(b); | |
340 | ||
341 | r = strcasecmp_ptr(a->machine, b->machine); | |
342 | if (r != 0) | |
343 | return r; | |
344 | ||
345 | r = strcmp(a->path, b->path); | |
346 | if (r != 0) | |
347 | return r; | |
348 | ||
349 | return strcmp(a->type, b->type); | |
350 | } | |
351 | ||
352 | static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { | |
353 | _cleanup_(table_unrefp) Table *table = NULL; | |
daf71ef6 LP |
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 */ | |
ef1e0b9a | 363 | r = table_set_display(table, (size_t) 0, (size_t) 2, (size_t) 3); |
daf71ef6 LP |
364 | if (r < 0) |
365 | return log_error_errno(r, "Failed to set columns to display: %m"); | |
366 | } | |
367 | ||
6906da26 | 368 | table_set_header(table, arg_legend != 0); |
daf71ef6 LP |
369 | if (arg_full) |
370 | table_set_width(table, 0); | |
371 | ||
372 | (void) table_set_empty_string(table, "-"); | |
373 | ||
374 | if (cs) { | |
deaf4b86 | 375 | for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) { |
daf71ef6 LP |
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 | ||
6906da26 | 419 | if (arg_legend != 0) { |
daf71ef6 LP |
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; | |
daf71ef6 | 434 | unsigned cs = 0; |
daf71ef6 LP |
435 | int r, n; |
436 | sd_bus *bus; | |
437 | ||
438 | r = acquire_bus(BUS_MANAGER, &bus); | |
439 | if (r < 0) | |
440 | return r; | |
441 | ||
384c2c32 | 442 | pager_open(arg_pager_flags); |
daf71ef6 LP |
443 | |
444 | r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL); | |
445 | if (r < 0) | |
446 | return r; | |
447 | ||
448 | if (argc == 1 || sockets_with_suffix) { | |
449 | n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies, &machines); | |
450 | if (n < 0) | |
451 | return n; | |
452 | ||
deaf4b86 | 453 | for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { |
daf71ef6 | 454 | _cleanup_strv_free_ char **listening = NULL, **triggered = NULL; |
deaf4b86 | 455 | int c; |
daf71ef6 LP |
456 | |
457 | if (!endswith(u->id, ".socket")) | |
458 | continue; | |
459 | ||
460 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
461 | if (r < 0) | |
462 | goto cleanup; | |
463 | ||
464 | c = get_listening(bus, u->unit_path, &listening); | |
465 | if (c < 0) { | |
466 | r = c; | |
467 | goto cleanup; | |
468 | } | |
469 | ||
319a4f4b | 470 | if (!GREEDY_REALLOC(socket_infos, cs + c)) { |
daf71ef6 LP |
471 | r = log_oom(); |
472 | goto cleanup; | |
473 | } | |
474 | ||
deaf4b86 | 475 | for (int i = 0; i < c; i++) |
daf71ef6 LP |
476 | socket_infos[cs + i] = (struct socket_info) { |
477 | .machine = u->machine, | |
478 | .id = u->id, | |
479 | .type = listening[i*2], | |
480 | .path = listening[i*2 + 1], | |
481 | .triggered = triggered, | |
482 | .own_triggered = i==0, | |
483 | }; | |
484 | ||
485 | /* from this point on we will cleanup those socket_infos */ | |
486 | cs += c; | |
487 | free(listening); | |
488 | listening = triggered = NULL; /* avoid cleanup */ | |
489 | } | |
490 | ||
491 | typesafe_qsort(socket_infos, cs, socket_info_compare); | |
492 | } | |
493 | ||
494 | output_sockets_list(socket_infos, cs); | |
495 | ||
496 | cleanup: | |
497 | assert(cs == 0 || socket_infos); | |
deaf4b86 | 498 | for (struct socket_info *s = socket_infos; s < socket_infos + cs; s++) { |
daf71ef6 LP |
499 | free(s->type); |
500 | free(s->path); | |
501 | if (s->own_triggered) | |
502 | strv_free(s->triggered); | |
503 | } | |
504 | ||
505 | return r; | |
506 | } | |
507 | ||
508 | static int get_next_elapse( | |
509 | sd_bus *bus, | |
510 | const char *path, | |
511 | dual_timestamp *next) { | |
512 | ||
513 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
514 | dual_timestamp t; | |
515 | int r; | |
516 | ||
517 | assert(bus); | |
518 | assert(path); | |
519 | assert(next); | |
520 | ||
521 | r = sd_bus_get_property_trivial( | |
522 | bus, | |
523 | "org.freedesktop.systemd1", | |
524 | path, | |
525 | "org.freedesktop.systemd1.Timer", | |
526 | "NextElapseUSecMonotonic", | |
527 | &error, | |
528 | 't', | |
529 | &t.monotonic); | |
530 | if (r < 0) | |
531 | return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r)); | |
532 | ||
533 | r = sd_bus_get_property_trivial( | |
534 | bus, | |
535 | "org.freedesktop.systemd1", | |
536 | path, | |
537 | "org.freedesktop.systemd1.Timer", | |
538 | "NextElapseUSecRealtime", | |
539 | &error, | |
540 | 't', | |
541 | &t.realtime); | |
542 | if (r < 0) | |
543 | return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r)); | |
544 | ||
545 | *next = t; | |
546 | return 0; | |
547 | } | |
548 | ||
549 | static int get_last_trigger( | |
550 | sd_bus *bus, | |
551 | const char *path, | |
552 | usec_t *last) { | |
553 | ||
554 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
555 | int r; | |
556 | ||
557 | assert(bus); | |
558 | assert(path); | |
559 | assert(last); | |
560 | ||
561 | r = sd_bus_get_property_trivial( | |
562 | bus, | |
563 | "org.freedesktop.systemd1", | |
564 | path, | |
565 | "org.freedesktop.systemd1.Timer", | |
566 | "LastTriggerUSec", | |
567 | &error, | |
568 | 't', | |
569 | last); | |
570 | if (r < 0) | |
571 | return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); | |
572 | ||
573 | return 0; | |
574 | } | |
575 | ||
576 | struct timer_info { | |
577 | const char* machine; | |
578 | const char* id; | |
579 | usec_t next_elapse; | |
580 | usec_t last_trigger; | |
581 | char** triggered; | |
582 | }; | |
583 | ||
584 | static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) { | |
585 | int r; | |
586 | ||
587 | assert(a); | |
588 | assert(b); | |
589 | ||
590 | r = strcasecmp_ptr(a->machine, b->machine); | |
591 | if (r != 0) | |
592 | return r; | |
593 | ||
594 | r = CMP(a->next_elapse, b->next_elapse); | |
595 | if (r != 0) | |
596 | return r; | |
597 | ||
598 | return strcmp(a->id, b->id); | |
599 | } | |
600 | ||
601 | static int output_timers_list(struct timer_info *timer_infos, unsigned n) { | |
602 | _cleanup_(table_unrefp) Table *table = NULL; | |
daf71ef6 LP |
603 | const char *on, *off; |
604 | int r; | |
605 | ||
606 | assert(timer_infos || n == 0); | |
607 | ||
608 | table = table_new("next", "left", "last", "passed", "unit", "activates"); | |
609 | if (!table) | |
610 | return log_oom(); | |
611 | ||
6906da26 | 612 | table_set_header(table, arg_legend != 0); |
daf71ef6 LP |
613 | if (arg_full) |
614 | table_set_width(table, 0); | |
615 | ||
616 | (void) table_set_empty_string(table, "-"); | |
617 | ||
deaf4b86 ZJS |
618 | for (struct timer_info *t = timer_infos; t < timer_infos + n; t++) { |
619 | _cleanup_free_ char *j = NULL, *activates = NULL; | |
620 | const char *unit; | |
daf71ef6 | 621 | |
deaf4b86 ZJS |
622 | if (t->machine) { |
623 | j = strjoin(t->machine, ":", t->id); | |
624 | if (!j) | |
daf71ef6 | 625 | return log_oom(); |
deaf4b86 ZJS |
626 | unit = j; |
627 | } else | |
628 | unit = t->id; | |
daf71ef6 | 629 | |
deaf4b86 ZJS |
630 | activates = strv_join(t->triggered, ", "); |
631 | if (!activates) | |
632 | return log_oom(); | |
daf71ef6 | 633 | |
deaf4b86 ZJS |
634 | r = table_add_many(table, |
635 | TABLE_TIMESTAMP, t->next_elapse, | |
636 | TABLE_TIMESTAMP_RELATIVE, t->next_elapse, | |
637 | TABLE_TIMESTAMP, t->last_trigger, | |
638 | TABLE_TIMESTAMP_RELATIVE, t->last_trigger, | |
639 | TABLE_STRING, unit, | |
640 | TABLE_STRING, activates); | |
641 | if (r < 0) | |
642 | return table_log_add_error(r); | |
643 | } | |
644 | ||
645 | if (n > 0) { | |
daf71ef6 LP |
646 | on = ansi_highlight(); |
647 | off = ansi_normal(); | |
648 | } else { | |
649 | on = ansi_highlight_red(); | |
650 | off = ansi_normal(); | |
651 | } | |
652 | ||
653 | r = output_table(table); | |
654 | if (r < 0) | |
655 | return r; | |
656 | ||
6906da26 | 657 | if (arg_legend != 0) { |
daf71ef6 LP |
658 | printf("\n%s%u timers listed.%s\n", on, n, off); |
659 | if (!arg_all) | |
660 | printf("Pass --all to see loaded but inactive timers, too.\n"); | |
661 | } | |
662 | ||
663 | return 0; | |
664 | } | |
665 | ||
666 | usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { | |
667 | usec_t next_elapse; | |
668 | ||
669 | assert(nw); | |
670 | assert(next); | |
671 | ||
672 | if (timestamp_is_set(next->monotonic)) { | |
673 | usec_t converted; | |
674 | ||
675 | if (next->monotonic > nw->monotonic) | |
676 | converted = nw->realtime + (next->monotonic - nw->monotonic); | |
677 | else | |
678 | converted = nw->realtime - (nw->monotonic - next->monotonic); | |
679 | ||
680 | if (timestamp_is_set(next->realtime)) | |
681 | next_elapse = MIN(converted, next->realtime); | |
682 | else | |
683 | next_elapse = converted; | |
684 | ||
685 | } else | |
686 | next_elapse = next->realtime; | |
687 | ||
688 | return next_elapse; | |
689 | } | |
690 | ||
691 | int list_timers(int argc, char *argv[], void *userdata) { | |
692 | _cleanup_(message_set_freep) Set *replies = NULL; | |
693 | _cleanup_strv_free_ char **machines = NULL; | |
694 | _cleanup_strv_free_ char **timers_with_suffix = NULL; | |
695 | _cleanup_free_ struct timer_info *timer_infos = NULL; | |
696 | _cleanup_free_ UnitInfo *unit_infos = NULL; | |
daf71ef6 LP |
697 | int n, c = 0; |
698 | dual_timestamp nw; | |
699 | sd_bus *bus; | |
700 | int r; | |
701 | ||
702 | r = acquire_bus(BUS_MANAGER, &bus); | |
703 | if (r < 0) | |
704 | return r; | |
705 | ||
384c2c32 | 706 | pager_open(arg_pager_flags); |
daf71ef6 LP |
707 | |
708 | r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL); | |
709 | if (r < 0) | |
710 | return r; | |
711 | ||
712 | if (argc == 1 || timers_with_suffix) { | |
713 | n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies, &machines); | |
714 | if (n < 0) | |
715 | return n; | |
716 | ||
717 | dual_timestamp_get(&nw); | |
718 | ||
deaf4b86 | 719 | for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) { |
daf71ef6 LP |
720 | _cleanup_strv_free_ char **triggered = NULL; |
721 | dual_timestamp next = DUAL_TIMESTAMP_NULL; | |
722 | usec_t m, last = 0; | |
723 | ||
724 | if (!endswith(u->id, ".timer")) | |
725 | continue; | |
726 | ||
727 | r = get_triggered_units(bus, u->unit_path, &triggered); | |
728 | if (r < 0) | |
729 | goto cleanup; | |
730 | ||
731 | r = get_next_elapse(bus, u->unit_path, &next); | |
732 | if (r < 0) | |
733 | goto cleanup; | |
734 | ||
735 | get_last_trigger(bus, u->unit_path, &last); | |
736 | ||
319a4f4b | 737 | if (!GREEDY_REALLOC(timer_infos, c+1)) { |
daf71ef6 LP |
738 | r = log_oom(); |
739 | goto cleanup; | |
740 | } | |
741 | ||
742 | m = calc_next_elapse(&nw, &next); | |
743 | ||
744 | timer_infos[c++] = (struct timer_info) { | |
745 | .machine = u->machine, | |
746 | .id = u->id, | |
747 | .next_elapse = m, | |
748 | .last_trigger = last, | |
749 | .triggered = TAKE_PTR(triggered), | |
750 | }; | |
751 | } | |
752 | ||
753 | typesafe_qsort(timer_infos, c, timer_info_compare); | |
754 | } | |
755 | ||
756 | output_timers_list(timer_infos, c); | |
757 | ||
758 | cleanup: | |
deaf4b86 | 759 | for (struct timer_info *t = timer_infos; t < timer_infos + c; t++) |
daf71ef6 LP |
760 | strv_free(t->triggered); |
761 | ||
762 | return r; | |
763 | } |