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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

57.61
/src/sysupdate/updatectl.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <locale.h>
4

5
#include "sd-bus.h"
6
#include "sd-event.h"
7
#include "sd-json.h"
8

9
#include "alloc-util.h"
10
#include "build.h"
11
#include "bus-error.h"
12
#include "bus-label.h"
13
#include "bus-locator.h"
14
#include "bus-map-properties.h"
15
#include "bus-util.h"
16
#include "errno-util.h"
17
#include "format-table.h"
18
#include "hashmap.h"
19
#include "json-util.h"
20
#include "main-func.h"
21
#include "options.h"
22
#include "pager.h"
23
#include "polkit-agent.h"
24
#include "pretty-print.h"
25
#include "runtime-scope.h"
26
#include "string-util.h"
27
#include "strv.h"
28
#include "sysupdate-update-set-flags.h"
29
#include "sysupdate-util.h"
30
#include "terminal-util.h"
31
#include "verbs.h"
32

33
static PagerFlags arg_pager_flags = 0;
34
static bool arg_legend = true;
35
static bool arg_reboot = false;
36
static bool arg_offline = false;
37
static bool arg_now = false;
38
static bool arg_ask_password = true;
39
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
40
static const char *arg_host = NULL;
41

42
#define SYSUPDATE_HOST_PATH "/org/freedesktop/sysupdate1/target/host"
43
#define SYSUPDATE_TARGET_INTERFACE "org.freedesktop.sysupdate1.Target"
44

45
typedef struct Version {
46
        char *version;
47
        UpdateSetFlags flags;
48
        char **changelog;
49
        char *contents_json;
50
} Version;
51

52
static void version_done(Version *v) {
96✔
53
        assert(v);
96✔
54

55
        v->version = mfree(v->version);
96✔
56
        v->changelog = strv_free(v->changelog);
96✔
57
        v->flags = 0;
96✔
58
        v->contents_json = mfree(v->contents_json);
96✔
59
}
96✔
60

61
typedef struct Operation {
62
        void *userdata;
63

64
        sd_bus *bus;
65
        sd_event *event;
66
        unsigned *remaining;
67

68
        const char *target_path;
69
        const char *target_id;
70

71
        uint64_t job_id;
72
        char *job_path;
73
        sd_event_source *job_interrupt_source;
74
        sd_bus_slot *job_properties_slot;
75
        sd_bus_slot *job_finished_slot;
76

77
        /* Only used for Acquire()/Install() operations: */
78
        char *acquired_version;
79
        /* The version the user requested, possibly empty */
80
        char *requested_version;
81
} Operation;
82

83
static Operation* operation_free(Operation *p) {
84✔
84
        if (!p)
84✔
85
                return NULL;
86

87
        assert(*p->remaining > 0);
84✔
88
        *p->remaining -= 1;
84✔
89
        if (*p->remaining == 0)
84✔
90
                /* We want to crash the program if we can't exit the loop
91
                 * cleanly, otherwise it will just hang */
92
                assert_se(sd_event_exit(p->event, 0) >= 0);
44✔
93

94
        free(p->job_path);
84✔
95
        free(p->acquired_version);
84✔
96
        free(p->requested_version);
84✔
97

98
        sd_event_source_disable_unref(p->job_interrupt_source);
84✔
99
        sd_bus_slot_unref(p->job_properties_slot);
84✔
100
        sd_bus_slot_unref(p->job_finished_slot);
84✔
101

102
        return mfree(p);
84✔
103
}
104

105
DEFINE_TRIVIAL_CLEANUP_FUNC(Operation*, operation_free);
332✔
106

107
static Operation* operation_new(
84✔
108
                void *userdata,
109
                sd_bus *bus,
110
                unsigned *remaining,
111
                const char *target_path,
112
                const char *target_id) {
113

114
        _cleanup_(operation_freep) Operation *o = NULL;
84✔
115

116
        o = new(Operation, 1);
84✔
117
        if (!o)
84✔
118
                return NULL;
119

120
        *o = (Operation) {
168✔
121
                .userdata = userdata,
122
                .bus = bus,
123
                .event = sd_bus_get_event(bus),
84✔
124
                .remaining = remaining,
125
                .target_path = target_path,
126
                .target_id = target_id,
127
        };
128
        return TAKE_PTR(o);
84✔
129
}
130

131
static int ensure_targets(sd_bus *bus, char **argv, char ***ret_targets) {
80✔
132
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
80✔
133
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
80✔
134
        _cleanup_strv_free_ char **targets = NULL;
80✔
135
        int r;
80✔
136

137
        assert(bus);
80✔
138
        assert(ret_targets);
80✔
139

140
        if (strv_isempty(argv)) {
80✔
141
                const char *class, *name, *path;
80✔
142

143
                r = bus_call_method(bus, bus_sysupdate_mgr, "ListTargets", &error, &reply, NULL);
80✔
144
                if (r < 0)
80✔
UNCOV
145
                        return log_error_errno(r, "Failed to call ListTargets: %s", bus_error_message(&error, r));
×
146

147
                r = sd_bus_message_enter_container(reply, 'a', "(sso)");
80✔
148
                if (r < 0)
80✔
UNCOV
149
                        return bus_log_parse_error(r);
×
150

151
                while ((r = sd_bus_message_read(reply, "(sso)", &class, &name, &path)) > 0) {
168✔
UNCOV
152
                        _cleanup_free_ char *id = NULL;
×
153

154
                        if (streq(class, "host"))
88✔
155
                                id = strdup("host");
80✔
156
                        else
157
                                id = strjoin(class, ":", name);
8✔
158
                        if (!id)
88✔
UNCOV
159
                                return log_oom();
×
160

161
                        r = strv_consume(&targets, TAKE_PTR(id));
88✔
162
                        if (r < 0)
88✔
UNCOV
163
                                return log_oom();
×
164
                }
165
                if (r < 0)
80✔
UNCOV
166
                        return bus_log_parse_error(r);
×
167

168
                r = sd_bus_message_exit_container(reply);
80✔
169
                if (r < 0)
80✔
UNCOV
170
                        return bus_log_parse_error(r);
×
171
        } else {
172
                r = strv_extend_strv(&targets, argv, true);
×
173
                if (r < 0)
×
UNCOV
174
                        return log_oom();
×
175
        }
176

177
        *ret_targets = TAKE_PTR(targets);
80✔
178
        return 0;
80✔
179
}
180

181
static int parse_target(
104✔
182
                const char *in,
183
                char **ret_bus_path,
184
                char **ret_version) {
185
        _cleanup_free_ char *id = NULL, *version = NULL, *escaped = NULL, *objpath = NULL;
104✔
186
        const char *s;
104✔
187

188
        /*
189
         * Parses the TARGET[@VERSION] syntax from the command line into
190
         * a bus object path and an optional version number.
191
         */
192

193
        assert(in);
104✔
194
        assert(ret_bus_path);
104✔
195
        assert(ret_version);
104✔
196

197
        s = strrchr(in, '@');
104✔
198
        if (s) {
104✔
199
                version = strdup(s + 1);
8✔
200
                if (!version)
8✔
201
                        return -ENOMEM;
202
                id = strndup(in, s - in);
8✔
203
        } else
204
                id = strdup(in);
96✔
205
        if (!id)
104✔
206
                return -ENOMEM;
207

208
        escaped = bus_label_escape(id);
104✔
209
        if (!escaped)
104✔
210
                return -ENOMEM;
211

212
        objpath = strjoin("/org/freedesktop/sysupdate1/target/", escaped);
104✔
213
        if (!objpath)
104✔
214
                return -ENOMEM;
215

216
        *ret_bus_path = TAKE_PTR(objpath);
104✔
217
        *ret_version = TAKE_PTR(version);
104✔
218
        return 0;
104✔
219
}
220

221
static int parse_targets(
80✔
222
                char **targets,
223
                size_t *ret_n,
224
                char ***ret_bus_paths,
225
                char ***ret_versions) {
226
        _cleanup_strv_free_ char **bus_paths = NULL;
80✔
227
        _cleanup_strv_free_ char **versions = NULL;
80✔
228
        size_t n = 0;
80✔
229
        int r;
80✔
230

231
        assert(ret_bus_paths);
80✔
232
        assert(ret_n);
80✔
233

234
        if (strv_isempty(targets))
80✔
UNCOV
235
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No targets found.");
×
236

237
        STRV_FOREACH(id, targets) {
168✔
238
                _cleanup_free_ char *bus_path = NULL, *version = NULL;
88✔
239

240
                r = parse_target(*id, &bus_path, &version);
88✔
241
                if (r < 0)
88✔
UNCOV
242
                        return log_oom();
×
243

244
                if (version && !ret_versions)
88✔
UNCOV
245
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
246
                                               "Unexpected version specifier in target: %s",
247
                                               *id);
248

249
                r = strv_extend(&bus_paths, strempty(bus_path));
88✔
250
                if (r < 0)
88✔
UNCOV
251
                        return log_oom();
×
252

253
                r = strv_extend(&versions, strempty(version));
176✔
254
                if (r < 0)
88✔
UNCOV
255
                        return log_oom();
×
256

257
                n++;
88✔
258
        }
259

260
        *ret_n = n;
80✔
261
        *ret_bus_paths = TAKE_PTR(bus_paths);
80✔
262
        if (ret_versions)
80✔
263
                *ret_versions = TAKE_PTR(versions);
28✔
264
        return 0;
265
}
266

267
static int log_bus_error(int r, const sd_bus_error *error, const char *target, const char *action) {
×
UNCOV
268
        assert(action);
×
269

270
        if (r == 0) {
×
271
                assert(sd_bus_error_is_set(error));
×
UNCOV
272
                r = sd_bus_error_get_errno(error);
×
273
        }
274

275
        if (sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_OBJECT)) {
×
276
                if (target)
×
277
                        return log_error_errno(r, "Invalid target: %s", target);
×
UNCOV
278
                return log_error_errno(r, "Invalid target");
×
279
        }
280

281
        if (target)
×
UNCOV
282
                return log_error_errno(r, "Failed to %s for '%s': %s", action, target,
×
283
                                       bus_error_message(error, r));
UNCOV
284
        return log_error_errno(r, "Failed to %s: %s", action, bus_error_message(error, r));
×
285
}
286

287
static int list_targets(sd_bus *bus) {
44✔
288
        _cleanup_(table_unrefp) Table *table = NULL;
44✔
289
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
44✔
290
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
44✔
291
        size_t n;
44✔
292
        int r;
44✔
293

294
        assert(bus);
44✔
295

296
        r = ensure_targets(bus, /* argv= */ NULL, &targets);
44✔
297
        if (r < 0)
44✔
UNCOV
298
                return log_error_errno(r, "Failed to find targets: %m");
×
299

300
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
44✔
301
        if (r < 0)
44✔
UNCOV
302
                return log_error_errno(r, "Failed to parse targets: %m");
×
303

304
        table = table_new("target", "version", "path");
44✔
305
        if (!table)
44✔
UNCOV
306
                return log_oom();
×
307

308
        for (size_t i = 0; i < n; i++) {
88✔
309
                char *version = NULL;
44✔
310
                _cleanup_free_ char *path = NULL;
44✔
311
                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
44✔
312

313
                r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination,
88✔
314
                                       target_paths[i], SYSUPDATE_TARGET_INTERFACE,
44✔
315
                                       "GetVersion", &error, &reply, NULL);
316
                if (r < 0)
44✔
UNCOV
317
                        return log_bus_error(r, &error, targets[i], "get current version");
×
318
                r = sd_bus_message_read_basic(reply, 's', &version);
44✔
319
                if (r < 0)
44✔
UNCOV
320
                        return bus_log_parse_error(r);
×
321

322
                r = sd_bus_get_property_string(bus, bus_sysupdate_mgr->destination,
88✔
323
                                               target_paths[i], SYSUPDATE_TARGET_INTERFACE,
44✔
324
                                               "Path", &error, &path);
325
                if (r < 0)
44✔
UNCOV
326
                        return log_bus_error(r, &error, targets[i], "get target bus path");
×
327

328
                r = table_add_many(table,
88✔
329
                                   TABLE_STRING, targets[i],
330
                                   TABLE_STRING, empty_to_dash(version),
331
                                   TABLE_STRING, path);
332
                if (r < 0)
44✔
UNCOV
333
                        return table_log_add_error(r);
×
334
        }
335

336
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
44✔
337
}
338

339
typedef struct DescribeParams {
340
        Version v;
341
        sd_json_variant *contents_json;
342
        bool newest;
343
        bool available;
344
        bool installed;
345
        bool partial;
346
        bool pending;
347
        bool obsolete;
348
        bool protected;
349
        bool incomplete;
350
} DescribeParams;
351

352
static void describe_params_done(DescribeParams *p) {
48✔
353
        assert(p);
48✔
354

355
        version_done(&p->v);
48✔
356
        sd_json_variant_unref(p->contents_json);
48✔
357
}
48✔
358

359
static int parse_describe(sd_bus_message *reply, Version *ret) {
48✔
360
        char *version_json = NULL;
48✔
361
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
48✔
362
        int r;
48✔
363

364
        assert(reply);
48✔
365
        assert(ret);
48✔
366

367
        r = sd_bus_message_read_basic(reply, 's', &version_json);
48✔
368
        if (r < 0)
48✔
UNCOV
369
                return bus_log_parse_error(r);
×
370

371
        r = sd_json_parse(version_json, 0, &json, NULL, NULL);
48✔
372
        if (r < 0)
48✔
UNCOV
373
                return log_error_errno(r, "Failed to parse JSON: %m");
×
374

375
        assert(sd_json_variant_is_object(json));
48✔
376

377
        static const sd_json_dispatch_field dispatch_table[] = {
48✔
378
                { "version",       SD_JSON_VARIANT_STRING,  json_dispatch_version,    offsetof(DescribeParams, v.version),     0 },
379
                { "newest",        SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, newest),        0 },
380
                { "available",     SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, available),     0 },
381
                { "installed",     SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, installed),     0 },
382
                { "partial",       SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, partial),       0 },
383
                { "pending",       SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, pending),       0 },
384
                { "obsolete",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, obsolete),      0 },
385
                { "protected",     SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, protected),     0 },
386
                { "incomplete",    SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, incomplete),    0 },
387
                { "changelogUrls", SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_strv,    offsetof(DescribeParams, v.changelog),   0 },
388
                { "contents",      SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_variant, offsetof(DescribeParams, contents_json), 0 },
389
                {},
390
        };
391

392
        _cleanup_(describe_params_done) DescribeParams p = {};
48✔
393

394
        r = sd_json_dispatch(json, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
48✔
395
        if (r < 0)
48✔
UNCOV
396
                return log_error_errno(r, "Failed to parse JSON: %m");
×
397

398
        SET_FLAG(p.v.flags, UPDATE_NEWEST, p.newest);
48✔
399
        SET_FLAG(p.v.flags, UPDATE_AVAILABLE, p.available);
48✔
400
        SET_FLAG(p.v.flags, UPDATE_INSTALLED, p.installed);
48✔
401
        SET_FLAG(p.v.flags, UPDATE_PARTIAL, p.partial);
48✔
402
        SET_FLAG(p.v.flags, UPDATE_PENDING, p.pending);
48✔
403
        SET_FLAG(p.v.flags, UPDATE_OBSOLETE, p.obsolete);
48✔
404
        SET_FLAG(p.v.flags, UPDATE_PROTECTED, p.protected);
48✔
405
        SET_FLAG(p.v.flags, UPDATE_INCOMPLETE, p.incomplete);
48✔
406

407
        r = sd_json_variant_format(p.contents_json, 0, &p.v.contents_json);
48✔
408
        if (r < 0)
48✔
UNCOV
409
                return log_error_errno(r, "Failed to format JSON for contents: %m");
×
410

411
        *ret = TAKE_STRUCT(p.v);
48✔
412
        return 0;
48✔
413
}
414

415
static int list_versions_finished(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
40✔
416
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
80✔
417
        Table *table = ASSERT_PTR(op->userdata);
40✔
418
        const sd_bus_error *e;
40✔
UNCOV
419
        _cleanup_(version_done) Version v = {};
×
420
        _cleanup_free_ char *version_link = NULL;
40✔
421
        const char *color;
40✔
422
        int r;
40✔
423

424
        assert(reply);
40✔
425

426
        e = sd_bus_message_get_error(reply);
40✔
427
        if (e)
40✔
UNCOV
428
                return log_bus_error(0, e, NULL, "call Describe");
×
429

430
        r = parse_describe(reply, &v);
40✔
431
        if (r < 0)
40✔
UNCOV
432
                return log_error_errno(r, "Failed to parse Describe output: %m");
×
433

434
        color = update_set_flags_to_color(v.flags);
40✔
435

436
        if (urlify_enabled() && !strv_isempty(v.changelog)) {
40✔
437
                version_link = strjoin(v.version, glyph(GLYPH_EXTERNAL_LINK));
×
438
                if (!version_link)
×
UNCOV
439
                        return log_oom();
×
440
        }
441

442
        r = table_add_many(table,
40✔
443
                           TABLE_STRING,    update_set_flags_to_glyph(v.flags),
444
                           TABLE_SET_COLOR, color,
445
                           TABLE_STRING,    version_link ?: v.version,
446
                           TABLE_SET_COLOR, color,
447
                           TABLE_SET_URL,   strv_isempty(v.changelog) ? NULL : v.changelog[0],
448
                           TABLE_STRING,    update_set_flags_to_string(v.flags),
449
                           TABLE_SET_COLOR, color);
450
        if (r < 0)
40✔
UNCOV
451
                return table_log_add_error(r);
×
452

453
        return 0;
454
}
455

456
static int list_versions(sd_bus *bus, const char *target_path) {
8✔
457
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
8✔
UNCOV
458
        _cleanup_(table_unrefp) Table *table = NULL;
×
459
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
8✔
460
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
8✔
461
        _cleanup_strv_free_ char **versions = NULL;
8✔
462
        unsigned remaining = 0;
8✔
463
        int r;
8✔
464

465
        r = sd_bus_call_method(
24✔
466
                        bus,
467
                        bus_sysupdate_mgr->destination,
8✔
468
                        target_path,
469
                        SYSUPDATE_TARGET_INTERFACE,
470
                        "List",
471
                        &error,
472
                        &reply,
473
                        "t",
474
                        arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
8✔
475
        if (r < 0)
8✔
UNCOV
476
                return log_bus_error(r, &error, NULL, "call List");
×
477

478
        r = sd_bus_message_read_strv(reply, &versions);
8✔
479
        if (r < 0)
8✔
UNCOV
480
                return bus_log_parse_error(r);
×
481

482
        table = table_new("", "version", "status");
8✔
483
        if (!table)
8✔
UNCOV
484
                return log_oom();
×
485

486
        (void) table_set_sort(table, 1);
8✔
487
        (void) table_set_reverse(table, 1, true);
8✔
488

489
        r = sd_event_default(&event);
8✔
490
        if (r < 0)
8✔
UNCOV
491
                return log_error_errno(r, "Failed to get event loop: %m");
×
492

493
        r = sd_bus_attach_event(bus, event, 0);
8✔
494
        if (r < 0)
8✔
UNCOV
495
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
496

497
        r = sd_event_set_signal_exit(event, true);
8✔
498
        if (r < 0)
8✔
UNCOV
499
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
500

501
        STRV_FOREACH(version, versions) {
48✔
UNCOV
502
                _cleanup_(operation_freep) Operation *op = NULL;
×
503
                op = operation_new(table, bus, &remaining, NULL, NULL);
40✔
504
                if (!op)
40✔
UNCOV
505
                        return log_oom();
×
506

507
                r = sd_bus_call_method_async(bus,
120✔
508
                                             NULL,
509
                                             bus_sysupdate_mgr->destination,
40✔
510
                                             target_path,
511
                                             SYSUPDATE_TARGET_INTERFACE,
512
                                             "Describe",
513
                                             list_versions_finished,
514
                                             op,
515
                                             "st",
516
                                             *version,
517
                                             arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
40✔
518
                if (r < 0)
40✔
UNCOV
519
                        return log_error_errno(r, "Failed to call Describe: %m");
×
520
                TAKE_PTR(op);
40✔
521

522
                remaining++;
40✔
523
        }
524

525
        r = sd_event_loop(event);
8✔
526
        if (r < 0)
8✔
UNCOV
527
                return log_error_errno(r, "Failed to start event loop: %m");
×
528

529
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
8✔
530
}
531

532
static int describe(sd_bus *bus, const char *target_path, const char *version) {
8✔
533
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
8✔
534
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
8✔
UNCOV
535
        _cleanup_(table_unrefp) Table *table = NULL;
×
536
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
8✔
537
        _cleanup_(version_done) Version v = {};
8✔
538
        sd_json_variant *entry;
8✔
539
        const char *color;
8✔
540
        int r;
8✔
541

542
        r = sd_bus_call_method(
24✔
543
                        bus,
544
                        bus_sysupdate_mgr->destination,
8✔
545
                        target_path,
546
                        SYSUPDATE_TARGET_INTERFACE,
547
                        "Describe",
548
                        &error,
549
                        &reply,
550
                        "st",
551
                        version,
552
                        arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
8✔
553
        if (r < 0)
8✔
UNCOV
554
                return log_bus_error(r, &error, NULL, "call Describe");
×
555

556
        r = parse_describe(reply, &v);
8✔
557
        if (r < 0)
8✔
UNCOV
558
                return log_error_errno(r, "Failed to parse Describe output: %m");
×
559

560
        color = strempty(update_set_flags_to_color(v.flags));
8✔
561

562
        printf("%s%s%s Version: %s\n"
24✔
563
               "    State: %s%s%s\n",
564
               color,
565
               update_set_flags_to_glyph(v.flags),
566
               ansi_normal(),
567
               v.version,
568
               color,
569
               update_set_flags_to_string(v.flags),
570
               ansi_normal());
571

572
        STRV_FOREACH(url, v.changelog) {
8✔
UNCOV
573
                _cleanup_free_ char *changelog_link = NULL;
×
574

575
                r = terminal_urlify(*url, NULL, &changelog_link);
×
576
                if (r < 0)
×
UNCOV
577
                        return log_error_errno(r, "Could not urlify link to change-log: %m");
×
578

UNCOV
579
                printf("ChangeLog: %s\n", strna(changelog_link));
×
580
        }
581
        printf("\n");
8✔
582

583
        r = sd_json_parse(v.contents_json, 0, &json, NULL, NULL);
8✔
584
        if (r < 0)
8✔
UNCOV
585
                return log_error_errno(r, "Failed to parse JSON: %m");
×
586

587
        assert(sd_json_variant_is_array(json));
8✔
588

589
        JSON_VARIANT_ARRAY_FOREACH(entry, json) {
64✔
590
                assert(sd_json_variant_is_object(entry));
56✔
591
                const char *key;
56✔
592
                sd_json_variant *value;
56✔
593

594
                if (!table) {
56✔
595
                        table = table_new_raw(sd_json_variant_elements(entry) / 2);
8✔
596
                        if (!table)
8✔
UNCOV
597
                                return log_oom();
×
598

599
                        JSON_VARIANT_OBJECT_FOREACH(key, value, entry) {
80✔
600

601
                                r = table_add_cell(table, NULL, TABLE_HEADER, key);
72✔
602
                                if (r < 0)
72✔
UNCOV
603
                                        return table_log_add_error(r);
×
604
                        }
605
                }
606

607
                JSON_VARIANT_OBJECT_FOREACH(key, value, entry) {
560✔
608
                        TableDataType type;
504✔
609
                        uint64_t number;
504✔
610
                        bool boolean;
504✔
611
                        const void *data;
504✔
612

613
                        if (sd_json_variant_is_string(value)) {
504✔
614
                                type = TABLE_STRING;
128✔
615
                                assert_se(data = sd_json_variant_string(value));
128✔
616
                        } else if (sd_json_variant_is_unsigned(value)) {
376✔
617
                                type = TABLE_UINT64;
112✔
618
                                number = sd_json_variant_unsigned(value);
112✔
619
                                data = &number;
112✔
620
                        } else if (sd_json_variant_is_boolean(value)) {
264✔
621
                                type = TABLE_BOOLEAN;
16✔
622
                                boolean = sd_json_variant_boolean(value);
16✔
623
                                data = &boolean;
16✔
624
                        } else if (sd_json_variant_is_null(value)) {
248✔
625
                                type = TABLE_EMPTY;
626
                                data = NULL;
627
                        } else
UNCOV
628
                                assert_not_reached();
×
629

630
                        if (streq(key, "ptflags"))
504✔
631
                                type = TABLE_UINT64_HEX;
632
                        else if (streq(key, "size"))
448✔
633
                                type = TABLE_SIZE;
634
                        else if (streq(key, "mode"))
448✔
635
                                type = TABLE_MODE;
636
                        else if (streq(key, "mtime"))
392✔
637
                                type = TABLE_TIMESTAMP;
56✔
638

639
                        r = table_add_cell(table, NULL, type, data);
504✔
640
                        if (r < 0)
504✔
UNCOV
641
                                return table_log_add_error(r);
×
642
                }
643
        }
644

645
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
8✔
646
}
647

648
VERB(verb_list, "list", "[TARGET[@VERSION]]", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY,
649
     "List available targets and versions");
650
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
60✔
651
        sd_bus *bus = ASSERT_PTR(userdata);
60✔
652
        _cleanup_free_ char *target_path = NULL, *version = NULL;
60✔
653
        int r;
60✔
654

655
        if (argc == 1)
60✔
656
                return list_targets(bus);
44✔
657

658
        r = parse_target(argv[1], &target_path, &version);
16✔
659
        if (r < 0)
16✔
UNCOV
660
                return log_oom();
×
661

662
        if (version)
16✔
663
                return describe(bus, target_path, version);
8✔
664
        else
665
                return list_versions(bus, target_path);
8✔
666
}
667

668
static int check_describe_finished(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
×
669
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
×
670
        Table *table = ASSERT_PTR(op->userdata);
×
671
        _cleanup_(version_done) Version v = {};
×
672
        _cleanup_free_ char *update = NULL;
×
673
        const sd_bus_error *e;
×
674
        sd_bus_error error = {};
×
675
        const char *lnk = NULL;
×
676
        char *current;
×
UNCOV
677
        int r;
×
678

UNCOV
679
        assert(reply);
×
680

681
        e = sd_bus_message_get_error(reply);
×
682
        if (e)
×
UNCOV
683
                return log_bus_error(0, e, NULL, "call Describe");
×
684

685
        r = parse_describe(reply, &v);
×
686
        if (r < 0)
×
UNCOV
687
                return log_error_errno(r, "Failed to parse output of Describe: %m");
×
688

UNCOV
689
        r = sd_bus_call_method(
×
690
                        op->bus,
691
                        bus_sysupdate_mgr->destination,
×
UNCOV
692
                        op->target_path,
×
693
                        SYSUPDATE_TARGET_INTERFACE,
694
                        "GetVersion",
695
                        &error,
696
                        &reply,
697
                        NULL);
698
        if (r < 0)
×
UNCOV
699
                return log_bus_error(r, &error, op->target_id, "get current version");
×
700

701
        r = sd_bus_message_read_basic(reply, 's', &current);
×
702
        if (r < 0)
×
UNCOV
703
                return bus_log_parse_error(r);
×
704

705
        if (urlify_enabled() && !strv_isempty(v.changelog))
×
UNCOV
706
                lnk = glyph(GLYPH_EXTERNAL_LINK);
×
707

UNCOV
708
        update = strjoin(empty_to_dash(current), " ",
×
709
                         glyph(GLYPH_ARROW_RIGHT), " ",
710
                         v.version, strempty(lnk));
711
        if (!update)
×
UNCOV
712
                return log_oom();
×
713

UNCOV
714
        r = table_add_many(table,
×
715
                           TABLE_STRING,  op->target_id,
716
                           TABLE_STRING,  update,
717
                           TABLE_SET_URL, strv_isempty(v.changelog) ? NULL : v.changelog[0]);
718
        if (r < 0)
×
UNCOV
719
                return table_log_add_error(r);
×
720

721
        return 0;
722
}
723

724
static int check_finished(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
16✔
725
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
16✔
726
        const sd_bus_error *e;
16✔
727
        const char *new_version = NULL;
16✔
728
        int r;
16✔
729

730
        assert(reply);
16✔
731

732
        e = sd_bus_message_get_error(reply);
16✔
733
        if (e)
16✔
UNCOV
734
                return log_bus_error(0, e, op->target_id, "call CheckNew");
×
735

736
        r = sd_bus_message_read(reply, "s", &new_version);
16✔
737
        if (r < 0)
16✔
UNCOV
738
                return bus_log_parse_error(r);
×
739

740
        if (isempty(new_version))
32✔
741
                return 0;
742

UNCOV
743
        r = sd_bus_call_method_async(op->bus,
×
744
                                     NULL,
UNCOV
745
                                     bus_sysupdate_mgr->destination,
×
746
                                     op->target_path,
747
                                     SYSUPDATE_TARGET_INTERFACE,
748
                                     "Describe",
749
                                     check_describe_finished,
750
                                     op,
751
                                     "st",
752
                                     new_version,
753
                                     arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
×
754
        if (r < 0)
×
755
                return log_error_errno(r, "Failed to call Describe: %m");
×
UNCOV
756
        TAKE_PTR(op);
×
757

UNCOV
758
        return 0;
×
759
}
760

761
VERB(verb_check, "check", "[TARGET...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,
762
     "Check for updates");
763
static int verb_check(int argc, char *argv[], uintptr_t _data, void *userdata) {
8✔
764
        sd_bus *bus = ASSERT_PTR(userdata);
8✔
UNCOV
765
        _cleanup_(table_unrefp) Table *table = NULL;
×
766
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
8✔
767
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
8✔
768
        size_t n;
8✔
769
        unsigned remaining = 0;
8✔
770
        int r;
8✔
771

772
        r = ensure_targets(bus, argv + 1, &targets);
8✔
773
        if (r < 0)
8✔
UNCOV
774
                return log_error_errno(r, "Failed to find targets: %m");
×
775

776
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
8✔
777
        if (r < 0)
8✔
UNCOV
778
                return log_error_errno(r, "Failed to parse targets: %m");
×
779

780
        table = table_new("target", "update");
8✔
781
        if (!table)
8✔
UNCOV
782
                return log_oom();
×
783

784
        (void) table_set_sort(table, 0);
8✔
785

786
        r = sd_event_default(&event);
8✔
787
        if (r < 0)
8✔
UNCOV
788
                return log_error_errno(r, "Failed to get event loop: %m");
×
789

790
        r = sd_bus_attach_event(bus, event, 0);
8✔
791
        if (r < 0)
8✔
UNCOV
792
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
793

794
        r = sd_event_set_signal_exit(event, true);
8✔
795
        if (r < 0)
8✔
UNCOV
796
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
797

798
        for (size_t i = 0; i < n; i++) {
24✔
UNCOV
799
                _cleanup_(operation_freep) Operation *op = NULL;
×
800
                op = operation_new(table, bus, &remaining, target_paths[i], targets[i]);
16✔
801
                if (!op)
16✔
UNCOV
802
                        return log_oom();
×
803

804
                r = sd_bus_call_method_async(bus, NULL, bus_sysupdate_mgr->destination, target_paths[i], SYSUPDATE_TARGET_INTERFACE, "CheckNew", check_finished, op, NULL);
16✔
805
                if (r < 0)
16✔
UNCOV
806
                        return log_error_errno(r, "Failed to call CheckNew for target %s: %m", targets[i]);
×
807
                TAKE_PTR(op);
16✔
808

809
                remaining++;
16✔
810
        }
811

812
        r = sd_event_loop(event);
8✔
813
        if (r < 0)
8✔
UNCOV
814
                return log_error_errno(r, "Failed to start event loop: %m");
×
815

816
        if (table_isempty(table)) {
16✔
817
                log_info("No updates available.");
8✔
818
                return 0;
819
        }
820

UNCOV
821
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
×
822
}
823

824
#define UPDATE_PROGRESS_FAILED INT_MIN
825
#define UPDATE_PROGRESS_ACQUIRED (INT_MAX - 1)
826
#define UPDATE_PROGRESS_DONE INT_MAX
827
/* Make sure it doesn't overlap w/ errno values */
828
assert_cc(UPDATE_PROGRESS_FAILED < -ERRNO_MAX);
829

830
static int update_render_progress(sd_event_source *source, void *userdata) {
367✔
831
        OrderedHashmap *map = ASSERT_PTR(userdata);
367✔
832
        const char *target;
367✔
833
        void *p;
367✔
834
        unsigned total;
367✔
835
        size_t n;
367✔
836
        bool exiting;
367✔
837

838
        exiting = sd_event_get_state(sd_event_source_get_event(source)) == SD_EVENT_EXITING;
367✔
839

840
        total = 0;
367✔
841
        n = ordered_hashmap_size(map);
367✔
842

843
        if (n == 0)
367✔
844
                return 0;
367✔
845

846
        /* We're outputting lots of small strings to STDERR, which is unbuffered by default. So let's turn
847
         * on full buffering, so we pass this all to the TTY in one go, to make things more efficient */
848
        WITH_BUFFERED_STDERR;
309✔
849

850
        if (!terminal_is_dumb()) {
309✔
851
                for (size_t i = 0; i <= n; i++)
×
UNCOV
852
                        fputs("\n", stderr); /* Possibly scroll the terminal to make room (including total) */
×
853

UNCOV
854
                fprintf(stderr, "\e[%zuF", n+1); /* Go back */
×
855

856
                fputs("\e7", stderr); /* Save cursor position */
×
UNCOV
857
                fputs("\e[?25l", stderr); /* Hide cursor */
×
858
        }
859

860
        ORDERED_HASHMAP_FOREACH_KEY(p, target, map) {
618✔
861
                int progress = PTR_TO_INT(p);
309✔
862

863
                if (progress == UPDATE_PROGRESS_FAILED) {
309✔
864
                        clear_progress_bar_unbuffered(target);
×
865
                        fprintf(stderr, "%s: %s Unknown failure\n", target, RED_CROSS_MARK());
×
UNCOV
866
                        total += 100;
×
867
                } else if (progress == -EALREADY) {
309✔
868
                        clear_progress_bar_unbuffered(target);
×
869
                        fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK());
×
UNCOV
870
                        n--; /* Don't consider this target in the total */
×
871
                } else if (progress == -EUCLEAN) {
309✔
872
                        clear_progress_bar_unbuffered(target);
2✔
873
                        fprintf(stderr, "%s: %s Update is already acquired and partially installed. Vacuum it to try installing again.\n", target, RED_CROSS_MARK());
2✔
874
                        total += 100;
2✔
875
                } else if (progress < 0) {
307✔
876
                        clear_progress_bar_unbuffered(target);
8✔
877
                        fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress));
8✔
878
                        total += 100;
8✔
879
                } else if (progress == UPDATE_PROGRESS_ACQUIRED) {
299✔
880
                        clear_progress_bar_unbuffered(target);
96✔
881
                        fprintf(stderr, "%s: %s Installing\n", target, glyph(GLYPH_DOWNLOAD));
96✔
882
                        total += 100;
96✔
883
                } else if (progress == UPDATE_PROGRESS_DONE) {
203✔
884
                        clear_progress_bar_unbuffered(target);
24✔
885
                        fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK());
24✔
886
                        total += 100;
24✔
887
                } else {
888
                        draw_progress_bar_unbuffered(target, progress);
179✔
889
                        fputs("\n", stderr);
179✔
890
                        total += progress;
179✔
891
                }
892
        }
893

894
        if (n > 1) {
309✔
895
                if (exiting)
×
UNCOV
896
                        clear_progress_bar_unbuffered(target);
×
897
                else {
898
                        draw_progress_bar_unbuffered("Total", (double) total / n);
×
899
                        if (terminal_is_dumb())
×
UNCOV
900
                                fputs("\n", stderr);
×
901
                }
902
        }
903

904
        if (!terminal_is_dumb()) {
309✔
905
                if (exiting)
×
UNCOV
906
                        fputs("\e[?25h", stderr); /* Show cursor again */
×
907
                else
UNCOV
908
                        fputs("\e8", stderr); /* Restore cursor position */
×
909
        } else if (!exiting)
309✔
910
                fputs("------\n", stderr);
281✔
911

912
        return 0;
309✔
913
}
914

915
static int update_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
127✔
916
        Operation *op = ASSERT_PTR(userdata);
127✔
917
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
127✔
918
        const char *interface;
127✔
919
        uint32_t progress = UINT32_MAX;
127✔
920
        static const struct bus_properties_map prop_map[] = {
127✔
921
                { "Progress", "u", NULL, 0 },
922
                {}
923
        };
924
        int r;
127✔
925

926
        assert(m);
127✔
927

928
        r = sd_bus_message_read(m, "s", &interface);
127✔
929
        if (r < 0) {
127✔
UNCOV
930
                bus_log_parse_error_debug(r);
×
931
                return 0;
127✔
932
        }
933

934
        if (!streq(interface, "org.freedesktop.sysupdate1.Job"))
127✔
935
                return 0;
936

937
        r = bus_message_map_all_properties(m, prop_map, /* flags= */ 0, error, &progress);
127✔
938
        if (r < 0)
127✔
939
                return 0; /* map_all_properties does the debug logging internally... */
940

941
        if (progress == UINT_MAX)
127✔
942
                return 0;
943

944
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR((int) progress));
127✔
945
        if (r < 0)
127✔
UNCOV
946
                log_debug_errno(r, "Failed to update hashmap: %m");
×
947
        return 0;
948
}
949

950
static int update_install_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
26✔
951
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
26✔
952
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
26✔
953
        const sd_bus_error *e;
26✔
954
        const char *job_path;
26✔
955
        int r;
26✔
956

957
        assert(reply);
26✔
958

959
        e = sd_bus_message_get_error(reply);
26✔
960
        if (e) {
26✔
961
                r = -sd_bus_error_get_errno(e);
2✔
962

963
                r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(r));
2✔
964
                if (r < 0)
2✔
965
                        log_debug_errno(r, "Failed to update hashmap: %m");
26✔
966

967
                return 0;
968
        }
969

970
        r = sd_bus_message_read(reply, "sto", NULL, &op->job_id, &job_path);
24✔
971
        if (r < 0)
24✔
UNCOV
972
                return bus_log_parse_error(r);
×
973
        r = free_and_strdup_warn(&op->job_path, job_path);
24✔
974
        if (r < 0)
24✔
975
                return r;
976

977
        /* Update this job in the hashmap. */
978
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(UPDATE_PROGRESS_ACQUIRED));
24✔
979
        if (r < 0)
24✔
UNCOV
980
                log_debug_errno(r, "Failed to update hashmap: %m");
×
981

982
        /* Register for progress notifications for this Install() D-Bus call; previously
983
         * op->job_properties_slot was registered for progress notifications for the Acquire() D-Bus call. */
984
        sd_bus_slot_unref(TAKE_PTR(op->job_properties_slot));
24✔
985
        r = sd_bus_match_signal_async(
48✔
986
                        op->bus,
987
                        &op->job_properties_slot,
988
                        bus_sysupdate_mgr->destination,
24✔
989
                        job_path,
990
                        "org.freedesktop.DBus.Properties",
991
                        "PropertiesChanged",
992
                        update_properties_changed,
993
                        NULL,
994
                        op);
995
        if (r < 0)
24✔
UNCOV
996
                return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
×
997

998
        TAKE_PTR(op); /* update_install_finished/update_interrupted take ownership of the data */
24✔
999

1000
        return 0;
24✔
1001
}
1002

1003
static int update_install_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
26✔
1004
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
26✔
1005
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
26✔
1006
        uint64_t id;
26✔
1007
        int r, status;
26✔
1008

1009
        assert(m);
26✔
1010

1011
        r = sd_bus_message_read(m, "toi", &id, NULL, &status);
26✔
1012
        if (r < 0) {
26✔
UNCOV
1013
                bus_log_parse_error_debug(r);
×
1014
                return 0;
1015
        }
1016

1017
        if (id != op->job_id) {
26✔
1018
                TAKE_PTR(op);
2✔
1019
                return 0;
2✔
1020
        }
1021

1022
        if (status == 0) /* success */
24✔
1023
                status = UPDATE_PROGRESS_DONE;
24✔
1024
        else if (status > 0) /* exit status without errno */
×
UNCOV
1025
                status = UPDATE_PROGRESS_FAILED; /* i.e. EXIT_FAILURE */
×
1026
        /* else errno */
1027

1028
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(status));
24✔
1029
        if (r < 0)
24✔
1030
                log_debug_errno(r, "Failed to update hashmap: %m");
26✔
1031
        return 0;
1032
}
1033

1034
static int update_acquire_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
28✔
1035
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
28✔
1036
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
28✔
1037
        uint64_t id;
28✔
1038
        int r, status;
28✔
1039

1040
        assert(m);
28✔
1041

1042
        r = sd_bus_message_read(m, "toi", &id, NULL, &status);
28✔
1043
        if (r < 0) {
28✔
1044
                bus_log_parse_error_debug(r);
28✔
1045
                return 0;
1046
        }
1047

1048
        if (id != op->job_id) {
28✔
1049
                TAKE_PTR(op);
2✔
1050
                return 0;
2✔
1051
        }
1052

1053
        if (status == 0) /* success */
26✔
1054
                status = UPDATE_PROGRESS_ACQUIRED;
24✔
1055
        else if (status > 0) /* exit status without errno */
2✔
UNCOV
1056
                status = UPDATE_PROGRESS_FAILED; /* i.e. EXIT_FAILURE */
×
1057
        /* else errno */
1058

1059
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(status));
26✔
1060
        if (r < 0)
26✔
UNCOV
1061
                log_debug_errno(r, "Failed to update hashmap: %m");
×
1062

1063
        /* Renew the JobRemoved notification for the Install() call instead. */
1064
        sd_bus_slot_unref(op->job_finished_slot);
26✔
1065
        r = bus_match_signal_async(
26✔
1066
                        op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_install_finished, NULL, op);
1067
        if (r < 0)
26✔
UNCOV
1068
                return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
×
1069

1070
        /* With the Acquire() call finished, immediately call Install() to deploy the downloaded update.
1071
         * This reuses the same Operation struct so the progress reporting continues to be done in the same
1072
         * slot in the terminal. */
1073
        r = sd_bus_call_method_async(
52✔
1074
                        op->bus,
1075
                        NULL,
1076
                        bus_sysupdate_mgr->destination,
26✔
1077
                        op->target_path,
1078
                        SYSUPDATE_TARGET_INTERFACE,
1079
                        "Install",
1080
                        update_install_started,
1081
                        op,
1082
                        "st",
1083
                        op->requested_version,
26✔
1084
                        0LU);
1085
        if (r < 0)
26✔
UNCOV
1086
                return log_bus_error(r, NULL, op->target_id, "call Install");
×
1087
        TAKE_PTR(op);
26✔
1088

1089
        return 0;
26✔
1090
}
1091

UNCOV
1092
static int update_interrupted(sd_event_source *source, void *userdata) {
×
1093
        /* Since the event loop is exiting, we will never receive the JobRemoved
1094
         * signal. So, we must free the userdata here. */
1095
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
×
1096
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1097
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
×
UNCOV
1098
        int r;
×
1099

1100
        /* This call should work regardless of whether we’re cancelling the Acquire() call or the Install()
1101
         * call. */
1102
        r = sd_bus_call_method(op->bus,
×
1103
                               bus_sysupdate_mgr->destination,
×
UNCOV
1104
                               op->job_path,
×
1105
                               "org.freedesktop.sysupdate1.Job",
1106
                               "Cancel",
1107
                               &error,
1108
                               /* ret_reply= */ NULL,
1109
                               NULL);
1110
        if (r < 0)
×
UNCOV
1111
                return log_bus_error(r, &error, NULL, "call Cancel");
×
1112

1113
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(-ECANCELED));
×
1114
        if (r < 0)
×
UNCOV
1115
                log_debug_errno(r, "Failed to update hashmap: %m");
×
1116

1117
        return 0;
1118
}
1119

1120
static int update_acquire_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
28✔
1121
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
28✔
1122
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
28✔
1123
        const sd_bus_error *e;
28✔
1124
        _cleanup_free_ char *key = NULL;
28✔
1125
        const char *new_version, *job_path;
28✔
1126
        int r;
28✔
1127

1128
        assert(reply);
28✔
1129

1130
        e = sd_bus_message_get_error(reply);
28✔
1131
        if (e) {
28✔
1132
                r = -sd_bus_error_get_errno(e);
2✔
1133

1134
                key = strdup(op->target_id);
2✔
1135
                if (!key)
2✔
UNCOV
1136
                        return log_oom();
×
1137
                r = ordered_hashmap_put(map, key, INT_TO_PTR(r));
2✔
1138
                if (r < 0)
2✔
UNCOV
1139
                        return log_error_errno(r, "Failed to update hashmap: %m");
×
1140
                TAKE_PTR(key);
28✔
1141

1142
                return 0;
1143
        }
1144

1145
        r = sd_bus_message_read(reply, "sto", &new_version, &op->job_id, &job_path);
26✔
1146
        if (r < 0)
26✔
UNCOV
1147
                return bus_log_parse_error(r);
×
1148
        op->job_path = strdup(job_path);
26✔
1149
        if (!op->job_path)
26✔
UNCOV
1150
                return log_oom();
×
1151

1152
        /* Store the version for the subsequent Install() call */
1153
        op->acquired_version = strdup(new_version);
26✔
1154
        if (!op->acquired_version)
26✔
UNCOV
1155
                return log_oom();
×
1156

1157
        if (isempty(new_version))
26✔
UNCOV
1158
                new_version = "latest";
×
1159

1160
        /* Register this job into the hashmap. This will give it a progress bar */
1161
        if (strchr(op->target_id, '@'))
26✔
UNCOV
1162
                key = strdup(op->target_id);
×
1163
        else
1164
                key = strjoin(op->target_id, "@", new_version);
26✔
1165
        if (!key)
26✔
UNCOV
1166
                return log_oom();
×
1167
        r = ordered_hashmap_put(map, key, INT_TO_PTR(0)); /* takes ownership of key */
26✔
1168
        if (r < 0)
26✔
UNCOV
1169
                return log_error_errno(r, "Failed to add target to tracking map: %m");
×
1170
        op->target_id = TAKE_PTR(key); /* just borrowing */
26✔
1171

1172
        /* Cancel the job if the event loop exits */
1173
        r = sd_event_add_exit(op->event, &op->job_interrupt_source, update_interrupted, op);
26✔
1174
        if (r < 0)
26✔
UNCOV
1175
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
1176

1177
        /* We need to cancel the job before the final iteration of the renderer runs */
1178
        r = sd_event_source_set_priority(op->job_interrupt_source, SD_EVENT_PRIORITY_IMPORTANT);
26✔
1179
        if (r < 0)
26✔
UNCOV
1180
                return log_error_errno(r, "Failed to set interrupt priority: %m");
×
1181

1182
        /* Register for progress notifications */
1183
        r = sd_bus_match_signal_async(
52✔
1184
                        op->bus,
1185
                        &op->job_properties_slot,
1186
                        bus_sysupdate_mgr->destination,
26✔
1187
                        job_path,
1188
                        "org.freedesktop.DBus.Properties",
1189
                        "PropertiesChanged",
1190
                        update_properties_changed,
1191
                        NULL,
1192
                        op);
1193
        if (r < 0)
26✔
UNCOV
1194
                return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
×
1195

1196
        TAKE_PTR(op); /* update_acquire_finished/update_interrupted take ownership of the data */
26✔
1197

1198
        return 0;
26✔
1199
}
1200

1201
static int do_update(sd_bus *bus, char **targets) {
28✔
1202
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
28✔
1203
        _cleanup_(sd_event_source_unrefp) sd_event_source *render_exit = NULL;
28✔
1204
        _cleanup_ordered_hashmap_free_ OrderedHashmap *map = NULL;
28✔
1205
        _cleanup_strv_free_ char **versions = NULL, **target_paths = NULL;
28✔
1206
        size_t n;
28✔
1207
        unsigned remaining = 0;
28✔
1208
        void *p;
28✔
1209
        bool did_anything = false;
28✔
1210
        int r;
28✔
1211

1212
        assert(bus);
28✔
1213
        assert(targets);
28✔
1214

1215
        r = parse_targets(targets, &n, &target_paths, &versions);
28✔
1216
        if (r < 0)
28✔
UNCOV
1217
                return log_error_errno(r, "Could not parse targets: %m");
×
1218

1219
        map = ordered_hashmap_new(&string_hash_ops_free);
28✔
1220
        if (!map)
28✔
UNCOV
1221
                return log_oom();
×
1222

1223
        r = sd_event_default(&event);
28✔
1224
        if (r < 0)
28✔
UNCOV
1225
                return log_error_errno(r, "Failed to get event loop: %m");
×
1226

1227
        r = sd_bus_attach_event(bus, event, 0);
28✔
1228
        if (r < 0)
28✔
UNCOV
1229
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
1230

1231
        r = sd_event_set_signal_exit(event, true);
28✔
1232
        if (r < 0)
28✔
UNCOV
1233
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
1234

1235
        for (size_t i = 0; i < n; i++) {
56✔
UNCOV
1236
                _cleanup_(operation_freep) Operation *op = NULL;
×
1237
                op = operation_new(map, bus, &remaining, target_paths[i], targets[i]);
28✔
1238
                if (!op)
28✔
UNCOV
1239
                        return log_oom();
×
1240

1241
                /* Sign up for notification when the associated job finishes */
1242
                r = bus_match_signal_async(
28✔
1243
                                op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_acquire_finished, NULL, op);
1244
                if (r < 0)
28✔
UNCOV
1245
                        return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
×
1246

1247
                r = sd_bus_call_method_async(
56✔
1248
                                bus,
1249
                                NULL,
1250
                                bus_sysupdate_mgr->destination,
28✔
1251
                                target_paths[i],
28✔
1252
                                SYSUPDATE_TARGET_INTERFACE,
1253
                                "Acquire",
1254
                                update_acquire_started,
1255
                                op,
1256
                                "st",
1257
                                versions[i],
28✔
1258
                                0LU);
1259
                if (r < 0)
28✔
UNCOV
1260
                        return log_bus_error(r, NULL, targets[i], "call Acquire");
×
1261

1262
                op->requested_version = strdup(versions[i]);
28✔
1263
                if (!op->requested_version)
28✔
UNCOV
1264
                        return log_oom();
×
1265

1266
                TAKE_PTR(op);
28✔
1267

1268
                remaining++;
28✔
1269
        }
1270

1271
        /* Set up the rendering */
1272
        r = sd_event_add_post(event, NULL, update_render_progress, map);
28✔
1273
        if (r < 0)
28✔
UNCOV
1274
                return log_error_errno(r, "Failed to add progress rendering callback: %m");
×
1275

1276
        r = sd_event_add_exit(event, &render_exit, update_render_progress, map);
28✔
1277
        if (r < 0)
28✔
UNCOV
1278
                return log_error_errno(r, "Failed to add exit callback: %m");
×
1279

1280
        r = sd_event_source_set_priority(render_exit, SD_EVENT_PRIORITY_IDLE);
28✔
1281
        if (r < 0)
28✔
UNCOV
1282
                return log_error_errno(r, "Failed to set priority of update job");
×
1283

1284
        r = sd_event_loop(event);
28✔
1285
        if (r < 0)
28✔
UNCOV
1286
                return log_error_errno(r, "Failed to start event loop");
×
1287

1288
        ORDERED_HASHMAP_FOREACH(p, map) {
52✔
1289
                r = PTR_TO_INT(p);
28✔
1290
                if (r == -EALREADY)
28✔
UNCOV
1291
                        continue;
×
1292
                if (r == UPDATE_PROGRESS_FAILED)
28✔
1293
                        return EXIT_FAILURE;
4✔
1294
                if (r < 0)
28✔
1295
                        return r;
1296

1297
                did_anything = true;
1298
        }
1299

1300
        return did_anything ? 1 : 0;
24✔
1301
}
1302

1303
VERB(verb_update, "update", "[TARGET[@VERSION]...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,
1304
     "Install updates");
1305
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
28✔
1306
        sd_bus *bus = ASSERT_PTR(userdata);
28✔
1307
        _cleanup_strv_free_ char **targets = NULL;
28✔
1308
        bool did_anything = false;
28✔
1309
        int r;
28✔
1310

1311
        r = ensure_targets(bus, argv + 1, &targets);
28✔
1312
        if (r < 0)
28✔
UNCOV
1313
                return log_error_errno(r, "Could not find targets: %m");
×
1314

1315
        r = do_update(bus, targets);
28✔
1316
        if (r < 0)
28✔
1317
                return r;
1318
        if (r > 0)
24✔
1319
                did_anything = true;
24✔
1320

1321
        if (!arg_reboot)
24✔
1322
                return 0;
1323

1324
        if (did_anything)
×
UNCOV
1325
                return reboot_now();
×
1326

UNCOV
1327
        log_info("Nothing was updated... skipping reboot.");
×
1328
        return 0;
1329
}
1330

1331
static int do_vacuum(sd_bus *bus, const char *target, const char *path) {
×
1332
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1333
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1334
        uint32_t count, disabled;
×
UNCOV
1335
        int r;
×
1336

1337
        r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination, path, SYSUPDATE_TARGET_INTERFACE, "Vacuum", &error, &reply, NULL);
×
1338
        if (r < 0)
×
UNCOV
1339
                return log_bus_error(r, &error, target, "call Vacuum");
×
1340

1341
        r = sd_bus_message_read(reply, "uu", &count, &disabled);
×
1342
        if (r < 0)
×
UNCOV
1343
                return bus_log_parse_error(r);
×
1344

1345
        if (count > 0 && disabled > 0)
×
UNCOV
1346
                log_info("Deleted %u instance(s) and %u disabled transfer(s) of %s.",
×
1347
                         count, disabled, target);
1348
        else if (count > 0)
×
1349
                log_info("Deleted %u instance(s) of %s.", count, target);
×
1350
        else if (disabled > 0)
×
UNCOV
1351
                log_info("Deleted %u disabled transfer(s) of %s.", disabled, target);
×
1352
        else
UNCOV
1353
                log_info("Found nothing to delete for %s.", target);
×
1354

UNCOV
1355
        return count + disabled > 0 ? 1 : 0;
×
1356
}
1357

1358
VERB(verb_vacuum, "vacuum", "[TARGET...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,
1359
     "Clean up old updates");
1360
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1361
        sd_bus *bus = ASSERT_PTR(userdata);
×
1362
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
×
1363
        size_t n;
×
UNCOV
1364
        int r;
×
1365

1366
        r = ensure_targets(bus, argv + 1, &targets);
×
1367
        if (r < 0)
×
UNCOV
1368
                return log_error_errno(r, "Failed to find targets: %m");
×
1369

1370
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
×
1371
        if (r < 0)
×
UNCOV
1372
                return log_error_errno(r, "Failed to parse targets: %m");
×
1373

1374
        for (size_t i = 0; i < n; i++) {
×
1375
                r = do_vacuum(bus, targets[i], target_paths[i]);
×
UNCOV
1376
                if (r < 0)
×
1377
                        return r;
1378
        }
1379
        return 0;
1380
}
1381

1382
typedef struct Feature {
1383
        char *name;
1384
        char *description;
1385
        bool enabled;
1386
        char *documentation;
1387
        char **transfers;
1388
} Feature;
1389

1390
static void feature_done(Feature *f) {
×
1391
        assert(f);
×
1392
        f->name = mfree(f->name);
×
1393
        f->description = mfree(f->description);
×
1394
        f->documentation = mfree(f->documentation);
×
1395
        f->transfers = strv_free(f->transfers);
×
UNCOV
1396
}
×
1397

1398
static int describe_feature(sd_bus *bus, const char *feature, Feature *ret) {
×
1399
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1400
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1401
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1402
        _cleanup_(feature_done) Feature f = {};
×
1403
        char *json;
×
UNCOV
1404
        int r;
×
1405

UNCOV
1406
        static const sd_json_dispatch_field dispatch_table[] = {
×
1407
                { "name",             SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, name),          SD_JSON_MANDATORY },
1408
                { "description",      SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, description),   0                 },
1409
                { "enabled",          SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Feature, enabled),       SD_JSON_MANDATORY },
1410
                { "documentationUrl", SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, documentation), 0                 },
1411
                { "transfers",        SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_strv,    offsetof(Feature, transfers),     0                 },
1412
                {}
1413
        };
1414

1415
        assert(bus);
×
1416
        assert(feature);
×
UNCOV
1417
        assert(ret);
×
1418

1419
        r = sd_bus_call_method(bus,
×
UNCOV
1420
                               bus_sysupdate_mgr->destination,
×
1421
                               SYSUPDATE_HOST_PATH,
1422
                               SYSUPDATE_TARGET_INTERFACE,
1423
                               "DescribeFeature",
1424
                               &error,
1425
                               &reply,
1426
                               "st",
1427
                               feature,
1428
                               UINT64_C(0));
1429
        if (r < 0)
×
UNCOV
1430
                return log_bus_error(r, &error, "host", "lookup feature");
×
1431

1432
        r = sd_bus_message_read_basic(reply, 's', &json);
×
1433
        if (r < 0)
×
UNCOV
1434
                return bus_log_parse_error(r);
×
1435

1436
        r = sd_json_parse(json, 0, &v, NULL, NULL);
×
1437
        if (r < 0)
×
UNCOV
1438
                return log_error_errno(r, "Failed to parse JSON: %m");
×
1439

1440
        r = sd_json_dispatch(v, dispatch_table, 0, &f);
×
1441
        if (r < 0)
×
UNCOV
1442
                return log_error_errno(r, "Failed to dispatch JSON: %m");
×
1443

1444
        *ret = TAKE_STRUCT(f);
×
UNCOV
1445
        return 0;
×
1446
}
1447

1448
static int list_features(sd_bus *bus) {
×
1449
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1450
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1451
        _cleanup_strv_free_ char **features = NULL;
×
1452
        _cleanup_(table_unrefp) Table *table = NULL;
×
UNCOV
1453
        int r;
×
1454

UNCOV
1455
        assert(bus);
×
1456

1457
        table = table_new("", "feature", "description");
×
1458
        if (!table)
×
UNCOV
1459
                return log_oom();
×
1460

1461
        r = sd_bus_call_method(bus,
×
UNCOV
1462
                               bus_sysupdate_mgr->destination,
×
1463
                               SYSUPDATE_HOST_PATH,
1464
                               SYSUPDATE_TARGET_INTERFACE,
1465
                               "ListFeatures",
1466
                               &error,
1467
                               &reply,
1468
                               "t",
1469
                               UINT64_C(0));
1470
        if (r < 0)
×
UNCOV
1471
                return log_bus_error(r, &error, "host", "lookup feature");
×
1472

1473
        r = sd_bus_message_read_strv(reply, &features);
×
1474
        if (r < 0)
×
UNCOV
1475
                return bus_log_parse_error(r);
×
1476

1477
        STRV_FOREACH(feature, features) {
×
1478
                _cleanup_(feature_done) Feature f = {};
×
UNCOV
1479
                _cleanup_free_ char *name_link = NULL;
×
1480

1481
                r = describe_feature(bus, *feature, &f);
×
UNCOV
1482
                if (r < 0)
×
1483
                        return r;
1484

1485
                if (urlify_enabled() && f.documentation) {
×
1486
                        name_link = strjoin(f.name, glyph(GLYPH_EXTERNAL_LINK));
×
1487
                        if (!name_link)
×
UNCOV
1488
                                return log_oom();
×
1489
                }
1490

UNCOV
1491
                r = table_add_many(table,
×
1492
                                   TABLE_BOOLEAN_CHECKMARK, f.enabled,
1493
                                   TABLE_SET_COLOR, ansi_highlight_green_red(f.enabled),
1494
                                   TABLE_STRING, name_link ?: f.name,
1495
                                   TABLE_SET_URL, f.documentation,
1496
                                   TABLE_STRING, f.description);
1497
                if (r < 0)
×
UNCOV
1498
                        return table_log_add_error(r);
×
1499
        }
1500

UNCOV
1501
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
×
1502
}
1503

1504
VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, VERB_ONLINE_ONLY,
1505
     "List and inspect optional features on host OS");
1506
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1507
        sd_bus *bus = ASSERT_PTR(userdata);
×
1508
        _cleanup_(table_unrefp) Table *table = NULL;
×
1509
        _cleanup_(feature_done) Feature f = {};
×
UNCOV
1510
        int r;
×
1511

1512
        if (argc == 1)
×
UNCOV
1513
                return list_features(bus);
×
1514

1515
        table = table_new_vertical();
×
1516
        if (!table)
×
UNCOV
1517
                return log_oom();
×
1518

1519
        r = describe_feature(bus, argv[1], &f);
×
UNCOV
1520
        if (r < 0)
×
1521
                return r;
1522

UNCOV
1523
        r = table_add_many(table,
×
1524
                           TABLE_FIELD, "Name",
1525
                           TABLE_STRING, f.name,
1526
                           TABLE_FIELD, "Enabled",
1527
                           TABLE_BOOLEAN, f.enabled);
1528
        if (r < 0)
×
UNCOV
1529
                return table_log_add_error(r);
×
1530

1531
        if (f.description) {
×
1532
                r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f.description);
×
1533
                if (r < 0)
×
UNCOV
1534
                        return table_log_add_error(r);
×
1535
        }
1536

1537
        if (f.documentation) {
×
UNCOV
1538
                r = table_add_many(table,
×
1539
                                   TABLE_FIELD, "Documentation",
1540
                                   TABLE_STRING, f.documentation,
1541
                                   TABLE_SET_URL, f.documentation);
1542
                if (r < 0)
×
UNCOV
1543
                        return table_log_add_error(r);
×
1544
        }
1545

1546
        if (!strv_isempty(f.transfers)) {
×
1547
                r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, f.transfers);
×
1548
                if (r < 0)
×
UNCOV
1549
                        return table_log_add_error(r);
×
1550
        }
1551

UNCOV
1552
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false);
×
1553
}
1554

1555
VERB(verb_enable, "enable", "FEATURE...", 2, VERB_ANY, VERB_ONLINE_ONLY,
1556
     "Enable optional feature on host OS");
1557
VERB(verb_enable, "disable", "FEATURE...", 2, VERB_ANY, VERB_ONLINE_ONLY,
1558
     "Disable optional feature on host OS");
1559
static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1560
        sd_bus *bus = ASSERT_PTR(userdata);
×
1561
        bool did_anything = false, enable;
×
1562
        char **features;
×
UNCOV
1563
        int r;
×
1564

1565
        enable = streq(argv[0], "enable");
×
UNCOV
1566
        features = strv_skip(argv, 1);
×
1567

1568
        STRV_FOREACH(feature, features) {
×
UNCOV
1569
                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1570

1571
                r = sd_bus_call_method(bus,
×
UNCOV
1572
                                       bus_sysupdate_mgr->destination,
×
1573
                                       SYSUPDATE_HOST_PATH,
1574
                                       SYSUPDATE_TARGET_INTERFACE,
1575
                                       "SetFeatureEnabled",
1576
                                       &error,
1577
                                       /* ret_reply= */ NULL,
1578
                                       "sit",
1579
                                       *feature,
1580
                                       (int) enable,
1581
                                       UINT64_C(0));
1582
                if (r < 0)
×
UNCOV
1583
                        return log_bus_error(r, &error, "host", "call SetFeatureEnabled");
×
1584
        }
1585

UNCOV
1586
        if (!arg_now) /* We weren't asked to apply the changes, so we're done! */
×
1587
                return 0;
1588

1589
        if (enable) {
×
1590
                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1591
                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1592
                _cleanup_free_ char *target = NULL;
×
UNCOV
1593
                char *version = NULL;
×
1594

1595
                /* We're downloading the new feature into the "current" version, which is either going to be
1596
                 * the currently booted version or it's going to be a pending update that has already been
1597
                 * installed and is just waiting for us to reboot into it. */
1598

1599
                r = sd_bus_call_method(bus,
×
UNCOV
1600
                                       bus_sysupdate_mgr->destination,
×
1601
                                       SYSUPDATE_HOST_PATH,
1602
                                       SYSUPDATE_TARGET_INTERFACE,
1603
                                       "GetVersion",
1604
                                       &error,
1605
                                       &reply,
1606
                                       NULL);
1607
                if (r < 0)
×
UNCOV
1608
                        return log_bus_error(r, &error, "host", "get current version");
×
1609

1610
                r = sd_bus_message_read_basic(reply, 's', &version);
×
1611
                if (r < 0)
×
UNCOV
1612
                        return bus_log_parse_error(r);
×
1613

1614
                target = strjoin("host@", version);
×
1615
                if (!target)
×
UNCOV
1616
                        return log_oom();
×
1617

UNCOV
1618
                r = do_update(bus, STRV_MAKE(target));
×
1619
        } else
1620
                r = do_vacuum(bus, "host", SYSUPDATE_HOST_PATH);
×
UNCOV
1621
        if (r < 0)
×
1622
                return r;
1623
        if (r > 0)
×
UNCOV
1624
                did_anything = true;
×
1625

1626
        if (arg_reboot && did_anything)
×
1627
                return reboot_now();
×
1628
        else if (did_anything)
×
UNCOV
1629
                log_info("Feature(s) %s.", enable ? "downloaded" : "deleted");
×
1630
        else
UNCOV
1631
                log_info("Nothing %s%s.",
×
1632
                         enable ? "downloaded" : "deleted",
1633
                         arg_reboot ? ", skipping reboot" :"");
1634

1635
        return 0;
1636
}
1637

1638
static int help(void) {
×
1639
        _cleanup_free_ char *link = NULL;
×
1640
        _cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL;
×
UNCOV
1641
        int r;
×
1642

1643
        r = terminal_urlify_man("updatectl", "1", &link);
×
1644
        if (r < 0)
×
UNCOV
1645
                return log_oom();
×
1646

1647
        r = verbs_get_help_table(&verbs);
×
UNCOV
1648
        if (r < 0)
×
1649
                return r;
1650

1651
        r = option_parser_get_help_table_group("Verbs", &verbs2);
×
UNCOV
1652
        if (r < 0)
×
1653
                return r;
1654

1655
        r = option_parser_get_help_table(&options);
×
UNCOV
1656
        if (r < 0)
×
1657
                return r;
1658

UNCOV
1659
        (void) table_sync_column_widths(0, verbs, verbs2, options);
×
1660

UNCOV
1661
        printf("%s [OPTIONS...] [VERSION]\n"
×
1662
               "\n%sManage system updates.%s\n"
1663
               "\n%sCommands:%s\n",
1664
               program_invocation_short_name,
1665
               ansi_highlight(),
1666
               ansi_normal(),
1667
               ansi_underline(),
1668
               ansi_normal());
1669

1670
        r = table_print_or_warn(verbs);
×
UNCOV
1671
        if (r < 0)
×
1672
                return r;
1673
        r = table_print_or_warn(verbs2);
×
UNCOV
1674
        if (r < 0)
×
1675
                return r;
1676

UNCOV
1677
        printf("\n%sOptions:%s\n",
×
1678
               ansi_underline(),
1679
               ansi_normal());
1680

1681
        r = table_print_or_warn(options);
×
UNCOV
1682
        if (r < 0)
×
1683
                return r;
1684

UNCOV
1685
        printf("\nSee the %s for details.\n", link);
×
1686
        return 0;
1687
}
1688

UNCOV
1689
VERB_COMMON_HELP_HIDDEN(help);
×
1690

1691
static int parse_argv(int argc, char *argv[], char ***ret_args) {
96✔
1692
        assert(argc >= 0);
96✔
1693
        assert(argv);
96✔
1694

1695
        OptionParser opts = { argc, argv };
96✔
1696

1697
        FOREACH_OPTION_OR_RETURN(c, &opts)
192✔
UNCOV
1698
                switch (c) {
×
1699

1700
                OPTION_LONG("reboot", NULL, "Reboot after updating to newer version"):
×
1701
                        arg_reboot = true;
×
UNCOV
1702
                        break;
×
1703

1704
                OPTION_LONG("offline", NULL, "Do not fetch metadata from the network"):
×
1705
                        arg_offline = true;
×
UNCOV
1706
                        break;
×
1707

1708
                OPTION_LONG("now", NULL, "Download/delete resources immediately"):
×
1709
                        arg_now = true;
×
UNCOV
1710
                        break;
×
1711

1712
                OPTION_COMMON_HOST:
×
1713
                        arg_transport = BUS_TRANSPORT_REMOTE;
×
1714
                        arg_host = opts.arg;
×
UNCOV
1715
                        break;
×
1716

1717
                OPTION_COMMON_NO_PAGER:
×
1718
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
1719
                        break;
×
1720

1721
                OPTION_COMMON_NO_LEGEND:
×
1722
                        arg_legend = false;
×
UNCOV
1723
                        break;
×
1724

UNCOV
1725
                OPTION_GROUP("Verbs"): {}
×
1726

1727
                OPTION_COMMON_NO_ASK_PASSWORD:
×
UNCOV
1728
                        arg_ask_password = false;
×
1729
                        break;
×
1730

UNCOV
1731
                OPTION_COMMON_HELP:
×
UNCOV
1732
                        return help();
×
1733

UNCOV
1734
                OPTION_COMMON_VERSION:
×
UNCOV
1735
                        return version();
×
1736
                }
1737

1738
        *ret_args = option_parser_get_args(&opts);
96✔
1739
        return 1;
96✔
1740
}
1741

1742
static int run(int argc, char *argv[]) {
96✔
1743
        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
96✔
1744
        int r;
96✔
1745

1746
        setlocale(LC_ALL, "");
96✔
1747
        log_setup();
96✔
1748

1749
        (void) signal(SIGWINCH, columns_lines_cache_reset);
96✔
1750

1751
        char **args = NULL;
96✔
1752
        r = parse_argv(argc, argv, &args);
96✔
1753
        if (r <= 0)
96✔
1754
                return r;
1755

1756
        r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
96✔
1757
        if (r < 0)
96✔
UNCOV
1758
                return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
×
1759

1760
        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
96✔
1761

1762
        (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
96✔
1763

1764
        return dispatch_verb(args, bus);
96✔
1765
}
1766

1767
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
96✔
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