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

systemd / systemd / 24917789359

24 Apr 2026 04:59PM UTC coverage: 71.97% (-0.2%) from 72.201%
24917789359

push

github

bluca
units: order networkd resolve hook After=network-pre.target

Without this, the socket is available well before systemd-networkd.service
is able to start, because of its own After=network-pre.target ordering.
Then, if resolved handles queries before network-pre.target, it will
hang waiting for networkd to reply to hook queries.

This is currently happening in the wild with cloud-init.

322629 of 448281 relevant lines covered (71.97%)

1175521.89 hits per line

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

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

3
#include <unistd.h>
4

5
#include "sd-daemon.h"
6

7
#include "build.h"
8
#include "conf-files.h"
9
#include "constants.h"
10
#include "dissect-image.h"
11
#include "format-table.h"
12
#include "glyph-util.h"
13
#include "hexdecoct.h"
14
#include "image-policy.h"
15
#include "loop-util.h"
16
#include "main-func.h"
17
#include "mount-util.h"
18
#include "options.h"
19
#include "os-util.h"
20
#include "pager.h"
21
#include "parse-argument.h"
22
#include "parse-util.h"
23
#include "path-util.h"
24
#include "pretty-print.h"
25
#include "set.h"
26
#include "sort-util.h"
27
#include "specifier.h"
28
#include "string-util.h"
29
#include "strv.h"
30
#include "sysupdate.h"
31
#include "sysupdate-feature.h"
32
#include "sysupdate-instance.h"
33
#include "sysupdate-transfer.h"
34
#include "sysupdate-update-set.h"
35
#include "sysupdate-util.h"
36
#include "utf8.h"
37
#include "verbs.h"
38

39
static char *arg_definitions = NULL;
40
bool arg_sync = true;
41
uint64_t arg_instances_max = UINT64_MAX;
42
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
43
static PagerFlags arg_pager_flags = 0;
44
static bool arg_legend = true;
45
char *arg_root = NULL;
46
static char *arg_image = NULL;
47
static bool arg_reboot = false;
48
static char *arg_component = NULL;
49
static int arg_verify = -1;
50
static ImagePolicy *arg_image_policy = NULL;
51
static bool arg_offline = false;
52
char *arg_transfer_source = NULL;
53

54
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
564✔
55
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
564✔
56
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
564✔
57
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
564✔
58
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
564✔
59
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
564✔
60

61
const Specifier specifier_table[] = {
62
        COMMON_SYSTEM_SPECIFIERS,
63
        COMMON_TMP_SPECIFIERS,
64
        {}
65
};
66

67
typedef struct Context {
68
        Transfer **transfers;
69
        size_t n_transfers;
70

71
        Transfer **disabled_transfers;
72
        size_t n_disabled_transfers;
73

74
        Hashmap *features; /* Defined features, keyed by ID */
75

76
        UpdateSet **update_sets;
77
        size_t n_update_sets;
78

79
        UpdateSet *newest_installed, *candidate;
80

81
        Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */
82
} Context;
83

84
static Context* context_free(Context *c) {
488✔
85
        if (!c)
488✔
86
                return NULL;
87

88
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
2,944✔
89
                transfer_free(*tr);
2,456✔
90
        free(c->transfers);
488✔
91

92
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
920✔
93
                transfer_free(*tr);
432✔
94
        free(c->disabled_transfers);
488✔
95

96
        hashmap_free(c->features);
488✔
97

98
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
2,292✔
99
                update_set_free(*us);
1,804✔
100
        free(c->update_sets);
488✔
101

102
        hashmap_free(c->web_cache);
488✔
103

104
        return mfree(c);
488✔
105
}
106

107
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
1,440✔
108

109
static Context* context_new(void) {
488✔
110
        /* For now, no fields to initialize non-zero */
111
        return new0(Context, 1);
488✔
112
}
113

114
static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free);
×
115

116
static int read_definitions(
488✔
117
                Context *c,
118
                const char **dirs,
119
                const char *suffix,
120
                const char *node) {
121

122
        ConfFile **files = NULL;
488✔
123
        Transfer **transfers = NULL, **disabled = NULL;
488✔
124
        size_t n_files = 0, n_transfers = 0, n_disabled = 0;
488✔
125
        int r;
488✔
126

127
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
488✔
128
        CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array);
488✔
129
        CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array);
488✔
130

131
        assert(c);
488✔
132
        assert(dirs);
488✔
133
        assert(suffix);
488✔
134

135
        r = conf_files_list_strv_full(suffix, arg_root,
488✔
136
                                      CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
137
                                      dirs, &files, &n_files);
138
        if (r < 0)
488✔
139
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*%s definitions: %m", suffix);
×
140

141
        FOREACH_ARRAY(i, files, n_files) {
3,376✔
142
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
143
                Transfer **appended;
2,888✔
144
                ConfFile *e = *i;
2,888✔
145

146
                t = transfer_new(c);
2,888✔
147
                if (!t)
2,888✔
148
                        return log_oom();
×
149

150
                r = transfer_read_definition(t, e->result, dirs, c->features);
2,888✔
151
                if (r < 0)
2,888✔
152
                        return r;
153

154
                r = transfer_resolve_paths(t, arg_root, node);
2,888✔
155
                if (r < 0)
2,888✔
156
                        return r;
157

158
                if (t->enabled)
2,888✔
159
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
2,456✔
160
                else
161
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
432✔
162
                if (!appended)
2,888✔
163
                        return log_oom();
×
164
                TAKE_PTR(t);
2,888✔
165
        }
166

167
        c->transfers = TAKE_PTR(transfers);
488✔
168
        c->n_transfers = n_transfers;
488✔
169
        c->disabled_transfers = TAKE_PTR(disabled);
488✔
170
        c->n_disabled_transfers = n_disabled;
488✔
171
        return 0;
488✔
172
}
173

174
static int context_read_definitions(Context *c, const char* node, bool requires_enabled_transfers) {
488✔
175
        _cleanup_strv_free_ char **dirs = NULL;
488✔
176
        int r;
488✔
177

178
        assert(c);
488✔
179

180
        if (arg_definitions)
488✔
181
                dirs = strv_new(arg_definitions);
×
182
        else if (arg_component) {
488✔
183
                char **l = CONF_PATHS_STRV("");
8✔
184
                size_t i = 0;
8✔
185

186
                dirs = new0(char*, strv_length(l) + 1);
8✔
187
                if (!dirs)
8✔
188
                        return log_oom();
×
189

190
                STRV_FOREACH(dir, l) {
40✔
191
                        char *j;
32✔
192

193
                        j = strjoin(*dir, "sysupdate.", arg_component, ".d");
32✔
194
                        if (!j)
32✔
195
                                return log_oom();
×
196

197
                        dirs[i++] = j;
32✔
198
                }
199
        } else
200
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
480✔
201
        if (!dirs)
488✔
202
                return log_oom();
×
203

204
        ConfFile **files = NULL;
488✔
205
        size_t n_files = 0;
488✔
206

207
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
488✔
208

209
        r = conf_files_list_strv_full(".feature", arg_root,
488✔
210
                                      CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
211
                                      (const char**) dirs, &files, &n_files);
212
        if (r < 0)
488✔
213
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m");
×
214

215
        FOREACH_ARRAY(i, files, n_files) {
968✔
216
                _cleanup_(feature_unrefp) Feature *f = NULL;
×
217
                ConfFile *e = *i;
480✔
218

219
                f = feature_new();
480✔
220
                if (!f)
480✔
221
                        return log_oom();
×
222

223
                r = feature_read_definition(f, e->result, (const char**) dirs);
480✔
224
                if (r < 0)
480✔
225
                        return r;
226

227
                r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f);
480✔
228
                if (r < 0)
480✔
229
                        return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id);
×
230
                TAKE_PTR(f);
480✔
231
        }
232

233
        r = read_definitions(c, (const char**) dirs, ".transfer", node);
488✔
234
        if (r < 0)
488✔
235
                return r;
236

237
        if (c->n_transfers + c->n_disabled_transfers == 0) {
488✔
238
                /* Backwards-compat: If no .transfer defs are found, fall back to trying .conf! */
239
                r = read_definitions(c, (const char**) dirs, ".conf", node);
×
240
                if (r < 0)
×
241
                        return r;
242

243
                if (c->n_transfers + c->n_disabled_transfers > 0)
×
244
                        log_warning("As of v257, transfer definitions should have the '.transfer' extension.");
×
245
        }
246

247
        if (c->n_transfers + (requires_enabled_transfers ? 0 : c->n_disabled_transfers) == 0) {
488✔
248
                if (arg_component)
×
249
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
250
                                               "No transfer definitions for component '%s' found.",
251
                                               arg_component);
252

253
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
254
                                       "No transfer definitions found.");
255
        }
256

257
        return 0;
258
}
259

260
static int context_load_installed_instances(Context *c) {
488✔
261
        int r;
488✔
262

263
        assert(c);
488✔
264

265
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
976✔
266

267
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
2,944✔
268
                Transfer *t = *tr;
2,456✔
269

270
                r = resource_load_instances(
2,456✔
271
                                &t->target,
272
                                arg_verify >= 0 ? arg_verify : t->verify,
2,456✔
273
                                &c->web_cache);
1,928✔
274
                if (r < 0)
2,456✔
275
                        return r;
276
        }
277

278
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
920✔
279
                Transfer *t = *tr;
432✔
280

281
                r = resource_load_instances(
432✔
282
                                &t->target,
283
                                arg_verify >= 0 ? arg_verify : t->verify,
432✔
284
                                &c->web_cache);
348✔
285
                if (r < 0)
432✔
286
                        return r;
287
        }
288

289
        return 0;
290
}
291

292
static int context_load_available_instances(Context *c) {
350✔
293
        int r;
350✔
294

295
        assert(c);
350✔
296

297
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
700✔
298

299
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
2,062✔
300
                Transfer *t = *tr;
1,720✔
301

302
                r = resource_load_instances(
1,720✔
303
                                &t->source,
304
                                arg_verify >= 0 ? arg_verify : t->verify,
1,720✔
305
                                &c->web_cache);
1,720✔
306
                if (r < 0)
1,720✔
307
                        return r;
308
        }
309

310
        return 0;
311
}
312

313
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
798✔
314
        _cleanup_free_ char *boundary = NULL;
798✔
315
        bool newest_found = false;
798✔
316
        int r;
798✔
317

318
        assert(c);
798✔
319
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
798✔
320

321
        for (;;) {
3,750✔
322
                _cleanup_free_ Instance **cursor_instances = NULL;
×
323
                bool skip = false;
3,750✔
324
                UpdateSetFlags extra_flags = 0;
3,750✔
325
                _cleanup_free_ char *cursor = NULL;
2,952✔
326
                UpdateSet *us = NULL;
3,750✔
327

328
                /* First, let's find the newest version that's older than the boundary. */
329
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
20,824✔
330
                        Resource *rr;
17,416✔
331

332
                        assert(*tr);
17,416✔
333

334
                        if (flags == UPDATE_AVAILABLE)
17,416✔
335
                                rr = &(*tr)->source;
8,990✔
336
                        else {
337
                                assert(flags == UPDATE_INSTALLED);
8,426✔
338
                                rr = &(*tr)->target;
8,426✔
339
                        }
340

341
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
49,394✔
342
                                Instance *i = *inst; /* Sorted newest-to-oldest */
45,170✔
343

344
                                assert(i);
45,170✔
345

346
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
45,170✔
347
                                        continue; /* Not older than the boundary */
31,978✔
348

349
                                if (cursor && strverscmp(i->metadata.version, cursor) <= 0)
13,192✔
350
                                        break; /* Not newer than the cursor. The same will be true for all
351
                                                * subsequent instances (due to sorting) so let's skip to the
352
                                                * next transfer. */
353

354
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
2,952✔
355
                                        return log_oom();
×
356

357
                                break; /* All subsequent instances will be older than this one */
358
                        }
359

360
                        if (flags == UPDATE_AVAILABLE && !cursor)
17,416✔
361
                                break; /* This transfer didn't have a version older than the boundary,
362
                                        * so any older-than-boundary version that might exist in a different
363
                                        * transfer must always be incomplete. For reasons described below,
364
                                        * we don't include incomplete versions for AVAILABLE updates. So we
365
                                        * are completely done looking. */
366
                }
367

368
                if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
3,750✔
369
                        break;
370

371
                cursor_instances = new0(Instance*, c->n_transfers);
2,952✔
372
                if (!cursor_instances)
2,952✔
373
                        return log_oom();
×
374

375
                /* Now let's find all the instances that match the version of the cursor, if we have them */
376
                for (size_t k = 0; k < c->n_transfers; k++) {
17,448✔
377
                        Transfer *t = c->transfers[k];
14,752✔
378
                        Instance *match = NULL;
14,752✔
379

380
                        assert(t);
14,752✔
381

382
                        if (flags == UPDATE_AVAILABLE) {
14,752✔
383
                                match = resource_find_instance(&t->source, cursor);
8,622✔
384
                                if (!match) {
8,622✔
385
                                        /* When we're looking for updates to download, we don't offer
386
                                         * incomplete versions at all. The server wants to send us an update
387
                                         * with parts of the OS missing. For robustness sake, let's not do
388
                                         * that. */
389
                                        skip = true;
390
                                        break;
391
                                }
392
                        } else {
393
                                assert(flags == UPDATE_INSTALLED);
6,130✔
394

395
                                match = resource_find_instance(&t->target, cursor);
6,130✔
396
                                if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING)))
6,130✔
397
                                        /* When we're looking for installed versions, let's be robust and treat
398
                                         * an incomplete installation as an installation. Otherwise, there are
399
                                         * situations that can lead to sysupdate wiping the currently booted OS.
400
                                         * See https://github.com/systemd/systemd/issues/33339 */
401
                                        extra_flags |= UPDATE_INCOMPLETE;
1,610✔
402
                        }
403

404
                        cursor_instances[k] = match;
14,496✔
405

406
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
14,496✔
407
                                extra_flags |= UPDATE_OBSOLETE;
×
408

409
                        if (strv_contains(t->protected_versions, cursor))
14,496✔
410
                                extra_flags |= UPDATE_PROTECTED;
×
411

412
                        /* Partial or pending updates by definition are not incomplete, they’re
413
                         * partial/pending instead */
414
                        if (match && match->is_partial)
14,496✔
415
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
×
416

417
                        if (match && match->is_pending)
12,886✔
418
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
226✔
419
                }
420

421
                r = free_and_strdup_warn(&boundary, cursor);
2,952✔
422
                if (r < 0)
2,952✔
423
                        return r;
424

425
                if (skip)
2,952✔
426
                        continue;
256✔
427

428
                /* See if we already have this update set in our table */
429
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
6,762✔
430
                        UpdateSet *u = *update_set;
4,958✔
431

432
                        if (strverscmp_improved(u->version, cursor) != 0)
4,958✔
433
                                continue;
4,066✔
434

435
                        /* Merge in what we've learned and continue onto the next version */
436

437
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
892✔
438
                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
568✔
439
                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
568✔
440
                                assert(u->n_instances == c->n_transfers);
338✔
441

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

445
                                for (size_t j = 0; j < u->n_instances; j++) {
2,098✔
446
                                        if (!u->instances[j])
1,760✔
447
                                                u->instances[j] = cursor_instances[j];
1,148✔
448

449
                                        /* Make sure that the list is full if the update is AVAILABLE */
450
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
1,760✔
451
                                }
452
                        }
453

454
                        u->flags |= flags | extra_flags;
892✔
455

456
                        /* If this is the newest installed version, that is incomplete and just became marked
457
                         * as available, and if there is no other candidate available, we promote this to be
458
                         * the candidate. Ignore partial or pending status on the update set. */
459
                        if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
892✔
460
                            !c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
32✔
461
                                c->candidate = u;
32✔
462

463
                        skip = true;
892✔
464
                        newest_found = true;
892✔
465
                        break;
892✔
466
                }
467

468
                if (skip)
2,696✔
469
                        continue;
892✔
470

471
                /* Doesn't exist yet, let's add it */
472
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
1,804✔
473
                        return log_oom();
×
474

475
                us = new(UpdateSet, 1);
1,804✔
476
                if (!us)
1,804✔
477
                        return log_oom();
×
478

479
                *us = (UpdateSet) {
1,804✔
480
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
1,804✔
481
                        .version = TAKE_PTR(cursor),
1,804✔
482
                        .instances = TAKE_PTR(cursor_instances),
1,804✔
483
                        .n_instances = c->n_transfers,
1,804✔
484
                };
485

486
                c->update_sets[c->n_update_sets++] = us;
1,804✔
487

488
                newest_found = true;
1,804✔
489

490
                /* Remember which one is the newest installed */
491
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
1,804✔
492
                        c->newest_installed = us;
438✔
493

494
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
495
                 * It may be partial or pending. */
496
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
1,804✔
497
                        c->candidate = us;
112✔
498
        }
499

500
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
501
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
798✔
502
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
682✔
503
                c->candidate = NULL;
14✔
504

505
        /* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */
506
        if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) &&
798✔
507
            !c->candidate)
64✔
508
                c->candidate = c->newest_installed;
64✔
509

510
        return 0;
511
}
512

513
static int context_discover_update_sets(Context *c) {
456✔
514
        int r;
456✔
515

516
        assert(c);
456✔
517

518
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
912✔
519

520
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
456✔
521
        if (r < 0)
456✔
522
                return r;
523

524
        if (!arg_offline) {
456✔
525
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
684✔
526

527
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
342✔
528
                if (r < 0)
342✔
529
                        return r;
530
        }
531

532
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
456✔
533
        return 0;
456✔
534
}
535

536
static int context_show_table(Context *c) {
×
537
        _cleanup_(table_unrefp) Table *t = NULL;
×
538
        int r;
×
539

540
        assert(c);
×
541

542
        t = table_new("", "version", "installed", "available", "assessment");
×
543
        if (!t)
×
544
                return log_oom();
×
545

546
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
547
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
548
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
549

550
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
551
                UpdateSet *us = *update_set;
×
552
                const char *color;
×
553

554
                color = update_set_flags_to_color(us->flags);
×
555

556
                r = table_add_many(t,
×
557
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
558
                                   TABLE_SET_COLOR, color,
559
                                   TABLE_STRING,    us->version,
560
                                   TABLE_SET_COLOR, color,
561
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
562
                                   TABLE_SET_COLOR, color,
563
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
564
                                   TABLE_SET_COLOR, color,
565
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
566
                                   TABLE_SET_COLOR, color);
567
                if (r < 0)
×
568
                        return table_log_add_error(r);
×
569
        }
570

571
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
572
}
573

574
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
88✔
575
        assert(c);
88✔
576
        assert(version);
88✔
577

578
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
168✔
579
                if (streq((*update_set)->version, version))
168✔
580
                        return *update_set;
581

582
        return NULL;
583
}
584

585
static int context_show_version(Context *c, const char *version) {
88✔
586
        bool show_fs_columns = false, show_partition_columns = false,
88✔
587
                have_fs_attributes = false, have_partition_attributes = false,
88✔
588
                have_size = false, have_tries = false, have_no_auto = false,
88✔
589
                have_read_only = false, have_growfs = false, have_sha256 = false;
88✔
590
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
88✔
591
        _cleanup_(table_unrefp) Table *t = NULL;
88✔
592
        _cleanup_strv_free_ char **changelog_urls = NULL;
88✔
593
        UpdateSet *us;
88✔
594
        int r;
88✔
595

596
        assert(c);
88✔
597
        assert(version);
88✔
598

599
        us = context_update_set_by_version(c, version);
88✔
600
        if (!us)
88✔
601
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
602

603
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
88✔
604
                pager_open(arg_pager_flags);
40✔
605

606
        if (!sd_json_format_enabled(arg_json_format_flags))
88✔
607
                printf("%s%s%s Version: %s\n"
320✔
608
                       "    State: %s%s%s\n"
609
                       "Installed: %s%s%s%s\n"
610
                       "Available: %s%s\n"
611
                       "Protected: %s%s%s\n"
612
                       " Obsolete: %s%s%s\n\n",
613
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
40✔
614
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
40✔
615
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
40✔
616
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
80✔
617
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
80✔
618
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
40✔
619
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
40✔
620

621
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
88✔
622
        if (!t)
88✔
623
                return log_oom();
×
624

625
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
88✔
626
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
88✔
627
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
88✔
628
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
88✔
629
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
88✔
630
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
88✔
631
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
88✔
632

633
        /* Starting in v257, these fields would be automatically formatted with underscores. This would have
634
         * been a breaking change, so to avoid that let's hard-code their original names. */
635
        (void) table_set_json_field_name(t, 7, "tries-done");
88✔
636
        (void) table_set_json_field_name(t, 8, "tries-left");
88✔
637

638
        /* Determine if the target will make use of partition/fs attributes for any of the transfers */
639
        FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
544✔
640
                Transfer *tr = *transfer;
456✔
641

642
                if (tr->target.type == RESOURCE_PARTITION)
456✔
643
                        show_partition_columns = true;
176✔
644
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
456✔
645
                        show_fs_columns = true;
646

647
                STRV_FOREACH(changelog, tr->changelog) {
456✔
648
                        assert(*changelog);
×
649

650
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
651
                        if (!changelog_url)
×
652
                                return log_oom();
×
653

654
                        /* Avoid duplicates */
655
                        if (strv_contains(changelog_urls, changelog_url))
×
656
                                continue;
×
657

658
                        /* changelog_urls takes ownership of expanded changelog_url */
659
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
660
                        if (r < 0)
×
661
                                return log_oom();
×
662
                }
663
        }
664

665
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
544✔
666
                Instance *i = *inst;
456✔
667

668
                if (!i) {
456✔
669
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
16✔
670
                        continue;
16✔
671
                }
672

673
                r = table_add_many(t,
440✔
674
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
675
                                   TABLE_PATH, i->path);
676
                if (r < 0)
440✔
677
                        return table_log_add_error(r);
×
678

679
                if (i->metadata.partition_uuid_set) {
440✔
680
                        have_partition_attributes = true;
128✔
681
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
128✔
682
                } else
683
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
312✔
684
                if (r < 0)
440✔
685
                        return table_log_add_error(r);
×
686

687
                if (i->metadata.partition_flags_set) {
440✔
688
                        have_partition_attributes = true;
128✔
689
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
128✔
690
                } else
691
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
312✔
692
                if (r < 0)
440✔
693
                        return table_log_add_error(r);
×
694

695
                if (i->metadata.mtime != USEC_INFINITY) {
440✔
696
                        have_fs_attributes = true;
312✔
697
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
312✔
698
                } else
699
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
128✔
700
                if (r < 0)
440✔
701
                        return table_log_add_error(r);
×
702

703
                if (i->metadata.mode != MODE_INVALID) {
440✔
704
                        have_fs_attributes = true;
312✔
705
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
312✔
706
                } else
707
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
128✔
708
                if (r < 0)
440✔
709
                        return table_log_add_error(r);
×
710

711
                if (i->metadata.size != UINT64_MAX) {
440✔
712
                        have_size = true;
×
713
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
714
                } else
715
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
440✔
716
                if (r < 0)
440✔
717
                        return table_log_add_error(r);
×
718

719
                if (i->metadata.tries_done != UINT64_MAX) {
440✔
720
                        have_tries = true;
64✔
721
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
64✔
722
                } else
723
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
376✔
724
                if (r < 0)
440✔
725
                        return table_log_add_error(r);
×
726

727
                if (i->metadata.tries_left != UINT64_MAX) {
440✔
728
                        have_tries = true;
64✔
729
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
64✔
730
                } else
731
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
376✔
732
                if (r < 0)
440✔
733
                        return table_log_add_error(r);
×
734

735
                if (i->metadata.no_auto >= 0) {
440✔
736
                        bool b;
×
737

738
                        have_no_auto = true;
×
739
                        b = i->metadata.no_auto;
×
740
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
741
                } else
742
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
440✔
743
                if (r < 0)
440✔
744
                        return table_log_add_error(r);
×
745
                if (i->metadata.read_only >= 0) {
440✔
746
                        bool b;
128✔
747

748
                        have_read_only = true;
128✔
749
                        b = i->metadata.read_only;
128✔
750
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
128✔
751
                } else
752
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
312✔
753
                if (r < 0)
440✔
754
                        return table_log_add_error(r);
×
755

756
                if (i->metadata.growfs >= 0) {
440✔
757
                        bool b;
×
758

759
                        have_growfs = true;
×
760
                        b = i->metadata.growfs;
×
761
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
762
                } else
763
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
440✔
764
                if (r < 0)
440✔
765
                        return table_log_add_error(r);
×
766

767
                if (i->metadata.sha256sum_set) {
440✔
768
                        _cleanup_free_ char *formatted = NULL;
×
769

770
                        have_sha256 = true;
×
771

772
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
×
773
                        if (!formatted)
×
774
                                return log_oom();
×
775

776
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
×
777
                } else
778
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
440✔
779
                if (r < 0)
440✔
780
                        return table_log_add_error(r);
×
781
        }
782

783
        /* Hide the fs/partition columns if we don't have any data to show there */
784
        if (!have_fs_attributes)
88✔
785
                show_fs_columns = false;
×
786
        if (!have_partition_attributes)
88✔
787
                show_partition_columns = false;
788

789
        if (!show_partition_columns)
64✔
790
                (void) table_hide_column_from_display(t, 2, 3);
24✔
791
        if (!show_fs_columns)
88✔
792
                (void) table_hide_column_from_display(t, 4, 5);
×
793
        if (!have_size)
88✔
794
                (void) table_hide_column_from_display(t, 6);
88✔
795
        if (!have_tries)
88✔
796
                (void) table_hide_column_from_display(t, 7, 8);
24✔
797
        if (!have_no_auto)
88✔
798
                (void) table_hide_column_from_display(t, 9);
88✔
799
        if (!have_read_only)
88✔
800
                (void) table_hide_column_from_display(t, 10);
24✔
801
        if (!have_growfs)
88✔
802
                (void) table_hide_column_from_display(t, 11);
88✔
803
        if (!have_sha256)
88✔
804
                (void) table_hide_column_from_display(t, 12);
88✔
805

806
        if (!sd_json_format_enabled(arg_json_format_flags)) {
88✔
807
                printf("%s%s%s Version: %s\n"
360✔
808
                       "    State: %s%s%s\n"
809
                       "Installed: %s%s%s%s%s%s%s\n"
810
                       "Available: %s%s\n"
811
                       "Protected: %s%s%s\n"
812
                       " Obsolete: %s%s%s\n",
813
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
40✔
814
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
40✔
815
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
40✔
816
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
56✔
817
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
80✔
818
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
80✔
819
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
40✔
820
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
40✔
821

822
                STRV_FOREACH(url, changelog_urls) {
40✔
823
                        _cleanup_free_ char *changelog_link = NULL;
×
824
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
825
                        if (r < 0)
×
826
                                return log_oom();
×
827
                        printf("ChangeLog: %s\n", changelog_link);
×
828
                }
829
                printf("\n");
40✔
830

831
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
40✔
832
        } else {
833
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
48✔
834

835
                r = table_to_json(t, &t_json);
48✔
836
                if (r < 0)
48✔
837
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
838

839
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
48✔
840
                                          SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
841
                                          SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
842
                                          SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
843
                                          SD_JSON_BUILD_PAIR_BOOLEAN("partial", FLAGS_SET(us->flags, UPDATE_PARTIAL)),
844
                                          SD_JSON_BUILD_PAIR_BOOLEAN("pending", FLAGS_SET(us->flags, UPDATE_PENDING)),
845
                                          SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
846
                                          SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
847
                                          SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
848
                                          SD_JSON_BUILD_PAIR_STRV("changelogUrls", changelog_urls),
849
                                          SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
850
                if (r < 0)
48✔
851
                        return log_error_errno(r, "Failed to create JSON: %m");
×
852

853
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
48✔
854
                if (r < 0)
48✔
855
                        return log_error_errno(r, "Failed to print JSON: %m");
×
856

857
                return 0;
858
        }
859
}
860

861
static int context_vacuum(
80✔
862
                Context *c,
863
                uint64_t space,
864
                const char *extra_protected_version) {
865

866
        size_t disabled_count = 0;
80✔
867
        int r, count = 0;
80✔
868

869
        assert(c);
80✔
870

871
        if (space == 0)
80✔
872
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
16✔
873
        else
874
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
144✔
875

876
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
488✔
877
                Transfer *t = *tr;
408✔
878

879
                /* Don't bother clearing out space if we're not going to be downloading anything */
880
                if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
408✔
881
                        continue;
72✔
882

883
                r = transfer_vacuum(t, space, extra_protected_version);
336✔
884
                if (r < 0)
336✔
885
                        return r;
886

887
                count = MAX(count, r);
336✔
888
        }
889

890
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
152✔
891
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
72✔
892
                if (r < 0)
72✔
893
                        return r;
894
                if (r > 0)
72✔
895
                        disabled_count++;
8✔
896
        }
897

898
        if (!sd_json_format_enabled(arg_json_format_flags)) {
80✔
899
                if (count > 0 && disabled_count > 0)
58✔
900
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
901
                else if (count > 0)
58✔
902
                        log_info("Removed %i instances.", count);
24✔
903
                else if (disabled_count > 0)
34✔
904
                        log_info("Removed %zu disabled transfers.", disabled_count);
8✔
905
                else
906
                        log_info("Found nothing to remove.");
26✔
907
        } else {
908
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
22✔
909

910
                r = sd_json_buildo(&json,
22✔
911
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
912
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
913
                if (r < 0)
22✔
914
                        return log_error_errno(r, "Failed to create JSON: %m");
×
915

916
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
22✔
917
                if (r < 0)
22✔
918
                        return log_error_errno(r, "Failed to print JSON: %m");
×
919
        }
920

921
        return 0;
922
}
923

924
static int context_make_offline(Context **ret, const char *node, bool requires_enabled_transfers) {
488✔
925
        _cleanup_(context_freep) Context* context = NULL;
488✔
926
        int r;
488✔
927

928
        assert(ret);
488✔
929

930
        /* Allocates a context object and initializes everything we can initialize offline, i.e. without
931
         * checking on the update source (i.e. the Internet) what versions are available */
932

933
        context = context_new();
488✔
934
        if (!context)
488✔
935
                return log_oom();
×
936

937
        r = context_read_definitions(context, node, requires_enabled_transfers);
488✔
938
        if (r < 0)
488✔
939
                return r;
940

941
        r = context_load_installed_instances(context);
488✔
942
        if (r < 0)
488✔
943
                return r;
944

945
        *ret = TAKE_PTR(context);
488✔
946
        return 0;
488✔
947
}
948

949
static int context_make_online(Context **ret, const char *node) {
464✔
950
        _cleanup_(context_freep) Context* context = NULL;
464✔
951
        int r;
464✔
952

953
        assert(ret);
464✔
954

955
        /* Like context_make_offline(), but also communicates with the update source looking for new
956
         * versions (as long as --offline is not specified on the command line). */
957

958
        r = context_make_offline(&context, node, /* requires_enabled_transfers= */ true);
464✔
959
        if (r < 0)
464✔
960
                return r;
961

962
        if (!arg_offline) {
464✔
963
                r = context_load_available_instances(context);
350✔
964
                if (r < 0)
350✔
965
                        return r;
966
        }
967

968
        r = context_discover_update_sets(context);
456✔
969
        if (r < 0)
456✔
970
                return r;
971

972
        *ret = TAKE_PTR(context);
456✔
973
        return 0;
456✔
974
}
975

976
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
251✔
977
        const Context *c = ASSERT_PTR(t->context);
251✔
978
        size_t i, n = c->n_transfers;
251✔
979
        uint64_t base, scaled;
251✔
980
        unsigned overall;
251✔
981

982
        for (i = 0; i < n; i++)
706✔
983
                if (c->transfers[i] == t)
706✔
984
                        break;
985
        assert(i < n); /* We should have found the index */
251✔
986

987
        base = (100 * 100 * i) / n;
251✔
988
        scaled = (100 * percentage) / n;
251✔
989
        overall = (unsigned) ((base + scaled) / 100);
251✔
990
        assert(overall <= 100);
251✔
991

992
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
251✔
993
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
502✔
994
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
995
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
996
                                              "STATUS=Updating to '%s' (%u%% complete).",
997
                                              overall, n - i, i, inst->metadata.version, overall);
251✔
998
}
999

1000
static int context_process_partial_and_pending(Context *c, const char *version);
1001

1002
static int context_acquire(
110✔
1003
                Context *c,
1004
                const char *version) {
1005

1006
        UpdateSet *us = NULL;
110✔
1007
        int r;
110✔
1008

1009
        assert(c);
110✔
1010

1011
        if (version) {
110✔
1012
                us = context_update_set_by_version(c, version);
×
1013
                if (!us)
×
1014
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
110✔
1015
        } else {
1016
                if (!c->candidate) {
110✔
1017
                        log_info("No update needed.");
24✔
1018

1019
                        return 0;
24✔
1020
                }
1021

1022
                us = c->candidate;
1023
        }
1024

1025
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
86✔
1026
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
16✔
1027
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
70✔
1028
                log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
×
1029

1030
                return 0;
×
1031
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
70✔
1032
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
14✔
1033

1034
                return context_process_partial_and_pending(c, version);
14✔
1035
        } else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
56✔
1036
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1037

1038
                return 0;
×
1039
        }
1040

1041
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
72✔
1042
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1043
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
72✔
1044
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1045

1046
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
72✔
1047
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1048
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
72✔
1049
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1050

1051
        log_info("Selected update '%s' for install.", us->version);
72✔
1052

1053
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
144✔
1054
        if (!metadata)
72✔
1055
                return log_oom();
×
1056

1057
        /* Compute up the temporary paths before vacuuming so we don't vacuum anything if we fail to compute
1058
         * any paths because of failed validations (e.g. exceeding the gpt partition label size). */
1059
        for (size_t i = 0; i < c->n_transfers; i++) {
440✔
1060
                Instance *inst = us->instances[i];
368✔
1061
                Transfer *t = c->transfers[i];
368✔
1062

1063
                assert(inst);
368✔
1064

1065
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
368✔
1066
                if (r < 0)
368✔
1067
                        return r;
1068
        }
1069

1070
        (void) sd_notifyf(/* unset_environment= */ false,
72✔
1071
                          "READY=1\n"
1072
                          "X_SYSUPDATE_VERSION=%s\n"
1073
                          "STATUS=Making room for '%s'.", us->version, us->version);
1074

1075
        /* Let's make some room. We make sure for each transfer we have one free space to fill. While
1076
         * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
1077
         * download succeeded already, in which case we shouldn't remove it just to acquire it again */
1078
        r = context_vacuum(
144✔
1079
                        c,
1080
                        /* space= */ 1,
1081
                        /* extra_protected_version= */ us->version);
72✔
1082
        if (r < 0)
72✔
1083
                return r;
1084

1085
        if (arg_sync)
72✔
1086
                sync();
72✔
1087

1088
        (void) sd_notifyf(/* unset_environment= */ false,
72✔
1089
                          "STATUS=Updating to '%s'.", us->version);
1090

1091
        /* There should now be one instance picked for each transfer, and the order is the same */
1092
        assert(us->n_instances == c->n_transfers);
72✔
1093

1094
        for (size_t i = 0; i < c->n_transfers; i++) {
440✔
1095
                Instance *inst = us->instances[i];
368✔
1096
                Transfer *t = c->transfers[i];
368✔
1097

1098
                assert(inst); /* ditto */
368✔
1099

1100
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
368✔
1101
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
72✔
1102
                        continue;
72✔
1103
                }
1104

1105
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
296✔
1106
                if (r < 0)
296✔
1107
                        return r;
1108
        }
1109

1110
        if (arg_sync)
72✔
1111
                sync();
72✔
1112

1113
        return 1;
1114
}
1115

1116
/* Check to see if we have an update set acquired and pending installation. */
1117
static int context_process_partial_and_pending(
50✔
1118
                Context *c,
1119
                const char *version) {
1120

1121
        UpdateSet *us = NULL;
50✔
1122
        int r;
50✔
1123

1124
        assert(c);
50✔
1125

1126
        if (version) {
50✔
1127
                us = context_update_set_by_version(c, version);
×
1128
                if (!us)
×
1129
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1130
        } else {
1131
                if (!c->candidate) {
50✔
1132
                        log_info("No update needed.");
×
1133

1134
                        return 0;
×
1135
                }
1136

1137
                us = c->candidate;
1138
        }
1139

1140
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
50✔
1141
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
×
1142
        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
50✔
1143
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1144

1145
                return 0;
×
1146
        }
1147

1148
        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
50✔
1149
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
×
1150
        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
50✔
1151
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
×
1152

1153
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
50✔
1154
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1155

1156
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
50✔
1157
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1158
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
50✔
1159
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1160

1161
        log_info("Selected update '%s' for install.", us->version);
50✔
1162

1163
        /* There should now be one instance picked for each transfer, and the order is the same */
1164
        assert(us->n_instances == c->n_transfers);
50✔
1165

1166
        for (size_t i = 0; i < c->n_transfers; i++) {
306✔
1167
                Instance *inst = us->instances[i];
256✔
1168
                Transfer *t = c->transfers[i];
256✔
1169

1170
                assert(inst);
256✔
1171

1172
                r = transfer_process_partial_and_pending_instance(t, inst);
256✔
1173
                if (r < 0)
256✔
1174
                        return r;
1175
        }
1176

1177
        return 1;
1178
}
1179

1180
static int context_install(
72✔
1181
                Context *c,
1182
                const char *version,
1183
                UpdateSet **ret_applied) {
1184

1185
        UpdateSet *us = NULL;
72✔
1186
        int r;
72✔
1187

1188
        assert(c);
72✔
1189

1190
        if (version) {
72✔
1191
                us = context_update_set_by_version(c, version);
×
1192
                if (!us)
×
1193
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1194
        } else {
1195
                if (!c->candidate) {
72✔
1196
                        log_info("No update needed.");
×
1197

1198
                        return 0;
×
1199
                }
1200

1201
                us = c->candidate;
1202
        }
1203

1204
        (void) sd_notifyf(/* unset_environment=*/ false,
72✔
1205
                          "READY=1\n"
1206
                          "X_SYSUPDATE_VERSION=%s\n"
1207
                          "STATUS=Installing '%s'.", us->version, us->version);
1208

1209
        for (size_t i = 0; i < c->n_transfers; i++) {
440✔
1210
                Instance *inst = us->instances[i];
368✔
1211
                Transfer *t = c->transfers[i];
368✔
1212

1213
                if (inst->resource == &t->target &&
368✔
1214
                    !inst->is_pending)
298✔
1215
                        continue;
72✔
1216

1217
                r = transfer_install_instance(t, inst, arg_root);
296✔
1218
                if (r < 0)
296✔
1219
                        return r;
1220
        }
1221

1222
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
72✔
1223

1224
        (void) sd_notifyf(/* unset_environment= */ false,
72✔
1225
                          "STATUS=Installed '%s'.", us->version);
1226

1227
        if (ret_applied)
72✔
1228
                *ret_applied = us;
72✔
1229

1230
        return 1;
1231
}
1232

1233
static int process_image(
564✔
1234
                bool ro,
1235
                char **ret_mounted_dir,
1236
                LoopDevice **ret_loop_device) {
1237

1238
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
564✔
1239
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
564✔
1240
        int r;
564✔
1241

1242
        assert(ret_mounted_dir);
564✔
1243
        assert(ret_loop_device);
564✔
1244

1245
        if (!arg_image)
564✔
1246
                return 0;
1247

1248
        assert(!arg_root);
×
1249

1250
        r = mount_image_privately_interactively(
×
1251
                        arg_image,
1252
                        arg_image_policy,
1253
                        (ro ? DISSECT_IMAGE_READ_ONLY : 0) |
1254
                        DISSECT_IMAGE_FSCK |
1255
                        DISSECT_IMAGE_MKDIR |
1256
                        DISSECT_IMAGE_GROWFS |
1257
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1258
                        DISSECT_IMAGE_USR_NO_ROOT |
1259
                        DISSECT_IMAGE_GENERIC_ROOT |
1260
                        DISSECT_IMAGE_REQUIRE_ROOT |
1261
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1262
                        &mounted_dir,
1263
                        /* ret_dir_fd= */ NULL,
1264
                        &loop_device);
1265
        if (r < 0)
×
1266
                return r;
1267

1268
        arg_root = strdup(mounted_dir);
×
1269
        if (!arg_root)
×
1270
                return log_oom();
×
1271

1272
        *ret_mounted_dir = TAKE_PTR(mounted_dir);
×
1273
        *ret_loop_device = TAKE_PTR(loop_device);
×
1274

1275
        return 0;
×
1276
}
1277

1278
VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT,
1279
     "Show installed and available versions");
1280
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
134✔
1281
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
134✔
1282
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
134✔
1283
        _cleanup_(context_freep) Context* context = NULL;
134✔
1284
        _cleanup_strv_free_ char **appstream_urls = NULL;
134✔
1285
        const char *version;
134✔
1286
        int r;
134✔
1287

1288
        assert(argc <= 2);
134✔
1289
        version = argc >= 2 ? argv[1] : NULL;
134✔
1290

1291
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
134✔
1292
        if (r < 0)
134✔
1293
                return r;
1294

1295
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
134✔
1296
        if (r < 0)
134✔
1297
                return r;
1298

1299
        if (version)
134✔
1300
                return context_show_version(context, version);
88✔
1301
        else if (!sd_json_format_enabled(arg_json_format_flags))
46✔
1302
                return context_show_table(context);
×
1303
        else {
1304
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
46✔
1305
                _cleanup_strv_free_ char **versions = NULL;
46✔
1306
                const char *current = NULL;
46✔
1307
                bool current_is_pending = false;
46✔
1308

1309
                FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
188✔
1310
                        UpdateSet *us = *update_set;
142✔
1311

1312
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
142✔
1313
                            FLAGS_SET(us->flags, UPDATE_NEWEST)) {
126✔
1314
                                current = us->version;
44✔
1315
                                current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
44✔
1316
                        }
1317

1318
                        r = strv_extend(&versions, us->version);
142✔
1319
                        if (r < 0)
142✔
1320
                                return log_oom();
×
1321
                }
1322

1323
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
278✔
1324
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
232✔
1325
                                /* Avoid duplicates */
1326
                                if (strv_contains(appstream_urls, *appstream_url))
×
1327
                                        continue;
×
1328

1329
                                r = strv_extend(&appstream_urls, *appstream_url);
×
1330
                                if (r < 0)
×
1331
                                        return log_oom();
×
1332
                        }
1333

1334
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current),
92✔
1335
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1336
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1337
                if (r < 0)
46✔
1338
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1339

1340
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
46✔
1341
                if (r < 0)
46✔
1342
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1343

1344
                return 0;
1345
        }
1346
}
1347

1348
VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0,
1349
     "Show optional features");
1350
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
16✔
1351
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
16✔
1352
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
16✔
1353
        _cleanup_(context_freep) Context* context = NULL;
16✔
1354
        _cleanup_(table_unrefp) Table *table = NULL;
16✔
1355
        const char *feature_id;
16✔
1356
        Feature *f;
16✔
1357
        int r;
16✔
1358

1359
        assert(argc <= 2);
16✔
1360
        feature_id = argc >= 2 ? argv[1] : NULL;
16✔
1361

1362
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
16✔
1363
        if (r < 0)
16✔
1364
                return r;
1365

1366
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
16✔
1367
        if (r < 0)
16✔
1368
                return r;
1369

1370
        if (feature_id) {
16✔
1371
                _cleanup_strv_free_ char **transfers = NULL;
8✔
1372

1373
                f = hashmap_get(context->features, feature_id);
8✔
1374
                if (!f)
8✔
1375
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1376
                                               "Optional feature not found: %s",
1377
                                               feature_id);
1378

1379
                table = table_new_vertical();
8✔
1380
                if (!table)
8✔
1381
                        return log_oom();
×
1382

1383
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
48✔
1384
                        Transfer *t = *tr;
40✔
1385

1386
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
40✔
1387
                                continue;
40✔
1388

1389
                        r = strv_extend(&transfers, t->id);
×
1390
                        if (r < 0)
×
1391
                                return log_oom();
×
1392
                }
1393

1394
                FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) {
16✔
1395
                        Transfer *t = *tr;
8✔
1396

1397
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
8✔
1398
                                continue;
×
1399

1400
                        r = strv_extend(&transfers, t->id);
8✔
1401
                        if (r < 0)
8✔
1402
                                return log_oom();
×
1403
                }
1404

1405
                r = table_add_many(table,
8✔
1406
                                   TABLE_FIELD, "Name",
1407
                                   TABLE_STRING, f->id,
1408
                                   TABLE_FIELD, "Enabled",
1409
                                   TABLE_BOOLEAN, f->enabled);
1410
                if (r < 0)
8✔
1411
                        return table_log_add_error(r);
×
1412

1413
                if (f->description) {
8✔
1414
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
8✔
1415
                        if (r < 0)
8✔
1416
                                return table_log_add_error(r);
×
1417
                }
1418

1419
                if (f->documentation) {
8✔
1420
                        r = table_add_many(table,
×
1421
                                           TABLE_FIELD, "Documentation",
1422
                                           TABLE_STRING, f->documentation,
1423
                                           TABLE_SET_URL, f->documentation);
1424
                        if (r < 0)
×
1425
                                return table_log_add_error(r);
×
1426
                }
1427

1428
                if (f->appstream) {
8✔
1429
                        r = table_add_many(table,
×
1430
                                           TABLE_FIELD, "AppStream",
1431
                                           TABLE_STRING, f->appstream,
1432
                                           TABLE_SET_URL, f->appstream);
1433
                        if (r < 0)
×
1434
                                return table_log_add_error(r);
×
1435
                }
1436

1437
                if (!strv_isempty(transfers)) {
8✔
1438
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
8✔
1439
                        if (r < 0)
8✔
1440
                                return table_log_add_error(r);
×
1441
                }
1442

1443
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
8✔
1444
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
8✔
1445
                table = table_new("", "feature", "description", "documentation");
8✔
1446
                if (!table)
8✔
1447
                        return log_oom();
×
1448

1449
                HASHMAP_FOREACH(f, context->features) {
16✔
1450
                        r = table_add_many(table,
8✔
1451
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1452
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
1453
                                           TABLE_STRING, f->id,
1454
                                           TABLE_STRING, f->description,
1455
                                           TABLE_STRING, f->documentation,
1456
                                           TABLE_SET_URL, f->documentation);
1457
                        if (r < 0)
8✔
1458
                                return table_log_add_error(r);
×
1459
                }
1460

1461
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
8✔
1462
        } else {
1463
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1464
                _cleanup_strv_free_ char **features = NULL;
×
1465

1466
                HASHMAP_FOREACH(f, context->features) {
×
1467
                        r = strv_extend(&features, f->id);
×
1468
                        if (r < 0)
×
1469
                                return log_oom();
×
1470
                }
1471

1472
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
1473
                if (r < 0)
×
1474
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1475

1476
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
×
1477
                if (r < 0)
×
1478
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1479
        }
1480

1481
        return 0;
×
1482
}
1483

1484
VERB_NOARG(verb_check_new, "check-new",
1485
           "Check if there's a new version available");
1486
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
176✔
1487
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
176✔
1488
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
176✔
1489
        _cleanup_(context_freep) Context* context = NULL;
176✔
1490
        int r;
176✔
1491

1492
        assert(argc <= 1);
176✔
1493

1494
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
176✔
1495
        if (r < 0)
176✔
1496
                return r;
1497

1498
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
176✔
1499
        if (r < 0)
176✔
1500
                return r;
1501

1502
        if (!sd_json_format_enabled(arg_json_format_flags)) {
176✔
1503
                if (!context->candidate) {
160✔
1504
                        log_debug("No candidate found.");
88✔
1505
                        return EXIT_FAILURE;
88✔
1506
                }
1507

1508
                puts(context->candidate->version);
72✔
1509
        } else {
1510
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
16✔
1511

1512
                if (context->candidate)
16✔
1513
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version));
×
1514
                else
1515
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
16✔
1516
                if (r < 0)
16✔
1517
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1518

1519
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
16✔
1520
                if (r < 0)
16✔
1521
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1522
        }
1523

1524
        return EXIT_SUCCESS;
1525
}
1526

1527
typedef enum {
1528
        UPDATE_ACTION_ACQUIRE = 1 << 0,
1529
        UPDATE_ACTION_INSTALL = 1 << 1,
1530
} UpdateActionFlags;
1531

1532
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
154✔
1533
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
154✔
1534
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
154✔
1535
        _cleanup_(context_freep) Context* context = NULL;
×
1536
        _cleanup_free_ char *booted_version = NULL;
154✔
1537
        UpdateSet *applied = NULL;
154✔
1538
        const char *version;
154✔
1539
        int r;
154✔
1540

1541
        assert(argc <= 2);
154✔
1542
        version = argc >= 2 ? argv[1] : NULL;
154✔
1543

1544
        if (arg_instances_max < 2)
154✔
1545
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1546
                                      "The --instances-max argument must be >= 2 while updating");
1547

1548
        if (arg_reboot) {
154✔
1549
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
1550

1551
                r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
1552
                if (r < 0)
×
1553
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1554
                if (!booted_version)
×
1555
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
1556
        }
1557

1558
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
154✔
1559
        if (r < 0)
154✔
1560
                return r;
1561

1562
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
154✔
1563
        if (r < 0)
154✔
1564
                return r;
1565

1566
        if (action_flags & UPDATE_ACTION_ACQUIRE)
146✔
1567
                r = context_acquire(context, version);
110✔
1568
        else
1569
                r = context_process_partial_and_pending(context, version);
36✔
1570
        if (r < 0)
146✔
1571
                return r;  /* error */
1572

1573
        if (action_flags & UPDATE_ACTION_INSTALL && r > 0)  /* update needed */
146✔
1574
                r = context_install(context, version, &applied);
72✔
1575
        if (r < 0)
146✔
1576
                return r;
1577

1578
        if (r > 0 && arg_reboot) {
146✔
1579
                assert(applied);
×
1580
                assert(booted_version);
×
1581

1582
                if (strverscmp_improved(applied->version, booted_version) > 0) {
×
1583
                        log_notice("Newly installed version is newer than booted version, rebooting.");
×
1584
                        return reboot_now();
×
1585
                }
1586

1587
                if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
1588
                    FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
1589
                        log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
1590
                        return reboot_now();
×
1591
                }
1592

1593
                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
1594
        }
1595

1596
        return 0;
1597
}
1598

1599
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
1600
     "Install new version now");
1601
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
104✔
1602
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
104✔
1603

1604
        if (!arg_offline)
104✔
1605
                flags |= UPDATE_ACTION_ACQUIRE;
68✔
1606

1607
        return verb_update_impl(argc, argv, flags);
104✔
1608
}
1609

1610
VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
1611
     "Acquire (download) new version now");
1612
static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
50✔
1613
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
50✔
1614
}
1615

1616
VERB_NOARG(verb_vacuum, "vacuum",
1617
           "Make room, by deleting old versions");
1618
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
8✔
1619
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
8✔
1620
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
8✔
1621
        _cleanup_(context_freep) Context* context = NULL;
8✔
1622
        int r;
8✔
1623

1624
        assert(argc <= 1);
8✔
1625

1626
        if (arg_instances_max < 1)
8✔
1627
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1628
                                      "The --instances-max argument must be >= 1 while vacuuming");
1629

1630
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
8✔
1631
        if (r < 0)
8✔
1632
                return r;
1633

1634
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
8✔
1635
        if (r < 0)
8✔
1636
                return r;
1637

1638
        return context_vacuum(context, 0, NULL);
8✔
1639
}
1640

1641
VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
1642
     "Report whether a newer version is installed than currently booted");
1643
VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
1644
     "Reboot if a newer version is installed than booted");
1645
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1646
        _cleanup_(context_freep) Context* context = NULL;
×
1647
        _cleanup_free_ char *booted_version = NULL;
×
1648
        int r;
×
1649

1650
        assert(argc == 1);
×
1651

1652
        if (arg_image || arg_root)
×
1653
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1654
                                       "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
1655

1656
        r = context_make_offline(&context, /* node= */ NULL, /* requires_enabled_transfers= */ true);
×
1657
        if (r < 0)
×
1658
                return r;
1659

1660
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
1661

1662
        r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
×
1663
        if (r < 0)
×
1664
                return r;
1665
        if (!context->newest_installed)
×
1666
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
1667

1668
        r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
1669
        if (r < 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable
×
1670
                    * if we see what the first argument is about */
1671
                return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1672
        if (!booted_version)
×
1673
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field.");
×
1674

1675
        r = strverscmp_improved(context->newest_installed->version, booted_version);
×
1676
        if (r > 0) {
×
1677
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
1678
                           context->newest_installed->version, booted_version,
1679
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
1680

1681
                if (streq(argv[0], "reboot"))
×
1682
                        return reboot_now();
×
1683

1684
                return EXIT_SUCCESS;
1685
        } else if (r == 0)
×
1686
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
1687
                         context->newest_installed->version, booted_version);
1688
        else
1689
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
1690
                            context->newest_installed->version, booted_version);
1691

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

1695
        return EXIT_SUCCESS;
1696
}
1697

1698
static int component_name_valid(const char *c) {
32✔
1699
        _cleanup_free_ char *j = NULL;
32✔
1700

1701
        /* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */
1702

1703
        if (isempty(c))
64✔
1704
                return false;
1705

1706
        if (string_has_cc(c, NULL))
32✔
1707
                return false;
1708

1709
        if (!utf8_is_valid(c))
32✔
1710
                return false;
1711

1712
        j = strjoin("sysupdate.", c, ".d");
32✔
1713
        if (!j)
32✔
1714
                return -ENOMEM;
1715

1716
        return filename_is_valid(j);
32✔
1717
}
1718

1719
VERB_NOARG(verb_components, "components",
1720
           "Show list of components");
1721
static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
76✔
1722
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
76✔
1723
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
76✔
1724
        _cleanup_set_free_ Set *names = NULL;
76✔
1725
        bool has_default_component = false;
76✔
1726
        int r;
76✔
1727

1728
        assert(argc <= 1);
76✔
1729

1730
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
76✔
1731
        if (r < 0)
76✔
1732
                return r;
1733

1734
        ConfFile **directories = NULL;
76✔
1735
        size_t n_directories = 0;
76✔
1736

1737
        CLEANUP_ARRAY(directories, n_directories, conf_file_free_array);
76✔
1738

1739
        r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY|CONF_FILES_WARN,
152✔
1740
                                      (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories);
76✔
1741
        if (r < 0)
76✔
1742
                return log_error_errno(r, "Failed to enumerate directories: %m");
×
1743

1744
        FOREACH_ARRAY(i, directories, n_directories) {
2,000✔
1745
                ConfFile *e = *i;
1,924✔
1746

1747
                if (streq(e->filename, "sysupdate.d")) {
1,924✔
1748
                        has_default_component = true;
76✔
1749
                        continue;
76✔
1750
                }
1751

1752
                const char *s = startswith(e->filename, "sysupdate.");
1,848✔
1753
                if (!s)
1,848✔
1754
                        continue;
1,824✔
1755

1756
                const char *a = endswith(s, ".d");
24✔
1757
                if (!a)
24✔
1758
                        continue;
×
1759

1760
                _cleanup_free_ char *n = strndup(s, a - s);
×
1761
                if (!n)
24✔
1762
                        return log_oom();
×
1763

1764
                r = component_name_valid(n);
24✔
1765
                if (r < 0)
24✔
1766
                        return log_error_errno(r, "Unable to validate component name '%s': %m", n);
×
1767
                if (r == 0)
24✔
1768
                        continue;
×
1769

1770
                r = set_ensure_put(&names, &string_hash_ops_free, n);
24✔
1771
                if (r < 0 && r != -EEXIST)
24✔
1772
                        return log_error_errno(r, "Failed to add component '%s' to set: %m", n);
×
1773
                TAKE_PTR(n);
1774
        }
1775

1776
        /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
1777
        _cleanup_free_ char **z = set_get_strv(names);
152✔
1778
        if (!z)
76✔
1779
                return log_oom();
×
1780

1781
        strv_sort(z);
76✔
1782

1783
        if (!sd_json_format_enabled(arg_json_format_flags)) {
76✔
1784
                if (!has_default_component && set_isempty(names)) {
×
1785
                        log_info("No components defined.");
×
1786
                        return 0;
×
1787
                }
1788

1789
                if (has_default_component)
×
1790
                        printf("%s<default>%s\n",
×
1791
                               ansi_highlight(), ansi_normal());
1792

1793
                STRV_FOREACH(i, z)
×
1794
                        puts(*i);
×
1795
        } else {
1796
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
76✔
1797

1798
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
76✔
1799
                                          SD_JSON_BUILD_PAIR_STRV("components", z));
1800
                if (r < 0)
76✔
1801
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1802

1803
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
76✔
1804
                if (r < 0)
76✔
1805
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1806
        }
1807

1808
        return 0;
1809
}
1810

1811
static int help(void) {
×
1812
        _cleanup_free_ char *link = NULL;
×
1813
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
1814
        int r;
×
1815

1816
        r = terminal_urlify_man("systemd-sysupdate", "8", &link);
×
1817
        if (r < 0)
×
1818
                return log_oom();
×
1819

1820
        r = verbs_get_help_table(&verbs);
×
1821
        if (r < 0)
×
1822
                return r;
1823

1824
        r = option_parser_get_help_table(&common_options);
×
1825
        if (r < 0)
×
1826
                return r;
1827

1828
        r = option_parser_get_help_table_group("Options", &options);
×
1829
        if (r < 0)
×
1830
                return r;
1831

1832
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
1833

1834
        printf("%s [OPTIONS...] [VERSION]\n"
×
1835
               "\n%sUpdate OS images.%s\n"
1836
               "\n%sCommands:%s\n",
1837
               program_invocation_short_name,
1838
               ansi_highlight(), ansi_normal(),
1839
               ansi_underline(), ansi_normal());
1840

1841
        r = table_print_or_warn(verbs);
×
1842
        if (r < 0)
×
1843
                return r;
1844

1845
        r = table_print_or_warn(common_options);
×
1846
        if (r < 0)
×
1847
                return r;
1848

1849
        printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal());
×
1850
        r = table_print_or_warn(options);
×
1851
        if (r < 0)
×
1852
                return r;
1853

1854
        printf("\nSee the %s for details.\n", link);
×
1855
        return 0;
1856
}
1857

1858
VERB_COMMON_HELP_HIDDEN(help);
×
1859

1860
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
564✔
1861
        assert(argc >= 0);
564✔
1862
        assert(argv);
564✔
1863
        assert(remaining_args);
564✔
1864

1865
        OptionParser state = { argc, argv };
564✔
1866
        const char *arg;
564✔
1867
        int r;
564✔
1868

1869
        FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
1,866✔
1870
                switch (c) {
738✔
1871

1872
                OPTION_COMMON_HELP:
×
1873
                        return help();
×
1874

1875
                OPTION_COMMON_VERSION:
×
1876
                        return version();
×
1877

1878
                OPTION_GROUP("Options"):
1879
                        break;
1880

1881
                OPTION('C', "component", "NAME",
8✔
1882
                       "Select component to update"):
1883
                        if (isempty(arg)) {
8✔
1884
                                arg_component = mfree(arg_component);
×
1885
                                break;
×
1886
                        }
1887

1888
                        r = component_name_valid(arg);
8✔
1889
                        if (r < 0)
8✔
1890
                                return log_error_errno(r, "Failed to determine if component name is valid: %m");
×
1891
                        if (r == 0)
8✔
1892
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", arg);
×
1893

1894
                        r = free_and_strdup_warn(&arg_component, arg);
8✔
1895
                        if (r < 0)
8✔
1896
                                return r;
1897

1898
                        break;
1899

1900
                OPTION_LONG("definitions", "DIR",
×
1901
                            "Find transfer definitions in specified directory"):
1902
                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_definitions);
×
1903
                        if (r < 0)
×
1904
                                return r;
1905
                        break;
1906

1907
                OPTION_LONG("root", "PATH",
×
1908
                            "Operate on an alternate filesystem root"):
1909
                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root);
×
1910
                        if (r < 0)
×
1911
                                return r;
1912
                        break;
1913

1914
                OPTION_LONG("image", "PATH",
×
1915
                            "Operate on disk image as filesystem root"):
1916
                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image);
×
1917
                        if (r < 0)
×
1918
                                return r;
1919
                        break;
1920

1921
                OPTION_LONG("image-policy", "POLICY",
×
1922
                            "Specify disk image dissection policy"):
1923
                        r = parse_image_policy_argument(arg, &arg_image_policy);
×
1924
                        if (r < 0)
×
1925
                                return r;
1926
                        break;
1927

1928
                OPTION_LONG("transfer-source", "PATH",
×
1929
                            "Specify the directory to transfer sources from"):
1930
                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_transfer_source);
×
1931
                        if (r < 0)
×
1932
                                return r;
1933

1934
                        break;
1935

1936
                OPTION('m', "instances-max", "INT",
×
1937
                       "How many instances to maintain"):
1938
                        r = safe_atou64(arg, &arg_instances_max);
×
1939
                        if (r < 0)
×
1940
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", arg);
×
1941

1942
                        break;
1943

1944
                OPTION_LONG("sync", "BOOL",
×
1945
                            "Controls whether to sync data to disk"):
1946
                        r = parse_boolean_argument("--sync=", arg, &arg_sync);
×
1947
                        if (r < 0)
×
1948
                                return r;
1949
                        break;
1950

1951
                OPTION_LONG("verify", "BOOL",
386✔
1952
                            "Force signature verification on or off"): {
1953
                        bool b;
386✔
1954

1955
                        r = parse_boolean_argument("--verify=", arg, &b);
386✔
1956
                        if (r < 0)
386✔
1957
                                return r;
×
1958

1959
                        arg_verify = b;
386✔
1960
                        break;
386✔
1961
                }
1962

1963
                OPTION_LONG("reboot", NULL,
×
1964
                            "Reboot after updating to newer version"):
1965
                        arg_reboot = true;
×
1966
                        break;
×
1967

1968
                OPTION_LONG("offline", NULL,
114✔
1969
                            "Do not fetch metadata from the network"):
1970
                        arg_offline = true;
114✔
1971
                        break;
114✔
1972

1973
                OPTION_COMMON_NO_PAGER:
×
1974
                        arg_pager_flags |= PAGER_DISABLE;
×
1975
                        break;
×
1976

1977
                OPTION_COMMON_NO_LEGEND:
×
1978
                        arg_legend = false;
×
1979
                        break;
×
1980

1981
                OPTION_COMMON_JSON:
230✔
1982
                        r = parse_json_argument(arg, &arg_json_format_flags);
230✔
1983
                        if (r <= 0)
230✔
1984
                                return r;
1985

1986
                        break;
1987
                }
1988

1989
        if (arg_image && arg_root)
564✔
1990
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
1991

1992
        if ((arg_image || arg_root) && arg_reboot)
564✔
1993
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
1994

1995
        if (arg_definitions && arg_component)
564✔
1996
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
1997

1998
        *remaining_args = option_parser_get_args(&state);
564✔
1999
        return 1;
564✔
2000
}
2001

2002
static int run(int argc, char *argv[]) {
564✔
2003
        int r;
564✔
2004

2005
        log_setup();
564✔
2006

2007
        char **args = NULL;
564✔
2008
        r = parse_argv(argc, argv, &args);
564✔
2009
        if (r <= 0)
564✔
2010
                return r;
564✔
2011

2012
        return dispatch_verb_with_args(args, NULL);
564✔
2013
}
2014

2015
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
564✔
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