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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

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

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

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

54.97
/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-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 "conf-files.h"
17
#include "conf-parser.h"
18
#include "errno-list.h"
19
#include "fd-util.h"
20
#include "fileio.h"
21
#include "format-table.h"
22
#include "fs-util.h"
23
#include "json-util.h"
24
#include "main-func.h"
25
#include "os-util.h"
26
#include "pager.h"
27
#include "path-util.h"
28
#include "polkit-agent.h"
29
#include "pretty-print.h"
30
#include "strv.h"
31
#include "sysupdate-update-set-flags.h"
32
#include "sysupdate-util.h"
33
#include "terminal-util.h"
34
#include "verbs.h"
35

36
static PagerFlags arg_pager_flags = 0;
37
static bool arg_legend = true;
38
static bool arg_reboot = false;
39
static bool arg_offline = false;
40
static bool arg_now = false;
41
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
42
static char *arg_host = NULL;
43

44
#define SYSUPDATE_HOST_PATH "/org/freedesktop/sysupdate1/target/host"
45
#define SYSUPDATE_TARGET_INTERFACE "org.freedesktop.sysupdate1.Target"
46

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

54
static void version_done(Version *v) {
24✔
55
        assert(v);
24✔
56

57
        v->version = mfree(v->version);
24✔
58
        v->changelog = strv_free(v->changelog);
24✔
59
        v->flags = 0;
24✔
60
        v->contents_json = mfree(v->contents_json);
24✔
61
}
24✔
62

63
typedef struct Operation {
64
        void *userdata;
65

66
        sd_bus *bus;
67
        sd_event *event;
68
        unsigned *remaining;
69

70
        const char *target_path;
71
        const char *target_id;
72

73
        uint64_t job_id;
74
        char *job_path;
75
        sd_event_source *job_interrupt_source;
76
        sd_bus_slot *job_properties_slot;
77
        sd_bus_slot *job_finished_slot;
78
} Operation;
79

80
static Operation* operation_free(Operation *p) {
16✔
81
        if (!p)
16✔
82
                return NULL;
83

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

91
        free(p->job_path);
16✔
92

93
        sd_event_source_disable_unref(p->job_interrupt_source);
16✔
94
        sd_bus_slot_unref(p->job_properties_slot);
16✔
95
        sd_bus_slot_unref(p->job_finished_slot);
16✔
96

97
        return mfree(p);
16✔
98
}
99

100
DEFINE_TRIVIAL_CLEANUP_FUNC(Operation*, operation_free);
50✔
101

102
static Operation* operation_new(
16✔
103
                void *userdata,
104
                sd_bus *bus,
105
                unsigned *remaining,
106
                const char *target_path,
107
                const char *target_id) {
108

109
        _cleanup_(operation_freep) Operation *o = NULL;
16✔
110

111
        o = new(Operation, 1);
16✔
112
        if (!o)
16✔
113
                return NULL;
114

115
        *o = (Operation) {
32✔
116
                .userdata = userdata,
117
                .bus = bus,
118
                .event = sd_bus_get_event(bus),
16✔
119
                .remaining = remaining,
120
                .target_path = target_path,
121
                .target_id = target_id,
122
        };
123
        return TAKE_PTR(o);
16✔
124
}
125

126
static int ensure_targets(sd_bus *bus, char **argv, char ***ret_targets) {
6✔
127
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
6✔
128
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
6✔
129
        _cleanup_strv_free_ char **targets = NULL;
6✔
130
        int r;
6✔
131

132
        assert(bus);
6✔
133
        assert(ret_targets);
6✔
134

135
        if (strv_isempty(argv)) {
6✔
136
                const char *class, *name, *path;
6✔
137

138
                r = bus_call_method(bus, bus_sysupdate_mgr, "ListTargets", &error, &reply, NULL);
6✔
139
                if (r < 0)
6✔
UNCOV
140
                        return log_error_errno(r, "Failed to call ListTargets: %s", bus_error_message(&error, r));
×
141

142
                r = sd_bus_message_enter_container(reply, 'a', "(sso)");
6✔
143
                if (r < 0)
6✔
UNCOV
144
                        return bus_log_parse_error(r);
×
145

146
                while ((r = sd_bus_message_read(reply, "(sso)", &class, &name, &path)) > 0) {
16✔
UNCOV
147
                        _cleanup_free_ char *id = NULL;
×
148

149
                        if (streq(class, "host"))
10✔
150
                                id = strdup("host");
6✔
151
                        else
152
                                id = strjoin(class, ":", name);
4✔
153
                        if (!id)
10✔
UNCOV
154
                                return log_oom();
×
155

156
                        r = strv_consume(&targets, TAKE_PTR(id));
10✔
157
                        if (r < 0)
10✔
UNCOV
158
                                return log_oom();
×
159
                }
160
                if (r < 0)
6✔
UNCOV
161
                        return bus_log_parse_error(r);
×
162

163
                r = sd_bus_message_exit_container(reply);
6✔
164
                if (r < 0)
6✔
UNCOV
165
                        return bus_log_parse_error(r);
×
166
        } else {
167
                r = strv_extend_strv(&targets, argv, true);
×
168
                if (r < 0)
×
UNCOV
169
                        return log_oom();
×
170
        }
171

172
        *ret_targets = TAKE_PTR(targets);
6✔
173
        return 0;
6✔
174
}
175

176
static int parse_target(
14✔
177
                const char *in,
178
                char **ret_bus_path,
179
                char **ret_version) {
180
        _cleanup_free_ char *id = NULL, *version = NULL, *escaped = NULL, *objpath = NULL;
14✔
181
        const char *s;
14✔
182

183
        /*
184
         * Parses the TARGET[@VERSION] syntax from the command line into
185
         * a bus object path and an optional version number.
186
         */
187

188
        assert(in);
14✔
189
        assert(ret_bus_path);
14✔
190
        assert(ret_version);
14✔
191

192
        s = strrchr(in, '@');
14✔
193
        if (s) {
14✔
194
                version = strdup(s + 1);
2✔
195
                if (!version)
2✔
196
                        return -ENOMEM;
197
                id = strndup(in, s - in);
2✔
198
        } else
199
                id = strdup(in);
12✔
200
        if (!id)
14✔
201
                return -ENOMEM;
202

203
        escaped = bus_label_escape(id);
14✔
204
        if (!escaped)
14✔
205
                return -ENOMEM;
206

207
        objpath = strjoin("/org/freedesktop/sysupdate1/target/", escaped);
14✔
208
        if (!objpath)
14✔
209
                return -ENOMEM;
210

211
        *ret_bus_path = TAKE_PTR(objpath);
14✔
212
        *ret_version = TAKE_PTR(version);
14✔
213
        return 0;
14✔
214
}
215

216
static int parse_targets(
6✔
217
                char **targets,
218
                size_t *ret_n,
219
                char ***ret_bus_paths,
220
                char ***ret_versions) {
221
        _cleanup_strv_free_ char **bus_paths = NULL;
6✔
222
        _cleanup_strv_free_ char **versions = NULL;
6✔
223
        size_t n = 0;
6✔
224
        int r;
6✔
225

226
        assert(ret_bus_paths);
6✔
227
        assert(ret_n);
6✔
228

229
        if (strv_isempty(targets))
6✔
UNCOV
230
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No targets found.");
×
231

232
        STRV_FOREACH(id, targets) {
16✔
233
                _cleanup_free_ char *bus_path = NULL, *version = NULL;
10✔
234

235
                r = parse_target(*id, &bus_path, &version);
10✔
236
                if (r < 0)
10✔
UNCOV
237
                        return log_oom();
×
238

239
                if (version && !ret_versions)
10✔
UNCOV
240
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
241
                                               "Unexpected version specifier in target: %s",
242
                                               *id);
243

244
                r = strv_extend(&bus_paths, strempty(bus_path));
10✔
245
                if (r < 0)
10✔
UNCOV
246
                        return log_oom();
×
247

248
                r = strv_extend(&versions, strempty(version));
20✔
249
                if (r < 0)
10✔
UNCOV
250
                        return log_oom();
×
251

252
                n++;
10✔
253
        }
254

255
        *ret_n = n;
6✔
256
        *ret_bus_paths = TAKE_PTR(bus_paths);
6✔
257
        if (ret_versions)
6✔
258
                *ret_versions = TAKE_PTR(versions);
2✔
259
        return 0;
260
}
261

262
static int log_bus_error(int r, const sd_bus_error *error, const char *target, const char *action) {
×
UNCOV
263
        assert(action);
×
264

265
        if (r == 0) {
×
266
                assert(sd_bus_error_is_set(error));
×
UNCOV
267
                r = sd_bus_error_get_errno(error);
×
268
        }
269

270
        if (sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_OBJECT)) {
×
271
                if (target)
×
272
                        return log_error_errno(r, "Invalid target: %s", target);
×
UNCOV
273
                return log_error_errno(r, "Invalid target");
×
274
        }
275

276
        if (target)
×
UNCOV
277
                return log_error_errno(r, "Failed to %s for '%s': %s", action, target,
×
278
                                       bus_error_message(error, r));
UNCOV
279
        return log_error_errno(r, "Failed to %s: %s", action, bus_error_message(error, r));
×
280
}
281

282
static int list_targets(sd_bus *bus) {
2✔
283
        _cleanup_(table_unrefp) Table *table = NULL;
2✔
284
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2✔
285
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
2✔
286
        size_t n;
2✔
287
        int r;
2✔
288

289
        assert(bus);
2✔
290

291
        r = ensure_targets(bus, /* argv= */ NULL, &targets);
2✔
292
        if (r < 0)
2✔
UNCOV
293
                return log_error_errno(r, "Failed to find targets: %m");
×
294

295
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
2✔
296
        if (r < 0)
2✔
UNCOV
297
                return log_error_errno(r, "Failed to parse targets: %m");
×
298

299
        table = table_new("target", "version", "path");
2✔
300
        if (!table)
2✔
UNCOV
301
                return log_oom();
×
302

303
        for (size_t i = 0; i < n; i++) {
6✔
304
                char *version = NULL;
4✔
305
                _cleanup_free_ char *path = NULL;
4✔
306
                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
4✔
307

308
                r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination,
8✔
309
                                       target_paths[i], SYSUPDATE_TARGET_INTERFACE,
4✔
310
                                       "GetVersion", &error, &reply, NULL);
311
                if (r < 0)
4✔
UNCOV
312
                        return log_bus_error(r, &error, targets[i], "get current version");
×
313
                r = sd_bus_message_read_basic(reply, 's', &version);
4✔
314
                if (r < 0)
4✔
UNCOV
315
                        return bus_log_parse_error(r);
×
316

317
                r = sd_bus_get_property_string(bus, bus_sysupdate_mgr->destination,
8✔
318
                                               target_paths[i], SYSUPDATE_TARGET_INTERFACE,
4✔
319
                                               "Path", &error, &path);
320
                if (r < 0)
4✔
UNCOV
321
                        return log_bus_error(r, &error, targets[i], "get target bus path");
×
322

323
                r = table_add_many(table,
8✔
324
                                   TABLE_STRING, targets[i],
325
                                   TABLE_STRING, empty_to_dash(version),
326
                                   TABLE_STRING, path);
327
                if (r < 0)
4✔
UNCOV
328
                        return table_log_add_error(r);
×
329
        }
330

331
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
2✔
332
}
333

334
typedef struct DescribeParams {
335
        Version v;
336
        sd_json_variant *contents_json;
337
        bool newest;
338
        bool available;
339
        bool installed;
340
        bool obsolete;
341
        bool protected;
342
        bool incomplete;
343
} DescribeParams;
344

345
static void describe_params_done(DescribeParams *p) {
12✔
346
        assert(p);
12✔
347

348
        version_done(&p->v);
12✔
349
        sd_json_variant_unref(p->contents_json);
12✔
350
}
12✔
351

352
static int parse_describe(sd_bus_message *reply, Version *ret) {
12✔
353
        char *version_json = NULL;
12✔
354
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
12✔
355
        int r;
12✔
356

357
        assert(reply);
12✔
358
        assert(ret);
12✔
359

360
        r = sd_bus_message_read_basic(reply, 's', &version_json);
12✔
361
        if (r < 0)
12✔
UNCOV
362
                return bus_log_parse_error(r);
×
363

364
        r = sd_json_parse(version_json, 0, &json, NULL, NULL);
12✔
365
        if (r < 0)
12✔
UNCOV
366
                return log_error_errno(r, "Failed to parse JSON: %m");
×
367

368
        assert(sd_json_variant_is_object(json));
12✔
369

370
        static const sd_json_dispatch_field dispatch_table[] = {
12✔
371
                { "version",       SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(DescribeParams, v.version),     0 },
372
                { "newest",        SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, newest),        0 },
373
                { "available",     SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, available),     0 },
374
                { "installed",     SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, installed),     0 },
375
                { "obsolete",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, obsolete),      0 },
376
                { "protected",     SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, protected),     0 },
377
                { "incomplete",    SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, incomplete),    0 },
378
                { "changelogUrls", SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_strv,    offsetof(DescribeParams, v.changelog),   0 },
379
                { "contents",      SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_variant, offsetof(DescribeParams, contents_json), 0 },
380
                {},
381
        };
382

383
        _cleanup_(describe_params_done) DescribeParams p = {};
12✔
384

385
        r = sd_json_dispatch(json, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
12✔
386
        if (r < 0)
12✔
UNCOV
387
                return log_error_errno(r, "Failed to parse JSON: %m");
×
388

389
        SET_FLAG(p.v.flags, UPDATE_NEWEST, p.newest);
12✔
390
        SET_FLAG(p.v.flags, UPDATE_AVAILABLE, p.available);
12✔
391
        SET_FLAG(p.v.flags, UPDATE_INSTALLED, p.installed);
12✔
392
        SET_FLAG(p.v.flags, UPDATE_OBSOLETE, p.obsolete);
12✔
393
        SET_FLAG(p.v.flags, UPDATE_PROTECTED, p.protected);
12✔
394
        SET_FLAG(p.v.flags, UPDATE_INCOMPLETE, p.incomplete);
12✔
395

396
        r = sd_json_variant_format(p.contents_json, 0, &p.v.contents_json);
12✔
397
        if (r < 0)
12✔
UNCOV
398
                return log_error_errno(r, "Failed to format JSON for contents: %m");
×
399

400
        *ret = TAKE_STRUCT(p.v);
12✔
401
        return 0;
12✔
402
}
403

404
static int list_versions_finished(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
10✔
405
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
20✔
406
        Table *table = ASSERT_PTR(op->userdata);
10✔
407
        const sd_bus_error *e;
10✔
UNCOV
408
        _cleanup_(version_done) Version v = {};
×
409
        _cleanup_free_ char *version_link = NULL;
10✔
410
        const char *color;
10✔
411
        int r;
10✔
412

413
        assert(reply);
10✔
414

415
        e = sd_bus_message_get_error(reply);
10✔
416
        if (e)
10✔
UNCOV
417
                return log_bus_error(0, e, NULL, "call Describe");
×
418

419
        r = parse_describe(reply, &v);
10✔
420
        if (r < 0)
10✔
UNCOV
421
                return log_error_errno(r, "Failed to parse Describe output: %m");
×
422

423
        color = update_set_flags_to_color(v.flags);
10✔
424

425
        if (urlify_enabled() && !strv_isempty(v.changelog)) {
10✔
426
                version_link = strjoin(v.version, glyph(GLYPH_EXTERNAL_LINK));
×
427
                if (!version_link)
×
UNCOV
428
                        return log_oom();
×
429
        }
430

431
        r = table_add_many(table,
10✔
432
                           TABLE_STRING,    update_set_flags_to_glyph(v.flags),
433
                           TABLE_SET_COLOR, color,
434
                           TABLE_STRING,    version_link ?: v.version,
435
                           TABLE_SET_COLOR, color,
436
                           TABLE_SET_URL,   strv_isempty(v.changelog) ? NULL : v.changelog[0],
437
                           TABLE_STRING,    update_set_flags_to_string(v.flags),
438
                           TABLE_SET_COLOR, color);
439
        if (r < 0)
10✔
UNCOV
440
                return table_log_add_error(r);
×
441

442
        return 0;
443
}
444

445
static int list_versions(sd_bus *bus, const char *target_path) {
2✔
446
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
2✔
UNCOV
447
        _cleanup_(table_unrefp) Table *table = NULL;
×
448
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
2✔
449
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2✔
450
        _cleanup_strv_free_ char **versions = NULL;
2✔
451
        unsigned remaining = 0;
2✔
452
        int r;
2✔
453

454
        r = sd_bus_call_method(
6✔
455
                        bus,
456
                        bus_sysupdate_mgr->destination,
2✔
457
                        target_path,
458
                        SYSUPDATE_TARGET_INTERFACE,
459
                        "List",
460
                        &error,
461
                        &reply,
462
                        "t",
463
                        arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
2✔
464
        if (r < 0)
2✔
UNCOV
465
                return log_bus_error(r, &error, NULL, "call List");
×
466

467
        r = sd_bus_message_read_strv(reply, &versions);
2✔
468
        if (r < 0)
2✔
UNCOV
469
                return bus_log_parse_error(r);
×
470

471
        table = table_new("", "version", "status");
2✔
472
        if (!table)
2✔
UNCOV
473
                return log_oom();
×
474

475
        (void) table_set_sort(table, 1);
2✔
476
        (void) table_set_reverse(table, 1, true);
2✔
477

478
        r = sd_event_default(&event);
2✔
479
        if (r < 0)
2✔
UNCOV
480
                return log_error_errno(r, "Failed to get event loop: %m");
×
481

482
        r = sd_bus_attach_event(bus, event, 0);
2✔
483
        if (r < 0)
2✔
UNCOV
484
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
485

486
        r = sd_event_set_signal_exit(event, true);
2✔
487
        if (r < 0)
2✔
UNCOV
488
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
489

490
        STRV_FOREACH(version, versions) {
12✔
UNCOV
491
                _cleanup_(operation_freep) Operation *op = NULL;
×
492
                op = operation_new(table, bus, &remaining, NULL, NULL);
10✔
493
                if (!op)
10✔
UNCOV
494
                        return log_oom();
×
495

496
                r = sd_bus_call_method_async(bus,
30✔
497
                                             NULL,
498
                                             bus_sysupdate_mgr->destination,
10✔
499
                                             target_path,
500
                                             SYSUPDATE_TARGET_INTERFACE,
501
                                             "Describe",
502
                                             list_versions_finished,
503
                                             op,
504
                                             "st",
505
                                             *version,
506
                                             arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
10✔
507
                if (r < 0)
10✔
UNCOV
508
                        return log_error_errno(r, "Failed to call Describe: %m");
×
509
                TAKE_PTR(op);
10✔
510

511
                remaining++;
10✔
512
        }
513

514
        r = sd_event_loop(event);
2✔
515
        if (r < 0)
2✔
UNCOV
516
                return log_error_errno(r, "Failed to start event loop: %m");
×
517

518
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
2✔
519
}
520

521
static int describe(sd_bus *bus, const char *target_path, const char *version) {
2✔
522
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
2✔
523
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2✔
UNCOV
524
        _cleanup_(table_unrefp) Table *table = NULL;
×
525
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
2✔
526
        _cleanup_(version_done) Version v = {};
2✔
527
        sd_json_variant *entry;
2✔
528
        const char *color;
2✔
529
        int r;
2✔
530

531
        r = sd_bus_call_method(
6✔
532
                        bus,
533
                        bus_sysupdate_mgr->destination,
2✔
534
                        target_path,
535
                        SYSUPDATE_TARGET_INTERFACE,
536
                        "Describe",
537
                        &error,
538
                        &reply,
539
                        "st",
540
                        version,
541
                        arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
2✔
542
        if (r < 0)
2✔
UNCOV
543
                return log_bus_error(r, &error, NULL, "call Describe");
×
544

545
        r = parse_describe(reply, &v);
2✔
546
        if (r < 0)
2✔
UNCOV
547
                return log_error_errno(r, "Failed to parse Describe output: %m");
×
548

549
        color = strempty(update_set_flags_to_color(v.flags));
2✔
550

551
        printf("%s%s%s Version: %s\n"
6✔
552
               "    State: %s%s%s\n",
553
               color,
554
               update_set_flags_to_glyph(v.flags),
555
               ansi_normal(),
556
               v.version,
557
               color,
558
               update_set_flags_to_string(v.flags),
559
               ansi_normal());
560

561
        STRV_FOREACH(url, v.changelog) {
2✔
UNCOV
562
                _cleanup_free_ char *changelog_link = NULL;
×
563

564
                r = terminal_urlify(*url, NULL, &changelog_link);
×
565
                if (r < 0)
×
UNCOV
566
                        return log_error_errno(r, "Could not urlify link to change-log: %m");
×
567

UNCOV
568
                printf("ChangeLog: %s\n", strna(changelog_link));
×
569
        }
570
        printf("\n");
2✔
571

572
        r = sd_json_parse(v.contents_json, 0, &json, NULL, NULL);
2✔
573
        if (r < 0)
2✔
UNCOV
574
                return log_error_errno(r, "Failed to parse JSON: %m");
×
575

576
        assert(sd_json_variant_is_array(json));
2✔
577

578
        JSON_VARIANT_ARRAY_FOREACH(entry, json) {
12✔
579
                assert(sd_json_variant_is_object(entry));
10✔
580
                const char *key;
10✔
581
                sd_json_variant *value;
10✔
582

583
                if (!table) {
10✔
584
                        table = table_new_raw(sd_json_variant_elements(entry) / 2);
2✔
585
                        if (!table)
2✔
UNCOV
586
                                return log_oom();
×
587

588
                        JSON_VARIANT_OBJECT_FOREACH(key, value, entry) {
20✔
589

590
                                r = table_add_cell(table, NULL, TABLE_HEADER, key);
18✔
591
                                if (r < 0)
18✔
UNCOV
592
                                        return table_log_add_error(r);
×
593
                        }
594
                }
595

596
                JSON_VARIANT_OBJECT_FOREACH(key, value, entry) {
100✔
597
                        TableDataType type;
90✔
598
                        uint64_t number;
90✔
599
                        bool boolean;
90✔
600
                        const void *data;
90✔
601

602
                        if (sd_json_variant_is_string(value)) {
90✔
603
                                type = TABLE_STRING;
24✔
604
                                assert_se(data = sd_json_variant_string(value));
24✔
605
                        } else if (sd_json_variant_is_unsigned(value)) {
66✔
606
                                type = TABLE_UINT64;
20✔
607
                                number = sd_json_variant_unsigned(value);
20✔
608
                                data = &number;
20✔
609
                        } else if (sd_json_variant_is_boolean(value)) {
46✔
610
                                type = TABLE_BOOLEAN;
4✔
611
                                boolean = sd_json_variant_boolean(value);
4✔
612
                                data = &boolean;
4✔
613
                        } else if (sd_json_variant_is_null(value)) {
42✔
614
                                type = TABLE_EMPTY;
615
                                data = NULL;
616
                        } else
UNCOV
617
                                assert_not_reached();
×
618

619
                        if (streq(key, "ptflags"))
90✔
620
                                type = TABLE_UINT64_HEX;
621
                        else if (streq(key, "size"))
80✔
622
                                type = TABLE_SIZE;
623
                        else if (streq(key, "mode"))
80✔
624
                                type = TABLE_MODE;
625
                        else if (streq(key, "mtime"))
70✔
626
                                type = TABLE_TIMESTAMP;
10✔
627

628
                        r = table_add_cell(table, NULL, type, data);
90✔
629
                        if (r < 0)
90✔
UNCOV
630
                                return table_log_add_error(r);
×
631
                }
632
        }
633

634
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
2✔
635
}
636

637
static int verb_list(int argc, char **argv, void *userdata) {
6✔
638
        sd_bus *bus = ASSERT_PTR(userdata);
6✔
639
        _cleanup_free_ char *target_path = NULL, *version = NULL;
6✔
640
        int r;
6✔
641

642
        if (argc == 1)
6✔
643
                return list_targets(bus);
2✔
644

645
        r = parse_target(argv[1], &target_path, &version);
4✔
646
        if (r < 0)
4✔
UNCOV
647
                return log_oom();
×
648

649
        if (version)
4✔
650
                return describe(bus, target_path, version);
2✔
651
        else
652
                return list_versions(bus, target_path);
2✔
653
}
654

655
static int check_describe_finished(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
×
656
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
×
657
        Table *table = ASSERT_PTR(op->userdata);
×
658
        _cleanup_(version_done) Version v = {};
×
659
        _cleanup_free_ char *update = NULL;
×
660
        const sd_bus_error *e;
×
661
        sd_bus_error error = {};
×
662
        const char *lnk = NULL;
×
663
        char *current;
×
UNCOV
664
        int r;
×
665

UNCOV
666
        assert(reply);
×
667

668
        e = sd_bus_message_get_error(reply);
×
669
        if (e)
×
UNCOV
670
                return log_bus_error(0, e, NULL, "call Describe");
×
671

672
        r = parse_describe(reply, &v);
×
673
        if (r < 0)
×
UNCOV
674
                return log_error_errno(r, "Failed to parse output of Describe: %m");
×
675

UNCOV
676
        r = sd_bus_call_method(
×
677
                        op->bus,
678
                        bus_sysupdate_mgr->destination,
×
UNCOV
679
                        op->target_path,
×
680
                        SYSUPDATE_TARGET_INTERFACE,
681
                        "GetVersion",
682
                        &error,
683
                        &reply,
684
                        NULL);
685
        if (r < 0)
×
UNCOV
686
                return log_bus_error(r, &error, op->target_id, "get current version");
×
687

688
        r = sd_bus_message_read_basic(reply, 's', &current);
×
689
        if (r < 0)
×
UNCOV
690
                return bus_log_parse_error(r);
×
691

692
        if (urlify_enabled() && !strv_isempty(v.changelog))
×
UNCOV
693
                lnk = glyph(GLYPH_EXTERNAL_LINK);
×
694

UNCOV
695
        update = strjoin(empty_to_dash(current), " ",
×
696
                         glyph(GLYPH_ARROW_RIGHT), " ",
697
                         v.version, strempty(lnk));
698
        if (!update)
×
UNCOV
699
                return log_oom();
×
700

UNCOV
701
        r = table_add_many(table,
×
702
                           TABLE_STRING,  op->target_id,
703
                           TABLE_STRING,  update,
704
                           TABLE_SET_URL, strv_isempty(v.changelog) ? NULL : v.changelog[0]);
705
        if (r < 0)
×
UNCOV
706
                return table_log_add_error(r);
×
707

708
        return 0;
709
}
710

711
static int check_finished(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
4✔
712
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
4✔
713
        const sd_bus_error *e;
4✔
714
        const char *new_version = NULL;
4✔
715
        int r;
4✔
716

717
        assert(reply);
4✔
718

719
        e = sd_bus_message_get_error(reply);
4✔
720
        if (e)
4✔
UNCOV
721
                return log_bus_error(0, e, op->target_id, "call CheckNew");
×
722

723
        r = sd_bus_message_read(reply, "s", &new_version);
4✔
724
        if (r < 0)
4✔
UNCOV
725
                return bus_log_parse_error(r);
×
726

727
        if (isempty(new_version))
8✔
728
                return 0;
729

UNCOV
730
        r = sd_bus_call_method_async(op->bus,
×
731
                                     NULL,
UNCOV
732
                                     bus_sysupdate_mgr->destination,
×
733
                                     op->target_path,
734
                                     SYSUPDATE_TARGET_INTERFACE,
735
                                     "Describe",
736
                                     check_describe_finished,
737
                                     op,
738
                                     "st",
739
                                     new_version,
740
                                     arg_offline ? SD_SYSUPDATE_OFFLINE : 0);
×
741
        if (r < 0)
×
742
                return log_error_errno(r, "Failed to call Describe: %m");
×
UNCOV
743
        TAKE_PTR(op);
×
744

UNCOV
745
        return 0;
×
746
}
747

748
static int verb_check(int argc, char **argv, void *userdata) {
2✔
749
        sd_bus *bus = ASSERT_PTR(userdata);
2✔
UNCOV
750
        _cleanup_(table_unrefp) Table *table = NULL;
×
751
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
2✔
752
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
2✔
753
        size_t n;
2✔
754
        unsigned remaining = 0;
2✔
755
        int r;
2✔
756

757
        r = ensure_targets(bus, argv + 1, &targets);
2✔
758
        if (r < 0)
2✔
UNCOV
759
                return log_error_errno(r, "Failed to find targets: %m");
×
760

761
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
2✔
762
        if (r < 0)
2✔
UNCOV
763
                return log_error_errno(r, "Failed to parse targets: %m");
×
764

765
        table = table_new("target", "update");
2✔
766
        if (!table)
2✔
UNCOV
767
                return log_oom();
×
768

769
        (void) table_set_sort(table, 0);
2✔
770

771
        r = sd_event_default(&event);
2✔
772
        if (r < 0)
2✔
UNCOV
773
                return log_error_errno(r, "Failed to get event loop: %m");
×
774

775
        r = sd_bus_attach_event(bus, event, 0);
2✔
776
        if (r < 0)
2✔
UNCOV
777
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
778

779
        r = sd_event_set_signal_exit(event, true);
2✔
780
        if (r < 0)
2✔
UNCOV
781
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
782

783
        for (size_t i = 0; i < n; i++) {
6✔
UNCOV
784
                _cleanup_(operation_freep) Operation *op = NULL;
×
785
                op = operation_new(table, bus, &remaining, target_paths[i], targets[i]);
4✔
786
                if (!op)
4✔
UNCOV
787
                        return log_oom();
×
788

789
                r = sd_bus_call_method_async(bus, NULL, bus_sysupdate_mgr->destination, target_paths[i], SYSUPDATE_TARGET_INTERFACE, "CheckNew", check_finished, op, NULL);
4✔
790
                if (r < 0)
4✔
UNCOV
791
                        return log_error_errno(r, "Failed to call CheckNew for target %s: %m", targets[i]);
×
792
                TAKE_PTR(op);
4✔
793

794
                remaining++;
4✔
795
        }
796

797
        r = sd_event_loop(event);
2✔
798
        if (r < 0)
2✔
UNCOV
799
                return log_error_errno(r, "Failed to start event loop: %m");
×
800

801
        if (table_isempty(table)) {
4✔
802
                log_info("No updates available.");
2✔
803
                return 0;
2✔
804
        }
805

UNCOV
806
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
×
807
}
808

809
#define UPDATE_PROGRESS_FAILED INT_MIN
810
#define UPDATE_PROGRESS_DONE INT_MAX
811
/* Make sure it doesn't overlap w/ errno values */
812
assert_cc(UPDATE_PROGRESS_FAILED < -ERRNO_MAX);
813

814
static int update_render_progress(sd_event_source *source, void *userdata) {
17✔
815
        OrderedHashmap *map = ASSERT_PTR(userdata);
17✔
816
        const char *target;
17✔
817
        void *p;
17✔
818
        unsigned total;
17✔
819
        size_t n;
17✔
820
        bool exiting;
17✔
821

822
        exiting = sd_event_get_state(sd_event_source_get_event(source)) == SD_EVENT_EXITING;
17✔
823

824
        total = 0;
17✔
825
        n = ordered_hashmap_size(map);
17✔
826

827
        if (n == 0)
17✔
828
                return 0;
17✔
829

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

834
        if (!terminal_is_dumb()) {
13✔
835
                for (size_t i = 0; i <= n; i++)
×
UNCOV
836
                        fputs("\n", stderr); /* Possibly scroll the terminal to make room (including total) */
×
837

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

840
                fputs("\e7", stderr); /* Save cursor position */
×
UNCOV
841
                fputs("\e[?25l", stderr); /* Hide cursor */
×
842
        }
843

844
        ORDERED_HASHMAP_FOREACH_KEY(p, target, map) {
26✔
845
                int progress = PTR_TO_INT(p);
13✔
846

847
                if (progress == UPDATE_PROGRESS_FAILED) {
13✔
848
                        clear_progress_bar_unbuffered(target);
×
849
                        fprintf(stderr, "%s: %s Unknown failure\n", target, RED_CROSS_MARK());
×
UNCOV
850
                        total += 100;
×
851
                } else if (progress == -EALREADY) {
13✔
852
                        clear_progress_bar_unbuffered(target);
×
853
                        fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK());
×
UNCOV
854
                        n--; /* Don't consider this target in the total */
×
855
                } else if (progress < 0) {
13✔
856
                        clear_progress_bar_unbuffered(target);
×
857
                        fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress));
×
UNCOV
858
                        total += 100;
×
859
                } else if (progress == UPDATE_PROGRESS_DONE) {
13✔
860
                        clear_progress_bar_unbuffered(target);
2✔
861
                        fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK());
2✔
862
                        total += 100;
2✔
863
                } else {
864
                        draw_progress_bar_unbuffered(target, progress);
11✔
865
                        fputs("\n", stderr);
11✔
866
                        total += progress;
11✔
867
                }
868
        }
869

870
        if (n > 1) {
13✔
871
                if (exiting)
×
UNCOV
872
                        clear_progress_bar_unbuffered(target);
×
873
                else {
874
                        draw_progress_bar_unbuffered("Total", (double) total / n);
×
875
                        if (terminal_is_dumb())
×
UNCOV
876
                                fputs("\n", stderr);
×
877
                }
878
        }
879

880
        if (!terminal_is_dumb()) {
13✔
881
                if (exiting)
×
UNCOV
882
                        fputs("\e[?25h", stderr); /* Show cursor again */
×
883
                else
UNCOV
884
                        fputs("\e8", stderr); /* Restore cursor position */
×
885
        } else if (!exiting)
13✔
886
                fputs("------\n", stderr);
11✔
887

888
        return 0;
13✔
889
}
890

891
static int update_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
7✔
892
        Operation *op = ASSERT_PTR(userdata);
7✔
893
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
7✔
894
        const char *interface;
7✔
895
        uint32_t progress = UINT32_MAX;
7✔
896
        static const struct bus_properties_map prop_map[] = {
7✔
897
                { "Progress", "u", NULL, 0 },
898
                {}
899
        };
900
        int r;
7✔
901

902
        assert(m);
7✔
903

904
        r = sd_bus_message_read(m, "s", &interface);
7✔
905
        if (r < 0) {
7✔
906
                bus_log_parse_error_debug(r);
×
UNCOV
907
                return 0;
×
908
        }
909

910
        if (!streq(interface, "org.freedesktop.sysupdate1.Job"))
7✔
911
                return 0;
912

913
        r = bus_message_map_all_properties(m, prop_map, /* flags= */ 0, error, &progress);
7✔
914
        if (r < 0)
7✔
915
                return 0; /* map_all_properties does the debug logging internally... */
916

917
        if (progress == UINT_MAX)
7✔
918
                return 0;
919

920
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR((int) progress));
7✔
921
        if (r < 0)
7✔
UNCOV
922
                log_debug_errno(r, "Failed to update hashmap: %m");
×
923
        return 0;
924
}
925

926
static int update_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
2✔
927
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
2✔
928
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
2✔
929
        uint64_t id;
2✔
930
        int r, status;
2✔
931

932
        assert(m);
2✔
933

934
        r = sd_bus_message_read(m, "toi", &id, NULL, &status);
2✔
935
        if (r < 0) {
2✔
936
                bus_log_parse_error_debug(r);
×
UNCOV
937
                return 0;
×
938
        }
939

940
        if (id != op->job_id) {
2✔
941
                TAKE_PTR(op);
×
UNCOV
942
                return 0;
×
943
        }
944

945
        if (status == 0) /* success */
2✔
946
                status = UPDATE_PROGRESS_DONE;
2✔
947
        else if (status > 0) /* exit status without errno */
×
UNCOV
948
                status = UPDATE_PROGRESS_FAILED; /* i.e. EXIT_FAILURE */
×
949
        /* else errno */
950

951
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(status));
2✔
952
        if (r < 0)
2✔
953
                log_debug_errno(r, "Failed to update hashmap: %m");
2✔
954
        return 0;
955
}
956

UNCOV
957
static int update_interrupted(sd_event_source *source, void *userdata) {
×
958
        /* Since the event loop is exiting, we will never receive the JobRemoved
959
         * signal. So, we must free the userdata here. */
960
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
×
961
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
962
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
×
UNCOV
963
        int r;
×
964

965
        r = sd_bus_call_method(op->bus,
×
966
                               bus_sysupdate_mgr->destination,
×
UNCOV
967
                               op->job_path,
×
968
                               "org.freedesktop.sysupdate1.Job",
969
                               "Cancel",
970
                               &error, /* reply= */ NULL,
971
                               NULL);
972
        if (r < 0)
×
UNCOV
973
                return log_bus_error(r, &error, NULL, "call Cancel");
×
974

975
        r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(-ECANCELED));
×
976
        if (r < 0)
×
UNCOV
977
                log_debug_errno(r, "Failed to update hashmap: %m");
×
978

979
        return 0;
980
}
981

982
static int update_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
2✔
983
        _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
2✔
984
        OrderedHashmap *map = ASSERT_PTR(op->userdata);
2✔
985
        const sd_bus_error *e;
2✔
986
        _cleanup_free_ char *key = NULL;
2✔
987
        const char *new_version, *job_path;
2✔
988
        int r;
2✔
989

990
        assert(reply);
2✔
991

992
        e = sd_bus_message_get_error(reply);
2✔
993
        if (e) {
2✔
UNCOV
994
                r = -sd_bus_error_get_errno(e);
×
995

996
                key = strdup(op->target_id);
×
997
                if (!key)
×
998
                        return log_oom();
×
999
                r = ordered_hashmap_put(map, key, INT_TO_PTR(r));
×
1000
                if (r < 0)
×
UNCOV
1001
                        return log_error_errno(r, "Failed to update hashmap: %m");
×
1002
                TAKE_PTR(key);
2✔
1003

1004
                return 0;
1005
        }
1006

1007
        r = sd_bus_message_read(reply, "sto", &new_version, &op->job_id, &job_path);
2✔
1008
        if (r < 0)
2✔
UNCOV
1009
                return bus_log_parse_error(r);
×
1010
        op->job_path = strdup(job_path);
2✔
1011
        if (!op->job_path)
2✔
UNCOV
1012
                return log_oom();
×
1013
        if (isempty(new_version))
2✔
UNCOV
1014
                new_version = "latest";
×
1015

1016
        /* Register this job into the hashmap. This will give it a progress bar */
1017
        if (strchr(op->target_id, '@'))
2✔
UNCOV
1018
                key = strdup(op->target_id);
×
1019
        else
1020
                key = strjoin(op->target_id, "@", new_version);
2✔
1021
        if (!key)
2✔
UNCOV
1022
                return log_oom();
×
1023
        r = ordered_hashmap_put(map, key, INT_TO_PTR(0)); /* takes ownership of key */
2✔
1024
        if (r < 0)
2✔
UNCOV
1025
                return log_error_errno(r, "Failed to add target to tracking map: %m");
×
1026
        op->target_id = TAKE_PTR(key); /* just borrowing */
2✔
1027

1028
        /* Cancel the job if the event loop exits */
1029
        r = sd_event_add_exit(op->event, &op->job_interrupt_source, update_interrupted, op);
2✔
1030
        if (r < 0)
2✔
UNCOV
1031
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
1032

1033
        /* We need to cancel the job before the final iteration of the renderer runs */
1034
        r = sd_event_source_set_priority(op->job_interrupt_source, SD_EVENT_PRIORITY_IMPORTANT);
2✔
1035
        if (r < 0)
2✔
UNCOV
1036
                return log_error_errno(r, "Failed to set interrupt priority: %m");
×
1037

1038
        /* Register for progress notifications */
1039
        r = sd_bus_match_signal_async(
4✔
1040
                        op->bus,
1041
                        &op->job_properties_slot,
1042
                        bus_sysupdate_mgr->destination,
2✔
1043
                        job_path,
1044
                        "org.freedesktop.DBus.Properties",
1045
                        "PropertiesChanged",
1046
                        update_properties_changed,
1047
                        NULL,
1048
                        op);
1049
        if (r < 0)
2✔
UNCOV
1050
                return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
×
1051

1052
        TAKE_PTR(op); /* update_finished/update_interrupted take ownership of the data */
2✔
1053

1054
        return 0;
2✔
1055
}
1056

1057
static int do_update(sd_bus *bus, char **targets) {
2✔
1058
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
2✔
1059
        _cleanup_(sd_event_source_unrefp) sd_event_source *render_exit = NULL;
2✔
1060
        _cleanup_ordered_hashmap_free_ OrderedHashmap *map = NULL;
2✔
1061
        _cleanup_strv_free_ char **versions = NULL, **target_paths = NULL;
2✔
1062
        size_t n;
2✔
1063
        unsigned remaining = 0;
2✔
1064
        void *p;
2✔
1065
        bool did_anything = false;
2✔
1066
        int r;
2✔
1067

1068
        assert(bus);
2✔
1069
        assert(targets);
2✔
1070

1071
        r = parse_targets(targets, &n, &target_paths, &versions);
2✔
1072
        if (r < 0)
2✔
UNCOV
1073
                return log_error_errno(r, "Could not parse targets: %m");
×
1074

1075
        map = ordered_hashmap_new(&string_hash_ops_free);
2✔
1076
        if (!map)
2✔
UNCOV
1077
                return log_oom();
×
1078

1079
        r = sd_event_default(&event);
2✔
1080
        if (r < 0)
2✔
UNCOV
1081
                return log_error_errno(r, "Failed to get event loop: %m");
×
1082

1083
        r = sd_bus_attach_event(bus, event, 0);
2✔
1084
        if (r < 0)
2✔
UNCOV
1085
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
1086

1087
        r = sd_event_set_signal_exit(event, true);
2✔
1088
        if (r < 0)
2✔
UNCOV
1089
                return log_error_errno(r, "Failed to set up interrupt handler: %m");
×
1090

1091
        for (size_t i = 0; i < n; i++) {
4✔
UNCOV
1092
                _cleanup_(operation_freep) Operation *op = NULL;
×
1093
                op = operation_new(map, bus, &remaining, target_paths[i], targets[i]);
2✔
1094
                if (!op)
2✔
UNCOV
1095
                        return log_oom();
×
1096

1097
                /* Sign up for notification when the associated job finishes */
1098
                r = bus_match_signal_async(
2✔
1099
                                op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_finished, NULL, op);
1100
                if (r < 0)
2✔
UNCOV
1101
                        return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
×
1102

1103
                r = sd_bus_call_method_async(
4✔
1104
                                bus,
1105
                                NULL,
1106
                                bus_sysupdate_mgr->destination,
2✔
1107
                                target_paths[i],
2✔
1108
                                SYSUPDATE_TARGET_INTERFACE,
1109
                                "Update",
1110
                                update_started,
1111
                                op,
1112
                                "st",
1113
                                versions[i],
2✔
1114
                                0LU);
1115
                if (r < 0)
2✔
UNCOV
1116
                        return log_bus_error(r, NULL, targets[i], "call Update");
×
1117
                TAKE_PTR(op);
2✔
1118

1119
                remaining++;
2✔
1120
        }
1121

1122
        /* Set up the rendering */
1123
        r = sd_event_add_post(event, NULL, update_render_progress, map);
2✔
1124
        if (r < 0)
2✔
UNCOV
1125
                return log_error_errno(r, "Failed to add progress rendering callback: %m");
×
1126

1127
        r = sd_event_add_exit(event, &render_exit, update_render_progress, map);
2✔
1128
        if (r < 0)
2✔
UNCOV
1129
                return log_error_errno(r, "Failed to add exit callback: %m");
×
1130

1131
        r = sd_event_source_set_priority(render_exit, SD_EVENT_PRIORITY_IDLE);
2✔
1132
        if (r < 0)
2✔
UNCOV
1133
                return log_error_errno(r, "Failed to set priority of update job");
×
1134

1135
        r = sd_event_loop(event);
2✔
1136
        if (r < 0)
2✔
UNCOV
1137
                return log_error_errno(r, "Failed to start event loop");
×
1138

1139
        ORDERED_HASHMAP_FOREACH(p, map) {
4✔
1140
                r = PTR_TO_INT(p);
2✔
1141
                if (r == -EALREADY)
2✔
UNCOV
1142
                        continue;
×
1143
                if (r == UPDATE_PROGRESS_FAILED)
2✔
UNCOV
1144
                        return EXIT_FAILURE;
×
1145
                if (r < 0)
2✔
1146
                        return r;
1147

1148
                did_anything = true;
1149
        }
1150

1151
        return did_anything ? 1 : 0;
2✔
1152
}
1153

1154
static int verb_update(int argc, char **argv, void *userdata) {
2✔
1155
        sd_bus *bus = ASSERT_PTR(userdata);
2✔
1156
        _cleanup_strv_free_ char **targets = NULL;
2✔
1157
        bool did_anything = false;
2✔
1158
        int r;
2✔
1159

1160
        r = ensure_targets(bus, argv + 1, &targets);
2✔
1161
        if (r < 0)
2✔
UNCOV
1162
                return log_error_errno(r, "Could not find targets: %m");
×
1163

1164
        r = do_update(bus, targets);
2✔
1165
        if (r < 0)
2✔
1166
                return r;
1167
        if (r > 0)
2✔
1168
                did_anything = true;
2✔
1169

1170
        if (!arg_reboot)
2✔
1171
                return 0;
1172

1173
        if (did_anything)
×
UNCOV
1174
                return reboot_now();
×
1175

UNCOV
1176
        log_info("Nothing was updated... skipping reboot.");
×
1177
        return 0;
1178
}
1179

1180
static int do_vacuum(sd_bus *bus, const char *target, const char *path) {
×
1181
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1182
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1183
        uint32_t count, disabled;
×
UNCOV
1184
        int r;
×
1185

1186
        r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination, path, SYSUPDATE_TARGET_INTERFACE, "Vacuum", &error, &reply, NULL);
×
1187
        if (r < 0)
×
UNCOV
1188
                return log_bus_error(r, &error, target, "call Vacuum");
×
1189

1190
        r = sd_bus_message_read(reply, "uu", &count, &disabled);
×
1191
        if (r < 0)
×
UNCOV
1192
                return bus_log_parse_error(r);
×
1193

1194
        if (count > 0 && disabled > 0)
×
UNCOV
1195
                log_info("Deleted %u instance(s) and %u disabled transfer(s) of %s.",
×
1196
                         count, disabled, target);
1197
        else if (count > 0)
×
1198
                log_info("Deleted %u instance(s) of %s.", count, target);
×
1199
        else if (disabled > 0)
×
UNCOV
1200
                log_info("Deleted %u disabled transfer(s) of %s.", disabled, target);
×
1201
        else
UNCOV
1202
                log_info("Found nothing to delete for %s.", target);
×
1203

UNCOV
1204
        return count + disabled > 0 ? 1 : 0;
×
1205
}
1206

1207
static int verb_vacuum(int argc, char **argv, void *userdata) {
×
1208
        sd_bus *bus = ASSERT_PTR(userdata);
×
1209
        _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
×
1210
        size_t n;
×
UNCOV
1211
        int r;
×
1212

1213
        r = ensure_targets(bus, argv + 1, &targets);
×
1214
        if (r < 0)
×
UNCOV
1215
                return log_error_errno(r, "Failed to find targets: %m");
×
1216

1217
        r = parse_targets(targets, &n, &target_paths, /* ret_versions= */ NULL);
×
1218
        if (r < 0)
×
UNCOV
1219
                return log_error_errno(r, "Failed to parse targets: %m");
×
1220

1221
        for (size_t i = 0; i < n; i++) {
×
1222
                r = do_vacuum(bus, targets[i], target_paths[i]);
×
UNCOV
1223
                if (r < 0)
×
1224
                        return r;
1225
        }
1226
        return 0;
1227
}
1228

1229
typedef struct Feature {
1230
        char *name;
1231
        char *description;
1232
        bool enabled;
1233
        char *documentation;
1234
        char **transfers;
1235
} Feature;
1236

1237
static void feature_done(Feature *f) {
×
1238
        assert(f);
×
1239
        f->name = mfree(f->name);
×
1240
        f->description = mfree(f->description);
×
1241
        f->documentation = mfree(f->documentation);
×
1242
        f->transfers = strv_free(f->transfers);
×
UNCOV
1243
}
×
1244

1245
static int describe_feature(sd_bus *bus, const char *feature, Feature *ret) {
×
1246
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1247
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1248
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1249
        _cleanup_(feature_done) Feature f = {};
×
1250
        char *json;
×
UNCOV
1251
        int r;
×
1252

UNCOV
1253
        static const sd_json_dispatch_field dispatch_table[] = {
×
1254
                { "name",             SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, name),          SD_JSON_MANDATORY },
1255
                { "description",      SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, description),   0                 },
1256
                { "enabled",          SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Feature, enabled),       SD_JSON_MANDATORY },
1257
                { "documentationUrl", SD_JSON_VARIANT_STRING,  sd_json_dispatch_string,  offsetof(Feature, documentation), 0                 },
1258
                { "transfers",        SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_strv,    offsetof(Feature, transfers),     0                 },
1259
                {}
1260
        };
1261

1262
        assert(bus);
×
1263
        assert(feature);
×
UNCOV
1264
        assert(ret);
×
1265

1266
        r = sd_bus_call_method(bus,
×
UNCOV
1267
                               bus_sysupdate_mgr->destination,
×
1268
                               SYSUPDATE_HOST_PATH,
1269
                               SYSUPDATE_TARGET_INTERFACE,
1270
                               "DescribeFeature",
1271
                               &error,
1272
                               &reply,
1273
                               "st",
1274
                               feature,
1275
                               UINT64_C(0));
1276
        if (r < 0)
×
UNCOV
1277
                return log_bus_error(r, &error, "host", "lookup feature");
×
1278

1279
        r = sd_bus_message_read_basic(reply, 's', &json);
×
1280
        if (r < 0)
×
UNCOV
1281
                return bus_log_parse_error(r);
×
1282

1283
        r = sd_json_parse(json, 0, &v, NULL, NULL);
×
1284
        if (r < 0)
×
UNCOV
1285
                return log_error_errno(r, "Failed to parse JSON: %m");
×
1286

1287
        r = sd_json_dispatch(v, dispatch_table, 0, &f);
×
1288
        if (r < 0)
×
UNCOV
1289
                return log_error_errno(r, "Failed to dispatch JSON: %m");
×
1290

1291
        *ret = TAKE_STRUCT(f);
×
UNCOV
1292
        return 0;
×
1293
}
1294

1295
static int list_features(sd_bus *bus) {
×
1296
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1297
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1298
        _cleanup_strv_free_ char **features = NULL;
×
1299
        _cleanup_(table_unrefp) Table *table = NULL;
×
UNCOV
1300
        int r;
×
1301

UNCOV
1302
        assert(bus);
×
1303

1304
        table = table_new("", "feature", "description");
×
1305
        if (!table)
×
UNCOV
1306
                return log_oom();
×
1307

1308
        r = sd_bus_call_method(bus,
×
UNCOV
1309
                               bus_sysupdate_mgr->destination,
×
1310
                               SYSUPDATE_HOST_PATH,
1311
                               SYSUPDATE_TARGET_INTERFACE,
1312
                               "ListFeatures",
1313
                               &error,
1314
                               &reply,
1315
                               "t",
1316
                               UINT64_C(0));
1317
        if (r < 0)
×
UNCOV
1318
                return log_bus_error(r, &error, "host", "lookup feature");
×
1319

1320
        r = sd_bus_message_read_strv(reply, &features);
×
1321
        if (r < 0)
×
UNCOV
1322
                return bus_log_parse_error(r);
×
1323

1324
        STRV_FOREACH(feature, features) {
×
1325
                _cleanup_(feature_done) Feature f = {};
×
UNCOV
1326
                _cleanup_free_ char *name_link = NULL;
×
1327

1328
                r = describe_feature(bus, *feature, &f);
×
UNCOV
1329
                if (r < 0)
×
1330
                        return r;
1331

1332
                if (urlify_enabled() && f.documentation) {
×
1333
                        name_link = strjoin(f.name, glyph(GLYPH_EXTERNAL_LINK));
×
1334
                        if (!name_link)
×
UNCOV
1335
                                return log_oom();
×
1336
                }
1337

UNCOV
1338
                r = table_add_many(table,
×
1339
                                   TABLE_BOOLEAN_CHECKMARK, f.enabled,
1340
                                   TABLE_SET_COLOR, ansi_highlight_green_red(f.enabled),
1341
                                   TABLE_STRING, name_link ?: f.name,
1342
                                   TABLE_SET_URL, f.documentation,
1343
                                   TABLE_STRING, f.description);
1344
                if (r < 0)
×
UNCOV
1345
                        return table_log_add_error(r);
×
1346
        }
1347

UNCOV
1348
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
×
1349
}
1350

1351
static int verb_features(int argc, char **argv, void *userdata) {
×
1352
        sd_bus *bus = ASSERT_PTR(userdata);
×
1353
        _cleanup_(table_unrefp) Table *table = NULL;
×
1354
        _cleanup_(feature_done) Feature f = {};
×
UNCOV
1355
        int r;
×
1356

1357
        if (argc == 1)
×
UNCOV
1358
                return list_features(bus);
×
1359

1360
        table = table_new_vertical();
×
1361
        if (!table)
×
UNCOV
1362
                return log_oom();
×
1363

1364
        r = describe_feature(bus, argv[1], &f);
×
UNCOV
1365
        if (r < 0)
×
1366
                return r;
1367

UNCOV
1368
        r = table_add_many(table,
×
1369
                           TABLE_FIELD, "Name",
1370
                           TABLE_STRING, f.name,
1371
                           TABLE_FIELD, "Enabled",
1372
                           TABLE_BOOLEAN, f.enabled);
1373
        if (r < 0)
×
UNCOV
1374
                return table_log_add_error(r);
×
1375

1376
        if (f.description) {
×
1377
                r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f.description);
×
1378
                if (r < 0)
×
UNCOV
1379
                        return table_log_add_error(r);
×
1380
        }
1381

1382
        if (f.documentation) {
×
UNCOV
1383
                r = table_add_many(table,
×
1384
                                   TABLE_FIELD, "Documentation",
1385
                                   TABLE_STRING, f.documentation,
1386
                                   TABLE_SET_URL, f.documentation);
1387
                if (r < 0)
×
UNCOV
1388
                        return table_log_add_error(r);
×
1389
        }
1390

1391
        if (!strv_isempty(f.transfers)) {
×
1392
                r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, f.transfers);
×
1393
                if (r < 0)
×
UNCOV
1394
                        return table_log_add_error(r);
×
1395
        }
1396

UNCOV
1397
        return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false);
×
1398
}
1399

1400
static int verb_enable(int argc, char **argv, void *userdata) {
×
1401
        sd_bus *bus = ASSERT_PTR(userdata);
×
1402
        bool did_anything = false, enable;
×
1403
        char **features;
×
UNCOV
1404
        int r;
×
1405

1406
        enable = streq(argv[0], "enable");
×
UNCOV
1407
        features = strv_skip(argv, 1);
×
1408

1409
        STRV_FOREACH(feature, features) {
×
UNCOV
1410
                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1411

1412
                r = sd_bus_call_method(bus,
×
UNCOV
1413
                                       bus_sysupdate_mgr->destination,
×
1414
                                       SYSUPDATE_HOST_PATH,
1415
                                       SYSUPDATE_TARGET_INTERFACE,
1416
                                       "SetFeatureEnabled",
1417
                                       &error,
1418
                                       /* reply= */ NULL,
1419
                                       "sit",
1420
                                       *feature,
1421
                                       (int) enable,
1422
                                       UINT64_C(0));
1423
                if (r < 0)
×
UNCOV
1424
                        return log_bus_error(r, &error, "host", "call SetFeatureEnabled");
×
1425
        }
1426

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

1430
        if (enable) {
×
1431
                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
1432
                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1433
                _cleanup_free_ char *target = NULL;
×
UNCOV
1434
                char *version = NULL;
×
1435

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

1440
                r = sd_bus_call_method(bus,
×
UNCOV
1441
                                       bus_sysupdate_mgr->destination,
×
1442
                                       SYSUPDATE_HOST_PATH,
1443
                                       SYSUPDATE_TARGET_INTERFACE,
1444
                                       "GetVersion",
1445
                                       &error,
1446
                                       &reply,
1447
                                       NULL);
1448
                if (r < 0)
×
UNCOV
1449
                        return log_bus_error(r, &error, "host", "get current version");
×
1450

1451
                r = sd_bus_message_read_basic(reply, 's', &version);
×
1452
                if (r < 0)
×
UNCOV
1453
                        return bus_log_parse_error(r);
×
1454

1455
                target = strjoin("host@", version);
×
1456
                if (!target)
×
UNCOV
1457
                        return log_oom();
×
1458

UNCOV
1459
                r = do_update(bus, STRV_MAKE(target));
×
1460
        } else
1461
                r = do_vacuum(bus, "host", SYSUPDATE_HOST_PATH);
×
UNCOV
1462
        if (r < 0)
×
1463
                return r;
1464
        if (r > 0)
×
UNCOV
1465
                did_anything = true;
×
1466

1467
        if (arg_reboot && did_anything)
×
1468
                return reboot_now();
×
1469
        else if (did_anything)
×
UNCOV
1470
                log_info("Feature(s) %s.", enable ? "downloaded" : "deleted");
×
1471
        else
UNCOV
1472
                log_info("Nothing %s%s.",
×
1473
                         enable ? "downloaded" : "deleted",
1474
                         arg_reboot ? ", skipping reboot" :"");
1475

1476
        return 0;
1477
}
1478

1479
static int help(void) {
×
1480
        _cleanup_free_ char *link = NULL;
×
UNCOV
1481
        int r;
×
1482

1483
        r = terminal_urlify_man("updatectl", "1", &link);
×
1484
        if (r < 0)
×
UNCOV
1485
                return log_oom();
×
1486

UNCOV
1487
        printf("%1$s [OPTIONS...] [VERSION]\n"
×
1488
               "\n%5$sManage system updates.%6$s\n"
1489
               "\n%3$sCommands:%4$s\n"
1490
               "  list [TARGET[@VERSION]]       List available targets and versions\n"
1491
               "  check [TARGET...]             Check for updates\n"
1492
               "  update [TARGET[@VERSION]...]  Install updates\n"
1493
               "  vacuum [TARGET...]            Clean up old updates\n"
1494
               "  features [FEATURE]            List and inspect optional features on host OS\n"
1495
               "  enable FEATURE...             Enable optional feature on host OS\n"
1496
               "  disable FEATURE...            Disable optional feature on host OS\n"
1497
               "  -h --help                     Show this help\n"
1498
               "     --version                  Show package version\n"
1499
               "\n%3$sOptions:%4$s\n"
1500
               "     --reboot             Reboot after updating to newer version\n"
1501
               "     --offline            Do not fetch metadata from the network\n"
1502
               "     --now                Download/delete resources immediately\n"
1503
               "  -H --host=[USER@]HOST   Operate on remote host\n"
1504
               "     --no-pager           Do not pipe output into a pager\n"
1505
               "     --no-legend          Do not show the headers and footers\n"
1506
               "\nSee the %2$s for details.\n"
1507
               , program_invocation_short_name
1508
               , link
1509
               , ansi_underline(), ansi_normal()
1510
               , ansi_highlight(), ansi_normal()
1511
        );
1512

1513
        return 0;
1514
}
1515

1516
static int parse_argv(int argc, char *argv[]) {
10✔
1517

1518
        enum {
10✔
1519
                ARG_VERSION = 0x100,
1520
                ARG_NO_PAGER,
1521
                ARG_NO_LEGEND,
1522
                ARG_REBOOT,
1523
                ARG_OFFLINE,
1524
                ARG_NOW,
1525
        };
1526

1527
        static const struct option options[] = {
10✔
1528
                { "help",      no_argument,       NULL, 'h'             },
1529
                { "version",   no_argument,       NULL, ARG_VERSION     },
1530
                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER    },
1531
                { "no-legend", no_argument,       NULL, ARG_NO_LEGEND   },
1532
                { "host",      required_argument, NULL, 'H'             },
1533
                { "reboot",    no_argument,       NULL, ARG_REBOOT      },
1534
                { "offline",   no_argument,       NULL, ARG_OFFLINE     },
1535
                { "now",       no_argument,       NULL, ARG_NOW         },
1536
                {}
1537
        };
1538

1539
        int c;
10✔
1540

1541
        assert(argc >= 0);
10✔
1542
        assert(argv);
10✔
1543

1544
        while ((c = getopt_long(argc, argv, "hH:", options, NULL)) >= 0) {
10✔
UNCOV
1545
                switch (c) {
×
1546

1547
                case 'h':
×
UNCOV
1548
                        return help();
×
1549

1550
                case ARG_VERSION:
×
UNCOV
1551
                        return version();
×
1552

1553
                case ARG_NO_PAGER:
×
1554
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
1555
                        break;
×
1556

1557
                case ARG_NO_LEGEND:
×
1558
                        arg_legend = false;
×
UNCOV
1559
                        break;
×
1560

1561
                case 'H':
×
1562
                        arg_transport = BUS_TRANSPORT_REMOTE;
×
1563
                        arg_host = optarg;
×
UNCOV
1564
                        break;
×
1565

1566
                case ARG_REBOOT:
×
1567
                        arg_reboot = true;
×
UNCOV
1568
                        break;
×
1569

1570
                case ARG_OFFLINE:
×
1571
                        arg_offline = true;
×
UNCOV
1572
                        break;
×
1573

1574
                case ARG_NOW:
×
1575
                        arg_now = true;
×
UNCOV
1576
                        break;
×
1577

1578
                case '?':
1579
                        return -EINVAL;
1580

1581
                default:
×
UNCOV
1582
                        assert_not_reached();
×
1583
                }
1584
        }
1585

1586
        return 1;
1587
}
1588

1589
static int run(int argc, char *argv[]) {
10✔
1590
        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
10✔
1591
        int r;
10✔
1592

1593
        static const Verb verbs[] = {
10✔
1594
                { "list",     VERB_ANY, 2,        VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list     },
1595
                { "check",    VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              verb_check    },
1596
                { "update",   VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              verb_update   },
1597
                { "vacuum",   VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,              verb_vacuum   },
1598
                { "features", VERB_ANY, 2,        VERB_ONLINE_ONLY,              verb_features },
1599
                { "enable",   2,        VERB_ANY, VERB_ONLINE_ONLY,              verb_enable   },
1600
                { "disable",  2,        VERB_ANY, VERB_ONLINE_ONLY,              verb_enable   },
1601
                {}
1602
        };
1603

1604
        setlocale(LC_ALL, "");
10✔
1605
        log_setup();
10✔
1606

1607
        (void) signal(SIGWINCH, columns_lines_cache_reset);
10✔
1608

1609
        r = parse_argv(argc, argv);
10✔
1610
        if (r <= 0)
10✔
1611
                return r;
1612

1613
        r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
10✔
1614
        if (r < 0)
10✔
1615
                return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
10✔
1616

1617
        if (arg_transport == BUS_TRANSPORT_LOCAL)
10✔
1618
                polkit_agent_open();
10✔
1619

1620
        return dispatch_verb(argc, argv, verbs, bus);
10✔
1621
}
1622

1623
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
10✔
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