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

systemd / systemd / 27854786886

20 Jun 2026 12:28AM UTC coverage: 73.048% (+0.05%) from 72.995%
27854786886

push

github

bluca
report: disable json normalization

Two PRs got merged at the same time, which cause a test to fail,
as they work individually but fail when combined

TEST-74-AUX-UTILS.sh[1688]: + /usr/lib/systemd/systemd-report generate io.systemd.Manager.UnitsTotal
TEST-74-AUX-UTILS.sh[1805]: {"mediaType":"application/vnd.io.systemd.report","metrics":[{"name":"io.systemd.Manager.UnitsTotal","value":249}],"timestamp":"Fri 2026-06-19 19:50:48 UTC"}
TEST-74-AUX-UTILS.sh[1806]: + /usr/lib/systemd/systemd-report generate io.systemd.Manager.UnitsTotal
TEST-74-AUX-UTILS.sh[1807]: + jq .
TEST-74-AUX-UTILS.sh[1807]: {
TEST-74-AUX-UTILS.sh[1807]:   "mediaType": "application/vnd.io.systemd.report",
TEST-74-AUX-UTILS.sh[1807]:   "metrics": [
TEST-74-AUX-UTILS.sh[1807]: {
TEST-74-AUX-UTILS.sh[1807]:   "name": "io.systemd.Manager.UnitsTotal",
TEST-74-AUX-UTILS.sh[1807]:   "value": 249
TEST-74-AUX-UTILS.sh[1807]: }
TEST-74-AUX-UTILS.sh[1807]:   ],
TEST-74-AUX-UTILS.sh[1807]:   "timestamp": "Fri 2026-06-19 19:50:48 UTC"
TEST-74-AUX-UTILS.sh[1807]: }
TEST-74-AUX-UTILS.sh[1688]: + /usr/lib/systemd/systemd-report upload --url=http://localhost:8089/
TEST-74-AUX-UTILS.sh[1808]: Failed to normalize report JSON: Wrong medium type

https://github.com/systemd/systemd/pull/42594
https://github.com/systemd/systemd/pull/42595

Disable normalization for now, and track the issue at
https://github.com/systemd/systemd/issues/42669

Follow-up for 3c2f7c600

339084 of 464191 relevant lines covered (73.05%)

1311580.8 hits per line

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

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

88
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
5,016✔
89
                transfer_free(*tr);
4,380✔
90
        free(c->transfers);
636✔
91

92
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
1,202✔
93
                transfer_free(*tr);
566✔
94
        free(c->disabled_transfers);
636✔
95

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

98
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
2,971✔
99
                update_set_free(*us);
2,335✔
100
        free(c->update_sets);
636✔
101

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

167
        c->transfers = TAKE_PTR(transfers);
638✔
168
        c->n_transfers = n_transfers;
638✔
169
        c->disabled_transfers = TAKE_PTR(disabled);
638✔
170
        c->n_disabled_transfers = n_disabled;
638✔
171
        return 0;
638✔
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) {
636✔
180
        _cleanup_strv_free_ char **dirs = NULL;
636✔
181
        int r;
636✔
182

183
        assert(c);
636✔
184

185
        if (arg_definitions)
636✔
186
                dirs = strv_new(arg_definitions);
1✔
187
        else if (arg_component) {
635✔
188
                char **l = CONF_PATHS_STRV("");
16✔
189
                size_t i = 0;
16✔
190

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

195
                STRV_FOREACH(dir, l) {
80✔
196
                        char *j;
64✔
197

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

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

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

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

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

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

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

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

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

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

242
        if (c->n_transfers + c->n_disabled_transfers == 0) {
636✔
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) &&
636✔
253
            c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) {
548✔
254
                if (arg_component)
×
255
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
256
                                               "No transfer definitions for component '%s' found.",
257
                                               arg_component);
258

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) {
636✔
267
        int r;
636✔
268

269
        assert(c);
636✔
270

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

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

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

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

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

295
        return 0;
296
}
297

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

301
        assert(c);
410✔
302

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

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

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

316
        return 0;
317
}
318

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

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

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

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

338
                        assert(*tr);
31,589✔
339

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

347
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
99,262✔
348
                                Instance *i = *inst; /* Sorted newest-to-oldest */
91,670✔
349

350
                                assert(i);
91,670✔
351

352
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
91,670✔
353
                                        continue; /* Not older than the boundary */
67,673✔
354

355
                                if (cursor && strverscmp(i->metadata.version, cursor) <= 0)
23,997✔
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)
3,981✔
361
                                        return log_oom();
×
362

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

366
                        if (flags == UPDATE_AVAILABLE && !cursor)
31,589✔
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. */
4,889✔
375
                        break;
376

377
                cursor_instances = new0(Instance*, c->n_transfers);
3,981✔
378
                if (!cursor_instances)
3,981✔
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++) {
30,708✔
383
                        Transfer *t = c->transfers[k];
27,041✔
384
                        Instance *match = NULL;
27,041✔
385

386
                        assert(t);
27,041✔
387

388
                        if (flags == UPDATE_AVAILABLE) {
27,041✔
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,004✔
400

401
                                match = resource_find_instance(&t->target, cursor);
12,004✔
402
                                if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING)))
12,004✔
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,722✔
408
                        }
409

410
                        cursor_instances[k] = match;
26,727✔
411

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

415
                        if (strv_contains(t->protected_versions, cursor))
26,727✔
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));
26,727✔
422

423
                        if (match && match->is_partial)
22,915✔
424
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
18✔
425

426
                        if (match && match->is_pending)
22,915✔
427
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
374✔
428
                }
429

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

434
                if (skip)
3,981✔
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,563✔
439
                        UpdateSet *u = *update_set;
8,228✔
440

441
                        if (strverscmp_improved(u->version, cursor) != 0)
8,228✔
442
                                continue;
6,896✔
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,335✔
482
                        return log_oom();
×
483

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

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

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

497
                newest_found = true;
2,335✔
498

499
                /* Remember which one is the newest installed */
500
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
2,335✔
501
                        c->newest_installed = us;
490✔
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,335✔
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) &&
908✔
511
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
794✔
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 &&
908✔
516
            (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) &&
874✔
517
            !c->candidate)
98✔
518
                c->candidate = c->newest_installed;
98✔
519

520
        return 0;
521
}
522

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

526
        assert(c);
507✔
527

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

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

534
        if (!arg_offline) {
507✔
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);
507✔
543
        return 0;
507✔
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");
×
553
        if (!t)
×
554
                return log_oom();
×
555

556
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
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) {
×
561
                UpdateSet *us = *update_set;
×
562
                const char *color;
×
563

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

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);
577
                if (r < 0)
×
578
                        return table_log_add_error(r);
×
579
        }
580

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✔
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✔
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✔
658
                        assert(*changelog);
×
659

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

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

668
                        /* changelog_urls takes ownership of expanded changelog_url */
669
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
670
                        if (r < 0)
×
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✔
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✔
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✔
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✔
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✔
719
                        return table_log_add_error(r);
×
720

721
                if (i->metadata.size != UINT64_MAX) {
688✔
722
                        have_size = true;
×
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✔
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✔
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✔
746
                        bool b;
×
747

748
                        have_no_auto = true;
×
749
                        b = i->metadata.no_auto;
×
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✔
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✔
767
                        bool b;
×
768

769
                        have_growfs = true;
×
770
                        b = i->metadata.growfs;
×
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✔
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✔
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✔
833
                        _cleanup_free_ char *changelog_link = NULL;
×
834
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
835
                        if (r < 0)
×
836
                                return log_oom();
×
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✔
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✔
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✔
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✔
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✔
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✔
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) {
636✔
935
        _cleanup_(context_freep) Context* context = NULL;
636✔
936
        int r;
636✔
937

938
        assert(ret);
636✔
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();
636✔
944
        if (!context)
636✔
945
                return log_oom();
×
946

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

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

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

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

963
        assert(ret);
516✔
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,
516✔
969
                                 READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS | READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
970
        if (r < 0)
516✔
971
                return r;
972

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

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

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

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

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

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

1003
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
400✔
1004
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
800✔
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);
400✔
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✔
1023
                us = context_update_set_by_version(c, version);
×
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✔
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✔
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✔
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✔
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✔
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✔
1136
                us = context_update_set_by_version(c, version);
×
1137
                if (!us)
×
1138
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1139
        } else {
1140
                if (!c->candidate) {
58✔
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✔
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✔
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✔
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✔
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✔
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✔
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✔
1200
                us = context_update_set_by_version(c, version);
×
1201
                if (!us)
×
1202
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1203
        } else {
1204
                if (!c->candidate) {
81✔
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(
636✔
1243
                bool ro,
1244
                char **ret_mounted_dir,
1245
                LoopDevice **ret_loop_device) {
1246

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

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

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

1257
        assert(!arg_root);
×
1258

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);
×
1278
        if (!arg_root)
×
1279
                return log_oom();
×
1280

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

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) {
128✔
1290
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
128✔
1291
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
128✔
1292
        _cleanup_(context_freep) Context* context = NULL;
128✔
1293
        _cleanup_strv_free_ char **appstream_urls = NULL;
128✔
1294
        const char *version;
128✔
1295
        int r;
128✔
1296

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

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

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

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

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

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

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

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

1338
                                r = strv_extend(&appstream_urls, *appstream_url);
×
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),
48✔
1344
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1345
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1346
                if (r < 0)
24✔
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);
24✔
1350
                if (r < 0)
24✔
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✔
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

1399
                        r = strv_extend(&transfers, t->id);
×
1400
                        if (r < 0)
×
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✔
1408
                                continue;
×
1409

1410
                        r = strv_extend(&transfers, t->id);
8✔
1411
                        if (r < 0)
8✔
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✔
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✔
1430
                        r = table_add_many(table,
×
1431
                                           TABLE_FIELD, "Documentation",
1432
                                           TABLE_STRING, f->documentation,
1433
                                           TABLE_SET_URL, f->documentation);
1434
                        if (r < 0)
×
1435
                                return table_log_add_error(r);
×
1436
                }
1437

1438
                if (f->appstream) {
8✔
1439
                        r = table_add_many(table,
×
1440
                                           TABLE_FIELD, "AppStream",
1441
                                           TABLE_STRING, f->appstream,
1442
                                           TABLE_SET_URL, f->appstream);
1443
                        if (r < 0)
×
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✔
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✔
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 {
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) {
×
1477
                        r = strv_extend(&features, f->id);
×
1478
                        if (r < 0)
×
1479
                                return log_oom();
×
1480
                }
1481

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

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

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) {
201✔
1497
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
201✔
1498
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
201✔
1499
        _cleanup_(context_freep) Context* context = NULL;
201✔
1500
        int r;
201✔
1501

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

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

1508
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
201✔
1509
        if (r < 0)
201✔
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✔
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✔
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✔
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

1561
                r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
1562
                if (r < 0)
×
1563
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1564
                if (!booted_version)
×
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) {
×
1593
                        log_notice("Newly installed version is newer than booted version, rebooting.");
×
1594
                        return reboot_now();
×
1595
                }
1596

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

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✔
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");
1656
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
2✔
1657
        _cleanup_(context_freep) Context* context = NULL;
×
1658
        _cleanup_free_ char *booted_version = NULL;
2✔
1659
        int r;
2✔
1660

1661
        assert(argc == 1);
2✔
1662

1663
        if (arg_image || arg_root)
2✔
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
        if (arg_component)
2✔
1668
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2✔
1669
                                       "The --component= switch may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]);
1670

1671
        r = context_make_offline(&context, /* node= */ NULL,
×
1672
                                 READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS | READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1673
        if (r < 0)
×
1674
                return r;
1675

1676
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
1677

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

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

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

1697
                if (streq(argv[0], "reboot"))
×
1698
                        return reboot_now();
×
1699

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

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

1711
        return EXIT_SUCCESS;
1712
}
1713

1714
static int component_name_valid(const char *c) {
53✔
1715
        _cleanup_free_ char *j = NULL;
53✔
1716

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

1719
        if (isempty(c))
106✔
1720
                return false;
1721

1722
        if (string_has_cc(c, NULL))
53✔
1723
                return false;
1724

1725
        if (!utf8_is_valid(c))
53✔
1726
                return false;
1727

1728
        j = strjoin("sysupdate.", c, ".d");
53✔
1729
        if (!j)
53✔
1730
                return -ENOMEM;
1731

1732
        return filename_is_valid(j);
53✔
1733
}
1734

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

1745
        assert(argc <= 1);
88✔
1746

1747
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
88✔
1748
        if (r < 0)
88✔
1749
                return r;
1750

1751
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, 0);
88✔
1752
        if (r < 0)
88✔
1753
                return r;
1754

1755
        ConfFile **directories = NULL;
88✔
1756
        size_t n_directories = 0;
88✔
1757

1758
        CLEANUP_ARRAY(directories, n_directories, conf_file_free_array);
88✔
1759

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

1765
        FOREACH_ARRAY(i, directories, n_directories) {
2,321✔
1766
                ConfFile *e = *i;
2,233✔
1767

1768
                if (streq(e->filename, "sysupdate.d")) {
2,233✔
1769
                        continue;
87✔
1770
                }
1771

1772
                const char *s = startswith(e->filename, "sysupdate.");
2,146✔
1773
                if (!s)
2,146✔
1774
                        continue;
2,112✔
1775

1776
                const char *a = endswith(s, ".d");
34✔
1777
                if (!a)
34✔
1778
                        continue;
×
1779

1780
                _cleanup_free_ char *n = strndup(s, a - s);
×
1781
                if (!n)
34✔
1782
                        return log_oom();
×
1783

1784
                r = component_name_valid(n);
34✔
1785
                if (r < 0)
34✔
1786
                        return log_error_errno(r, "Unable to validate component name '%s': %m", n);
×
1787
                if (r == 0)
34✔
1788
                        continue;
×
1789

1790
                r = set_ensure_put(&names, &string_hash_ops_free, n);
34✔
1791
                if (r < 0 && r != -EEXIST)
34✔
1792
                        return log_error_errno(r, "Failed to add component '%s' to set: %m", n);
×
1793
                TAKE_PTR(n);
1794
        }
1795

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

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

1809
        strv_sort(z);
88✔
1810

1811
        if (!sd_json_format_enabled(arg_json_format_flags)) {
88✔
1812
                if (!has_default_component && set_isempty(names)) {
×
1813
                        log_info("No components defined.");
×
1814
                        return 0;
1815
                }
1816

1817
                if (has_default_component)
1818
                        printf("%s<default>%s\n",
×
1819
                               ansi_highlight(), ansi_normal());
1820

1821
                STRV_FOREACH(i, z)
×
1822
                        puts(*i);
×
1823
        } else {
1824
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
88✔
1825

1826
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
88✔
1827
                                          SD_JSON_BUILD_PAIR_STRV("components", z));
1828
                if (r < 0)
88✔
1829
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1830

1831
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
88✔
1832
                if (r < 0)
88✔
1833
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1834
        }
1835

1836
        return 0;
1837
}
1838

1839
static int help(void) {
×
1840
        _cleanup_free_ char *link = NULL;
×
1841
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
1842
        int r;
×
1843

1844
        r = terminal_urlify_man("systemd-sysupdate", "8", &link);
×
1845
        if (r < 0)
×
1846
                return log_oom();
×
1847

1848
        r = verbs_get_help_table(&verbs);
×
1849
        if (r < 0)
×
1850
                return r;
1851

1852
        r = option_parser_get_help_table(&common_options);
×
1853
        if (r < 0)
×
1854
                return r;
1855

1856
        r = option_parser_get_help_table_group("Options", &options);
×
1857
        if (r < 0)
×
1858
                return r;
1859

1860
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
1861

1862
        printf("%s [OPTIONS...] [VERSION]\n"
×
1863
               "\n%sUpdate OS images.%s\n"
1864
               "\n%sCommands:%s\n",
1865
               program_invocation_short_name,
1866
               ansi_highlight(), ansi_normal(),
1867
               ansi_underline(), ansi_normal());
1868

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

1873
        r = table_print_or_warn(common_options);
×
1874
        if (r < 0)
×
1875
                return r;
1876

1877
        printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal());
×
1878
        r = table_print_or_warn(options);
×
1879
        if (r < 0)
×
1880
                return r;
1881

1882
        printf("\nSee the %s for details.\n", link);
×
1883
        return 0;
1884
}
1885

1886
VERB_COMMON_HELP_HIDDEN(help);
×
1887

1888
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
639✔
1889
        assert(argc >= 0);
639✔
1890
        assert(argv);
639✔
1891
        assert(remaining_args);
639✔
1892

1893
        OptionParser opts = { argc, argv };
639✔
1894
        int r;
639✔
1895

1896
        FOREACH_OPTION_OR_RETURN(c, &opts)
2,095✔
1897
                switch (c) {
817✔
1898

1899
                OPTION_COMMON_HELP:
×
1900
                        return help();
×
1901

1902
                OPTION_COMMON_VERSION:
×
1903
                        return version();
×
1904

1905
                OPTION_GROUP("Options"):
1906
                        break;
1907

1908
                OPTION('C', "component", "NAME",
19✔
1909
                       "Select component to update"):
1910
                        if (isempty(opts.arg)) {
19✔
1911
                                arg_component = mfree(arg_component);
×
1912
                                break;
×
1913
                        }
1914

1915
                        r = component_name_valid(opts.arg);
19✔
1916
                        if (r < 0)
19✔
1917
                                return log_error_errno(r, "Failed to determine if component name is valid: %m");
×
1918
                        if (r == 0)
19✔
1919
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", opts.arg);
×
1920

1921
                        r = free_and_strdup_warn(&arg_component, opts.arg);
19✔
1922
                        if (r < 0)
19✔
1923
                                return r;
1924

1925
                        break;
1926

1927
                OPTION_LONG("definitions", "DIR",
1✔
1928
                            "Find transfer definitions in specified directory"):
1929
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions);
1✔
1930
                        if (r < 0)
1✔
1931
                                return r;
1932
                        break;
1933

1934
                OPTION_LONG("root", "PATH",
×
1935
                            "Operate on an alternate filesystem root"):
1936
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
1937
                        if (r < 0)
×
1938
                                return r;
1939
                        break;
1940

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

1948
                OPTION_LONG("image-policy", "POLICY",
×
1949
                            "Specify disk image dissection policy"):
1950
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
1951
                        if (r < 0)
×
1952
                                return r;
1953
                        break;
1954

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

1961
                        break;
1962

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

1969
                        break;
1970

1971
                OPTION_LONG("sync", "BOOL",
×
1972
                            "Controls whether to sync data to disk"):
1973
                        r = parse_boolean_argument("--sync=", opts.arg, &arg_sync);
×
1974
                        if (r < 0)
×
1975
                                return r;
1976
                        break;
1977

1978
                OPTION_LONG("verify", "BOOL",
452✔
1979
                            "Force signature verification on or off"): {
1980
                        bool b;
452✔
1981

1982
                        r = parse_boolean_argument("--verify=", opts.arg, &b);
452✔
1983
                        if (r < 0)
452✔
1984
                                return r;
×
1985

1986
                        arg_verify = b;
452✔
1987
                        break;
452✔
1988
                }
1989

1990
                OPTION_LONG("reboot", NULL,
1✔
1991
                            "Reboot after updating to newer version"):
1992
                        arg_reboot = true;
1✔
1993
                        break;
1✔
1994

1995
                OPTION_LONG("offline", NULL,
114✔
1996
                            "Do not fetch metadata from the network"):
1997
                        arg_offline = true;
114✔
1998
                        break;
114✔
1999

2000
                OPTION_COMMON_NO_PAGER:
×
2001
                        arg_pager_flags |= PAGER_DISABLE;
×
2002
                        break;
×
2003

2004
                OPTION_COMMON_NO_LEGEND:
×
2005
                        arg_legend = false;
×
2006
                        break;
×
2007

2008
                OPTION_COMMON_JSON:
230✔
2009
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
230✔
2010
                        if (r <= 0)
230✔
2011
                                return r;
2012

2013
                        break;
2014
                }
2015

2016
        if (arg_image && arg_root)
639✔
2017
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
2018

2019
        if ((arg_image || arg_root) && arg_reboot)
639✔
2020
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
2021

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

2025
        if (arg_definitions && arg_component)
638✔
2026
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
2027

2028
        *remaining_args = option_parser_get_args(&opts);
638✔
2029
        return 1;
638✔
2030
}
2031

2032
static int run(int argc, char *argv[]) {
639✔
2033
        int r;
639✔
2034

2035
        log_setup();
639✔
2036

2037
        char **args = NULL;
639✔
2038
        r = parse_argv(argc, argv, &args);
639✔
2039
        if (r <= 0)
639✔
2040
                return r;
639✔
2041

2042
        return dispatch_verb(args, NULL);
638✔
2043
}
2044

2045
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
639✔
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