• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 20684862027

03 Jan 2026 10:26PM UTC coverage: 72.702% (+0.03%) from 72.677%
20684862027

push

github

web-flow
core/dynamic-user: two trivial modernizations (#40264)

2 of 4 new or added lines in 1 file covered. (50.0%)

215 existing lines in 37 files now uncovered.

310139 of 426587 relevant lines covered (72.7%)

1143601.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.02
/src/systemctl/systemctl-list-units.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include "sd-bus.h"
4
#include "sd-login.h"
5

6
#include "alloc-util.h"
7
#include "ansi-color.h"
8
#include "bus-error.h"
9
#include "bus-locator.h"
10
#include "bus-message-util.h"
11
#include "bus-unit-util.h"
12
#include "bus-util.h"
13
#include "format-table.h"
14
#include "glyph-util.h"
15
#include "path-util.h"
16
#include "set.h"
17
#include "sort-util.h"
18
#include "string-util.h"
19
#include "strv.h"
20
#include "systemctl.h"
21
#include "systemctl-list-units.h"
22
#include "systemctl-util.h"
23
#include "unit-def.h"
24
#include "unit-name.h"
25

26
static int get_unit_list_recursive(
75✔
27
                sd_bus *bus,
28
                char **patterns,
29
                UnitInfo **ret_unit_infos,
30
                Set **ret_replies) {
31

32
        _cleanup_free_ UnitInfo *unit_infos = NULL;
75✔
33
        _cleanup_set_free_ Set *replies = NULL;
×
34
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
75✔
35
        int c, r;
75✔
36

37
        assert(bus);
75✔
38
        assert(ret_replies);
75✔
39
        assert(ret_unit_infos);
75✔
40

41
        c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply);
75✔
42
        if (c < 0)
75✔
43
                return c;
44

45
        r = set_ensure_consume(&replies, &bus_message_hash_ops, TAKE_PTR(reply));
75✔
46
        if (r < 0)
75✔
47
                return log_oom();
×
48

49
        if (arg_recursive) {
75✔
50
                _cleanup_strv_free_ char **machines = NULL;
1✔
51

52
                r = sd_get_machine_names(&machines);
1✔
53
                if (r < 0)
1✔
54
                        return log_error_errno(r, "Failed to get machine names: %m");
×
55

56
                STRV_FOREACH(i, machines) {
1✔
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_consume(replies, TAKE_PTR(reply));
×
73
                        if (r < 0)
×
74
                                return log_oom();
×
75
                }
76
        }
77

78
        *ret_unit_infos = TAKE_PTR(unit_infos);
75✔
79
        *ret_replies = TAKE_PTR(replies);
75✔
80

81
        return c;
75✔
82
}
83

84
static void output_legend(const char *type, size_t n_items) {
5✔
85
        const char *on, *off;
5✔
86

87
        assert(type);
5✔
88

89
        on = n_items > 0 ? ansi_highlight() : ansi_highlight_red();
5✔
90
        off = ansi_normal();
5✔
91

92
        printf("\n%s%zu %ss listed.%s\n", on, n_items, type, off);
5✔
93
        if (!arg_all)
5✔
94
                printf("Pass --all to see loaded but inactive %ss, too.\n", type);
4✔
95
}
5✔
96

97
static int table_add_triggered(Table *table, char **triggered) {
92✔
98
        assert(table);
92✔
99

100
        if (strv_isempty(triggered))
92✔
101
                return table_add_cell(table, NULL, TABLE_EMPTY, NULL);
33✔
102
        else if (strv_length(triggered) == 1)
59✔
103
                return table_add_cell(table, NULL, TABLE_STRING, triggered[0]);
59✔
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

111
static char *format_unit_id(const char *unit, const char *machine) {
8,377✔
112
        assert(unit);
8,377✔
113

114
        return machine ? strjoin(machine, ":", unit) : strdup(unit);
8,377✔
115
}
116

117
static int output_units_list(const UnitInfo *unit_infos, size_t c) {
68✔
118
        _cleanup_(table_unrefp) Table *table = NULL;
68✔
119
        size_t job_count = 0;
68✔
120
        int r;
68✔
121

122
        table = table_new("", "unit", "load", "active", "sub", "job", "description");
68✔
123
        if (!table)
68✔
124
                return log_oom();
×
125

126
        table_set_header(table, arg_legend != 0);
68✔
127
        if (arg_plain) {
68✔
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)
68✔
134
                table_set_width(table, 0);
×
135

136
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
68✔
137

138
        FOREACH_ARRAY(u, unit_infos, c) {
8,353✔
139
                const char *on_loaded = NULL, *on_active = NULL, *on_sub = NULL, *on_circle = NULL;
8,285✔
140
                _cleanup_free_ char *id = NULL;
8,285✔
141
                bool circle = false, underline;
8,285✔
142

143
                underline = u + 1 < unit_infos + c && !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id));
8,285✔
144

145
                if (streq(u->load_state, "not-found")) {
8,285✔
146
                        on_circle = on_loaded = ansi_highlight_yellow();
×
147
                        circle = true;
148
                } else if (STR_IN_SET(u->load_state, "bad-setting", "error", "masked")) {
8,285✔
149
                        on_loaded = ansi_highlight_red();
×
150
                        on_circle = ansi_highlight_yellow();
×
151
                        circle = true;
152
                }
153

154
                if (streq(u->active_state, "failed")) {
8,285✔
155
                        on_sub = on_active = ansi_highlight_red();
2✔
156

157
                        /* Here override any load_state highlighting */
158
                        on_circle = ansi_highlight_red();
2✔
159
                        circle = true;
160
                } else if (STR_IN_SET(u->active_state, "reloading", "activating", "maintenance", "refreshing", "deactivating")) {
8,283✔
161
                        on_sub = on_active = ansi_highlight();
8✔
162

163
                        if (!circle) { /* Here we let load_state highlighting win */
8✔
164
                                on_circle = ansi_highlight();
8✔
165
                                circle = true;
166
                        }
167
                } else if (streq(u->active_state, "inactive"))
8,275✔
168
                        on_sub = on_active = ansi_grey();
552✔
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"))
8,285✔
173
                        on_sub = ansi_grey();
113✔
174

175
                if (arg_plain)
8,285✔
176
                        circle = false;
×
177

178
                id = format_unit_id(u->id, u->machine);
8,285✔
179
                if (!id)
8,285✔
180
                        return log_oom();
×
181

182
                r = table_add_many(table,
8,295✔
183
                                   TABLE_STRING, circle ? glyph(GLYPH_BLACK_CIRCLE) : " ",
184
                                   TABLE_SET_COLOR, on_circle,
185
                                   TABLE_SET_BOTH_UNDERLINES, underline,
186
                                   TABLE_STRING, id,
187
                                   TABLE_SET_COLOR, on_active,
188
                                   TABLE_SET_BOTH_UNDERLINES, underline,
189
                                   TABLE_STRING, u->load_state,
190
                                   TABLE_SET_COLOR, on_loaded,
191
                                   TABLE_SET_BOTH_UNDERLINES, underline,
192
                                   TABLE_STRING, u->active_state,
193
                                   TABLE_SET_COLOR, on_active,
194
                                   TABLE_SET_BOTH_UNDERLINES, underline,
195
                                   TABLE_STRING, u->sub_state,
196
                                   TABLE_SET_COLOR, on_sub,
197
                                   TABLE_SET_BOTH_UNDERLINES, underline,
198
                                   TABLE_STRING, u->job_id ? u->job_type: "",
199
                                   TABLE_SET_BOTH_UNDERLINES, underline,
200
                                   TABLE_STRING, u->description,
201
                                   TABLE_SET_BOTH_UNDERLINES, underline);
202
                if (r < 0)
8,285✔
203
                        return table_log_add_error(r);
×
204

205
                if (u->job_id != 0)
8,285✔
206
                        job_count++;
11✔
207
        }
208

209
        if (job_count == 0) {
68✔
210
                /* There's no data in the JOB column, so let's hide it */
211
                r = table_hide_column_from_display(table, 5);
64✔
212
                if (r < 0)
64✔
213
                        return log_error_errno(r, "Failed to hide column: %m");
×
214
        }
215

216
        r = output_table(table);
68✔
217
        if (r < 0)
68✔
218
                return r;
219

220
        if (arg_legend != 0) {
66✔
221
                const char *on, *off;
17✔
222
                size_t records = table_get_rows(table) - 1;
17✔
223

224
                if (records > 0) {
17✔
225
                        printf("\n"
32✔
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(),
230
                               glyph(GLYPH_ARROW_RIGHT),
231
                               ansi_normal());
232
                        if (job_count > 0)
16✔
233
                                printf("%s        JOB    %s Pending job for the unit.%s\n",
8✔
234
                                       ansi_grey(),
235
                                       glyph(GLYPH_ARROW_RIGHT),
236
                                       ansi_normal());
237
                }
238

239
                putchar('\n');
17✔
240

241
                on = records > 0 ? ansi_highlight() : ansi_highlight_red();
17✔
242
                off = ansi_normal();
17✔
243

244
                if (arg_all || strv_contains(arg_states, "inactive"))
17✔
245
                        printf("%s%zu loaded units listed.%s\n"
2✔
246
                               "%sTo show all installed unit files use 'systemctl list-unit-files'.%s\n",
247
                               on, records, off,
248
                               ansi_grey(), ansi_normal());
249
                else if (!arg_states)
16✔
250
                        printf("%s%zu loaded units listed.%s %sPass --all to see loaded but inactive units, too.%s\n"
33✔
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());
254
                else
255
                        printf("%zu loaded units listed.\n", records);
5✔
256
        }
257

258
        return 0;
259
}
260

261
int verb_list_units(int argc, char *argv[], void *userdata) {
68✔
262
        _cleanup_free_ UnitInfo *unit_infos = NULL;
68✔
263
        _cleanup_set_free_ Set *replies = NULL;
68✔
264
        sd_bus *bus;
68✔
265
        int r;
68✔
266

267
        r = acquire_bus(BUS_MANAGER, &bus);
68✔
268
        if (r < 0)
68✔
269
                return r;
270

271
        pager_open(arg_pager_flags);
68✔
272

273
        if (arg_with_dependencies) {
68✔
274
                _cleanup_strv_free_ char **names = NULL;
3✔
275

276
                r = append_unit_dependencies(bus, strv_skip(argv, 1), &names);
3✔
277
                if (r < 0)
3✔
278
                        return r;
279

280
                r = get_unit_list_recursive(bus, names, &unit_infos, &replies);
3✔
281
                if (r < 0)
3✔
282
                        return r;
283
        } else {
284
                r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies);
65✔
285
                if (r < 0)
65✔
286
                        return r;
287
        }
288

289
        typesafe_qsort(unit_infos, r, unit_info_compare);
68✔
290
        return output_units_list(unit_infos, r);
68✔
291
}
292

293
static int get_triggered_units(
83✔
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;
83✔
299
        int r;
83✔
300

301
        assert(bus);
83✔
302
        assert(path);
83✔
303
        assert(ret);
83✔
304

305
        r = sd_bus_get_property_strv(
83✔
306
                        bus,
307
                        "org.freedesktop.systemd1",
308
                        path,
309
                        "org.freedesktop.systemd1.Unit",
310
                        "Triggers",
311
                        &error,
312
                        ret);
313
        if (r < 0)
83✔
314
                return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r));
×
315

316
        return 0;
317
}
318

319
typedef 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

331
static void socket_info_array_free(SocketInfo *sockets, size_t n_sockets) {
4✔
332
        assert(sockets || n_sockets == 0);
4✔
333

334
        FOREACH_ARRAY(s, sockets, n_sockets) {
89✔
335
                free(s->type);
85✔
336
                free(s->path);
85✔
337
                strv_free(s->triggered);
85✔
338
        }
339

340
        free(sockets);
4✔
341
}
4✔
342

343
static int socket_info_compare(const SocketInfo *a, const SocketInfo *b) {
281✔
344
        int r;
281✔
345

346
        assert(a);
281✔
347
        assert(b);
281✔
348

349
        r = strcasecmp_ptr(a->machine, b->machine);
281✔
350
        if (r != 0)
281✔
351
                return r;
352

353
        r = CMP(path_is_absolute(a->path), path_is_absolute(b->path));
562✔
354
        if (r != 0)
273✔
355
                return r;
12✔
356

357
        r = path_is_absolute(a->path) ? path_compare(a->path, b->path) : strcmp(a->path, b->path);
269✔
358
        if (r != 0)
269✔
359
                return r;
360

361
        return strcmp(a->type, b->type);
×
362
}
363

364
static int socket_info_add(
269✔
365
                sd_bus *bus,
366
                const UnitInfo *u,
367
                SocketInfo **sockets,
368
                size_t *n_sockets) {
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;
269✔
372
        _cleanup_strv_free_ char **triggered = NULL;
269✔
373
        const char *type, *path;
269✔
374
        int r;
269✔
375

376
        assert(bus);
269✔
377
        assert(u);
269✔
378
        assert(sockets);
269✔
379
        assert(n_sockets);
269✔
380

381
        if (!endswith(u->id, ".socket"))
269✔
382
                return 0;
383

384
        r = get_triggered_units(bus, u->unit_path, &triggered);
76✔
385
        if (r < 0)
76✔
386
                return r;
387

388
        r = sd_bus_get_property(
152✔
389
                        bus,
390
                        "org.freedesktop.systemd1",
391
                        u->unit_path,
76✔
392
                        "org.freedesktop.systemd1.Socket",
393
                        "Listen",
394
                        &error,
395
                        &reply,
396
                        "a(ss)");
397
        if (r < 0)
76✔
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)");
76✔
401
        if (r < 0)
76✔
402
                return bus_log_parse_error(r);
×
403

404
        while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) {
161✔
405
                _cleanup_free_ char *type_dup = NULL, *path_dup = NULL;
85✔
406
                _cleanup_strv_free_ char **triggered_dup = NULL;
×
407

408
                type_dup = strdup(type);
85✔
409
                if (!type_dup)
85✔
410
                        return log_oom();
×
411

412
                path_dup = strdup(path);
85✔
413
                if (!path_dup)
85✔
414
                        return log_oom();
×
415

416
                triggered_dup = strv_copy(triggered);
85✔
417
                if (!triggered_dup)
85✔
418
                        return log_oom();
×
419

420
                if (!GREEDY_REALLOC(*sockets, *n_sockets + 1))
85✔
421
                        return log_oom();
×
422

423
                (*sockets)[(*n_sockets)++] = (SocketInfo) {
85✔
424
                        .machine = u->machine,
85✔
425
                        .id = u->id,
85✔
426
                        .type = TAKE_PTR(type_dup),
85✔
427
                        .path = TAKE_PTR(path_dup),
85✔
428
                        .triggered = TAKE_PTR(triggered_dup),
85✔
429
                };
430
        }
431
        if (r < 0)
76✔
432
                return bus_log_parse_error(r);
×
433

434
        r = sd_bus_message_exit_container(reply);
76✔
435
        if (r < 0)
76✔
436
                return bus_log_parse_error(r);
×
437

438
        return 0;
439
}
440

441
static int output_sockets_list(const SocketInfo *sockets, size_t n_sockets) {
4✔
442
        _cleanup_(table_unrefp) Table *table = NULL;
4✔
443
        int r;
4✔
444

445
        assert(sockets || n_sockets == 0);
4✔
446

447
        table = table_new("listen", "type", "unit", "activates");
4✔
448
        if (!table)
4✔
449
                return log_oom();
×
450

451
        if (!arg_show_types) {
4✔
452
                /* Hide the second (TYPE) column */
453
                r = table_set_display(table, (size_t) 0, (size_t) 2, (size_t) 3);
3✔
454
                if (r < 0)
3✔
455
                        return log_error_errno(r, "Failed to set columns to display: %m");
×
456
        }
457

458
        table_set_header(table, arg_legend != 0);
4✔
459
        if (arg_full)
4✔
460
                table_set_width(table, 0);
×
461

462
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
4✔
463

464
        FOREACH_ARRAY(s, sockets, n_sockets) {
89✔
465
                _cleanup_free_ char *unit = NULL;
85✔
466

467
                unit = format_unit_id(s->id, s->machine);
85✔
468
                if (!unit)
85✔
469
                        return log_oom();
×
470

471
                r = table_add_many(table,
85✔
472
                                   TABLE_STRING, s->path,
473
                                   TABLE_STRING, s->type,
474
                                   TABLE_STRING, unit);
475
                if (r < 0)
85✔
476
                        return table_log_add_error(r);
×
477

478
                r = table_add_triggered(table, s->triggered);
85✔
479
                if (r < 0)
85✔
480
                        return table_log_add_error(r);
×
481
        }
482

483
        r = output_table(table);
4✔
484
        if (r < 0)
4✔
485
                return r;
486

487
        if (arg_legend != 0)
4✔
488
                output_legend("socket", n_sockets);
3✔
489

490
        return 0;
491
}
492

493
int verb_list_sockets(int argc, char *argv[], void *userdata) {
4✔
494
        _cleanup_set_free_ Set *replies = NULL;
4✔
495
        _cleanup_strv_free_ char **sockets_with_suffix = NULL;
×
496
        _cleanup_free_ UnitInfo *unit_infos = NULL;
4✔
497
        SocketInfo *sockets = NULL;
4✔
498
        size_t n_sockets = 0;
4✔
499
        sd_bus *bus;
4✔
500
        int r;
4✔
501

502
        CLEANUP_ARRAY(sockets, n_sockets, socket_info_array_free);
4✔
503

504
        r = acquire_bus(BUS_MANAGER, &bus);
4✔
505
        if (r < 0)
4✔
506
                return r;
507

508
        pager_open(arg_pager_flags);
4✔
509

510
        r = expand_unit_names(bus, strv_skip(argv, 1), ".socket", &sockets_with_suffix, NULL);
4✔
511
        if (r < 0)
4✔
512
                return r;
513

514
        if (argc == 1 || sockets_with_suffix) {
4✔
515
                int n;
4✔
516

517
                n = get_unit_list_recursive(bus, sockets_with_suffix, &unit_infos, &replies);
4✔
518
                if (n < 0)
4✔
519
                        return n;
520

521
                FOREACH_ARRAY(u, unit_infos, n) {
273✔
522
                        r = socket_info_add(bus, u, &sockets, &n_sockets);
269✔
523
                        if (r < 0)
269✔
524
                                return r;
525
                }
526
        }
527

528
        typesafe_qsort(sockets, n_sockets, socket_info_compare);
4✔
529
        output_sockets_list(sockets, n_sockets);
4✔
530

531
        return 0;
532
}
533

534
static int get_next_elapse(
3✔
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;
3✔
540
        dual_timestamp t;
3✔
541
        int r;
3✔
542

543
        assert(bus);
3✔
544
        assert(path);
3✔
545
        assert(next);
3✔
546

547
        r = sd_bus_get_property_trivial(
3✔
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)
3✔
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(
3✔
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)
3✔
569
                return log_error_errno(r, "Failed to get next elapse time: %s", bus_error_message(&error, r));
×
570

571
        *next = t;
3✔
572
        return 0;
3✔
573
}
574

575
static int get_last_trigger(
3✔
576
                sd_bus *bus,
577
                const char *path,
578
                dual_timestamp *last) {
579

580
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
3✔
581
        dual_timestamp t;
3✔
582
        int r;
3✔
583

584
        assert(bus);
3✔
585
        assert(path);
3✔
586
        assert(last);
3✔
587

588
        r = sd_bus_get_property_trivial(
3✔
589
                        bus,
590
                        "org.freedesktop.systemd1",
591
                        path,
592
                        "org.freedesktop.systemd1.Timer",
593
                        "LastTriggerUSec",
594
                        &error,
595
                        't',
596
                        &t.realtime);
597
        if (r < 0)
3✔
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(
3✔
601
                        bus,
602
                        "org.freedesktop.systemd1",
603
                        path,
604
                        "org.freedesktop.systemd1.Timer",
605
                        "LastTriggerUSecMonotonic",
606
                        &error,
607
                        't',
608
                        &t.monotonic);
609
        if (r < 0)
3✔
610
                return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r));
×
611

612
        *last = t;
3✔
613
        return 0;
3✔
614
}
615

616
typedef struct TimerInfo {
617
        const char* machine;
618
        const char* id;
619
        usec_t next_elapse;
620
        dual_timestamp last_trigger;
621
        char **triggered;
622
} TimerInfo;
623

624
static void timer_info_array_free(TimerInfo *timers, size_t n_timers) {
1✔
625
        assert(timers || n_timers == 0);
1✔
626

627
        FOREACH_ARRAY(t, timers, n_timers)
4✔
628
                strv_free(t->triggered);
3✔
629

630
        free(timers);
1✔
631
}
1✔
632

633
static int timer_info_compare(const TimerInfo *a, const TimerInfo *b) {
2✔
634
        int r;
2✔
635

636
        assert(a);
2✔
637
        assert(b);
2✔
638

639
        r = strcasecmp_ptr(a->machine, b->machine);
2✔
640
        if (r != 0)
2✔
641
                return r;
642

643
        r = CMP(a->next_elapse, b->next_elapse);
2✔
UNCOV
644
        if (r != 0)
×
645
                return r;
2✔
646

647
        return strcmp(a->id, b->id);
×
648
}
649

650
static int output_timers_list(const TimerInfo *timers, size_t n_timers) {
1✔
651
        _cleanup_(table_unrefp) Table *table = NULL;
1✔
652
        int r;
1✔
653

654
        assert(timers || n_timers == 0);
1✔
655

656
        table = table_new("next", "left", "last", "passed", "unit", "activates");
1✔
657
        if (!table)
1✔
658
                return log_oom();
×
659

660
        table_set_header(table, arg_legend != 0);
1✔
661
        if (arg_full)
1✔
662
                table_set_width(table, 0);
1✔
663

664
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
1✔
665

666
        (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
1✔
667
        (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
1✔
668

669
        FOREACH_ARRAY(t, timers, n_timers) {
4✔
670
                _cleanup_free_ char *unit = NULL;
3✔
671

672
                unit = format_unit_id(t->id, t->machine);
3✔
673
                if (!unit)
3✔
674
                        return log_oom();
×
675

676
                r = table_add_many(table,
3✔
677
                                   TABLE_TIMESTAMP, t->next_elapse,
678
                                   TABLE_TIMESTAMP_LEFT, t->next_elapse,
679
                                   TABLE_TIMESTAMP, t->last_trigger.realtime,
680
                                   TABLE_TIMESTAMP_RELATIVE_MONOTONIC, t->last_trigger.monotonic,
681
                                   TABLE_STRING, unit);
682
                if (r < 0)
3✔
683
                        return table_log_add_error(r);
×
684

685
                r = table_add_triggered(table, t->triggered);
3✔
686
                if (r < 0)
3✔
687
                        return table_log_add_error(r);
×
688
        }
689

690
        r = output_table(table);
1✔
691
        if (r < 0)
1✔
692
                return r;
693

694
        if (arg_legend != 0)
1✔
695
                output_legend("timer", n_timers);
1✔
696

697
        return 0;
698
}
699

700
usec_t calc_next_elapse(const dual_timestamp *nw, const dual_timestamp *next) {
17✔
701
        usec_t next_elapse;
17✔
702

703
        assert(nw);
17✔
704
        assert(next);
17✔
705

706
        if (timestamp_is_set(next->monotonic)) {
17✔
707
                usec_t converted;
4✔
708

709
                if (next->monotonic > nw->monotonic)
4✔
710
                        converted = nw->realtime + (next->monotonic - nw->monotonic);
4✔
711
                else
712
                        converted = nw->realtime - (nw->monotonic - next->monotonic);
×
713

714
                if (timestamp_is_set(next->realtime))
4✔
715
                        next_elapse = MIN(converted, next->realtime);
×
716
                else
717
                        next_elapse = converted;
718

719
        } else
720
                next_elapse = next->realtime;
13✔
721

722
        return next_elapse;
17✔
723
}
724

725
static int add_timer_info(
243✔
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;
243✔
733
        dual_timestamp next, last;
243✔
734
        usec_t m;
243✔
735
        int r;
243✔
736

737
        assert(bus);
243✔
738
        assert(u);
243✔
739
        assert(nw);
243✔
740
        assert(timers);
243✔
741
        assert(n_timers);
243✔
742

743
        if (!endswith(u->id, ".timer"))
243✔
744
                return 0;
745

746
        r = get_triggered_units(bus, u->unit_path, &triggered);
3✔
747
        if (r < 0)
3✔
748
                return r;
749

750
        r = get_next_elapse(bus, u->unit_path, &next);
3✔
751
        if (r < 0)
3✔
752
                return r;
753

754
        r = get_last_trigger(bus, u->unit_path, &last);
3✔
755
        if (r < 0)
3✔
756
                return r;
757

758
        m = calc_next_elapse(nw, &next);
3✔
759

760
        if (!GREEDY_REALLOC(*timers, *n_timers + 1))
3✔
761
                return log_oom();
×
762

763
        (*timers)[(*n_timers)++] = (TimerInfo) {
3✔
764
                .machine = u->machine,
3✔
765
                .id = u->id,
3✔
766
                .next_elapse = m,
767
                .last_trigger = last,
768
                .triggered = TAKE_PTR(triggered),
3✔
769
        };
770

771
        return 0;
3✔
772
}
773

774
int verb_list_timers(int argc, char *argv[], void *userdata) {
1✔
775
        _cleanup_set_free_ Set *replies = NULL;
1✔
776
        _cleanup_strv_free_ char **timers_with_suffix = NULL;
×
777
        _cleanup_free_ UnitInfo *unit_infos = NULL;
1✔
778
        TimerInfo *timers = NULL;
1✔
779
        size_t n_timers = 0;
1✔
780
        sd_bus *bus;
1✔
781
        int r;
1✔
782

783
        CLEANUP_ARRAY(timers, n_timers, timer_info_array_free);
1✔
784

785
        r = acquire_bus(BUS_MANAGER, &bus);
1✔
786
        if (r < 0)
1✔
787
                return r;
788

789
        pager_open(arg_pager_flags);
1✔
790

791
        r = expand_unit_names(bus, strv_skip(argv, 1), ".timer", &timers_with_suffix, NULL);
1✔
792
        if (r < 0)
1✔
793
                return r;
794

795
        if (argc == 1 || timers_with_suffix) {
1✔
796
                dual_timestamp nw;
1✔
797
                int n;
1✔
798

799
                n = get_unit_list_recursive(bus, timers_with_suffix, &unit_infos, &replies);
1✔
800
                if (n < 0)
1✔
801
                        return n;
×
802

803
                dual_timestamp_now(&nw);
1✔
804

805
                FOREACH_ARRAY(u, unit_infos, n) {
244✔
806
                        r = add_timer_info(bus, u, &nw, &timers, &n_timers);
243✔
807
                        if (r < 0)
243✔
808
                                return r;
809
                }
810
        }
811

812
        typesafe_qsort(timers, n_timers, timer_info_compare);
1✔
813
        output_timers_list(timers, n_timers);
1✔
814

815
        return 0;
816
}
817

818
typedef struct AutomountInfo {
819
        const char *machine;
820
        const char *id;
821
        char *what;
822
        char *where;
823
        usec_t timeout_idle_usec;
824
        bool mounted;
825
} AutomountInfo;
826

827
static void automount_info_array_free(AutomountInfo *automounts, size_t n_automounts) {
×
828
        assert(automounts || n_automounts == 0);
×
829

830
        FOREACH_ARRAY(i, automounts, n_automounts) {
×
831
                free(i->what);
×
832
                free(i->where);
×
833
        }
834

835
        free(automounts);
×
836
}
×
837

838
static int automount_info_compare(const AutomountInfo *a, const AutomountInfo *b) {
×
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

848
        return path_compare(a->where, b->where);
×
849
}
850

851
static int automount_info_add(
×
852
                sd_bus* bus,
853
                const UnitInfo *info,
854
                AutomountInfo **automounts,
855
                size_t *n_automounts) {
856

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;
×
859
        uint64_t timeout_idle_usec;
×
860
        BusLocator locator;
×
861
        int r;
×
862

863
        assert(bus);
×
864
        assert(info);
×
865
        assert(automounts);
×
866
        assert(n_automounts);
×
867

868
        if (!endswith(info->id, ".automount"))
×
869
                return 0;
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

906
        if (!GREEDY_REALLOC(*automounts, *n_automounts + 1))
×
907
                return log_oom();
×
908

909
        (*automounts)[(*n_automounts)++] = (AutomountInfo) {
×
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

921
static int output_automounts_list(const AutomountInfo *infos, size_t n_infos) {
×
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

935
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
×
936

937
        FOREACH_ARRAY(info, infos, n_infos) {
×
938
                _cleanup_free_ char *unit = NULL;
×
939

940
                unit = format_unit_id(info->id, info->machine);
×
941
                if (!unit)
×
942
                        return log_oom();
×
943

944
                r = table_add_many(table,
×
945
                                   TABLE_STRING, info->what,
946
                                   TABLE_STRING, info->where,
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);
×
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

973
int verb_list_automounts(int argc, char *argv[], void *userdata) {
×
974
        _cleanup_set_free_ Set *replies = NULL;
×
975
        _cleanup_strv_free_ char **names = NULL;
×
976
        _cleanup_free_ UnitInfo *unit_infos = NULL;
×
977
        AutomountInfo *automounts = NULL;
×
978
        size_t n_automounts = 0;
×
979
        sd_bus *bus;
×
980
        int r;
×
981

982
        CLEANUP_ARRAY(automounts, n_automounts, automount_info_array_free);
×
983

984
        r = acquire_bus(BUS_MANAGER, &bus);
×
985
        if (r < 0)
×
986
                return r;
987

988
        pager_open(arg_pager_flags);
×
989

990
        r = expand_unit_names(bus, strv_skip(argv, 1), ".automount", &names, NULL);
×
991
        if (r < 0)
×
992
                return r;
993

994
        if (argc == 1 || automounts) {
×
995
                int n;
×
996

997
                n = get_unit_list_recursive(bus, names, &unit_infos, &replies);
×
998
                if (n < 0)
×
999
                        return n;
1000

1001
                FOREACH_ARRAY(u, unit_infos, n) {
×
1002
                        r = automount_info_add(bus, u, &automounts, &n_automounts);
×
1003
                        if (r < 0)
×
1004
                                return r;
1005
                }
1006

1007
        }
1008

1009
        typesafe_qsort(automounts, n_automounts, automount_info_compare);
×
1010
        output_automounts_list(automounts, n_automounts);
×
1011

1012
        return 0;
1013
}
1014

1015
typedef struct PathInfo {
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;
1025
} PathInfo;
1026

1027
static int path_info_compare(const PathInfo *a, const PathInfo *b) {
2✔
1028
        int r;
2✔
1029

1030
        assert(a);
2✔
1031
        assert(b);
2✔
1032

1033
        r = strcasecmp_ptr(a->machine, b->machine);
2✔
1034
        if (r != 0)
2✔
1035
                return r;
1036

1037
        r = path_compare(a->path, b->path);
2✔
1038
        if (r != 0)
2✔
1039
                return r;
1040

1041
        r = strcmp(a->condition, b->condition);
2✔
1042
        if (r != 0)
2✔
1043
                return r;
1044

1045
        return strcasecmp_ptr(a->id, b->id);
2✔
1046
}
1047

1048
static void path_info_array_free(PathInfo *paths, size_t n_paths) {
2✔
1049
        assert(paths || n_paths == 0);
2✔
1050

1051
        FOREACH_ARRAY(p, paths, n_paths) {
6✔
1052
                free(p->condition);
4✔
1053
                free(p->path);
4✔
1054
                strv_free(p->triggered);
4✔
1055
        }
1056

1057
        free(paths);
2✔
1058
}
2✔
1059

1060
static int path_info_add(
221✔
1061
                sd_bus *bus,
1062
                const struct UnitInfo *u,
1063
                PathInfo **paths,
1064
                size_t *n_paths) {
1065

1066
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
221✔
1067
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
221✔
1068
        _cleanup_strv_free_ char **triggered = NULL;
221✔
1069
        const char *condition, *path;
221✔
1070
        int r;
221✔
1071

1072
        assert(bus);
221✔
1073
        assert(u);
221✔
1074
        assert(paths);
221✔
1075
        assert(n_paths);
221✔
1076

1077
        if (!endswith(u->id, ".path"))
221✔
1078
                return 0;
1079

1080
        r = get_triggered_units(bus, u->unit_path, &triggered);
4✔
1081
        if (r < 0)
4✔
1082
                return r;
1083

1084
        r = sd_bus_get_property(bus,
8✔
1085
                                "org.freedesktop.systemd1",
1086
                                u->unit_path,
4✔
1087
                                "org.freedesktop.systemd1.Path",
1088
                                "Paths",
1089
                                &error,
1090
                                &reply,
1091
                                "a(ss)");
1092
        if (r < 0)
4✔
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)");
4✔
1096
        if (r < 0)
4✔
1097
                return bus_log_parse_error(r);
×
1098

1099
        while ((r = sd_bus_message_read(reply, "(ss)", &condition, &path)) > 0) {
8✔
1100
                _cleanup_free_ char *condition_dup = NULL, *path_dup = NULL;
4✔
1101
                _cleanup_strv_free_ char **triggered_dup = NULL;
×
1102

1103
                condition_dup = strdup(condition);
4✔
1104
                if (!condition_dup)
4✔
1105
                        return log_oom();
×
1106

1107
                path_dup = strdup(path);
4✔
1108
                if (!path_dup)
4✔
1109
                        return log_oom();
×
1110

1111
                triggered_dup = strv_copy(triggered);
4✔
1112
                if (!triggered_dup)
4✔
1113
                        return log_oom();
×
1114

1115
                if (!GREEDY_REALLOC(*paths, *n_paths + 1))
4✔
1116
                        return log_oom();
×
1117

1118
                (*paths)[(*n_paths)++] = (PathInfo) {
4✔
1119
                        .machine = u->machine,
4✔
1120
                        .id = u->id,
4✔
1121
                        .condition = TAKE_PTR(condition_dup),
4✔
1122
                        .path = TAKE_PTR(path_dup),
4✔
1123
                        .triggered = TAKE_PTR(triggered_dup),
4✔
1124
                };
1125
        }
1126
        if (r < 0)
4✔
1127
                return bus_log_parse_error(r);
×
1128

1129
        r = sd_bus_message_exit_container(reply);
4✔
1130
        if (r < 0)
4✔
1131
                return bus_log_parse_error(r);
×
1132

1133
        return 0;
1134
}
1135

1136
static int output_paths_list(const PathInfo *paths, size_t n_paths) {
2✔
1137
        _cleanup_(table_unrefp) Table *table = NULL;
2✔
1138
        int r;
2✔
1139

1140
        assert(paths || n_paths == 0);
2✔
1141

1142
        table = table_new("path", "condition", "unit", "activates");
2✔
1143
        if (!table)
2✔
1144
                return log_oom();
×
1145

1146
        table_set_header(table, arg_legend != 0);
2✔
1147
        if (arg_full)
2✔
1148
                table_set_width(table, 0);
×
1149

1150
        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
2✔
1151

1152
        FOREACH_ARRAY(p, paths, n_paths) {
6✔
1153
                _cleanup_free_ char *unit = NULL;
4✔
1154

1155
                unit = format_unit_id(p->id, p->machine);
4✔
1156
                if (!unit)
4✔
1157
                        return log_oom();
×
1158

1159
                r = table_add_many(table,
4✔
1160
                                   TABLE_STRING, p->path,
1161
                                   TABLE_STRING, p->condition,
1162
                                   TABLE_STRING, unit);
1163
                if (r < 0)
4✔
1164
                        return table_log_add_error(r);
×
1165

1166
                r = table_add_triggered(table, p->triggered);
4✔
1167
                if (r < 0)
4✔
1168
                        return table_log_add_error(r);
×
1169
        }
1170

1171
        r = output_table(table);
2✔
1172
        if (r < 0)
2✔
1173
                return r;
1174

1175
        if (arg_legend != 0)
2✔
1176
                output_legend("path", n_paths);
1✔
1177

1178
        return 0;
1179
}
1180

1181
int verb_list_paths(int argc, char *argv[], void *userdata) {
2✔
1182
        _cleanup_set_free_ Set *replies = NULL;
2✔
1183
        _cleanup_strv_free_ char **units = NULL;
×
1184
        _cleanup_free_ UnitInfo *unit_infos = NULL;
2✔
1185
        PathInfo *paths = NULL;
2✔
1186
        size_t n_paths = 0;
2✔
1187
        sd_bus *bus;
2✔
1188
        int r;
2✔
1189

1190
        CLEANUP_ARRAY(paths, n_paths, path_info_array_free);
2✔
1191

1192
        r = acquire_bus(BUS_MANAGER, &bus);
2✔
1193
        if (r < 0)
2✔
1194
                return r;
1195

1196
        pager_open(arg_pager_flags);
2✔
1197

1198
        r = expand_unit_names(bus, strv_skip(argv, 1), ".path", &units, NULL);
2✔
1199
        if (r < 0)
2✔
1200
                return r;
1201

1202
        if (argc == 1 || units) {
2✔
1203
                int n;
2✔
1204

1205
                n = get_unit_list_recursive(bus, units, &unit_infos, &replies);
2✔
1206
                if (n < 0)
2✔
1207
                        return n;
1208

1209
                FOREACH_ARRAY(u, unit_infos, n) {
223✔
1210
                        r = path_info_add(bus, u, &paths, &n_paths);
221✔
1211
                        if (r < 0)
221✔
1212
                                return r;
1213
                }
1214
        }
1215

1216
        typesafe_qsort(paths, n_paths, path_info_compare);
2✔
1217
        output_paths_list(paths, n_paths);
2✔
1218

1219
        return 0;
1220
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc