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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 hits per line

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

70.77
/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 "build.h"
7
#include "chase.h"
8
#include "conf-files.h"
9
#include "constants.h"
10
#include "dirent-util.h"
11
#include "dissect-image.h"
12
#include "fd-util.h"
13
#include "format-table.h"
14
#include "glyph-util.h"
15
#include "hexdecoct.h"
16
#include "image-policy.h"
17
#include "json-util.h"
18
#include "main-func.h"
19
#include "mount-util.h"
20
#include "os-util.h"
21
#include "pager.h"
22
#include "parse-argument.h"
23
#include "parse-util.h"
24
#include "path-util.h"
25
#include "pretty-print.h"
26
#include "set.h"
27
#include "signal-util.h"
28
#include "sort-util.h"
29
#include "specifier.h"
30
#include "string-util.h"
31
#include "strv.h"
32
#include "sysupdate.h"
33
#include "sysupdate-feature.h"
34
#include "sysupdate-instance.h"
35
#include "sysupdate-transfer.h"
36
#include "sysupdate-update-set.h"
37
#include "sysupdate-util.h"
38
#include "terminal-util.h"
39
#include "utf8.h"
40
#include "verbs.h"
41

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

57
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
106✔
58
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
106✔
59
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
106✔
60
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
106✔
61
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
106✔
62
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
106✔
63

64
const Specifier specifier_table[] = {
65
        COMMON_SYSTEM_SPECIFIERS,
66
        COMMON_TMP_SPECIFIERS,
67
        {}
68
};
69

70
typedef struct Context {
71
        Transfer **transfers;
72
        size_t n_transfers;
73

74
        Transfer **disabled_transfers;
75
        size_t n_disabled_transfers;
76

77
        Hashmap *features; /* Defined features, keyed by ID */
78

79
        UpdateSet **update_sets;
80
        size_t n_update_sets;
81

82
        UpdateSet *newest_installed, *candidate;
83

84
        Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */
85
} Context;
86

87
static Context* context_free(Context *c) {
96✔
88
        if (!c)
96✔
89
                return NULL;
90

91
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
570✔
92
                transfer_free(*tr);
474✔
93
        free(c->transfers);
96✔
94

95
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
178✔
96
                transfer_free(*tr);
82✔
97
        free(c->disabled_transfers);
96✔
98

99
        hashmap_free(c->features);
96✔
100

101
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
458✔
102
                update_set_free(*us);
362✔
103
        free(c->update_sets);
96✔
104

105
        hashmap_free(c->web_cache);
96✔
106

107
        return mfree(c);
96✔
108
}
109

110
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
282✔
111

112
static Context* context_new(void) {
96✔
113
        /* For now, no fields to initialize non-zero */
114
        return new0(Context, 1);
96✔
115
}
116

117
static void free_transfers(Transfer **array, size_t n) {
×
118
        FOREACH_ARRAY(t, array, n)
×
119
                transfer_free(*t);
×
120
        free(array);
×
121
}
×
122

123
static int read_definitions(
96✔
124
                Context *c,
125
                const char **dirs,
126
                const char *suffix,
127
                const char *node) {
128

129
        _cleanup_strv_free_ char **files = NULL;
96✔
130
        Transfer **transfers = NULL, **disabled = NULL;
96✔
131
        size_t n_transfers = 0, n_disabled = 0;
96✔
132
        int r;
96✔
133

134
        CLEANUP_ARRAY(transfers, n_transfers, free_transfers);
96✔
135
        CLEANUP_ARRAY(disabled, n_disabled, free_transfers);
96✔
136

137
        assert(c);
96✔
138
        assert(dirs);
96✔
139
        assert(suffix);
96✔
140

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

145
        STRV_FOREACH(p, files) {
652✔
146
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
147
                Transfer **appended;
556✔
148

149
                t = transfer_new(c);
556✔
150
                if (!t)
556✔
151
                        return log_oom();
×
152

153
                r = transfer_read_definition(t, *p, dirs, c->features);
556✔
154
                if (r < 0)
556✔
155
                        return r;
156

157
                r = transfer_resolve_paths(t, arg_root, node);
556✔
158
                if (r < 0)
556✔
159
                        return r;
160

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

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

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

181
        assert(c);
96✔
182

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

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

193
                STRV_FOREACH(dir, l) {
20✔
194
                        char *j;
16✔
195

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

200
                        dirs[i++] = j;
16✔
201
                }
202
        } else
203
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
92✔
204
        if (!dirs)
96✔
205
                return log_oom();
×
206

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

215
        STRV_FOREACH(p, files) {
188✔
216
                _cleanup_(feature_unrefp) Feature *f = NULL;
×
217

218
                f = feature_new();
92✔
219
                if (!f)
92✔
220
                        return log_oom();
×
221

222
                r = feature_read_definition(f, *p, (const char**) dirs);
92✔
223
                if (r < 0)
92✔
224
                        return r;
225

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

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

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

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

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

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

256
        return 0;
257
}
258

259
static int context_load_installed_instances(Context *c) {
96✔
260
        int r;
96✔
261

262
        assert(c);
96✔
263

264
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
192✔
265

266
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
570✔
267
                Transfer *t = *tr;
474✔
268

269
                r = resource_load_instances(
474✔
270
                                &t->target,
271
                                arg_verify >= 0 ? arg_verify : t->verify,
474✔
272
                                &c->web_cache);
378✔
273
                if (r < 0)
474✔
274
                        return r;
275
        }
276

277
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
178✔
278
                Transfer *t = *tr;
82✔
279

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

288
        return 0;
289
}
290

291
static int context_load_available_instances(Context *c) {
76✔
292
        int r;
76✔
293

294
        assert(c);
76✔
295

296
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
152✔
297

298
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
454✔
299
                Transfer *t = *tr;
378✔
300

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

309
        return 0;
310
}
311

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

317
        assert(c);
166✔
318
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
166✔
319

320
        for (;;) {
780✔
321
                _cleanup_free_ Instance **cursor_instances = NULL;
×
322
                bool skip = false;
780✔
323
                UpdateSetFlags extra_flags = 0;
780✔
324
                _cleanup_free_ char *cursor = NULL;
614✔
325
                UpdateSet *us = NULL;
780✔
326

327
                /* First, let's find the newest version that's older than the boundary. */
328
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
4,274✔
329
                        Resource *rr;
3,570✔
330

331
                        assert(*tr);
3,570✔
332

333
                        if (flags == UPDATE_AVAILABLE)
3,570✔
334
                                rr = &(*tr)->source;
1,948✔
335
                        else {
336
                                assert(flags == UPDATE_INSTALLED);
1,622✔
337
                                rr = &(*tr)->target;
1,622✔
338
                        }
339

340
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
10,116✔
341
                                Instance *i = *inst; /* Sorted newest-to-oldest */
9,290✔
342

343
                                assert(i);
9,290✔
344

345
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
9,290✔
346
                                        continue; /* Not older than the boundary */
6,546✔
347

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

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

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

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

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

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

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

379
                        assert(t);
3,044✔
380

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

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

404
                        cursor_instances[k] = match;
2,988✔
405

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

409
                        if (strv_contains(t->protected_versions, cursor))
2,988✔
410
                                extra_flags |= UPDATE_PROTECTED;
×
411
                }
412

413
                r = free_and_strdup_warn(&boundary, cursor);
614✔
414
                if (r < 0)
614✔
415
                        return r;
416

417
                if (skip)
614✔
418
                        continue;
56✔
419

420
                /* See if we already have this update set in our table */
421
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
1,394✔
422
                        UpdateSet *u = *update_set;
1,032✔
423

424
                        if (strverscmp_improved(u->version, cursor) != 0)
1,032✔
425
                                continue;
836✔
426

427
                        /* Merge in what we've learned and continue onto the next version */
428

429
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE)) {
196✔
430
                                assert(u->n_instances == c->n_transfers);
72✔
431

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

435
                                for (size_t j = 0; j < u->n_instances; j++) {
448✔
436
                                        if (!u->instances[j])
376✔
437
                                                u->instances[j] = cursor_instances[j];
252✔
438

439
                                        /* Make sure that the list is full if the update is AVAILABLE */
440
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
376✔
441
                                }
442
                        }
443

444
                        u->flags |= flags | extra_flags;
196✔
445

446
                        /* If this is the newest installed version, that is incomplete and just became marked
447
                         * as available, and if there is no other candidate available, we promote this to be
448
                         * the candidate. */
449
                        if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
196✔
450
                            !c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
8✔
451
                                c->candidate = u;
8✔
452

453
                        skip = true;
196✔
454
                        newest_found = true;
196✔
455
                        break;
196✔
456
                }
457

458
                if (skip)
558✔
459
                        continue;
196✔
460

461
                /* Doesn't exist yet, let's add it */
462
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
362✔
463
                        return log_oom();
×
464

465
                us = new(UpdateSet, 1);
362✔
466
                if (!us)
362✔
467
                        return log_oom();
×
468

469
                *us = (UpdateSet) {
362✔
470
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
362✔
471
                        .version = TAKE_PTR(cursor),
362✔
472
                        .instances = TAKE_PTR(cursor_instances),
362✔
473
                        .n_instances = c->n_transfers,
362✔
474
                };
475

476
                c->update_sets[c->n_update_sets++] = us;
362✔
477

478
                newest_found = true;
362✔
479

480
                /* Remember which one is the newest installed */
481
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
362✔
482
                        c->newest_installed = us;
86✔
483

484
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate" */
485
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
362✔
486
                        c->candidate = us;
28✔
487
        }
488

489
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
490
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
166✔
491
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
138✔
492
                c->candidate = NULL;
×
493

494
        return 0;
495
}
496

497
static int context_discover_update_sets(Context *c) {
90✔
498
        int r;
90✔
499

500
        assert(c);
90✔
501

502
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
180✔
503

504
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
90✔
505
        if (r < 0)
90✔
506
                return r;
507

508
        if (!arg_offline) {
90✔
509
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
152✔
510

511
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
76✔
512
                if (r < 0)
76✔
513
                        return r;
514
        }
515

516
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
90✔
517
        return 0;
90✔
518
}
519

520
static int context_show_table(Context *c) {
×
521
        _cleanup_(table_unrefp) Table *t = NULL;
×
522
        int r;
×
523

524
        assert(c);
×
525

526
        t = table_new("", "version", "installed", "available", "assessment");
×
527
        if (!t)
×
528
                return log_oom();
×
529

530
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
531
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
532
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
533

534
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
535
                UpdateSet *us = *update_set;
×
536
                const char *color;
×
537

538
                color = update_set_flags_to_color(us->flags);
×
539

540
                r = table_add_many(t,
×
541
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
542
                                   TABLE_SET_COLOR, color,
543
                                   TABLE_STRING,    us->version,
544
                                   TABLE_SET_COLOR, color,
545
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
546
                                   TABLE_SET_COLOR, color,
547
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
548
                                   TABLE_SET_COLOR, color,
549
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
550
                                   TABLE_SET_COLOR, color);
551
                if (r < 0)
×
552
                        return table_log_add_error(r);
×
553
        }
554

555
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
556
}
557

558
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
22✔
559
        assert(c);
22✔
560
        assert(version);
22✔
561

562
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
42✔
563
                if (streq((*update_set)->version, version))
42✔
564
                        return *update_set;
565

566
        return NULL;
567
}
568

569
static int context_show_version(Context *c, const char *version) {
22✔
570
        bool show_fs_columns = false, show_partition_columns = false,
22✔
571
                have_fs_attributes = false, have_partition_attributes = false,
22✔
572
                have_size = false, have_tries = false, have_no_auto = false,
22✔
573
                have_read_only = false, have_growfs = false, have_sha256 = false;
22✔
574
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
22✔
575
        _cleanup_(table_unrefp) Table *t = NULL;
22✔
576
        _cleanup_strv_free_ char **changelog_urls = NULL;
22✔
577
        UpdateSet *us;
22✔
578
        int r;
22✔
579

580
        assert(c);
22✔
581
        assert(version);
22✔
582

583
        us = context_update_set_by_version(c, version);
22✔
584
        if (!us)
22✔
585
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
586

587
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
22✔
588
                (void) pager_open(arg_pager_flags);
10✔
589

590
        if (!sd_json_format_enabled(arg_json_format_flags))
22✔
591
                printf("%s%s%s Version: %s\n"
80✔
592
                       "    State: %s%s%s\n"
593
                       "Installed: %s%s\n"
594
                       "Available: %s%s\n"
595
                       "Protected: %s%s%s\n"
596
                       " Obsolete: %s%s%s\n\n",
597
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
10✔
598
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
10✔
599
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
10✔
600
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
20✔
601
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
10✔
602
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
10✔
603

604
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
22✔
605
        if (!t)
22✔
606
                return log_oom();
×
607

608
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
22✔
609
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
22✔
610
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
22✔
611
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
22✔
612
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
22✔
613
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
22✔
614
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
22✔
615

616
        /* Starting in v257, these fields would be automatically formatted with underscores. This would have
617
         * been a breaking change, so to avoid that let's hard-code their original names. */
618
        (void) table_set_json_field_name(t, 7, "tries-done");
22✔
619
        (void) table_set_json_field_name(t, 8, "tries-left");
22✔
620

621
        /* Determine if the target will make use of partition/fs attributes for any of the transfers */
622
        FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
136✔
623
                Transfer *tr = *transfer;
114✔
624

625
                if (tr->target.type == RESOURCE_PARTITION)
114✔
626
                        show_partition_columns = true;
44✔
627
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
114✔
628
                        show_fs_columns = true;
629

630
                STRV_FOREACH(changelog, tr->changelog) {
114✔
631
                        assert(*changelog);
×
632

633
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
634
                        if (!changelog_url)
×
635
                                return log_oom();
×
636

637
                        /* Avoid duplicates */
638
                        if (strv_contains(changelog_urls, changelog_url))
×
639
                                continue;
×
640

641
                        /* changelog_urls takes ownership of expanded changelog_url */
642
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
643
                        if (r < 0)
×
644
                                return log_oom();
×
645
                }
646
        }
647

648
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
136✔
649
                Instance *i = *inst;
114✔
650

651
                if (!i) {
114✔
652
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
4✔
653
                        continue;
4✔
654
                }
655

656
                r = table_add_many(t,
110✔
657
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
658
                                   TABLE_PATH, i->path);
659
                if (r < 0)
110✔
660
                        return table_log_add_error(r);
×
661

662
                if (i->metadata.partition_uuid_set) {
110✔
663
                        have_partition_attributes = true;
32✔
664
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
32✔
665
                } else
666
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
78✔
667
                if (r < 0)
110✔
668
                        return table_log_add_error(r);
×
669

670
                if (i->metadata.partition_flags_set) {
110✔
671
                        have_partition_attributes = true;
32✔
672
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
32✔
673
                } else
674
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
78✔
675
                if (r < 0)
110✔
676
                        return table_log_add_error(r);
×
677

678
                if (i->metadata.mtime != USEC_INFINITY) {
110✔
679
                        have_fs_attributes = true;
78✔
680
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
78✔
681
                } else
682
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
32✔
683
                if (r < 0)
110✔
684
                        return table_log_add_error(r);
×
685

686
                if (i->metadata.mode != MODE_INVALID) {
110✔
687
                        have_fs_attributes = true;
78✔
688
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
78✔
689
                } else
690
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
32✔
691
                if (r < 0)
110✔
692
                        return table_log_add_error(r);
×
693

694
                if (i->metadata.size != UINT64_MAX) {
110✔
695
                        have_size = true;
×
696
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
697
                } else
698
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
110✔
699
                if (r < 0)
110✔
700
                        return table_log_add_error(r);
×
701

702
                if (i->metadata.tries_done != UINT64_MAX) {
110✔
703
                        have_tries = true;
16✔
704
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
16✔
705
                } else
706
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
94✔
707
                if (r < 0)
110✔
708
                        return table_log_add_error(r);
×
709

710
                if (i->metadata.tries_left != UINT64_MAX) {
110✔
711
                        have_tries = true;
16✔
712
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
16✔
713
                } else
714
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
94✔
715
                if (r < 0)
110✔
716
                        return table_log_add_error(r);
×
717

718
                if (i->metadata.no_auto >= 0) {
110✔
719
                        bool b;
×
720

721
                        have_no_auto = true;
×
722
                        b = i->metadata.no_auto;
×
723
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
724
                } else
725
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
110✔
726
                if (r < 0)
110✔
727
                        return table_log_add_error(r);
×
728
                if (i->metadata.read_only >= 0) {
110✔
729
                        bool b;
32✔
730

731
                        have_read_only = true;
32✔
732
                        b = i->metadata.read_only;
32✔
733
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
32✔
734
                } else
735
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
78✔
736
                if (r < 0)
110✔
737
                        return table_log_add_error(r);
×
738

739
                if (i->metadata.growfs >= 0) {
110✔
740
                        bool b;
×
741

742
                        have_growfs = true;
×
743
                        b = i->metadata.growfs;
×
744
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
745
                } else
746
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
110✔
747
                if (r < 0)
110✔
748
                        return table_log_add_error(r);
×
749

750
                if (i->metadata.sha256sum_set) {
110✔
751
                        _cleanup_free_ char *formatted = NULL;
×
752

753
                        have_sha256 = true;
×
754

755
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
×
756
                        if (!formatted)
×
757
                                return log_oom();
×
758

759
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
×
760
                } else
761
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
110✔
762
                if (r < 0)
110✔
763
                        return table_log_add_error(r);
×
764
        }
765

766
        /* Hide the fs/partition columns if we don't have any data to show there */
767
        if (!have_fs_attributes)
22✔
768
                show_fs_columns = false;
×
769
        if (!have_partition_attributes)
22✔
770
                show_partition_columns = false;
771

772
        if (!show_partition_columns)
16✔
773
                (void) table_hide_column_from_display(t, 2, 3);
6✔
774
        if (!show_fs_columns)
22✔
775
                (void) table_hide_column_from_display(t, 4, 5);
×
776
        if (!have_size)
22✔
777
                (void) table_hide_column_from_display(t, 6);
22✔
778
        if (!have_tries)
22✔
779
                (void) table_hide_column_from_display(t, 7, 8);
6✔
780
        if (!have_no_auto)
22✔
781
                (void) table_hide_column_from_display(t, 9);
22✔
782
        if (!have_read_only)
22✔
783
                (void) table_hide_column_from_display(t, 10);
6✔
784
        if (!have_growfs)
22✔
785
                (void) table_hide_column_from_display(t, 11);
22✔
786
        if (!have_sha256)
22✔
787
                (void) table_hide_column_from_display(t, 12);
22✔
788

789
        if (!sd_json_format_enabled(arg_json_format_flags)) {
22✔
790
                printf("%s%s%s Version: %s\n"
100✔
791
                       "    State: %s%s%s\n"
792
                       "Installed: %s%s%s%s%s\n"
793
                       "Available: %s%s\n"
794
                       "Protected: %s%s%s\n"
795
                       " Obsolete: %s%s%s\n",
796
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
10✔
797
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
10✔
798
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
10✔
799
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
14✔
800
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
20✔
801
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
10✔
802
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
10✔
803

804
                STRV_FOREACH(url, changelog_urls) {
10✔
805
                        _cleanup_free_ char *changelog_link = NULL;
×
806
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
807
                        if (r < 0)
×
808
                                return log_oom();
×
809
                        printf("ChangeLog: %s\n", changelog_link);
×
810
                }
811
                printf("\n");
10✔
812

813
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
10✔
814
        } else {
815
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
12✔
816

817
                r = table_to_json(t, &t_json);
12✔
818
                if (r < 0)
12✔
819
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
820

821
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
12✔
822
                                          SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
823
                                          SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
824
                                          SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
825
                                          SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
826
                                          SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
827
                                          SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
828
                                          SD_JSON_BUILD_PAIR_STRV("changelogUrls", changelog_urls),
829
                                          SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
830
                if (r < 0)
12✔
831
                        return log_error_errno(r, "Failed to create JSON: %m");
×
832

833
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
12✔
834
                if (r < 0)
12✔
835
                        return log_error_errno(r, "Failed to print JSON: %m");
×
836

837
                return 0;
838
        }
839
}
840

841
static int context_vacuum(
20✔
842
                Context *c,
843
                uint64_t space,
844
                const char *extra_protected_version) {
845

846
        size_t disabled_count = 0;
20✔
847
        int r, count = 0;
20✔
848

849
        assert(c);
20✔
850

851
        if (space == 0)
20✔
852
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
4✔
853
        else
854
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
36✔
855

856
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
122✔
857
                Transfer *t = *tr;
102✔
858

859
                /* Don't bother clearing out space if we're not going to be downloading anything */
860
                if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
102✔
861
                        continue;
18✔
862

863
                r = transfer_vacuum(t, space, extra_protected_version);
84✔
864
                if (r < 0)
84✔
865
                        return r;
866

867
                count = MAX(count, r);
84✔
868
        }
869

870
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
38✔
871
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
18✔
872
                if (r < 0)
18✔
873
                        return r;
874
                if (r > 0)
18✔
875
                        disabled_count++;
2✔
876
        }
877

878
        if (!sd_json_format_enabled(arg_json_format_flags)) {
20✔
879
                if (count > 0 && disabled_count > 0)
18✔
880
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
881
                else if (count > 0)
18✔
882
                        log_info("Removed %i instances.", count);
8✔
883
                else if (disabled_count > 0)
10✔
884
                        log_info("Removed %zu disabled transfers.", disabled_count);
2✔
885
                else
886
                        log_info("Found nothing to remove.");
20✔
887
        } else {
888
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
2✔
889

890
                r = sd_json_buildo(&json,
2✔
891
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
892
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
893
                if (r < 0)
2✔
894
                        return log_error_errno(r, "Failed to create JSON: %m");
×
895

896
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
2✔
897
                if (r < 0)
2✔
898
                        return log_error_errno(r, "Failed to print JSON: %m");
×
899
        }
900

901
        return 0;
902
}
903

904
static int context_make_offline(Context **ret, const char *node, bool requires_enabled_transfers) {
96✔
905
        _cleanup_(context_freep) Context* context = NULL;
96✔
906
        int r;
96✔
907

908
        assert(ret);
96✔
909

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

913
        context = context_new();
96✔
914
        if (!context)
96✔
915
                return log_oom();
×
916

917
        r = context_read_definitions(context, node, requires_enabled_transfers);
96✔
918
        if (r < 0)
96✔
919
                return r;
920

921
        r = context_load_installed_instances(context);
96✔
922
        if (r < 0)
96✔
923
                return r;
924

925
        *ret = TAKE_PTR(context);
96✔
926
        return 0;
96✔
927
}
928

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

933
        assert(ret);
90✔
934

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

938
        r = context_make_offline(&context, node, /* requires_enabled_transfers= */ true);
90✔
939
        if (r < 0)
90✔
940
                return r;
941

942
        if (!arg_offline) {
90✔
943
                r = context_load_available_instances(context);
76✔
944
                if (r < 0)
76✔
945
                        return r;
946
        }
947

948
        r = context_discover_update_sets(context);
90✔
949
        if (r < 0)
90✔
950
                return r;
951

952
        *ret = TAKE_PTR(context);
90✔
953
        return 0;
90✔
954
}
955

956
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
71✔
957
        const Context *c = ASSERT_PTR(t->context);
71✔
958
        size_t i, n = c->n_transfers;
71✔
959
        uint64_t base, scaled;
71✔
960
        unsigned overall;
71✔
961

962
        for (i = 0; i < n; i++)
207✔
963
                if (c->transfers[i] == t)
207✔
964
                        break;
965
        assert(i < n); /* We should have found the index */
71✔
966

967
        base = (100 * 100 * i) / n;
71✔
968
        scaled = (100 * percentage) / n;
71✔
969
        overall = (unsigned) ((base + scaled) / 100);
71✔
970
        assert(overall <= 100);
71✔
971

972
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
71✔
973
        return sd_notifyf(/* unset= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
142✔
974
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
975
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
976
                                              "STATUS=Updating to '%s' (%u%% complete).",
977
                                              overall, n - i, i, inst->metadata.version, overall);
71✔
978
}
979

980
static int context_apply(
18✔
981
                Context *c,
982
                const char *version,
983
                UpdateSet **ret_applied) {
984

985
        UpdateSet *us = NULL;
18✔
986
        int r;
18✔
987

988
        assert(c);
18✔
989

990
        if (version) {
18✔
991
                us = context_update_set_by_version(c, version);
×
992
                if (!us)
×
993
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
994
        } else {
995
                if (!c->candidate) {
18✔
996
                        log_info("No update needed.");
×
997

998
                        if (ret_applied)
×
999
                                *ret_applied = NULL;
×
1000

1001
                        return 0;
×
1002
                }
1003

1004
                us = c->candidate;
1005
        }
1006

1007
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
18✔
1008
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
4✔
1009
        else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
14✔
1010
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1011

1012
                if (ret_applied)
×
1013
                        *ret_applied = NULL;
×
1014

1015
                return 0;
×
1016
        }
1017

1018
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
18✔
1019
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1020
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
18✔
1021
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1022

1023
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
18✔
1024
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1025
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
18✔
1026
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1027

1028
        log_info("Selected update '%s' for install.", us->version);
18✔
1029

1030
        (void) sd_notifyf(/* unset= */ false,
18✔
1031
                          "READY=1\n"
1032
                          "X_SYSUPDATE_VERSION=%s\n"
1033
                          "STATUS=Making room for '%s'.", us->version, us->version);
1034

1035
        /* Let's make some room. We make sure for each transfer we have one free space to fill. While
1036
         * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
1037
         * download succeeded already, in which case we shouldn't remove it just to acquire it again */
1038
        r = context_vacuum(
36✔
1039
                        c,
1040
                        /* space = */ 1,
1041
                        /* extra_protected_version = */ us->version);
18✔
1042
        if (r < 0)
18✔
1043
                return r;
1044

1045
        if (arg_sync)
18✔
1046
                sync();
18✔
1047

1048
        (void) sd_notifyf(/* unset= */ false,
18✔
1049
                          "STATUS=Updating to '%s'.", us->version);
1050

1051
        /* There should now be one instance picked for each transfer, and the order is the same */
1052
        assert(us->n_instances == c->n_transfers);
18✔
1053

1054
        for (size_t i = 0; i < c->n_transfers; i++) {
110✔
1055
                Instance *inst = us->instances[i];
92✔
1056
                Transfer *t = c->transfers[i];
92✔
1057

1058
                assert(inst); /* ditto */
92✔
1059

1060
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
92✔
1061
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
18✔
1062
                        continue;
18✔
1063
                }
1064

1065
                r = transfer_acquire_instance(t, inst, context_on_acquire_progress, c);
74✔
1066
                if (r < 0)
74✔
1067
                        return r;
1068
        }
1069

1070
        if (arg_sync)
18✔
1071
                sync();
18✔
1072

1073
        (void) sd_notifyf(/* unset= */ false,
18✔
1074
                          "STATUS=Installing '%s'.", us->version);
1075

1076
        for (size_t i = 0; i < c->n_transfers; i++) {
110✔
1077
                Instance *inst = us->instances[i];
92✔
1078
                Transfer *t = c->transfers[i];
92✔
1079

1080
                if (inst->resource == &t->target)
92✔
1081
                        continue;
18✔
1082

1083
                r = transfer_install_instance(t, inst, arg_root);
74✔
1084
                if (r < 0)
74✔
1085
                        return r;
1086
        }
1087

1088
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
18✔
1089

1090
        if (ret_applied)
18✔
1091
                *ret_applied = us;
18✔
1092

1093
        return 1;
1094
}
1095

1096
static int process_image(
106✔
1097
                bool ro,
1098
                char **ret_mounted_dir,
1099
                LoopDevice **ret_loop_device) {
1100

1101
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
106✔
1102
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
106✔
1103
        int r;
106✔
1104

1105
        assert(ret_mounted_dir);
106✔
1106
        assert(ret_loop_device);
106✔
1107

1108
        if (!arg_image)
106✔
1109
                return 0;
1110

1111
        assert(!arg_root);
×
1112

1113
        r = mount_image_privately_interactively(
×
1114
                        arg_image,
1115
                        arg_image_policy,
1116
                        (ro ? DISSECT_IMAGE_READ_ONLY : 0) |
1117
                        DISSECT_IMAGE_FSCK |
1118
                        DISSECT_IMAGE_MKDIR |
1119
                        DISSECT_IMAGE_GROWFS |
1120
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1121
                        DISSECT_IMAGE_USR_NO_ROOT |
1122
                        DISSECT_IMAGE_GENERIC_ROOT |
1123
                        DISSECT_IMAGE_REQUIRE_ROOT |
1124
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1125
                        &mounted_dir,
1126
                        /* ret_dir_fd= */ NULL,
1127
                        &loop_device);
1128
        if (r < 0)
×
1129
                return r;
1130

1131
        arg_root = strdup(mounted_dir);
×
1132
        if (!arg_root)
×
1133
                return log_oom();
×
1134

1135
        *ret_mounted_dir = TAKE_PTR(mounted_dir);
×
1136
        *ret_loop_device = TAKE_PTR(loop_device);
×
1137

1138
        return 0;
×
1139
}
1140

1141
static int verb_list(int argc, char **argv, void *userdata) {
28✔
1142
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
28✔
1143
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
28✔
1144
        _cleanup_(context_freep) Context* context = NULL;
28✔
1145
        _cleanup_strv_free_ char **appstream_urls = NULL;
28✔
1146
        const char *version;
28✔
1147
        int r;
28✔
1148

1149
        assert(argc <= 2);
28✔
1150
        version = argc >= 2 ? argv[1] : NULL;
28✔
1151

1152
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
28✔
1153
        if (r < 0)
28✔
1154
                return r;
1155

1156
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
28✔
1157
        if (r < 0)
28✔
1158
                return r;
1159

1160
        if (version)
28✔
1161
                return context_show_version(context, version);
22✔
1162
        else if (!sd_json_format_enabled(arg_json_format_flags))
6✔
1163
                return context_show_table(context);
×
1164
        else {
1165
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
6✔
1166
                _cleanup_strv_free_ char **versions = NULL;
6✔
1167
                const char *current = NULL;
6✔
1168

1169
                FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
26✔
1170
                        UpdateSet *us = *update_set;
20✔
1171

1172
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
20✔
1173
                            FLAGS_SET(us->flags, UPDATE_NEWEST))
1174
                                current = us->version;
6✔
1175

1176
                        r = strv_extend(&versions, us->version);
20✔
1177
                        if (r < 0)
20✔
1178
                                return log_oom();
×
1179
                }
1180

1181
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
28✔
1182
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
22✔
1183
                                /* Avoid duplicates */
1184
                                if (strv_contains(appstream_urls, *appstream_url))
×
1185
                                        continue;
×
1186

1187
                                r = strv_extend(&appstream_urls, *appstream_url);
×
1188
                                if (r < 0)
×
1189
                                        return log_oom();
×
1190
                        }
1191

1192
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
6✔
1193
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1194
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1195
                if (r < 0)
6✔
1196
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1197

1198
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
6✔
1199
                if (r < 0)
6✔
1200
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1201

1202
                return 0;
1203
        }
1204
}
1205

1206
static int verb_features(int argc, char **argv, void *userdata) {
4✔
1207
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
4✔
1208
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
4✔
1209
        _cleanup_(context_freep) Context* context = NULL;
4✔
1210
        _cleanup_(table_unrefp) Table *table = NULL;
4✔
1211
        const char *feature_id;
4✔
1212
        Feature *f;
4✔
1213
        int r;
4✔
1214

1215
        assert(argc <= 2);
4✔
1216
        feature_id = argc >= 2 ? argv[1] : NULL;
4✔
1217

1218
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
4✔
1219
        if (r < 0)
4✔
1220
                return r;
1221

1222
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
4✔
1223
        if (r < 0)
4✔
1224
                return r;
1225

1226
        if (feature_id) {
4✔
1227
                _cleanup_strv_free_ char **transfers = NULL;
2✔
1228

1229
                f = hashmap_get(context->features, feature_id);
2✔
1230
                if (!f)
2✔
1231
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1232
                                               "Optional feature not found: %s",
1233
                                               feature_id);
1234

1235
                table = table_new_vertical();
2✔
1236
                if (!table)
2✔
1237
                        return log_oom();
×
1238

1239
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
12✔
1240
                        Transfer *t = *tr;
10✔
1241

1242
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
10✔
1243
                                continue;
10✔
1244

1245
                        r = strv_extend(&transfers, t->id);
×
1246
                        if (r < 0)
×
1247
                                return log_oom();
×
1248
                }
1249

1250
                FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) {
4✔
1251
                        Transfer *t = *tr;
2✔
1252

1253
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
2✔
1254
                                continue;
×
1255

1256
                        r = strv_extend(&transfers, t->id);
2✔
1257
                        if (r < 0)
2✔
1258
                                return log_oom();
×
1259
                }
1260

1261
                r = table_add_many(table,
2✔
1262
                                   TABLE_FIELD, "Name",
1263
                                   TABLE_STRING, f->id,
1264
                                   TABLE_FIELD, "Enabled",
1265
                                   TABLE_BOOLEAN, f->enabled);
1266
                if (r < 0)
2✔
1267
                        return table_log_add_error(r);
×
1268

1269
                if (f->description) {
2✔
1270
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
2✔
1271
                        if (r < 0)
2✔
1272
                                return table_log_add_error(r);
×
1273
                }
1274

1275
                if (f->documentation) {
2✔
1276
                        r = table_add_many(table,
×
1277
                                           TABLE_FIELD, "Documentation",
1278
                                           TABLE_STRING, f->documentation,
1279
                                           TABLE_SET_URL, f->documentation);
1280
                        if (r < 0)
×
1281
                                return table_log_add_error(r);
×
1282
                }
1283

1284
                if (f->appstream) {
2✔
1285
                        r = table_add_many(table,
×
1286
                                           TABLE_FIELD, "AppStream",
1287
                                           TABLE_STRING, f->appstream,
1288
                                           TABLE_SET_URL, f->appstream);
1289
                        if (r < 0)
×
1290
                                return table_log_add_error(r);
×
1291
                }
1292

1293
                if (!strv_isempty(transfers)) {
2✔
1294
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
2✔
1295
                        if (r < 0)
2✔
1296
                                return table_log_add_error(r);
×
1297
                }
1298

1299
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
2✔
1300
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
2✔
1301
                table = table_new("", "feature", "description", "documentation");
2✔
1302
                if (!table)
2✔
1303
                        return log_oom();
×
1304

1305
                HASHMAP_FOREACH(f, context->features) {
4✔
1306
                        r = table_add_many(table,
2✔
1307
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1308
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
1309
                                           TABLE_STRING, f->id,
1310
                                           TABLE_STRING, f->description,
1311
                                           TABLE_STRING, f->documentation,
1312
                                           TABLE_SET_URL, f->documentation);
1313
                        if (r < 0)
2✔
1314
                                return table_log_add_error(r);
×
1315
                }
1316

1317
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
2✔
1318
        } else {
1319
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1320
                _cleanup_strv_free_ char **features = NULL;
×
1321

1322
                HASHMAP_FOREACH(f, context->features) {
×
1323
                        r = strv_extend(&features, f->id);
×
1324
                        if (r < 0)
×
1325
                                return log_oom();
×
1326
                }
1327

1328
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
1329
                if (r < 0)
×
1330
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1331

1332
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
×
1333
                if (r < 0)
×
1334
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1335
        }
1336

1337
        return 0;
×
1338
}
1339

1340
static int verb_check_new(int argc, char **argv, void *userdata) {
44✔
1341
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
44✔
1342
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
44✔
1343
        _cleanup_(context_freep) Context* context = NULL;
44✔
1344
        int r;
44✔
1345

1346
        assert(argc <= 1);
44✔
1347

1348
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
44✔
1349
        if (r < 0)
44✔
1350
                return r;
1351

1352
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
44✔
1353
        if (r < 0)
44✔
1354
                return r;
1355

1356
        if (!sd_json_format_enabled(arg_json_format_flags)) {
44✔
1357
                if (!context->candidate) {
40✔
1358
                        log_debug("No candidate found.");
22✔
1359
                        return EXIT_FAILURE;
22✔
1360
                }
1361

1362
                puts(context->candidate->version);
18✔
1363
        } else {
1364
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
4✔
1365

1366
                if (context->candidate)
4✔
1367
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version));
×
1368
                else
1369
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
4✔
1370
                if (r < 0)
4✔
1371
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1372

1373
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
4✔
1374
                if (r < 0)
4✔
1375
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1376
        }
1377

1378
        return EXIT_SUCCESS;
1379
}
1380

1381
static int verb_vacuum(int argc, char **argv, void *userdata) {
2✔
1382
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
2✔
1383
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
2✔
1384
        _cleanup_(context_freep) Context* context = NULL;
2✔
1385
        int r;
2✔
1386

1387
        assert(argc <= 1);
2✔
1388

1389
        if (arg_instances_max < 1)
2✔
1390
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1391
                                      "The --instances-max argument must be >= 1 while vacuuming");
1392

1393
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
2✔
1394
        if (r < 0)
2✔
1395
                return r;
1396

1397
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
2✔
1398
        if (r < 0)
2✔
1399
                return r;
1400

1401
        return context_vacuum(context, 0, NULL);
2✔
1402
}
1403

1404
static int verb_update(int argc, char **argv, void *userdata) {
18✔
1405
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
18✔
1406
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
18✔
1407
        _cleanup_(context_freep) Context* context = NULL;
×
1408
        _cleanup_free_ char *booted_version = NULL;
18✔
1409
        UpdateSet *applied = NULL;
18✔
1410
        const char *version;
18✔
1411
        int r;
18✔
1412

1413
        assert(argc <= 2);
18✔
1414
        version = argc >= 2 ? argv[1] : NULL;
18✔
1415

1416
        if (arg_instances_max < 2)
18✔
1417
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1418
                                      "The --instances-max argument must be >= 2 while updating");
1419

1420
        if (arg_reboot) {
18✔
1421
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
1422

1423
                r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
1424
                if (r < 0)
×
1425
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1426
                if (!booted_version)
×
1427
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
1428
        }
1429

1430
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
18✔
1431
        if (r < 0)
18✔
1432
                return r;
1433

1434
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
18✔
1435
        if (r < 0)
18✔
1436
                return r;
1437

1438
        r = context_apply(context, version, &applied);
18✔
1439
        if (r < 0)
18✔
1440
                return r;
1441

1442
        if (r > 0 && arg_reboot) {
18✔
1443
                assert(applied);
×
1444
                assert(booted_version);
×
1445

1446
                if (strverscmp_improved(applied->version, booted_version) > 0) {
×
1447
                        log_notice("Newly installed version is newer than booted version, rebooting.");
×
1448
                        return reboot_now();
×
1449
                }
1450

1451
                if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
1452
                    FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
1453
                        log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
1454
                        return reboot_now();
×
1455
                }
1456

1457
                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
18✔
1458
        }
1459

1460
        return 0;
1461
}
1462

1463
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
×
1464
        _cleanup_(context_freep) Context* context = NULL;
×
1465
        _cleanup_free_ char *booted_version = NULL;
×
1466
        int r;
×
1467

1468
        assert(argc == 1);
×
1469

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

1474
        r = context_make_offline(&context, /* node= */ NULL, /* requires_enabled_transfers= */ true);
×
1475
        if (r < 0)
×
1476
                return r;
1477

1478
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
1479

1480
        r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
×
1481
        if (r < 0)
×
1482
                return r;
1483
        if (!context->newest_installed)
×
1484
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
1485

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

1493
        r = strverscmp_improved(context->newest_installed->version, booted_version);
×
1494
        if (r > 0) {
×
1495
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
1496
                           context->newest_installed->version, booted_version,
1497
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
1498

1499
                if (streq(argv[0], "reboot"))
×
1500
                        return reboot_now();
×
1501

1502
                return EXIT_SUCCESS;
1503
        } else if (r == 0)
×
1504
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
1505
                         context->newest_installed->version, booted_version);
1506
        else
1507
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
1508
                            context->newest_installed->version, booted_version);
1509

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

1513
        return EXIT_SUCCESS;
1514
}
1515

1516
static int component_name_valid(const char *c) {
12✔
1517
        _cleanup_free_ char *j = NULL;
12✔
1518

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

1521
        if (isempty(c))
24✔
1522
                return false;
1523

1524
        if (string_has_cc(c, NULL))
12✔
1525
                return false;
1526

1527
        if (!utf8_is_valid(c))
12✔
1528
                return false;
1529

1530
        j = strjoin("sysupdate.", c, ".d");
12✔
1531
        if (!j)
12✔
1532
                return -ENOMEM;
1533

1534
        return filename_is_valid(j);
12✔
1535
}
1536

1537
static int verb_components(int argc, char **argv, void *userdata) {
10✔
1538
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
10✔
1539
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
10✔
1540
        _cleanup_set_free_ Set *names = NULL;
×
1541
        _cleanup_free_ char **z = NULL; /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
10✔
1542
        char **l = CONF_PATHS_STRV("");
10✔
1543
        bool has_default_component = false;
10✔
1544
        int r;
10✔
1545

1546
        assert(argc <= 1);
10✔
1547

1548
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
10✔
1549
        if (r < 0)
10✔
1550
                return r;
1551

1552
        STRV_FOREACH(i, l) {
50✔
1553
                _cleanup_closedir_ DIR *d = NULL;
40✔
1554
                _cleanup_free_ char *p = NULL;
40✔
1555

1556
                r = chase_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
40✔
1557
                if (r == -ENOENT)
40✔
1558
                        continue;
×
1559
                if (r < 0)
40✔
1560
                        return log_error_errno(r, "Failed to open directory '%s': %m", *i);
×
1561

1562
                for (;;) {
18,038✔
1563
                        _cleanup_free_ char *n = NULL;
17,990✔
1564
                        struct dirent *de;
18,038✔
1565
                        const char *e, *a;
18,038✔
1566

1567
                        de = readdir_ensure_type(d);
18,038✔
1568
                        if (!de) {
18,038✔
1569
                                if (errno != 0)
40✔
1570
                                        return log_error_errno(errno, "Failed to enumerate directory '%s': %m", p);
×
1571

1572
                                break;
40✔
1573
                        }
1574

1575
                        if (de->d_type != DT_DIR)
17,998✔
1576
                                continue;
16,260✔
1577

1578
                        if (dot_or_dot_dot(de->d_name))
1,738✔
1579
                                continue;
80✔
1580

1581
                        if (streq(de->d_name, "sysupdate.d")) {
1,658✔
1582
                                has_default_component = true;
10✔
1583
                                continue;
10✔
1584
                        }
1585

1586
                        e = startswith(de->d_name, "sysupdate.");
1,648✔
1587
                        if (!e)
1,648✔
1588
                                continue;
1,640✔
1589

1590
                        a = endswith(e, ".d");
8✔
1591
                        if (!a)
8✔
1592
                                continue;
×
1593

1594
                        n = strndup(e, a - e);
8✔
1595
                        if (!n)
8✔
1596
                                return log_oom();
×
1597

1598
                        r = component_name_valid(n);
8✔
1599
                        if (r < 0)
8✔
1600
                                return log_error_errno(r, "Unable to validate component name: %m");
×
1601
                        if (r == 0)
8✔
1602
                                continue;
×
1603

1604
                        r = set_ensure_consume(&names, &string_hash_ops_free, TAKE_PTR(n));
8✔
1605
                        if (r < 0 && r != -EEXIST)
8✔
1606
                                return log_error_errno(r, "Failed to add component to set: %m");
×
1607
                }
1608
        }
1609

1610
        z = set_get_strv(names);
10✔
1611
        if (!z)
10✔
1612
                return log_oom();
×
1613

1614
        strv_sort(z);
10✔
1615

1616
        if (!sd_json_format_enabled(arg_json_format_flags)) {
10✔
1617
                if (!has_default_component && set_isempty(names)) {
×
1618
                        log_info("No components defined.");
×
1619
                        return 0;
×
1620
                }
1621

1622
                if (has_default_component)
×
1623
                        printf("%s<default>%s\n",
×
1624
                               ansi_highlight(), ansi_normal());
1625

1626
                STRV_FOREACH(i, z)
×
1627
                        puts(*i);
×
1628
        } else {
1629
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
10✔
1630

1631
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
10✔
1632
                                          SD_JSON_BUILD_PAIR_STRV("components", z));
1633
                if (r < 0)
10✔
1634
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1635

1636
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
10✔
1637
                if (r < 0)
10✔
1638
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1639
        }
1640

1641
        return 0;
1642
}
1643

1644
static int verb_help(int argc, char **argv, void *userdata) {
×
1645
        _cleanup_free_ char *link = NULL;
×
1646
        int r;
×
1647

1648
        r = terminal_urlify_man("systemd-sysupdate", "8", &link);
×
1649
        if (r < 0)
×
1650
                return log_oom();
×
1651

1652
        printf("%1$s [OPTIONS...] [VERSION]\n"
×
1653
               "\n%5$sUpdate OS images.%6$s\n"
1654
               "\n%3$sCommands:%4$s\n"
1655
               "  list [VERSION]          Show installed and available versions\n"
1656
               "  features [FEATURE]      Show optional features\n"
1657
               "  check-new               Check if there's a new version available\n"
1658
               "  update [VERSION]        Install new version now\n"
1659
               "  vacuum                  Make room, by deleting old versions\n"
1660
               "  pending                 Report whether a newer version is installed than\n"
1661
               "                          currently booted\n"
1662
               "  reboot                  Reboot if a newer version is installed than booted\n"
1663
               "  components              Show list of components\n"
1664
               "  -h --help               Show this help\n"
1665
               "     --version            Show package version\n"
1666
               "\n%3$sOptions:%4$s\n"
1667
               "  -C --component=NAME     Select component to update\n"
1668
               "     --definitions=DIR    Find transfer definitions in specified directory\n"
1669
               "     --root=PATH          Operate on an alternate filesystem root\n"
1670
               "     --image=PATH         Operate on disk image as filesystem root\n"
1671
               "     --image-policy=POLICY\n"
1672
               "                          Specify disk image dissection policy\n"
1673
               "  -m --instances-max=INT  How many instances to maintain\n"
1674
               "     --sync=BOOL          Controls whether to sync data to disk\n"
1675
               "     --verify=BOOL        Force signature verification on or off\n"
1676
               "     --reboot             Reboot after updating to newer version\n"
1677
               "     --offline            Do not fetch metadata from the network\n"
1678
               "     --no-pager           Do not pipe output into a pager\n"
1679
               "     --no-legend          Do not show the headers and footers\n"
1680
               "     --json=pretty|short|off\n"
1681
               "                          Generate JSON output\n"
1682
               "     --transfer-source=PATH\n"
1683
               "                          Specify the directory to transfer sources from\n"
1684
               "\nSee the %2$s for details.\n",
1685
               program_invocation_short_name,
1686
               link,
1687
               ansi_underline(),
1688
               ansi_normal(),
1689
               ansi_highlight(),
1690
               ansi_normal());
1691

1692
        return 0;
1693
}
1694

1695
static int parse_argv(int argc, char *argv[]) {
106✔
1696

1697
        enum {
106✔
1698
                ARG_VERSION = 0x100,
1699
                ARG_NO_PAGER,
1700
                ARG_NO_LEGEND,
1701
                ARG_SYNC,
1702
                ARG_DEFINITIONS,
1703
                ARG_JSON,
1704
                ARG_ROOT,
1705
                ARG_IMAGE,
1706
                ARG_IMAGE_POLICY,
1707
                ARG_REBOOT,
1708
                ARG_VERIFY,
1709
                ARG_OFFLINE,
1710
                ARG_TRANSFER_SOURCE,
1711
        };
1712

1713
        static const struct option options[] = {
106✔
1714
                { "help",              no_argument,       NULL, 'h'                   },
1715
                { "version",           no_argument,       NULL, ARG_VERSION           },
1716
                { "no-pager",          no_argument,       NULL, ARG_NO_PAGER          },
1717
                { "no-legend",         no_argument,       NULL, ARG_NO_LEGEND         },
1718
                { "definitions",       required_argument, NULL, ARG_DEFINITIONS       },
1719
                { "instances-max",     required_argument, NULL, 'm'                   },
1720
                { "sync",              required_argument, NULL, ARG_SYNC              },
1721
                { "json",              required_argument, NULL, ARG_JSON              },
1722
                { "root",              required_argument, NULL, ARG_ROOT              },
1723
                { "image",             required_argument, NULL, ARG_IMAGE             },
1724
                { "image-policy",      required_argument, NULL, ARG_IMAGE_POLICY      },
1725
                { "reboot",            no_argument,       NULL, ARG_REBOOT            },
1726
                { "component",         required_argument, NULL, 'C'                   },
1727
                { "verify",            required_argument, NULL, ARG_VERIFY            },
1728
                { "offline",           no_argument,       NULL, ARG_OFFLINE           },
1729
                { "transfer-source",   required_argument, NULL, ARG_TRANSFER_SOURCE   },
1730
                {}
1731
        };
1732

1733
        int c, r;
106✔
1734

1735
        assert(argc >= 0);
106✔
1736
        assert(argv);
106✔
1737

1738
        while ((c = getopt_long(argc, argv, "hm:C:", options, NULL)) >= 0) {
234✔
1739

1740
                switch (c) {
128✔
1741

1742
                case 'h':
×
1743
                        return verb_help(0, NULL, NULL);
×
1744

1745
                case ARG_VERSION:
×
1746
                        return version();
×
1747

1748
                case ARG_NO_PAGER:
×
1749
                        arg_pager_flags |= PAGER_DISABLE;
×
1750
                        break;
×
1751

1752
                case ARG_NO_LEGEND:
×
1753
                        arg_legend = false;
×
1754
                        break;
×
1755

1756
                case 'm':
×
1757
                        r = safe_atou64(optarg, &arg_instances_max);
×
1758
                        if (r < 0)
×
1759
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg);
×
1760

1761
                        break;
1762

1763
                case ARG_SYNC:
×
1764
                        r = parse_boolean_argument("--sync=", optarg, &arg_sync);
×
1765
                        if (r < 0)
×
1766
                                return r;
1767
                        break;
1768

1769
                case ARG_DEFINITIONS:
×
1770
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions);
×
1771
                        if (r < 0)
×
1772
                                return r;
1773
                        break;
1774

1775
                case ARG_JSON:
34✔
1776
                        r = parse_json_argument(optarg, &arg_json_format_flags);
34✔
1777
                        if (r <= 0)
34✔
1778
                                return r;
1779

1780
                        break;
1781

1782
                case ARG_ROOT:
×
1783
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
×
1784
                        if (r < 0)
×
1785
                                return r;
1786
                        break;
1787

1788
                case ARG_IMAGE:
×
1789
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
1790
                        if (r < 0)
×
1791
                                return r;
1792
                        break;
1793

1794
                case ARG_IMAGE_POLICY:
×
1795
                        r = parse_image_policy_argument(optarg, &arg_image_policy);
×
1796
                        if (r < 0)
×
1797
                                return r;
1798
                        break;
1799

1800
                case ARG_REBOOT:
×
1801
                        arg_reboot = true;
×
1802
                        break;
×
1803

1804
                case 'C':
4✔
1805
                        if (isempty(optarg)) {
4✔
1806
                                arg_component = mfree(arg_component);
×
1807
                                break;
×
1808
                        }
1809

1810
                        r = component_name_valid(optarg);
4✔
1811
                        if (r < 0)
4✔
1812
                                return log_error_errno(r, "Failed to determine if component name is valid: %m");
×
1813
                        if (r == 0)
4✔
1814
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg);
×
1815

1816
                        r = free_and_strdup_warn(&arg_component, optarg);
4✔
1817
                        if (r < 0)
4✔
1818
                                return r;
1819

1820
                        break;
1821

1822
                case ARG_VERIFY: {
76✔
1823
                        bool b;
76✔
1824

1825
                        r = parse_boolean_argument("--verify=", optarg, &b);
76✔
1826
                        if (r < 0)
76✔
1827
                                return r;
×
1828

1829
                        arg_verify = b;
76✔
1830
                        break;
76✔
1831
                }
1832

1833
                case ARG_OFFLINE:
14✔
1834
                        arg_offline = true;
14✔
1835
                        break;
14✔
1836

1837
                case ARG_TRANSFER_SOURCE:
×
1838
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source);
×
1839
                        if (r < 0)
×
1840
                                return r;
1841

1842
                        break;
1843

1844
                case '?':
1845
                        return -EINVAL;
1846

1847
                default:
×
1848
                        assert_not_reached();
×
1849
                }
1850
        }
1851

1852
        if (arg_image && arg_root)
106✔
1853
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
1854

1855
        if ((arg_image || arg_root) && arg_reboot)
106✔
1856
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
1857

1858
        if (arg_definitions && arg_component)
106✔
1859
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
1860

1861
        return 1;
1862
}
1863

1864
static int sysupdate_main(int argc, char *argv[]) {
106✔
1865

1866
        static const Verb verbs[] = {
106✔
1867
                { "list",       VERB_ANY, 2, VERB_DEFAULT, verb_list              },
1868
                { "components", VERB_ANY, 1, 0,            verb_components        },
1869
                { "features",   VERB_ANY, 2, 0,            verb_features          },
1870
                { "check-new",  VERB_ANY, 1, 0,            verb_check_new         },
1871
                { "update",     VERB_ANY, 2, 0,            verb_update            },
1872
                { "vacuum",     VERB_ANY, 1, 0,            verb_vacuum            },
1873
                { "reboot",     1,        1, 0,            verb_pending_or_reboot },
1874
                { "pending",    1,        1, 0,            verb_pending_or_reboot },
1875
                { "help",       VERB_ANY, 1, 0,            verb_help              },
1876
                {}
1877
        };
1878

1879
        return dispatch_verb(argc, argv, verbs, NULL);
106✔
1880
}
1881

1882
static int run(int argc, char *argv[]) {
106✔
1883
        int r;
106✔
1884

1885
        log_setup();
106✔
1886

1887
        r = parse_argv(argc, argv);
106✔
1888
        if (r <= 0)
106✔
1889
                return r;
1890

1891
        /* SIGCHLD signal must be blocked for sd_event_add_child to work */
1892
        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
106✔
1893

1894
        return sysupdate_main(argc, argv);
106✔
1895
}
1896

1897
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
212✔
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