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

systemd / systemd / 29132483780

10 Jul 2026 08:43PM UTC coverage: 72.912% (+0.2%) from 72.702%
29132483780

push

github

bluca
man: run forgotten 'update-man-rules'

344600 of 472622 relevant lines covered (72.91%)

1365091.83 hits per line

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

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

3
#include <unistd.h>
4

5
#include "sd-bus.h"
6
#include "sd-daemon.h"
7
#include "sd-json.h"
8
#include "sd-varlink.h"
9

10
#include "build.h"
11
#include "bus-polkit.h"
12
#include "conf-files.h"
13
#include "conf-parser.h"
14
#include "constants.h"
15
#include "discover-image.h"
16
#include "dissect-image.h"
17
#include "dlopen-note.h"
18
#include "dropin.h"
19
#include "env-util.h"
20
#include "errno-util.h"
21
#include "fd-util.h"
22
#include "format-table.h"
23
#include "glyph-util.h"
24
#include "hashmap.h"
25
#include "help-util.h"
26
#include "hexdecoct.h"
27
#include "image-policy.h"
28
#include "json-util.h"
29
#include "loop-util.h"
30
#include "main-func.h"
31
#include "mount-util.h"
32
#include "options.h"
33
#include "os-util.h"
34
#include "pager.h"
35
#include "parse-argument.h"
36
#include "parse-util.h"
37
#include "path-util.h"
38
#include "pretty-print.h"
39
#include "runtime-scope.h"
40
#include "sort-util.h"
41
#include "string-util.h"
42
#include "strv.h"
43
#include "sysupdate.h"
44
#include "sysupdate-cleanup.h"
45
#include "sysupdate-config.h"
46
#include "sysupdate-feature.h"
47
#include "sysupdate-instance.h"
48
#include "sysupdate-target.h"
49
#include "sysupdate-transfer.h"
50
#include "sysupdate-update-set.h"
51
#include "sysupdate-util.h"
52
#include "varlink-io.systemd.SysUpdate.h"
53
#include "varlink-util.h"
54
#include "verbs.h"
55

56
static char *arg_definitions = NULL;
57
static bool arg_sync = true;
58
static uint64_t arg_instances_max = UINT64_MAX;
59
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
60
static PagerFlags arg_pager_flags = 0;
61
static bool arg_legend = true;
62
static char *arg_root = NULL;
63
static char *arg_image = NULL;
64
static bool arg_reboot = false;
65
static int arg_cleanup = -1;
66
static char *arg_component = NULL;
67
static bool arg_component_all = false;
68
static int arg_verify = -1;
69
static ImagePolicy *arg_image_policy = NULL;
70
static bool arg_offline = false;
71
static char *arg_transfer_source = NULL;
72
static bool arg_varlink = false;
73

74
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
1,336✔
75
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
1,336✔
76
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
1,336✔
77
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
1,336✔
78
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
1,336✔
79
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
1,336✔
80

81
#define CONTEXT_NULL                                              \
82
        (Context) {                                               \
83
                .component_enabled = true,                        \
84
                .sync = true,                                     \
85
                .instances_max = UINT64_MAX,                      \
86
                .verify = -1,                                     \
87
                .cleanup = -1,                                    \
88
                .installdb_fd = -EBADF,                           \
89
                .target_identifier.class = _TARGET_CLASS_INVALID, \
90
        }
91

92
void context_done(Context *c) {
2,438✔
93
        assert(c);
2,438✔
94

95
        c->mounted_dir = umount_and_rmdir_and_free(c->mounted_dir);
2,438✔
96
        c->loop_device = loop_device_unref(c->loop_device);
2,438✔
97

98
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
11,229✔
99
                transfer_free(*tr);
8,791✔
100
        c->transfers = mfree(c->transfers);
2,438✔
101
        c->n_transfers = 0;
2,438✔
102

103
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
3,570✔
104
                transfer_free(*tr);
1,132✔
105
        c->disabled_transfers = mfree(c->disabled_transfers);
2,438✔
106
        c->n_disabled_transfers = 0;
2,438✔
107

108
        c->features = hashmap_free(c->features);
2,438✔
109

110
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
7,131✔
111
                update_set_free(*us);
4,693✔
112
        c->update_sets = mfree(c->update_sets);
2,438✔
113
        c->n_update_sets = 0;
2,438✔
114

115
        c->web_cache = hashmap_free(c->web_cache);
2,438✔
116

117
        c->installdb_fd = safe_close(c->installdb_fd);
2,438✔
118

119
        c->definitions = mfree(c->definitions);
2,438✔
120
        c->root = mfree(c->root);
2,438✔
121
        c->image = mfree(c->image);
2,438✔
122
        c->component = mfree(c->component);
2,438✔
123
        c->component_description = mfree(c->component_description);
2,438✔
124
        c->component_documentation = strv_free(c->component_documentation);
2,438✔
125
        c->image_policy = image_policy_free(c->image_policy);
2,438✔
126
        c->transfer_source = mfree(c->transfer_source);
2,438✔
127

128
        target_identifier_done(&c->target_identifier);
2,438✔
129
}
2,438✔
130

131
static int context_from_cmdline(Context *ret) {
1,119✔
132
        assert(ret);
1,119✔
133

134
        _cleanup_(context_done) Context context = CONTEXT_NULL;
1,119✔
135

136
        context.instances_max = arg_instances_max;
1,119✔
137
        context.sync = arg_sync;
1,119✔
138
        context.reboot = arg_reboot;
1,119✔
139
        context.verify = arg_verify;
1,119✔
140
        context.offline = arg_offline;
1,119✔
141
        context.cleanup = arg_cleanup;
1,119✔
142
        context.component_all = arg_component_all;
1,119✔
143

144
        if (strdup_to(&context.definitions, arg_definitions) < 0)
1,119✔
145
                return log_oom();
×
146

147
        if (strdup_to(&context.root, arg_root) < 0)
1,119✔
148
                return log_oom();
×
149

150
        if (strdup_to(&context.image, arg_image) < 0)
1,119✔
151
                return log_oom();
×
152

153
        if (strdup_to(&context.component, arg_component) < 0)
1,119✔
154
                return log_oom();
×
155

156
        if (strdup_to(&context.transfer_source, arg_transfer_source) < 0)
1,119✔
157
                return log_oom();
×
158

159
        if (arg_image_policy) {
1,119✔
160
                context.image_policy = image_policy_copy(arg_image_policy);
×
161
                if (!context.image_policy)
×
162
                        return log_oom();
×
163
        }
164

165
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
1,119✔
166
        return 0;
1,119✔
167
}
168

169
static int context_from_base_with_component(const Context *base, const char *component, Context *ret) {
×
170
        assert(base);
×
171
        assert(component);
×
172
        assert(ret);
×
173

174
        /* Copies the specified context, but changes the component to the specified one */
175

176
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
177

178
        context.instances_max = base->instances_max;
×
179
        context.sync = base->sync;
×
180
        context.reboot = base->reboot;
×
181
        context.verify = base->verify;
×
182
        context.offline = base->offline;
×
183
        context.cleanup = base->cleanup;
×
184

185
        if (strdup_to(&context.root, base->root) < 0)
×
186
                return log_oom();
×
187

188
        if (strdup_to(&context.image, base->image) < 0)
×
189
                return log_oom();
×
190

191
        if (strdup_to(&context.transfer_source, base->transfer_source) < 0)
×
192
                return log_oom();
×
193

194
        if (base->image_policy) {
×
195
                context.image_policy = image_policy_copy(base->image_policy);
×
196
                if (!context.image_policy)
×
197
                        return log_oom();
×
198
        }
199

200
        if (strdup_to(&context.component, component) < 0)
×
201
                return log_oom();
×
202

203
        /* NB: we do not copy .loop_device/.mounted_dir here, since that's for lifetime tracking only, and
204
         * not needed for access (we only need to copy .root/.image) for that. Under the assumption that the
205
         * contexts initialized via context_from_base_with_component() have a shorter lifetime than the
206
         * contexts they are copied from we don't bother with lifetime tracking of the loopback device/mount
207
         * point here. */
208

209
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
×
210
        return 0;
×
211
}
212

213
/* Stores any long-running server state which needs to persist between varlink calls, such as state for
214
 * pending polkit requests */
215
typedef struct Server {
216
        sd_bus *system_bus;
217
        Hashmap *polkit_registry;
218
} Server;
219

220
#define SERVER_NULL \
221
        (Server) { \
222
                /* all fields fine with being initialised to NULL */ \
223
        }
224

225
static void server_done(Server *s) {
212✔
226
        assert(s);
212✔
227

228
        s->polkit_registry = hashmap_free(s->polkit_registry);
212✔
229
        s->system_bus = sd_bus_flush_close_unref(s->system_bus);
212✔
230
}
212✔
231

232
static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free);
×
233

234
static int read_features(
1,299✔
235
                Context *c,
236
                const char **dirs) {
237

238
        int r;
1,299✔
239

240
        assert(c);
1,299✔
241

242
        ConfFile **files = NULL;
1,299✔
243
        size_t n_files = 0;
1,299✔
244
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,299✔
245

246
        r = conf_files_list_strv_full(
2,598✔
247
                        ".feature",
248
                        c->root,
1,299✔
249
                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
250
                        dirs,
251
                        &files,
252
                        &n_files);
253
        if (r < 0)
1,299✔
254
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m");
×
255

256
        FOREACH_ARRAY(i, files, n_files) {
2,531✔
257
                ConfFile *e = *i;
1,232✔
258

259
                _cleanup_(feature_unrefp) Feature *f = feature_new();
1,232✔
260
                if (!f)
1,232✔
261
                        return log_oom();
×
262

263
                r = feature_read_definition(f, c->root, e->result, dirs);
1,232✔
264
                if (r < 0)
1,232✔
265
                        return r;
266

267
                r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f);
1,232✔
268
                if (r < 0)
1,232✔
269
                        return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id);
×
270

271
                TAKE_PTR(f);
1,232✔
272
        }
273

274
        return 0;
275
}
276

277
static int read_transfers(
1,306✔
278
                Context *c,
279
                const char **dirs,
280
                const char *suffix,
281
                const char *node) {
282

283
        ConfFile **files = NULL;
1,306✔
284
        Transfer **transfers = NULL, **disabled = NULL;
1,306✔
285
        size_t n_files = 0, n_transfers = 0, n_disabled = 0;
1,306✔
286
        int r;
1,306✔
287

288
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,306✔
289
        CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array);
1,306✔
290
        CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array);
1,306✔
291

292
        assert(c);
1,306✔
293
        assert(dirs);
1,306✔
294
        assert(suffix);
1,306✔
295

296
        r = conf_files_list_strv_full(suffix, c->root,
1,306✔
297
                                      CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
298
                                      dirs, &files, &n_files);
299
        if (r < 0)
1,306✔
300
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*%s definitions: %m", suffix);
×
301

302
        FOREACH_ARRAY(i, files, n_files) {
11,229✔
303
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
304
                Transfer **appended;
9,923✔
305
                ConfFile *e = *i;
9,923✔
306

307
                t = transfer_new(c);
9,923✔
308
                if (!t)
9,923✔
309
                        return log_oom();
×
310

311
                r = transfer_read_definition(t, e->result, dirs, c->features);
9,923✔
312
                if (r < 0)
9,923✔
313
                        return r;
314

315
                r = transfer_resolve_paths(t, c->root, node);
9,923✔
316
                if (r < 0)
9,923✔
317
                        return r;
318

319
                if (t->enabled)
9,923✔
320
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
8,791✔
321
                else
322
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
1,132✔
323
                if (!appended)
9,923✔
324
                        return log_oom();
×
325
                TAKE_PTR(t);
9,923✔
326
        }
327

328
        c->transfers = TAKE_PTR(transfers);
1,306✔
329
        c->n_transfers = n_transfers;
1,306✔
330
        c->disabled_transfers = TAKE_PTR(disabled);
1,306✔
331
        c->n_disabled_transfers = n_disabled;
1,306✔
332
        return 0;
1,306✔
333
}
334

335
static int read_component(Context *c) {
1,299✔
336
        int r;
1,299✔
337

338
        assert(c);
1,299✔
339

340
        /* Read a component description file, but only if we actually operate on a component */
341
        if (c->definitions || !c->component)
1,299✔
342
                return 0;
1,299✔
343

344
        _cleanup_free_ char *j = strjoin("sysupdate.", c->component, ".component");
76✔
345
        if (!j)
38✔
346
                return log_oom();
×
347

348
        ConfigTableItem table[] = {
38✔
349
                { "Component", "Description",   config_parse_string,              0, &c->component_description   },
38✔
350
                { "Component", "Documentation", config_parse_url_specifiers_many, 0, &c->component_documentation },
38✔
351
                { "Component", "Enabled",       config_parse_bool,                0, &c->component_enabled       },
38✔
352
                {}
353
        };
354

355
        r = config_parse_standard_file_with_dropins_full(
76✔
356
                        c->root,
357
                        /* root_fd= */ -EBADF,
358
                        j,
359
                        "Component\0",
360
                        config_item_table_lookup, table,
361
                        CONFIG_PARSE_WARN,
362
                        /* userdata= */ (void *) c->root,
38✔
363
                        /* ret_stats_by_path= */ NULL,
364
                        /* ret_dropin_files= */ NULL);
365
        if (r < 0)
38✔
366
                return r;
×
367

368
        return 0;
369
}
370

371
typedef enum ReadDefinitionsFlags {
372
        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0, /* fail unless there's at least one enabled transfer */
373
        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS     = 1 << 1, /* fail unless there's at least one transfer */
374
        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT = 1 << 2, /* fail if component is disabled */
375
} ReadDefinitionsFlags;
376

377
static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
1,299✔
378
        _cleanup_strv_free_ char **dirs = NULL;
1,299✔
379
        int r;
1,299✔
380

381
        assert(c);
1,299✔
382

383
        if (c->definitions)
1,299✔
384
                dirs = strv_new(c->definitions);
5✔
385
        else if (c->component) {
1,294✔
386
                char **l = CONF_PATHS_STRV("");
38✔
387
                size_t i = 0;
38✔
388

389
                dirs = new0(char*, strv_length(l) + 1);
38✔
390
                if (!dirs)
38✔
391
                        return log_oom();
×
392

393
                STRV_FOREACH(dir, l) {
190✔
394
                        char *j;
152✔
395

396
                        j = strjoin(*dir, "sysupdate.", c->component, ".d");
152✔
397
                        if (!j)
152✔
398
                                return log_oom();
×
399

400
                        dirs[i++] = j;
152✔
401
                }
402
        } else
403
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
1,256✔
404
        if (!dirs)
1,299✔
405
                return log_oom();
×
406

407
        r = read_component(c);
1,299✔
408
        if (r < 0)
1,299✔
409
                return r;
410

411
        r = read_features(c, (const char**) dirs);
1,299✔
412
        if (r < 0)
1,299✔
413
                return r;
414

415
        r = read_transfers(c, (const char**) dirs, ".transfer", node);
1,299✔
416
        if (r < 0)
1,299✔
417
                return r;
418

419
        if (c->n_transfers + c->n_disabled_transfers == 0) {
1,299✔
420
                /* Backwards-compat: If no .transfer defs are found, fall back to trying .conf! */
421
                r = read_transfers(c, (const char**) dirs, ".conf", node);
7✔
422
                if (r < 0)
7✔
423
                        return r;
424

425
                if (c->n_transfers + c->n_disabled_transfers > 0)
7✔
426
                        log_warning("As of v257, transfer definitions should have the '.transfer' extension.");
×
427
        }
428

429
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS) &&
1,299✔
430
            c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) {
1,115✔
431
                if (c->component)
×
432
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
433
                                               "No transfer definitions for component '%s' found.",
434
                                               c->component);
435

436
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
437
                                       "No transfer definitions found.");
438
        }
439

440
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT) && !c->component_enabled)
1,299✔
441
                return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Component is disabled.");
×
442

443
        return 0;
444
}
445

446
static int context_load_installed_instances(Context *c) {
1,299✔
447
        int r;
1,299✔
448

449
        assert(c);
1,299✔
450

451
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
2,598✔
452

453
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
10,090✔
454
                Transfer *t = *tr;
8,791✔
455

456
                r = resource_load_instances(
8,791✔
457
                                &t->target,
458
                                c->verify >= 0 ? c->verify : t->verify,
8,791✔
459
                                &c->web_cache);
460
                if (r < 0)
8,791✔
461
                        return r;
462
        }
463

464
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
2,431✔
465
                Transfer *t = *tr;
1,132✔
466

467
                r = resource_load_instances(
1,132✔
468
                                &t->target,
469
                                c->verify >= 0 ? c->verify : t->verify,
1,132✔
470
                                &c->web_cache);
471
                if (r < 0)
1,132✔
472
                        return r;
473
        }
474

475
        return 0;
476
}
477

478
static int context_load_available_instances(Context *c) {
839✔
479
        int r;
839✔
480

481
        assert(c);
839✔
482

483
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
1,678✔
484

485
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
6,436✔
486
                Transfer *t = *tr;
5,617✔
487

488
                r = resource_load_instances(
5,617✔
489
                                &t->source,
490
                                c->verify >= 0 ? c->verify : t->verify,
5,617✔
491
                                &c->web_cache);
492
                if (r < 0)
5,617✔
493
                        return r;
494
        }
495

496
        return 0;
497
}
498

499
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
1,850✔
500
        _cleanup_free_ char *boundary = NULL;
1,850✔
501
        bool newest_found = false;
1,850✔
502
        int r;
1,850✔
503

504
        assert(c);
1,850✔
505
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
1,850✔
506

507
        for (;;) {
9,840✔
508
                _cleanup_free_ Instance **cursor_instances = NULL;
×
509
                bool skip = false;
9,840✔
510
                UpdateSetFlags extra_flags = 0;
9,840✔
511
                _cleanup_free_ char *cursor = NULL;
7,990✔
512
                UpdateSet *us = NULL;
9,840✔
513

514
                /* First, let's find the newest version that's older than the boundary. */
515
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
72,278✔
516
                        Resource *rr;
63,257✔
517

518
                        assert(*tr);
63,257✔
519

520
                        if (flags == UPDATE_AVAILABLE)
63,257✔
521
                                rr = &(*tr)->source;
32,235✔
522
                        else {
523
                                assert(flags == UPDATE_INSTALLED);
31,022✔
524
                                rr = &(*tr)->target;
31,022✔
525
                        }
526

527
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
198,644✔
528
                                Instance *i = *inst; /* Sorted newest-to-oldest */
183,419✔
529

530
                                assert(i);
183,419✔
531

532
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
183,419✔
533
                                        continue; /* Not older than the boundary */
135,387✔
534

535
                                if (cursor && strverscmp(i->metadata.version, cursor) <= 0)
48,032✔
536
                                        break; /* Not newer than the cursor. The same will be true for all
537
                                                * subsequent instances (due to sorting) so let's skip to the
538
                                                * next transfer. */
539

540
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
7,990✔
541
                                        return log_oom();
×
542

543
                                break; /* All subsequent instances will be older than this one */
544
                        }
545

546
                        if (flags == UPDATE_AVAILABLE && !cursor)
63,257✔
547
                                break; /* This transfer didn't have a version older than the boundary,
548
                                        * so any older-than-boundary version that might exist in a different
549
                                        * transfer must always be incomplete. For reasons described below,
550
                                        * we don't include incomplete versions for AVAILABLE updates. So we
551
                                        * are completely done looking. */
552
                }
553

554
                if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
9,840✔
555
                        break;
556

557
                cursor_instances = new0(Instance*, c->n_transfers);
7,990✔
558
                if (!cursor_instances)
7,990✔
559
                        return log_oom();
×
560

561
                /* Now let's find all the instances that match the version of the cursor, if we have them */
562
                for (size_t k = 0; k < c->n_transfers; k++) {
61,480✔
563
                        Transfer *t = c->transfers[k];
54,119✔
564
                        Instance *match = NULL;
54,119✔
565

566
                        assert(t);
54,119✔
567

568
                        if (flags == UPDATE_AVAILABLE) {
54,119✔
569
                                match = resource_find_instance(&t->source, cursor);
30,106✔
570
                                if (!match) {
30,106✔
571
                                        /* When we're looking for updates to download, we don't offer
572
                                         * incomplete versions at all. The server wants to send us an update
573
                                         * with parts of the OS missing. For robustness sake, let's not do
574
                                         * that. */
575
                                        skip = true;
576
                                        break;
577
                                }
578
                        } else {
579
                                assert(flags == UPDATE_INSTALLED);
24,013✔
580

581
                                match = resource_find_instance(&t->target, cursor);
24,013✔
582
                                if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING)))
24,013✔
583
                                        /* When we're looking for installed versions, let's be robust and treat
584
                                         * an incomplete installation as an installation. Otherwise, there are
585
                                         * situations that can lead to sysupdate wiping the currently booted OS.
586
                                         * See https://github.com/systemd/systemd/issues/33339 */
587
                                        extra_flags |= UPDATE_INCOMPLETE;
7,444✔
588
                        }
589

590
                        cursor_instances[k] = match;
53,490✔
591

592
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
53,490✔
593
                                extra_flags |= UPDATE_OBSOLETE;
×
594

595
                        if (strv_contains(t->protected_versions, cursor))
53,490✔
596
                                extra_flags |= UPDATE_PROTECTED;
×
597

598
                        /* Partial or pending updates by definition are not incomplete, they’re
599
                         * partial/pending instead. While an individual Instance cannot be both partial and
600
                         * pending, an UpdateSet as a whole can contain both partial and pending instances. */
601
                        assert(!match || !(match->is_partial && match->is_pending));
53,490✔
602

603
                        if (match && match->is_partial)
45,866✔
604
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
36✔
605

606
                        if (match && match->is_pending)
45,866✔
607
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
748✔
608
                }
609

610
                r = free_and_strdup_warn(&boundary, cursor);
7,990✔
611
                if (r < 0)
7,990✔
612
                        return r;
613

614
                if (skip)
7,990✔
615
                        continue;
629✔
616

617
                /* See if we already have this update set in our table */
618
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
21,160✔
619
                        UpdateSet *u = *update_set;
16,467✔
620

621
                        if (strverscmp_improved(u->version, cursor) != 0)
16,467✔
622
                                continue;
13,799✔
623

624
                        /* Merge in what we've learned and continue onto the next version */
625

626
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
2,668✔
627
                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
1,324✔
628
                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
1,308✔
629
                                assert(u->n_instances == c->n_transfers);
1,392✔
630

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

634
                                for (size_t j = 0; j < u->n_instances; j++) {
11,328✔
635
                                        if (!u->instances[j])
9,936✔
636
                                                u->instances[j] = cursor_instances[j];
5,792✔
637

638
                                        /* Make sure that the list is full if the update is AVAILABLE */
639
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
9,936✔
640
                                }
641
                        }
642

643
                        u->flags |= flags | extra_flags;
2,668✔
644

645
                        /* If this is the newest installed version, that is incomplete and just became marked
646
                         * as available, and if there is no other candidate available, we promote this to be
647
                         * the candidate. Ignore partial or pending status on the update set. */
648
                        if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
2,668✔
649
                            !c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
64✔
650
                                c->candidate = u;
64✔
651

652
                        skip = true;
653
                        newest_found = true;
654
                        break;
655
                }
656

657
                if (skip)
64✔
658
                        continue;
2,668✔
659

660
                /* Doesn't exist yet, let's add it */
661
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
4,693✔
662
                        return log_oom();
×
663

664
                us = new(UpdateSet, 1);
4,693✔
665
                if (!us)
4,693✔
666
                        return log_oom();
×
667

668
                *us = (UpdateSet) {
4,693✔
669
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
4,693✔
670
                        .version = TAKE_PTR(cursor),
4,693✔
671
                        .instances = TAKE_PTR(cursor_instances),
4,693✔
672
                        .n_instances = c->n_transfers,
4,693✔
673
                };
674

675
                c->update_sets[c->n_update_sets++] = us;
4,693✔
676

677
                newest_found = true;
4,693✔
678

679
                /* Remember which one is the newest installed */
680
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
4,693✔
681
                        c->newest_installed = us;
984✔
682

683
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
684
                 * It may be partial or pending. */
685
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
4,693✔
686
                        c->candidate = us;
321✔
687
        }
688

689
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
690
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
1,850✔
691
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
1,596✔
692
                c->candidate = NULL;
48✔
693

694
        /* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */
695
        if (c->newest_installed &&
1,850✔
696
            (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) &&
1,756✔
697
            !c->candidate)
196✔
698
                c->candidate = c->newest_installed;
196✔
699

700
        return 0;
701
}
702

703
static int context_discover_update_sets(Context *c) {
1,031✔
704
        int r;
1,031✔
705

706
        assert(c);
1,031✔
707

708
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
2,062✔
709

710
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
1,031✔
711
        if (r < 0)
1,031✔
712
                return r;
713

714
        if (!c->offline) {
1,031✔
715
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
1,638✔
716

717
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
819✔
718
                if (r < 0)
819✔
719
                        return r;
720
        }
721

722
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
1,031✔
723
        return 0;
1,031✔
724
}
725

726
static int context_show_table(Context *c) {
×
727
        _cleanup_(table_unrefp) Table *t = NULL;
×
728
        int r;
×
729

730
        assert(c);
×
731

732
        t = table_new("", "version", "installed", "available", "assessment");
×
733
        if (!t)
×
734
                return log_oom();
×
735

736
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
737
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
738
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
739

740
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
741
                UpdateSet *us = *update_set;
×
742
                const char *color;
×
743

744
                color = update_set_flags_to_color(us->flags);
×
745

746
                r = table_add_many(t,
×
747
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
748
                                   TABLE_SET_COLOR, color,
749
                                   TABLE_STRING,    us->version,
750
                                   TABLE_SET_COLOR, color,
751
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
752
                                   TABLE_SET_COLOR, color,
753
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
754
                                   TABLE_SET_COLOR, color,
755
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
756
                                   TABLE_SET_COLOR, color);
757
                if (r < 0)
×
758
                        return table_log_add_error(r);
×
759
        }
760

761
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
762
}
763

764
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
208✔
765
        assert(c);
208✔
766
        assert(version);
208✔
767

768
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
368✔
769
                if (streq((*update_set)->version, version))
368✔
770
                        return *update_set;
771

772
        return NULL;
773
}
774

775
static int context_show_version(Context *c, const char *version) {
208✔
776
        bool show_fs_columns = false, show_partition_columns = false,
208✔
777
                have_fs_attributes = false, have_partition_attributes = false,
208✔
778
                have_size = false, have_tries = false, have_no_auto = false,
208✔
779
                have_read_only = false, have_growfs = false, have_sha256 = false;
208✔
780
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
208✔
781
        _cleanup_(table_unrefp) Table *t = NULL;
208✔
782
        _cleanup_strv_free_ char **changelog_urls = NULL;
208✔
783
        UpdateSet *us;
208✔
784
        int r;
208✔
785

786
        assert(c);
208✔
787
        assert(version);
208✔
788

789
        us = context_update_set_by_version(c, version);
208✔
790
        if (!us)
208✔
791
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
792

793
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
208✔
794
                pager_open(arg_pager_flags);
112✔
795

796
        if (!sd_json_format_enabled(arg_json_format_flags))
208✔
797
                printf("%s%s%s Version: %s\n"
896✔
798
                       "    State: %s%s%s\n"
799
                       "Installed: %s%s%s%s\n"
800
                       "Available: %s%s\n"
801
                       "Protected: %s%s%s\n"
802
                       " Obsolete: %s%s%s\n\n",
803
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
112✔
804
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
112✔
805
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
128✔
806
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
208✔
807
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
208✔
808
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
112✔
809
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
112✔
810

811
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
208✔
812
        if (!t)
208✔
813
                return log_oom();
×
814

815
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
208✔
816
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
208✔
817
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
208✔
818
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
208✔
819
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
208✔
820
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
208✔
821
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
208✔
822

823
        /* Starting in v257, these fields would be automatically formatted with underscores. This would have
824
         * been a breaking change, so to avoid that let's hard-code their original names. */
825
        (void) table_set_json_field_name(t, 7, "tries-done");
208✔
826
        (void) table_set_json_field_name(t, 8, "tries-left");
208✔
827

828
        /* Determine if the target will make use of partition/fs attributes for any of the transfers */
829
        FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) {
1,696✔
830
                Transfer *tr = *transfer;
1,488✔
831

832
                if (tr->target.type == RESOURCE_PARTITION)
1,488✔
833
                        show_partition_columns = true;
416✔
834
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
1,488✔
835
                        show_fs_columns = true;
1,072✔
836

837
                STRV_FOREACH(changelog, tr->changelog) {
1,488✔
838
                        assert(*changelog);
×
839

840
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
841
                        if (!changelog_url)
×
842
                                return log_oom();
×
843

844
                        /* Avoid duplicates */
845
                        if (strv_contains(changelog_urls, changelog_url))
×
846
                                continue;
×
847

848
                        /* changelog_urls takes ownership of expanded changelog_url */
849
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
850
                        if (r < 0)
×
851
                                return log_oom();
×
852
                }
853
        }
854

855
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
1,696✔
856
                Instance *i = *inst;
1,488✔
857

858
                if (!i) {
1,488✔
859
                        assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING));
112✔
860
                        continue;
112✔
861
                }
862

863
                r = table_add_many(t,
1,376✔
864
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
865
                                   TABLE_PATH, i->path);
866
                if (r < 0)
1,376✔
867
                        return table_log_add_error(r);
×
868

869
                if (i->metadata.partition_uuid_set) {
1,376✔
870
                        have_partition_attributes = true;
288✔
871
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
288✔
872
                } else
873
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
874
                if (r < 0)
1,376✔
875
                        return table_log_add_error(r);
×
876

877
                if (i->metadata.partition_flags_set) {
1,376✔
878
                        have_partition_attributes = true;
288✔
879
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
288✔
880
                } else
881
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
882
                if (r < 0)
1,376✔
883
                        return table_log_add_error(r);
×
884

885
                if (i->metadata.mtime != USEC_INFINITY) {
1,376✔
886
                        have_fs_attributes = true;
1,056✔
887
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
1,056✔
888
                } else
889
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
320✔
890
                if (r < 0)
1,376✔
891
                        return table_log_add_error(r);
×
892

893
                if (i->metadata.mode != MODE_INVALID) {
1,376✔
894
                        have_fs_attributes = true;
1,056✔
895
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
1,056✔
896
                } else
897
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
320✔
898
                if (r < 0)
1,376✔
899
                        return table_log_add_error(r);
×
900

901
                if (i->metadata.size != UINT64_MAX) {
1,376✔
902
                        have_size = true;
×
903
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
904
                } else
905
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
906
                if (r < 0)
1,376✔
907
                        return table_log_add_error(r);
×
908

909
                if (i->metadata.tries_done != UINT64_MAX) {
1,376✔
910
                        have_tries = true;
128✔
911
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
128✔
912
                } else
913
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,248✔
914
                if (r < 0)
1,376✔
915
                        return table_log_add_error(r);
×
916

917
                if (i->metadata.tries_left != UINT64_MAX) {
1,376✔
918
                        have_tries = true;
128✔
919
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
128✔
920
                } else
921
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,248✔
922
                if (r < 0)
1,376✔
923
                        return table_log_add_error(r);
×
924

925
                if (i->metadata.no_auto >= 0) {
1,376✔
926
                        bool b;
×
927

928
                        have_no_auto = true;
×
929
                        b = i->metadata.no_auto;
×
930
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
931
                } else
932
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
933
                if (r < 0)
1,376✔
934
                        return table_log_add_error(r);
×
935
                if (i->metadata.read_only >= 0) {
1,376✔
936
                        bool b;
288✔
937

938
                        have_read_only = true;
288✔
939
                        b = i->metadata.read_only;
288✔
940
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
288✔
941
                } else
942
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
943
                if (r < 0)
1,376✔
944
                        return table_log_add_error(r);
×
945

946
                if (i->metadata.growfs >= 0) {
1,376✔
947
                        bool b;
×
948

949
                        have_growfs = true;
×
950
                        b = i->metadata.growfs;
×
951
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
952
                } else
953
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
954
                if (r < 0)
1,376✔
955
                        return table_log_add_error(r);
×
956

957
                if (i->metadata.sha256sum_set) {
1,376✔
958
                        _cleanup_free_ char *formatted = NULL;
32✔
959

960
                        have_sha256 = true;
32✔
961

962
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
32✔
963
                        if (!formatted)
32✔
964
                                return log_oom();
×
965

966
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
32✔
967
                } else
968
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,344✔
969
                if (r < 0)
1,376✔
970
                        return table_log_add_error(r);
×
971
        }
972

973
        /* Hide the fs/partition columns if we don't have any data to show there */
974
        if (!have_fs_attributes)
208✔
975
                show_fs_columns = false;
16✔
976
        if (!have_partition_attributes)
208✔
977
                show_partition_columns = false;
978

979
        if (!show_partition_columns)
144✔
980
                (void) table_hide_column_from_display(t, 2, 3);
64✔
981
        if (!show_fs_columns)
208✔
982
                (void) table_hide_column_from_display(t, 4, 5);
16✔
983
        if (!have_size)
208✔
984
                (void) table_hide_column_from_display(t, 6);
208✔
985
        if (!have_tries)
208✔
986
                (void) table_hide_column_from_display(t, 7, 8);
80✔
987
        if (!have_no_auto)
208✔
988
                (void) table_hide_column_from_display(t, 9);
208✔
989
        if (!have_read_only)
208✔
990
                (void) table_hide_column_from_display(t, 10);
64✔
991
        if (!have_growfs)
208✔
992
                (void) table_hide_column_from_display(t, 11);
208✔
993
        if (!have_sha256)
208✔
994
                (void) table_hide_column_from_display(t, 12);
192✔
995

996
        if (!sd_json_format_enabled(arg_json_format_flags)) {
208✔
997
                printf("%s%s%s Version: %s\n"
1,008✔
998
                       "    State: %s%s%s\n"
999
                       "Installed: %s%s%s%s%s%s%s\n"
1000
                       "Available: %s%s\n"
1001
                       "Protected: %s%s%s\n"
1002
                       " Obsolete: %s%s%s\n",
1003
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
112✔
1004
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
112✔
1005
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
128✔
1006
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
144✔
1007
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
208✔
1008
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
208✔
1009
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
112✔
1010
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
112✔
1011

1012
                STRV_FOREACH(url, changelog_urls) {
112✔
1013
                        _cleanup_free_ char *changelog_link = NULL;
×
1014
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
1015
                        if (r < 0)
×
1016
                                return log_oom();
×
1017
                        printf("ChangeLog: %s\n", changelog_link);
×
1018
                }
1019
                printf("\n");
112✔
1020

1021
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
112✔
1022
        } else {
1023
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
96✔
1024

1025
                r = table_to_json(t, &t_json);
96✔
1026
                if (r < 0)
96✔
1027
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
1028

1029
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
96✔
1030
                                          SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
1031
                                          SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
1032
                                          SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
1033
                                          SD_JSON_BUILD_PAIR_BOOLEAN("partial", FLAGS_SET(us->flags, UPDATE_PARTIAL)),
1034
                                          SD_JSON_BUILD_PAIR_BOOLEAN("pending", FLAGS_SET(us->flags, UPDATE_PENDING)),
1035
                                          SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
1036
                                          SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
1037
                                          SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
1038
                                          SD_JSON_BUILD_PAIR_STRV("changelogUrls", changelog_urls),
1039
                                          SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
1040
                if (r < 0)
96✔
1041
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1042

1043
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
96✔
1044
                if (r < 0)
96✔
1045
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1046

1047
                return 0;
1048
        }
1049
}
1050

1051
static int context_vacuum(
224✔
1052
                Context *c,
1053
                uint64_t space,
1054
                const char *extra_protected_version) {
1055

1056
        size_t disabled_count = 0;
224✔
1057
        int r, count = 0;
224✔
1058

1059
        assert(c);
224✔
1060

1061
        if (space == 0)
224✔
1062
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
64✔
1063
        else
1064
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
384✔
1065

1066
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
1,718✔
1067
                Transfer *t = *tr;
1,494✔
1068

1069
                /* Don't bother clearing out space if we're not going to be downloading anything */
1070
                if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
1,494✔
1071
                        continue;
208✔
1072

1073
                r = transfer_vacuum(t, space, extra_protected_version);
1,286✔
1074
                if (r < 0)
1,286✔
1075
                        return r;
1076

1077
                count = MAX(count, r);
1,286✔
1078
        }
1079

1080
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
416✔
1081
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
192✔
1082
                if (r < 0)
192✔
1083
                        return r;
1084
                if (r > 0)
192✔
1085
                        disabled_count++;
16✔
1086
        }
1087

1088
        if (!sd_json_format_enabled(arg_json_format_flags)) {
224✔
1089
                if (count > 0 && disabled_count > 0)
172✔
1090
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
1091
                else if (count > 0)
172✔
1092
                        log_info("Removed %i instances.", count);
76✔
1093
                else if (disabled_count > 0)
96✔
1094
                        log_info("Removed %zu disabled transfers.", disabled_count);
16✔
1095
                else
1096
                        log_info("Found nothing to remove.");
80✔
1097
        } else {
1098
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
52✔
1099

1100
                r = sd_json_buildo(&json,
52✔
1101
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
1102
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
1103
                if (r < 0)
52✔
1104
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1105

1106
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
52✔
1107
                if (r < 0)
52✔
1108
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1109
        }
1110

1111
        return 0;
1112
}
1113

1114
typedef enum ProcessImageFlags {
1115
        PROCESS_IMAGE_READ_ONLY = 1 << 0,
1116
} ProcessImageFlags;
1117

1118
static int context_process_image(
1,299✔
1119
                Context *c,
1120
                ProcessImageFlags flags) {
1121

1122
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1,299✔
1123
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1,299✔
1124
        int r;
1,299✔
1125

1126
        assert(c);
1,299✔
1127

1128
        if (c->root || !c->image) /* Idempotent or nothing to do */
1,299✔
1129
                return 0;
1130

1131
        assert(!c->mounted_dir);
×
1132
        assert(!c->loop_device);
×
1133

1134
        r = mount_image_privately_interactively(
×
1135
                        c->image,
1136
                        c->image_policy,
×
1137
                        (FLAGS_SET(flags, PROCESS_IMAGE_READ_ONLY) ? DISSECT_IMAGE_READ_ONLY : 0) |
×
1138
                        DISSECT_IMAGE_FSCK |
1139
                        DISSECT_IMAGE_MKDIR |
1140
                        DISSECT_IMAGE_GROWFS |
1141
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1142
                        DISSECT_IMAGE_USR_NO_ROOT |
1143
                        DISSECT_IMAGE_GENERIC_ROOT |
1144
                        DISSECT_IMAGE_REQUIRE_ROOT |
1145
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1146
                        &mounted_dir,
1147
                        /* ret_dir_fd= */ NULL,
1148
                        &loop_device);
1149
        if (r < 0)
×
1150
                return r;
1151

1152
        c->root = strdup(mounted_dir);
×
1153
        if (!c->root)
×
1154
                return log_oom();
×
1155

1156
        c->mounted_dir = TAKE_PTR(mounted_dir);
×
1157
        c->loop_device = TAKE_PTR(loop_device);
×
1158

1159
        return 0;
×
1160
}
1161

1162
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component);
1163

1164
static int context_load_offline(
1,299✔
1165
                Context *context,
1166
                ProcessImageFlags process_image_flags,
1167
                ReadDefinitionsFlags read_definitions_flags) {
1168
        int r;
1,299✔
1169

1170
        assert(context);
1,299✔
1171

1172
        /* Sets up a context object and initializes everything we can initialize offline, i.e. without
1173
         * checking on the update source (i.e. the Internet) what versions are available */
1174

1175
        r = context_process_image(context, process_image_flags);
1,299✔
1176
        if (r < 0)
1,299✔
1177
                return r;
1178

1179
        r = context_read_definitions(context, context->loop_device ? context->loop_device->node : NULL, read_definitions_flags);
1,299✔
1180
        if (r < 0)
1,299✔
1181
                return r;
1182

1183
        r = context_load_installed_instances(context);
1,299✔
1184
        if (r < 0)
1,299✔
1185
                return r;
×
1186

1187
        return 0;
1188
}
1189

1190
static int context_load_online(
1,051✔
1191
                Context *context,
1192
                ProcessImageFlags process_image_flags,
1193
                ReadDefinitionsFlags read_definitions_flags) {
1194

1195
        int r;
1,051✔
1196

1197
        assert(context);
1,051✔
1198

1199
        /* Like context_load_offline(), but also communicates with the update source looking for new
1200
         * versions (as long as --offline is not specified on the command line). */
1201

1202
        r = context_load_offline(
1,051✔
1203
                        context,
1204
                        process_image_flags,
1205
                        read_definitions_flags);
1206
        if (r < 0)
1,051✔
1207
                return r;
1208

1209
        if (!context->offline) {
1,051✔
1210
                r = context_load_available_instances(context);
839✔
1211
                if (r < 0)
839✔
1212
                        return r;
1213
        }
1214

1215
        r = context_discover_update_sets(context);
1,031✔
1216
        if (r < 0)
1,031✔
1217
                return r;
×
1218

1219
        return 0;
1220
}
1221

1222
static bool image_type_can_sysupdate(ImageType image_type) {
×
1223
        /* systemd-sysupdate doesn't support mstack images yet */
1224
        return IN_SET(image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME, IMAGE_RAW, IMAGE_BLOCK);
×
1225
}
1226

1227
static int context_load_paths_from_image(Context *context, Image *image) {
×
1228
        assert(context);
×
1229
        assert(image);
×
1230

1231
        assert(!context->root);
×
1232
        assert(!context->image);
×
1233

1234
        switch (image->type) {
×
1235
        case IMAGE_DIRECTORY:
×
1236
        case IMAGE_SUBVOLUME:
1237
                context->root = strdup(image->path);
×
1238
                if (!context->root)
×
1239
                        return log_oom();
×
1240
                return 0;
1241
        case IMAGE_RAW:
×
1242
        case IMAGE_BLOCK:
1243
                context->image = strdup(image->path);
×
1244
                if (!context->image)
×
1245
                        return log_oom();
×
1246
                return 0;
1247
        default:
×
1248
                assert_not_reached();
×
1249
        }
1250
}
1251

1252
/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been
1253
 * syntactically validated by dispatch_target_identifier(), but might still point to components which don’t
1254
 * exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier
1255
 * against an enumerated list of known targets, which are safe to update without additional permissions. */
1256
static int context_load_online_from_target(
192✔
1257
                Context *context,
1258
                ProcessImageFlags process_image_flags,
1259
                ReadDefinitionsFlags read_definitions_flags) {
1260
        int r;
192✔
1261

1262
        assert(context);
192✔
1263
        assert(context->target_identifier.class != _TARGET_CLASS_INVALID);
192✔
1264

1265
        /* These shouldn’t have been set up some other way first */
1266
        assert(!context->component);
192✔
1267
        assert(!context->root);
192✔
1268
        assert(!context->image);
192✔
1269

1270
        switch (context->target_identifier.class) {
192✔
1271
        case TARGET_MACHINE:
×
1272
        case TARGET_PORTABLE:
1273
        case TARGET_SYSEXT:
1274
        case TARGET_CONFEXT: {
1275
                _cleanup_hashmap_free_ Hashmap *images = NULL;
×
1276
                Image *image, *selected_image = NULL;
×
1277

1278
                /* These are all image-based target classes, so first find the corresponding image. */
1279
                r = image_discover(RUNTIME_SCOPE_SYSTEM, (ImageClass) context->target_identifier.class, NULL, &images);
×
1280
                if (r < 0)
×
1281
                        return r;
1282

1283
                HASHMAP_FOREACH(image, images) {
×
1284
                        bool have = false;
×
1285
                        _cleanup_(context_done) Context image_context = CONTEXT_NULL;
×
1286

1287
                        if (image_is_host(image))
×
1288
                                continue; /* We already enroll the host ourselves */
×
1289

1290
                        if (!image_type_can_sysupdate(image->type))
×
1291
                                continue;
×
1292

1293
                        if (!streq(image->name, context->target_identifier.name))
×
1294
                                continue;
×
1295

1296
                        r = context_load_paths_from_image(&image_context, image);
×
1297
                        if (r < 0)
×
1298
                                return r;
1299

1300
                        /* Load the components in a separate Context specific to the given Image before
1301
                         * committing to loading that state to the main Context. */
1302
                        r = context_load_offline(&image_context, 0, 0);
×
1303
                        if (r < 0)
×
1304
                                return r;
1305

1306
                        r = context_list_components(&image_context, /* ret_component_names= */ NULL, &have);
×
1307
                        if (r < 0)
×
1308
                                return r;
1309
                        if (!have) {
×
1310
                                log_debug("Skipping %s because it has no default component", image->path);
×
1311
                                continue;
×
1312
                        }
1313

1314
                        /* This is the match we were looking for */
1315
                        selected_image = image;
×
1316
                        break;
×
1317
                }
1318

1319
                if (!selected_image)
×
1320
                        return -ENOENT;
1321

1322
                r = context_load_paths_from_image(context, selected_image);
×
1323
                if (r < 0)
×
1324
                        return r;
1325

1326
                break;
×
1327
        }
1328
        case TARGET_HOST:
1329
                /* No additional setup needed */
1330
                break;
1331
        case TARGET_COMPONENT: {
8✔
1332
                _cleanup_strv_free_ char **component_names = NULL;
8✔
1333

1334
                r = context_list_components(context, &component_names, /* ret_has_default_component= */ NULL);
8✔
1335
                if (r < 0)
8✔
1336
                        return r;
1337

1338
                if (!strv_contains(component_names, context->target_identifier.name))
8✔
1339
                        return -ENOENT;
1340

1341
                context->component = strdup(context->target_identifier.name);
×
1342
                if (!context->component)
×
1343
                        return log_oom();
×
1344
                break;
×
1345
        }
1346
        default:
×
1347
                assert_not_reached();
×
1348
        }
1349

1350
        return context_load_online(context, process_image_flags, read_definitions_flags);
184✔
1351
}
1352

1353
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
830✔
1354
        const Context *c = ASSERT_PTR(t->context);
830✔
1355
        size_t i, n = c->n_transfers;
830✔
1356
        uint64_t base, scaled;
830✔
1357
        unsigned overall;
830✔
1358

1359
        for (i = 0; i < n; i++)
2,879✔
1360
                if (c->transfers[i] == t)
2,879✔
1361
                        break;
1362
        assert(i < n); /* We should have found the index */
830✔
1363

1364
        base = (100 * 100 * i) / n;
830✔
1365
        scaled = (100 * percentage) / n;
830✔
1366
        overall = (unsigned) ((base + scaled) / 100);
830✔
1367
        assert(overall <= 100);
830✔
1368

1369
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
830✔
1370
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
1,660✔
1371
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
1372
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
1373
                                              "STATUS=Updating to '%s' (%u%% complete).",
1374
                                              overall, n - i, i, inst->metadata.version, overall);
830✔
1375
}
1376

1377
static int context_process_partial_and_pending(Context *c, const char *version);
1378

1379
static int context_acquire(
290✔
1380
                Context *c,
1381
                const char *version) {
1382

1383
        UpdateSet *us = NULL;
290✔
1384
        int r;
290✔
1385

1386
        assert(c);
290✔
1387

1388
        if (version) {
290✔
1389
                us = context_update_set_by_version(c, version);
×
1390
                if (!us)
×
1391
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
290✔
1392
        } else {
1393
                if (!c->candidate) {
290✔
1394
                        log_info("No update needed.");
50✔
1395

1396
                        return 0;
1397
                }
1398

1399
                us = c->candidate;
1400
        }
1401

1402
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
240✔
1403
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
32✔
1404
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
208✔
1405
                return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
16✔
1406
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
192✔
1407
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
32✔
1408

1409
                return context_process_partial_and_pending(c, version);
32✔
1410
        } else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
160✔
1411
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1412

1413
                return 0;
1414
        }
1415

1416
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
192✔
1417
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1418
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
192✔
1419
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1420

1421
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
192✔
1422
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1423
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
192✔
1424
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1425

1426
        log_info("Selected update '%s' for install.", us->version);
192✔
1427

1428
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
384✔
1429
        if (!metadata)
192✔
1430
                return log_oom();
×
1431

1432
        /* Compute up the temporary paths before vacuuming so we don't vacuum anything if we fail to compute
1433
         * any paths because of failed validations (e.g. exceeding the gpt partition label size). */
1434
        for (size_t i = 0; i < c->n_transfers; i++) {
1,462✔
1435
                Instance *inst = us->instances[i];
1,270✔
1436
                Transfer *t = c->transfers[i];
1,270✔
1437

1438
                assert(inst);
1,270✔
1439

1440
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
1,270✔
1441
                if (r < 0)
1,270✔
1442
                        return r;
1443
        }
1444

1445
        (void) sd_notifyf(/* unset_environment= */ false,
192✔
1446
                          "READY=1\n"
1447
                          "X_SYSUPDATE_VERSION=%s\n"
1448
                          "STATUS=Making room for '%s'.", us->version, us->version);
1449

1450
        /* Let's make some room. We make sure for each transfer we have one free space to fill. While
1451
         * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
1452
         * download succeeded already, in which case we shouldn't remove it just to acquire it again */
1453
        r = context_vacuum(
384✔
1454
                        c,
1455
                        /* space= */ 1,
1456
                        /* extra_protected_version= */ us->version);
192✔
1457
        if (r < 0)
192✔
1458
                return r;
1459

1460
        if (c->sync)
192✔
1461
                sync();
192✔
1462

1463
        (void) sd_notifyf(/* unset_environment= */ false,
192✔
1464
                          "STATUS=Updating to '%s'.", us->version);
1465

1466
        /* There should now be one instance picked for each transfer, and the order is the same */
1467
        assert(us->n_instances == c->n_transfers);
192✔
1468

1469
        for (size_t i = 0; i < c->n_transfers; i++) {
1,366✔
1470
                Instance *inst = us->instances[i];
1,190✔
1471
                Transfer *t = c->transfers[i];
1,190✔
1472

1473
                assert(inst); /* ditto */
1,190✔
1474

1475
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
1,190✔
1476
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
208✔
1477
                        continue;
208✔
1478
                }
1479

1480
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
982✔
1481
                if (r < 0)
982✔
1482
                        return r;
1483
        }
1484

1485
        if (c->sync)
176✔
1486
                sync();
176✔
1487

1488
        return 1;
1489
}
1490

1491
/* Check to see if we have an update set acquired and pending installation. */
1492
static int context_process_partial_and_pending(
116✔
1493
                Context *c,
1494
                const char *version) {
1495

1496
        UpdateSet *us = NULL;
116✔
1497
        int r;
116✔
1498

1499
        assert(c);
116✔
1500

1501
        if (version) {
116✔
1502
                us = context_update_set_by_version(c, version);
×
1503
                if (!us)
×
1504
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1505
        } else {
1506
                if (!c->candidate) {
116✔
1507
                        log_info("No update needed.");
×
1508

1509
                        return 0;
1510
                }
1511

1512
                us = c->candidate;
1513
        }
1514

1515
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
116✔
1516
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
×
1517
        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
116✔
1518
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1519

1520
                return 0;
1521
        }
1522

1523
        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
116✔
1524
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
4✔
1525
        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
112✔
1526
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
×
1527

1528
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
112✔
1529
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1530

1531
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
112✔
1532
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1533
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
112✔
1534
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1535

1536
        log_info("Selected update '%s' for install.", us->version);
112✔
1537

1538
        /* There should now be one instance picked for each transfer, and the order is the same */
1539
        assert(us->n_instances == c->n_transfers);
112✔
1540

1541
        for (size_t i = 0; i < c->n_transfers; i++) {
908✔
1542
                Instance *inst = us->instances[i];
796✔
1543
                Transfer *t = c->transfers[i];
796✔
1544

1545
                assert(inst);
796✔
1546

1547
                r = transfer_process_partial_and_pending_instance(t, inst);
796✔
1548
                if (r < 0)
796✔
1549
                        return r;
1550
        }
1551

1552
        return 1;
1553
}
1554

1555
static int notify_subscribers_reply(
177✔
1556
                sd_varlink *link,
1557
                sd_json_variant *reply,
1558
                const char *error_id,
1559
                sd_varlink_reply_flags_t flags,
1560
                void *userdata) {
1561

1562
        assert(link);
177✔
1563

1564
        if (error_id)
177✔
1565
                log_warning("Notification subscriber '%s' returned error, ignoring: %s",
×
1566
                            strna(sd_varlink_get_description(link)), error_id);
1567

1568
        return 0;
177✔
1569
}
1570

1571
static int context_notify_subscribers(Context *c, UpdateSet *us) {
176✔
1572
        int r;
176✔
1573

1574
        assert(c);
176✔
1575

1576
        /* 'us' is NULL when we are forced to notify even though no update was applied (via
1577
         * SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1). In that case we send neither a version nor a resource list. */
1578

1579
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *resources = NULL;
176✔
1580
        if (us)
176✔
1581
                for (size_t i = 0; i < c->n_transfers; i++) {
1,334✔
1582
                        Instance *inst = us->instances[i];
1,158✔
1583
                        Transfer *t = c->transfers[i];
1,158✔
1584

1585
                        if (inst->resource == &t->target &&
1,158✔
1586
                            !inst->is_pending)
920✔
1587
                                continue;
208✔
1588

1589
                        /* Report where the resource was installed *to* (not the source it came from): the
1590
                         * final on-disk path for filesystem targets, the partition device node for partition
1591
                         * targets. */
1592
                        const char *target_path =
256✔
1593
                                RESOURCE_IS_FILESYSTEM(t->target.type) ? t->final_path :
950✔
1594
                                t->target.type == RESOURCE_PARTITION ? t->partition_info.device :
1595
                                NULL;
1596

1597
                        r = sd_json_variant_append_arraybo(
950✔
1598
                                        &resources,
1599
                                        SD_JSON_BUILD_PAIR_STRING("transfer", t->id),
1600
                                        SD_JSON_BUILD_PAIR_CONDITION(!!target_path, "path", SD_JSON_BUILD_STRING(target_path)));
1601
                        if (r < 0)
950✔
1602
                                return log_warning_errno(r, "Failed to build sysupdate notify resources list, skipping notification: %m");
×
1603
                }
1604

1605
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL;
176✔
1606
        r = sd_json_buildo(
176✔
1607
                        &params,
1608
                        SD_JSON_BUILD_PAIR_CONDITION(!!c->component, "component", SD_JSON_BUILD_STRING(c->component)),
1609
                        SD_JSON_BUILD_PAIR_CONDITION(!!us, "version", SD_JSON_BUILD_STRING(us ? us->version : NULL)),
1610
                        SD_JSON_BUILD_PAIR_CONDITION(!!resources, "resources", SD_JSON_BUILD_VARIANT(resources)));
1611
        if (r < 0)
176✔
1612
                return log_warning_errno(r, "Failed to build sysupdate notify parameters, skipping notification: %m");
×
1613

1614
        ssize_t n = varlink_execute_directory(
176✔
1615
                        VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK,
1616
                        "io.systemd.SysUpdate.Notify.OnCompletedUpdate",
1617
                        params,
1618
                        /* more= */ false,
1619
                        /* timeout_usec= */ 5 * USEC_PER_MINUTE,
1620
                        notify_subscribers_reply,
1621
                        /* userdata= */ NULL);
1622
        if (n < 0)
176✔
1623
                log_debug_errno(n, "Failed to dispatch sysupdate notification to %s, ignoring: %m",
176✔
1624
                                VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
1625
        else if (n > 0)
176✔
1626
                log_debug("Dispatched sysupdate notification to %zi subscribers in %s.", n, VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
176✔
1627

1628
        return 0;
1629
}
1630

1631
static int context_install(
176✔
1632
                Context *c,
1633
                const char *version,
1634
                UpdateSet **ret_applied) {
1635

1636
        UpdateSet *us = NULL;
176✔
1637
        int r;
176✔
1638

1639
        assert(c);
176✔
1640

1641
        if (version) {
176✔
1642
                us = context_update_set_by_version(c, version);
×
1643
                if (!us)
×
1644
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1645
        } else {
1646
                if (!c->candidate) {
176✔
1647
                        log_info("No update needed.");
×
1648

1649
                        return 0;
1650
                }
1651

1652
                us = c->candidate;
1653
        }
1654

1655
        (void) sd_notifyf(/* unset_environment=*/ false,
176✔
1656
                          "READY=1\n"
1657
                          "X_SYSUPDATE_VERSION=%s\n"
1658
                          "STATUS=Installing '%s'.", us->version, us->version);
1659

1660
        for (size_t i = 0; i < c->n_transfers; i++) {
1,510✔
1661
                Instance *inst = us->instances[i];
1,158✔
1662
                Transfer *t = c->transfers[i];
1,158✔
1663

1664
                if (inst->resource == &t->target &&
1,158✔
1665
                    !inst->is_pending)
920✔
1666
                        continue;
208✔
1667

1668
                r = transfer_install_instance(t, inst, c->root);
950✔
1669
                if (r < 0)
950✔
1670
                        return r;
1671
        }
1672

1673
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
176✔
1674

1675
        if (!c->root)
176✔
1676
                (void) context_notify_subscribers(c, us);
176✔
1677

1678
        (void) sd_notifyf(/* unset_environment= */ false,
176✔
1679
                          "STATUS=Installed '%s'.", us->version);
1680

1681
        if (ret_applied)
176✔
1682
                *ret_applied = us;
176✔
1683

1684
        return 1;
1685
}
1686

1687
static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string);
200✔
1688

1689
static int dispatch_target_identifier(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
200✔
1690
        TargetIdentifier *t = ASSERT_PTR(userdata);
200✔
1691
        static const sd_json_dispatch_field dispatch[] = {
200✔
1692
                { "class", SD_JSON_VARIANT_STRING, dispatch_target_class,   voffsetof(*t, class), SD_JSON_MANDATORY },
1693
                { "name",  SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(*t, name),  SD_JSON_NULLABLE  },
1694
                {}
1695
        };
1696
        int r;
200✔
1697

1698
        r = sd_json_dispatch(variant, dispatch, flags, t);
200✔
1699
        if (r < 0)
200✔
1700
                return r;
1701

1702
        /* Name is mandatory unless class is `host` */
1703
        if ((t->class == TARGET_HOST) != (!t->name))
200✔
1704
                return json_log(variant, flags, SYNTHETIC_ERRNO(ENXIO), "Target name does not match class.");
×
1705

1706
        if (t->class == TARGET_COMPONENT && !component_name_valid(t->name))
200✔
1707
                        return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", t->name);
8✔
1708

1709
        return 0;
1710
}
1711

1712
static int verify_polkit(Context *context, sd_varlink *link, const char *action, const char **details) {
192✔
1713
        int r;
192✔
1714
        Server *s = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
192✔
1715

1716
        assert(context);
192✔
1717

1718
        if (!s->system_bus) {
192✔
1719
                r = sd_bus_open_system_with_description(&s->system_bus, "sysupdate-system");
192✔
1720
                if (r < 0)
192✔
1721
                        return log_error_errno(r, "Failed to get system bus connection: %m");
×
1722

1723
                r = sd_bus_attach_event(s->system_bus, sd_varlink_get_event(link), SD_EVENT_PRIORITY_NORMAL);
192✔
1724
                if (r < 0)
192✔
1725
                        return log_error_errno(r, "Failed to attach system bus to event loop: %m");
×
1726
        }
1727

1728
        return varlink_verify_polkit_async(link,
192✔
1729
                        s->system_bus,
1730
                        action,
1731
                        details,
1732
                        &s->polkit_registry);
1733
}
1734

1735
VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT,
1736
     "Show installed and available versions");
1737
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
256✔
1738
        _cleanup_(context_done) Context context = CONTEXT_NULL;
256✔
1739
        _cleanup_strv_free_ char **appstream_urls = NULL;
256✔
1740
        const char *version;
256✔
1741
        int r;
256✔
1742

1743
        assert(argc <= 2);
256✔
1744
        version = argc >= 2 ? argv[1] : NULL;
256✔
1745

1746
        r = context_from_cmdline(&context);
256✔
1747
        if (r < 0)
256✔
1748
                return r;
1749

1750
        if (context.component_all)
256✔
1751
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
1752

1753
        r = context_load_online(
256✔
1754
                        &context,
1755
                        PROCESS_IMAGE_READ_ONLY,
1756
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
1757
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1758
        if (r < 0)
256✔
1759
                return r;
1760

1761
        if (version)
256✔
1762
                return context_show_version(&context, version);
208✔
1763
        else if (!sd_json_format_enabled(arg_json_format_flags))
48✔
1764
                return context_show_table(&context);
×
1765
        else {
1766
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
48✔
1767
                _cleanup_strv_free_ char **versions = NULL;
48✔
1768
                const char *current = NULL;
48✔
1769
                bool current_is_pending = false;
48✔
1770

1771
                FOREACH_ARRAY(update_set, context.update_sets, context.n_update_sets) {
224✔
1772
                        UpdateSet *us = *update_set;
176✔
1773

1774
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
176✔
1775
                            FLAGS_SET(us->flags, UPDATE_NEWEST)) {
160✔
1776
                                current = us->version;
48✔
1777
                                current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
48✔
1778
                        }
1779

1780
                        r = strv_extend(&versions, us->version);
176✔
1781
                        if (r < 0)
176✔
1782
                                return log_oom();
×
1783
                }
1784

1785
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers)
288✔
1786
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
240✔
1787
                                /* Avoid duplicates */
1788
                                if (strv_contains(appstream_urls, *appstream_url))
×
1789
                                        continue;
×
1790

1791
                                r = strv_extend(&appstream_urls, *appstream_url);
×
1792
                                if (r < 0)
×
1793
                                        return log_oom();
×
1794
                        }
1795

1796
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current),
96✔
1797
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1798
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1799
                if (r < 0)
48✔
1800
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1801

1802
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
48✔
1803
                if (r < 0)
48✔
1804
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1805

1806
                return 0;
1807
        }
1808
}
1809

1810
VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0,
1811
     "Show optional features");
1812
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
1813
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
1814
        _cleanup_(table_unrefp) Table *table = NULL;
32✔
1815
        const char *feature_id;
32✔
1816
        Feature *f;
32✔
1817
        int r;
32✔
1818

1819
        assert(argc <= 2);
32✔
1820
        feature_id = argc >= 2 ? argv[1] : NULL;
32✔
1821

1822
        r = context_from_cmdline(&context);
32✔
1823
        if (r < 0)
32✔
1824
                return r;
1825

1826
        if (context.component_all)
32✔
1827
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
1828

1829
        r = context_load_offline(
32✔
1830
                        &context,
1831
                        PROCESS_IMAGE_READ_ONLY,
1832
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1833
        if (r < 0)
32✔
1834
                return r;
1835

1836
        if (feature_id) {
32✔
1837
                _cleanup_strv_free_ char **transfers = NULL;
16✔
1838

1839
                f = hashmap_get(context.features, feature_id);
16✔
1840
                if (!f)
16✔
1841
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1842
                                               "Optional feature not found: %s",
1843
                                               feature_id);
1844

1845
                table = table_new_vertical();
16✔
1846
                if (!table)
16✔
1847
                        return log_oom();
×
1848

1849
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers) {
128✔
1850
                        Transfer *t = *tr;
112✔
1851

1852
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
112✔
1853
                                continue;
112✔
1854

1855
                        r = strv_extend(&transfers, t->id);
×
1856
                        if (r < 0)
×
1857
                                return log_oom();
×
1858
                }
1859

1860
                FOREACH_ARRAY(tr, context.disabled_transfers, context.n_disabled_transfers) {
32✔
1861
                        Transfer *t = *tr;
16✔
1862

1863
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
16✔
1864
                                continue;
×
1865

1866
                        r = strv_extend(&transfers, t->id);
16✔
1867
                        if (r < 0)
16✔
1868
                                return log_oom();
×
1869
                }
1870

1871
                r = table_add_many(table,
16✔
1872
                                   TABLE_FIELD, "Name",
1873
                                   TABLE_STRING, f->id,
1874
                                   TABLE_FIELD, "Enabled",
1875
                                   TABLE_BOOLEAN, f->enabled);
1876
                if (r < 0)
16✔
1877
                        return table_log_add_error(r);
×
1878

1879
                if (f->description) {
16✔
1880
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
16✔
1881
                        if (r < 0)
16✔
1882
                                return table_log_add_error(r);
×
1883
                }
1884

1885
                if (f->documentation) {
16✔
1886
                        r = table_add_many(table,
×
1887
                                           TABLE_FIELD, "Documentation",
1888
                                           TABLE_STRING, f->documentation,
1889
                                           TABLE_SET_URL, f->documentation);
1890
                        if (r < 0)
×
1891
                                return table_log_add_error(r);
×
1892
                }
1893

1894
                if (f->appstream) {
16✔
1895
                        r = table_add_many(table,
×
1896
                                           TABLE_FIELD, "AppStream",
1897
                                           TABLE_STRING, f->appstream,
1898
                                           TABLE_SET_URL, f->appstream);
1899
                        if (r < 0)
×
1900
                                return table_log_add_error(r);
×
1901
                }
1902

1903
                if (!strv_isempty(transfers)) {
16✔
1904
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
16✔
1905
                        if (r < 0)
16✔
1906
                                return table_log_add_error(r);
×
1907
                }
1908

1909
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
16✔
1910
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
16✔
1911
                table = table_new("", "feature", "description", "documentation");
16✔
1912
                if (!table)
16✔
1913
                        return log_oom();
×
1914

1915
                HASHMAP_FOREACH(f, context.features) {
32✔
1916
                        r = table_add_many(table,
16✔
1917
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1918
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
1919
                                           TABLE_STRING, f->id,
1920
                                           TABLE_STRING, f->description,
1921
                                           TABLE_STRING, f->documentation,
1922
                                           TABLE_SET_URL, f->documentation);
1923
                        if (r < 0)
16✔
1924
                                return table_log_add_error(r);
×
1925
                }
1926

1927
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
16✔
1928
        } else {
1929
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1930
                _cleanup_strv_free_ char **features = NULL;
×
1931

1932
                HASHMAP_FOREACH(f, context.features) {
×
1933
                        r = strv_extend(&features, f->id);
×
1934
                        if (r < 0)
×
1935
                                return log_oom();
×
1936
                }
1937

1938
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
1939
                if (r < 0)
×
1940
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1941

1942
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
×
1943
                if (r < 0)
×
1944
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1945
        }
1946

1947
        return 0;
×
1948
}
1949

1950
static int make_dropin_dir(Context *c, char **ret) {
×
1951
        _cleanup_free_ char *dir = NULL;
×
1952

1953
        assert(c);
×
1954
        assert(ret);
×
1955

1956
        /* Returns the (writable) directory where feature definitions live, so that we can drop our
1957
         * 'Enabled=' override right next to them. This mirrors the directory logic in
1958
         * context_read_definitions(), but settles on a single writable location below /etc. */
1959

1960
        if (c->definitions)
×
1961
                dir = strdup(c->definitions); /* --root= is not supported for this for now */
×
1962
        else if (c->component) {
×
1963
                _cleanup_free_ char *n = strjoin("sysupdate.", c->component, ".d");
×
1964
                if (!n)
×
1965
                        return log_oom();
×
1966

1967
                dir = path_join(c->root, SYSCONF_DIR, n);
×
1968
        } else
1969
                dir = path_join(c->root, SYSCONF_DIR "/sysupdate.d");
×
1970
        if (!dir)
×
1971
                return log_oom();
×
1972

1973
        *ret = TAKE_PTR(dir);
×
1974
        return 0;
×
1975
}
1976

1977
VERB(verb_enable_feature, "enable-feature", "FEATURE…", 2, VERB_ANY, 0,
1978
     "Enable optional feature");
1979
VERB(verb_enable_feature, "disable-feature", "FEATURE…", 2, VERB_ANY, 0,
1980
     "Disable optional feature");
1981
static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
1982
        bool enable = streq(argv[0], "enable-feature");
×
1983
        int r;
×
1984

1985
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
1986
        r = context_from_cmdline(&context);
×
1987
        if (r < 0)
×
1988
                return r;
1989

1990
        if (context.component_all)
×
1991
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
1992

1993
        r = context_load_offline(
×
1994
                        &context,
1995
                        /* process_image_flags= */ 0,
1996
                        /* read_definitions_flags= */ 0);
1997
        if (r < 0)
×
1998
                return r;
1999

2000
        _cleanup_free_ char *dropin_dir = NULL;
×
2001
        r = make_dropin_dir(&context, &dropin_dir);
×
2002
        if (r < 0)
×
2003
                return r;
2004

2005
        STRV_FOREACH(name, strv_skip(argv, 1)) {
×
2006

2007
                if (!feature_name_valid(*name))
×
2008
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Feature name invalid: %s", *name);
×
2009

2010
                if (!hashmap_contains(context.features, *name))
×
2011
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
2012
                                               "Optional feature not found: %s", *name);
2013

2014
                _cleanup_free_ char *fname = strjoin(*name, ".feature");
×
2015
                if (!fname)
×
2016
                        return log_oom();
×
2017

2018
                /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
2019
                r = write_drop_in_format(
×
2020
                                dropin_dir,
2021
                                fname,
2022
                                50, "systemd-sysupdate-enabled",
2023
                                "# Generated via 'systemd-sysupdate %s'\n\n"
2024
                                "[Feature]\n"
2025
                                "Enabled=%s\n",
2026
                                argv[0],
2027
                                yes_no(enable));
2028
                if (r < 0)
×
2029
                        return log_error_errno(r, "Failed to write drop-in for feature '%s': %m", *name);
×
2030

2031
                log_info("Feature '%s' %s.", *name, enable ? "enabled" : "disabled");
×
2032
        }
2033

2034
        return 0;
2035
}
2036

2037
VERB_NOARG(verb_check_new, "check-new",
2038
           "Check if there's a new version available");
2039
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
218✔
2040
        _cleanup_(context_done) Context context = CONTEXT_NULL;
218✔
2041
        int r;
218✔
2042

2043
        assert(argc <= 1);
218✔
2044

2045
        r = context_from_cmdline(&context);
218✔
2046
        if (r < 0)
218✔
2047
                return r;
2048

2049
        if (context.component_all)
218✔
2050
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
2051

2052
        r = context_load_online(
218✔
2053
                        &context,
2054
                        PROCESS_IMAGE_READ_ONLY,
2055
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2056
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2057
        if (r < 0)
218✔
2058
                return r;
2059

2060
        if (!sd_json_format_enabled(arg_json_format_flags)) {
217✔
2061
                if (!context.candidate) {
185✔
2062
                        log_debug("No candidate found.");
96✔
2063
                        return EXIT_FAILURE;
2064
                }
2065

2066
                puts(context.candidate->version);
89✔
2067
        } else {
2068
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
32✔
2069

2070
                if (context.candidate)
32✔
2071
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
×
2072
                else
2073
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
32✔
2074
                if (r < 0)
32✔
2075
                        return log_error_errno(r, "Failed to create JSON: %m");
×
2076

2077
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
32✔
2078
                if (r < 0)
32✔
2079
                        return log_error_errno(r, "Failed to print JSON: %m");
×
2080
        }
2081

2082
        return EXIT_SUCCESS;
2083
}
2084

2085
static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
200✔
2086
        _cleanup_(context_done) Context context = CONTEXT_NULL;
200✔
2087
        int r;
200✔
2088

2089
        assert(link);
200✔
2090

2091
        static const sd_json_dispatch_field dispatch_table[] = {
200✔
2092
                { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY },
2093
                VARLINK_DISPATCH_POLKIT_FIELD,
2094
                {},
2095
        };
2096

2097
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &context);
200✔
2098
        if (r != 0)
200✔
2099
                return r;
2100

2101
        r = verify_polkit(&context, link, "org.freedesktop.sysupdate1.check",
576✔
2102
                        (const char**) STRV_MAKE(
376✔
2103
                                        "class", target_class_to_string(context.target_identifier.class),
2104
                                        "offline", "0",
2105
                                        context.target_identifier.name ? "name" : NULL, context.target_identifier.name));
2106
        if (r <= 0)
192✔
2107
                return r;
2108

2109
        if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
192✔
2110
                context.verify = 0;
192✔
2111

2112
        /* CheckNew is always online */
2113
        context.offline = false;
192✔
2114

2115
        r = context_load_online_from_target(
192✔
2116
                        &context,
2117
                        PROCESS_IMAGE_READ_ONLY,
2118
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2119
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2120
        if (r == -ENOENT)
192✔
2121
                return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
8✔
2122
        if (r < 0)
184✔
2123
                return r;
2124

2125
        if (context.candidate)
184✔
2126
                r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
88✔
2127
        else
2128
                r = sd_varlink_error(link, "io.systemd.SysUpdate.NoUpdateNeeded", NULL);
96✔
2129
        if (r < 0)
184✔
2130
                return r;
96✔
2131

2132
        return 0;
2133
}
2134

2135
typedef enum {
2136
        UPDATE_ACTION_ACQUIRE = 1 << 0,
2137
        UPDATE_ACTION_INSTALL = 1 << 1,
2138
} UpdateActionFlags;
2139

2140
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
394✔
2141
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2142
        _cleanup_free_ char *booted_version = NULL;
394✔
2143
        UpdateSet *applied = NULL;
394✔
2144
        const char *version;
394✔
2145
        int r;
394✔
2146

2147
        assert(argc <= 2);
394✔
2148
        version = argc >= 2 ? argv[1] : NULL;
394✔
2149

2150
        r = context_from_cmdline(&context);
394✔
2151
        if (r < 0)
394✔
2152
                return r;
2153

2154
        if (context.component_all)
394✔
2155
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
1✔
2156

2157
        if (context.instances_max < 2)
393✔
2158
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2159
                                      "The --instances-max argument must be >= 2 while updating");
2160

2161
        if (context.reboot) {
393✔
2162
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
2163

2164
                r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
×
2165
                if (r < 0)
×
2166
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
2167
                if (!booted_version)
×
2168
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
2169
        }
2170

2171
        bool installed = false;
393✔
2172
        int ret = 0;
393✔
2173

2174
        r = context_load_online(
393✔
2175
                        &context,
2176
                        /* process_image_flags= */ 0,
2177
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2178
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
2179
                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
2180
        if (r < 0) {
393✔
2181
                if (r != -ENOENT)
19✔
2182
                        return r;
2183

2184
                /* No transfer files found. In that case, still do the installdb cleanup below */
2185
                RET_GATHER(ret, r);
2186
        } else {
2187
                if (action_flags & UPDATE_ACTION_ACQUIRE)
374✔
2188
                        r = context_acquire(&context, version);
290✔
2189
                else
2190
                        r = context_process_partial_and_pending(&context, version);
84✔
2191
                if (r < 0)
374✔
2192
                        return r;
2193

2194
                if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */
338✔
2195
                        r = context_install(&context, version, &applied);
176✔
2196
                        if (r < 0)
176✔
2197
                                return r;
2198

2199
                        installed = r > 0;
176✔
2200
                }
2201

2202
                /* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If
2203
                 * nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a
2204
                 * resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */
2205
                if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) {
226✔
2206
                        int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY");
50✔
2207
                        if (f < 0 && f != -ENXIO)
50✔
2208
                                log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m");
×
2209
                        if (f > 0)
50✔
2210
                                (void) context_notify_subscribers(&context, /* us= */ NULL);
×
2211
                }
2212
        }
2213

2214
        if (context.cleanup > 0)
338✔
2215
                RET_GATHER(ret, installdb_cleanup_component(&context));
2✔
2216

2217
        if (installed) {
338✔
2218
                /* We installed something, yay */
2219

2220
                if (context.reboot) {
176✔
2221
                        assert(applied);
×
2222
                        assert(booted_version);
×
2223

2224
                        if (strverscmp_improved(applied->version, booted_version) > 0) {
×
2225
                                log_notice("Newly installed version is newer than booted version, rebooting.");
×
2226
                                RET_GATHER(ret, reboot_now());
×
2227
                        } else if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
2228
                                   FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
2229
                                log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
2230
                                RET_GATHER(ret, reboot_now());
×
2231
                        } else
2232
                                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
2233
                }
2234
        }
2235

2236
        return ret;
2237
}
2238

2239
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
2240
     "Install new version now");
2241
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
258✔
2242
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
258✔
2243

2244
        if (!arg_offline)
258✔
2245
                flags |= UPDATE_ACTION_ACQUIRE;
174✔
2246

2247
        return verb_update_impl(argc, argv, flags);
258✔
2248
}
2249

2250
VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
2251
     "Acquire (download) new version now");
2252
static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
136✔
2253
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
136✔
2254
}
2255

2256
VERB_NOARG(verb_vacuum, "vacuum",
2257
           "Make room, by deleting old versions");
2258
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
2259
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
2260
        int r;
32✔
2261

2262
        assert(argc <= 1);
32✔
2263

2264
        r = context_from_cmdline(&context);
32✔
2265
        if (r < 0)
32✔
2266
                return r;
2267

2268
        if (context.component_all)
32✔
2269
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
2270

2271
        if (context.instances_max < 1)
32✔
2272
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2273
                                      "The --instances-max argument must be >= 1 while vacuuming");
2274

2275
        r = context_load_offline(
32✔
2276
                        &context,
2277
                        /* process_image_flags= */ 0,
2278
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2279
        if (r < 0)
32✔
2280
                return r;
2281

2282
        return context_vacuum(&context, 0, NULL);
32✔
2283
}
2284

2285
VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files");
2286
static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) {
7✔
2287
        _cleanup_(context_done) Context context = CONTEXT_NULL;
7✔
2288
        int r;
7✔
2289

2290
        assert(argc <= 1);
7✔
2291

2292
        r = context_from_cmdline(&context);
7✔
2293
        if (r < 0)
7✔
2294
                return r;
2295

2296
        if (context.cleanup == 0)
7✔
2297
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invocation of 'cleanup' with --cleanup=no is contradictory, refusing.");
1✔
2298

2299
        r = context_load_offline(
6✔
2300
                        &context,
2301
                        /* process_image_flags= */ 0,
2302
                        /* read_definitions_flags= */ 0);
2303
        if (r < 0)
6✔
2304
                return r;
2305

2306
        int ret = 0;
6✔
2307
        RET_GATHER(ret, installdb_cleanup_component(&context));
6✔
2308

2309
        if (context.component_all) {
6✔
2310
                _cleanup_strv_free_ char **z = NULL;
2✔
2311
                r = installdb_list_components(&context, &z);
2✔
2312
                if (r < 0)
2✔
2313
                        return log_error_errno(r, "Failed to enumerate components: %m");
×
2314

2315
                STRV_FOREACH(i, z) {
6✔
2316
                        _cleanup_(context_done) Context component_context = CONTEXT_NULL;
×
2317

2318
                        r = context_from_cmdline(&component_context);
4✔
2319
                        if (r < 0)
4✔
2320
                                return r;
2321

2322
                        /* Override the component with our iter. This needs to be done in a fresh Context
2323
                         * as the installdb_fd and other state are specific to the component. */
2324
                        r = free_and_strdup_warn(&component_context.component, *i);
4✔
2325
                        if (r < 0)
4✔
2326
                                return r;
2327

2328
                        r = context_load_offline(
4✔
2329
                                        &component_context,
2330
                                        /* process_image_flags= */ 0,
2331
                                        /* read_definitions_flags= */ 0);
2332
                        if (r < 0)
4✔
2333
                                return r;
2334

2335
                        RET_GATHER(ret, installdb_cleanup_component(&component_context));
4✔
2336
                }
2337
        }
2338

2339
        return ret;
2340
}
2341

2342
VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
2343
     "Report whether a newer version is installed than currently booted");
2344
VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
2345
     "Reboot if a newer version is installed than booted");
2346
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
2✔
2347
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2348
        _cleanup_free_ char *booted_version = NULL;
2✔
2349
        int r;
2✔
2350

2351
        assert(argc == 1);
2✔
2352

2353
        r = context_from_cmdline(&context);
2✔
2354
        if (r < 0)
2✔
2355
                return r;
2356

2357
        if (context.image || context.root)
2✔
2358
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2359
                                       "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
2360

2361
        if (context.component || context.component_all)
2✔
2362
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2✔
2363
                                       "The --component= and --component-all switches may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]);
2364

2365
        r = context_load_offline(
×
2366
                        &context,
2367
                        /* process_image_flags= */ 0,
2368
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2369
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
2370
                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
2371
        if (r < 0)
×
2372
                return r;
2373

2374
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
2375

2376
        r = context_discover_update_sets_by_flag(&context, UPDATE_INSTALLED);
×
2377
        if (r < 0)
×
2378
                return r;
2379
        if (!context.newest_installed)
×
2380
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
2381

2382
        r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
×
2383
        if (r < 0) /* yes, context.root is NULL here, but we have to pass something, and it's a lot more readable
×
2384
                    * if we see what the first argument is about */
2385
                return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
2386
        if (!booted_version)
×
2387
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field.");
×
2388

2389
        r = strverscmp_improved(context.newest_installed->version, booted_version);
×
2390
        if (r > 0) {
×
2391
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
2392
                           context.newest_installed->version, booted_version,
2393
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
2394

2395
                if (streq(argv[0], "reboot"))
×
2396
                        return reboot_now();
×
2397

2398
                return EXIT_SUCCESS;
2399
        } else if (r == 0)
×
2400
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
2401
                         context.newest_installed->version, booted_version);
2402
        else
2403
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
2404
                            context.newest_installed->version, booted_version);
2405

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

2409
        return EXIT_SUCCESS;
2410
}
2411

2412
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component) {
182✔
2413
        int r;
182✔
2414

2415
        assert(context);
182✔
2416

2417
        _cleanup_strv_free_ char **z = NULL;
182✔
2418
        r = get_component_list(context->root, &z);
182✔
2419
        if (r < 0)
182✔
2420
                return r;
2421

2422
        if (ret_component_names)
182✔
2423
                *ret_component_names = TAKE_PTR(z);
182✔
2424

2425
        /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a
2426
         * TARGET_HOST? See target_get_argument() in sysupdated.c */
2427
        if (ret_has_default_component)
182✔
2428
                *ret_has_default_component =
348✔
2429
                        !context->definitions &&
348✔
2430
                        !context->component &&
174✔
2431
                        !context->root &&
174✔
2432
                        !context->image &&
348✔
2433
                        context->n_transfers > 0;
176✔
2434

2435
        return 0;
2436
}
2437

2438
VERB_NOARG(verb_components, "components",
2439
           "Show list of components");
2440
static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
174✔
2441
        _cleanup_(context_done) Context context = CONTEXT_NULL;
174✔
2442
        _cleanup_strv_free_ char **component_names = NULL;
174✔
2443
        bool has_default_component = false;
174✔
2444
        int r;
174✔
2445

2446
        assert(argc <= 1);
174✔
2447

2448
        r = context_from_cmdline(&context);
174✔
2449
        if (r < 0)
174✔
2450
                return r;
2451

2452
        if (context.component_all)
174✔
2453
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
×
2454

2455
        r = context_load_offline(
174✔
2456
                        &context,
2457
                        /* process_image_flags= */ 0,
2458
                        /* read_definitions_flags= */ 0);
2459
        if (r < 0)
174✔
2460
                return r;
2461

2462
        r = context_list_components(&context, &component_names, &has_default_component);
174✔
2463
        if (r < 0)
174✔
2464
                return log_error_errno(r, "Failed to enumerate components: %m");
×
2465

2466
        if (!sd_json_format_enabled(arg_json_format_flags)) {
174✔
2467
                if (!has_default_component && strv_isempty(component_names)) {
×
2468
                        log_info("No components defined.");
×
2469
                        return 0;
×
2470
                }
2471

2472
                _cleanup_(table_unrefp) Table *t = table_new("", "component", "description", "documentation");
×
2473
                if (!t)
×
2474
                        return log_oom();
×
2475

2476
                table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
×
2477

2478
                if (has_default_component) {
×
2479
                        r = table_add_many(
×
2480
                                        t,
2481
                                        TABLE_EMPTY,
2482
                                        TABLE_STRING, "<default>",
2483
                                        TABLE_SET_COLOR, ansi_highlight(),
2484
                                        TABLE_STRING, "Default Component",
2485
                                        TABLE_EMPTY);
2486
                        if (r < 0)
×
2487
                                return table_log_add_error(r);
×
2488
                }
2489

2490
                STRV_FOREACH(i, component_names) {
×
2491
                        _cleanup_(context_done) Context cc = CONTEXT_NULL;
×
2492

2493
                        r = context_from_base_with_component(
×
2494
                                        &context,
2495
                                        *i,
2496
                                        &cc);
2497
                        if (r < 0)
×
2498
                                return r;
2499

2500
                        r = context_load_offline(
×
2501
                                        &cc,
2502
                                        /* process_image_flags= */ 0,
2503
                                        /* read_definitions_flags= */ 0);
2504
                        if (r == -ENOMEM)
×
2505
                                return r;
2506
                        if (r < 0)
×
2507
                                continue;
×
2508

2509
                        const char *doc = cc.component_documentation ? cc.component_documentation[0] : NULL;
×
2510

2511
                        r = table_add_many(
×
2512
                                        t,
2513
                                        TABLE_BOOLEAN_CHECKMARK, cc.component_enabled,
2514
                                        TABLE_SET_COLOR, ansi_highlight_green_red(cc.component_enabled),
2515
                                        TABLE_STRING, *i,
2516
                                        TABLE_STRING, cc.component_description,
2517
                                        TABLE_STRING, doc,
2518
                                        TABLE_SET_URL, doc);
2519
                        if (r < 0)
×
2520
                                return table_log_add_error(r);
×
2521
                }
2522

2523
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
2524
        } else {
2525
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
2526

2527
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
174✔
2528
                                          SD_JSON_BUILD_PAIR_STRV("components", component_names));
2529
                if (r < 0)
174✔
2530
                        return log_error_errno(r, "Failed to create JSON: %m");
×
2531

2532
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
174✔
2533
                if (r < 0)
174✔
2534
                        return log_error_errno(r, "Failed to print JSON: %m");
×
2535
        }
2536

2537
        return 0;
174✔
2538
}
2539

2540
VERB(verb_enable_component, "enable-component", "COMPONENT…", 2, VERB_ANY, 0,
2541
     "Enable component");
2542
VERB(verb_enable_component, "disable-component", "COMPONENT…", 2, VERB_ANY, 0,
2543
     "Disable component");
2544
static int verb_enable_component(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
2545
        bool enable = streq(argv[0], "enable-component");
×
2546
        int r;
×
2547

2548
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2549
        r = context_from_cmdline(&context);
×
2550
        if (r < 0)
×
2551
                return r;
2552

2553
        if (context.component || context.component_all)
×
2554
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2555
                                       "The --component= and --component-all switches may not be combined with '%s', "
2556
                                       "specify the component(s) as arguments instead.", argv[0]);
2557

2558
        if (context.definitions)
×
2559
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2560
                                       "The --definitions= switch may not be combined with '%s'.", argv[0]);
2561

2562
        r = context_load_offline(
×
2563
                        &context,
2564
                        /* process_image_flags= */ 0,
2565
                        /* read_definitions_flags= */ 0);
2566
        if (r < 0)
×
2567
                return r;
2568

2569
        _cleanup_strv_free_ char **component_names = NULL;
×
2570
        r = context_list_components(&context, &component_names, /* ret_has_default_component= */ NULL);
×
2571
        if (r < 0)
×
2572
                return r;
2573

2574
        /* Component definition files live directly below the configuration directories, hence the drop-in
2575
         * goes right next to them below /etc. */
2576
        _cleanup_free_ char *dropin_dir = path_join(context.root, SYSCONF_DIR);
×
2577
        if (!dropin_dir)
×
2578
                return log_oom();
×
2579

2580
        STRV_FOREACH(name, strv_skip(argv, 1)) {
×
2581
                if (!component_name_valid(*name))
×
2582
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", *name);
×
2583

2584
                if (!strv_contains(component_names, *name))
×
2585
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Component not found: %s", *name);
×
2586

2587
                _cleanup_free_ char *fname = strjoin("sysupdate.", *name, ".component");
×
2588
                if (!fname)
×
2589
                        return log_oom();
×
2590

2591
                /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
2592
                r = write_drop_in_format(
×
2593
                                dropin_dir,
2594
                                fname,
2595
                                50, "systemd-sysupdate-enabled",
2596
                                "# Generated via 'systemd-sysupdate %s'\n\n"
2597
                                "[Component]\n"
2598
                                "Enabled=%s\n",
2599
                                argv[0],
2600
                                yes_no(enable));
2601
                if (r < 0)
×
2602
                        return log_error_errno(r, "Failed to write drop-in for component '%s': %m", *name);
×
2603

2604
                log_info("Component '%s' %s.", *name, enable ? "enabled" : "disabled");
×
2605
        }
2606

2607
        return 0;
2608
}
2609

2610
static int help(void) {
×
2611
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
2612
        int r;
×
2613

2614
        r = verbs_get_help_table(&verbs);
×
2615
        if (r < 0)
×
2616
                return r;
2617

2618
        r = option_parser_get_help_table(&common_options);
×
2619
        if (r < 0)
×
2620
                return r;
2621

2622
        r = option_parser_get_help_table_group("Options", &options);
×
2623
        if (r < 0)
×
2624
                return r;
2625

2626
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
2627

2628
        help_cmdline("[OPTIONS…] [VERSION]");
×
2629
        help_abstract("Update OS images.");
×
2630

2631
        help_section("Commands");
×
2632
        r = table_print_or_warn(verbs);
×
2633
        if (r < 0)
×
2634
                return r;
2635

2636
        r = table_print_or_warn(common_options);
×
2637
        if (r < 0)
×
2638
                return r;
2639

2640
        help_section("Options");
×
2641
        r = table_print_or_warn(options);
×
2642
        if (r < 0)
×
2643
                return r;
2644

2645
        help_man_page_reference("systemd-sysupdate", "8");
×
2646
        return 0;
2647
}
2648

2649
VERB_COMMON_HELP_HIDDEN(help);
×
2650

2651
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
1,336✔
2652
        assert(argc >= 0);
1,336✔
2653
        assert(argv);
1,336✔
2654
        assert(remaining_args);
1,336✔
2655

2656
        OptionParser opts = { argc, argv };
1,336✔
2657
        int r;
1,336✔
2658

2659
        FOREACH_OPTION_OR_RETURN(c, &opts)
4,152✔
2660
                switch (c) {
1,488✔
2661

2662
                OPTION_COMMON_HELP:
×
2663
                        return help();
×
2664

2665
                OPTION_COMMON_VERSION:
×
2666
                        return version();
×
2667

2668
                OPTION_GROUP("Options"):
2669
                        break;
2670

2671
                OPTION('C', "component", "NAME",
45✔
2672
                       "Select component to update"):
2673
                        if (isempty(opts.arg)) {
45✔
2674
                                arg_component = mfree(arg_component);
×
2675
                                arg_component_all = false;
×
2676
                                break;
×
2677
                        }
2678

2679
                        if (!component_name_valid(opts.arg))
45✔
2680
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", opts.arg);
8✔
2681

2682
                        r = free_and_strdup_warn(&arg_component, opts.arg);
37✔
2683
                        if (r < 0)
37✔
2684
                                return r;
2685

2686
                        arg_component_all = false;
37✔
2687
                        break;
37✔
2688

2689
                OPTION('A', "component-all", NULL, "Process all components"):
3✔
2690

2691
                        arg_component = mfree(arg_component);
3✔
2692
                        arg_component_all = true;
3✔
2693
                        break;
3✔
2694

2695
                OPTION_LONG("definitions", "DIR",
5✔
2696
                            "Find transfer definitions in specified directory"):
2697
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions);
5✔
2698
                        if (r < 0)
5✔
2699
                                return r;
2700
                        break;
2701

2702
                OPTION_LONG("root", "PATH",
×
2703
                            "Operate on an alternate filesystem root"):
2704
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
2705
                        if (r < 0)
×
2706
                                return r;
2707
                        break;
2708

2709
                OPTION_LONG("image", "PATH",
×
2710
                            "Operate on disk image as filesystem root"):
2711
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
2712
                        if (r < 0)
×
2713
                                return r;
2714
                        break;
2715

2716
                OPTION_LONG("image-policy", "POLICY",
×
2717
                            "Specify disk image dissection policy"):
2718
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
2719
                        if (r < 0)
×
2720
                                return r;
2721
                        break;
2722

2723
                OPTION_LONG("transfer-source", "PATH",
×
2724
                            "Specify the directory to transfer sources from"):
2725
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_transfer_source);
×
2726
                        if (r < 0)
×
2727
                                return r;
2728

2729
                        break;
2730

2731
                OPTION('m', "instances-max", "INT",
×
2732
                       "How many instances to maintain"):
2733
                        r = safe_atou64(opts.arg, &arg_instances_max);
×
2734
                        if (r < 0)
×
2735
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", opts.arg);
×
2736

2737
                        break;
2738

2739
                OPTION_LONG("sync", "BOOL",
×
2740
                            "Controls whether to sync data to disk"):
2741
                        r = parse_boolean_argument("--sync=", opts.arg, &arg_sync);
×
2742
                        if (r < 0)
×
2743
                                return r;
2744
                        break;
2745

2746
                OPTION_LONG("verify", "BOOL",
744✔
2747
                            "Force signature verification on or off"): {
2748
                        bool b;
744✔
2749

2750
                        r = parse_boolean_argument("--verify=", opts.arg, &b);
744✔
2751
                        if (r < 0)
744✔
2752
                                return r;
×
2753

2754
                        arg_verify = b;
744✔
2755
                        break;
744✔
2756
                }
2757

2758
                OPTION_LONG("reboot", NULL,
1✔
2759
                            "Reboot after updating to newer version"):
2760
                        arg_reboot = true;
1✔
2761
                        break;
1✔
2762

2763
                OPTION_LONG("offline", NULL,
228✔
2764
                            "Do not fetch metadata from the network"):
2765
                        arg_offline = true;
228✔
2766
                        break;
228✔
2767

2768
                OPTION_LONG("cleanup", "BOOL", "Clean up orphaned files after completing update"): {
4✔
2769
                        bool b;
4✔
2770

2771
                        r = parse_boolean_argument("--cleanup=", opts.arg, &b);
4✔
2772
                        if (r < 0)
4✔
2773
                                return r;
×
2774

2775
                        arg_cleanup = b;
4✔
2776
                        break;
4✔
2777
                }
2778

2779
                OPTION_COMMON_NO_PAGER:
×
2780
                        arg_pager_flags |= PAGER_DISABLE;
×
2781
                        break;
×
2782

2783
                OPTION_COMMON_NO_LEGEND:
×
2784
                        arg_legend = false;
×
2785
                        break;
×
2786

2787
                OPTION_COMMON_JSON:
458✔
2788
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
458✔
2789
                        if (r <= 0)
458✔
2790
                                return r;
2791

2792
                        break;
2793
                }
2794

2795
        if (arg_image && arg_root)
1,328✔
2796
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
×
2797

2798
        if (arg_image || arg_root) {
1,328✔
2799
                if (arg_reboot)
×
2800
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
2801

2802
                if (arg_definitions)
×
2803
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= switch may not be combined with --root= or --image=.");
×
2804
        }
2805

2806
        if (arg_reboot && arg_component)
1,328✔
2807
                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✔
2808

2809
        if (arg_definitions && arg_component)
1,327✔
2810
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
2811

2812
        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
1,327✔
2813
        if (r < 0)
1,327✔
2814
                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
×
2815
        if (r > 0)
1,327✔
2816
                arg_varlink = true;
212✔
2817

2818
        *remaining_args = option_parser_get_args(&opts);
1,327✔
2819
        return 1;
1,327✔
2820
}
2821

2822
static int vl_server(void) {
212✔
2823
        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
212✔
2824
        _cleanup_(server_done) Server server = SERVER_NULL;
212✔
2825
        int r;
212✔
2826

2827
        r = varlink_server_new(&varlink_server,
212✔
2828
                               SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
2829
                               &server);
2830
        if (r < 0)
212✔
2831
                return log_error_errno(r, "Failed to allocate Varlink server: %m");
×
2832

2833
        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysUpdate);
212✔
2834
        if (r < 0)
212✔
2835
                return log_error_errno(r, "Failed to add Varlink interface: %m");
×
2836

2837
        r = sd_varlink_server_bind_method_many(
212✔
2838
                        varlink_server,
2839
                        "io.systemd.SysUpdate.CheckNew", vl_method_check_new);
2840
        if (r < 0)
212✔
2841
                return log_error_errno(r, "Failed to bind Varlink method: %m");
×
2842

2843
        r = sd_varlink_server_loop_auto(varlink_server);
212✔
2844
        if (r < 0)
212✔
2845
                return log_error_errno(r, "Failed to run Varlink event loop: %m");
×
2846

2847
        return 0;
2848
}
2849

2850
static int run(int argc, char *argv[]) {
1,336✔
2851
        int r;
1,336✔
2852

2853
        LIBBLKID_NOTE(recommended);
1,336✔
2854
        LIBCRYPTO_NOTE(suggested);
1,336✔
2855
        LIBCRYPTSETUP_NOTE(suggested);
1,336✔
2856
        LIBMOUNT_NOTE(recommended);
1,336✔
2857

2858
        log_setup();
1,336✔
2859

2860
        char **args = NULL;
1,336✔
2861
        r = parse_argv(argc, argv, &args);
1,336✔
2862
        if (r <= 0)
1,336✔
2863
                return r;
1,336✔
2864

2865
        if (arg_varlink)
1,327✔
2866
                return vl_server(); /* Invocation as Varlink service */
212✔
2867

2868
        return dispatch_verb(args, NULL);
1,115✔
2869
}
2870

2871
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
1,336✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc