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

systemd / systemd / 23966986054

03 Apr 2026 09:22PM UTC coverage: 72.107% (-0.3%) from 72.362%
23966986054

push

github

daandemeyer
fd-util: Add missing assert()

1 of 1 new or added line in 1 file covered. (100.0%)

7891 existing lines in 120 files now uncovered.

318256 of 441368 relevant lines covered (72.11%)

1186882.96 hits per line

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

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

3
#include <getopt.h>
4
#include <locale.h>
5

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

10
#include "alloc-util.h"
11
#include "build.h"
12
#include "bus-error.h"
13
#include "bus-label.h"
14
#include "bus-locator.h"
15
#include "bus-map-properties.h"
16
#include "bus-util.h"
17
#include "errno-util.h"
18
#include "format-table.h"
19
#include "hashmap.h"
20
#include "json-util.h"
21
#include "main-func.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 BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
39
static const char *arg_host = NULL;
40

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

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

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

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

60
typedef struct Operation {
61
        void *userdata;
62

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

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

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

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

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

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

93
        free(p->job_path);
78✔
94
        free(p->acquired_version);
78✔
95
        free(p->requested_version);
78✔
96

97
        sd_event_source_disable_unref(p->job_interrupt_source);
78✔
98
        sd_bus_slot_unref(p->job_properties_slot);
78✔
99
        sd_bus_slot_unref(p->job_finished_slot);
78✔
100

101
        return mfree(p);
78✔
102
}
103

104
DEFINE_TRIVIAL_CLEANUP_FUNC(Operation*, operation_free);
300✔
105

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

113
        _cleanup_(operation_freep) Operation *o = NULL;
78✔
114

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

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

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

136
        assert(bus);
68✔
137
        assert(ret_targets);
68✔
138

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

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

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

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

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

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

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

176
        *ret_targets = TAKE_PTR(targets);
68✔
177
        return 0;
68✔
178
}
179

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

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

192
        assert(in);
92✔
193
        assert(ret_bus_path);
92✔
194
        assert(ret_version);
92✔
195

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

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

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

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

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

230
        assert(ret_bus_paths);
68✔
231
        assert(ret_n);
68✔
232

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

236
        STRV_FOREACH(id, targets) {
144✔
237
                _cleanup_free_ char *bus_path = NULL, *version = NULL;
76✔
238

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

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

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

252
                r = strv_extend(&versions, strempty(version));
152✔
253
                if (r < 0)
76✔
UNCOV
254
                        return log_oom();
×
255

256
                n++;
76✔
257
        }
258

259
        *ret_n = n;
68✔
260
        *ret_bus_paths = TAKE_PTR(bus_paths);
68✔
261
        if (ret_versions)
68✔
262
                *ret_versions = TAKE_PTR(versions);
22✔
263
        return 0;
264
}
265

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

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

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

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

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

293
        assert(bus);
38✔
294

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

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

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

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

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

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

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

335
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
38✔
336
}
337

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

423
        assert(reply);
40✔
424

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

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

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

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

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

452
        return 0;
453
}
454

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

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

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

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

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

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

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

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

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

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

521
                remaining++;
40✔
522
        }
523

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

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

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

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

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

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

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

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

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

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

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

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

588
        JSON_VARIANT_ARRAY_FOREACH(entry, json) {
48✔
589
                assert(sd_json_variant_is_object(entry));
40✔
590
                const char *key;
40✔
591
                sd_json_variant *value;
40✔
592

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

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

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

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

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

629
                        if (streq(key, "ptflags"))
360✔
630
                                type = TABLE_UINT64_HEX;
631
                        else if (streq(key, "size"))
320✔
632
                                type = TABLE_SIZE;
633
                        else if (streq(key, "mode"))
320✔
634
                                type = TABLE_MODE;
635
                        else if (streq(key, "mtime"))
280✔
636
                                type = TABLE_TIMESTAMP;
40✔
637

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

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

647
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
54✔
648
        sd_bus *bus = ASSERT_PTR(userdata);
54✔
649
        _cleanup_free_ char *target_path = NULL, *version = NULL;
54✔
650
        int r;
54✔
651

652
        if (argc == 1)
54✔
653
                return list_targets(bus);
38✔
654

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

659
        if (version)
16✔
660
                return describe(bus, target_path, version);
8✔
661
        else
662
                return list_versions(bus, target_path);
8✔
663
}
664

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

676
        assert(reply);
×
677

UNCOV
678
        e = sd_bus_message_get_error(reply);
×
679
        if (e)
×
680
                return log_bus_error(0, e, NULL, "call Describe");
×
681

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

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

UNCOV
698
        r = sd_bus_message_read_basic(reply, 's', &current);
×
699
        if (r < 0)
×
700
                return bus_log_parse_error(r);
×
701

702
        if (urlify_enabled() && !strv_isempty(v.changelog))
×
UNCOV
703
                lnk = glyph(GLYPH_EXTERNAL_LINK);
×
704

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

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

718
        return 0;
719
}
720

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

727
        assert(reply);
16✔
728

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

733
        r = sd_bus_message_read(reply, "s", &new_version);
16✔
734
        if (r < 0)
16✔
UNCOV
735
                return bus_log_parse_error(r);
×
736

737
        if (isempty(new_version))
32✔
738
                return 0;
739

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

UNCOV
755
        return 0;
×
756
}
757

758
static int verb_check(int argc, char *argv[], uintptr_t _data, void *userdata) {
8✔
759
        sd_bus *bus = ASSERT_PTR(userdata);
8✔
UNCOV
760
        _cleanup_(table_unrefp) Table *table = NULL;
×
761
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
8✔
762
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
8✔
763
        size_t n;
8✔
764
        unsigned remaining = 0;
8✔
765
        int r;
8✔
766

767
        r = ensure_targets(bus, argv + 1, &targets);
8✔
768
        if (r < 0)
8✔
UNCOV
769
                return log_error_errno(r, "Failed to find targets: %m");
×
770

771
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
8✔
772
        if (r < 0)
8✔
UNCOV
773
                return log_error_errno(r, "Failed to parse targets: %m");
×
774

775
        table = table_new("target", "update");
8✔
776
        if (!table)
8✔
UNCOV
777
                return log_oom();
×
778

779
        (void) table_set_sort(table, 0);
8✔
780

781
        r = sd_event_default(&event);
8✔
782
        if (r < 0)
8✔
UNCOV
783
                return log_error_errno(r, "Failed to get event loop: %m");
×
784

785
        r = sd_bus_attach_event(bus, event, 0);
8✔
786
        if (r < 0)
8✔
UNCOV
787
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
788

789
        r = sd_event_set_signal_exit(event, true);
8✔
790
        if (r < 0)
8✔
791
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
792

793
        for (size_t i = 0; i < n; i++) {
24✔
794
                _cleanup_(operation_freep) Operation *op = NULL;
×
795
                op = operation_new(table, bus, &remaining, target_paths[i], targets[i]);
16✔
796
                if (!op)
16✔
UNCOV
797
                        return log_oom();
×
798

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

804
                remaining++;
16✔
805
        }
806

807
        r = sd_event_loop(event);
8✔
808
        if (r < 0)
8✔
UNCOV
809
                return log_error_errno(r, "Failed to start event loop: %m");
×
810

811
        if (table_isempty(table)) {
16✔
812
                log_info("No updates available.");
8✔
813
                return 0;
8✔
814
        }
815

UNCOV
816
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
×
817
}
818

819
#define UPDATE_PROGRESS_FAILED INT_MIN
820
#define UPDATE_PROGRESS_ACQUIRED (INT_MAX - 1)
821
#define UPDATE_PROGRESS_DONE INT_MAX
822
/* Make sure it doesn't overlap w/ errno values */
823
assert_cc(UPDATE_PROGRESS_FAILED < -ERRNO_MAX);
824

825
static int update_render_progress(sd_event_source *source, void *userdata) {
254✔
826
        OrderedHashmap *map = ASSERT_PTR(userdata);
254✔
827
        const char *target;
254✔
828
        void *p;
254✔
829
        unsigned total;
254✔
830
        size_t n;
254✔
831
        bool exiting;
254✔
832

833
        exiting = sd_event_get_state(sd_event_source_get_event(source)) == SD_EVENT_EXITING;
254✔
834

835
        total = 0;
254✔
836
        n = ordered_hashmap_size(map);
254✔
837

838
        if (n == 0)
254✔
839
                return 0;
254✔
840

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

845
        if (!terminal_is_dumb()) {
210✔
846
                for (size_t i = 0; i <= n; i++)
×
UNCOV
847
                        fputs("\n", stderr); /* Possibly scroll the terminal to make room (including total) */
×
848

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

UNCOV
851
                fputs("\e7", stderr); /* Save cursor position */
×
UNCOV
852
                fputs("\e[?25l", stderr); /* Hide cursor */
×
853
        }
854

855
        ORDERED_HASHMAP_FOREACH_KEY(p, target, map) {
420✔
856
                int progress = PTR_TO_INT(p);
210✔
857

858
                if (progress == UPDATE_PROGRESS_FAILED) {
210✔
UNCOV
859
                        clear_progress_bar_unbuffered(target);
×
UNCOV
860
                        fprintf(stderr, "%s: %s Unknown failure\n", target, RED_CROSS_MARK());
×
UNCOV
861
                        total += 100;
×
862
                } else if (progress == -EALREADY) {
210✔
863
                        clear_progress_bar_unbuffered(target);
22✔
864
                        fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK());
22✔
865
                        n--; /* Don't consider this target in the total */
22✔
866
                } else if (progress < 0) {
188✔
UNCOV
867
                        clear_progress_bar_unbuffered(target);
×
UNCOV
868
                        fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress));
×
UNCOV
869
                        total += 100;
×
870
                } else if (progress == UPDATE_PROGRESS_ACQUIRED) {
188✔
871
                        clear_progress_bar_unbuffered(target);
66✔
872
                        fprintf(stderr, "%s: %s Installing\n", target, glyph(GLYPH_DOWNLOAD));
66✔
873
                        total += 100;
66✔
874
                } else if (progress == UPDATE_PROGRESS_DONE) {
122✔
UNCOV
875
                        clear_progress_bar_unbuffered(target);
×
UNCOV
876
                        fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK());
×
UNCOV
877
                        total += 100;
×
878
                } else {
879
                        draw_progress_bar_unbuffered(target, progress);
122✔
880
                        fputs("\n", stderr);
122✔
881
                        total += progress;
122✔
882
                }
883
        }
884

885
        if (n > 1) {
210✔
886
                if (exiting)
×
887
                        clear_progress_bar_unbuffered(target);
×
888
                else {
UNCOV
889
                        draw_progress_bar_unbuffered("Total", (double) total / n);
×
UNCOV
890
                        if (terminal_is_dumb())
×
UNCOV
891
                                fputs("\n", stderr);
×
892
                }
893
        }
894

895
        if (!terminal_is_dumb()) {
210✔
896
                if (exiting)
×
UNCOV
897
                        fputs("\e[?25h", stderr); /* Show cursor again */
×
898
                else
UNCOV
899
                        fputs("\e8", stderr); /* Restore cursor position */
×
900
        } else if (!exiting)
210✔
901
                fputs("------\n", stderr);
188✔
902

903
        return 0;
210✔
904
}
905

906
static int update_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
78✔
907
        Operation *op = ASSERT_PTR(userdata);
78✔
908
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
78✔
909
        const char *interface;
78✔
910
        uint32_t progress = UINT32_MAX;
78✔
911
        static const struct bus_properties_map prop_map[] = {
78✔
912
                { "Progress", "u", NULL, 0 },
913
                {}
914
        };
915
        int r;
78✔
916

917
        assert(m);
78✔
918

919
        r = sd_bus_message_read(m, "s", &interface);
78✔
920
        if (r < 0) {
78✔
UNCOV
921
                bus_log_parse_error_debug(r);
×
UNCOV
922
                return 0;
×
923
        }
924

925
        if (!streq(interface, "org.freedesktop.sysupdate1.Job"))
78✔
926
                return 0;
927

928
        r = bus_message_map_all_properties(m, prop_map, /* flags= */ 0, error, &progress);
78✔
929
        if (r < 0)
78✔
930
                return 0; /* map_all_properties does the debug logging internally... */
931

932
        if (progress == UINT_MAX)
78✔
933
                return 0;
934

935
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR((int) progress));
78✔
936
        if (r < 0)
78✔
UNCOV
937
                log_debug_errno(r, "Failed to update hashmap: %m");
×
938
        return 0;
939
}
940

941
static int update_install_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
22✔
942
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
44✔
943
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
22✔
944
        const sd_bus_error *e;
22✔
945
        const char *job_path;
22✔
946
        int r;
22✔
947

948
        assert(reply);
22✔
949

950
        e = sd_bus_message_get_error(reply);
22✔
951
        if (e) {
22✔
952
                r = -sd_bus_error_get_errno(e);
22✔
953

954
                r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(r));
22✔
955
                if (r < 0)
22✔
UNCOV
956
                        log_debug_errno(r, "Failed to update hashmap: %m");
×
957

958
                return 0;
22✔
959
        }
960

961
        r = sd_bus_message_read(reply, "sto", NULL, &op->job_id, &job_path);
×
962
        if (r < 0)
×
UNCOV
963
                return bus_log_parse_error(r);
×
UNCOV
964
        r = free_and_strdup_warn(&op->job_path, job_path);
×
UNCOV
965
        if (r < 0)
×
966
                return r;
967

968
        /* Update this job in the hashmap. */
UNCOV
969
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(UPDATE_PROGRESS_ACQUIRED));
×
UNCOV
970
        if (r < 0)
×
UNCOV
971
                log_debug_errno(r, "Failed to update hashmap: %m");
×
972

973
        /* Register for progress notifications for this Install() D-Bus call; previously
974
         * op->job_properties_slot was registered for progress notifications for the Acquire() D-Bus call. */
UNCOV
975
        sd_bus_slot_unref(TAKE_PTR(op->job_properties_slot));
×
976
        r = sd_bus_match_signal_async(
×
977
                        op->bus,
978
                        &op->job_properties_slot,
UNCOV
979
                        bus_sysupdate_mgr->destination,
×
980
                        job_path,
981
                        "org.freedesktop.DBus.Properties",
982
                        "PropertiesChanged",
983
                        update_properties_changed,
984
                        NULL,
985
                        op);
986
        if (r < 0)
×
UNCOV
987
                return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
×
988

UNCOV
989
        TAKE_PTR(op); /* update_install_finished/update_interrupted take ownership of the data */
×
990

UNCOV
991
        return 0;
×
992
}
993

994
static int update_install_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
22✔
995
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
22✔
996
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
22✔
997
        uint64_t id;
22✔
998
        int r, status;
22✔
999

1000
        assert(m);
22✔
1001

1002
        r = sd_bus_message_read(m, "toi", &id, NULL, &status);
22✔
1003
        if (r < 0) {
22✔
UNCOV
1004
                bus_log_parse_error_debug(r);
×
UNCOV
1005
                return 0;
×
1006
        }
1007

1008
        if (id != op->job_id) {
22✔
1009
                TAKE_PTR(op);
22✔
1010
                return 0;
22✔
1011
        }
1012

1013
        if (status == 0) /* success */
×
UNCOV
1014
                status = UPDATE_PROGRESS_DONE;
×
UNCOV
1015
        else if (status > 0) /* exit status without errno */
×
1016
                status = UPDATE_PROGRESS_FAILED; /* i.e. EXIT_FAILURE */
×
1017
        /* else errno */
1018

UNCOV
1019
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(status));
×
UNCOV
1020
        if (r < 0)
×
1021
                log_debug_errno(r, "Failed to update hashmap: %m");
22✔
1022
        return 0;
1023
}
1024

1025
static int update_acquire_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
22✔
1026
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
44✔
1027
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
22✔
1028
        uint64_t id;
22✔
1029
        int r, status;
22✔
1030

1031
        assert(m);
22✔
1032

1033
        r = sd_bus_message_read(m, "toi", &id, NULL, &status);
22✔
1034
        if (r < 0) {
22✔
UNCOV
1035
                bus_log_parse_error_debug(r);
×
UNCOV
1036
                return 0;
×
1037
        }
1038

1039
        if (id != op->job_id) {
22✔
UNCOV
1040
                TAKE_PTR(op);
×
UNCOV
1041
                return 0;
×
1042
        }
1043

1044
        if (status == 0) /* success */
22✔
1045
                status = UPDATE_PROGRESS_ACQUIRED;
22✔
UNCOV
1046
        else if (status > 0) /* exit status without errno */
×
UNCOV
1047
                status = UPDATE_PROGRESS_FAILED; /* i.e. EXIT_FAILURE */
×
1048
        /* else errno */
1049

1050
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(status));
22✔
1051
        if (r < 0)
22✔
UNCOV
1052
                log_debug_errno(r, "Failed to update hashmap: %m");
×
1053

1054
        /* Renew the JobRemoved notification for the Install() call instead. */
1055
        sd_bus_slot_unref(op->job_finished_slot);
22✔
1056
        r = bus_match_signal_async(
22✔
1057
                        op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_install_finished, NULL, op);
1058
        if (r < 0)
22✔
UNCOV
1059
                return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
×
1060

1061
        /* With the Acquire() call finished, immediately call Install() to deploy the downloaded update.
1062
         * This reuses the same Operation struct so the progress reporting continues to be done in the same
1063
         * slot in the terminal. */
1064
        r = sd_bus_call_method_async(
44✔
1065
                        op->bus,
1066
                        NULL,
1067
                        bus_sysupdate_mgr->destination,
22✔
1068
                        op->target_path,
1069
                        SYSUPDATE_TARGET_INTERFACE,
1070
                        "Install",
1071
                        update_install_started,
1072
                        op,
1073
                        "st",
1074
                        op->requested_version,
22✔
1075
                        0LU);
1076
        if (r < 0)
22✔
UNCOV
1077
                return log_bus_error(r, NULL, op->target_id, "call Install");
×
1078
        TAKE_PTR(op);
22✔
1079

1080
        return 0;
22✔
1081
}
1082

1083
static int update_interrupted(sd_event_source *source, void *userdata) {
×
1084
        /* Since the event loop is exiting, we will never receive the JobRemoved
1085
         * signal. So, we must free the userdata here. */
1086
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
×
UNCOV
1087
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
UNCOV
1088
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
×
UNCOV
1089
        int r;
×
1090

1091
        /* This call should work regardless of whether we’re cancelling the Acquire() call or the Install()
1092
         * call. */
UNCOV
1093
        r = sd_bus_call_method(op->bus,
×
UNCOV
1094
                               bus_sysupdate_mgr->destination,
×
UNCOV
1095
                               op->job_path,
×
1096
                               "org.freedesktop.sysupdate1.Job",
1097
                               "Cancel",
1098
                               &error,
1099
                               /* ret_reply= */ NULL,
1100
                               NULL);
1101
        if (r < 0)
×
1102
                return log_bus_error(r, &error, NULL, "call Cancel");
×
1103

UNCOV
1104
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(-ECANCELED));
×
UNCOV
1105
        if (r < 0)
×
UNCOV
1106
                log_debug_errno(r, "Failed to update hashmap: %m");
×
1107

1108
        return 0;
1109
}
1110

1111
static int update_acquire_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
22✔
1112
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
22✔
1113
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
22✔
1114
        const sd_bus_error *e;
22✔
1115
        _cleanup_free_ char *key = NULL;
22✔
1116
        const char *new_version, *job_path;
22✔
1117
        int r;
22✔
1118

1119
        assert(reply);
22✔
1120

1121
        e = sd_bus_message_get_error(reply);
22✔
1122
        if (e) {
22✔
1123
                r = -sd_bus_error_get_errno(e);
×
1124

1125
                key = strdup(op->target_id);
×
1126
                if (!key)
×
1127
                        return log_oom();
×
UNCOV
1128
                r = ordered_hashmap_put(map, key, INT_TO_PTR(r));
×
UNCOV
1129
                if (r < 0)
×
UNCOV
1130
                        return log_error_errno(r, "Failed to update hashmap: %m");
×
1131
                TAKE_PTR(key);
22✔
1132

1133
                return 0;
1134
        }
1135

1136
        r = sd_bus_message_read(reply, "sto", &new_version, &op->job_id, &job_path);
22✔
1137
        if (r < 0)
22✔
1138
                return bus_log_parse_error(r);
×
1139
        op->job_path = strdup(job_path);
22✔
1140
        if (!op->job_path)
22✔
UNCOV
1141
                return log_oom();
×
1142

1143
        /* Store the version for the subsequent Install() call */
1144
        op->acquired_version = strdup(new_version);
22✔
1145
        if (!op->acquired_version)
22✔
1146
                return log_oom();
×
1147

1148
        if (isempty(new_version))
22✔
UNCOV
1149
                new_version = "latest";
×
1150

1151
        /* Register this job into the hashmap. This will give it a progress bar */
1152
        if (strchr(op->target_id, '@'))
22✔
UNCOV
1153
                key = strdup(op->target_id);
×
1154
        else
1155
                key = strjoin(op->target_id, "@", new_version);
22✔
1156
        if (!key)
22✔
1157
                return log_oom();
×
1158
        r = ordered_hashmap_put(map, key, INT_TO_PTR(0)); /* takes ownership of key */
22✔
1159
        if (r < 0)
22✔
UNCOV
1160
                return log_error_errno(r, "Failed to add target to tracking map: %m");
×
1161
        op->target_id = TAKE_PTR(key); /* just borrowing */
22✔
1162

1163
        /* Cancel the job if the event loop exits */
1164
        r = sd_event_add_exit(op->event, &op->job_interrupt_source, update_interrupted, op);
22✔
1165
        if (r < 0)
22✔
UNCOV
1166
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
1167

1168
        /* We need to cancel the job before the final iteration of the renderer runs */
1169
        r = sd_event_source_set_priority(op->job_interrupt_source, SD_EVENT_PRIORITY_IMPORTANT);
22✔
1170
        if (r < 0)
22✔
UNCOV
1171
                return log_error_errno(r, "Failed to set interrupt priority: %m");
×
1172

1173
        /* Register for progress notifications */
1174
        r = sd_bus_match_signal_async(
44✔
1175
                        op->bus,
1176
                        &op->job_properties_slot,
1177
                        bus_sysupdate_mgr->destination,
22✔
1178
                        job_path,
1179
                        "org.freedesktop.DBus.Properties",
1180
                        "PropertiesChanged",
1181
                        update_properties_changed,
1182
                        NULL,
1183
                        op);
1184
        if (r < 0)
22✔
UNCOV
1185
                return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
×
1186

1187
        TAKE_PTR(op); /* update_acquire_finished/update_interrupted take ownership of the data */
22✔
1188

1189
        return 0;
22✔
1190
}
1191

1192
static int do_update(sd_bus *bus, char **targets) {
22✔
1193
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
22✔
1194
        _cleanup_(sd_event_source_unrefp) sd_event_source *render_exit = NULL;
22✔
1195
        _cleanup_ordered_hashmap_free_ OrderedHashmap *map = NULL;
22✔
1196
        _cleanup_strv_free_ char **versions = NULL, **target_paths = NULL;
22✔
1197
        size_t n;
22✔
1198
        unsigned remaining = 0;
22✔
1199
        void *p;
22✔
1200
        bool did_anything = false;
22✔
1201
        int r;
22✔
1202

1203
        assert(bus);
22✔
1204
        assert(targets);
22✔
1205

1206
        r = parse_targets(targets, &n, &target_paths, &versions);
22✔
1207
        if (r < 0)
22✔
UNCOV
1208
                return log_error_errno(r, "Could not parse targets: %m");
×
1209

1210
        map = ordered_hashmap_new(&string_hash_ops_free);
22✔
1211
        if (!map)
22✔
UNCOV
1212
                return log_oom();
×
1213

1214
        r = sd_event_default(&event);
22✔
1215
        if (r < 0)
22✔
UNCOV
1216
                return log_error_errno(r, "Failed to get event loop: %m");
×
1217

1218
        r = sd_bus_attach_event(bus, event, 0);
22✔
1219
        if (r < 0)
22✔
UNCOV
1220
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
1221

1222
        r = sd_event_set_signal_exit(event, true);
22✔
1223
        if (r < 0)
22✔
1224
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
1225

1226
        for (size_t i = 0; i < n; i++) {
44✔
1227
                _cleanup_(operation_freep) Operation *op = NULL;
×
1228
                op = operation_new(map, bus, &remaining, target_paths[i], targets[i]);
22✔
1229
                if (!op)
22✔
UNCOV
1230
                        return log_oom();
×
1231

1232
                /* Sign up for notification when the associated job finishes */
1233
                r = bus_match_signal_async(
22✔
1234
                                op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_acquire_finished, NULL, op);
1235
                if (r < 0)
22✔
UNCOV
1236
                        return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
×
1237

1238
                r = sd_bus_call_method_async(
44✔
1239
                                bus,
1240
                                NULL,
1241
                                bus_sysupdate_mgr->destination,
22✔
1242
                                target_paths[i],
22✔
1243
                                SYSUPDATE_TARGET_INTERFACE,
1244
                                "Acquire",
1245
                                update_acquire_started,
1246
                                op,
1247
                                "st",
1248
                                versions[i],
22✔
1249
                                0LU);
1250
                if (r < 0)
22✔
UNCOV
1251
                        return log_bus_error(r, NULL, targets[i], "call Acquire");
×
1252

1253
                op->requested_version = strdup(versions[i]);
22✔
1254
                if (!op->requested_version)
22✔
UNCOV
1255
                        return log_oom();
×
1256

1257
                TAKE_PTR(op);
22✔
1258

1259
                remaining++;
22✔
1260
        }
1261

1262
        /* Set up the rendering */
1263
        r = sd_event_add_post(event, NULL, update_render_progress, map);
22✔
1264
        if (r < 0)
22✔
1265
                return log_error_errno(r, "Failed to add progress rendering callback: %m");
×
1266

1267
        r = sd_event_add_exit(event, &render_exit, update_render_progress, map);
22✔
1268
        if (r < 0)
22✔
1269
                return log_error_errno(r, "Failed to add exit callback: %m");
×
1270

1271
        r = sd_event_source_set_priority(render_exit, SD_EVENT_PRIORITY_IDLE);
22✔
1272
        if (r < 0)
22✔
UNCOV
1273
                return log_error_errno(r, "Failed to set priority of update job");
×
1274

1275
        r = sd_event_loop(event);
22✔
1276
        if (r < 0)
22✔
1277
                return log_error_errno(r, "Failed to start event loop");
×
1278

1279
        ORDERED_HASHMAP_FOREACH(p, map) {
44✔
1280
                r = PTR_TO_INT(p);
22✔
1281
                if (r == -EALREADY)
22✔
1282
                        continue;
22✔
UNCOV
1283
                if (r == UPDATE_PROGRESS_FAILED)
×
UNCOV
1284
                        return EXIT_FAILURE;
×
UNCOV
1285
                if (r < 0)
×
1286
                        return r;
1287

1288
                did_anything = true;
1289
        }
1290

1291
        return did_anything ? 1 : 0;
22✔
1292
}
1293

1294
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
22✔
1295
        sd_bus *bus = ASSERT_PTR(userdata);
22✔
1296
        _cleanup_strv_free_ char **targets = NULL;
22✔
1297
        bool did_anything = false;
22✔
1298
        int r;
22✔
1299

1300
        r = ensure_targets(bus, argv + 1, &targets);
22✔
1301
        if (r < 0)
22✔
UNCOV
1302
                return log_error_errno(r, "Could not find targets: %m");
×
1303

1304
        r = do_update(bus, targets);
22✔
1305
        if (r < 0)
22✔
1306
                return r;
1307
        if (r > 0)
22✔
1308
                did_anything = true;
×
1309

1310
        if (!arg_reboot)
22✔
1311
                return 0;
1312

1313
        if (did_anything)
×
1314
                return reboot_now();
×
1315

1316
        log_info("Nothing was updated... skipping reboot.");
×
1317
        return 0;
1318
}
1319

1320
static int do_vacuum(sd_bus *bus, const char *target, const char *path) {
×
UNCOV
1321
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1322
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1323
        uint32_t count, disabled;
×
1324
        int r;
×
1325

1326
        r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination, path, SYSUPDATE_TARGET_INTERFACE, "Vacuum", &error, &reply, NULL);
×
1327
        if (r < 0)
×
UNCOV
1328
                return log_bus_error(r, &error, target, "call Vacuum");
×
1329

1330
        r = sd_bus_message_read(reply, "uu", &count, &disabled);
×
1331
        if (r < 0)
×
1332
                return bus_log_parse_error(r);
×
1333

1334
        if (count > 0 && disabled > 0)
×
UNCOV
1335
                log_info("Deleted %u instance(s) and %u disabled transfer(s) of %s.",
×
1336
                         count, disabled, target);
UNCOV
1337
        else if (count > 0)
×
UNCOV
1338
                log_info("Deleted %u instance(s) of %s.", count, target);
×
1339
        else if (disabled > 0)
×
1340
                log_info("Deleted %u disabled transfer(s) of %s.", disabled, target);
×
1341
        else
1342
                log_info("Found nothing to delete for %s.", target);
×
1343

UNCOV
1344
        return count + disabled > 0 ? 1 : 0;
×
1345
}
1346

1347
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
UNCOV
1348
        sd_bus *bus = ASSERT_PTR(userdata);
×
1349
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
×
1350
        size_t n;
×
1351
        int r;
×
1352

1353
        r = ensure_targets(bus, argv + 1, &targets);
×
1354
        if (r < 0)
×
1355
                return log_error_errno(r, "Failed to find targets: %m");
×
1356

UNCOV
1357
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
×
UNCOV
1358
        if (r < 0)
×
UNCOV
1359
                return log_error_errno(r, "Failed to parse targets: %m");
×
1360

UNCOV
1361
        for (size_t i = 0; i < n; i++) {
×
UNCOV
1362
                r = do_vacuum(bus, targets[i], target_paths[i]);
×
UNCOV
1363
                if (r < 0)
×
1364
                        return r;
1365
        }
1366
        return 0;
1367
}
1368

1369
typedef struct Feature {
1370
        char *name;
1371
        char *description;
1372
        bool enabled;
1373
        char *documentation;
1374
        char **transfers;
1375
} Feature;
1376

1377
static void feature_done(Feature *f) {
×
1378
        assert(f);
×
1379
        f->name = mfree(f->name);
×
1380
        f->description = mfree(f->description);
×
1381
        f->documentation = mfree(f->documentation);
×
1382
        f->transfers = strv_free(f->transfers);
×
1383
}
×
1384

1385
static int describe_feature(sd_bus *bus, const char *feature, Feature *ret) {
×
UNCOV
1386
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
UNCOV
1387
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
1388
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
UNCOV
1389
        _cleanup_(feature_done) Feature f = {};
×
UNCOV
1390
        char *json;
×
UNCOV
1391
        int r;
×
1392

UNCOV
1393
        static const sd_json_dispatch_field dispatch_table[] = {
×
1394
                { "name",             SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, name),          SD_JSON_MANDATORY },
1395
                { "description",      SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, description),   0                 },
1396
                { "enabled",          SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Feature, enabled),       SD_JSON_MANDATORY },
1397
                { "documentationUrl", SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, documentation), 0                 },
1398
                { "transfers",        SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_strv,    offsetof(Feature, transfers),     0                 },
1399
                {}
1400
        };
1401

UNCOV
1402
        assert(bus);
×
UNCOV
1403
        assert(feature);
×
UNCOV
1404
        assert(ret);
×
1405

UNCOV
1406
        r = sd_bus_call_method(bus,
×
UNCOV
1407
                               bus_sysupdate_mgr->destination,
×
1408
                               SYSUPDATE_HOST_PATH,
1409
                               SYSUPDATE_TARGET_INTERFACE,
1410
                               "DescribeFeature",
1411
                               &error,
1412
                               &reply,
1413
                               "st",
1414
                               feature,
1415
                               UINT64_C(0));
1416
        if (r < 0)
×
1417
                return log_bus_error(r, &error, "host", "lookup feature");
×
1418

1419
        r = sd_bus_message_read_basic(reply, 's', &json);
×
1420
        if (r < 0)
×
1421
                return bus_log_parse_error(r);
×
1422

1423
        r = sd_json_parse(json, 0, &v, NULL, NULL);
×
1424
        if (r < 0)
×
UNCOV
1425
                return log_error_errno(r, "Failed to parse JSON: %m");
×
1426

1427
        r = sd_json_dispatch(v, dispatch_table, 0, &f);
×
1428
        if (r < 0)
×
1429
                return log_error_errno(r, "Failed to dispatch JSON: %m");
×
1430

1431
        *ret = TAKE_STRUCT(f);
×
1432
        return 0;
×
1433
}
1434

UNCOV
1435
static int list_features(sd_bus *bus) {
×
1436
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1437
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1438
        _cleanup_strv_free_ char **features = NULL;
×
UNCOV
1439
        _cleanup_(table_unrefp) Table *table = NULL;
×
1440
        int r;
×
1441

UNCOV
1442
        assert(bus);
×
1443

UNCOV
1444
        table = table_new("", "feature", "description");
×
UNCOV
1445
        if (!table)
×
UNCOV
1446
                return log_oom();
×
1447

UNCOV
1448
        r = sd_bus_call_method(bus,
×
1449
                               bus_sysupdate_mgr->destination,
×
1450
                               SYSUPDATE_HOST_PATH,
1451
                               SYSUPDATE_TARGET_INTERFACE,
1452
                               "ListFeatures",
1453
                               &error,
1454
                               &reply,
1455
                               "t",
1456
                               UINT64_C(0));
1457
        if (r < 0)
×
1458
                return log_bus_error(r, &error, "host", "lookup feature");
×
1459

1460
        r = sd_bus_message_read_strv(reply, &features);
×
1461
        if (r < 0)
×
UNCOV
1462
                return bus_log_parse_error(r);
×
1463

1464
        STRV_FOREACH(feature, features) {
×
1465
                _cleanup_(feature_done) Feature f = {};
×
1466
                _cleanup_free_ char *name_link = NULL;
×
1467

UNCOV
1468
                r = describe_feature(bus, *feature, &f);
×
UNCOV
1469
                if (r < 0)
×
1470
                        return r;
1471

UNCOV
1472
                if (urlify_enabled() && f.documentation) {
×
UNCOV
1473
                        name_link = strjoin(f.name, glyph(GLYPH_EXTERNAL_LINK));
×
UNCOV
1474
                        if (!name_link)
×
UNCOV
1475
                                return log_oom();
×
1476
                }
1477

UNCOV
1478
                r = table_add_many(table,
×
1479
                                   TABLE_BOOLEAN_CHECKMARK, f.enabled,
1480
                                   TABLE_SET_COLOR, ansi_highlight_green_red(f.enabled),
1481
                                   TABLE_STRING, name_link ?: f.name,
1482
                                   TABLE_SET_URL, f.documentation,
1483
                                   TABLE_STRING, f.description);
1484
                if (r < 0)
×
1485
                        return table_log_add_error(r);
×
1486
        }
1487

UNCOV
1488
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
×
1489
}
1490

UNCOV
1491
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1492
        sd_bus *bus = ASSERT_PTR(userdata);
×
1493
        _cleanup_(table_unrefp) Table *table = NULL;
×
1494
        _cleanup_(feature_done) Feature f = {};
×
UNCOV
1495
        int r;
×
1496

1497
        if (argc == 1)
×
UNCOV
1498
                return list_features(bus);
×
1499

1500
        table = table_new_vertical();
×
UNCOV
1501
        if (!table)
×
UNCOV
1502
                return log_oom();
×
1503

UNCOV
1504
        r = describe_feature(bus, argv[1], &f);
×
1505
        if (r < 0)
×
1506
                return r;
1507

1508
        r = table_add_many(table,
×
1509
                           TABLE_FIELD, "Name",
1510
                           TABLE_STRING, f.name,
1511
                           TABLE_FIELD, "Enabled",
1512
                           TABLE_BOOLEAN, f.enabled);
UNCOV
1513
        if (r < 0)
×
1514
                return table_log_add_error(r);
×
1515

UNCOV
1516
        if (f.description) {
×
UNCOV
1517
                r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f.description);
×
UNCOV
1518
                if (r < 0)
×
1519
                        return table_log_add_error(r);
×
1520
        }
1521

UNCOV
1522
        if (f.documentation) {
×
1523
                r = table_add_many(table,
×
1524
                                   TABLE_FIELD, "Documentation",
1525
                                   TABLE_STRING, f.documentation,
1526
                                   TABLE_SET_URL, f.documentation);
UNCOV
1527
                if (r < 0)
×
UNCOV
1528
                        return table_log_add_error(r);
×
1529
        }
1530

UNCOV
1531
        if (!strv_isempty(f.transfers)) {
×
1532
                r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, f.transfers);
×
1533
                if (r < 0)
×
1534
                        return table_log_add_error(r);
×
1535
        }
1536

UNCOV
1537
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false);
×
1538
}
1539

UNCOV
1540
static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1541
        sd_bus *bus = ASSERT_PTR(userdata);
×
1542
        bool did_anything = false, enable;
×
UNCOV
1543
        char **features;
×
1544
        int r;
×
1545

UNCOV
1546
        enable = streq(argv[0], "enable");
×
UNCOV
1547
        features = strv_skip(argv, 1);
×
1548

UNCOV
1549
        STRV_FOREACH(feature, features) {
×
UNCOV
1550
                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1551

UNCOV
1552
                r = sd_bus_call_method(bus,
×
UNCOV
1553
                                       bus_sysupdate_mgr->destination,
×
1554
                                       SYSUPDATE_HOST_PATH,
1555
                                       SYSUPDATE_TARGET_INTERFACE,
1556
                                       "SetFeatureEnabled",
1557
                                       &error,
1558
                                       /* ret_reply= */ NULL,
1559
                                       "sit",
1560
                                       *feature,
1561
                                       (int) enable,
1562
                                       UINT64_C(0));
1563
                if (r < 0)
×
1564
                        return log_bus_error(r, &error, "host", "call SetFeatureEnabled");
×
1565
        }
1566

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

UNCOV
1570
        if (enable) {
×
UNCOV
1571
                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1572
                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1573
                _cleanup_free_ char *target = NULL;
×
UNCOV
1574
                char *version = NULL;
×
1575

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

1580
                r = sd_bus_call_method(bus,
×
1581
                                       bus_sysupdate_mgr->destination,
×
1582
                                       SYSUPDATE_HOST_PATH,
1583
                                       SYSUPDATE_TARGET_INTERFACE,
1584
                                       "GetVersion",
1585
                                       &error,
1586
                                       &reply,
1587
                                       NULL);
1588
                if (r < 0)
×
1589
                        return log_bus_error(r, &error, "host", "get current version");
×
1590

1591
                r = sd_bus_message_read_basic(reply, 's', &version);
×
UNCOV
1592
                if (r < 0)
×
1593
                        return bus_log_parse_error(r);
×
1594

UNCOV
1595
                target = strjoin("host@", version);
×
1596
                if (!target)
×
1597
                        return log_oom();
×
1598

1599
                r = do_update(bus, STRV_MAKE(target));
×
1600
        } else
1601
                r = do_vacuum(bus, "host", SYSUPDATE_HOST_PATH);
×
1602
        if (r < 0)
×
1603
                return r;
1604
        if (r > 0)
×
UNCOV
1605
                did_anything = true;
×
1606

UNCOV
1607
        if (arg_reboot && did_anything)
×
UNCOV
1608
                return reboot_now();
×
UNCOV
1609
        else if (did_anything)
×
UNCOV
1610
                log_info("Feature(s) %s.", enable ? "downloaded" : "deleted");
×
1611
        else
1612
                log_info("Nothing %s%s.",
×
1613
                         enable ? "downloaded" : "deleted",
1614
                         arg_reboot ? ", skipping reboot" :"");
1615

1616
        return 0;
1617
}
1618

1619
static int help(void) {
×
UNCOV
1620
        _cleanup_free_ char *link = NULL;
×
UNCOV
1621
        int r;
×
1622

UNCOV
1623
        r = terminal_urlify_man("updatectl", "1", &link);
×
UNCOV
1624
        if (r < 0)
×
UNCOV
1625
                return log_oom();
×
1626

UNCOV
1627
        printf("%1$s [OPTIONS...] [VERSION]\n"
×
1628
               "\n%5$sManage system updates.%6$s\n"
1629
               "\n%3$sCommands:%4$s\n"
1630
               "  list [TARGET[@VERSION]]       List available targets and versions\n"
1631
               "  check [TARGET...]             Check for updates\n"
1632
               "  update [TARGET[@VERSION]...]  Install updates\n"
1633
               "  vacuum [TARGET...]            Clean up old updates\n"
1634
               "  features [FEATURE]            List and inspect optional features on host OS\n"
1635
               "  enable FEATURE...             Enable optional feature on host OS\n"
1636
               "  disable FEATURE...            Disable optional feature on host OS\n"
1637
               "  -h --help                     Show this help\n"
1638
               "     --version                  Show package version\n"
1639
               "\n%3$sOptions:%4$s\n"
1640
               "     --reboot             Reboot after updating to newer version\n"
1641
               "     --offline            Do not fetch metadata from the network\n"
1642
               "     --now                Download/delete resources immediately\n"
1643
               "  -H --host=[USER@]HOST   Operate on remote host\n"
1644
               "     --no-pager           Do not pipe output into a pager\n"
1645
               "     --no-legend          Do not show the headers and footers\n"
1646
               "\nSee the %2$s for details.\n"
1647
               , program_invocation_short_name
1648
               , link
1649
               , ansi_underline(), ansi_normal()
1650
               , ansi_highlight(), ansi_normal()
1651
        );
1652

1653
        return 0;
1654
}
1655

1656
static int parse_argv(int argc, char *argv[]) {
84✔
1657

1658
        enum {
84✔
1659
                ARG_VERSION = 0x100,
1660
                ARG_NO_PAGER,
1661
                ARG_NO_LEGEND,
1662
                ARG_REBOOT,
1663
                ARG_OFFLINE,
1664
                ARG_NOW,
1665
        };
1666

1667
        static const struct option options[] = {
84✔
1668
                { "help",      no_argument,       NULL, 'h'             },
1669
                { "version",   no_argument,       NULL, ARG_VERSION     },
1670
                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER    },
1671
                { "no-legend", no_argument,       NULL, ARG_NO_LEGEND   },
1672
                { "host",      required_argument, NULL, 'H'             },
1673
                { "reboot",    no_argument,       NULL, ARG_REBOOT      },
1674
                { "offline",   no_argument,       NULL, ARG_OFFLINE     },
1675
                { "now",       no_argument,       NULL, ARG_NOW         },
1676
                {}
1677
        };
1678

1679
        int c;
84✔
1680

1681
        assert(argc >= 0);
84✔
1682
        assert(argv);
84✔
1683

1684
        while ((c = getopt_long(argc, argv, "hH:", options, NULL)) >= 0) {
84✔
1685
                switch (c) {
×
1686

1687
                case 'h':
×
UNCOV
1688
                        return help();
×
1689

1690
                case ARG_VERSION:
×
1691
                        return version();
×
1692

1693
                case ARG_NO_PAGER:
×
1694
                        arg_pager_flags |= PAGER_DISABLE;
×
1695
                        break;
×
1696

UNCOV
1697
                case ARG_NO_LEGEND:
×
1698
                        arg_legend = false;
×
1699
                        break;
×
1700

UNCOV
1701
                case 'H':
×
1702
                        arg_transport = BUS_TRANSPORT_REMOTE;
×
1703
                        arg_host = optarg;
×
1704
                        break;
×
1705

1706
                case ARG_REBOOT:
×
1707
                        arg_reboot = true;
×
1708
                        break;
×
1709

UNCOV
1710
                case ARG_OFFLINE:
×
UNCOV
1711
                        arg_offline = true;
×
UNCOV
1712
                        break;
×
1713

1714
                case ARG_NOW:
×
UNCOV
1715
                        arg_now = true;
×
UNCOV
1716
                        break;
×
1717

1718
                case '?':
1719
                        return -EINVAL;
1720

UNCOV
1721
                default:
×
UNCOV
1722
                        assert_not_reached();
×
1723
                }
1724
        }
1725

1726
        return 1;
1727
}
1728

1729
static int run(int argc, char *argv[]) {
84✔
1730
        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
84✔
1731
        int r;
84✔
1732

1733
        static const Verb verbs[] = {
84✔
1734
                { "list",     VERB_ANY, 2,        VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list     },
1735
                { "check",    VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              verb_check    },
1736
                { "update",   VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              verb_update   },
1737
                { "vacuum",   VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              verb_vacuum   },
1738
                { "features", VERB_ANY, 2,        VERB_ONLINE_ONLY,              verb_features },
1739
                { "enable",   2,        VERB_ANY, VERB_ONLINE_ONLY,              verb_enable   },
1740
                { "disable",  2,        VERB_ANY, VERB_ONLINE_ONLY,              verb_enable   },
1741
                {}
1742
        };
1743

1744
        setlocale(LC_ALL, "");
84✔
1745
        log_setup();
84✔
1746

1747
        (void) signal(SIGWINCH, columns_lines_cache_reset);
84✔
1748

1749
        r = parse_argv(argc, argv);
84✔
1750
        if (r <= 0)
84✔
1751
                return r;
1752

1753
        r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
84✔
1754
        if (r < 0)
84✔
UNCOV
1755
                return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
×
1756

1757
        if (arg_transport == BUS_TRANSPORT_LOCAL)
84✔
1758
                polkit_agent_open();
84✔
1759

1760
        (void) sd_bus_set_allow_interactive_authorization(bus, true);
84✔
1761

1762
        return dispatch_verb(argc, argv, verbs, bus);
84✔
1763
}
1764

1765
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
84✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc