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

systemd / systemd / 22007273413

13 Feb 2026 09:37PM UTC coverage: 72.743% (+0.3%) from 72.485%
22007273413

push

github

web-flow
test: do not fail when parsing PID that isn't thread-group leader (#40677)

```
TEST-02-UNITTESTS.sh[4382]: [  707.393188] test-cgroup-util[426]: Failed to open pidfd for pid 414: Invalid argument
TEST-02-UNITTESTS.sh[4382]: [  707.393193] test-cgroup-util[426]: src/test/test-cgroup-util.c:249: Assertion failed: Expected "r = proc_dir_read_pidref(d, &pid)" to succeed, but got error: -22/EINVAL
```

The kernel can return EINVAL on pidfd_open() when the selected PID is
not a thread group leader. Don't fail the test, as we are iterating on
everything, so this can seldomly happen.

3 of 4 new or added lines in 1 file covered. (75.0%)

6126 existing lines in 58 files now uncovered.

312809 of 430017 relevant lines covered (72.74%)

1147140.71 hits per line

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

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

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

6
#include "sd-daemon.h"
7

8
#include "build.h"
9
#include "conf-files.h"
10
#include "constants.h"
11
#include "dissect-image.h"
12
#include "format-table.h"
13
#include "glyph-util.h"
14
#include "hexdecoct.h"
15
#include "image-policy.h"
16
#include "loop-util.h"
17
#include "main-func.h"
18
#include "mount-util.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);
366✔
55
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
366✔
56
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
366✔
57
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
366✔
58
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
366✔
59
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
366✔
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) {
336✔
85
        if (!c)
336✔
86
                return NULL;
87

88
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
1,998✔
89
                transfer_free(*tr);
1,662✔
90
        free(c->transfers);
336✔
91

92
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
630✔
93
                transfer_free(*tr);
294✔
94
        free(c->disabled_transfers);
336✔
95

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

98
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
1,606✔
99
                update_set_free(*us);
1,270✔
100
        free(c->update_sets);
336✔
101

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

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

107
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
990✔
108

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

114
static void free_transfers(Transfer **array, size_t n) {
×
115
        FOREACH_ARRAY(t, array, n)
×
116
                transfer_free(*t);
×
117
        free(array);
×
118
}
×
119

120
static int read_definitions(
336✔
121
                Context *c,
122
                const char **dirs,
123
                const char *suffix,
124
                const char *node) {
125

126
        ConfFile **files = NULL;
336✔
127
        Transfer **transfers = NULL, **disabled = NULL;
336✔
128
        size_t n_files = 0, n_transfers = 0, n_disabled = 0;
336✔
129
        int r;
336✔
130

131
        CLEANUP_ARRAY(files, n_files, conf_file_free_many);
336✔
132
        CLEANUP_ARRAY(transfers, n_transfers, free_transfers);
336✔
133
        CLEANUP_ARRAY(disabled, n_disabled, free_transfers);
336✔
134

135
        assert(c);
336✔
136
        assert(dirs);
336✔
137
        assert(suffix);
336✔
138

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

145
        FOREACH_ARRAY(i, files, n_files) {
2,292✔
146
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
147
                Transfer **appended;
1,956✔
148
                ConfFile *e = *i;
1,956✔
149

150
                t = transfer_new(c);
1,956✔
151
                if (!t)
1,956✔
152
                        return log_oom();
×
153

154
                r = transfer_read_definition(t, e->result, dirs, c->features);
1,956✔
155
                if (r < 0)
1,956✔
156
                        return r;
157

158
                r = transfer_resolve_paths(t, arg_root, node);
1,956✔
159
                if (r < 0)
1,956✔
160
                        return r;
161

162
                if (t->enabled)
1,956✔
163
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
1,662✔
164
                else
165
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
294✔
166
                if (!appended)
1,956✔
167
                        return log_oom();
×
168
                TAKE_PTR(t);
1,956✔
169
        }
170

171
        c->transfers = TAKE_PTR(transfers);
336✔
172
        c->n_transfers = n_transfers;
336✔
173
        c->disabled_transfers = TAKE_PTR(disabled);
336✔
174
        c->n_disabled_transfers = n_disabled;
336✔
175
        return 0;
336✔
176
}
177

178
static int context_read_definitions(Context *c, const char* node, bool requires_enabled_transfers) {
336✔
179
        _cleanup_strv_free_ char **dirs = NULL;
336✔
180
        int r;
336✔
181

182
        assert(c);
336✔
183

184
        if (arg_definitions)
336✔
185
                dirs = strv_new(arg_definitions);
×
186
        else if (arg_component) {
336✔
187
                char **l = CONF_PATHS_STRV("");
12✔
188
                size_t i = 0;
12✔
189

190
                dirs = new0(char*, strv_length(l) + 1);
12✔
191
                if (!dirs)
12✔
192
                        return log_oom();
×
193

194
                STRV_FOREACH(dir, l) {
60✔
195
                        char *j;
48✔
196

197
                        j = strjoin(*dir, "sysupdate.", arg_component, ".d");
48✔
198
                        if (!j)
48✔
199
                                return log_oom();
×
200

201
                        dirs[i++] = j;
48✔
202
                }
203
        } else
204
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
324✔
205
        if (!dirs)
336✔
206
                return log_oom();
×
207

208
        ConfFile **files = NULL;
336✔
209
        size_t n_files = 0;
336✔
210

211
        CLEANUP_ARRAY(files, n_files, conf_file_free_many);
336✔
212

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

219
        FOREACH_ARRAY(i, files, n_files) {
660✔
220
                _cleanup_(feature_unrefp) Feature *f = NULL;
×
221
                ConfFile *e = *i;
324✔
222

223
                f = feature_new();
324✔
224
                if (!f)
324✔
225
                        return log_oom();
×
226

227
                r = feature_read_definition(f, e->result, (const char**) dirs);
324✔
228
                if (r < 0)
324✔
229
                        return r;
230

231
                r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f);
324✔
232
                if (r < 0)
324✔
233
                        return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id);
×
234
                TAKE_PTR(f);
324✔
235
        }
236

237
        r = read_definitions(c, (const char**) dirs, ".transfer", node);
336✔
238
        if (r < 0)
336✔
239
                return r;
240

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

247
                if (c->n_transfers + c->n_disabled_transfers > 0)
×
248
                        log_warning("As of v257, transfer definitions should have the '.transfer' extension.");
×
249
        }
250

251
        if (c->n_transfers + (requires_enabled_transfers ? 0 : c->n_disabled_transfers) == 0) {
336✔
252
                if (arg_component)
×
253
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
254
                                               "No transfer definitions for component '%s' found.",
255
                                               arg_component);
256

257
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
258
                                       "No transfer definitions found.");
259
        }
260

261
        return 0;
262
}
263

264
static int context_load_installed_instances(Context *c) {
336✔
265
        int r;
336✔
266

267
        assert(c);
336✔
268

269
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
672✔
270

271
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
1,998✔
272
                Transfer *t = *tr;
1,662✔
273

274
                r = resource_load_instances(
1,662✔
275
                                &t->target,
276
                                arg_verify >= 0 ? arg_verify : t->verify,
1,662✔
277
                                &c->web_cache);
1,374✔
278
                if (r < 0)
1,662✔
279
                        return r;
280
        }
281

282
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
630✔
283
                Transfer *t = *tr;
294✔
284

285
                r = resource_load_instances(
294✔
286
                                &t->target,
287
                                arg_verify >= 0 ? arg_verify : t->verify,
294✔
288
                                &c->web_cache);
252✔
289
                if (r < 0)
294✔
290
                        return r;
291
        }
292

293
        return 0;
294
}
295

296
static int context_load_available_instances(Context *c) {
264✔
297
        int r;
264✔
298

299
        assert(c);
264✔
300

301
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
528✔
302

303
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
1,554✔
304
                Transfer *t = *tr;
1,296✔
305

306
                r = resource_load_instances(
1,296✔
307
                                &t->source,
308
                                arg_verify >= 0 ? arg_verify : t->verify,
1,296✔
309
                                &c->web_cache);
1,296✔
310
                if (r < 0)
1,296✔
311
                        return r;
312
        }
313

314
        return 0;
315
}
316

317
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
570✔
318
        _cleanup_free_ char *boundary = NULL;
570✔
319
        bool newest_found = false;
570✔
320
        int r;
570✔
321

322
        assert(c);
570✔
323
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
570✔
324

325
        for (;;) {
2,704✔
326
                _cleanup_free_ Instance **cursor_instances = NULL;
×
327
                bool skip = false;
2,704✔
328
                UpdateSetFlags extra_flags = 0;
2,704✔
329
                _cleanup_free_ char *cursor = NULL;
2,134✔
330
                UpdateSet *us = NULL;
2,704✔
331

332
                /* First, let's find the newest version that's older than the boundary. */
333
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
14,856✔
334
                        Resource *rr;
12,410✔
335

336
                        assert(*tr);
12,410✔
337

338
                        if (flags == UPDATE_AVAILABLE)
12,410✔
339
                                rr = &(*tr)->source;
6,764✔
340
                        else {
341
                                assert(flags == UPDATE_INSTALLED);
5,646✔
342
                                rr = &(*tr)->target;
5,646✔
343
                        }
344

345
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
35,638✔
346
                                Instance *i = *inst; /* Sorted newest-to-oldest */
32,784✔
347

348
                                assert(i);
32,784✔
349

350
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
32,784✔
351
                                        continue; /* Not older than the boundary */
23,228✔
352

353
                                if (cursor && strverscmp(i->metadata.version, cursor) <= 0)
9,556✔
354
                                        break; /* Not newer than the cursor. The same will be true for all
355
                                                * subsequent instances (due to sorting) so let's skip to the
356
                                                * next transfer. */
357

358
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
2,134✔
359
                                        return log_oom();
×
360

361
                                break; /* All subsequent instances will be older than this one */
362
                        }
363

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

372
                if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
2,704✔
373
                        break;
374

375
                cursor_instances = new0(Instance*, c->n_transfers);
2,134✔
376
                if (!cursor_instances)
2,134✔
377
                        return log_oom();
×
378

379
                /* Now let's find all the instances that match the version of the cursor, if we have them */
380
                for (size_t k = 0; k < c->n_transfers; k++) {
12,534✔
381
                        Transfer *t = c->transfers[k];
10,592✔
382
                        Instance *match = NULL;
10,592✔
383

384
                        assert(t);
10,592✔
385

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

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

409
                        cursor_instances[k] = match;
10,400✔
410

411
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
10,400✔
412
                                extra_flags |= UPDATE_OBSOLETE;
×
413

414
                        if (strv_contains(t->protected_versions, cursor))
10,400✔
415
                                extra_flags |= UPDATE_PROTECTED;
×
416

417
                        /* Partial or pending updates by definition are not incomplete, they’re
418
                         * partial/pending instead */
419
                        if (match && match->is_partial)
10,400✔
420
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
×
421

422
                        if (match && match->is_pending)
9,328✔
423
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
120✔
424
                }
425

426
                r = free_and_strdup_warn(&boundary, cursor);
2,134✔
427
                if (r < 0)
2,134✔
428
                        return r;
429

430
                if (skip)
2,134✔
431
                        continue;
192✔
432

433
                /* See if we already have this update set in our table */
434
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
4,918✔
435
                        UpdateSet *u = *update_set;
3,648✔
436

437
                        if (strverscmp_improved(u->version, cursor) != 0)
3,648✔
438
                                continue;
2,976✔
439

440
                        /* Merge in what we've learned and continue onto the next version */
441

442
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
672✔
443
                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
430✔
444
                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
430✔
445
                                assert(u->n_instances == c->n_transfers);
254✔
446

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

450
                                for (size_t j = 0; j < u->n_instances; j++) {
1,572✔
451
                                        if (!u->instances[j])
1,318✔
452
                                                u->instances[j] = cursor_instances[j];
860✔
453

454
                                        /* Make sure that the list is full if the update is AVAILABLE */
455
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
1,318✔
456
                                }
457
                        }
458

459
                        u->flags |= flags | extra_flags;
672✔
460

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

468
                        skip = true;
672✔
469
                        newest_found = true;
672✔
470
                        break;
672✔
471
                }
472

473
                if (skip)
1,942✔
474
                        continue;
672✔
475

476
                /* Doesn't exist yet, let's add it */
477
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
1,270✔
478
                        return log_oom();
×
479

480
                us = new(UpdateSet, 1);
1,270✔
481
                if (!us)
1,270✔
482
                        return log_oom();
×
483

484
                *us = (UpdateSet) {
1,270✔
485
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
1,270✔
486
                        .version = TAKE_PTR(cursor),
1,270✔
487
                        .instances = TAKE_PTR(cursor_instances),
1,270✔
488
                        .n_instances = c->n_transfers,
1,270✔
489
                };
490

491
                c->update_sets[c->n_update_sets++] = us;
1,270✔
492

493
                newest_found = true;
1,270✔
494

495
                /* Remember which one is the newest installed */
496
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
1,270✔
497
                        c->newest_installed = us;
300✔
498

499
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
500
                 * It may be partial or pending. */
501
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
1,270✔
502
                        c->candidate = us;
84✔
503
        }
504

505
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
506
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
570✔
507
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
486✔
508
                c->candidate = NULL;
12✔
509

510
        /* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */
511
        if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) &&
570✔
512
            !c->candidate)
36✔
513
                c->candidate = c->newest_installed;
36✔
514

515
        return 0;
516
}
517

518
static int context_discover_update_sets(Context *c) {
312✔
519
        int r;
312✔
520

521
        assert(c);
312✔
522

523
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
624✔
524

525
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
312✔
526
        if (r < 0)
312✔
527
                return r;
528

529
        if (!arg_offline) {
312✔
530
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
516✔
531

532
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
258✔
533
                if (r < 0)
258✔
534
                        return r;
535
        }
536

537
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
312✔
538
        return 0;
312✔
539
}
540

541
static int context_show_table(Context *c) {
×
542
        _cleanup_(table_unrefp) Table *t = NULL;
×
543
        int r;
×
544

545
        assert(c);
×
546

547
        t = table_new("", "version", "installed", "available", "assessment");
×
548
        if (!t)
×
549
                return log_oom();
×
550

551
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
552
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
553
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
554

555
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
556
                UpdateSet *us = *update_set;
×
557
                const char *color;
×
558

559
                color = update_set_flags_to_color(us->flags);
×
560

561
                r = table_add_many(t,
×
562
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
563
                                   TABLE_SET_COLOR, color,
564
                                   TABLE_STRING,    us->version,
565
                                   TABLE_SET_COLOR, color,
566
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
567
                                   TABLE_SET_COLOR, color,
568
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
569
                                   TABLE_SET_COLOR, color,
570
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
571
                                   TABLE_SET_COLOR, color);
572
                if (r < 0)
×
573
                        return table_log_add_error(r);
×
574
        }
575

576
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
577
}
578

579
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
66✔
580
        assert(c);
66✔
581
        assert(version);
66✔
582

583
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
126✔
584
                if (streq((*update_set)->version, version))
126✔
585
                        return *update_set;
586

587
        return NULL;
588
}
589

590
static int context_show_version(Context *c, const char *version) {
66✔
591
        bool show_fs_columns = false, show_partition_columns = false,
66✔
592
                have_fs_attributes = false, have_partition_attributes = false,
66✔
593
                have_size = false, have_tries = false, have_no_auto = false,
66✔
594
                have_read_only = false, have_growfs = false, have_sha256 = false;
66✔
595
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
66✔
596
        _cleanup_(table_unrefp) Table *t = NULL;
66✔
597
        _cleanup_strv_free_ char **changelog_urls = NULL;
66✔
598
        UpdateSet *us;
66✔
599
        int r;
66✔
600

601
        assert(c);
66✔
602
        assert(version);
66✔
603

604
        us = context_update_set_by_version(c, version);
66✔
605
        if (!us)
66✔
606
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
607

608
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
66✔
609
                pager_open(arg_pager_flags);
30✔
610

611
        if (!sd_json_format_enabled(arg_json_format_flags))
66✔
612
                printf("%s%s%s Version: %s\n"
240✔
613
                       "    State: %s%s%s\n"
614
                       "Installed: %s%s%s%s\n"
615
                       "Available: %s%s\n"
616
                       "Protected: %s%s%s\n"
617
                       " Obsolete: %s%s%s\n\n",
618
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
30✔
619
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
30✔
620
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
30✔
621
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
60✔
622
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
60✔
623
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
30✔
624
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
30✔
625

626
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
66✔
627
        if (!t)
66✔
628
                return log_oom();
×
629

630
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
66✔
631
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
66✔
632
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
66✔
633
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
66✔
634
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
66✔
635
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
66✔
636
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
66✔
637

638
        /* Starting in v257, these fields would be automatically formatted with underscores. This would have
639
         * been a breaking change, so to avoid that let's hard-code their original names. */
640
        (void) table_set_json_field_name(t, 7, "tries-done");
66✔
641
        (void) table_set_json_field_name(t, 8, "tries-left");
66✔
642

643
        /* Determine if the target will make use of partition/fs attributes for any of the transfers */
644
        FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
408✔
645
                Transfer *tr = *transfer;
342✔
646

647
                if (tr->target.type == RESOURCE_PARTITION)
342✔
648
                        show_partition_columns = true;
132✔
649
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
342✔
650
                        show_fs_columns = true;
651

652
                STRV_FOREACH(changelog, tr->changelog) {
342✔
653
                        assert(*changelog);
×
654

655
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
656
                        if (!changelog_url)
×
657
                                return log_oom();
×
658

659
                        /* Avoid duplicates */
660
                        if (strv_contains(changelog_urls, changelog_url))
×
661
                                continue;
×
662

663
                        /* changelog_urls takes ownership of expanded changelog_url */
664
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
665
                        if (r < 0)
×
666
                                return log_oom();
×
667
                }
668
        }
669

670
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
408✔
671
                Instance *i = *inst;
342✔
672

673
                if (!i) {
342✔
674
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
12✔
675
                        continue;
12✔
676
                }
677

678
                r = table_add_many(t,
330✔
679
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
680
                                   TABLE_PATH, i->path);
681
                if (r < 0)
330✔
682
                        return table_log_add_error(r);
×
683

684
                if (i->metadata.partition_uuid_set) {
330✔
685
                        have_partition_attributes = true;
96✔
686
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
96✔
687
                } else
688
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
234✔
689
                if (r < 0)
330✔
690
                        return table_log_add_error(r);
×
691

692
                if (i->metadata.partition_flags_set) {
330✔
693
                        have_partition_attributes = true;
96✔
694
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
96✔
695
                } else
696
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
234✔
697
                if (r < 0)
330✔
698
                        return table_log_add_error(r);
×
699

700
                if (i->metadata.mtime != USEC_INFINITY) {
330✔
701
                        have_fs_attributes = true;
234✔
702
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
234✔
703
                } else
704
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
96✔
705
                if (r < 0)
330✔
706
                        return table_log_add_error(r);
×
707

708
                if (i->metadata.mode != MODE_INVALID) {
330✔
709
                        have_fs_attributes = true;
234✔
710
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
234✔
711
                } else
712
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
96✔
713
                if (r < 0)
330✔
714
                        return table_log_add_error(r);
×
715

716
                if (i->metadata.size != UINT64_MAX) {
330✔
717
                        have_size = true;
×
718
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
719
                } else
720
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
330✔
721
                if (r < 0)
330✔
722
                        return table_log_add_error(r);
×
723

724
                if (i->metadata.tries_done != UINT64_MAX) {
330✔
725
                        have_tries = true;
48✔
726
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
48✔
727
                } else
728
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
282✔
729
                if (r < 0)
330✔
730
                        return table_log_add_error(r);
×
731

732
                if (i->metadata.tries_left != UINT64_MAX) {
330✔
733
                        have_tries = true;
48✔
734
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
48✔
735
                } else
736
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
282✔
737
                if (r < 0)
330✔
738
                        return table_log_add_error(r);
×
739

740
                if (i->metadata.no_auto >= 0) {
330✔
741
                        bool b;
×
742

743
                        have_no_auto = true;
×
744
                        b = i->metadata.no_auto;
×
745
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
746
                } else
747
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
330✔
748
                if (r < 0)
330✔
749
                        return table_log_add_error(r);
×
750
                if (i->metadata.read_only >= 0) {
330✔
751
                        bool b;
96✔
752

753
                        have_read_only = true;
96✔
754
                        b = i->metadata.read_only;
96✔
755
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
96✔
756
                } else
757
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
234✔
758
                if (r < 0)
330✔
759
                        return table_log_add_error(r);
×
760

761
                if (i->metadata.growfs >= 0) {
330✔
762
                        bool b;
×
763

764
                        have_growfs = true;
×
765
                        b = i->metadata.growfs;
×
766
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
767
                } else
768
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
330✔
769
                if (r < 0)
330✔
770
                        return table_log_add_error(r);
×
771

772
                if (i->metadata.sha256sum_set) {
330✔
773
                        _cleanup_free_ char *formatted = NULL;
×
774

775
                        have_sha256 = true;
×
776

777
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
×
778
                        if (!formatted)
×
779
                                return log_oom();
×
780

781
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
×
782
                } else
783
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
330✔
784
                if (r < 0)
330✔
785
                        return table_log_add_error(r);
×
786
        }
787

788
        /* Hide the fs/partition columns if we don't have any data to show there */
789
        if (!have_fs_attributes)
66✔
790
                show_fs_columns = false;
×
791
        if (!have_partition_attributes)
66✔
792
                show_partition_columns = false;
793

794
        if (!show_partition_columns)
48✔
795
                (void) table_hide_column_from_display(t, 2, 3);
18✔
796
        if (!show_fs_columns)
66✔
797
                (void) table_hide_column_from_display(t, 4, 5);
×
798
        if (!have_size)
66✔
799
                (void) table_hide_column_from_display(t, 6);
66✔
800
        if (!have_tries)
66✔
801
                (void) table_hide_column_from_display(t, 7, 8);
18✔
802
        if (!have_no_auto)
66✔
803
                (void) table_hide_column_from_display(t, 9);
66✔
804
        if (!have_read_only)
66✔
805
                (void) table_hide_column_from_display(t, 10);
18✔
806
        if (!have_growfs)
66✔
807
                (void) table_hide_column_from_display(t, 11);
66✔
808
        if (!have_sha256)
66✔
809
                (void) table_hide_column_from_display(t, 12);
66✔
810

811
        if (!sd_json_format_enabled(arg_json_format_flags)) {
66✔
812
                printf("%s%s%s Version: %s\n"
270✔
813
                       "    State: %s%s%s\n"
814
                       "Installed: %s%s%s%s%s%s%s\n"
815
                       "Available: %s%s\n"
816
                       "Protected: %s%s%s\n"
817
                       " Obsolete: %s%s%s\n",
818
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
30✔
819
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
30✔
820
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
30✔
821
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
42✔
822
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
60✔
823
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
60✔
824
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
30✔
825
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
30✔
826

827
                STRV_FOREACH(url, changelog_urls) {
30✔
828
                        _cleanup_free_ char *changelog_link = NULL;
×
829
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
830
                        if (r < 0)
×
831
                                return log_oom();
×
832
                        printf("ChangeLog: %s\n", changelog_link);
×
833
                }
834
                printf("\n");
30✔
835

836
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
30✔
837
        } else {
838
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
36✔
839

840
                r = table_to_json(t, &t_json);
36✔
841
                if (r < 0)
36✔
842
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
843

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

858
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
36✔
859
                if (r < 0)
36✔
860
                        return log_error_errno(r, "Failed to print JSON: %m");
×
861

862
                return 0;
863
        }
864
}
865

866
static int context_vacuum(
60✔
867
                Context *c,
868
                uint64_t space,
869
                const char *extra_protected_version) {
870

871
        size_t disabled_count = 0;
60✔
872
        int r, count = 0;
60✔
873

874
        assert(c);
60✔
875

876
        if (space == 0)
60✔
877
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
12✔
878
        else
879
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
108✔
880

881
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
366✔
882
                Transfer *t = *tr;
306✔
883

884
                /* Don't bother clearing out space if we're not going to be downloading anything */
885
                if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
306✔
886
                        continue;
54✔
887

888
                r = transfer_vacuum(t, space, extra_protected_version);
252✔
889
                if (r < 0)
252✔
890
                        return r;
891

892
                count = MAX(count, r);
252✔
893
        }
894

895
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
114✔
896
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
54✔
897
                if (r < 0)
54✔
898
                        return r;
899
                if (r > 0)
54✔
900
                        disabled_count++;
6✔
901
        }
902

903
        if (!sd_json_format_enabled(arg_json_format_flags)) {
60✔
904
                if (count > 0 && disabled_count > 0)
54✔
905
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
906
                else if (count > 0)
54✔
907
                        log_info("Removed %i instances.", count);
24✔
908
                else if (disabled_count > 0)
30✔
909
                        log_info("Removed %zu disabled transfers.", disabled_count);
6✔
910
                else
911
                        log_info("Found nothing to remove.");
24✔
912
        } else {
913
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
6✔
914

915
                r = sd_json_buildo(&json,
6✔
916
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
917
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
918
                if (r < 0)
6✔
919
                        return log_error_errno(r, "Failed to create JSON: %m");
×
920

921
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
6✔
922
                if (r < 0)
6✔
923
                        return log_error_errno(r, "Failed to print JSON: %m");
×
924
        }
925

926
        return 0;
927
}
928

929
static int context_make_offline(Context **ret, const char *node, bool requires_enabled_transfers) {
336✔
930
        _cleanup_(context_freep) Context* context = NULL;
336✔
931
        int r;
336✔
932

933
        assert(ret);
336✔
934

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

938
        context = context_new();
336✔
939
        if (!context)
336✔
940
                return log_oom();
×
941

942
        r = context_read_definitions(context, node, requires_enabled_transfers);
336✔
943
        if (r < 0)
336✔
944
                return r;
945

946
        r = context_load_installed_instances(context);
336✔
947
        if (r < 0)
336✔
948
                return r;
949

950
        *ret = TAKE_PTR(context);
336✔
951
        return 0;
336✔
952
}
953

954
static int context_make_online(Context **ret, const char *node) {
318✔
955
        _cleanup_(context_freep) Context* context = NULL;
318✔
956
        int r;
318✔
957

958
        assert(ret);
318✔
959

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

963
        r = context_make_offline(&context, node, /* requires_enabled_transfers= */ true);
318✔
964
        if (r < 0)
318✔
965
                return r;
966

967
        if (!arg_offline) {
318✔
968
                r = context_load_available_instances(context);
264✔
969
                if (r < 0)
264✔
970
                        return r;
971
        }
972

973
        r = context_discover_update_sets(context);
312✔
974
        if (r < 0)
312✔
975
                return r;
976

977
        *ret = TAKE_PTR(context);
312✔
978
        return 0;
312✔
979
}
980

981
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
189✔
982
        const Context *c = ASSERT_PTR(t->context);
189✔
983
        size_t i, n = c->n_transfers;
189✔
984
        uint64_t base, scaled;
189✔
985
        unsigned overall;
189✔
986

987
        for (i = 0; i < n; i++)
530✔
988
                if (c->transfers[i] == t)
530✔
989
                        break;
990
        assert(i < n); /* We should have found the index */
189✔
991

992
        base = (100 * 100 * i) / n;
189✔
993
        scaled = (100 * percentage) / n;
189✔
994
        overall = (unsigned) ((base + scaled) / 100);
189✔
995
        assert(overall <= 100);
189✔
996

997
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
189✔
998
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
378✔
999
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
1000
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
1001
                                              "STATUS=Updating to '%s' (%u%% complete).",
1002
                                              overall, n - i, i, inst->metadata.version, overall);
189✔
1003
}
1004

1005
static int context_process_partial_and_pending(Context *c, const char *version);
1006

1007
static int context_acquire(
84✔
1008
                Context *c,
1009
                const char *version) {
1010

1011
        UpdateSet *us = NULL;
84✔
1012
        int r;
84✔
1013

1014
        assert(c);
84✔
1015

1016
        if (version) {
84✔
1017
                us = context_update_set_by_version(c, version);
×
1018
                if (!us)
×
1019
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
84✔
1020
        } else {
1021
                if (!c->candidate) {
84✔
1022
                        log_info("No update needed.");
18✔
1023

1024
                        return 0;
18✔
1025
                }
1026

1027
                us = c->candidate;
1028
        }
1029

1030
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
66✔
1031
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
12✔
1032
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
54✔
1033
                log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
×
1034

1035
                return 0;
×
1036
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
54✔
1037
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
12✔
1038

1039
                return context_process_partial_and_pending(c, version);
12✔
1040
        } else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
42✔
1041
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1042

1043
                return 0;
×
1044
        }
1045

1046
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
54✔
1047
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1048
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
54✔
1049
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1050

1051
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
54✔
1052
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1053
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
54✔
1054
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1055

1056
        log_info("Selected update '%s' for install.", us->version);
54✔
1057

1058
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
108✔
1059
        if (!metadata)
54✔
UNCOV
1060
                return log_oom();
×
1061

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

1068
                assert(inst);
276✔
1069

1070
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
276✔
1071
                if (r < 0)
276✔
1072
                        return r;
1073
        }
1074

1075
        (void) sd_notifyf(/* unset_environment= */ false,
54✔
1076
                          "READY=1\n"
1077
                          "X_SYSUPDATE_VERSION=%s\n"
1078
                          "STATUS=Making room for '%s'.", us->version, us->version);
1079

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

1090
        if (arg_sync)
54✔
1091
                sync();
54✔
1092

1093
        (void) sd_notifyf(/* unset_environment= */ false,
54✔
1094
                          "STATUS=Updating to '%s'.", us->version);
1095

1096
        /* There should now be one instance picked for each transfer, and the order is the same */
1097
        assert(us->n_instances == c->n_transfers);
54✔
1098

1099
        for (size_t i = 0; i < c->n_transfers; i++) {
330✔
1100
                Instance *inst = us->instances[i];
276✔
1101
                Transfer *t = c->transfers[i];
276✔
1102

1103
                assert(inst); /* ditto */
276✔
1104

1105
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
276✔
1106
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
54✔
1107
                        continue;
54✔
1108
                }
1109

1110
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
222✔
1111
                if (r < 0)
222✔
1112
                        return r;
1113
        }
1114

1115
        if (arg_sync)
54✔
1116
                sync();
54✔
1117

1118
        return 1;
1119
}
1120

1121
/* Check to see if we have an update set acquired and pending installation. */
1122
static int context_process_partial_and_pending(
24✔
1123
                Context *c,
1124
                const char *version) {
1125

1126
        UpdateSet *us = NULL;
24✔
1127
        int r;
24✔
1128

1129
        assert(c);
24✔
1130

1131
        if (version) {
24✔
UNCOV
1132
                us = context_update_set_by_version(c, version);
×
1133
                if (!us)
×
UNCOV
1134
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1135
        } else {
1136
                if (!c->candidate) {
24✔
1137
                        log_info("No update needed.");
×
1138

1139
                        return 0;
×
1140
                }
1141

1142
                us = c->candidate;
1143
        }
1144

1145
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
24✔
UNCOV
1146
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
×
1147
        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
24✔
UNCOV
1148
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1149

UNCOV
1150
                return 0;
×
1151
        }
1152

1153
        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
24✔
UNCOV
1154
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
×
1155
        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
24✔
UNCOV
1156
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
×
1157

1158
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
24✔
UNCOV
1159
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1160

1161
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
24✔
UNCOV
1162
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1163
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
24✔
UNCOV
1164
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1165

1166
        log_info("Selected update '%s' for install.", us->version);
24✔
1167

1168
        /* There should now be one instance picked for each transfer, and the order is the same */
1169
        assert(us->n_instances == c->n_transfers);
24✔
1170

1171
        for (size_t i = 0; i < c->n_transfers; i++) {
144✔
1172
                Instance *inst = us->instances[i];
120✔
1173
                Transfer *t = c->transfers[i];
120✔
1174

1175
                assert(inst);
120✔
1176

1177
                r = transfer_process_partial_and_pending_instance(t, inst);
120✔
1178
                if (r < 0)
120✔
1179
                        return r;
1180
        }
1181

1182
        return 1;
1183
}
1184

1185
static int context_install(
54✔
1186
                Context *c,
1187
                const char *version,
1188
                UpdateSet **ret_applied) {
1189

1190
        UpdateSet *us = NULL;
54✔
1191
        int r;
54✔
1192

1193
        assert(c);
54✔
1194

1195
        if (version) {
54✔
UNCOV
1196
                us = context_update_set_by_version(c, version);
×
UNCOV
1197
                if (!us)
×
UNCOV
1198
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1199
        } else {
1200
                if (!c->candidate) {
54✔
UNCOV
1201
                        log_info("No update needed.");
×
1202

UNCOV
1203
                        return 0;
×
1204
                }
1205

1206
                us = c->candidate;
1207
        }
1208

1209
        (void) sd_notifyf(/* unset_environment=*/ false,
54✔
1210
                          "STATUS=Installing '%s'.", us->version);
1211

1212
        for (size_t i = 0; i < c->n_transfers; i++) {
330✔
1213
                Instance *inst = us->instances[i];
276✔
1214
                Transfer *t = c->transfers[i];
276✔
1215

1216
                if (inst->resource == &t->target &&
276✔
1217
                    !inst->is_pending)
174✔
1218
                        continue;
54✔
1219

1220
                r = transfer_install_instance(t, inst, arg_root);
222✔
1221
                if (r < 0)
222✔
1222
                        return r;
1223
        }
1224

1225
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
54✔
1226

1227
        (void) sd_notifyf(/* unset_environment= */ false,
54✔
1228
                          "STATUS=Installed '%s'.", us->version);
1229

1230
        if (ret_applied)
54✔
1231
                *ret_applied = us;
54✔
1232

1233
        return 1;
1234
}
1235

1236
static int process_image(
366✔
1237
                bool ro,
1238
                char **ret_mounted_dir,
1239
                LoopDevice **ret_loop_device) {
1240

1241
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
366✔
1242
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
366✔
1243
        int r;
366✔
1244

1245
        assert(ret_mounted_dir);
366✔
1246
        assert(ret_loop_device);
366✔
1247

1248
        if (!arg_image)
366✔
1249
                return 0;
1250

1251
        assert(!arg_root);
×
1252

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

UNCOV
1271
        arg_root = strdup(mounted_dir);
×
UNCOV
1272
        if (!arg_root)
×
UNCOV
1273
                return log_oom();
×
1274

UNCOV
1275
        *ret_mounted_dir = TAKE_PTR(mounted_dir);
×
UNCOV
1276
        *ret_loop_device = TAKE_PTR(loop_device);
×
1277

UNCOV
1278
        return 0;
×
1279
}
1280

1281
static int verb_list(int argc, char **argv, void *userdata) {
84✔
1282
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
84✔
1283
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
84✔
1284
        _cleanup_(context_freep) Context* context = NULL;
84✔
1285
        _cleanup_strv_free_ char **appstream_urls = NULL;
84✔
1286
        const char *version;
84✔
1287
        int r;
84✔
1288

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

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

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

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

1310
                FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
78✔
1311
                        UpdateSet *us = *update_set;
60✔
1312

1313
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
60✔
1314
                            FLAGS_SET(us->flags, UPDATE_NEWEST)) {
48✔
1315
                                current = us->version;
18✔
1316
                                current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
18✔
1317
                        }
1318

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

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

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

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

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

1345
                return 0;
1346
        }
1347
}
1348

1349
static int verb_features(int argc, char **argv, void *userdata) {
12✔
1350
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
12✔
1351
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
12✔
1352
        _cleanup_(context_freep) Context* context = NULL;
12✔
1353
        _cleanup_(table_unrefp) Table *table = NULL;
12✔
1354
        const char *feature_id;
12✔
1355
        Feature *f;
12✔
1356
        int r;
12✔
1357

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

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

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

1369
        if (feature_id) {
12✔
1370
                _cleanup_strv_free_ char **transfers = NULL;
6✔
1371

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

1378
                table = table_new_vertical();
6✔
1379
                if (!table)
6✔
1380
                        return log_oom();
×
1381

1382
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
36✔
1383
                        Transfer *t = *tr;
30✔
1384

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

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

1393
                FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) {
12✔
1394
                        Transfer *t = *tr;
6✔
1395

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
1480
        return 0;
×
1481
}
1482

1483
static int verb_check_new(int argc, char **argv, void *userdata) {
132✔
1484
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
132✔
1485
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
132✔
1486
        _cleanup_(context_freep) Context* context = NULL;
132✔
1487
        int r;
132✔
1488

1489
        assert(argc <= 1);
132✔
1490

1491
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
132✔
1492
        if (r < 0)
132✔
1493
                return r;
1494

1495
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
132✔
1496
        if (r < 0)
132✔
1497
                return r;
1498

1499
        if (!sd_json_format_enabled(arg_json_format_flags)) {
132✔
1500
                if (!context->candidate) {
120✔
1501
                        log_debug("No candidate found.");
66✔
1502
                        return EXIT_FAILURE;
66✔
1503
                }
1504

1505
                puts(context->candidate->version);
54✔
1506
        } else {
1507
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
12✔
1508

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

1516
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
12✔
1517
                if (r < 0)
12✔
UNCOV
1518
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1519
        }
1520

1521
        return EXIT_SUCCESS;
1522
}
1523

1524
static int verb_vacuum(int argc, char **argv, void *userdata) {
6✔
1525
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
6✔
1526
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
6✔
1527
        _cleanup_(context_freep) Context* context = NULL;
6✔
1528
        int r;
6✔
1529

1530
        assert(argc <= 1);
6✔
1531

1532
        if (arg_instances_max < 1)
6✔
UNCOV
1533
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1534
                                      "The --instances-max argument must be >= 1 while vacuuming");
1535

1536
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
6✔
1537
        if (r < 0)
6✔
1538
                return r;
1539

1540
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
6✔
1541
        if (r < 0)
6✔
1542
                return r;
1543

1544
        return context_vacuum(context, 0, NULL);
6✔
1545
}
1546

1547
typedef enum {
1548
        UPDATE_ACTION_ACQUIRE = 1 << 0,
1549
        UPDATE_ACTION_INSTALL = 1 << 1,
1550
} UpdateActionFlags;
1551

1552
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
102✔
1553
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
102✔
1554
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
102✔
1555
        _cleanup_(context_freep) Context* context = NULL;
×
1556
        _cleanup_free_ char *booted_version = NULL;
102✔
1557
        UpdateSet *applied = NULL;
102✔
1558
        const char *version;
102✔
1559
        int r;
102✔
1560

1561
        assert(argc <= 2);
102✔
1562
        version = argc >= 2 ? argv[1] : NULL;
102✔
1563

1564
        if (arg_instances_max < 2)
102✔
UNCOV
1565
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1566
                                      "The --instances-max argument must be >= 2 while updating");
1567

1568
        if (arg_reboot) {
102✔
1569
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
1570

UNCOV
1571
                r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
UNCOV
1572
                if (r < 0)
×
UNCOV
1573
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
UNCOV
1574
                if (!booted_version)
×
UNCOV
1575
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
1576
        }
1577

1578
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
102✔
1579
        if (r < 0)
102✔
1580
                return r;
1581

1582
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
102✔
1583
        if (r < 0)
102✔
1584
                return r;
1585

1586
        if (action_flags & UPDATE_ACTION_ACQUIRE)
96✔
1587
                r = context_acquire(context, version);
84✔
1588
        else
1589
                r = context_process_partial_and_pending(context, version);
12✔
1590
        if (r < 0)
96✔
1591
                return r;  /* error */
1592

1593
        if (action_flags & UPDATE_ACTION_INSTALL && r > 0)  /* update needed */
96✔
1594
                r = context_install(context, version, &applied);
54✔
1595
        if (r < 0)
96✔
1596
                return r;
1597

1598
        if (r > 0 && arg_reboot) {
96✔
UNCOV
1599
                assert(applied);
×
UNCOV
1600
                assert(booted_version);
×
1601

UNCOV
1602
                if (strverscmp_improved(applied->version, booted_version) > 0) {
×
UNCOV
1603
                        log_notice("Newly installed version is newer than booted version, rebooting.");
×
UNCOV
1604
                        return reboot_now();
×
1605
                }
1606

UNCOV
1607
                if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
UNCOV
1608
                    FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
UNCOV
1609
                        log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
UNCOV
1610
                        return reboot_now();
×
1611
                }
1612

UNCOV
1613
                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
1614
        }
1615

1616
        return 0;
1617
}
1618

1619
static int verb_update(int argc, char **argv, void *userdata) {
78✔
1620
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
78✔
1621

1622
        if (!arg_offline)
78✔
1623
                flags |= UPDATE_ACTION_ACQUIRE;
66✔
1624

1625
        return verb_update_impl(argc, argv, flags);
78✔
1626
}
1627

1628
static int verb_acquire(int argc, char **argv, void *userdata) {
24✔
1629
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
24✔
1630
}
1631

1632
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
×
1633
        _cleanup_(context_freep) Context* context = NULL;
×
UNCOV
1634
        _cleanup_free_ char *booted_version = NULL;
×
1635
        int r;
×
1636

UNCOV
1637
        assert(argc == 1);
×
1638

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

1643
        r = context_make_offline(&context, /* node= */ NULL, /* requires_enabled_transfers= */ true);
×
UNCOV
1644
        if (r < 0)
×
1645
                return r;
1646

1647
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
1648

UNCOV
1649
        r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
×
UNCOV
1650
        if (r < 0)
×
1651
                return r;
1652
        if (!context->newest_installed)
×
UNCOV
1653
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
1654

1655
        r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
1656
        if (r < 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable
×
1657
                    * if we see what the first argument is about */
UNCOV
1658
                return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1659
        if (!booted_version)
×
UNCOV
1660
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field.");
×
1661

1662
        r = strverscmp_improved(context->newest_installed->version, booted_version);
×
1663
        if (r > 0) {
×
UNCOV
1664
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
1665
                           context->newest_installed->version, booted_version,
1666
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
1667

UNCOV
1668
                if (streq(argv[0], "reboot"))
×
UNCOV
1669
                        return reboot_now();
×
1670

1671
                return EXIT_SUCCESS;
UNCOV
1672
        } else if (r == 0)
×
UNCOV
1673
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
1674
                         context->newest_installed->version, booted_version);
1675
        else
UNCOV
1676
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
1677
                            context->newest_installed->version, booted_version);
1678

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

1682
        return EXIT_SUCCESS;
1683
}
1684

1685
static int component_name_valid(const char *c) {
36✔
1686
        _cleanup_free_ char *j = NULL;
36✔
1687

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

1690
        if (isempty(c))
72✔
1691
                return false;
1692

1693
        if (string_has_cc(c, NULL))
36✔
1694
                return false;
1695

1696
        if (!utf8_is_valid(c))
36✔
1697
                return false;
1698

1699
        j = strjoin("sysupdate.", c, ".d");
36✔
1700
        if (!j)
36✔
1701
                return -ENOMEM;
1702

1703
        return filename_is_valid(j);
36✔
1704
}
1705

1706
static int verb_components(int argc, char **argv, void *userdata) {
30✔
1707
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
30✔
1708
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
30✔
1709
        _cleanup_set_free_ Set *names = NULL;
30✔
1710
        bool has_default_component = false;
30✔
1711
        int r;
30✔
1712

1713
        assert(argc <= 1);
30✔
1714

1715
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
30✔
1716
        if (r < 0)
30✔
1717
                return r;
1718

1719
        ConfFile **directories = NULL;
30✔
1720
        size_t n_directories = 0;
30✔
1721

1722
        CLEANUP_ARRAY(directories, n_directories, conf_file_free_many);
30✔
1723

1724
        r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY|CONF_FILES_WARN,
60✔
1725
                                      (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories);
30✔
1726
        if (r < 0)
30✔
UNCOV
1727
                return log_error_errno(r, "Failed to enumerate directories: %m");
×
1728

1729
        FOREACH_ARRAY(i, directories, n_directories) {
804✔
1730
                ConfFile *e = *i;
774✔
1731

1732
                if (streq(e->filename, "sysupdate.d")) {
774✔
1733
                        has_default_component = true;
30✔
1734
                        continue;
30✔
1735
                }
1736

1737
                const char *s = startswith(e->filename, "sysupdate.");
744✔
1738
                if (!s)
744✔
1739
                        continue;
720✔
1740

1741
                const char *a = endswith(s, ".d");
24✔
1742
                if (!a)
24✔
UNCOV
1743
                        continue;
×
1744

UNCOV
1745
                _cleanup_free_ char *n = strndup(s, a - s);
×
1746
                if (!n)
24✔
1747
                        return log_oom();
×
1748

1749
                r = component_name_valid(n);
24✔
1750
                if (r < 0)
24✔
UNCOV
1751
                        return log_error_errno(r, "Unable to validate component name '%s': %m", n);
×
1752
                if (r == 0)
24✔
1753
                        continue;
×
1754

1755
                r = set_ensure_put(&names, &string_hash_ops_free, n);
24✔
1756
                if (r < 0 && r != -EEXIST)
24✔
1757
                        return log_error_errno(r, "Failed to add component '%s' to set: %m", n);
×
1758
                TAKE_PTR(n);
1759
        }
1760

1761
        /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
1762
        _cleanup_free_ char **z = set_get_strv(names);
60✔
1763
        if (!z)
30✔
UNCOV
1764
                return log_oom();
×
1765

1766
        strv_sort(z);
30✔
1767

1768
        if (!sd_json_format_enabled(arg_json_format_flags)) {
30✔
1769
                if (!has_default_component && set_isempty(names)) {
×
UNCOV
1770
                        log_info("No components defined.");
×
UNCOV
1771
                        return 0;
×
1772
                }
1773

UNCOV
1774
                if (has_default_component)
×
UNCOV
1775
                        printf("%s<default>%s\n",
×
1776
                               ansi_highlight(), ansi_normal());
1777

UNCOV
1778
                STRV_FOREACH(i, z)
×
1779
                        puts(*i);
×
1780
        } else {
1781
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
30✔
1782

1783
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
30✔
1784
                                          SD_JSON_BUILD_PAIR_STRV("components", z));
1785
                if (r < 0)
30✔
UNCOV
1786
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1787

1788
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
30✔
1789
                if (r < 0)
30✔
UNCOV
1790
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1791
        }
1792

1793
        return 0;
1794
}
1795

UNCOV
1796
static int verb_help(int argc, char **argv, void *userdata) {
×
UNCOV
1797
        _cleanup_free_ char *link = NULL;
×
UNCOV
1798
        int r;
×
1799

UNCOV
1800
        r = terminal_urlify_man("systemd-sysupdate", "8", &link);
×
UNCOV
1801
        if (r < 0)
×
UNCOV
1802
                return log_oom();
×
1803

UNCOV
1804
        printf("%1$s [OPTIONS...] [VERSION]\n"
×
1805
               "\n%5$sUpdate OS images.%6$s\n"
1806
               "\n%3$sCommands:%4$s\n"
1807
               "  list [VERSION]          Show installed and available versions\n"
1808
               "  features [FEATURE]      Show optional features\n"
1809
               "  check-new               Check if there's a new version available\n"
1810
               "  update [VERSION]        Install new version now\n"
1811
               "  acquire [VERSION]       Acquire (download) new version now\n"
1812
               "  vacuum                  Make room, by deleting old versions\n"
1813
               "  pending                 Report whether a newer version is installed than\n"
1814
               "                          currently booted\n"
1815
               "  reboot                  Reboot if a newer version is installed than booted\n"
1816
               "  components              Show list of components\n"
1817
               "  -h --help               Show this help\n"
1818
               "     --version            Show package version\n"
1819
               "\n%3$sOptions:%4$s\n"
1820
               "  -C --component=NAME     Select component to update\n"
1821
               "     --definitions=DIR    Find transfer definitions in specified directory\n"
1822
               "     --root=PATH          Operate on an alternate filesystem root\n"
1823
               "     --image=PATH         Operate on disk image as filesystem root\n"
1824
               "     --image-policy=POLICY\n"
1825
               "                          Specify disk image dissection policy\n"
1826
               "  -m --instances-max=INT  How many instances to maintain\n"
1827
               "     --sync=BOOL          Controls whether to sync data to disk\n"
1828
               "     --verify=BOOL        Force signature verification on or off\n"
1829
               "     --reboot             Reboot after updating to newer version\n"
1830
               "     --offline            Do not fetch metadata from the network\n"
1831
               "     --no-pager           Do not pipe output into a pager\n"
1832
               "     --no-legend          Do not show the headers and footers\n"
1833
               "     --json=pretty|short|off\n"
1834
               "                          Generate JSON output\n"
1835
               "     --transfer-source=PATH\n"
1836
               "                          Specify the directory to transfer sources from\n"
1837
               "\nSee the %2$s for details.\n",
1838
               program_invocation_short_name,
1839
               link,
1840
               ansi_underline(),
1841
               ansi_normal(),
1842
               ansi_highlight(),
1843
               ansi_normal());
1844

1845
        return 0;
1846
}
1847

1848
static int parse_argv(int argc, char *argv[]) {
366✔
1849

1850
        enum {
366✔
1851
                ARG_VERSION = 0x100,
1852
                ARG_NO_PAGER,
1853
                ARG_NO_LEGEND,
1854
                ARG_SYNC,
1855
                ARG_DEFINITIONS,
1856
                ARG_JSON,
1857
                ARG_ROOT,
1858
                ARG_IMAGE,
1859
                ARG_IMAGE_POLICY,
1860
                ARG_REBOOT,
1861
                ARG_VERIFY,
1862
                ARG_OFFLINE,
1863
                ARG_TRANSFER_SOURCE,
1864
        };
1865

1866
        static const struct option options[] = {
366✔
1867
                { "help",              no_argument,       NULL, 'h'                   },
1868
                { "version",           no_argument,       NULL, ARG_VERSION           },
1869
                { "no-pager",          no_argument,       NULL, ARG_NO_PAGER          },
1870
                { "no-legend",         no_argument,       NULL, ARG_NO_LEGEND         },
1871
                { "definitions",       required_argument, NULL, ARG_DEFINITIONS       },
1872
                { "instances-max",     required_argument, NULL, 'm'                   },
1873
                { "sync",              required_argument, NULL, ARG_SYNC              },
1874
                { "json",              required_argument, NULL, ARG_JSON              },
1875
                { "root",              required_argument, NULL, ARG_ROOT              },
1876
                { "image",             required_argument, NULL, ARG_IMAGE             },
1877
                { "image-policy",      required_argument, NULL, ARG_IMAGE_POLICY      },
1878
                { "reboot",            no_argument,       NULL, ARG_REBOOT            },
1879
                { "component",         required_argument, NULL, 'C'                   },
1880
                { "verify",            required_argument, NULL, ARG_VERIFY            },
1881
                { "offline",           no_argument,       NULL, ARG_OFFLINE           },
1882
                { "transfer-source",   required_argument, NULL, ARG_TRANSFER_SOURCE   },
1883
                {}
1884
        };
1885

1886
        int c, r;
366✔
1887

1888
        assert(argc >= 0);
366✔
1889
        assert(argv);
366✔
1890

1891
        while ((c = getopt_long(argc, argv, "hm:C:", options, NULL)) >= 0) {
810✔
1892

1893
                switch (c) {
444✔
1894

1895
                case 'h':
×
UNCOV
1896
                        return verb_help(0, NULL, NULL);
×
1897

UNCOV
1898
                case ARG_VERSION:
×
1899
                        return version();
×
1900

1901
                case ARG_NO_PAGER:
×
UNCOV
1902
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
1903
                        break;
×
1904

1905
                case ARG_NO_LEGEND:
×
1906
                        arg_legend = false;
×
1907
                        break;
×
1908

UNCOV
1909
                case 'm':
×
UNCOV
1910
                        r = safe_atou64(optarg, &arg_instances_max);
×
UNCOV
1911
                        if (r < 0)
×
UNCOV
1912
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg);
×
1913

1914
                        break;
1915

UNCOV
1916
                case ARG_SYNC:
×
UNCOV
1917
                        r = parse_boolean_argument("--sync=", optarg, &arg_sync);
×
1918
                        if (r < 0)
×
1919
                                return r;
1920
                        break;
1921

UNCOV
1922
                case ARG_DEFINITIONS:
×
UNCOV
1923
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions);
×
1924
                        if (r < 0)
×
1925
                                return r;
1926
                        break;
1927

1928
                case ARG_JSON:
102✔
1929
                        r = parse_json_argument(optarg, &arg_json_format_flags);
102✔
1930
                        if (r <= 0)
102✔
1931
                                return r;
1932

1933
                        break;
1934

UNCOV
1935
                case ARG_ROOT:
×
1936
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
×
1937
                        if (r < 0)
×
1938
                                return r;
1939
                        break;
1940

UNCOV
1941
                case ARG_IMAGE:
×
1942
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
1943
                        if (r < 0)
×
1944
                                return r;
1945
                        break;
1946

UNCOV
1947
                case ARG_IMAGE_POLICY:
×
1948
                        r = parse_image_policy_argument(optarg, &arg_image_policy);
×
UNCOV
1949
                        if (r < 0)
×
1950
                                return r;
1951
                        break;
1952

UNCOV
1953
                case ARG_REBOOT:
×
UNCOV
1954
                        arg_reboot = true;
×
UNCOV
1955
                        break;
×
1956

1957
                case 'C':
12✔
1958
                        if (isempty(optarg)) {
12✔
UNCOV
1959
                                arg_component = mfree(arg_component);
×
UNCOV
1960
                                break;
×
1961
                        }
1962

1963
                        r = component_name_valid(optarg);
12✔
1964
                        if (r < 0)
12✔
UNCOV
1965
                                return log_error_errno(r, "Failed to determine if component name is valid: %m");
×
1966
                        if (r == 0)
12✔
UNCOV
1967
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg);
×
1968

1969
                        r = free_and_strdup_warn(&arg_component, optarg);
12✔
1970
                        if (r < 0)
12✔
1971
                                return r;
1972

1973
                        break;
1974

1975
                case ARG_VERIFY: {
276✔
1976
                        bool b;
276✔
1977

1978
                        r = parse_boolean_argument("--verify=", optarg, &b);
276✔
1979
                        if (r < 0)
276✔
UNCOV
1980
                                return r;
×
1981

1982
                        arg_verify = b;
276✔
1983
                        break;
276✔
1984
                }
1985

1986
                case ARG_OFFLINE:
54✔
1987
                        arg_offline = true;
54✔
1988
                        break;
54✔
1989

UNCOV
1990
                case ARG_TRANSFER_SOURCE:
×
UNCOV
1991
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source);
×
1992
                        if (r < 0)
×
1993
                                return r;
1994

1995
                        break;
1996

1997
                case '?':
1998
                        return -EINVAL;
1999

UNCOV
2000
                default:
×
UNCOV
2001
                        assert_not_reached();
×
2002
                }
2003
        }
2004

2005
        if (arg_image && arg_root)
366✔
UNCOV
2006
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
2007

2008
        if ((arg_image || arg_root) && arg_reboot)
366✔
UNCOV
2009
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
2010

2011
        if (arg_definitions && arg_component)
366✔
UNCOV
2012
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
2013

2014
        return 1;
2015
}
2016

2017
static int sysupdate_main(int argc, char *argv[]) {
366✔
2018

2019
        static const Verb verbs[] = {
366✔
2020
                { "list",       VERB_ANY, 2, VERB_DEFAULT, verb_list              },
2021
                { "components", VERB_ANY, 1, 0,            verb_components        },
2022
                { "features",   VERB_ANY, 2, 0,            verb_features          },
2023
                { "check-new",  VERB_ANY, 1, 0,            verb_check_new         },
2024
                { "update",     VERB_ANY, 2, 0,            verb_update            },
2025
                { "acquire",    VERB_ANY, 2, 0,            verb_acquire           },
2026
                { "vacuum",     VERB_ANY, 1, 0,            verb_vacuum            },
2027
                { "reboot",     1,        1, 0,            verb_pending_or_reboot },
2028
                { "pending",    1,        1, 0,            verb_pending_or_reboot },
2029
                { "help",       VERB_ANY, 1, 0,            verb_help              },
2030
                {}
2031
        };
2032

2033
        return dispatch_verb(argc, argv, verbs, NULL);
366✔
2034
}
2035

2036
static int run(int argc, char *argv[]) {
366✔
2037
        int r;
366✔
2038

2039
        log_setup();
366✔
2040

2041
        r = parse_argv(argc, argv);
366✔
2042
        if (r <= 0)
366✔
2043
                return r;
2044

2045
        return sysupdate_main(argc, argv);
366✔
2046
}
2047

2048
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
366✔
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