]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/systemctl/systemctl-list-units.c
systemctl: cleanups for list-timers
[thirdparty/systemd.git] / src / systemctl / systemctl-list-units.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
daf71ef6
LP
2
3#include "sd-login.h"
4
5#include "bus-error.h"
ed462ea3 6#include "bus-locator.h"
daf71ef6
LP
7#include "format-table.h"
8#include "locale-util.h"
5fb5f49b 9#include "path-util.h"
daf71ef6
LP
10#include "set.h"
11#include "sort-util.h"
12#include "systemctl-list-units.h"
13#include "systemctl-util.h"
14#include "systemctl.h"
15#include "terminal-util.h"
16
17static void message_set_freep(Set **set) {
18 set_free_with_destructor(*set, sd_bus_message_unref);
19}
20
21static int get_unit_list_recursive(
22 sd_bus *bus,
23 char **patterns,
24 UnitInfo **ret_unit_infos,
f2ccc0d3 25 Set **ret_replies) {
daf71ef6
LP
26
27 _cleanup_free_ UnitInfo *unit_infos = NULL;
c2b2df60 28 _cleanup_(message_set_freep) Set *replies = NULL;
eef96911 29 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
daf71ef6
LP
30 int c, r;
31
32 assert(bus);
33 assert(ret_replies);
34 assert(ret_unit_infos);
daf71ef6
LP
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);
eef96911 45 if (r < 0)
daf71ef6 46 return log_oom();
eef96911 47 TAKE_PTR(reply);
daf71ef6
LP
48
49 if (arg_recursive) {
50 _cleanup_strv_free_ char **machines = NULL;
daf71ef6
LP
51
52 r = sd_get_machine_names(&machines);
53 if (r < 0)
54 return log_error_errno(r, "Failed to get machine names: %m");
55
56 STRV_FOREACH(i, machines) {
57 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL;
58 int k;
59
60 r = sd_bus_open_system_machine(&container, *i);
61 if (r < 0) {
62 log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i);
63 continue;
64 }
65
66 k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply);
67 if (k < 0)
68 return k;
69
70 c = k;
71
72 r = set_put(replies, reply);
eef96911 73 if (r < 0)
daf71ef6 74 return log_oom();
eef96911 75 TAKE_PTR(reply);
daf71ef6 76 }
f2ccc0d3 77 }
daf71ef6
LP
78
79 *ret_unit_infos = TAKE_PTR(unit_infos);
80 *ret_replies = TAKE_PTR(replies);
81
82 return c;
83}
84
11d6270b
DT
85static void output_legend(const char *type, size_t n_items) {
86 const char *on, *off;
87
88 assert(type);
89
90 on = n_items > 0 ? ansi_highlight() : ansi_highlight_red();
91 off = ansi_normal();
92
93 printf("\n%s%zu %ss listed.%s\n", on, n_items, type, off);
94 if (!arg_all)
95 printf("Pass --all to see loaded but inactive %ss, too.\n", type);
96}
97
531a45f3
DT
98static int table_add_triggered(Table *table, char **triggered) {
99 assert(table);
100
101 if (strv_isempty(triggered))
102 return table_add_cell(table, NULL, TABLE_EMPTY, NULL);
103 else if (strv_length(triggered) == 1)
104 return table_add_cell(table, NULL, TABLE_STRING, triggered[0]);
105 else
106 /* This should never happen, currently our socket units can only trigger a
107 * single unit. But let's handle this anyway, who knows what the future
108 * brings? */
109 return table_add_cell(table, NULL, TABLE_STRV, triggered);
110}
111
ac140596
DT
112static char *format_unit_id(const char *unit, const char *machine) {
113 assert(unit);
114
115 return machine ? strjoin(machine, ":", unit) : strdup(unit);
116}
117
46e6449a 118static int output_units_list(const UnitInfo *unit_infos, size_t c) {
daf71ef6 119 _cleanup_(table_unrefp) Table *table = NULL;
46e6449a 120 size_t job_count = 0;
daf71ef6
LP
121 int r;
122
123 table = table_new("", "unit", "load", "active", "sub", "job", "description");
124 if (!table)
125 return log_oom();
126
6906da26 127 table_set_header(table, arg_legend != 0);
daf71ef6
LP
128 if (arg_plain) {
129 /* Hide the 'glyph' column when --plain is requested */
130 r = table_hide_column_from_display(table, 0);
131 if (r < 0)
132 return log_error_errno(r, "Failed to hide column: %m");
133 }
134 if (arg_full)
135 table_set_width(table, 0);
136
c8b62cf6 137 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
daf71ef6 138
7ed7d3e6 139 for (const UnitInfo *u = unit_infos; unit_infos && (size_t) (u - unit_infos) < c; u++) {
ac140596
DT
140 _cleanup_free_ char *id = NULL;
141 const char *on_underline = "", *on_loaded = "", *on_active = "", *on_circle = "";
daf71ef6
LP
142 bool circle = false, underline = false;
143
144 if (u + 1 < unit_infos + c &&
145 !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id))) {
146 on_underline = ansi_underline();
147 underline = true;
148 }
149
150 if (STR_IN_SET(u->load_state, "error", "not-found", "bad-setting", "masked") && !arg_plain) {
151 on_circle = underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
152 circle = true;
153 on_loaded = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
154 } else if (streq(u->active_state, "failed") && !arg_plain) {
155 on_circle = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
156 circle = true;
157 on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
158 } else {
159 on_circle = on_underline;
160 on_active = on_underline;
161 on_loaded = on_underline;
162 }
163
ac140596
DT
164 id = format_unit_id(u->id, u->machine);
165 if (!id)
166 return log_oom();
daf71ef6
LP
167
168 r = table_add_many(table,
169 TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ",
170 TABLE_SET_BOTH_COLORS, on_circle,
171 TABLE_STRING, id,
172 TABLE_SET_BOTH_COLORS, on_active,
173 TABLE_STRING, u->load_state,
174 TABLE_SET_BOTH_COLORS, on_loaded,
175 TABLE_STRING, u->active_state,
176 TABLE_SET_BOTH_COLORS, on_active,
177 TABLE_STRING, u->sub_state,
178 TABLE_SET_BOTH_COLORS, on_active,
179 TABLE_STRING, u->job_id ? u->job_type: "",
81039448 180 TABLE_SET_BOTH_COLORS, on_underline,
daf71ef6
LP
181 TABLE_STRING, u->description,
182 TABLE_SET_BOTH_COLORS, on_underline);
183 if (r < 0)
184 return table_log_add_error(r);
185
186 if (u->job_id != 0)
187 job_count++;
188 }
189
190 if (job_count == 0) {
191 /* There's no data in the JOB column, so let's hide it */
192 r = table_hide_column_from_display(table, 5);
193 if (r < 0)
194 return log_error_errno(r, "Failed to hide column: %m");
195 }
196
197 r = output_table(table);
198 if (r < 0)
199 return r;
200
6906da26 201 if (arg_legend != 0) {
daf71ef6
LP
202 const char *on, *off;
203 size_t records = table_get_rows(table) - 1;
204
205 if (records > 0) {
206 puts("\n"
207 "LOAD = Reflects whether the unit definition was properly loaded.\n"
208 "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n"
209 "SUB = The low-level unit activation state, values depend on unit type.");
5ba97fc0
YW
210 if (job_count > 0)
211 puts("JOB = Pending job for the unit.\n");
daf71ef6
LP
212 }
213
0f787940
DT
214 on = records > 0 ? ansi_highlight() : ansi_highlight_red();
215 off = ansi_normal();
216
daf71ef6
LP
217 if (arg_all || strv_contains(arg_states, "inactive"))
218 printf("%s%zu loaded units listed.%s\n"
219 "To show all installed unit files use 'systemctl list-unit-files'.\n",
220 on, records, off);
221 else if (!arg_states)
222 printf("%s%zu loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
223 "To show all installed unit files use 'systemctl list-unit-files'.\n",
224 on, records, off);
225 else
226 printf("%zu loaded units listed.\n", records);
227 }
228
229 return 0;
230}
231
32baf64d 232int verb_list_units(int argc, char *argv[], void *userdata) {
daf71ef6
LP
233 _cleanup_free_ UnitInfo *unit_infos = NULL;
234 _cleanup_(message_set_freep) Set *replies = NULL;
daf71ef6
LP
235 sd_bus *bus;
236 int r;
237
238 r = acquire_bus(BUS_MANAGER, &bus);
239 if (r < 0)
240 return r;
241
384c2c32 242 pager_open(arg_pager_flags);
daf71ef6
LP
243
244 if (arg_with_dependencies) {
245 _cleanup_strv_free_ char **names = NULL;
246
247 r = append_unit_dependencies(bus, strv_skip(argv, 1), &names);
248 if (r < 0)
249 return r;
250
f2ccc0d3 251 r = get_unit_list_recursive(bus, names, &unit_infos, &replies);
daf71ef6
LP
252 if (r < 0)
253 return r;
254 } else {
f2ccc0d3 255 r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies);
daf71ef6
LP
256 if (r < 0)
257 return r;
258 }
259
260 typesafe_qsort(unit_infos, r, unit_info_compare);
261 return output_units_list(unit_infos, r);
262}
263
264static int get_triggered_units(
265 sd_bus *bus,
266 const char* path,
267 char*** ret) {
268
269 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
270 int r;
271
272 assert(bus);
273 assert(path);
274 assert(ret);
275
276 r = sd_bus_get_property_strv(
277 bus,
278 "org.freedesktop.systemd1",
279 path,
280 "org.freedesktop.systemd1.Unit",
281 "Triggers",
282 &error,
283 ret);
284 if (r < 0)
285 return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r));
286
287 return 0;
288}
289
1b876066
YW
290typedef struct SocketInfo {
291 const char *machine;
292 const char* id;
293
294 char* type;
295 char* path; /* absolute path or socket address */
296
297 /* Note: triggered is a list here, although it almost certainly will always be one
298 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
299 char** triggered;
300} SocketInfo;
301
302static void socket_info_array_free(SocketInfo *sockets, size_t n_sockets) {
303 assert(sockets || n_sockets == 0);
304
305 for (SocketInfo *s = sockets; s < sockets + n_sockets; s++) {
306 free(s->type);
307 free(s->path);
308 strv_free(s->triggered);
309 }
310
311 free(sockets);
312}
313
314static int socket_info_compare(const SocketInfo *a, const SocketInfo *b) {
315 int r;
316
317 assert(a);
318 assert(b);
319
320 r = strcasecmp_ptr(a->machine, b->machine);
321 if (r != 0)
322 return r;
323
324 r = CMP(path_is_absolute(a->path), path_is_absolute(b->path));
325 if (r != 0)
326 return r;
327
328 r = path_is_absolute(a->path) ? path_compare(a->path, b->path) : strcmp(a->path, b->path);
329 if (r != 0)
330 return r;
331
332 return strcmp(a->type, b->type);
333}
334
335static int socket_info_add(
daf71ef6 336 sd_bus *bus,
1b876066
YW
337 const UnitInfo *u,
338 SocketInfo **sockets,
339 size_t *n_sockets) {
daf71ef6
LP
340
341 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
342 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1b876066 343 _cleanup_strv_free_ char **triggered = NULL;
daf71ef6 344 const char *type, *path;
1b876066
YW
345 int r;
346
347 assert(bus);
348 assert(u);
349 assert(sockets);
350 assert(n_sockets);
351
352 if (!endswith(u->id, ".socket"))
353 return 0;
354
355 r = get_triggered_units(bus, u->unit_path, &triggered);
356 if (r < 0)
357 return r;
daf71ef6
LP
358
359 r = sd_bus_get_property(
360 bus,
361 "org.freedesktop.systemd1",
1b876066 362 u->unit_path,
daf71ef6
LP
363 "org.freedesktop.systemd1.Socket",
364 "Listen",
365 &error,
366 &reply,
367 "a(ss)");
368 if (r < 0)
369 return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r));
370
371 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
372 if (r < 0)
373 return bus_log_parse_error(r);
374
375 while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) {
1b876066
YW
376 _cleanup_free_ char *type_dup = NULL, *path_dup = NULL;
377 _cleanup_strv_free_ char **triggered_dup = NULL;
daf71ef6 378
1b876066
YW
379 type_dup = strdup(type);
380 if (!type_dup)
daf71ef6
LP
381 return log_oom();
382
1b876066
YW
383 path_dup = strdup(path);
384 if (!path_dup)
385 return log_oom();
386
387 triggered_dup = strv_copy(triggered);
388 if (!triggered_dup)
daf71ef6
LP
389 return log_oom();
390
1b876066
YW
391 if (!GREEDY_REALLOC(*sockets, *n_sockets + 1))
392 return log_oom();
393
394 (*sockets)[(*n_sockets)++] = (SocketInfo) {
395 .machine = u->machine,
396 .id = u->id,
397 .type = TAKE_PTR(type_dup),
398 .path = TAKE_PTR(path_dup),
399 .triggered = TAKE_PTR(triggered_dup),
400 };
daf71ef6
LP
401 }
402 if (r < 0)
403 return bus_log_parse_error(r);
404
405 r = sd_bus_message_exit_container(reply);
406 if (r < 0)
407 return bus_log_parse_error(r);
408
1b876066 409 return 0;
daf71ef6
LP
410}
411
1b876066 412static int output_sockets_list(const SocketInfo *sockets, size_t n_sockets) {
daf71ef6 413 _cleanup_(table_unrefp) Table *table = NULL;
daf71ef6
LP
414 int r;
415
1b876066 416 assert(sockets || n_sockets == 0);
25facd03 417
daf71ef6
LP
418 table = table_new("listen", "type", "unit", "activates");
419 if (!table)
420 return log_oom();
421
422 if (!arg_show_types) {
423 /* Hide the second (TYPE) column */
ef1e0b9a 424 r = table_set_display(table, (size_t) 0, (size_t) 2, (size_t) 3);
daf71ef6
LP
425 if (r < 0)
426 return log_error_errno(r, "Failed to set columns to display: %m");
427 }
428
6906da26 429 table_set_header(table, arg_legend != 0);
daf71ef6
LP
430 if (arg_full)
431 table_set_width(table, 0);
432
c8b62cf6 433 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
daf71ef6 434
1b876066 435 for (const SocketInfo *s = sockets; s < sockets + n_sockets; s++) {
f748b2d0 436 _cleanup_free_ char *unit = NULL;
25facd03 437
f748b2d0
DT
438 unit = format_unit_id(s->id, s->machine);
439 if (!unit)
440 return log_oom();
25facd03
DT
441
442 r = table_add_many(table,
1b876066
YW
443 TABLE_STRING, s->path,
444 TABLE_STRING, s->type,
445 TABLE_STRING, unit);
25facd03
DT
446 if (r < 0)
447 return table_log_add_error(r);
448
531a45f3 449 r = table_add_triggered(table, s->triggered);
25facd03
DT
450 if (r < 0)
451 return table_log_add_error(r);
daf71ef6
LP
452 }
453
454 r = output_table(table);
455 if (r < 0)
456 return r;
457
11d6270b 458 if (arg_legend != 0)
1b876066 459 output_legend("socket", n_sockets);
daf71ef6
LP
460
461 return 0;
462}
463
32baf64d 464int verb_list_sockets(int argc, char *argv[], void *userdata) {
daf71ef6 465 _cleanup_(message_set_freep) Set *replies = NULL;
daf71ef6
LP
466 _cleanup_strv_free_ char **sockets_with_suffix = NULL;
467 _cleanup_free_ UnitInfo *unit_infos = NULL;
1b876066
YW
468 SocketInfo *sockets = NULL;
469 size_t n_sockets = 0;
daf71ef6 470 sd_bus *bus;
1b876066
YW
471 int r;
472
473 CLEANUP_ARRAY(sockets, n_sockets, socket_info_array_free);
daf71ef6
LP
474
475 r = acquire_bus(BUS_MANAGER, &bus);
476 if (r < 0)
477 return r;
478
384c2c32 479 pager_open(arg_pager_flags);
daf71ef6
LP
480
481 r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL);
482 if (r < 0)
483 return r;
484
485 if (argc == 1 || sockets_with_suffix) {
1b876066
YW
486 int n;
487
f2ccc0d3 488 n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies);
daf71ef6
LP
489 if (n < 0)
490 return n;
491
deaf4b86 492 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
1b876066 493 r = socket_info_add(bus, u, &sockets, &n_sockets);
daf71ef6 494 if (r < 0)
1b876066 495 return r;
daf71ef6 496 }
daf71ef6
LP
497 }
498
1b876066
YW
499 typesafe_qsort(sockets, n_sockets, socket_info_compare);
500 output_sockets_list(sockets, n_sockets);
daf71ef6 501
1b876066 502 return 0;
daf71ef6
LP
503}
504
505static int get_next_elapse(
506 sd_bus *bus,
507 const char *path,
508 dual_timestamp *next) {
509
510 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
511 dual_timestamp t;
512 int r;
513
514 assert(bus);
515 assert(path);
516 assert(next);
517
518 r = sd_bus_get_property_trivial(
519 bus,
520 "org.freedesktop.systemd1",
521 path,
522 "org.freedesktop.systemd1.Timer",
523 "NextElapseUSecMonotonic",
524 &error,
525 't',
526 &t.monotonic);
527 if (r < 0)
528 return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
529
530 r = sd_bus_get_property_trivial(
531 bus,
532 "org.freedesktop.systemd1",
533 path,
534 "org.freedesktop.systemd1.Timer",
535 "NextElapseUSecRealtime",
536 &error,
537 't',
538 &t.realtime);
539 if (r < 0)
540 return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
541
542 *next = t;
543 return 0;
544}
545
546static int get_last_trigger(
547 sd_bus *bus,
548 const char *path,
549 usec_t *last) {
550
551 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
552 int r;
553
554 assert(bus);
555 assert(path);
556 assert(last);
557
558 r = sd_bus_get_property_trivial(
559 bus,
560 "org.freedesktop.systemd1",
561 path,
562 "org.freedesktop.systemd1.Timer",
563 "LastTriggerUSec",
564 &error,
565 't',
566 last);
567 if (r < 0)
568 return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r));
569
570 return 0;
571}
572
0e351e9a 573typedef struct TimerInfo {
daf71ef6
LP
574 const char* machine;
575 const char* id;
576 usec_t next_elapse;
577 usec_t last_trigger;
0e351e9a
YW
578 char **triggered;
579} TimerInfo;
580
581static void timer_info_array_free(TimerInfo *timers, size_t n_timers) {
582 assert(timers || n_timers == 0);
583
584 for (TimerInfo *t = timers; t < timers + n_timers; t++)
585 strv_free(t->triggered);
586
587 free(timers);
588}
daf71ef6 589
0e351e9a 590static int timer_info_compare(const TimerInfo *a, const TimerInfo *b) {
daf71ef6
LP
591 int r;
592
593 assert(a);
594 assert(b);
595
596 r = strcasecmp_ptr(a->machine, b->machine);
597 if (r != 0)
598 return r;
599
600 r = CMP(a->next_elapse, b->next_elapse);
601 if (r != 0)
602 return r;
603
604 return strcmp(a->id, b->id);
605}
606
0e351e9a 607static int output_timers_list(const TimerInfo *timers, size_t n_timers) {
daf71ef6 608 _cleanup_(table_unrefp) Table *table = NULL;
daf71ef6
LP
609 int r;
610
0e351e9a 611 assert(timers || n_timers == 0);
daf71ef6
LP
612
613 table = table_new("next", "left", "last", "passed", "unit", "activates");
614 if (!table)
615 return log_oom();
616
6906da26 617 table_set_header(table, arg_legend != 0);
daf71ef6
LP
618 if (arg_full)
619 table_set_width(table, 0);
620
c8b62cf6 621 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
daf71ef6 622
f168919d
LP
623 (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
624 (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
625
0e351e9a 626 for (const TimerInfo *t = timers; t < timers + n_timers; t++) {
ac140596 627 _cleanup_free_ char *unit = NULL;
daf71ef6 628
ac140596
DT
629 unit = format_unit_id(t->id, t->machine);
630 if (!unit)
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,
f4384e19
DT
638 TABLE_STRING, unit);
639 if (r < 0)
640 return table_log_add_error(r);
641
642 r = table_add_triggered(table, t->triggered);
deaf4b86
ZJS
643 if (r < 0)
644 return table_log_add_error(r);
645 }
646
daf71ef6
LP
647 r = output_table(table);
648 if (r < 0)
649 return r;
650
11d6270b 651 if (arg_legend != 0)
0e351e9a 652 output_legend("timer", n_timers);
daf71ef6
LP
653
654 return 0;
655}
656
0e351e9a 657usec_t calc_next_elapse(const dual_timestamp *nw, const dual_timestamp *next) {
daf71ef6
LP
658 usec_t next_elapse;
659
660 assert(nw);
661 assert(next);
662
663 if (timestamp_is_set(next->monotonic)) {
664 usec_t converted;
665
666 if (next->monotonic > nw->monotonic)
667 converted = nw->realtime + (next->monotonic - nw->monotonic);
668 else
669 converted = nw->realtime - (nw->monotonic - next->monotonic);
670
671 if (timestamp_is_set(next->realtime))
672 next_elapse = MIN(converted, next->realtime);
673 else
674 next_elapse = converted;
675
676 } else
677 next_elapse = next->realtime;
678
679 return next_elapse;
680}
681
0e351e9a
YW
682static int add_timer_info(
683 sd_bus *bus,
684 const UnitInfo *u,
685 const dual_timestamp *nw,
686 TimerInfo **timers,
687 size_t *n_timers) {
688
689 _cleanup_strv_free_ char **triggered = NULL;
690 dual_timestamp next = DUAL_TIMESTAMP_NULL;
691 usec_t m, last = 0;
692 int r;
693
694 assert(bus);
695 assert(u);
696 assert(nw);
697 assert(timers);
698 assert(n_timers);
699
700 if (!endswith(u->id, ".timer"))
701 return 0;
702
703 r = get_triggered_units(bus, u->unit_path, &triggered);
704 if (r < 0)
705 return r;
706
707 r = get_next_elapse(bus, u->unit_path, &next);
708 if (r < 0)
709 return r;
710
711 r = get_last_trigger(bus, u->unit_path, &last);
712 if (r < 0)
713 return r;
714
715 m = calc_next_elapse(nw, &next);
716
717 if (!GREEDY_REALLOC(*timers, *n_timers + 1))
718 return log_oom();
719
720 (*timers)[(*n_timers)++] = (TimerInfo) {
721 .machine = u->machine,
722 .id = u->id,
723 .next_elapse = m,
724 .last_trigger = last,
725 .triggered = TAKE_PTR(triggered),
726 };
727
728 return 0;
729}
730
32baf64d 731int verb_list_timers(int argc, char *argv[], void *userdata) {
daf71ef6 732 _cleanup_(message_set_freep) Set *replies = NULL;
daf71ef6 733 _cleanup_strv_free_ char **timers_with_suffix = NULL;
daf71ef6 734 _cleanup_free_ UnitInfo *unit_infos = NULL;
0e351e9a
YW
735 TimerInfo *timers = NULL;
736 size_t n_timers = 0;
daf71ef6 737 sd_bus *bus;
0e351e9a
YW
738 int r;
739
740 CLEANUP_ARRAY(timers, n_timers, timer_info_array_free);
daf71ef6
LP
741
742 r = acquire_bus(BUS_MANAGER, &bus);
743 if (r < 0)
744 return r;
745
384c2c32 746 pager_open(arg_pager_flags);
daf71ef6
LP
747
748 r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL);
749 if (r < 0)
750 return r;
751
752 if (argc == 1 || timers_with_suffix) {
0e351e9a
YW
753 dual_timestamp nw;
754 int n;
755
f2ccc0d3 756 n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies);
daf71ef6
LP
757 if (n < 0)
758 return n;
759
760 dual_timestamp_get(&nw);
761
deaf4b86 762 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
0e351e9a 763 r = add_timer_info(bus, u, &nw, &timers, &n_timers);
daf71ef6 764 if (r < 0)
0e351e9a 765 return r;
daf71ef6 766 }
daf71ef6
LP
767 }
768
0e351e9a
YW
769 typesafe_qsort(timers, n_timers, timer_info_compare);
770 output_timers_list(timers, n_timers);
daf71ef6 771
0e351e9a 772 return 0;
daf71ef6 773}
ed462ea3
DT
774
775struct automount_info {
776 const char *machine;
777 const char *id;
778 char *what;
779 char *where;
780 usec_t timeout_idle_usec;
781 bool mounted;
782};
783
784static int automount_info_compare(const struct automount_info *a, const struct automount_info *b) {
785 int r;
786
787 assert(a);
788 assert(b);
789
790 r = strcasecmp_ptr(a->machine, b->machine);
791 if (r != 0)
792 return r;
793
794 return strcmp(a->where, b->where);
795}
796
797static int collect_automount_info(sd_bus* bus, const UnitInfo* info, struct automount_info *ret_info) {
798 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
799 _cleanup_free_ char *mount = NULL, *mount_path = NULL, *where = NULL, *what = NULL, *state = NULL;
976baf26 800 uint64_t timeout_idle_usec;
ed462ea3
DT
801 BusLocator locator;
802 int r;
803
804 assert(bus);
805 assert(info);
806 assert(ret_info);
807
808 locator = (BusLocator) {
809 .destination = "org.freedesktop.systemd1",
810 .path = info->unit_path,
811 .interface = "org.freedesktop.systemd1.Automount",
812 };
813
814 r = bus_get_property_string(bus, &locator, "Where", &error, &where);
815 if (r < 0)
816 return log_error_errno(r, "Failed to get automount target: %s", bus_error_message(&error, r));
817
818 r = bus_get_property_trivial(bus, &locator, "TimeoutIdleUSec", &error, 't', &timeout_idle_usec);
819 if (r < 0)
820 return log_error_errno(r, "Failed to get idle timeout: %s", bus_error_message(&error, r));
821
822 r = unit_name_from_path(where, ".mount", &mount);
823 if (r < 0)
824 return log_error_errno(r, "Failed to generate unit name from path: %m");
825
826 mount_path = unit_dbus_path_from_name(mount);
827 if (!mount_path)
828 return log_oom();
829
830 locator.path = mount_path;
831 locator.interface = "org.freedesktop.systemd1.Mount";
832
833 r = bus_get_property_string(bus, &locator, "What", &error, &what);
834 if (r < 0)
835 return log_error_errno(r, "Failed to get mount source: %s", bus_error_message(&error, r));
836
837 locator.interface = "org.freedesktop.systemd1.Unit";
838
839 r = bus_get_property_string(bus, &locator, "ActiveState", &error, &state);
840 if (r < 0)
841 return log_error_errno(r, "Failed to get mount state: %s", bus_error_message(&error, r));
842
843 *ret_info = (struct automount_info) {
844 .machine = info->machine,
845 .id = info->id,
846 .what = TAKE_PTR(what),
847 .where = TAKE_PTR(where),
848 .timeout_idle_usec = timeout_idle_usec,
849 .mounted = streq_ptr(state, "active"),
850 };
851
852 return 0;
853}
854
855static int output_automounts_list(struct automount_info *infos, size_t n_infos) {
856 _cleanup_(table_unrefp) Table *table = NULL;
857 int r;
858
859 assert(infos || n_infos == 0);
860
861 table = table_new("what", "where", "mounted", "idle timeout", "unit");
862 if (!table)
863 return log_oom();
864
865 table_set_header(table, arg_legend != 0);
866 if (arg_full)
867 table_set_width(table, 0);
868
c8b62cf6 869 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
ed462ea3
DT
870
871 for (struct automount_info *info = infos; info < infos + n_infos; info++) {
ac140596 872 _cleanup_free_ char *unit = NULL;
ed462ea3 873
ac140596
DT
874 unit = format_unit_id(info->id, info->machine);
875 if (!unit)
876 return log_oom();
ed462ea3
DT
877
878 r = table_add_many(table,
879 TABLE_STRING, info->what,
880 TABLE_STRING, info->where,
21ae8c17
LP
881 TABLE_BOOLEAN, info->mounted);
882 if (r < 0)
883 return table_log_add_error(r);
884
885 if (timestamp_is_set(info->timeout_idle_usec))
886 r = table_add_cell(table, NULL, TABLE_TIMESPAN_MSEC, &info->timeout_idle_usec);
887 else
888 r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
889 if (r < 0)
890 return table_log_add_error(r);
891
892 r = table_add_cell(table, NULL, TABLE_STRING, unit);
ed462ea3
DT
893 if (r < 0)
894 return table_log_add_error(r);
895 }
896
897 r = output_table(table);
898 if (r < 0)
899 return r;
900
901 if (arg_legend != 0)
902 output_legend("automount", n_infos);
903
904 return 0;
905}
906
907int verb_list_automounts(int argc, char *argv[], void *userdata) {
908 _cleanup_(message_set_freep) Set *replies = NULL;
f2ccc0d3 909 _cleanup_strv_free_ char **automounts = NULL;
ed462ea3
DT
910 _cleanup_free_ UnitInfo *unit_infos = NULL;
911 _cleanup_free_ struct automount_info *automount_infos = NULL;
912 size_t c = 0;
913 int r, n;
914 sd_bus *bus;
915
916 r = acquire_bus(BUS_MANAGER, &bus);
917 if (r < 0)
918 return r;
919
920 pager_open(arg_pager_flags);
921
922 r = expand_unit_names(bus, strv_skip(argv, 1), ".automount", &automounts, NULL);
923 if (r < 0)
924 return r;
925
926 if (argc == 1 || automounts) {
f2ccc0d3 927 n = get_unit_list_recursive(bus, automounts, &unit_infos, &replies);
ed462ea3
DT
928 if (n < 0)
929 return n;
930
931 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
932 if (!endswith(u->id, ".automount"))
933 continue;
934
935 if (!GREEDY_REALLOC(automount_infos, c + 1)) {
936 r = log_oom();
937 goto cleanup;
938 }
939
940 r = collect_automount_info(bus, u, &automount_infos[c]);
941 if (r < 0)
942 goto cleanup;
943
944 c++;
945 }
946
947 typesafe_qsort(automount_infos, c, automount_info_compare);
948 }
949
950 output_automounts_list(automount_infos, c);
951
952 cleanup:
953 assert(c == 0 || automount_infos);
954 for (struct automount_info *info = automount_infos; info < automount_infos + c; info++) {
955 free(info->what);
956 free(info->where);
957 }
958
959 return r;
960}
5fb5f49b
DT
961
962struct path_info {
963 const char *machine;
964 const char *id;
965
966 char *path;
967 char *condition;
968
969 /* Note: triggered is a list here, although it almost certainly will always be one
970 * unit. Nevertheless, dbus API allows for multiple values, so let's follow that. */
971 char** triggered;
972};
973
974struct path_infos {
975 size_t count;
976 struct path_info *items;
977};
978
979static int path_info_compare(const struct path_info *a, const struct path_info *b) {
980 int r;
981
982 assert(a);
983 assert(b);
984
985 r = strcasecmp_ptr(a->machine, b->machine);
986 if (r != 0)
987 return r;
988
989 r = path_compare(a->path, b->path);
990 if (r != 0)
991 return r;
992
993 r = strcmp(a->condition, b->condition);
994 if (r != 0)
995 return r;
996
997 return strcasecmp_ptr(a->id, b->id);
998}
999
1000static void path_infos_done(struct path_infos *ps) {
1001 assert(ps);
1002 assert(ps->items || ps->count == 0);
1003
1004 for (struct path_info *p = ps->items; p < ps->items + ps->count; p++) {
1005 free(p->condition);
1006 free(p->path);
1007 strv_free(p->triggered);
1008 }
1009
1010 free(ps->items);
1011}
1012
1013static int get_paths(sd_bus *bus, const char *unit_path, char ***ret_conditions, char ***ret_paths) {
1014 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1015 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1016 _cleanup_strv_free_ char **conditions = NULL, **paths = NULL;
1017 const char *condition, *path;
1018 int r, n = 0;
1019
1020 assert(bus);
1021 assert(unit_path);
1022 assert(ret_conditions);
1023 assert(ret_paths);
1024
1025 r = sd_bus_get_property(bus,
1026 "org.freedesktop.systemd1",
1027 unit_path,
1028 "org.freedesktop.systemd1.Path",
1029 "Paths",
1030 &error,
1031 &reply,
1032 "a(ss)");
1033 if (r < 0)
1034 return log_error_errno(r, "Failed to get paths: %s", bus_error_message(&error, r));
1035
1036 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)");
1037 if (r < 0)
1038 return bus_log_parse_error(r);
1039
1040 while ((r = sd_bus_message_read(reply, "(ss)", &condition, &path)) > 0) {
1041 if (strv_extend(&conditions, condition) < 0)
1042 return log_oom();
1043
1044 if (strv_extend(&paths, path) < 0)
1045 return log_oom();
1046
1047 n++;
1048 }
1049 if (r < 0)
1050 return bus_log_parse_error(r);
1051
1052 r = sd_bus_message_exit_container(reply);
1053 if (r < 0)
1054 return bus_log_parse_error(r);
1055
1056 *ret_conditions = TAKE_PTR(conditions);
1057 *ret_paths = TAKE_PTR(paths);
1058
1059 return n;
1060}
1061
1062static int output_paths_list(struct path_infos *ps) {
1063 _cleanup_(table_unrefp) Table *table = NULL;
1064 int r;
1065
1066 assert(ps);
1067 assert(ps->items || ps->count == 0);
1068
1069 table = table_new("path", "condition", "unit", "activates");
1070 if (!table)
1071 return log_oom();
1072
1073 table_set_header(table, arg_legend != 0);
1074 if (arg_full)
1075 table_set_width(table, 0);
1076
1077 table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
1078
1079 for (struct path_info *p = ps->items; p < ps->items + ps->count; p++) {
1080 _cleanup_free_ char *unit = NULL;
1081
1082 unit = format_unit_id(p->id, p->machine);
1083 if (!unit)
1084 return log_oom();
1085
1086 r = table_add_many(table,
1087 TABLE_STRING, p->path,
1088 TABLE_STRING, p->condition,
1089 TABLE_STRING, unit);
1090 if (r < 0)
1091 return table_log_add_error(r);
1092
1093 r = table_add_triggered(table, p->triggered);
1094 if (r < 0)
1095 return table_log_add_error(r);
1096 }
1097
1098 r = output_table(table);
1099 if (r < 0)
1100 return r;
1101
1102 if (arg_legend != 0)
1103 output_legend("path", ps->count);
1104
1105 return 0;
1106}
1107
1108int verb_list_paths(int argc, char *argv[], void *userdata) {
1109 _cleanup_(message_set_freep) Set *replies = NULL;
f2ccc0d3 1110 _cleanup_strv_free_ char **units = NULL;
5fb5f49b
DT
1111 _cleanup_free_ UnitInfo *unit_infos = NULL;
1112 _cleanup_(path_infos_done) struct path_infos path_infos = {};
1113 int r, n;
1114 sd_bus *bus;
1115
1116 r = acquire_bus(BUS_MANAGER, &bus);
1117 if (r < 0)
1118 return r;
1119
1120 pager_open(arg_pager_flags);
1121
1122 r = expand_unit_names(bus, strv_skip(argv, 1), ".path", &units, NULL);
1123 if (r < 0)
1124 return r;
1125
1126 if (argc == 1 || units) {
f2ccc0d3 1127 n = get_unit_list_recursive(bus, units, &unit_infos, &replies);
5fb5f49b
DT
1128 if (n < 0)
1129 return n;
1130
1131 for (const UnitInfo *u = unit_infos; u < unit_infos + n; u++) {
1132 _cleanup_strv_free_ char **conditions = NULL, **paths = NULL, **triggered = NULL;
1133 int c;
1134
1135 if (!endswith(u->id, ".path"))
1136 continue;
1137
1138 r = get_triggered_units(bus, u->unit_path, &triggered);
1139 if (r < 0)
1140 return r;
1141
1142 c = get_paths(bus, u->unit_path, &conditions, &paths);
1143 if (c < 0)
1144 return c;
1145
1146 if (!GREEDY_REALLOC(path_infos.items, path_infos.count + c))
1147 return log_oom();
1148
1149 for (int i = c - 1; i >= 0; i--) {
1150 char **t;
1151
1152 t = strv_copy(triggered);
1153 if (!t)
1154 return log_oom();
1155
1156 path_infos.items[path_infos.count++] = (struct path_info) {
1157 .machine = u->machine,
1158 .id = u->id,
1159 .condition = TAKE_PTR(conditions[i]),
1160 .path = TAKE_PTR(paths[i]),
1161 .triggered = t,
1162 };
1163 }
1164 }
1165
1166 typesafe_qsort(path_infos.items, path_infos.count, path_info_compare);
1167 }
1168
1169 output_paths_list(&path_infos);
1170
1171 return 0;
1172}