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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

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

3
#include <unistd.h>
4

5
#include "sd-daemon.h"
6

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

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

54
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
663✔
55
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
663✔
56
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
663✔
57
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
663✔
58
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
663✔
59
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
663✔
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) {
663✔
85
        if (!c)
663✔
86
                return NULL;
87

88
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
5,288✔
89
                transfer_free(*tr);
4,625✔
90
        free(c->transfers);
663✔
91

92
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
1,263✔
93
                transfer_free(*tr);
600✔
94
        free(c->disabled_transfers);
663✔
95

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

98
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
3,104✔
99
                update_set_free(*us);
2,441✔
100
        free(c->update_sets);
663✔
101

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

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

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

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

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

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

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

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

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

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

141
        FOREACH_ARRAY(i, files, n_files) {
5,890✔
142
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
143
                Transfer **appended;
5,225✔
144
                ConfFile *e = *i;
5,225✔
145

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

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

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

158
                if (t->enabled)
5,225✔
159
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
4,625✔
160
                else
161
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
600✔
162
                if (!appended)
5,225✔
163
                        return log_oom();
×
164
                TAKE_PTR(t);
5,225✔
165
        }
166

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

174
typedef enum ReadDefinitionsFlags {
175
        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0,
176
        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS     = 1 << 1,
177
} ReadDefinitionsFlags;
178

179
static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
663✔
180
        _cleanup_strv_free_ char **dirs = NULL;
663✔
181
        int r;
663✔
182

183
        assert(c);
663✔
184

185
        if (arg_definitions)
663✔
UNCOV
186
                dirs = strv_new(arg_definitions);
×
187
        else if (arg_component) {
663✔
188
                char **l = CONF_PATHS_STRV("");
8✔
189
                size_t i = 0;
8✔
190

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

195
                STRV_FOREACH(dir, l) {
40✔
196
                        char *j;
32✔
197

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

202
                        dirs[i++] = j;
32✔
203
                }
204
        } else
205
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
655✔
206
        if (!dirs)
663✔
UNCOV
207
                return log_oom();
×
208

209
        ConfFile **files = NULL;
663✔
210
        size_t n_files = 0;
663✔
211

212
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
663✔
213

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

220
        FOREACH_ARRAY(i, files, n_files) {
1,315✔
221
                _cleanup_(feature_unrefp) Feature *f = NULL;
×
222
                ConfFile *e = *i;
652✔
223

224
                f = feature_new();
652✔
225
                if (!f)
652✔
UNCOV
226
                        return log_oom();
×
227

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

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

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

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

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

252
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS) &&
663✔
253
            c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) {
575✔
UNCOV
254
                if (arg_component)
×
UNCOV
255
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
256
                                               "No transfer definitions for component '%s' found.",
257
                                               arg_component);
258

UNCOV
259
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
260
                                       "No transfer definitions found.");
261
        }
262

263
        return 0;
264
}
265

266
static int context_load_installed_instances(Context *c) {
663✔
267
        int r;
663✔
268

269
        assert(c);
663✔
270

271
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
1,326✔
272

273
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
5,288✔
274
                Transfer *t = *tr;
4,625✔
275

276
                r = resource_load_instances(
4,625✔
277
                                &t->target,
278
                                arg_verify >= 0 ? arg_verify : t->verify,
4,625✔
279
                                &c->web_cache);
280
                if (r < 0)
4,625✔
281
                        return r;
282
        }
283

284
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
1,263✔
285
                Transfer *t = *tr;
600✔
286

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

295
        return 0;
296
}
297

298
static int context_load_available_instances(Context *c) {
409✔
299
        int r;
409✔
300

301
        assert(c);
409✔
302

303
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
818✔
304

305
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
3,196✔
306
                Transfer *t = *tr;
2,795✔
307

308
                r = resource_load_instances(
2,795✔
309
                                &t->source,
310
                                arg_verify >= 0 ? arg_verify : t->verify,
2,795✔
311
                                &c->web_cache);
312
                if (r < 0)
2,795✔
313
                        return r;
314
        }
315

316
        return 0;
317
}
318

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

324
        assert(c);
936✔
325
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
936✔
326

327
        for (;;) {
5,023✔
UNCOV
328
                _cleanup_free_ Instance **cursor_instances = NULL;
×
329
                bool skip = false;
5,023✔
330
                UpdateSetFlags extra_flags = 0;
5,023✔
331
                _cleanup_free_ char *cursor = NULL;
4,087✔
332
                UpdateSet *us = NULL;
5,023✔
333

334
                /* First, let's find the newest version that's older than the boundary. */
335
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
37,303✔
336
                        Resource *rr;
32,681✔
337

338
                        assert(*tr);
32,681✔
339

340
                        if (flags == UPDATE_AVAILABLE)
32,681✔
341
                                rr = &(*tr)->source;
16,092✔
342
                        else {
343
                                assert(flags == UPDATE_INSTALLED);
16,589✔
344
                                rr = &(*tr)->target;
16,589✔
345
                        }
346

347
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
101,962✔
348
                                Instance *i = *inst; /* Sorted newest-to-oldest */
93,856✔
349

350
                                assert(i);
93,856✔
351

352
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
93,856✔
353
                                        continue; /* Not older than the boundary */
69,281✔
354

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

360
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
4,087✔
UNCOV
361
                                        return log_oom();
×
362

363
                                break; /* All subsequent instances will be older than this one */
364
                        }
365

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

374
                if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
5,023✔
375
                        break;
376

377
                cursor_instances = new0(Instance*, c->n_transfers);
4,087✔
378
                if (!cursor_instances)
4,087✔
UNCOV
379
                        return log_oom();
×
380

381
                /* Now let's find all the instances that match the version of the cursor, if we have them */
382
                for (size_t k = 0; k < c->n_transfers; k++) {
31,660✔
383
                        Transfer *t = c->transfers[k];
27,887✔
384
                        Instance *match = NULL;
27,887✔
385

386
                        assert(t);
27,887✔
387

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

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

410
                        cursor_instances[k] = match;
27,573✔
411

412
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
27,573✔
UNCOV
413
                                extra_flags |= UPDATE_OBSOLETE;
×
414

415
                        if (strv_contains(t->protected_versions, cursor))
27,573✔
UNCOV
416
                                extra_flags |= UPDATE_PROTECTED;
×
417

418
                        /* Partial or pending updates by definition are not incomplete, they’re
419
                         * partial/pending instead. While an individual Instance cannot be both partial and
420
                         * pending, an UpdateSet as a whole can contain both partial and pending instances. */
421
                        assert(!match || !(match->is_partial && match->is_pending));
27,573✔
422

423
                        if (match && match->is_partial)
23,483✔
424
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
20✔
425

426
                        if (match && match->is_pending)
23,483✔
427
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
376✔
428
                }
429

430
                r = free_and_strdup_warn(&boundary, cursor);
4,087✔
431
                if (r < 0)
4,087✔
432
                        return r;
433

434
                if (skip)
4,087✔
435
                        continue;
314✔
436

437
                /* See if we already have this update set in our table */
438
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
10,831✔
439
                        UpdateSet *u = *update_set;
8,390✔
440

441
                        if (strverscmp_improved(u->version, cursor) != 0)
8,390✔
442
                                continue;
7,058✔
443

444
                        /* Merge in what we've learned and continue onto the next version */
445

446
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
1,332✔
447
                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
660✔
448
                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
652✔
449
                                assert(u->n_instances == c->n_transfers);
696✔
450

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

454
                                for (size_t j = 0; j < u->n_instances; j++) {
5,664✔
455
                                        if (!u->instances[j])
4,968✔
456
                                                u->instances[j] = cursor_instances[j];
2,896✔
457

458
                                        /* Make sure that the list is full if the update is AVAILABLE */
459
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
4,968✔
460
                                }
461
                        }
462

463
                        u->flags |= flags | extra_flags;
1,332✔
464

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

472
                        skip = true;
473
                        newest_found = true;
474
                        break;
475
                }
476

477
                if (skip)
32✔
478
                        continue;
1,332✔
479

480
                /* Doesn't exist yet, let's add it */
481
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
2,441✔
UNCOV
482
                        return log_oom();
×
483

484
                us = new(UpdateSet, 1);
2,441✔
485
                if (!us)
2,441✔
UNCOV
486
                        return log_oom();
×
487

488
                *us = (UpdateSet) {
2,441✔
489
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
2,441✔
490
                        .version = TAKE_PTR(cursor),
2,441✔
491
                        .instances = TAKE_PTR(cursor_instances),
2,441✔
492
                        .n_instances = c->n_transfers,
2,441✔
493
                };
494

495
                c->update_sets[c->n_update_sets++] = us;
2,441✔
496

497
                newest_found = true;
2,441✔
498

499
                /* Remember which one is the newest installed */
500
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
2,441✔
501
                        c->newest_installed = us;
516✔
502

503
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
504
                 * It may be partial or pending. */
505
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
2,441✔
506
                        c->candidate = us;
153✔
507
        }
508

509
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
510
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
936✔
511
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
818✔
512
                c->candidate = NULL;
24✔
513

514
        /* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */
515
        if (c->newest_installed &&
936✔
516
            (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) &&
900✔
517
            !c->candidate)
100✔
518
                c->candidate = c->newest_installed;
100✔
519

520
        return 0;
521
}
522

523
static int context_discover_update_sets(Context *c) {
535✔
524
        int r;
535✔
525

526
        assert(c);
535✔
527

528
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
1,070✔
529

530
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
535✔
531
        if (r < 0)
535✔
532
                return r;
533

534
        if (!arg_offline) {
535✔
535
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
802✔
536

537
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
401✔
538
                if (r < 0)
401✔
539
                        return r;
540
        }
541

542
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
535✔
543
        return 0;
535✔
544
}
545

546
static int context_show_table(Context *c) {
×
547
        _cleanup_(table_unrefp) Table *t = NULL;
×
548
        int r;
×
549

550
        assert(c);
×
551

552
        t = table_new("", "version", "installed", "available", "assessment");
×
UNCOV
553
        if (!t)
×
554
                return log_oom();
×
555

556
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
UNCOV
557
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
558
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
559

560
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
UNCOV
561
                UpdateSet *us = *update_set;
×
UNCOV
562
                const char *color;
×
563

UNCOV
564
                color = update_set_flags_to_color(us->flags);
×
565

UNCOV
566
                r = table_add_many(t,
×
567
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
568
                                   TABLE_SET_COLOR, color,
569
                                   TABLE_STRING,    us->version,
570
                                   TABLE_SET_COLOR, color,
571
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
572
                                   TABLE_SET_COLOR, color,
573
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
574
                                   TABLE_SET_COLOR, color,
575
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
576
                                   TABLE_SET_COLOR, color);
UNCOV
577
                if (r < 0)
×
UNCOV
578
                        return table_log_add_error(r);
×
579
        }
580

UNCOV
581
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
582
}
583

584
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
104✔
585
        assert(c);
104✔
586
        assert(version);
104✔
587

588
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
184✔
589
                if (streq((*update_set)->version, version))
184✔
590
                        return *update_set;
591

592
        return NULL;
593
}
594

595
static int context_show_version(Context *c, const char *version) {
104✔
596
        bool show_fs_columns = false, show_partition_columns = false,
104✔
597
                have_fs_attributes = false, have_partition_attributes = false,
104✔
598
                have_size = false, have_tries = false, have_no_auto = false,
104✔
599
                have_read_only = false, have_growfs = false, have_sha256 = false;
104✔
600
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
104✔
601
        _cleanup_(table_unrefp) Table *t = NULL;
104✔
602
        _cleanup_strv_free_ char **changelog_urls = NULL;
104✔
603
        UpdateSet *us;
104✔
604
        int r;
104✔
605

606
        assert(c);
104✔
607
        assert(version);
104✔
608

609
        us = context_update_set_by_version(c, version);
104✔
610
        if (!us)
104✔
UNCOV
611
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
612

613
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
104✔
614
                pager_open(arg_pager_flags);
56✔
615

616
        if (!sd_json_format_enabled(arg_json_format_flags))
104✔
617
                printf("%s%s%s Version: %s\n"
448✔
618
                       "    State: %s%s%s\n"
619
                       "Installed: %s%s%s%s\n"
620
                       "Available: %s%s\n"
621
                       "Protected: %s%s%s\n"
622
                       " Obsolete: %s%s%s\n\n",
623
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
56✔
624
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
56✔
625
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
64✔
626
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
104✔
627
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
104✔
628
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
56✔
629
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
56✔
630

631
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
104✔
632
        if (!t)
104✔
UNCOV
633
                return log_oom();
×
634

635
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
104✔
636
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
104✔
637
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
104✔
638
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
104✔
639
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
104✔
640
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
104✔
641
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
104✔
642

643
        /* Starting in v257, these fields would be automatically formatted with underscores. This would have
644
         * been a breaking change, so to avoid that let's hard-code their original names. */
645
        (void) table_set_json_field_name(t, 7, "tries-done");
104✔
646
        (void) table_set_json_field_name(t, 8, "tries-left");
104✔
647

648
        /* Determine if the target will make use of partition/fs attributes for any of the transfers */
649
        FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
848✔
650
                Transfer *tr = *transfer;
744✔
651

652
                if (tr->target.type == RESOURCE_PARTITION)
744✔
653
                        show_partition_columns = true;
208✔
654
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
744✔
655
                        show_fs_columns = true;
536✔
656

657
                STRV_FOREACH(changelog, tr->changelog) {
744✔
UNCOV
658
                        assert(*changelog);
×
659

660
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
UNCOV
661
                        if (!changelog_url)
×
UNCOV
662
                                return log_oom();
×
663

664
                        /* Avoid duplicates */
665
                        if (strv_contains(changelog_urls, changelog_url))
×
UNCOV
666
                                continue;
×
667

668
                        /* changelog_urls takes ownership of expanded changelog_url */
UNCOV
669
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
UNCOV
670
                        if (r < 0)
×
UNCOV
671
                                return log_oom();
×
672
                }
673
        }
674

675
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
848✔
676
                Instance *i = *inst;
744✔
677

678
                if (!i) {
744✔
679
                        assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING));
56✔
680
                        continue;
56✔
681
                }
682

683
                r = table_add_many(t,
688✔
684
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
685
                                   TABLE_PATH, i->path);
686
                if (r < 0)
688✔
UNCOV
687
                        return table_log_add_error(r);
×
688

689
                if (i->metadata.partition_uuid_set) {
688✔
690
                        have_partition_attributes = true;
144✔
691
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
144✔
692
                } else
693
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
544✔
694
                if (r < 0)
688✔
UNCOV
695
                        return table_log_add_error(r);
×
696

697
                if (i->metadata.partition_flags_set) {
688✔
698
                        have_partition_attributes = true;
144✔
699
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
144✔
700
                } else
701
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
544✔
702
                if (r < 0)
688✔
UNCOV
703
                        return table_log_add_error(r);
×
704

705
                if (i->metadata.mtime != USEC_INFINITY) {
688✔
706
                        have_fs_attributes = true;
528✔
707
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
528✔
708
                } else
709
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
160✔
710
                if (r < 0)
688✔
UNCOV
711
                        return table_log_add_error(r);
×
712

713
                if (i->metadata.mode != MODE_INVALID) {
688✔
714
                        have_fs_attributes = true;
528✔
715
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
528✔
716
                } else
717
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
160✔
718
                if (r < 0)
688✔
UNCOV
719
                        return table_log_add_error(r);
×
720

721
                if (i->metadata.size != UINT64_MAX) {
688✔
UNCOV
722
                        have_size = true;
×
UNCOV
723
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
724
                } else
725
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
688✔
726
                if (r < 0)
688✔
UNCOV
727
                        return table_log_add_error(r);
×
728

729
                if (i->metadata.tries_done != UINT64_MAX) {
688✔
730
                        have_tries = true;
64✔
731
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
64✔
732
                } else
733
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
624✔
734
                if (r < 0)
688✔
UNCOV
735
                        return table_log_add_error(r);
×
736

737
                if (i->metadata.tries_left != UINT64_MAX) {
688✔
738
                        have_tries = true;
64✔
739
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
64✔
740
                } else
741
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
624✔
742
                if (r < 0)
688✔
743
                        return table_log_add_error(r);
×
744

745
                if (i->metadata.no_auto >= 0) {
688✔
UNCOV
746
                        bool b;
×
747

748
                        have_no_auto = true;
×
UNCOV
749
                        b = i->metadata.no_auto;
×
UNCOV
750
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
751
                } else
752
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
688✔
753
                if (r < 0)
688✔
UNCOV
754
                        return table_log_add_error(r);
×
755
                if (i->metadata.read_only >= 0) {
688✔
756
                        bool b;
144✔
757

758
                        have_read_only = true;
144✔
759
                        b = i->metadata.read_only;
144✔
760
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
144✔
761
                } else
762
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
544✔
763
                if (r < 0)
688✔
764
                        return table_log_add_error(r);
×
765

766
                if (i->metadata.growfs >= 0) {
688✔
UNCOV
767
                        bool b;
×
768

769
                        have_growfs = true;
×
UNCOV
770
                        b = i->metadata.growfs;
×
UNCOV
771
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
772
                } else
773
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
688✔
774
                if (r < 0)
688✔
UNCOV
775
                        return table_log_add_error(r);
×
776

777
                if (i->metadata.sha256sum_set) {
688✔
778
                        _cleanup_free_ char *formatted = NULL;
16✔
779

780
                        have_sha256 = true;
16✔
781

782
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
16✔
783
                        if (!formatted)
16✔
784
                                return log_oom();
×
785

786
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
16✔
787
                } else
788
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
672✔
789
                if (r < 0)
688✔
UNCOV
790
                        return table_log_add_error(r);
×
791
        }
792

793
        /* Hide the fs/partition columns if we don't have any data to show there */
794
        if (!have_fs_attributes)
104✔
795
                show_fs_columns = false;
8✔
796
        if (!have_partition_attributes)
104✔
797
                show_partition_columns = false;
798

799
        if (!show_partition_columns)
72✔
800
                (void) table_hide_column_from_display(t, 2, 3);
32✔
801
        if (!show_fs_columns)
104✔
802
                (void) table_hide_column_from_display(t, 4, 5);
8✔
803
        if (!have_size)
104✔
804
                (void) table_hide_column_from_display(t, 6);
104✔
805
        if (!have_tries)
104✔
806
                (void) table_hide_column_from_display(t, 7, 8);
40✔
807
        if (!have_no_auto)
104✔
808
                (void) table_hide_column_from_display(t, 9);
104✔
809
        if (!have_read_only)
104✔
810
                (void) table_hide_column_from_display(t, 10);
32✔
811
        if (!have_growfs)
104✔
812
                (void) table_hide_column_from_display(t, 11);
104✔
813
        if (!have_sha256)
104✔
814
                (void) table_hide_column_from_display(t, 12);
96✔
815

816
        if (!sd_json_format_enabled(arg_json_format_flags)) {
104✔
817
                printf("%s%s%s Version: %s\n"
504✔
818
                       "    State: %s%s%s\n"
819
                       "Installed: %s%s%s%s%s%s%s\n"
820
                       "Available: %s%s\n"
821
                       "Protected: %s%s%s\n"
822
                       " Obsolete: %s%s%s\n",
823
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
56✔
824
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
56✔
825
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
64✔
826
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
72✔
827
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
104✔
828
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
104✔
829
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
56✔
830
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
56✔
831

832
                STRV_FOREACH(url, changelog_urls) {
56✔
UNCOV
833
                        _cleanup_free_ char *changelog_link = NULL;
×
UNCOV
834
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
UNCOV
835
                        if (r < 0)
×
UNCOV
836
                                return log_oom();
×
UNCOV
837
                        printf("ChangeLog: %s\n", changelog_link);
×
838
                }
839
                printf("\n");
56✔
840

841
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
56✔
842
        } else {
843
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
48✔
844

845
                r = table_to_json(t, &t_json);
48✔
846
                if (r < 0)
48✔
UNCOV
847
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
848

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

863
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
48✔
864
                if (r < 0)
48✔
UNCOV
865
                        return log_error_errno(r, "Failed to print JSON: %m");
×
866

867
                return 0;
868
        }
869
}
870

871
static int context_vacuum(
105✔
872
                Context *c,
873
                uint64_t space,
874
                const char *extra_protected_version) {
875

876
        size_t disabled_count = 0;
105✔
877
        int r, count = 0;
105✔
878

879
        assert(c);
105✔
880

881
        if (space == 0)
105✔
882
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
32✔
883
        else
884
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
178✔
885

886
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
842✔
887
                Transfer *t = *tr;
737✔
888

889
                /* Don't bother clearing out space if we're not going to be downloading anything */
890
                if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
737✔
891
                        continue;
104✔
892

893
                r = transfer_vacuum(t, space, extra_protected_version);
633✔
894
                if (r < 0)
633✔
895
                        return r;
896

897
                count = MAX(count, r);
633✔
898
        }
899

900
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
201✔
901
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
96✔
902
                if (r < 0)
96✔
903
                        return r;
904
                if (r > 0)
96✔
905
                        disabled_count++;
8✔
906
        }
907

908
        if (!sd_json_format_enabled(arg_json_format_flags)) {
105✔
909
                if (count > 0 && disabled_count > 0)
79✔
UNCOV
910
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
911
                else if (count > 0)
79✔
912
                        log_info("Removed %i instances.", count);
38✔
913
                else if (disabled_count > 0)
41✔
914
                        log_info("Removed %zu disabled transfers.", disabled_count);
8✔
915
                else
916
                        log_info("Found nothing to remove.");
33✔
917
        } else {
918
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
26✔
919

920
                r = sd_json_buildo(&json,
26✔
921
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
922
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
923
                if (r < 0)
26✔
UNCOV
924
                        return log_error_errno(r, "Failed to create JSON: %m");
×
925

926
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
26✔
927
                if (r < 0)
26✔
UNCOV
928
                        return log_error_errno(r, "Failed to print JSON: %m");
×
929
        }
930

931
        return 0;
932
}
933

934
static int context_make_offline(Context **ret, const char *node, ReadDefinitionsFlags read_definitions_flags) {
663✔
935
        _cleanup_(context_freep) Context* context = NULL;
663✔
936
        int r;
663✔
937

938
        assert(ret);
663✔
939

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

943
        context = context_new();
663✔
944
        if (!context)
663✔
UNCOV
945
                return log_oom();
×
946

947
        r = context_read_definitions(context, node, read_definitions_flags);
663✔
948
        if (r < 0)
663✔
949
                return r;
950

951
        r = context_load_installed_instances(context);
663✔
952
        if (r < 0)
663✔
953
                return r;
954

955
        *ret = TAKE_PTR(context);
663✔
956
        return 0;
663✔
957
}
958

959
static int context_make_online(Context **ret, const char *node) {
543✔
960
        _cleanup_(context_freep) Context* context = NULL;
543✔
961
        int r;
543✔
962

963
        assert(ret);
543✔
964

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

968
        r = context_make_offline(&context, node,
543✔
969
                                 READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS | READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
970
        if (r < 0)
543✔
971
                return r;
972

973
        if (!arg_offline) {
543✔
974
                r = context_load_available_instances(context);
409✔
975
                if (r < 0)
409✔
976
                        return r;
977
        }
978

979
        r = context_discover_update_sets(context);
535✔
980
        if (r < 0)
535✔
981
                return r;
982

983
        *ret = TAKE_PTR(context);
535✔
984
        return 0;
535✔
985
}
986

987
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
410✔
988
        const Context *c = ASSERT_PTR(t->context);
410✔
989
        size_t i, n = c->n_transfers;
410✔
990
        uint64_t base, scaled;
410✔
991
        unsigned overall;
410✔
992

993
        for (i = 0; i < n; i++)
1,457✔
994
                if (c->transfers[i] == t)
1,457✔
995
                        break;
996
        assert(i < n); /* We should have found the index */
410✔
997

998
        base = (100 * 100 * i) / n;
410✔
999
        scaled = (100 * percentage) / n;
410✔
1000
        overall = (unsigned) ((base + scaled) / 100);
410✔
1001
        assert(overall <= 100);
410✔
1002

1003
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
410✔
1004
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
820✔
1005
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
1006
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
1007
                                              "STATUS=Updating to '%s' (%u%% complete).",
1008
                                              overall, n - i, i, inst->metadata.version, overall);
410✔
1009
}
1010

1011
static int context_process_partial_and_pending(Context *c, const char *version);
1012

1013
static int context_acquire(
137✔
1014
                Context *c,
1015
                const char *version) {
1016

1017
        UpdateSet *us = NULL;
137✔
1018
        int r;
137✔
1019

1020
        assert(c);
137✔
1021

1022
        if (version) {
137✔
UNCOV
1023
                us = context_update_set_by_version(c, version);
×
UNCOV
1024
                if (!us)
×
1025
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
137✔
1026
        } else {
1027
                if (!c->candidate) {
137✔
1028
                        log_info("No update needed.");
24✔
1029

1030
                        return 0;
1031
                }
1032

1033
                us = c->candidate;
1034
        }
1035

1036
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
113✔
1037
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
16✔
1038
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
97✔
1039
                return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
8✔
1040
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
89✔
1041
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
16✔
1042

1043
                return context_process_partial_and_pending(c, version);
16✔
1044
        } else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
73✔
UNCOV
1045
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1046

1047
                return 0;
1048
        }
1049

1050
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
89✔
1051
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1052
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
89✔
UNCOV
1053
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1054

1055
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
89✔
UNCOV
1056
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1057
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
89✔
UNCOV
1058
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1059

1060
        log_info("Selected update '%s' for install.", us->version);
89✔
1061

1062
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
178✔
1063
        if (!metadata)
89✔
UNCOV
1064
                return log_oom();
×
1065

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

1072
                assert(inst);
625✔
1073

1074
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
625✔
1075
                if (r < 0)
625✔
1076
                        return r;
1077
        }
1078

1079
        (void) sd_notifyf(/* unset_environment= */ false,
89✔
1080
                          "READY=1\n"
1081
                          "X_SYSUPDATE_VERSION=%s\n"
1082
                          "STATUS=Making room for '%s'.", us->version, us->version);
1083

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

1094
        if (arg_sync)
89✔
1095
                sync();
89✔
1096

1097
        (void) sd_notifyf(/* unset_environment= */ false,
89✔
1098
                          "STATUS=Updating to '%s'.", us->version);
1099

1100
        /* There should now be one instance picked for each transfer, and the order is the same */
1101
        assert(us->n_instances == c->n_transfers);
89✔
1102

1103
        for (size_t i = 0; i < c->n_transfers; i++) {
666✔
1104
                Instance *inst = us->instances[i];
585✔
1105
                Transfer *t = c->transfers[i];
585✔
1106

1107
                assert(inst); /* ditto */
585✔
1108

1109
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
585✔
1110
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
104✔
1111
                        continue;
104✔
1112
                }
1113

1114
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
481✔
1115
                if (r < 0)
481✔
1116
                        return r;
1117
        }
1118

1119
        if (arg_sync)
81✔
1120
                sync();
81✔
1121

1122
        return 1;
1123
}
1124

1125
/* Check to see if we have an update set acquired and pending installation. */
1126
static int context_process_partial_and_pending(
58✔
1127
                Context *c,
1128
                const char *version) {
1129

1130
        UpdateSet *us = NULL;
58✔
1131
        int r;
58✔
1132

1133
        assert(c);
58✔
1134

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

1143
                        return 0;
1144
                }
1145

1146
                us = c->candidate;
1147
        }
1148

1149
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
58✔
UNCOV
1150
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
×
1151
        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
58✔
UNCOV
1152
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1153

1154
                return 0;
1155
        }
1156

1157
        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
58✔
1158
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
2✔
1159
        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
56✔
UNCOV
1160
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
×
1161

1162
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
56✔
UNCOV
1163
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1164

1165
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
56✔
UNCOV
1166
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1167
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
56✔
UNCOV
1168
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1169

1170
        log_info("Selected update '%s' for install.", us->version);
56✔
1171

1172
        /* There should now be one instance picked for each transfer, and the order is the same */
1173
        assert(us->n_instances == c->n_transfers);
56✔
1174

1175
        for (size_t i = 0; i < c->n_transfers; i++) {
454✔
1176
                Instance *inst = us->instances[i];
398✔
1177
                Transfer *t = c->transfers[i];
398✔
1178

1179
                assert(inst);
398✔
1180

1181
                r = transfer_process_partial_and_pending_instance(t, inst);
398✔
1182
                if (r < 0)
398✔
1183
                        return r;
1184
        }
1185

1186
        return 1;
1187
}
1188

1189
static int context_install(
81✔
1190
                Context *c,
1191
                const char *version,
1192
                UpdateSet **ret_applied) {
1193

1194
        UpdateSet *us = NULL;
81✔
1195
        int r;
81✔
1196

1197
        assert(c);
81✔
1198

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

1207
                        return 0;
1208
                }
1209

1210
                us = c->candidate;
1211
        }
1212

1213
        (void) sd_notifyf(/* unset_environment=*/ false,
81✔
1214
                          "READY=1\n"
1215
                          "X_SYSUPDATE_VERSION=%s\n"
1216
                          "STATUS=Installing '%s'.", us->version, us->version);
1217

1218
        for (size_t i = 0; i < c->n_transfers; i++) {
731✔
1219
                Instance *inst = us->instances[i];
569✔
1220
                Transfer *t = c->transfers[i];
569✔
1221

1222
                if (inst->resource == &t->target &&
569✔
1223
                    !inst->is_pending)
460✔
1224
                        continue;
104✔
1225

1226
                r = transfer_install_instance(t, inst, arg_root);
465✔
1227
                if (r < 0)
465✔
1228
                        return r;
1229
        }
1230

1231
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
81✔
1232

1233
        (void) sd_notifyf(/* unset_environment= */ false,
81✔
1234
                          "STATUS=Installed '%s'.", us->version);
1235

1236
        if (ret_applied)
81✔
1237
                *ret_applied = us;
81✔
1238

1239
        return 1;
1240
}
1241

1242
static int process_image(
663✔
1243
                bool ro,
1244
                char **ret_mounted_dir,
1245
                LoopDevice **ret_loop_device) {
1246

1247
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
663✔
1248
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
663✔
1249
        int r;
663✔
1250

1251
        assert(ret_mounted_dir);
663✔
1252
        assert(ret_loop_device);
663✔
1253

1254
        if (!arg_image)
663✔
1255
                return 0;
1256

UNCOV
1257
        assert(!arg_root);
×
1258

UNCOV
1259
        r = mount_image_privately_interactively(
×
1260
                        arg_image,
1261
                        arg_image_policy,
1262
                        (ro ? DISSECT_IMAGE_READ_ONLY : 0) |
1263
                        DISSECT_IMAGE_FSCK |
1264
                        DISSECT_IMAGE_MKDIR |
1265
                        DISSECT_IMAGE_GROWFS |
1266
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1267
                        DISSECT_IMAGE_USR_NO_ROOT |
1268
                        DISSECT_IMAGE_GENERIC_ROOT |
1269
                        DISSECT_IMAGE_REQUIRE_ROOT |
1270
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1271
                        &mounted_dir,
1272
                        /* ret_dir_fd= */ NULL,
1273
                        &loop_device);
1274
        if (r < 0)
×
1275
                return r;
1276

1277
        arg_root = strdup(mounted_dir);
×
UNCOV
1278
        if (!arg_root)
×
UNCOV
1279
                return log_oom();
×
1280

UNCOV
1281
        *ret_mounted_dir = TAKE_PTR(mounted_dir);
×
UNCOV
1282
        *ret_loop_device = TAKE_PTR(loop_device);
×
1283

UNCOV
1284
        return 0;
×
1285
}
1286

1287
VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT,
1288
     "Show installed and available versions");
1289
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
156✔
1290
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
156✔
1291
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
156✔
1292
        _cleanup_(context_freep) Context* context = NULL;
156✔
1293
        _cleanup_strv_free_ char **appstream_urls = NULL;
156✔
1294
        const char *version;
156✔
1295
        int r;
156✔
1296

1297
        assert(argc <= 2);
156✔
1298
        version = argc >= 2 ? argv[1] : NULL;
156✔
1299

1300
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
156✔
1301
        if (r < 0)
156✔
1302
                return r;
1303

1304
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
156✔
1305
        if (r < 0)
156✔
1306
                return r;
1307

1308
        if (version)
156✔
1309
                return context_show_version(context, version);
104✔
1310
        else if (!sd_json_format_enabled(arg_json_format_flags))
52✔
UNCOV
1311
                return context_show_table(context);
×
1312
        else {
1313
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
52✔
1314
                _cleanup_strv_free_ char **versions = NULL;
52✔
1315
                const char *current = NULL;
52✔
1316
                bool current_is_pending = false;
52✔
1317

1318
                FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
246✔
1319
                        UpdateSet *us = *update_set;
194✔
1320

1321
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
194✔
1322
                            FLAGS_SET(us->flags, UPDATE_NEWEST)) {
186✔
1323
                                current = us->version;
50✔
1324
                                current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
50✔
1325
                        }
1326

1327
                        r = strv_extend(&versions, us->version);
194✔
1328
                        if (r < 0)
194✔
1329
                                return log_oom();
×
1330
                }
1331

1332
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
418✔
1333
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
366✔
1334
                                /* Avoid duplicates */
UNCOV
1335
                                if (strv_contains(appstream_urls, *appstream_url))
×
UNCOV
1336
                                        continue;
×
1337

UNCOV
1338
                                r = strv_extend(&appstream_urls, *appstream_url);
×
UNCOV
1339
                                if (r < 0)
×
1340
                                        return log_oom();
×
1341
                        }
1342

1343
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current),
102✔
1344
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1345
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1346
                if (r < 0)
52✔
UNCOV
1347
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1348

1349
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
52✔
1350
                if (r < 0)
52✔
UNCOV
1351
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1352

1353
                return 0;
1354
        }
1355
}
1356

1357
VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0,
1358
     "Show optional features");
1359
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
16✔
1360
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
16✔
1361
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
16✔
1362
        _cleanup_(context_freep) Context* context = NULL;
16✔
1363
        _cleanup_(table_unrefp) Table *table = NULL;
16✔
1364
        const char *feature_id;
16✔
1365
        Feature *f;
16✔
1366
        int r;
16✔
1367

1368
        assert(argc <= 2);
16✔
1369
        feature_id = argc >= 2 ? argv[1] : NULL;
16✔
1370

1371
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
16✔
1372
        if (r < 0)
16✔
1373
                return r;
1374

1375
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL,
16✔
1376
                                 READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1377
        if (r < 0)
16✔
1378
                return r;
1379

1380
        if (feature_id) {
16✔
1381
                _cleanup_strv_free_ char **transfers = NULL;
8✔
1382

1383
                f = hashmap_get(context->features, feature_id);
8✔
1384
                if (!f)
8✔
UNCOV
1385
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1386
                                               "Optional feature not found: %s",
1387
                                               feature_id);
1388

1389
                table = table_new_vertical();
8✔
1390
                if (!table)
8✔
1391
                        return log_oom();
×
1392

1393
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
64✔
1394
                        Transfer *t = *tr;
56✔
1395

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

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

1404
                FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) {
16✔
1405
                        Transfer *t = *tr;
8✔
1406

1407
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
8✔
UNCOV
1408
                                continue;
×
1409

1410
                        r = strv_extend(&transfers, t->id);
8✔
1411
                        if (r < 0)
8✔
UNCOV
1412
                                return log_oom();
×
1413
                }
1414

1415
                r = table_add_many(table,
8✔
1416
                                   TABLE_FIELD, "Name",
1417
                                   TABLE_STRING, f->id,
1418
                                   TABLE_FIELD, "Enabled",
1419
                                   TABLE_BOOLEAN, f->enabled);
1420
                if (r < 0)
8✔
UNCOV
1421
                        return table_log_add_error(r);
×
1422

1423
                if (f->description) {
8✔
1424
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
8✔
1425
                        if (r < 0)
8✔
1426
                                return table_log_add_error(r);
×
1427
                }
1428

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

1438
                if (f->appstream) {
8✔
UNCOV
1439
                        r = table_add_many(table,
×
1440
                                           TABLE_FIELD, "AppStream",
1441
                                           TABLE_STRING, f->appstream,
1442
                                           TABLE_SET_URL, f->appstream);
UNCOV
1443
                        if (r < 0)
×
UNCOV
1444
                                return table_log_add_error(r);
×
1445
                }
1446

1447
                if (!strv_isempty(transfers)) {
8✔
1448
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
8✔
1449
                        if (r < 0)
8✔
UNCOV
1450
                                return table_log_add_error(r);
×
1451
                }
1452

1453
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
8✔
1454
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
8✔
1455
                table = table_new("", "feature", "description", "documentation");
8✔
1456
                if (!table)
8✔
UNCOV
1457
                        return log_oom();
×
1458

1459
                HASHMAP_FOREACH(f, context->features) {
16✔
1460
                        r = table_add_many(table,
8✔
1461
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1462
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
1463
                                           TABLE_STRING, f->id,
1464
                                           TABLE_STRING, f->description,
1465
                                           TABLE_STRING, f->documentation,
1466
                                           TABLE_SET_URL, f->documentation);
1467
                        if (r < 0)
8✔
1468
                                return table_log_add_error(r);
×
1469
                }
1470

1471
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
8✔
1472
        } else {
UNCOV
1473
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1474
                _cleanup_strv_free_ char **features = NULL;
×
1475

1476
                HASHMAP_FOREACH(f, context->features) {
×
UNCOV
1477
                        r = strv_extend(&features, f->id);
×
1478
                        if (r < 0)
×
1479
                                return log_oom();
×
1480
                }
1481

UNCOV
1482
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
1483
                if (r < 0)
×
UNCOV
1484
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1485

UNCOV
1486
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
×
UNCOV
1487
                if (r < 0)
×
UNCOV
1488
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1489
        }
1490

UNCOV
1491
        return 0;
×
1492
}
1493

1494
VERB_NOARG(verb_check_new, "check-new",
1495
           "Check if there's a new version available");
1496
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
200✔
1497
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
200✔
1498
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
200✔
1499
        _cleanup_(context_freep) Context* context = NULL;
200✔
1500
        int r;
200✔
1501

1502
        assert(argc <= 1);
200✔
1503

1504
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
200✔
1505
        if (r < 0)
200✔
1506
                return r;
1507

1508
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
200✔
1509
        if (r < 0)
200✔
1510
                return r;
1511

1512
        if (!sd_json_format_enabled(arg_json_format_flags)) {
200✔
1513
                if (!context->candidate) {
184✔
1514
                        log_debug("No candidate found.");
96✔
1515
                        return EXIT_FAILURE;
1516
                }
1517

1518
                puts(context->candidate->version);
88✔
1519
        } else {
1520
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
16✔
1521

1522
                if (context->candidate)
16✔
1523
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version));
×
1524
                else
1525
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
16✔
1526
                if (r < 0)
16✔
UNCOV
1527
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1528

1529
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
16✔
1530
                if (r < 0)
16✔
UNCOV
1531
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1532
        }
1533

1534
        return EXIT_SUCCESS;
1535
}
1536

1537
typedef enum {
1538
        UPDATE_ACTION_ACQUIRE = 1 << 0,
1539
        UPDATE_ACTION_INSTALL = 1 << 1,
1540
} UpdateActionFlags;
1541

1542
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
187✔
1543
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
187✔
1544
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
187✔
UNCOV
1545
        _cleanup_(context_freep) Context* context = NULL;
×
1546
        _cleanup_free_ char *booted_version = NULL;
187✔
1547
        UpdateSet *applied = NULL;
187✔
1548
        const char *version;
187✔
1549
        int r;
187✔
1550

1551
        assert(argc <= 2);
187✔
1552
        version = argc >= 2 ? argv[1] : NULL;
187✔
1553

1554
        if (arg_instances_max < 2)
187✔
1555
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1556
                                      "The --instances-max argument must be >= 2 while updating");
1557

1558
        if (arg_reboot) {
187✔
1559
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
1560

UNCOV
1561
                r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
UNCOV
1562
                if (r < 0)
×
UNCOV
1563
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
UNCOV
1564
                if (!booted_version)
×
UNCOV
1565
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
1566
        }
1567

1568
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
187✔
1569
        if (r < 0)
187✔
1570
                return r;
1571

1572
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
187✔
1573
        if (r < 0)
187✔
1574
                return r;
1575

1576
        if (action_flags & UPDATE_ACTION_ACQUIRE)
179✔
1577
                r = context_acquire(context, version);
137✔
1578
        else
1579
                r = context_process_partial_and_pending(context, version);
42✔
1580
        if (r < 0)
179✔
1581
                return r;  /* error */
1582

1583
        if (action_flags & UPDATE_ACTION_INSTALL && r > 0)  /* update needed */
161✔
1584
                r = context_install(context, version, &applied);
81✔
1585
        if (r < 0)
161✔
1586
                return r;
1587

1588
        if (r > 0 && arg_reboot) {
161✔
1589
                assert(applied);
×
1590
                assert(booted_version);
×
1591

1592
                if (strverscmp_improved(applied->version, booted_version) > 0) {
×
UNCOV
1593
                        log_notice("Newly installed version is newer than booted version, rebooting.");
×
UNCOV
1594
                        return reboot_now();
×
1595
                }
1596

UNCOV
1597
                if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
UNCOV
1598
                    FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
UNCOV
1599
                        log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
UNCOV
1600
                        return reboot_now();
×
1601
                }
1602

UNCOV
1603
                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
1604
        }
1605

1606
        return 0;
1607
}
1608

1609
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
1610
     "Install new version now");
1611
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
119✔
1612
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
119✔
1613

1614
        if (!arg_offline)
119✔
1615
                flags |= UPDATE_ACTION_ACQUIRE;
77✔
1616

1617
        return verb_update_impl(argc, argv, flags);
119✔
1618
}
1619

1620
VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
1621
     "Acquire (download) new version now");
1622
static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
68✔
1623
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
68✔
1624
}
1625

1626
VERB_NOARG(verb_vacuum, "vacuum",
1627
           "Make room, by deleting old versions");
1628
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
16✔
1629
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
16✔
1630
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
16✔
1631
        _cleanup_(context_freep) Context* context = NULL;
16✔
1632
        int r;
16✔
1633

1634
        assert(argc <= 1);
16✔
1635

1636
        if (arg_instances_max < 1)
16✔
UNCOV
1637
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1638
                                      "The --instances-max argument must be >= 1 while vacuuming");
1639

1640
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
16✔
1641
        if (r < 0)
16✔
1642
                return r;
1643

1644
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL,
16✔
1645
                                 READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1646
        if (r < 0)
16✔
1647
                return r;
1648

1649
        return context_vacuum(context, 0, NULL);
16✔
1650
}
1651

1652
VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
1653
     "Report whether a newer version is installed than currently booted");
1654
VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
1655
     "Reboot if a newer version is installed than booted");
UNCOV
1656
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
UNCOV
1657
        _cleanup_(context_freep) Context* context = NULL;
×
1658
        _cleanup_free_ char *booted_version = NULL;
×
1659
        int r;
×
1660

UNCOV
1661
        assert(argc == 1);
×
1662

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

1667
        r = context_make_offline(&context, /* node= */ NULL,
×
1668
                                 READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS | READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
UNCOV
1669
        if (r < 0)
×
1670
                return r;
1671

UNCOV
1672
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
1673

1674
        r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
×
1675
        if (r < 0)
×
1676
                return r;
1677
        if (!context->newest_installed)
×
1678
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
1679

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

1687
        r = strverscmp_improved(context->newest_installed->version, booted_version);
×
1688
        if (r > 0) {
×
UNCOV
1689
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
1690
                           context->newest_installed->version, booted_version,
1691
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
1692

UNCOV
1693
                if (streq(argv[0], "reboot"))
×
1694
                        return reboot_now();
×
1695

1696
                return EXIT_SUCCESS;
UNCOV
1697
        } else if (r == 0)
×
UNCOV
1698
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
1699
                         context->newest_installed->version, booted_version);
1700
        else
UNCOV
1701
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
1702
                            context->newest_installed->version, booted_version);
1703

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

1707
        return EXIT_SUCCESS;
1708
}
1709

1710
static int component_name_valid(const char *c) {
34✔
1711
        _cleanup_free_ char *j = NULL;
34✔
1712

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

1715
        if (isempty(c))
68✔
1716
                return false;
1717

1718
        if (string_has_cc(c, NULL))
34✔
1719
                return false;
1720

1721
        if (!utf8_is_valid(c))
34✔
1722
                return false;
1723

1724
        j = strjoin("sysupdate.", c, ".d");
34✔
1725
        if (!j)
34✔
1726
                return -ENOMEM;
1727

1728
        return filename_is_valid(j);
34✔
1729
}
1730

1731
VERB_NOARG(verb_components, "components",
1732
           "Show list of components");
1733
static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
88✔
1734
        _cleanup_(context_freep) Context* context = NULL;
88✔
1735
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
88✔
1736
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
88✔
1737
        _cleanup_set_free_ Set *names = NULL;
88✔
1738
        bool has_default_component = false;
88✔
1739
        int r;
88✔
1740

1741
        assert(argc <= 1);
88✔
1742

1743
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
88✔
1744
        if (r < 0)
88✔
1745
                return r;
1746

1747
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, 0);
88✔
1748
        if (r < 0)
88✔
1749
                return r;
1750

1751
        ConfFile **directories = NULL;
88✔
1752
        size_t n_directories = 0;
88✔
1753

1754
        CLEANUP_ARRAY(directories, n_directories, conf_file_free_array);
88✔
1755

1756
        r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY|CONF_FILES_WARN,
176✔
1757
                                      (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories);
88✔
1758
        if (r < 0)
88✔
UNCOV
1759
                return log_error_errno(r, "Failed to enumerate directories: %m");
×
1760

1761
        FOREACH_ARRAY(i, directories, n_directories) {
2,313✔
1762
                ConfFile *e = *i;
2,225✔
1763

1764
                if (streq(e->filename, "sysupdate.d")) {
2,225✔
1765
                        continue;
87✔
1766
                }
1767

1768
                const char *s = startswith(e->filename, "sysupdate.");
2,138✔
1769
                if (!s)
2,138✔
1770
                        continue;
2,112✔
1771

1772
                const char *a = endswith(s, ".d");
26✔
1773
                if (!a)
26✔
1774
                        continue;
×
1775

UNCOV
1776
                _cleanup_free_ char *n = strndup(s, a - s);
×
1777
                if (!n)
26✔
UNCOV
1778
                        return log_oom();
×
1779

1780
                r = component_name_valid(n);
26✔
1781
                if (r < 0)
26✔
UNCOV
1782
                        return log_error_errno(r, "Unable to validate component name '%s': %m", n);
×
1783
                if (r == 0)
26✔
UNCOV
1784
                        continue;
×
1785

1786
                r = set_ensure_put(&names, &string_hash_ops_free, n);
26✔
1787
                if (r < 0 && r != -EEXIST)
26✔
UNCOV
1788
                        return log_error_errno(r, "Failed to add component '%s' to set: %m", n);
×
1789
                TAKE_PTR(n);
1790
        }
1791

1792
        /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a
1793
         * TARGET_HOST? See target_get_argument() in sysupdated.c */
1794
        has_default_component = (!arg_definitions &&
264✔
1795
                                 !arg_component &&
88✔
1796
                                 !arg_root &&
88✔
1797
                                 !arg_image &&
176✔
1798
                                 context->n_transfers > 0);
88✔
1799

1800
        /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
1801
        _cleanup_free_ char **z = set_get_strv(names);
176✔
1802
        if (!z)
88✔
1803
                return log_oom();
×
1804

1805
        strv_sort(z);
88✔
1806

1807
        if (!sd_json_format_enabled(arg_json_format_flags)) {
88✔
UNCOV
1808
                if (!has_default_component && set_isempty(names)) {
×
UNCOV
1809
                        log_info("No components defined.");
×
1810
                        return 0;
1811
                }
1812

1813
                if (has_default_component)
1814
                        printf("%s<default>%s\n",
×
1815
                               ansi_highlight(), ansi_normal());
1816

UNCOV
1817
                STRV_FOREACH(i, z)
×
1818
                        puts(*i);
×
1819
        } else {
1820
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
88✔
1821

1822
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
88✔
1823
                                          SD_JSON_BUILD_PAIR_STRV("components", z));
1824
                if (r < 0)
88✔
UNCOV
1825
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1826

1827
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
88✔
1828
                if (r < 0)
88✔
UNCOV
1829
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1830
        }
1831

1832
        return 0;
1833
}
1834

UNCOV
1835
static int help(void) {
×
1836
        _cleanup_free_ char *link = NULL;
×
UNCOV
1837
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
UNCOV
1838
        int r;
×
1839

UNCOV
1840
        r = terminal_urlify_man("systemd-sysupdate", "8", &link);
×
UNCOV
1841
        if (r < 0)
×
UNCOV
1842
                return log_oom();
×
1843

1844
        r = verbs_get_help_table(&verbs);
×
UNCOV
1845
        if (r < 0)
×
1846
                return r;
1847

1848
        r = option_parser_get_help_table(&common_options);
×
UNCOV
1849
        if (r < 0)
×
1850
                return r;
1851

1852
        r = option_parser_get_help_table_group("Options", &options);
×
1853
        if (r < 0)
×
1854
                return r;
1855

1856
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
1857

UNCOV
1858
        printf("%s [OPTIONS...] [VERSION]\n"
×
1859
               "\n%sUpdate OS images.%s\n"
1860
               "\n%sCommands:%s\n",
1861
               program_invocation_short_name,
1862
               ansi_highlight(), ansi_normal(),
1863
               ansi_underline(), ansi_normal());
1864

UNCOV
1865
        r = table_print_or_warn(verbs);
×
UNCOV
1866
        if (r < 0)
×
1867
                return r;
1868

UNCOV
1869
        r = table_print_or_warn(common_options);
×
UNCOV
1870
        if (r < 0)
×
1871
                return r;
1872

1873
        printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal());
×
1874
        r = table_print_or_warn(options);
×
UNCOV
1875
        if (r < 0)
×
1876
                return r;
1877

UNCOV
1878
        printf("\nSee the %s for details.\n", link);
×
1879
        return 0;
1880
}
1881

UNCOV
1882
VERB_COMMON_HELP_HIDDEN(help);
×
1883

1884
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
663✔
1885
        assert(argc >= 0);
663✔
1886
        assert(argv);
663✔
1887
        assert(remaining_args);
663✔
1888

1889
        OptionParser opts = { argc, argv };
663✔
1890
        int r;
663✔
1891

1892
        FOREACH_OPTION_OR_RETURN(c, &opts)
2,185✔
1893
                switch (c) {
859✔
1894

UNCOV
1895
                OPTION_COMMON_HELP:
×
UNCOV
1896
                        return help();
×
1897

UNCOV
1898
                OPTION_COMMON_VERSION:
×
UNCOV
1899
                        return version();
×
1900

1901
                OPTION_GROUP("Options"):
1902
                        break;
1903

1904
                OPTION('C', "component", "NAME",
8✔
1905
                       "Select component to update"):
1906
                        if (isempty(opts.arg)) {
8✔
UNCOV
1907
                                arg_component = mfree(arg_component);
×
1908
                                break;
×
1909
                        }
1910

1911
                        r = component_name_valid(opts.arg);
8✔
1912
                        if (r < 0)
8✔
UNCOV
1913
                                return log_error_errno(r, "Failed to determine if component name is valid: %m");
×
1914
                        if (r == 0)
8✔
1915
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", opts.arg);
×
1916

1917
                        r = free_and_strdup_warn(&arg_component, opts.arg);
8✔
1918
                        if (r < 0)
8✔
1919
                                return r;
1920

1921
                        break;
1922

UNCOV
1923
                OPTION_LONG("definitions", "DIR",
×
1924
                            "Find transfer definitions in specified directory"):
1925
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions);
×
UNCOV
1926
                        if (r < 0)
×
1927
                                return r;
1928
                        break;
1929

UNCOV
1930
                OPTION_LONG("root", "PATH",
×
1931
                            "Operate on an alternate filesystem root"):
1932
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
UNCOV
1933
                        if (r < 0)
×
1934
                                return r;
1935
                        break;
1936

1937
                OPTION_LONG("image", "PATH",
×
1938
                            "Operate on disk image as filesystem root"):
1939
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
1940
                        if (r < 0)
×
1941
                                return r;
1942
                        break;
1943

UNCOV
1944
                OPTION_LONG("image-policy", "POLICY",
×
1945
                            "Specify disk image dissection policy"):
UNCOV
1946
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
1947
                        if (r < 0)
×
1948
                                return r;
1949
                        break;
1950

UNCOV
1951
                OPTION_LONG("transfer-source", "PATH",
×
1952
                            "Specify the directory to transfer sources from"):
UNCOV
1953
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_transfer_source);
×
UNCOV
1954
                        if (r < 0)
×
1955
                                return r;
1956

1957
                        break;
1958

UNCOV
1959
                OPTION('m', "instances-max", "INT",
×
1960
                       "How many instances to maintain"):
UNCOV
1961
                        r = safe_atou64(opts.arg, &arg_instances_max);
×
UNCOV
1962
                        if (r < 0)
×
UNCOV
1963
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", opts.arg);
×
1964

1965
                        break;
1966

1967
                OPTION_LONG("sync", "BOOL",
×
1968
                            "Controls whether to sync data to disk"):
UNCOV
1969
                        r = parse_boolean_argument("--sync=", opts.arg, &arg_sync);
×
UNCOV
1970
                        if (r < 0)
×
1971
                                return r;
1972
                        break;
1973

1974
                OPTION_LONG("verify", "BOOL",
451✔
1975
                            "Force signature verification on or off"): {
1976
                        bool b;
451✔
1977

1978
                        r = parse_boolean_argument("--verify=", opts.arg, &b);
451✔
1979
                        if (r < 0)
451✔
1980
                                return r;
×
1981

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

UNCOV
1986
                OPTION_LONG("reboot", NULL,
×
1987
                            "Reboot after updating to newer version"):
UNCOV
1988
                        arg_reboot = true;
×
UNCOV
1989
                        break;
×
1990

1991
                OPTION_LONG("offline", NULL,
142✔
1992
                            "Do not fetch metadata from the network"):
1993
                        arg_offline = true;
142✔
1994
                        break;
142✔
1995

UNCOV
1996
                OPTION_COMMON_NO_PAGER:
×
1997
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
1998
                        break;
×
1999

UNCOV
2000
                OPTION_COMMON_NO_LEGEND:
×
UNCOV
2001
                        arg_legend = false;
×
UNCOV
2002
                        break;
×
2003

2004
                OPTION_COMMON_JSON:
258✔
2005
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
258✔
2006
                        if (r <= 0)
258✔
2007
                                return r;
2008

2009
                        break;
2010
                }
2011

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

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

2018
        if (arg_definitions && arg_component)
663✔
UNCOV
2019
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
2020

2021
        *remaining_args = option_parser_get_args(&opts);
663✔
2022
        return 1;
663✔
2023
}
2024

2025
static int run(int argc, char *argv[]) {
663✔
2026
        int r;
663✔
2027

2028
        log_setup();
663✔
2029

2030
        char **args = NULL;
663✔
2031
        r = parse_argv(argc, argv, &args);
663✔
2032
        if (r <= 0)
663✔
2033
                return r;
663✔
2034

2035
        return dispatch_verb(args, NULL);
663✔
2036
}
2037

2038
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
663✔
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