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

systemd / systemd / 28689257516

03 Jul 2026 11:32PM UTC coverage: 72.9% (+0.3%) from 72.618%
28689257516

push

github

bluca
hwdb: map mic-mute key on Logitech K950 (Bluetooth)

The mic-mute key on the Logitech K950 keyboard (046D:B388, Bluetooth)
emits BTN_0 (scancode 0x100e1) instead of a usable key, so it does
nothing under GNOME/KDE. Map it to KEY_MICMUTE, its intended function.

Scancode determined with evtest on the device.

343301 of 470919 relevant lines covered (72.9%)

1344559.44 hits per line

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

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

3
#include <unistd.h>
4

5
#include "sd-bus.h"
6
#include "sd-daemon.h"
7
#include "sd-json.h"
8
#include "sd-varlink.h"
9

10
#include "build.h"
11
#include "bus-polkit.h"
12
#include "conf-files.h"
13
#include "constants.h"
14
#include "discover-image.h"
15
#include "dissect-image.h"
16
#include "env-util.h"
17
#include "errno-util.h"
18
#include "fd-util.h"
19
#include "format-table.h"
20
#include "glyph-util.h"
21
#include "hashmap.h"
22
#include "help-util.h"
23
#include "hexdecoct.h"
24
#include "image-policy.h"
25
#include "json-util.h"
26
#include "loop-util.h"
27
#include "main-func.h"
28
#include "mount-util.h"
29
#include "options.h"
30
#include "os-util.h"
31
#include "pager.h"
32
#include "parse-argument.h"
33
#include "parse-util.h"
34
#include "pretty-print.h"
35
#include "runtime-scope.h"
36
#include "sort-util.h"
37
#include "string-util.h"
38
#include "strv.h"
39
#include "sysupdate.h"
40
#include "sysupdate-cleanup.h"
41
#include "sysupdate-feature.h"
42
#include "sysupdate-instance.h"
43
#include "sysupdate-target.h"
44
#include "sysupdate-transfer.h"
45
#include "sysupdate-update-set.h"
46
#include "sysupdate-util.h"
47
#include "varlink-io.systemd.SysUpdate.h"
48
#include "varlink-util.h"
49
#include "verbs.h"
50

51
static char *arg_definitions = NULL;
52
static bool arg_sync = true;
53
static uint64_t arg_instances_max = UINT64_MAX;
54
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
55
static PagerFlags arg_pager_flags = 0;
56
static bool arg_legend = true;
57
static char *arg_root = NULL;
58
static char *arg_image = NULL;
59
static bool arg_reboot = false;
60
static int arg_cleanup = -1;
61
static char *arg_component = NULL;
62
static bool arg_component_all = false;
63
static int arg_verify = -1;
64
static ImagePolicy *arg_image_policy = NULL;
65
static bool arg_offline = false;
66
static char *arg_transfer_source = NULL;
67
static bool arg_varlink = false;
68

69
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
1,328✔
70
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
1,328✔
71
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
1,328✔
72
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
1,328✔
73
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
1,328✔
74
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
1,328✔
75

76
#define CONTEXT_NULL                                              \
77
        (Context) {                                               \
78
                .sync = true,                                     \
79
                .instances_max = UINT64_MAX,                      \
80
                .verify = -1,                                     \
81
                .cleanup = -1,                                    \
82
                .installdb_fd = -EBADF,                           \
83
                .target_identifier.class = _TARGET_CLASS_INVALID, \
84
        }
85

86
void context_done(Context *c) {
2,422✔
87
        assert(c);
2,422✔
88

89
        c->mounted_dir = umount_and_rmdir_and_free(c->mounted_dir);
2,422✔
90
        c->loop_device = loop_device_unref(c->loop_device);
2,422✔
91

92
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
11,202✔
93
                transfer_free(*tr);
8,780✔
94
        c->transfers = mfree(c->transfers);
2,422✔
95
        c->n_transfers = 0;
2,422✔
96

97
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
3,554✔
98
                transfer_free(*tr);
1,132✔
99
        c->disabled_transfers = mfree(c->disabled_transfers);
2,422✔
100
        c->n_disabled_transfers = 0;
2,422✔
101

102
        c->features = hashmap_free(c->features);
2,422✔
103

104
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
7,105✔
105
                update_set_free(*us);
4,683✔
106
        c->update_sets = mfree(c->update_sets);
2,422✔
107
        c->n_update_sets = 0;
2,422✔
108

109
        c->web_cache = hashmap_free(c->web_cache);
2,422✔
110

111
        c->installdb_fd = safe_close(c->installdb_fd);
2,422✔
112

113
        c->definitions = mfree(c->definitions);
2,422✔
114
        c->root = mfree(c->root);
2,422✔
115
        c->image = mfree(c->image);
2,422✔
116
        c->component = mfree(c->component);
2,422✔
117
        c->image_policy = image_policy_free(c->image_policy);
2,422✔
118
        c->transfer_source = mfree(c->transfer_source);
2,422✔
119

120
        target_identifier_done(&c->target_identifier);
2,422✔
121
}
2,422✔
122

123
static int context_from_cmdline(Context *ret) {
1,111✔
124
        assert(ret);
1,111✔
125

126
        _cleanup_(context_done) Context context = CONTEXT_NULL;
1,111✔
127

128
        context.instances_max = arg_instances_max;
1,111✔
129
        context.sync = arg_sync;
1,111✔
130
        context.reboot = arg_reboot;
1,111✔
131
        context.verify = arg_verify;
1,111✔
132
        context.offline = arg_offline;
1,111✔
133
        context.cleanup = arg_cleanup;
1,111✔
134
        context.component_all = arg_component_all;
1,111✔
135

136
        if (strdup_to(&context.definitions, arg_definitions) < 0)
1,111✔
137
                return log_oom();
×
138

139
        if (strdup_to(&context.root, arg_root) < 0)
1,111✔
140
                return log_oom();
×
141

142
        if (strdup_to(&context.image, arg_image) < 0)
1,111✔
143
                return log_oom();
×
144

145
        if (strdup_to(&context.component, arg_component) < 0)
1,111✔
146
                return log_oom();
×
147

148
        if (strdup_to(&context.transfer_source, arg_transfer_source) < 0)
1,111✔
149
                return log_oom();
×
150

151
        if (arg_image_policy) {
1,111✔
152
                context.image_policy = image_policy_copy(arg_image_policy);
×
153
                if (!context.image_policy)
×
154
                        return log_oom();
×
155
        }
156

157
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
1,111✔
158
        return 0;
1,111✔
159
}
160

161
/* Stores any long-running server state which needs to persist between varlink calls, such as state for
162
 * pending polkit requests */
163
typedef struct Server {
164
        sd_bus *system_bus;
165
        Hashmap *polkit_registry;
166
} Server;
167

168
#define SERVER_NULL \
169
        (Server) { \
170
                /* all fields fine with being initialised to NULL */ \
171
        }
172

173
static void server_done(Server *s) {
212✔
174
        assert(s);
212✔
175

176
        s->polkit_registry = hashmap_free(s->polkit_registry);
212✔
177
        s->system_bus = sd_bus_flush_close_unref(s->system_bus);
212✔
178
}
212✔
179

180
static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free);
×
181

182
static int read_features(
1,291✔
183
                Context *c,
184
                const char **dirs) {
185

186
        int r;
1,291✔
187

188
        assert(c);
1,291✔
189

190
        ConfFile **files = NULL;
1,291✔
191
        size_t n_files = 0;
1,291✔
192
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,291✔
193

194
        r = conf_files_list_strv_full(
2,582✔
195
                        ".feature",
196
                        c->root,
1,291✔
197
                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
198
                        dirs,
199
                        &files,
200
                        &n_files);
201
        if (r < 0)
1,291✔
202
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m");
×
203

204
        FOREACH_ARRAY(i, files, n_files) {
2,523✔
205
                ConfFile *e = *i;
1,232✔
206

207
                _cleanup_(feature_unrefp) Feature *f = feature_new();
1,232✔
208
                if (!f)
1,232✔
209
                        return log_oom();
×
210

211
                r = feature_read_definition(f, c->root, e->result, dirs);
1,232✔
212
                if (r < 0)
1,232✔
213
                        return r;
214

215
                r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f);
1,232✔
216
                if (r < 0)
1,232✔
217
                        return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id);
×
218

219
                TAKE_PTR(f);
1,232✔
220
        }
221

222
        return 0;
223
}
224

225
static int read_transfers(
1,298✔
226
                Context *c,
227
                const char **dirs,
228
                const char *suffix,
229
                const char *node) {
230

231
        ConfFile **files = NULL;
1,298✔
232
        Transfer **transfers = NULL, **disabled = NULL;
1,298✔
233
        size_t n_files = 0, n_transfers = 0, n_disabled = 0;
1,298✔
234
        int r;
1,298✔
235

236
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,298✔
237
        CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array);
1,298✔
238
        CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array);
1,298✔
239

240
        assert(c);
1,298✔
241
        assert(dirs);
1,298✔
242
        assert(suffix);
1,298✔
243

244
        r = conf_files_list_strv_full(suffix, c->root,
1,298✔
245
                                      CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
246
                                      dirs, &files, &n_files);
247
        if (r < 0)
1,298✔
248
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*%s definitions: %m", suffix);
×
249

250
        FOREACH_ARRAY(i, files, n_files) {
11,210✔
251
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
252
                Transfer **appended;
9,912✔
253
                ConfFile *e = *i;
9,912✔
254

255
                t = transfer_new(c);
9,912✔
256
                if (!t)
9,912✔
257
                        return log_oom();
×
258

259
                r = transfer_read_definition(t, e->result, dirs, c->features);
9,912✔
260
                if (r < 0)
9,912✔
261
                        return r;
262

263
                r = transfer_resolve_paths(t, c->root, node);
9,912✔
264
                if (r < 0)
9,912✔
265
                        return r;
266

267
                if (t->enabled)
9,912✔
268
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
8,780✔
269
                else
270
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
1,132✔
271
                if (!appended)
9,912✔
272
                        return log_oom();
×
273
                TAKE_PTR(t);
9,912✔
274
        }
275

276
        c->transfers = TAKE_PTR(transfers);
1,298✔
277
        c->n_transfers = n_transfers;
1,298✔
278
        c->disabled_transfers = TAKE_PTR(disabled);
1,298✔
279
        c->n_disabled_transfers = n_disabled;
1,298✔
280
        return 0;
1,298✔
281
}
282

283
typedef enum ReadDefinitionsFlags {
284
        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0,
285
        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS     = 1 << 1,
286
} ReadDefinitionsFlags;
287

288
static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
1,291✔
289
        _cleanup_strv_free_ char **dirs = NULL;
1,291✔
290
        int r;
1,291✔
291

292
        assert(c);
1,291✔
293

294
        if (c->definitions)
1,291✔
295
                dirs = strv_new(c->definitions);
5✔
296
        else if (c->component) {
1,286✔
297
                char **l = CONF_PATHS_STRV("");
38✔
298
                size_t i = 0;
38✔
299

300
                dirs = new0(char*, strv_length(l) + 1);
38✔
301
                if (!dirs)
38✔
302
                        return log_oom();
×
303

304
                STRV_FOREACH(dir, l) {
190✔
305
                        char *j;
152✔
306

307
                        j = strjoin(*dir, "sysupdate.", c->component, ".d");
152✔
308
                        if (!j)
152✔
309
                                return log_oom();
×
310

311
                        dirs[i++] = j;
152✔
312
                }
313
        } else
314
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
1,248✔
315
        if (!dirs)
1,291✔
316
                return log_oom();
×
317

318
        r = read_features(c, (const char**) dirs);
1,291✔
319
        if (r < 0)
1,291✔
320
                return r;
321

322
        r = read_transfers(c, (const char**) dirs, ".transfer", node);
1,291✔
323
        if (r < 0)
1,291✔
324
                return r;
325

326
        if (c->n_transfers + c->n_disabled_transfers == 0) {
1,291✔
327
                /* Backwards-compat: If no .transfer defs are found, fall back to trying .conf! */
328
                r = read_transfers(c, (const char**) dirs, ".conf", node);
7✔
329
                if (r < 0)
7✔
330
                        return r;
331

332
                if (c->n_transfers + c->n_disabled_transfers > 0)
7✔
333
                        log_warning("As of v257, transfer definitions should have the '.transfer' extension.");
×
334
        }
335

336
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS) &&
1,291✔
337
            c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) {
1,107✔
338
                if (c->component)
×
339
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
340
                                               "No transfer definitions for component '%s' found.",
341
                                               c->component);
342

343
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
344
                                       "No transfer definitions found.");
345
        }
346

347
        return 0;
348
}
349

350
static int context_load_installed_instances(Context *c) {
1,291✔
351
        int r;
1,291✔
352

353
        assert(c);
1,291✔
354

355
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
2,582✔
356

357
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
10,071✔
358
                Transfer *t = *tr;
8,780✔
359

360
                r = resource_load_instances(
8,780✔
361
                                &t->target,
362
                                c->verify >= 0 ? c->verify : t->verify,
8,780✔
363
                                &c->web_cache);
364
                if (r < 0)
8,780✔
365
                        return r;
366
        }
367

368
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
2,423✔
369
                Transfer *t = *tr;
1,132✔
370

371
                r = resource_load_instances(
1,132✔
372
                                &t->target,
373
                                c->verify >= 0 ? c->verify : t->verify,
1,132✔
374
                                &c->web_cache);
375
                if (r < 0)
1,132✔
376
                        return r;
377
        }
378

379
        return 0;
380
}
381

382
static int context_load_available_instances(Context *c) {
831✔
383
        int r;
831✔
384

385
        assert(c);
831✔
386

387
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
1,662✔
388

389
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
6,419✔
390
                Transfer *t = *tr;
5,606✔
391

392
                r = resource_load_instances(
5,606✔
393
                                &t->source,
394
                                c->verify >= 0 ? c->verify : t->verify,
5,606✔
395
                                &c->web_cache);
396
                if (r < 0)
5,606✔
397
                        return r;
398
        }
399

400
        return 0;
401
}
402

403
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
1,838✔
404
        _cleanup_free_ char *boundary = NULL;
1,838✔
405
        bool newest_found = false;
1,838✔
406
        int r;
1,838✔
407

408
        assert(c);
1,838✔
409
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
1,838✔
410

411
        for (;;) {
9,817✔
412
                _cleanup_free_ Instance **cursor_instances = NULL;
×
413
                bool skip = false;
9,817✔
414
                UpdateSetFlags extra_flags = 0;
9,817✔
415
                _cleanup_free_ char *cursor = NULL;
7,979✔
416
                UpdateSet *us = NULL;
9,817✔
417

418
                /* First, let's find the newest version that's older than the boundary. */
419
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
72,229✔
420
                        Resource *rr;
63,225✔
421

422
                        assert(*tr);
63,225✔
423

424
                        if (flags == UPDATE_AVAILABLE)
63,225✔
425
                                rr = &(*tr)->source;
32,212✔
426
                        else {
427
                                assert(flags == UPDATE_INSTALLED);
31,013✔
428
                                rr = &(*tr)->target;
31,013✔
429
                        }
430

431
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
198,593✔
432
                                Instance *i = *inst; /* Sorted newest-to-oldest */
183,384✔
433

434
                                assert(i);
183,384✔
435

436
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
183,384✔
437
                                        continue; /* Not older than the boundary */
135,368✔
438

439
                                if (cursor && strverscmp(i->metadata.version, cursor) <= 0)
48,016✔
440
                                        break; /* Not newer than the cursor. The same will be true for all
441
                                                * subsequent instances (due to sorting) so let's skip to the
442
                                                * next transfer. */
443

444
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
7,979✔
445
                                        return log_oom();
×
446

447
                                break; /* All subsequent instances will be older than this one */
448
                        }
449

450
                        if (flags == UPDATE_AVAILABLE && !cursor)
63,225✔
451
                                break; /* This transfer didn't have a version older than the boundary,
452
                                        * so any older-than-boundary version that might exist in a different
453
                                        * transfer must always be incomplete. For reasons described below,
454
                                        * we don't include incomplete versions for AVAILABLE updates. So we
455
                                        * are completely done looking. */
456
                }
457

458
                if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
9,817✔
459
                        break;
460

461
                cursor_instances = new0(Instance*, c->n_transfers);
7,979✔
462
                if (!cursor_instances)
7,979✔
463
                        return log_oom();
×
464

465
                /* Now let's find all the instances that match the version of the cursor, if we have them */
466
                for (size_t k = 0; k < c->n_transfers; k++) {
61,455✔
467
                        Transfer *t = c->transfers[k];
54,104✔
468
                        Instance *match = NULL;
54,104✔
469

470
                        assert(t);
54,104✔
471

472
                        if (flags == UPDATE_AVAILABLE) {
54,104✔
473
                                match = resource_find_instance(&t->source, cursor);
30,091✔
474
                                if (!match) {
30,091✔
475
                                        /* When we're looking for updates to download, we don't offer
476
                                         * incomplete versions at all. The server wants to send us an update
477
                                         * with parts of the OS missing. For robustness sake, let's not do
478
                                         * that. */
479
                                        skip = true;
480
                                        break;
481
                                }
482
                        } else {
483
                                assert(flags == UPDATE_INSTALLED);
24,013✔
484

485
                                match = resource_find_instance(&t->target, cursor);
24,013✔
486
                                if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING)))
24,013✔
487
                                        /* When we're looking for installed versions, let's be robust and treat
488
                                         * an incomplete installation as an installation. Otherwise, there are
489
                                         * situations that can lead to sysupdate wiping the currently booted OS.
490
                                         * See https://github.com/systemd/systemd/issues/33339 */
491
                                        extra_flags |= UPDATE_INCOMPLETE;
7,444✔
492
                        }
493

494
                        cursor_instances[k] = match;
53,476✔
495

496
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
53,476✔
497
                                extra_flags |= UPDATE_OBSOLETE;
×
498

499
                        if (strv_contains(t->protected_versions, cursor))
53,476✔
500
                                extra_flags |= UPDATE_PROTECTED;
×
501

502
                        /* Partial or pending updates by definition are not incomplete, they’re
503
                         * partial/pending instead. While an individual Instance cannot be both partial and
504
                         * pending, an UpdateSet as a whole can contain both partial and pending instances. */
505
                        assert(!match || !(match->is_partial && match->is_pending));
53,476✔
506

507
                        if (match && match->is_partial)
45,852✔
508
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
36✔
509

510
                        if (match && match->is_pending)
45,852✔
511
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
748✔
512
                }
513

514
                r = free_and_strdup_warn(&boundary, cursor);
7,979✔
515
                if (r < 0)
7,979✔
516
                        return r;
517

518
                if (skip)
7,979✔
519
                        continue;
628✔
520

521
                /* See if we already have this update set in our table */
522
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
21,146✔
523
                        UpdateSet *u = *update_set;
16,463✔
524

525
                        if (strverscmp_improved(u->version, cursor) != 0)
16,463✔
526
                                continue;
13,795✔
527

528
                        /* Merge in what we've learned and continue onto the next version */
529

530
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
2,668✔
531
                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
1,324✔
532
                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
1,308✔
533
                                assert(u->n_instances == c->n_transfers);
1,392✔
534

535
                                /* Incomplete updates will have picked NULL instances for the transfers that
536
                                 * are missing. Now we have more information, so let's try to fill them in. */
537

538
                                for (size_t j = 0; j < u->n_instances; j++) {
11,328✔
539
                                        if (!u->instances[j])
9,936✔
540
                                                u->instances[j] = cursor_instances[j];
5,792✔
541

542
                                        /* Make sure that the list is full if the update is AVAILABLE */
543
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
9,936✔
544
                                }
545
                        }
546

547
                        u->flags |= flags | extra_flags;
2,668✔
548

549
                        /* If this is the newest installed version, that is incomplete and just became marked
550
                         * as available, and if there is no other candidate available, we promote this to be
551
                         * the candidate. Ignore partial or pending status on the update set. */
552
                        if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
2,668✔
553
                            !c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
64✔
554
                                c->candidate = u;
64✔
555

556
                        skip = true;
557
                        newest_found = true;
558
                        break;
559
                }
560

561
                if (skip)
64✔
562
                        continue;
2,668✔
563

564
                /* Doesn't exist yet, let's add it */
565
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
4,683✔
566
                        return log_oom();
×
567

568
                us = new(UpdateSet, 1);
4,683✔
569
                if (!us)
4,683✔
570
                        return log_oom();
×
571

572
                *us = (UpdateSet) {
4,683✔
573
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
4,683✔
574
                        .version = TAKE_PTR(cursor),
4,683✔
575
                        .instances = TAKE_PTR(cursor_instances),
4,683✔
576
                        .n_instances = c->n_transfers,
4,683✔
577
                };
578

579
                c->update_sets[c->n_update_sets++] = us;
4,683✔
580

581
                newest_found = true;
4,683✔
582

583
                /* Remember which one is the newest installed */
584
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
4,683✔
585
                        c->newest_installed = us;
984✔
586

587
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
588
                 * It may be partial or pending. */
589
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
4,683✔
590
                        c->candidate = us;
315✔
591
        }
592

593
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
594
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
1,838✔
595
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
1,596✔
596
                c->candidate = NULL;
48✔
597

598
        /* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */
599
        if (c->newest_installed &&
1,838✔
600
            (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) &&
1,756✔
601
            !c->candidate)
196✔
602
                c->candidate = c->newest_installed;
196✔
603

604
        return 0;
605
}
606

607
static int context_discover_update_sets(Context *c) {
1,025✔
608
        int r;
1,025✔
609

610
        assert(c);
1,025✔
611

612
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
2,050✔
613

614
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
1,025✔
615
        if (r < 0)
1,025✔
616
                return r;
617

618
        if (!c->offline) {
1,025✔
619
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
1,626✔
620

621
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
813✔
622
                if (r < 0)
813✔
623
                        return r;
624
        }
625

626
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
1,025✔
627
        return 0;
1,025✔
628
}
629

630
static int context_show_table(Context *c) {
×
631
        _cleanup_(table_unrefp) Table *t = NULL;
×
632
        int r;
×
633

634
        assert(c);
×
635

636
        t = table_new("", "version", "installed", "available", "assessment");
×
637
        if (!t)
×
638
                return log_oom();
×
639

640
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
641
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
642
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
643

644
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
645
                UpdateSet *us = *update_set;
×
646
                const char *color;
×
647

648
                color = update_set_flags_to_color(us->flags);
×
649

650
                r = table_add_many(t,
×
651
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
652
                                   TABLE_SET_COLOR, color,
653
                                   TABLE_STRING,    us->version,
654
                                   TABLE_SET_COLOR, color,
655
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
656
                                   TABLE_SET_COLOR, color,
657
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
658
                                   TABLE_SET_COLOR, color,
659
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
660
                                   TABLE_SET_COLOR, color);
661
                if (r < 0)
×
662
                        return table_log_add_error(r);
×
663
        }
664

665
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
666
}
667

668
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
208✔
669
        assert(c);
208✔
670
        assert(version);
208✔
671

672
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
368✔
673
                if (streq((*update_set)->version, version))
368✔
674
                        return *update_set;
675

676
        return NULL;
677
}
678

679
static int context_show_version(Context *c, const char *version) {
208✔
680
        bool show_fs_columns = false, show_partition_columns = false,
208✔
681
                have_fs_attributes = false, have_partition_attributes = false,
208✔
682
                have_size = false, have_tries = false, have_no_auto = false,
208✔
683
                have_read_only = false, have_growfs = false, have_sha256 = false;
208✔
684
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
208✔
685
        _cleanup_(table_unrefp) Table *t = NULL;
208✔
686
        _cleanup_strv_free_ char **changelog_urls = NULL;
208✔
687
        UpdateSet *us;
208✔
688
        int r;
208✔
689

690
        assert(c);
208✔
691
        assert(version);
208✔
692

693
        us = context_update_set_by_version(c, version);
208✔
694
        if (!us)
208✔
695
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
696

697
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
208✔
698
                pager_open(arg_pager_flags);
112✔
699

700
        if (!sd_json_format_enabled(arg_json_format_flags))
208✔
701
                printf("%s%s%s Version: %s\n"
896✔
702
                       "    State: %s%s%s\n"
703
                       "Installed: %s%s%s%s\n"
704
                       "Available: %s%s\n"
705
                       "Protected: %s%s%s\n"
706
                       " Obsolete: %s%s%s\n\n",
707
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
112✔
708
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
112✔
709
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
128✔
710
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
208✔
711
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
208✔
712
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
112✔
713
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
112✔
714

715
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
208✔
716
        if (!t)
208✔
717
                return log_oom();
×
718

719
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
208✔
720
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
208✔
721
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
208✔
722
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
208✔
723
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
208✔
724
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
208✔
725
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
208✔
726

727
        /* Starting in v257, these fields would be automatically formatted with underscores. This would have
728
         * been a breaking change, so to avoid that let's hard-code their original names. */
729
        (void) table_set_json_field_name(t, 7, "tries-done");
208✔
730
        (void) table_set_json_field_name(t, 8, "tries-left");
208✔
731

732
        /* Determine if the target will make use of partition/fs attributes for any of the transfers */
733
        FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
1,696✔
734
                Transfer *tr = *transfer;
1,488✔
735

736
                if (tr->target.type == RESOURCE_PARTITION)
1,488✔
737
                        show_partition_columns = true;
416✔
738
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
1,488✔
739
                        show_fs_columns = true;
1,072✔
740

741
                STRV_FOREACH(changelog, tr->changelog) {
1,488✔
742
                        assert(*changelog);
×
743

744
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
745
                        if (!changelog_url)
×
746
                                return log_oom();
×
747

748
                        /* Avoid duplicates */
749
                        if (strv_contains(changelog_urls, changelog_url))
×
750
                                continue;
×
751

752
                        /* changelog_urls takes ownership of expanded changelog_url */
753
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
754
                        if (r < 0)
×
755
                                return log_oom();
×
756
                }
757
        }
758

759
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
1,696✔
760
                Instance *i = *inst;
1,488✔
761

762
                if (!i) {
1,488✔
763
                        assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING));
112✔
764
                        continue;
112✔
765
                }
766

767
                r = table_add_many(t,
1,376✔
768
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
769
                                   TABLE_PATH, i->path);
770
                if (r < 0)
1,376✔
771
                        return table_log_add_error(r);
×
772

773
                if (i->metadata.partition_uuid_set) {
1,376✔
774
                        have_partition_attributes = true;
288✔
775
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
288✔
776
                } else
777
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
778
                if (r < 0)
1,376✔
779
                        return table_log_add_error(r);
×
780

781
                if (i->metadata.partition_flags_set) {
1,376✔
782
                        have_partition_attributes = true;
288✔
783
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
288✔
784
                } else
785
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
786
                if (r < 0)
1,376✔
787
                        return table_log_add_error(r);
×
788

789
                if (i->metadata.mtime != USEC_INFINITY) {
1,376✔
790
                        have_fs_attributes = true;
1,056✔
791
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
1,056✔
792
                } else
793
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
320✔
794
                if (r < 0)
1,376✔
795
                        return table_log_add_error(r);
×
796

797
                if (i->metadata.mode != MODE_INVALID) {
1,376✔
798
                        have_fs_attributes = true;
1,056✔
799
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
1,056✔
800
                } else
801
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
320✔
802
                if (r < 0)
1,376✔
803
                        return table_log_add_error(r);
×
804

805
                if (i->metadata.size != UINT64_MAX) {
1,376✔
806
                        have_size = true;
×
807
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
808
                } else
809
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
810
                if (r < 0)
1,376✔
811
                        return table_log_add_error(r);
×
812

813
                if (i->metadata.tries_done != UINT64_MAX) {
1,376✔
814
                        have_tries = true;
128✔
815
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
128✔
816
                } else
817
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,248✔
818
                if (r < 0)
1,376✔
819
                        return table_log_add_error(r);
×
820

821
                if (i->metadata.tries_left != UINT64_MAX) {
1,376✔
822
                        have_tries = true;
128✔
823
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
128✔
824
                } else
825
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,248✔
826
                if (r < 0)
1,376✔
827
                        return table_log_add_error(r);
×
828

829
                if (i->metadata.no_auto >= 0) {
1,376✔
830
                        bool b;
×
831

832
                        have_no_auto = true;
×
833
                        b = i->metadata.no_auto;
×
834
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
835
                } else
836
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
837
                if (r < 0)
1,376✔
838
                        return table_log_add_error(r);
×
839
                if (i->metadata.read_only >= 0) {
1,376✔
840
                        bool b;
288✔
841

842
                        have_read_only = true;
288✔
843
                        b = i->metadata.read_only;
288✔
844
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
288✔
845
                } else
846
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
847
                if (r < 0)
1,376✔
848
                        return table_log_add_error(r);
×
849

850
                if (i->metadata.growfs >= 0) {
1,376✔
851
                        bool b;
×
852

853
                        have_growfs = true;
×
854
                        b = i->metadata.growfs;
×
855
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
856
                } else
857
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
858
                if (r < 0)
1,376✔
859
                        return table_log_add_error(r);
×
860

861
                if (i->metadata.sha256sum_set) {
1,376✔
862
                        _cleanup_free_ char *formatted = NULL;
32✔
863

864
                        have_sha256 = true;
32✔
865

866
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
32✔
867
                        if (!formatted)
32✔
868
                                return log_oom();
×
869

870
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
32✔
871
                } else
872
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,344✔
873
                if (r < 0)
1,376✔
874
                        return table_log_add_error(r);
×
875
        }
876

877
        /* Hide the fs/partition columns if we don't have any data to show there */
878
        if (!have_fs_attributes)
208✔
879
                show_fs_columns = false;
16✔
880
        if (!have_partition_attributes)
208✔
881
                show_partition_columns = false;
882

883
        if (!show_partition_columns)
144✔
884
                (void) table_hide_column_from_display(t, 2, 3);
64✔
885
        if (!show_fs_columns)
208✔
886
                (void) table_hide_column_from_display(t, 4, 5);
16✔
887
        if (!have_size)
208✔
888
                (void) table_hide_column_from_display(t, 6);
208✔
889
        if (!have_tries)
208✔
890
                (void) table_hide_column_from_display(t, 7, 8);
80✔
891
        if (!have_no_auto)
208✔
892
                (void) table_hide_column_from_display(t, 9);
208✔
893
        if (!have_read_only)
208✔
894
                (void) table_hide_column_from_display(t, 10);
64✔
895
        if (!have_growfs)
208✔
896
                (void) table_hide_column_from_display(t, 11);
208✔
897
        if (!have_sha256)
208✔
898
                (void) table_hide_column_from_display(t, 12);
192✔
899

900
        if (!sd_json_format_enabled(arg_json_format_flags)) {
208✔
901
                printf("%s%s%s Version: %s\n"
1,008✔
902
                       "    State: %s%s%s\n"
903
                       "Installed: %s%s%s%s%s%s%s\n"
904
                       "Available: %s%s\n"
905
                       "Protected: %s%s%s\n"
906
                       " Obsolete: %s%s%s\n",
907
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
112✔
908
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
112✔
909
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
128✔
910
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
144✔
911
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
208✔
912
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
208✔
913
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
112✔
914
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
112✔
915

916
                STRV_FOREACH(url, changelog_urls) {
112✔
917
                        _cleanup_free_ char *changelog_link = NULL;
×
918
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
919
                        if (r < 0)
×
920
                                return log_oom();
×
921
                        printf("ChangeLog: %s\n", changelog_link);
×
922
                }
923
                printf("\n");
112✔
924

925
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
112✔
926
        } else {
927
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
96✔
928

929
                r = table_to_json(t, &t_json);
96✔
930
                if (r < 0)
96✔
931
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
932

933
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
96✔
934
                                          SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
935
                                          SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
936
                                          SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
937
                                          SD_JSON_BUILD_PAIR_BOOLEAN("partial", FLAGS_SET(us->flags, UPDATE_PARTIAL)),
938
                                          SD_JSON_BUILD_PAIR_BOOLEAN("pending", FLAGS_SET(us->flags, UPDATE_PENDING)),
939
                                          SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
940
                                          SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
941
                                          SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
942
                                          SD_JSON_BUILD_PAIR_STRV("changelogUrls", changelog_urls),
943
                                          SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
944
                if (r < 0)
96✔
945
                        return log_error_errno(r, "Failed to create JSON: %m");
×
946

947
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
96✔
948
                if (r < 0)
96✔
949
                        return log_error_errno(r, "Failed to print JSON: %m");
×
950

951
                return 0;
952
        }
953
}
954

955
static int context_vacuum(
218✔
956
                Context *c,
957
                uint64_t space,
958
                const char *extra_protected_version) {
959

960
        size_t disabled_count = 0;
218✔
961
        int r, count = 0;
218✔
962

963
        assert(c);
218✔
964

965
        if (space == 0)
218✔
966
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
64✔
967
        else
968
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
372✔
969

970
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
1,703✔
971
                Transfer *t = *tr;
1,485✔
972

973
                /* Don't bother clearing out space if we're not going to be downloading anything */
974
                if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
1,485✔
975
                        continue;
208✔
976

977
                r = transfer_vacuum(t, space, extra_protected_version);
1,277✔
978
                if (r < 0)
1,277✔
979
                        return r;
980

981
                count = MAX(count, r);
1,277✔
982
        }
983

984
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
410✔
985
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
192✔
986
                if (r < 0)
192✔
987
                        return r;
988
                if (r > 0)
192✔
989
                        disabled_count++;
16✔
990
        }
991

992
        if (!sd_json_format_enabled(arg_json_format_flags)) {
218✔
993
                if (count > 0 && disabled_count > 0)
166✔
994
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
995
                else if (count > 0)
166✔
996
                        log_info("Removed %i instances.", count);
76✔
997
                else if (disabled_count > 0)
90✔
998
                        log_info("Removed %zu disabled transfers.", disabled_count);
16✔
999
                else
1000
                        log_info("Found nothing to remove.");
74✔
1001
        } else {
1002
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
52✔
1003

1004
                r = sd_json_buildo(&json,
52✔
1005
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
1006
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
1007
                if (r < 0)
52✔
1008
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1009

1010
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
52✔
1011
                if (r < 0)
52✔
1012
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1013
        }
1014

1015
        return 0;
1016
}
1017

1018
typedef enum ProcessImageFlags {
1019
        PROCESS_IMAGE_READ_ONLY = 1 << 0,
1020
} ProcessImageFlags;
1021

1022
static int context_process_image(
1,291✔
1023
                Context *c,
1024
                ProcessImageFlags flags) {
1025

1026
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1,291✔
1027
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1,291✔
1028
        int r;
1,291✔
1029

1030
        assert(c);
1,291✔
1031

1032
        if (!c->image)
1,291✔
1033
                return 0;
1034

1035
        assert(!c->root);
×
1036
        assert(!c->mounted_dir);
×
1037
        assert(!c->loop_device);
×
1038

1039
        r = mount_image_privately_interactively(
×
1040
                        c->image,
1041
                        c->image_policy,
×
1042
                        (FLAGS_SET(flags, PROCESS_IMAGE_READ_ONLY) ? DISSECT_IMAGE_READ_ONLY : 0) |
×
1043
                        DISSECT_IMAGE_FSCK |
1044
                        DISSECT_IMAGE_MKDIR |
1045
                        DISSECT_IMAGE_GROWFS |
1046
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1047
                        DISSECT_IMAGE_USR_NO_ROOT |
1048
                        DISSECT_IMAGE_GENERIC_ROOT |
1049
                        DISSECT_IMAGE_REQUIRE_ROOT |
1050
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1051
                        &mounted_dir,
1052
                        /* ret_dir_fd= */ NULL,
1053
                        &loop_device);
1054
        if (r < 0)
×
1055
                return r;
1056

1057
        c->root = strdup(mounted_dir);
×
1058
        if (!c->root)
×
1059
                return log_oom();
×
1060

1061
        c->mounted_dir = TAKE_PTR(mounted_dir);
×
1062
        c->loop_device = TAKE_PTR(loop_device);
×
1063

1064
        return 0;
×
1065
}
1066

1067
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component);
1068

1069
static int context_load_offline(
1,291✔
1070
                Context *context,
1071
                ProcessImageFlags process_image_flags,
1072
                ReadDefinitionsFlags read_definitions_flags) {
1073
        int r;
1,291✔
1074

1075
        assert(context);
1,291✔
1076

1077
        /* Sets up a context object and initializes everything we can initialize offline, i.e. without
1078
         * checking on the update source (i.e. the Internet) what versions are available */
1079

1080
        r = context_process_image(context, process_image_flags);
1,291✔
1081
        if (r < 0)
1,291✔
1082
                return r;
1083

1084
        r = context_read_definitions(context, context->loop_device ? context->loop_device->node : NULL, read_definitions_flags);
1,291✔
1085
        if (r < 0)
1,291✔
1086
                return r;
1087

1088
        r = context_load_installed_instances(context);
1,291✔
1089
        if (r < 0)
1,291✔
1090
                return r;
×
1091

1092
        return 0;
1093
}
1094

1095
static int context_load_online(
1,043✔
1096
                Context *context,
1097
                ProcessImageFlags process_image_flags) {
1098
        int r;
1,043✔
1099

1100
        assert(context);
1,043✔
1101

1102
        /* Like context_load_offline(), but also communicates with the update source looking for new
1103
         * versions (as long as --offline is not specified on the command line). */
1104

1105
        r = context_load_offline(
1,043✔
1106
                        context,
1107
                        process_image_flags,
1108
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1109
        if (r < 0)
1,043✔
1110
                return r;
1111

1112
        if (!context->offline) {
1,043✔
1113
                r = context_load_available_instances(context);
831✔
1114
                if (r < 0)
831✔
1115
                        return r;
1116
        }
1117

1118
        r = context_discover_update_sets(context);
1,025✔
1119
        if (r < 0)
1,025✔
1120
                return r;
×
1121

1122
        return 0;
1123
}
1124

1125
static bool image_type_can_sysupdate(ImageType image_type) {
×
1126
        /* systemd-sysupdate doesn't support mstack images yet */
1127
        return IN_SET(image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME, IMAGE_RAW, IMAGE_BLOCK);
×
1128
}
1129

1130
static int context_load_paths_from_image(Context *context, Image *image) {
×
1131
        assert(context);
×
1132
        assert(image);
×
1133

1134
        assert(!context->root);
×
1135
        assert(!context->image);
×
1136

1137
        switch (image->type) {
×
1138
        case IMAGE_DIRECTORY:
×
1139
        case IMAGE_SUBVOLUME:
1140
                context->root = strdup(image->path);
×
1141
                if (!context->root)
×
1142
                        return log_oom();
×
1143
                return 0;
1144
        case IMAGE_RAW:
×
1145
        case IMAGE_BLOCK:
1146
                context->image = strdup(image->path);
×
1147
                if (!context->image)
×
1148
                        return log_oom();
×
1149
                return 0;
1150
        default:
×
1151
                assert_not_reached();
×
1152
        }
1153
}
1154

1155
/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been
1156
 * syntactically validated by dispatch_target_identifier(), but might still point to components which don’t
1157
 * exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier
1158
 * against an enumerated list of known targets, which are safe to update without additional permissions. */
1159
static int context_load_online_from_target(Context *context, ProcessImageFlags process_image_flags) {
192✔
1160
        int r;
192✔
1161

1162
        assert(context);
192✔
1163
        assert(context->target_identifier.class != _TARGET_CLASS_INVALID);
192✔
1164

1165
        /* These shouldn’t have been set up some other way first */
1166
        assert(!context->component);
192✔
1167
        assert(!context->root);
192✔
1168
        assert(!context->image);
192✔
1169

1170
        switch (context->target_identifier.class) {
192✔
1171
        case TARGET_MACHINE:
×
1172
        case TARGET_PORTABLE:
1173
        case TARGET_SYSEXT:
1174
        case TARGET_CONFEXT: {
1175
                _cleanup_hashmap_free_ Hashmap *images = NULL;
×
1176
                Image *image, *selected_image = NULL;
×
1177

1178
                /* These are all image-based target classes, so first find the corresponding image. */
1179
                r = image_discover(RUNTIME_SCOPE_SYSTEM, (ImageClass) context->target_identifier.class, NULL, &images);
×
1180
                if (r < 0)
×
1181
                        return r;
1182

1183
                HASHMAP_FOREACH(image, images) {
×
1184
                        bool have = false;
×
1185
                        _cleanup_(context_done) Context image_context = CONTEXT_NULL;
×
1186

1187
                        if (image_is_host(image))
×
1188
                                continue; /* We already enroll the host ourselves */
×
1189

1190
                        if (!image_type_can_sysupdate(image->type))
×
1191
                                continue;
×
1192

1193
                        if (!streq(image->name, context->target_identifier.name))
×
1194
                                continue;
×
1195

1196
                        r = context_load_paths_from_image(&image_context, image);
×
1197
                        if (r < 0)
×
1198
                                return r;
1199

1200
                        /* Load the components in a separate Context specific to the given Image before
1201
                         * committing to loading that state to the main Context. */
1202
                        r = context_load_offline(&image_context, 0, 0);
×
1203
                        if (r < 0)
×
1204
                                return r;
1205

1206
                        r = context_list_components(&image_context, /* ret_component_names= */ NULL, &have);
×
1207
                        if (r < 0)
×
1208
                                return r;
1209
                        if (!have) {
×
1210
                                log_debug("Skipping %s because it has no default component", image->path);
×
1211
                                continue;
×
1212
                        }
1213

1214
                        /* This is the match we were looking for */
1215
                        selected_image = image;
×
1216
                        break;
×
1217
                }
1218

1219
                if (!selected_image)
×
1220
                        return -ENOENT;
1221

1222
                r = context_load_paths_from_image(context, selected_image);
×
1223
                if (r < 0)
×
1224
                        return r;
1225

1226
                break;
×
1227
        }
1228
        case TARGET_HOST:
1229
                /* No additional setup needed */
1230
                break;
1231
        case TARGET_COMPONENT: {
8✔
1232
                _cleanup_strv_free_ char **component_names = NULL;
8✔
1233

1234
                r = context_list_components(context, &component_names, /* ret_has_default_component= */ NULL);
8✔
1235
                if (r < 0)
8✔
1236
                        return r;
1237

1238
                if (!strv_contains(component_names, context->target_identifier.name))
8✔
1239
                        return -ENOENT;
1240

1241
                context->component = strdup(context->target_identifier.name);
×
1242
                if (!context->component)
×
1243
                        return log_oom();
×
1244
                break;
×
1245
        }
1246
        default:
×
1247
                assert_not_reached();
×
1248
        }
1249

1250
        return context_load_online(context, process_image_flags);
184✔
1251
}
1252

1253
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
828✔
1254
        const Context *c = ASSERT_PTR(t->context);
828✔
1255
        size_t i, n = c->n_transfers;
828✔
1256
        uint64_t base, scaled;
828✔
1257
        unsigned overall;
828✔
1258

1259
        for (i = 0; i < n; i++)
2,903✔
1260
                if (c->transfers[i] == t)
2,903✔
1261
                        break;
1262
        assert(i < n); /* We should have found the index */
828✔
1263

1264
        base = (100 * 100 * i) / n;
828✔
1265
        scaled = (100 * percentage) / n;
828✔
1266
        overall = (unsigned) ((base + scaled) / 100);
828✔
1267
        assert(overall <= 100);
828✔
1268

1269
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
828✔
1270
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
1,656✔
1271
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
1272
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
1273
                                              "STATUS=Updating to '%s' (%u%% complete).",
1274
                                              overall, n - i, i, inst->metadata.version, overall);
828✔
1275
}
1276

1277
static int context_process_partial_and_pending(Context *c, const char *version);
1278

1279
static int context_acquire(
284✔
1280
                Context *c,
1281
                const char *version) {
1282

1283
        UpdateSet *us = NULL;
284✔
1284
        int r;
284✔
1285

1286
        assert(c);
284✔
1287

1288
        if (version) {
284✔
1289
                us = context_update_set_by_version(c, version);
×
1290
                if (!us)
×
1291
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
284✔
1292
        } else {
1293
                if (!c->candidate) {
284✔
1294
                        log_info("No update needed.");
50✔
1295

1296
                        return 0;
1297
                }
1298

1299
                us = c->candidate;
1300
        }
1301

1302
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
234✔
1303
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
32✔
1304
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
202✔
1305
                return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
16✔
1306
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
186✔
1307
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
32✔
1308

1309
                return context_process_partial_and_pending(c, version);
32✔
1310
        } else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
154✔
1311
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1312

1313
                return 0;
1314
        }
1315

1316
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
186✔
1317
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1318
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
186✔
1319
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1320

1321
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
186✔
1322
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1323
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
186✔
1324
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1325

1326
        log_info("Selected update '%s' for install.", us->version);
186✔
1327

1328
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
372✔
1329
        if (!metadata)
186✔
1330
                return log_oom();
×
1331

1332
        /* Compute up the temporary paths before vacuuming so we don't vacuum anything if we fail to compute
1333
         * any paths because of failed validations (e.g. exceeding the gpt partition label size). */
1334
        for (size_t i = 0; i < c->n_transfers; i++) {
1,447✔
1335
                Instance *inst = us->instances[i];
1,261✔
1336
                Transfer *t = c->transfers[i];
1,261✔
1337

1338
                assert(inst);
1,261✔
1339

1340
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
1,261✔
1341
                if (r < 0)
1,261✔
1342
                        return r;
1343
        }
1344

1345
        (void) sd_notifyf(/* unset_environment= */ false,
186✔
1346
                          "READY=1\n"
1347
                          "X_SYSUPDATE_VERSION=%s\n"
1348
                          "STATUS=Making room for '%s'.", us->version, us->version);
1349

1350
        /* Let's make some room. We make sure for each transfer we have one free space to fill. While
1351
         * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
1352
         * download succeeded already, in which case we shouldn't remove it just to acquire it again */
1353
        r = context_vacuum(
372✔
1354
                        c,
1355
                        /* space= */ 1,
1356
                        /* extra_protected_version= */ us->version);
186✔
1357
        if (r < 0)
186✔
1358
                return r;
1359

1360
        if (c->sync)
186✔
1361
                sync();
186✔
1362

1363
        (void) sd_notifyf(/* unset_environment= */ false,
186✔
1364
                          "STATUS=Updating to '%s'.", us->version);
1365

1366
        /* There should now be one instance picked for each transfer, and the order is the same */
1367
        assert(us->n_instances == c->n_transfers);
186✔
1368

1369
        for (size_t i = 0; i < c->n_transfers; i++) {
1,351✔
1370
                Instance *inst = us->instances[i];
1,181✔
1371
                Transfer *t = c->transfers[i];
1,181✔
1372

1373
                assert(inst); /* ditto */
1,181✔
1374

1375
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
1,181✔
1376
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
208✔
1377
                        continue;
208✔
1378
                }
1379

1380
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
973✔
1381
                if (r < 0)
973✔
1382
                        return r;
1383
        }
1384

1385
        if (c->sync)
170✔
1386
                sync();
170✔
1387

1388
        return 1;
1389
}
1390

1391
/* Check to see if we have an update set acquired and pending installation. */
1392
static int context_process_partial_and_pending(
116✔
1393
                Context *c,
1394
                const char *version) {
1395

1396
        UpdateSet *us = NULL;
116✔
1397
        int r;
116✔
1398

1399
        assert(c);
116✔
1400

1401
        if (version) {
116✔
1402
                us = context_update_set_by_version(c, version);
×
1403
                if (!us)
×
1404
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1405
        } else {
1406
                if (!c->candidate) {
116✔
1407
                        log_info("No update needed.");
×
1408

1409
                        return 0;
1410
                }
1411

1412
                us = c->candidate;
1413
        }
1414

1415
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
116✔
1416
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
×
1417
        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
116✔
1418
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1419

1420
                return 0;
1421
        }
1422

1423
        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
116✔
1424
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
4✔
1425
        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
112✔
1426
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
×
1427

1428
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
112✔
1429
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1430

1431
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
112✔
1432
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1433
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
112✔
1434
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1435

1436
        log_info("Selected update '%s' for install.", us->version);
112✔
1437

1438
        /* There should now be one instance picked for each transfer, and the order is the same */
1439
        assert(us->n_instances == c->n_transfers);
112✔
1440

1441
        for (size_t i = 0; i < c->n_transfers; i++) {
908✔
1442
                Instance *inst = us->instances[i];
796✔
1443
                Transfer *t = c->transfers[i];
796✔
1444

1445
                assert(inst);
796✔
1446

1447
                r = transfer_process_partial_and_pending_instance(t, inst);
796✔
1448
                if (r < 0)
796✔
1449
                        return r;
1450
        }
1451

1452
        return 1;
1453
}
1454

1455
static int notify_subscribers_reply(
171✔
1456
                sd_varlink *link,
1457
                sd_json_variant *reply,
1458
                const char *error_id,
1459
                sd_varlink_reply_flags_t flags,
1460
                void *userdata) {
1461

1462
        assert(link);
171✔
1463

1464
        if (error_id)
171✔
1465
                log_warning("Notification subscriber '%s' returned error, ignoring: %s",
×
1466
                            strna(sd_varlink_get_description(link)), error_id);
1467

1468
        return 0;
171✔
1469
}
1470

1471
static int context_notify_subscribers(Context *c, UpdateSet *us) {
170✔
1472
        int r;
170✔
1473

1474
        assert(c);
170✔
1475

1476
        /* 'us' is NULL when we are forced to notify even though no update was applied (via
1477
         * SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1). In that case we send neither a version nor a resource list. */
1478

1479
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *resources = NULL;
170✔
1480
        if (us)
170✔
1481
                for (size_t i = 0; i < c->n_transfers; i++) {
1,319✔
1482
                        Instance *inst = us->instances[i];
1,149✔
1483
                        Transfer *t = c->transfers[i];
1,149✔
1484

1485
                        if (inst->resource == &t->target &&
1,149✔
1486
                            !inst->is_pending)
920✔
1487
                                continue;
208✔
1488

1489
                        /* Report where the resource was installed *to* (not the source it came from): the
1490
                         * final on-disk path for filesystem targets, the partition device node for partition
1491
                         * targets. */
1492
                        const char *target_path =
256✔
1493
                                RESOURCE_IS_FILESYSTEM(t->target.type) ? t->final_path :
941✔
1494
                                t->target.type == RESOURCE_PARTITION ? t->partition_info.device :
1495
                                NULL;
1496

1497
                        r = sd_json_variant_append_arraybo(
941✔
1498
                                        &resources,
1499
                                        SD_JSON_BUILD_PAIR_STRING("transfer", t->id),
1500
                                        SD_JSON_BUILD_PAIR_CONDITION(!!target_path, "path", SD_JSON_BUILD_STRING(target_path)));
1501
                        if (r < 0)
941✔
1502
                                return log_warning_errno(r, "Failed to build sysupdate notify resources list, skipping notification: %m");
×
1503
                }
1504

1505
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL;
170✔
1506
        r = sd_json_buildo(
170✔
1507
                        &params,
1508
                        SD_JSON_BUILD_PAIR_CONDITION(!!c->component, "component", SD_JSON_BUILD_STRING(c->component)),
1509
                        SD_JSON_BUILD_PAIR_CONDITION(!!us, "version", SD_JSON_BUILD_STRING(us ? us->version : NULL)),
1510
                        SD_JSON_BUILD_PAIR_CONDITION(!!resources, "resources", SD_JSON_BUILD_VARIANT(resources)));
1511
        if (r < 0)
170✔
1512
                return log_warning_errno(r, "Failed to build sysupdate notify parameters, skipping notification: %m");
×
1513

1514
        ssize_t n = varlink_execute_directory(
170✔
1515
                        VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK,
1516
                        "io.systemd.SysUpdate.Notify.OnCompletedUpdate",
1517
                        params,
1518
                        /* more= */ false,
1519
                        /* timeout_usec= */ 5 * USEC_PER_MINUTE,
1520
                        notify_subscribers_reply,
1521
                        /* userdata= */ NULL);
1522
        if (n < 0)
170✔
1523
                log_debug_errno(n, "Failed to dispatch sysupdate notification to %s, ignoring: %m",
170✔
1524
                                VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
1525
        else if (n > 0)
170✔
1526
                log_debug("Dispatched sysupdate notification to %zi subscribers in %s.", n, VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
170✔
1527

1528
        return 0;
1529
}
1530

1531
static int context_install(
170✔
1532
                Context *c,
1533
                const char *version,
1534
                UpdateSet **ret_applied) {
1535

1536
        UpdateSet *us = NULL;
170✔
1537
        int r;
170✔
1538

1539
        assert(c);
170✔
1540

1541
        if (version) {
170✔
1542
                us = context_update_set_by_version(c, version);
×
1543
                if (!us)
×
1544
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1545
        } else {
1546
                if (!c->candidate) {
170✔
1547
                        log_info("No update needed.");
×
1548

1549
                        return 0;
1550
                }
1551

1552
                us = c->candidate;
1553
        }
1554

1555
        (void) sd_notifyf(/* unset_environment=*/ false,
170✔
1556
                          "READY=1\n"
1557
                          "X_SYSUPDATE_VERSION=%s\n"
1558
                          "STATUS=Installing '%s'.", us->version, us->version);
1559

1560
        for (size_t i = 0; i < c->n_transfers; i++) {
1,489✔
1561
                Instance *inst = us->instances[i];
1,149✔
1562
                Transfer *t = c->transfers[i];
1,149✔
1563

1564
                if (inst->resource == &t->target &&
1,149✔
1565
                    !inst->is_pending)
920✔
1566
                        continue;
208✔
1567

1568
                r = transfer_install_instance(t, inst, c->root);
941✔
1569
                if (r < 0)
941✔
1570
                        return r;
1571
        }
1572

1573
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
170✔
1574

1575
        if (!c->root)
170✔
1576
                (void) context_notify_subscribers(c, us);
170✔
1577

1578
        (void) sd_notifyf(/* unset_environment= */ false,
170✔
1579
                          "STATUS=Installed '%s'.", us->version);
1580

1581
        if (ret_applied)
170✔
1582
                *ret_applied = us;
170✔
1583

1584
        return 1;
1585
}
1586

1587
static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string);
200✔
1588

1589
static int dispatch_target_identifier(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
200✔
1590
        TargetIdentifier *t = ASSERT_PTR(userdata);
200✔
1591
        static const sd_json_dispatch_field dispatch[] = {
200✔
1592
                { "class", SD_JSON_VARIANT_STRING, dispatch_target_class,   voffsetof(*t, class), SD_JSON_MANDATORY },
1593
                { "name",  SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(*t, name),  SD_JSON_NULLABLE  },
1594
                {}
1595
        };
1596
        int r;
200✔
1597

1598
        r = sd_json_dispatch(variant, dispatch, flags, t);
200✔
1599
        if (r < 0)
200✔
1600
                return r;
1601

1602
        /* Name is mandatory unless class is `host` */
1603
        if ((t->class == TARGET_HOST) != (!t->name))
200✔
1604
                return json_log(variant, flags, SYNTHETIC_ERRNO(ENXIO), "Target name does not match class.");
×
1605

1606
        if (t->class == TARGET_COMPONENT && !component_name_valid(t->name))
200✔
1607
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", t->name);
8✔
1608

1609
        return 0;
1610
}
1611

1612
static int verify_polkit(Context *context, sd_varlink *link, const char *action, const char **details) {
192✔
1613
        int r;
192✔
1614
        Server *s = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
192✔
1615

1616
        assert(context);
192✔
1617

1618
        if (!s->system_bus) {
192✔
1619
                r = sd_bus_open_system_with_description(&s->system_bus, "sysupdate-system");
192✔
1620
                if (r < 0)
192✔
1621
                        return log_error_errno(r, "Failed to get system bus connection: %m");
×
1622

1623
                r = sd_bus_attach_event(s->system_bus, sd_varlink_get_event(link), SD_EVENT_PRIORITY_NORMAL);
192✔
1624
                if (r < 0)
192✔
1625
                        return log_error_errno(r, "Failed to attach system bus to event loop: %m");
×
1626
        }
1627

1628
        return varlink_verify_polkit_async(link,
192✔
1629
                        s->system_bus,
1630
                        action,
1631
                        details,
1632
                        &s->polkit_registry);
1633
}
1634

1635
VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT,
1636
     "Show installed and available versions");
1637
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
256✔
1638
        _cleanup_(context_done) Context context = CONTEXT_NULL;
256✔
1639
        _cleanup_strv_free_ char **appstream_urls = NULL;
256✔
1640
        const char *version;
256✔
1641
        int r;
256✔
1642

1643
        assert(argc <= 2);
256✔
1644
        version = argc >= 2 ? argv[1] : NULL;
256✔
1645

1646
        r = context_from_cmdline(&context);
256✔
1647
        if (r < 0)
256✔
1648
                return r;
1649

1650
        if (context.component_all)
256✔
1651
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
1652

1653
        r = context_load_online(&context, PROCESS_IMAGE_READ_ONLY);
256✔
1654
        if (r < 0)
256✔
1655
                return r;
1656

1657
        if (version)
256✔
1658
                return context_show_version(&context, version);
208✔
1659
        else if (!sd_json_format_enabled(arg_json_format_flags))
48✔
1660
                return context_show_table(&context);
×
1661
        else {
1662
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
48✔
1663
                _cleanup_strv_free_ char **versions = NULL;
48✔
1664
                const char *current = NULL;
48✔
1665
                bool current_is_pending = false;
48✔
1666

1667
                FOREACH_ARRAY(update_set, context.update_sets, context.n_update_sets) {
224✔
1668
                        UpdateSet *us = *update_set;
176✔
1669

1670
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
176✔
1671
                            FLAGS_SET(us->flags, UPDATE_NEWEST)) {
160✔
1672
                                current = us->version;
48✔
1673
                                current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
48✔
1674
                        }
1675

1676
                        r = strv_extend(&versions, us->version);
176✔
1677
                        if (r < 0)
176✔
1678
                                return log_oom();
×
1679
                }
1680

1681
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers)
288✔
1682
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
240✔
1683
                                /* Avoid duplicates */
1684
                                if (strv_contains(appstream_urls, *appstream_url))
×
1685
                                        continue;
×
1686

1687
                                r = strv_extend(&appstream_urls, *appstream_url);
×
1688
                                if (r < 0)
×
1689
                                        return log_oom();
×
1690
                        }
1691

1692
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current),
96✔
1693
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1694
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1695
                if (r < 0)
48✔
1696
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1697

1698
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
48✔
1699
                if (r < 0)
48✔
1700
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1701

1702
                return 0;
1703
        }
1704
}
1705

1706
VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0,
1707
     "Show optional features");
1708
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
1709
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
1710
        _cleanup_(table_unrefp) Table *table = NULL;
32✔
1711
        const char *feature_id;
32✔
1712
        Feature *f;
32✔
1713
        int r;
32✔
1714

1715
        assert(argc <= 2);
32✔
1716
        feature_id = argc >= 2 ? argv[1] : NULL;
32✔
1717

1718
        r = context_from_cmdline(&context);
32✔
1719
        if (r < 0)
32✔
1720
                return r;
1721

1722
        if (context.component_all)
32✔
1723
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
1724

1725
        r = context_load_offline(
32✔
1726
                        &context,
1727
                        PROCESS_IMAGE_READ_ONLY,
1728
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1729
        if (r < 0)
32✔
1730
                return r;
1731

1732
        if (feature_id) {
32✔
1733
                _cleanup_strv_free_ char **transfers = NULL;
16✔
1734

1735
                f = hashmap_get(context.features, feature_id);
16✔
1736
                if (!f)
16✔
1737
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1738
                                               "Optional feature not found: %s",
1739
                                               feature_id);
1740

1741
                table = table_new_vertical();
16✔
1742
                if (!table)
16✔
1743
                        return log_oom();
×
1744

1745
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers) {
128✔
1746
                        Transfer *t = *tr;
112✔
1747

1748
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
112✔
1749
                                continue;
112✔
1750

1751
                        r = strv_extend(&transfers, t->id);
×
1752
                        if (r < 0)
×
1753
                                return log_oom();
×
1754
                }
1755

1756
                FOREACH_ARRAY(tr, context.disabled_transfers, context.n_disabled_transfers) {
32✔
1757
                        Transfer *t = *tr;
16✔
1758

1759
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
16✔
1760
                                continue;
×
1761

1762
                        r = strv_extend(&transfers, t->id);
16✔
1763
                        if (r < 0)
16✔
1764
                                return log_oom();
×
1765
                }
1766

1767
                r = table_add_many(table,
16✔
1768
                                   TABLE_FIELD, "Name",
1769
                                   TABLE_STRING, f->id,
1770
                                   TABLE_FIELD, "Enabled",
1771
                                   TABLE_BOOLEAN, f->enabled);
1772
                if (r < 0)
16✔
1773
                        return table_log_add_error(r);
×
1774

1775
                if (f->description) {
16✔
1776
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
16✔
1777
                        if (r < 0)
16✔
1778
                                return table_log_add_error(r);
×
1779
                }
1780

1781
                if (f->documentation) {
16✔
1782
                        r = table_add_many(table,
×
1783
                                           TABLE_FIELD, "Documentation",
1784
                                           TABLE_STRING, f->documentation,
1785
                                           TABLE_SET_URL, f->documentation);
1786
                        if (r < 0)
×
1787
                                return table_log_add_error(r);
×
1788
                }
1789

1790
                if (f->appstream) {
16✔
1791
                        r = table_add_many(table,
×
1792
                                           TABLE_FIELD, "AppStream",
1793
                                           TABLE_STRING, f->appstream,
1794
                                           TABLE_SET_URL, f->appstream);
1795
                        if (r < 0)
×
1796
                                return table_log_add_error(r);
×
1797
                }
1798

1799
                if (!strv_isempty(transfers)) {
16✔
1800
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
16✔
1801
                        if (r < 0)
16✔
1802
                                return table_log_add_error(r);
×
1803
                }
1804

1805
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
16✔
1806
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
16✔
1807
                table = table_new("", "feature", "description", "documentation");
16✔
1808
                if (!table)
16✔
1809
                        return log_oom();
×
1810

1811
                HASHMAP_FOREACH(f, context.features) {
32✔
1812
                        r = table_add_many(table,
16✔
1813
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1814
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
1815
                                           TABLE_STRING, f->id,
1816
                                           TABLE_STRING, f->description,
1817
                                           TABLE_STRING, f->documentation,
1818
                                           TABLE_SET_URL, f->documentation);
1819
                        if (r < 0)
16✔
1820
                                return table_log_add_error(r);
×
1821
                }
1822

1823
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
16✔
1824
        } else {
1825
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1826
                _cleanup_strv_free_ char **features = NULL;
×
1827

1828
                HASHMAP_FOREACH(f, context.features) {
×
1829
                        r = strv_extend(&features, f->id);
×
1830
                        if (r < 0)
×
1831
                                return log_oom();
×
1832
                }
1833

1834
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
1835
                if (r < 0)
×
1836
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1837

1838
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
×
1839
                if (r < 0)
×
1840
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1841
        }
1842

1843
        return 0;
×
1844
}
1845

1846
VERB_NOARG(verb_check_new, "check-new",
1847
           "Check if there's a new version available");
1848
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
218✔
1849
        _cleanup_(context_done) Context context = CONTEXT_NULL;
218✔
1850
        int r;
218✔
1851

1852
        assert(argc <= 1);
218✔
1853

1854
        r = context_from_cmdline(&context);
218✔
1855
        if (r < 0)
218✔
1856
                return r;
1857

1858
        if (context.component_all)
218✔
1859
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
1860

1861
        r = context_load_online(
218✔
1862
                        &context,
1863
                        PROCESS_IMAGE_READ_ONLY);
1864
        if (r < 0)
218✔
1865
                return r;
1866

1867
        if (!sd_json_format_enabled(arg_json_format_flags)) {
217✔
1868
                if (!context.candidate) {
185✔
1869
                        log_debug("No candidate found.");
96✔
1870
                        return EXIT_FAILURE;
1871
                }
1872

1873
                puts(context.candidate->version);
89✔
1874
        } else {
1875
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
32✔
1876

1877
                if (context.candidate)
32✔
1878
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
×
1879
                else
1880
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
32✔
1881
                if (r < 0)
32✔
1882
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1883

1884
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
32✔
1885
                if (r < 0)
32✔
1886
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1887
        }
1888

1889
        return EXIT_SUCCESS;
1890
}
1891

1892
static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
200✔
1893
        _cleanup_(context_done) Context context = CONTEXT_NULL;
200✔
1894
        int r;
200✔
1895

1896
        assert(link);
200✔
1897

1898
        static const sd_json_dispatch_field dispatch_table[] = {
200✔
1899
                { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY },
1900
                VARLINK_DISPATCH_POLKIT_FIELD,
1901
                {},
1902
        };
1903

1904
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &context);
200✔
1905
        if (r != 0)
200✔
1906
                return r;
1907

1908
        r = verify_polkit(&context, link, "org.freedesktop.sysupdate1.check",
576✔
1909
                        (const char**) STRV_MAKE(
376✔
1910
                                        "class", target_class_to_string(context.target_identifier.class),
1911
                                        "offline", "0",
1912
                                        context.target_identifier.name ? "name" : NULL, context.target_identifier.name));
1913
        if (r <= 0)
192✔
1914
                return r;
1915

1916
        if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
192✔
1917
                context.verify = 0;
192✔
1918

1919
        /* CheckNew is always online */
1920
        context.offline = false;
192✔
1921

1922
        r = context_load_online_from_target(&context, PROCESS_IMAGE_READ_ONLY);
192✔
1923
        if (r == -ENOENT)
192✔
1924
                return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
8✔
1925
        if (r < 0)
184✔
1926
                return r;
1927

1928
        if (context.candidate)
184✔
1929
                r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
88✔
1930
        else
1931
                r = sd_varlink_error(link, "io.systemd.SysUpdate.NoUpdateNeeded", NULL);
96✔
1932
        if (r < 0)
184✔
1933
                return r;
96✔
1934

1935
        return 0;
1936
}
1937

1938
typedef enum {
1939
        UPDATE_ACTION_ACQUIRE = 1 << 0,
1940
        UPDATE_ACTION_INSTALL = 1 << 1,
1941
} UpdateActionFlags;
1942

1943
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
386✔
1944
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
1945
        _cleanup_free_ char *booted_version = NULL;
386✔
1946
        UpdateSet *applied = NULL;
386✔
1947
        const char *version;
386✔
1948
        int r;
386✔
1949

1950
        assert(argc <= 2);
386✔
1951
        version = argc >= 2 ? argv[1] : NULL;
386✔
1952

1953
        r = context_from_cmdline(&context);
386✔
1954
        if (r < 0)
386✔
1955
                return r;
1956

1957
        if (context.component_all)
386✔
1958
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
1✔
1959

1960
        if (context.instances_max < 2)
385✔
1961
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1962
                                      "The --instances-max argument must be >= 2 while updating");
1963

1964
        if (context.reboot) {
385✔
1965
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
1966

1967
                r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
×
1968
                if (r < 0)
×
1969
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1970
                if (!booted_version)
×
1971
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
1972
        }
1973

1974
        bool installed = false;
385✔
1975
        int ret = 0;
385✔
1976

1977
        r = context_load_online(
385✔
1978
                        &context,
1979
                        /* process_image_flags= */ 0);
1980
        if (r < 0) {
385✔
1981
                if (r != -ENOENT)
17✔
1982
                        return r;
1983

1984
                /* No transfer files found. In that case, still do the installdb cleanup below */
1985
                RET_GATHER(ret, r);
1986
        } else {
1987
                if (action_flags & UPDATE_ACTION_ACQUIRE)
368✔
1988
                        r = context_acquire(&context, version);
284✔
1989
                else
1990
                        r = context_process_partial_and_pending(&context, version);
84✔
1991
                if (r < 0)
368✔
1992
                        return r;
1993

1994
                if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */
332✔
1995
                        r = context_install(&context, version, &applied);
170✔
1996
                        if (r < 0)
170✔
1997
                                return r;
1998

1999
                        installed = r > 0;
170✔
2000
                }
2001

2002
                /* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If
2003
                 * nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a
2004
                 * resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */
2005
                if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) {
220✔
2006
                        int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY");
50✔
2007
                        if (f < 0 && f != -ENXIO)
50✔
2008
                                log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m");
×
2009
                        if (f > 0)
50✔
2010
                                (void) context_notify_subscribers(&context, /* us= */ NULL);
×
2011
                }
2012
        }
2013

2014
        if (context.cleanup > 0)
332✔
2015
                RET_GATHER(ret, installdb_cleanup_component(&context));
2✔
2016

2017
        if (installed) {
332✔
2018
                /* We installed something, yay */
2019

2020
                if (context.reboot) {
170✔
2021
                        assert(applied);
×
2022
                        assert(booted_version);
×
2023

2024
                        if (strverscmp_improved(applied->version, booted_version) > 0) {
×
2025
                                log_notice("Newly installed version is newer than booted version, rebooting.");
×
2026
                                RET_GATHER(ret, reboot_now());
×
2027
                        } else if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
2028
                                   FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
2029
                                log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
2030
                                RET_GATHER(ret, reboot_now());
×
2031
                        } else
2032
                                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
2033
                }
2034
        }
2035

2036
        return ret;
2037
}
2038

2039
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
2040
     "Install new version now");
2041
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
250✔
2042
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
250✔
2043

2044
        if (!arg_offline)
250✔
2045
                flags |= UPDATE_ACTION_ACQUIRE;
166✔
2046

2047
        return verb_update_impl(argc, argv, flags);
250✔
2048
}
2049

2050
VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
2051
     "Acquire (download) new version now");
2052
static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
136✔
2053
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
136✔
2054
}
2055

2056
VERB_NOARG(verb_vacuum, "vacuum",
2057
           "Make room, by deleting old versions");
2058
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
2059
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
2060
        int r;
32✔
2061

2062
        assert(argc <= 1);
32✔
2063

2064
        r = context_from_cmdline(&context);
32✔
2065
        if (r < 0)
32✔
2066
                return r;
2067

2068
        if (context.component_all)
32✔
2069
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
2070

2071
        if (context.instances_max < 1)
32✔
2072
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2073
                                      "The --instances-max argument must be >= 1 while vacuuming");
2074

2075
        r = context_load_offline(
32✔
2076
                        &context,
2077
                        /* process_image_flags= */ 0,
2078
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2079
        if (r < 0)
32✔
2080
                return r;
2081

2082
        return context_vacuum(&context, 0, NULL);
32✔
2083
}
2084

2085
VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
2086
     "Report whether a newer version is installed than currently booted");
2087
VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
2088
     "Reboot if a newer version is installed than booted");
2089
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
2✔
2090
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2091
        _cleanup_free_ char *booted_version = NULL;
2✔
2092
        int r;
2✔
2093

2094
        assert(argc == 1);
2✔
2095

2096
        r = context_from_cmdline(&context);
2✔
2097
        if (r < 0)
2✔
2098
                return r;
2099

2100
        if (context.image || context.root)
2✔
2101
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2102
                                       "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
2103

2104
        if (context.component || context.component_all)
2✔
2105
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2✔
2106
                                       "The --component= and --component-all switches may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]);
2107

2108
        r = context_load_offline(
×
2109
                        &context,
2110
                        /* process_image_flags= */ 0,
2111
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2112
        if (r < 0)
×
2113
                return r;
2114

2115
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
2116

2117
        r = context_discover_update_sets_by_flag(&context, UPDATE_INSTALLED);
×
2118
        if (r < 0)
×
2119
                return r;
2120
        if (!context.newest_installed)
×
2121
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
2122

2123
        r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
×
2124
        if (r < 0) /* yes, context.root is NULL here, but we have to pass something, and it's a lot more readable
×
2125
                    * if we see what the first argument is about */
2126
                return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
2127
        if (!booted_version)
×
2128
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field.");
×
2129

2130
        r = strverscmp_improved(context.newest_installed->version, booted_version);
×
2131
        if (r > 0) {
×
2132
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
2133
                           context.newest_installed->version, booted_version,
2134
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
2135

2136
                if (streq(argv[0], "reboot"))
×
2137
                        return reboot_now();
×
2138

2139
                return EXIT_SUCCESS;
2140
        } else if (r == 0)
×
2141
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
2142
                         context.newest_installed->version, booted_version);
2143
        else
2144
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
2145
                            context.newest_installed->version, booted_version);
2146

2147
        if (streq(argv[0], "pending")) /* When called as 'pending' tell the caller via failure exit code that there's nothing newer installed */
×
2148
                return EXIT_FAILURE;
×
2149

2150
        return EXIT_SUCCESS;
2151
}
2152

2153
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component) {
182✔
2154
        int r;
182✔
2155

2156
        assert(context);
182✔
2157

2158
        _cleanup_strv_free_ char **z = NULL;
182✔
2159
        r = get_component_list(context->root, &z);
182✔
2160
        if (r < 0)
182✔
2161
                return r;
2162

2163
        if (ret_component_names)
182✔
2164
                *ret_component_names = TAKE_PTR(z);
182✔
2165

2166
        /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a
2167
         * TARGET_HOST? See target_get_argument() in sysupdated.c */
2168
        if (ret_has_default_component)
182✔
2169
                *ret_has_default_component =
348✔
2170
                        !context->definitions &&
348✔
2171
                        !context->component &&
174✔
2172
                        !context->root &&
174✔
2173
                        !context->image &&
348✔
2174
                        context->n_transfers > 0;
176✔
2175

2176
        return 0;
2177
}
2178

2179
VERB_NOARG(verb_components, "components",
2180
           "Show list of components");
2181
static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
174✔
2182
        _cleanup_(context_done) Context context = CONTEXT_NULL;
174✔
2183
        _cleanup_strv_free_ char **component_names = NULL;
174✔
2184
        bool has_default_component = false;
174✔
2185
        int r;
174✔
2186

2187
        assert(argc <= 1);
174✔
2188

2189
        r = context_from_cmdline(&context);
174✔
2190
        if (r < 0)
174✔
2191
                return r;
2192

2193
        if (context.component_all)
174✔
2194
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
2195

2196
        r = context_load_offline(
174✔
2197
                        &context,
2198
                        /* process_image_flags= */ 0,
2199
                        /* read_definitions_flags= */ 0);
2200
        if (r < 0)
174✔
2201
                return r;
2202

2203
        r = context_list_components(&context, &component_names, &has_default_component);
174✔
2204
        if (r < 0)
174✔
2205
                return log_error_errno(r, "Failed to enumerate components: %m");
×
2206

2207
        if (!sd_json_format_enabled(arg_json_format_flags)) {
174✔
2208
                if (!has_default_component && strv_isempty(component_names)) {
×
2209
                        log_info("No components defined.");
×
2210
                        return 0;
2211
                }
2212

2213
                if (has_default_component)
×
2214
                        printf("%s<default>%s\n",
×
2215
                               ansi_highlight(), ansi_normal());
2216

2217
                STRV_FOREACH(i, component_names)
×
2218
                        puts(*i);
×
2219
        } else {
2220
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
174✔
2221

2222
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
174✔
2223
                                          SD_JSON_BUILD_PAIR_STRV("components", component_names));
2224
                if (r < 0)
174✔
2225
                        return log_error_errno(r, "Failed to create JSON: %m");
×
2226

2227
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
174✔
2228
                if (r < 0)
174✔
2229
                        return log_error_errno(r, "Failed to print JSON: %m");
×
2230
        }
2231

2232
        return 0;
2233
}
2234

2235
VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files");
2236
static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) {
7✔
2237
        _cleanup_(context_done) Context context = CONTEXT_NULL;
7✔
2238
        int r;
7✔
2239

2240
        assert(argc <= 1);
7✔
2241

2242
        r = context_from_cmdline(&context);
7✔
2243
        if (r < 0)
7✔
2244
                return r;
2245

2246
        if (context.cleanup == 0)
7✔
2247
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invocation of 'cleanup' with --cleanup=no is contradictory, refusing.");
1✔
2248

2249
        r = context_load_offline(
6✔
2250
                        &context,
2251
                        /* process_image_flags= */ 0,
2252
                        /* read_definitions_flags= */ 0);
2253
        if (r < 0)
6✔
2254
                return r;
2255

2256
        int ret = 0;
6✔
2257
        RET_GATHER(ret, installdb_cleanup_component(&context));
6✔
2258

2259
        if (context.component_all) {
6✔
2260
                _cleanup_strv_free_ char **z = NULL;
2✔
2261
                r = installdb_list_components(&context, &z);
2✔
2262
                if (r < 0)
2✔
2263
                        return log_error_errno(r, "Failed to enumerate components: %m");
×
2264

2265
                STRV_FOREACH(i, z) {
6✔
2266
                        _cleanup_(context_done) Context component_context = CONTEXT_NULL;
×
2267

2268
                        r = context_from_cmdline(&component_context);
4✔
2269
                        if (r < 0)
4✔
2270
                                return r;
2271

2272
                        /* Override the component with our iter. This needs to be done in a fresh Context
2273
                         * as the installdb_fd and other state are specific to the component. */
2274
                        r = free_and_strdup_warn(&component_context.component, *i);
4✔
2275
                        if (r < 0)
4✔
2276
                                return r;
2277

2278
                        r = context_load_offline(
4✔
2279
                                        &component_context,
2280
                                        /* process_image_flags= */ 0,
2281
                                        /* read_definitions_flags= */ 0);
2282
                        if (r < 0)
4✔
2283
                                return r;
2284

2285
                        RET_GATHER(ret, installdb_cleanup_component(&component_context));
4✔
2286
                }
2287
        }
2288

2289
        return ret;
2290
}
2291

2292
static int help(void) {
×
2293
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
2294
        int r;
×
2295

2296
        r = verbs_get_help_table(&verbs);
×
2297
        if (r < 0)
×
2298
                return r;
2299

2300
        r = option_parser_get_help_table(&common_options);
×
2301
        if (r < 0)
×
2302
                return r;
2303

2304
        r = option_parser_get_help_table_group("Options", &options);
×
2305
        if (r < 0)
×
2306
                return r;
2307

2308
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
2309

2310
        help_cmdline("[OPTIONS…] [VERSION]");
×
2311
        help_abstract("Update OS images.");
×
2312

2313
        help_section("Commands");
×
2314
        r = table_print_or_warn(verbs);
×
2315
        if (r < 0)
×
2316
                return r;
2317

2318
        r = table_print_or_warn(common_options);
×
2319
        if (r < 0)
×
2320
                return r;
2321

2322
        help_section("Options");
×
2323
        r = table_print_or_warn(options);
×
2324
        if (r < 0)
×
2325
                return r;
2326

2327
        help_man_page_reference("systemd-sysupdate", "8");
×
2328
        return 0;
2329
}
2330

2331
VERB_COMMON_HELP_HIDDEN(help);
×
2332

2333
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
1,328✔
2334
        assert(argc >= 0);
1,328✔
2335
        assert(argv);
1,328✔
2336
        assert(remaining_args);
1,328✔
2337

2338
        OptionParser opts = { argc, argv };
1,328✔
2339
        int r;
1,328✔
2340

2341
        FOREACH_OPTION_OR_RETURN(c, &opts)
4,128✔
2342
                switch (c) {
1,480✔
2343

2344
                OPTION_COMMON_HELP:
×
2345
                        return help();
×
2346

2347
                OPTION_COMMON_VERSION:
×
2348
                        return version();
×
2349

2350
                OPTION_GROUP("Options"):
2351
                        break;
2352

2353
                OPTION('C', "component", "NAME",
45✔
2354
                       "Select component to update"):
2355
                        if (isempty(opts.arg)) {
45✔
2356
                                arg_component = mfree(arg_component);
×
2357
                                arg_component_all = false;
×
2358
                                break;
×
2359
                        }
2360

2361
                        if (!component_name_valid(opts.arg))
45✔
2362
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", opts.arg);
8✔
2363

2364
                        r = free_and_strdup_warn(&arg_component, opts.arg);
37✔
2365
                        if (r < 0)
37✔
2366
                                return r;
2367

2368
                        arg_component_all = false;
37✔
2369
                        break;
37✔
2370

2371
                OPTION('A', "component-all", NULL, "Process all components"):
3✔
2372

2373
                        arg_component = mfree(arg_component);
3✔
2374
                        arg_component_all = true;
3✔
2375
                        break;
3✔
2376

2377
                OPTION_LONG("definitions", "DIR",
5✔
2378
                            "Find transfer definitions in specified directory"):
2379
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions);
5✔
2380
                        if (r < 0)
5✔
2381
                                return r;
2382
                        break;
2383

2384
                OPTION_LONG("root", "PATH",
×
2385
                            "Operate on an alternate filesystem root"):
2386
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
2387
                        if (r < 0)
×
2388
                                return r;
2389
                        break;
2390

2391
                OPTION_LONG("image", "PATH",
×
2392
                            "Operate on disk image as filesystem root"):
2393
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
2394
                        if (r < 0)
×
2395
                                return r;
2396
                        break;
2397

2398
                OPTION_LONG("image-policy", "POLICY",
×
2399
                            "Specify disk image dissection policy"):
2400
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
2401
                        if (r < 0)
×
2402
                                return r;
2403
                        break;
2404

2405
                OPTION_LONG("transfer-source", "PATH",
×
2406
                            "Specify the directory to transfer sources from"):
2407
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_transfer_source);
×
2408
                        if (r < 0)
×
2409
                                return r;
2410

2411
                        break;
2412

2413
                OPTION('m', "instances-max", "INT",
×
2414
                       "How many instances to maintain"):
2415
                        r = safe_atou64(opts.arg, &arg_instances_max);
×
2416
                        if (r < 0)
×
2417
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", opts.arg);
×
2418

2419
                        break;
2420

2421
                OPTION_LONG("sync", "BOOL",
×
2422
                            "Controls whether to sync data to disk"):
2423
                        r = parse_boolean_argument("--sync=", opts.arg, &arg_sync);
×
2424
                        if (r < 0)
×
2425
                                return r;
2426
                        break;
2427

2428
                OPTION_LONG("verify", "BOOL",
736✔
2429
                            "Force signature verification on or off"): {
2430
                        bool b;
736✔
2431

2432
                        r = parse_boolean_argument("--verify=", opts.arg, &b);
736✔
2433
                        if (r < 0)
736✔
2434
                                return r;
×
2435

2436
                        arg_verify = b;
736✔
2437
                        break;
736✔
2438
                }
2439

2440
                OPTION_LONG("reboot", NULL,
1✔
2441
                            "Reboot after updating to newer version"):
2442
                        arg_reboot = true;
1✔
2443
                        break;
1✔
2444

2445
                OPTION_LONG("offline", NULL,
228✔
2446
                            "Do not fetch metadata from the network"):
2447
                        arg_offline = true;
228✔
2448
                        break;
228✔
2449

2450
                OPTION_LONG("cleanup", "BOOL", "Clean up orphaned files after completing update"): {
4✔
2451
                        bool b;
4✔
2452

2453
                        r = parse_boolean_argument("--cleanup=", opts.arg, &b);
4✔
2454
                        if (r < 0)
4✔
2455
                                return r;
×
2456

2457
                        arg_cleanup = b;
4✔
2458
                        break;
4✔
2459
                }
2460

2461
                OPTION_COMMON_NO_PAGER:
×
2462
                        arg_pager_flags |= PAGER_DISABLE;
×
2463
                        break;
×
2464

2465
                OPTION_COMMON_NO_LEGEND:
×
2466
                        arg_legend = false;
×
2467
                        break;
×
2468

2469
                OPTION_COMMON_JSON:
458✔
2470
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
458✔
2471
                        if (r <= 0)
458✔
2472
                                return r;
2473

2474
                        break;
2475
                }
2476

2477
        if (arg_image && arg_root)
1,320✔
2478
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
2479

2480
        if ((arg_image || arg_root) && arg_reboot)
1,320✔
2481
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
2482

2483
        if (arg_reboot && arg_component)
1,320✔
2484
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --component=, as automatic reboots only apply to the booted OS version.");
1✔
2485

2486
        if (arg_definitions && arg_component)
1,319✔
2487
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
2488

2489
        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
1,319✔
2490
        if (r < 0)
1,319✔
2491
                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
×
2492
        if (r > 0)
1,319✔
2493
                arg_varlink = true;
212✔
2494

2495
        *remaining_args = option_parser_get_args(&opts);
1,319✔
2496
        return 1;
1,319✔
2497
}
2498

2499
static int vl_server(void) {
212✔
2500
        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
212✔
2501
        _cleanup_(server_done) Server server = SERVER_NULL;
212✔
2502
        int r;
212✔
2503

2504
        r = varlink_server_new(&varlink_server,
212✔
2505
                               SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
2506
                               &server);
2507
        if (r < 0)
212✔
2508
                return log_error_errno(r, "Failed to allocate Varlink server: %m");
×
2509

2510
        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysUpdate);
212✔
2511
        if (r < 0)
212✔
2512
                return log_error_errno(r, "Failed to add Varlink interface: %m");
×
2513

2514
        r = sd_varlink_server_bind_method_many(
212✔
2515
                        varlink_server,
2516
                        "io.systemd.SysUpdate.CheckNew", vl_method_check_new);
2517
        if (r < 0)
212✔
2518
                return log_error_errno(r, "Failed to bind Varlink method: %m");
×
2519

2520
        r = sd_varlink_server_loop_auto(varlink_server);
212✔
2521
        if (r < 0)
212✔
2522
                return log_error_errno(r, "Failed to run Varlink event loop: %m");
×
2523

2524
        return 0;
2525
}
2526

2527
static int run(int argc, char *argv[]) {
1,328✔
2528
        int r;
1,328✔
2529

2530
        log_setup();
1,328✔
2531

2532
        char **args = NULL;
1,328✔
2533
        r = parse_argv(argc, argv, &args);
1,328✔
2534
        if (r <= 0)
1,328✔
2535
                return r;
1,328✔
2536

2537
        if (arg_varlink)
1,319✔
2538
                return vl_server(); /* Invocation as Varlink service */
212✔
2539

2540
        return dispatch_verb(args, NULL);
1,107✔
2541
}
2542

2543
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
1,328✔
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