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