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

systemd / systemd / 17872489626

19 Sep 2025 10:01PM UTC coverage: 72.323% (-0.003%) from 72.326%
17872489626

push

github

YHNdnzj
core/manager: honor show_status_overridden in manager_watch_jobs_next_time()

Prompted by #39029

1 of 1 new or added line in 1 file covered. (100.0%)

8275 existing lines in 112 files now uncovered.

302883 of 418794 relevant lines covered (72.32%)

1056411.46 hits per line

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

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

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

6
#include "sd-daemon.h"
7

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

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

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

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

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

72
        Transfer **disabled_transfers;
73
        size_t n_disabled_transfers;
74

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

77
        UpdateSet **update_sets;
78
        size_t n_update_sets;
79

80
        UpdateSet *newest_installed, *candidate;
81

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

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

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

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

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

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

103
        hashmap_free(c->web_cache);
96✔
104

105
        return mfree(c);
96✔
106
}
107

108
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
282✔
109

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

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

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

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

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

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

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

144
        FOREACH_ARRAY(i, files, n_files) {
652✔
145
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
146
                Transfer **appended;
556✔
147
                ConfFile *e = *i;
556✔
148

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

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

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

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

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

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

181
        assert(c);
96✔
182

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

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

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

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

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

207
        ConfFile **files = NULL;
96✔
208
        size_t n_files = 0;
96✔
209

210
        CLEANUP_ARRAY(files, n_files, conf_file_free_many);
96✔
211

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

218
        FOREACH_ARRAY(i, files, n_files) {
188✔
219
                _cleanup_(feature_unrefp) Feature *f = NULL;
×
220
                ConfFile *e = *i;
92✔
221

222
                f = feature_new();
92✔
223
                if (!f)
92✔
UNCOV
224
                        return log_oom();
×
225

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

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

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

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

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

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

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

260
        return 0;
261
}
262

263
static int context_load_installed_instances(Context *c) {
96✔
264
        int r;
96✔
265

266
        assert(c);
96✔
267

268
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
192✔
269

270
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
570✔
271
                Transfer *t = *tr;
474✔
272

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

281
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
178✔
282
                Transfer *t = *tr;
82✔
283

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

292
        return 0;
293
}
294

295
static int context_load_available_instances(Context *c) {
76✔
296
        int r;
76✔
297

298
        assert(c);
76✔
299

300
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
152✔
301

302
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
454✔
303
                Transfer *t = *tr;
378✔
304

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

313
        return 0;
314
}
315

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

321
        assert(c);
166✔
322
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
166✔
323

324
        for (;;) {
780✔
UNCOV
325
                _cleanup_free_ Instance **cursor_instances = NULL;
×
326
                bool skip = false;
780✔
327
                UpdateSetFlags extra_flags = 0;
780✔
328
                _cleanup_free_ char *cursor = NULL;
614✔
329
                UpdateSet *us = NULL;
780✔
330

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

335
                        assert(*tr);
3,570✔
336

337
                        if (flags == UPDATE_AVAILABLE)
3,570✔
338
                                rr = &(*tr)->source;
1,948✔
339
                        else {
340
                                assert(flags == UPDATE_INSTALLED);
1,622✔
341
                                rr = &(*tr)->target;
1,622✔
342
                        }
343

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

347
                                assert(i);
9,290✔
348

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

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

357
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
614✔
UNCOV
358
                                        return log_oom();
×
359

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

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

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

374
                cursor_instances = new0(Instance*, c->n_transfers);
614✔
375
                if (!cursor_instances)
614✔
UNCOV
376
                        return log_oom();
×
377

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

383
                        assert(t);
3,044✔
384

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

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

408
                        cursor_instances[k] = match;
2,988✔
409

410
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
2,988✔
UNCOV
411
                                extra_flags |= UPDATE_OBSOLETE;
×
412

413
                        if (strv_contains(t->protected_versions, cursor))
2,988✔
414
                                extra_flags |= UPDATE_PROTECTED;
×
415
                }
416

417
                r = free_and_strdup_warn(&boundary, cursor);
614✔
418
                if (r < 0)
614✔
419
                        return r;
420

421
                if (skip)
614✔
422
                        continue;
56✔
423

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

428
                        if (strverscmp_improved(u->version, cursor) != 0)
1,032✔
429
                                continue;
836✔
430

431
                        /* Merge in what we've learned and continue onto the next version */
432

433
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE)) {
196✔
434
                                assert(u->n_instances == c->n_transfers);
72✔
435

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

439
                                for (size_t j = 0; j < u->n_instances; j++) {
448✔
440
                                        if (!u->instances[j])
376✔
441
                                                u->instances[j] = cursor_instances[j];
252✔
442

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

448
                        u->flags |= flags | extra_flags;
196✔
449

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

457
                        skip = true;
196✔
458
                        newest_found = true;
196✔
459
                        break;
196✔
460
                }
461

462
                if (skip)
558✔
463
                        continue;
196✔
464

465
                /* Doesn't exist yet, let's add it */
466
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
362✔
UNCOV
467
                        return log_oom();
×
468

469
                us = new(UpdateSet, 1);
362✔
470
                if (!us)
362✔
UNCOV
471
                        return log_oom();
×
472

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

480
                c->update_sets[c->n_update_sets++] = us;
362✔
481

482
                newest_found = true;
362✔
483

484
                /* Remember which one is the newest installed */
485
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
362✔
486
                        c->newest_installed = us;
86✔
487

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

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

498
        return 0;
499
}
500

501
static int context_discover_update_sets(Context *c) {
90✔
502
        int r;
90✔
503

504
        assert(c);
90✔
505

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

508
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
90✔
509
        if (r < 0)
90✔
510
                return r;
511

512
        if (!arg_offline) {
90✔
513
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
152✔
514

515
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
76✔
516
                if (r < 0)
76✔
517
                        return r;
518
        }
519

520
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
90✔
521
        return 0;
90✔
522
}
523

UNCOV
524
static int context_show_table(Context *c) {
×
UNCOV
525
        _cleanup_(table_unrefp) Table *t = NULL;
×
UNCOV
526
        int r;
×
527

528
        assert(c);
×
529

UNCOV
530
        t = table_new("", "version", "installed", "available", "assessment");
×
531
        if (!t)
×
UNCOV
532
                return log_oom();
×
533

534
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
535
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
UNCOV
536
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
537

538
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
539
                UpdateSet *us = *update_set;
×
UNCOV
540
                const char *color;
×
541

542
                color = update_set_flags_to_color(us->flags);
×
543

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

559
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
560
}
561

562
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
22✔
563
        assert(c);
22✔
564
        assert(version);
22✔
565

566
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
42✔
567
                if (streq((*update_set)->version, version))
42✔
568
                        return *update_set;
569

570
        return NULL;
571
}
572

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

584
        assert(c);
22✔
585
        assert(version);
22✔
586

587
        us = context_update_set_by_version(c, version);
22✔
588
        if (!us)
22✔
UNCOV
589
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
590

591
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
22✔
592
                (void) pager_open(arg_pager_flags);
10✔
593

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

608
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
22✔
609
        if (!t)
22✔
UNCOV
610
                return log_oom();
×
611

612
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
22✔
613
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
22✔
614
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
22✔
615
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
22✔
616
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
22✔
617
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
22✔
618
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
22✔
619

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

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

629
                if (tr->target.type == RESOURCE_PARTITION)
114✔
630
                        show_partition_columns = true;
44✔
631
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
114✔
632
                        show_fs_columns = true;
633

634
                STRV_FOREACH(changelog, tr->changelog) {
114✔
UNCOV
635
                        assert(*changelog);
×
636

UNCOV
637
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
638
                        if (!changelog_url)
×
UNCOV
639
                                return log_oom();
×
640

641
                        /* Avoid duplicates */
642
                        if (strv_contains(changelog_urls, changelog_url))
×
UNCOV
643
                                continue;
×
644

645
                        /* changelog_urls takes ownership of expanded changelog_url */
646
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
UNCOV
647
                        if (r < 0)
×
UNCOV
648
                                return log_oom();
×
649
                }
650
        }
651

652
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
136✔
653
                Instance *i = *inst;
114✔
654

655
                if (!i) {
114✔
656
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
4✔
657
                        continue;
4✔
658
                }
659

660
                r = table_add_many(t,
110✔
661
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
662
                                   TABLE_PATH, i->path);
663
                if (r < 0)
110✔
UNCOV
664
                        return table_log_add_error(r);
×
665

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

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

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

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

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

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

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

722
                if (i->metadata.no_auto >= 0) {
110✔
723
                        bool b;
×
724

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

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

743
                if (i->metadata.growfs >= 0) {
110✔
744
                        bool b;
×
745

UNCOV
746
                        have_growfs = true;
×
747
                        b = i->metadata.growfs;
×
UNCOV
748
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
749
                } else
750
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
110✔
751
                if (r < 0)
110✔
UNCOV
752
                        return table_log_add_error(r);
×
753

754
                if (i->metadata.sha256sum_set) {
110✔
755
                        _cleanup_free_ char *formatted = NULL;
×
756

UNCOV
757
                        have_sha256 = true;
×
758

UNCOV
759
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
×
760
                        if (!formatted)
×
UNCOV
761
                                return log_oom();
×
762

763
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
×
764
                } else
765
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
110✔
766
                if (r < 0)
110✔
UNCOV
767
                        return table_log_add_error(r);
×
768
        }
769

770
        /* Hide the fs/partition columns if we don't have any data to show there */
771
        if (!have_fs_attributes)
22✔
UNCOV
772
                show_fs_columns = false;
×
773
        if (!have_partition_attributes)
22✔
774
                show_partition_columns = false;
775

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

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

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

817
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
10✔
818
        } else {
819
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
12✔
820

821
                r = table_to_json(t, &t_json);
12✔
822
                if (r < 0)
12✔
UNCOV
823
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
824

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

837
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
12✔
838
                if (r < 0)
12✔
UNCOV
839
                        return log_error_errno(r, "Failed to print JSON: %m");
×
840

841
                return 0;
842
        }
843
}
844

845
static int context_vacuum(
20✔
846
                Context *c,
847
                uint64_t space,
848
                const char *extra_protected_version) {
849

850
        size_t disabled_count = 0;
20✔
851
        int r, count = 0;
20✔
852

853
        assert(c);
20✔
854

855
        if (space == 0)
20✔
856
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
4✔
857
        else
858
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
36✔
859

860
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
122✔
861
                Transfer *t = *tr;
102✔
862

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

867
                r = transfer_vacuum(t, space, extra_protected_version);
84✔
868
                if (r < 0)
84✔
869
                        return r;
870

871
                count = MAX(count, r);
84✔
872
        }
873

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

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

894
                r = sd_json_buildo(&json,
2✔
895
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
896
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
897
                if (r < 0)
2✔
UNCOV
898
                        return log_error_errno(r, "Failed to create JSON: %m");
×
899

900
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
2✔
901
                if (r < 0)
2✔
UNCOV
902
                        return log_error_errno(r, "Failed to print JSON: %m");
×
903
        }
904

905
        return 0;
906
}
907

908
static int context_make_offline(Context **ret, const char *node, bool requires_enabled_transfers) {
96✔
909
        _cleanup_(context_freep) Context* context = NULL;
96✔
910
        int r;
96✔
911

912
        assert(ret);
96✔
913

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

917
        context = context_new();
96✔
918
        if (!context)
96✔
UNCOV
919
                return log_oom();
×
920

921
        r = context_read_definitions(context, node, requires_enabled_transfers);
96✔
922
        if (r < 0)
96✔
923
                return r;
924

925
        r = context_load_installed_instances(context);
96✔
926
        if (r < 0)
96✔
927
                return r;
928

929
        *ret = TAKE_PTR(context);
96✔
930
        return 0;
96✔
931
}
932

933
static int context_make_online(Context **ret, const char *node) {
90✔
934
        _cleanup_(context_freep) Context* context = NULL;
90✔
935
        int r;
90✔
936

937
        assert(ret);
90✔
938

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

942
        r = context_make_offline(&context, node, /* requires_enabled_transfers= */ true);
90✔
943
        if (r < 0)
90✔
944
                return r;
945

946
        if (!arg_offline) {
90✔
947
                r = context_load_available_instances(context);
76✔
948
                if (r < 0)
76✔
949
                        return r;
950
        }
951

952
        r = context_discover_update_sets(context);
90✔
953
        if (r < 0)
90✔
954
                return r;
955

956
        *ret = TAKE_PTR(context);
90✔
957
        return 0;
90✔
958
}
959

960
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
67✔
961
        const Context *c = ASSERT_PTR(t->context);
67✔
962
        size_t i, n = c->n_transfers;
67✔
963
        uint64_t base, scaled;
67✔
964
        unsigned overall;
67✔
965

966
        for (i = 0; i < n; i++)
189✔
967
                if (c->transfers[i] == t)
189✔
968
                        break;
969
        assert(i < n); /* We should have found the index */
67✔
970

971
        base = (100 * 100 * i) / n;
67✔
972
        scaled = (100 * percentage) / n;
67✔
973
        overall = (unsigned) ((base + scaled) / 100);
67✔
974
        assert(overall <= 100);
67✔
975

976
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
67✔
977
        return sd_notifyf(/* unset_environment=*/ false, "X_SYSUPDATE_PROGRESS=%u\n"
134✔
978
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
979
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
980
                                              "STATUS=Updating to '%s' (%u%% complete).",
981
                                              overall, n - i, i, inst->metadata.version, overall);
67✔
982
}
983

984
static int context_apply(
18✔
985
                Context *c,
986
                const char *version,
987
                UpdateSet **ret_applied) {
988

989
        UpdateSet *us = NULL;
18✔
990
        int r;
18✔
991

992
        assert(c);
18✔
993

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

UNCOV
1002
                        if (ret_applied)
×
1003
                                *ret_applied = NULL;
×
1004

1005
                        return 0;
×
1006
                }
1007

1008
                us = c->candidate;
1009
        }
1010

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

UNCOV
1016
                if (ret_applied)
×
1017
                        *ret_applied = NULL;
×
1018

1019
                return 0;
×
1020
        }
1021

1022
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
18✔
UNCOV
1023
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1024
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
18✔
UNCOV
1025
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1026

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

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

1034
        (void) sd_notifyf(/* unset_environment=*/ false,
18✔
1035
                          "READY=1\n"
1036
                          "X_SYSUPDATE_VERSION=%s\n"
1037
                          "STATUS=Making room for '%s'.", us->version, us->version);
1038

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

1049
        if (arg_sync)
18✔
1050
                sync();
18✔
1051

1052
        (void) sd_notifyf(/* unset_environment=*/ false,
18✔
1053
                          "STATUS=Updating to '%s'.", us->version);
1054

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

1058
        for (size_t i = 0; i < c->n_transfers; i++) {
110✔
1059
                Instance *inst = us->instances[i];
92✔
1060
                Transfer *t = c->transfers[i];
92✔
1061

1062
                assert(inst); /* ditto */
92✔
1063

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

1069
                r = transfer_acquire_instance(t, inst, context_on_acquire_progress, c);
74✔
1070
                if (r < 0)
74✔
1071
                        return r;
1072
        }
1073

1074
        if (arg_sync)
18✔
1075
                sync();
18✔
1076

1077
        (void) sd_notifyf(/* unset_environment=*/ false,
18✔
1078
                          "STATUS=Installing '%s'.", us->version);
1079

1080
        for (size_t i = 0; i < c->n_transfers; i++) {
110✔
1081
                Instance *inst = us->instances[i];
92✔
1082
                Transfer *t = c->transfers[i];
92✔
1083

1084
                if (inst->resource == &t->target)
92✔
1085
                        continue;
18✔
1086

1087
                r = transfer_install_instance(t, inst, arg_root);
74✔
1088
                if (r < 0)
74✔
1089
                        return r;
1090
        }
1091

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

1094
        (void) sd_notifyf(/* unset_environment=*/ false,
18✔
1095
                          "STATUS=Installed '%s'.", us->version);
1096

1097
        if (ret_applied)
18✔
1098
                *ret_applied = us;
18✔
1099

1100
        return 1;
1101
}
1102

1103
static int process_image(
106✔
1104
                bool ro,
1105
                char **ret_mounted_dir,
1106
                LoopDevice **ret_loop_device) {
1107

1108
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
106✔
1109
        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
106✔
1110
        int r;
106✔
1111

1112
        assert(ret_mounted_dir);
106✔
1113
        assert(ret_loop_device);
106✔
1114

1115
        if (!arg_image)
106✔
1116
                return 0;
1117

UNCOV
1118
        assert(!arg_root);
×
1119

UNCOV
1120
        r = mount_image_privately_interactively(
×
1121
                        arg_image,
1122
                        arg_image_policy,
1123
                        (ro ? DISSECT_IMAGE_READ_ONLY : 0) |
1124
                        DISSECT_IMAGE_FSCK |
1125
                        DISSECT_IMAGE_MKDIR |
1126
                        DISSECT_IMAGE_GROWFS |
1127
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1128
                        DISSECT_IMAGE_USR_NO_ROOT |
1129
                        DISSECT_IMAGE_GENERIC_ROOT |
1130
                        DISSECT_IMAGE_REQUIRE_ROOT |
1131
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1132
                        &mounted_dir,
1133
                        /* ret_dir_fd= */ NULL,
1134
                        &loop_device);
UNCOV
1135
        if (r < 0)
×
1136
                return r;
1137

1138
        arg_root = strdup(mounted_dir);
×
UNCOV
1139
        if (!arg_root)
×
UNCOV
1140
                return log_oom();
×
1141

1142
        *ret_mounted_dir = TAKE_PTR(mounted_dir);
×
1143
        *ret_loop_device = TAKE_PTR(loop_device);
×
1144

1145
        return 0;
×
1146
}
1147

1148
static int verb_list(int argc, char **argv, void *userdata) {
28✔
1149
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
28✔
1150
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
28✔
1151
        _cleanup_(context_freep) Context* context = NULL;
28✔
1152
        _cleanup_strv_free_ char **appstream_urls = NULL;
28✔
1153
        const char *version;
28✔
1154
        int r;
28✔
1155

1156
        assert(argc <= 2);
28✔
1157
        version = argc >= 2 ? argv[1] : NULL;
28✔
1158

1159
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
28✔
1160
        if (r < 0)
28✔
1161
                return r;
1162

1163
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
28✔
1164
        if (r < 0)
28✔
1165
                return r;
1166

1167
        if (version)
28✔
1168
                return context_show_version(context, version);
22✔
1169
        else if (!sd_json_format_enabled(arg_json_format_flags))
6✔
UNCOV
1170
                return context_show_table(context);
×
1171
        else {
1172
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
6✔
1173
                _cleanup_strv_free_ char **versions = NULL;
6✔
1174
                const char *current = NULL;
6✔
1175

1176
                FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
26✔
1177
                        UpdateSet *us = *update_set;
20✔
1178

1179
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
20✔
1180
                            FLAGS_SET(us->flags, UPDATE_NEWEST))
16✔
1181
                                current = us->version;
6✔
1182

1183
                        r = strv_extend(&versions, us->version);
20✔
1184
                        if (r < 0)
20✔
UNCOV
1185
                                return log_oom();
×
1186
                }
1187

1188
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers)
28✔
1189
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
22✔
1190
                                /* Avoid duplicates */
UNCOV
1191
                                if (strv_contains(appstream_urls, *appstream_url))
×
UNCOV
1192
                                        continue;
×
1193

1194
                                r = strv_extend(&appstream_urls, *appstream_url);
×
1195
                                if (r < 0)
×
UNCOV
1196
                                        return log_oom();
×
1197
                        }
1198

1199
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
6✔
1200
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1201
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1202
                if (r < 0)
6✔
UNCOV
1203
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1204

1205
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
6✔
1206
                if (r < 0)
6✔
UNCOV
1207
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1208

1209
                return 0;
1210
        }
1211
}
1212

1213
static int verb_features(int argc, char **argv, void *userdata) {
4✔
1214
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
4✔
1215
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
4✔
1216
        _cleanup_(context_freep) Context* context = NULL;
4✔
1217
        _cleanup_(table_unrefp) Table *table = NULL;
4✔
1218
        const char *feature_id;
4✔
1219
        Feature *f;
4✔
1220
        int r;
4✔
1221

1222
        assert(argc <= 2);
4✔
1223
        feature_id = argc >= 2 ? argv[1] : NULL;
4✔
1224

1225
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
4✔
1226
        if (r < 0)
4✔
1227
                return r;
1228

1229
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
4✔
1230
        if (r < 0)
4✔
1231
                return r;
1232

1233
        if (feature_id) {
4✔
1234
                _cleanup_strv_free_ char **transfers = NULL;
2✔
1235

1236
                f = hashmap_get(context->features, feature_id);
2✔
1237
                if (!f)
2✔
UNCOV
1238
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1239
                                               "Optional feature not found: %s",
1240
                                               feature_id);
1241

1242
                table = table_new_vertical();
2✔
1243
                if (!table)
2✔
UNCOV
1244
                        return log_oom();
×
1245

1246
                FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
12✔
1247
                        Transfer *t = *tr;
10✔
1248

1249
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
10✔
1250
                                continue;
10✔
1251

UNCOV
1252
                        r = strv_extend(&transfers, t->id);
×
UNCOV
1253
                        if (r < 0)
×
UNCOV
1254
                                return log_oom();
×
1255
                }
1256

1257
                FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) {
4✔
1258
                        Transfer *t = *tr;
2✔
1259

1260
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
2✔
UNCOV
1261
                                continue;
×
1262

1263
                        r = strv_extend(&transfers, t->id);
2✔
1264
                        if (r < 0)
2✔
UNCOV
1265
                                return log_oom();
×
1266
                }
1267

1268
                r = table_add_many(table,
2✔
1269
                                   TABLE_FIELD, "Name",
1270
                                   TABLE_STRING, f->id,
1271
                                   TABLE_FIELD, "Enabled",
1272
                                   TABLE_BOOLEAN, f->enabled);
1273
                if (r < 0)
2✔
UNCOV
1274
                        return table_log_add_error(r);
×
1275

1276
                if (f->description) {
2✔
1277
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
2✔
1278
                        if (r < 0)
2✔
UNCOV
1279
                                return table_log_add_error(r);
×
1280
                }
1281

1282
                if (f->documentation) {
2✔
UNCOV
1283
                        r = table_add_many(table,
×
1284
                                           TABLE_FIELD, "Documentation",
1285
                                           TABLE_STRING, f->documentation,
1286
                                           TABLE_SET_URL, f->documentation);
UNCOV
1287
                        if (r < 0)
×
UNCOV
1288
                                return table_log_add_error(r);
×
1289
                }
1290

1291
                if (f->appstream) {
2✔
UNCOV
1292
                        r = table_add_many(table,
×
1293
                                           TABLE_FIELD, "AppStream",
1294
                                           TABLE_STRING, f->appstream,
1295
                                           TABLE_SET_URL, f->appstream);
UNCOV
1296
                        if (r < 0)
×
UNCOV
1297
                                return table_log_add_error(r);
×
1298
                }
1299

1300
                if (!strv_isempty(transfers)) {
2✔
1301
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
2✔
1302
                        if (r < 0)
2✔
UNCOV
1303
                                return table_log_add_error(r);
×
1304
                }
1305

1306
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
2✔
1307
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
2✔
1308
                table = table_new("", "feature", "description", "documentation");
2✔
1309
                if (!table)
2✔
UNCOV
1310
                        return log_oom();
×
1311

1312
                HASHMAP_FOREACH(f, context->features) {
4✔
1313
                        r = table_add_many(table,
2✔
1314
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1315
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
1316
                                           TABLE_STRING, f->id,
1317
                                           TABLE_STRING, f->description,
1318
                                           TABLE_STRING, f->documentation,
1319
                                           TABLE_SET_URL, f->documentation);
1320
                        if (r < 0)
2✔
UNCOV
1321
                                return table_log_add_error(r);
×
1322
                }
1323

1324
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
2✔
1325
        } else {
UNCOV
1326
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
UNCOV
1327
                _cleanup_strv_free_ char **features = NULL;
×
1328

1329
                HASHMAP_FOREACH(f, context->features) {
×
1330
                        r = strv_extend(&features, f->id);
×
UNCOV
1331
                        if (r < 0)
×
1332
                                return log_oom();
×
1333
                }
1334

1335
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
UNCOV
1336
                if (r < 0)
×
UNCOV
1337
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1338

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

1344
        return 0;
×
1345
}
1346

1347
static int verb_check_new(int argc, char **argv, void *userdata) {
44✔
1348
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
44✔
1349
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
44✔
1350
        _cleanup_(context_freep) Context* context = NULL;
44✔
1351
        int r;
44✔
1352

1353
        assert(argc <= 1);
44✔
1354

1355
        r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
44✔
1356
        if (r < 0)
44✔
1357
                return r;
1358

1359
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
44✔
1360
        if (r < 0)
44✔
1361
                return r;
1362

1363
        if (!sd_json_format_enabled(arg_json_format_flags)) {
44✔
1364
                if (!context->candidate) {
40✔
1365
                        log_debug("No candidate found.");
22✔
1366
                        return EXIT_FAILURE;
22✔
1367
                }
1368

1369
                puts(context->candidate->version);
18✔
1370
        } else {
1371
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
4✔
1372

1373
                if (context->candidate)
4✔
UNCOV
1374
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version));
×
1375
                else
1376
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
4✔
1377
                if (r < 0)
4✔
UNCOV
1378
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1379

1380
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
4✔
1381
                if (r < 0)
4✔
UNCOV
1382
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1383
        }
1384

1385
        return EXIT_SUCCESS;
1386
}
1387

1388
static int verb_vacuum(int argc, char **argv, void *userdata) {
2✔
1389
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
2✔
1390
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
2✔
1391
        _cleanup_(context_freep) Context* context = NULL;
2✔
1392
        int r;
2✔
1393

1394
        assert(argc <= 1);
2✔
1395

1396
        if (arg_instances_max < 1)
2✔
UNCOV
1397
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1398
                                      "The --instances-max argument must be >= 1 while vacuuming");
1399

1400
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
2✔
1401
        if (r < 0)
2✔
1402
                return r;
1403

1404
        r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false);
2✔
1405
        if (r < 0)
2✔
1406
                return r;
1407

1408
        return context_vacuum(context, 0, NULL);
2✔
1409
}
1410

1411
static int verb_update(int argc, char **argv, void *userdata) {
18✔
1412
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
18✔
1413
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
18✔
UNCOV
1414
        _cleanup_(context_freep) Context* context = NULL;
×
1415
        _cleanup_free_ char *booted_version = NULL;
18✔
1416
        UpdateSet *applied = NULL;
18✔
1417
        const char *version;
18✔
1418
        int r;
18✔
1419

1420
        assert(argc <= 2);
18✔
1421
        version = argc >= 2 ? argv[1] : NULL;
18✔
1422

1423
        if (arg_instances_max < 2)
18✔
UNCOV
1424
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1425
                                      "The --instances-max argument must be >= 2 while updating");
1426

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

UNCOV
1430
                r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
×
UNCOV
1431
                if (r < 0)
×
UNCOV
1432
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
1433
                if (!booted_version)
×
1434
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
1435
        }
1436

1437
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
18✔
1438
        if (r < 0)
18✔
1439
                return r;
1440

1441
        r = context_make_online(&context, loop_device ? loop_device->node : NULL);
18✔
1442
        if (r < 0)
18✔
1443
                return r;
1444

1445
        r = context_apply(context, version, &applied);
18✔
1446
        if (r < 0)
18✔
1447
                return r;
1448

1449
        if (r > 0 && arg_reboot) {
18✔
UNCOV
1450
                assert(applied);
×
UNCOV
1451
                assert(booted_version);
×
1452

1453
                if (strverscmp_improved(applied->version, booted_version) > 0) {
×
1454
                        log_notice("Newly installed version is newer than booted version, rebooting.");
×
UNCOV
1455
                        return reboot_now();
×
1456
                }
1457

1458
                if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
UNCOV
1459
                    FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
UNCOV
1460
                        log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
1461
                        return reboot_now();
×
1462
                }
1463

1464
                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
1465
        }
1466

1467
        return 0;
1468
}
1469

UNCOV
1470
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
×
UNCOV
1471
        _cleanup_(context_freep) Context* context = NULL;
×
UNCOV
1472
        _cleanup_free_ char *booted_version = NULL;
×
1473
        int r;
×
1474

1475
        assert(argc == 1);
×
1476

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

1481
        r = context_make_offline(&context, /* node= */ NULL, /* requires_enabled_transfers= */ true);
×
UNCOV
1482
        if (r < 0)
×
1483
                return r;
1484

1485
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
1486

UNCOV
1487
        r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
×
1488
        if (r < 0)
×
1489
                return r;
1490
        if (!context->newest_installed)
×
1491
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
1492

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

1500
        r = strverscmp_improved(context->newest_installed->version, booted_version);
×
1501
        if (r > 0) {
×
UNCOV
1502
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
1503
                           context->newest_installed->version, booted_version,
1504
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
1505

UNCOV
1506
                if (streq(argv[0], "reboot"))
×
UNCOV
1507
                        return reboot_now();
×
1508

1509
                return EXIT_SUCCESS;
1510
        } else if (r == 0)
×
UNCOV
1511
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
1512
                         context->newest_installed->version, booted_version);
1513
        else
1514
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
1515
                            context->newest_installed->version, booted_version);
1516

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

1520
        return EXIT_SUCCESS;
1521
}
1522

1523
static int component_name_valid(const char *c) {
12✔
1524
        _cleanup_free_ char *j = NULL;
12✔
1525

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

1528
        if (isempty(c))
24✔
1529
                return false;
1530

1531
        if (string_has_cc(c, NULL))
12✔
1532
                return false;
1533

1534
        if (!utf8_is_valid(c))
12✔
1535
                return false;
1536

1537
        j = strjoin("sysupdate.", c, ".d");
12✔
1538
        if (!j)
12✔
1539
                return -ENOMEM;
1540

1541
        return filename_is_valid(j);
12✔
1542
}
1543

1544
static int verb_components(int argc, char **argv, void *userdata) {
10✔
1545
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
10✔
1546
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
10✔
1547
        _cleanup_set_free_ Set *names = NULL;
10✔
1548
        bool has_default_component = false;
10✔
1549
        int r;
10✔
1550

1551
        assert(argc <= 1);
10✔
1552

1553
        r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
10✔
1554
        if (r < 0)
10✔
1555
                return r;
1556

1557
        ConfFile **directories = NULL;
10✔
1558
        size_t n_directories = 0;
10✔
1559

1560
        CLEANUP_ARRAY(directories, n_directories, conf_file_free_many);
10✔
1561

1562
        r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY, (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories);
10✔
1563
        if (r < 0)
10✔
UNCOV
1564
                return log_error_errno(r, "Failed to enumerate directories: %m");
×
1565

1566
        FOREACH_ARRAY(i, directories, n_directories) {
268✔
1567
                ConfFile *e = *i;
258✔
1568

1569
                if (streq(e->name, "sysupdate.d")) {
258✔
1570
                        has_default_component = true;
10✔
1571
                        continue;
10✔
1572
                }
1573

1574
                const char *s = startswith(e->name, "sysupdate.");
248✔
1575
                if (!s)
248✔
1576
                        continue;
240✔
1577

1578
                const char *a = endswith(s, ".d");
8✔
1579
                if (!a)
8✔
UNCOV
1580
                        continue;
×
1581

UNCOV
1582
                _cleanup_free_ char *n = strndup(s, a - s);
×
1583
                if (!n)
8✔
UNCOV
1584
                        return log_oom();
×
1585

1586
                r = component_name_valid(n);
8✔
1587
                if (r < 0)
8✔
UNCOV
1588
                        return log_error_errno(r, "Unable to validate component name '%s': %m", n);
×
1589
                if (r == 0)
8✔
UNCOV
1590
                        continue;
×
1591

1592
                r = set_ensure_put(&names, &string_hash_ops_free, n);
8✔
1593
                if (r < 0 && r != -EEXIST)
8✔
UNCOV
1594
                        return log_error_errno(r, "Failed to add component '%s' to set: %m", n);
×
1595
                TAKE_PTR(n);
1596
        }
1597

1598
        /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
1599
        _cleanup_free_ char **z = set_get_strv(names);
20✔
1600
        if (!z)
10✔
UNCOV
1601
                return log_oom();
×
1602

1603
        strv_sort(z);
10✔
1604

1605
        if (!sd_json_format_enabled(arg_json_format_flags)) {
10✔
UNCOV
1606
                if (!has_default_component && set_isempty(names)) {
×
UNCOV
1607
                        log_info("No components defined.");
×
UNCOV
1608
                        return 0;
×
1609
                }
1610

1611
                if (has_default_component)
×
UNCOV
1612
                        printf("%s<default>%s\n",
×
1613
                               ansi_highlight(), ansi_normal());
1614

1615
                STRV_FOREACH(i, z)
×
UNCOV
1616
                        puts(*i);
×
1617
        } else {
1618
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
10✔
1619

1620
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
10✔
1621
                                          SD_JSON_BUILD_PAIR_STRV("components", z));
1622
                if (r < 0)
10✔
UNCOV
1623
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1624

1625
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
10✔
1626
                if (r < 0)
10✔
UNCOV
1627
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1628
        }
1629

1630
        return 0;
1631
}
1632

UNCOV
1633
static int verb_help(int argc, char **argv, void *userdata) {
×
UNCOV
1634
        _cleanup_free_ char *link = NULL;
×
UNCOV
1635
        int r;
×
1636

1637
        r = terminal_urlify_man("systemd-sysupdate", "8", &link);
×
1638
        if (r < 0)
×
UNCOV
1639
                return log_oom();
×
1640

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

1681
        return 0;
1682
}
1683

1684
static int parse_argv(int argc, char *argv[]) {
106✔
1685

1686
        enum {
106✔
1687
                ARG_VERSION = 0x100,
1688
                ARG_NO_PAGER,
1689
                ARG_NO_LEGEND,
1690
                ARG_SYNC,
1691
                ARG_DEFINITIONS,
1692
                ARG_JSON,
1693
                ARG_ROOT,
1694
                ARG_IMAGE,
1695
                ARG_IMAGE_POLICY,
1696
                ARG_REBOOT,
1697
                ARG_VERIFY,
1698
                ARG_OFFLINE,
1699
                ARG_TRANSFER_SOURCE,
1700
        };
1701

1702
        static const struct option options[] = {
106✔
1703
                { "help",              no_argument,       NULL, 'h'                   },
1704
                { "version",           no_argument,       NULL, ARG_VERSION           },
1705
                { "no-pager",          no_argument,       NULL, ARG_NO_PAGER          },
1706
                { "no-legend",         no_argument,       NULL, ARG_NO_LEGEND         },
1707
                { "definitions",       required_argument, NULL, ARG_DEFINITIONS       },
1708
                { "instances-max",     required_argument, NULL, 'm'                   },
1709
                { "sync",              required_argument, NULL, ARG_SYNC              },
1710
                { "json",              required_argument, NULL, ARG_JSON              },
1711
                { "root",              required_argument, NULL, ARG_ROOT              },
1712
                { "image",             required_argument, NULL, ARG_IMAGE             },
1713
                { "image-policy",      required_argument, NULL, ARG_IMAGE_POLICY      },
1714
                { "reboot",            no_argument,       NULL, ARG_REBOOT            },
1715
                { "component",         required_argument, NULL, 'C'                   },
1716
                { "verify",            required_argument, NULL, ARG_VERIFY            },
1717
                { "offline",           no_argument,       NULL, ARG_OFFLINE           },
1718
                { "transfer-source",   required_argument, NULL, ARG_TRANSFER_SOURCE   },
1719
                {}
1720
        };
1721

1722
        int c, r;
106✔
1723

1724
        assert(argc >= 0);
106✔
1725
        assert(argv);
106✔
1726

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

1729
                switch (c) {
128✔
1730

UNCOV
1731
                case 'h':
×
UNCOV
1732
                        return verb_help(0, NULL, NULL);
×
1733

1734
                case ARG_VERSION:
×
1735
                        return version();
×
1736

1737
                case ARG_NO_PAGER:
×
1738
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
1739
                        break;
×
1740

1741
                case ARG_NO_LEGEND:
×
1742
                        arg_legend = false;
×
UNCOV
1743
                        break;
×
1744

1745
                case 'm':
×
1746
                        r = safe_atou64(optarg, &arg_instances_max);
×
UNCOV
1747
                        if (r < 0)
×
1748
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg);
×
1749

1750
                        break;
1751

UNCOV
1752
                case ARG_SYNC:
×
UNCOV
1753
                        r = parse_boolean_argument("--sync=", optarg, &arg_sync);
×
UNCOV
1754
                        if (r < 0)
×
1755
                                return r;
1756
                        break;
1757

UNCOV
1758
                case ARG_DEFINITIONS:
×
UNCOV
1759
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions);
×
UNCOV
1760
                        if (r < 0)
×
1761
                                return r;
1762
                        break;
1763

1764
                case ARG_JSON:
34✔
1765
                        r = parse_json_argument(optarg, &arg_json_format_flags);
34✔
1766
                        if (r <= 0)
34✔
1767
                                return r;
1768

1769
                        break;
1770

UNCOV
1771
                case ARG_ROOT:
×
UNCOV
1772
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
×
UNCOV
1773
                        if (r < 0)
×
1774
                                return r;
1775
                        break;
1776

UNCOV
1777
                case ARG_IMAGE:
×
UNCOV
1778
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
×
UNCOV
1779
                        if (r < 0)
×
1780
                                return r;
1781
                        break;
1782

UNCOV
1783
                case ARG_IMAGE_POLICY:
×
UNCOV
1784
                        r = parse_image_policy_argument(optarg, &arg_image_policy);
×
UNCOV
1785
                        if (r < 0)
×
1786
                                return r;
1787
                        break;
1788

UNCOV
1789
                case ARG_REBOOT:
×
UNCOV
1790
                        arg_reboot = true;
×
UNCOV
1791
                        break;
×
1792

1793
                case 'C':
4✔
1794
                        if (isempty(optarg)) {
4✔
UNCOV
1795
                                arg_component = mfree(arg_component);
×
UNCOV
1796
                                break;
×
1797
                        }
1798

1799
                        r = component_name_valid(optarg);
4✔
1800
                        if (r < 0)
4✔
UNCOV
1801
                                return log_error_errno(r, "Failed to determine if component name is valid: %m");
×
1802
                        if (r == 0)
4✔
UNCOV
1803
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg);
×
1804

1805
                        r = free_and_strdup_warn(&arg_component, optarg);
4✔
1806
                        if (r < 0)
4✔
1807
                                return r;
1808

1809
                        break;
1810

1811
                case ARG_VERIFY: {
76✔
1812
                        bool b;
76✔
1813

1814
                        r = parse_boolean_argument("--verify=", optarg, &b);
76✔
1815
                        if (r < 0)
76✔
UNCOV
1816
                                return r;
×
1817

1818
                        arg_verify = b;
76✔
1819
                        break;
76✔
1820
                }
1821

1822
                case ARG_OFFLINE:
14✔
1823
                        arg_offline = true;
14✔
1824
                        break;
14✔
1825

UNCOV
1826
                case ARG_TRANSFER_SOURCE:
×
UNCOV
1827
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source);
×
UNCOV
1828
                        if (r < 0)
×
1829
                                return r;
1830

1831
                        break;
1832

1833
                case '?':
1834
                        return -EINVAL;
1835

UNCOV
1836
                default:
×
UNCOV
1837
                        assert_not_reached();
×
1838
                }
1839
        }
1840

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

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

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

1850
        return 1;
1851
}
1852

1853
static int sysupdate_main(int argc, char *argv[]) {
106✔
1854

1855
        static const Verb verbs[] = {
106✔
1856
                { "list",       VERB_ANY, 2, VERB_DEFAULT, verb_list              },
1857
                { "components", VERB_ANY, 1, 0,            verb_components        },
1858
                { "features",   VERB_ANY, 2, 0,            verb_features          },
1859
                { "check-new",  VERB_ANY, 1, 0,            verb_check_new         },
1860
                { "update",     VERB_ANY, 2, 0,            verb_update            },
1861
                { "vacuum",     VERB_ANY, 1, 0,            verb_vacuum            },
1862
                { "reboot",     1,        1, 0,            verb_pending_or_reboot },
1863
                { "pending",    1,        1, 0,            verb_pending_or_reboot },
1864
                { "help",       VERB_ANY, 1, 0,            verb_help              },
1865
                {}
1866
        };
1867

1868
        return dispatch_verb(argc, argv, verbs, NULL);
106✔
1869
}
1870

1871
static int run(int argc, char *argv[]) {
106✔
1872
        int r;
106✔
1873

1874
        log_setup();
106✔
1875

1876
        r = parse_argv(argc, argv);
106✔
1877
        if (r <= 0)
106✔
1878
                return r;
1879

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

1883
        return sysupdate_main(argc, argv);
106✔
1884
}
1885

1886
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
106✔
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