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

systemd / systemd / 29295695877

13 Jul 2026 11:19PM UTC coverage: 72.94% (-0.05%) from 72.994%
29295695877

push

github

web-flow
userdb: suppress userdb queries for backends indicating uid/gid/name range info via xattrs on entrypoint sockets (#42961)

Let's optimize userdb queries a bit: by encoding the covered UID/GID
ranges and user/group name patterns on the varlink entrypoint sockets
for userdb backends we can make them wake up less and reduce the work
triggered by queries.

81 of 92 new or added lines in 5 files covered. (88.04%)

5898 existing lines in 76 files now uncovered.

345436 of 473591 relevant lines covered (72.94%)

1320061.25 hits per line

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

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

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

77
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
1,336✔
78
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
1,336✔
79
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
1,336✔
80
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
1,336✔
81
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
1,336✔
82
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
1,336✔
83

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

96
void context_done(Context *c) {
2,438✔
97
        assert(c);
2,438✔
98

99
        c->mounted_dir = umount_and_rmdir_and_free(c->mounted_dir);
2,438✔
100
        c->loop_device = loop_device_unref(c->loop_device);
2,438✔
101

102
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
11,229✔
103
                transfer_free(*tr);
8,791✔
104
        c->transfers = mfree(c->transfers);
2,438✔
105
        c->n_transfers = 0;
2,438✔
106

107
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
3,570✔
108
                transfer_free(*tr);
1,132✔
109
        c->disabled_transfers = mfree(c->disabled_transfers);
2,438✔
110
        c->n_disabled_transfers = 0;
2,438✔
111

112
        c->features = hashmap_free(c->features);
2,438✔
113

114
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
7,131✔
115
                update_set_free(*us);
4,693✔
116
        c->update_sets = mfree(c->update_sets);
2,438✔
117
        c->n_update_sets = 0;
2,438✔
118

119
        c->web_cache = hashmap_free(c->web_cache);
2,438✔
120

121
        c->installdb_fd = safe_close(c->installdb_fd);
2,438✔
122

123
        c->definitions = mfree(c->definitions);
2,438✔
124
        c->root = mfree(c->root);
2,438✔
125
        c->image = mfree(c->image);
2,438✔
126
        c->component = mfree(c->component);
2,438✔
127
        c->component_description = mfree(c->component_description);
2,438✔
128
        c->component_documentation = strv_free(c->component_documentation);
2,438✔
129
        c->image_policy = image_policy_free(c->image_policy);
2,438✔
130
        c->transfer_source = mfree(c->transfer_source);
2,438✔
131

132
        target_identifier_done(&c->target_identifier);
2,438✔
133
        condition_free_list(c->component_suggest_on);
2,438✔
134
}
2,438✔
135

136
static int context_from_cmdline(Context *ret) {
1,119✔
137
        assert(ret);
1,119✔
138

139
        _cleanup_(context_done) Context context = CONTEXT_NULL;
1,119✔
140

141
        context.instances_max = arg_instances_max;
1,119✔
142
        context.sync = arg_sync;
1,119✔
143
        context.reboot = arg_reboot;
1,119✔
144
        context.verify = arg_verify;
1,119✔
145
        context.offline = arg_offline;
1,119✔
146
        context.cleanup = arg_cleanup;
1,119✔
147
        context.component_select = arg_component_select;
1,119✔
148
        context.feature_select = arg_feature_select;
1,119✔
149

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

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

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

159
        if (strdup_to(&context.component, arg_component) < 0)
1,119✔
160
                return log_oom();
×
161

162
        if (strdup_to(&context.transfer_source, arg_transfer_source) < 0)
1,119✔
UNCOV
163
                return log_oom();
×
164

165
        if (arg_image_policy) {
1,119✔
UNCOV
166
                context.image_policy = image_policy_copy(arg_image_policy);
×
UNCOV
167
                if (!context.image_policy)
×
UNCOV
168
                        return log_oom();
×
169
        }
170

171
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
1,119✔
172
        return 0;
1,119✔
173
}
174

UNCOV
175
static int context_from_base_with_component(const Context *base, const char *component, Context *ret) {
×
176
        assert(base);
×
UNCOV
177
        assert(component);
×
178
        assert(ret);
×
179

180
        /* Copies the specified context, but changes the component to the specified one */
181

182
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
183

UNCOV
184
        context.instances_max = base->instances_max;
×
185
        context.sync = base->sync;
×
186
        context.reboot = base->reboot;
×
UNCOV
187
        context.verify = base->verify;
×
188
        context.offline = base->offline;
×
189
        context.cleanup = base->cleanup;
×
UNCOV
190
        context.feature_select = base->feature_select;
×
191

192
        if (strdup_to(&context.root, base->root) < 0)
×
UNCOV
193
                return log_oom();
×
194

195
        if (strdup_to(&context.image, base->image) < 0)
×
196
                return log_oom();
×
197

UNCOV
198
        if (strdup_to(&context.transfer_source, base->transfer_source) < 0)
×
UNCOV
199
                return log_oom();
×
200

201
        if (base->image_policy) {
×
UNCOV
202
                context.image_policy = image_policy_copy(base->image_policy);
×
UNCOV
203
                if (!context.image_policy)
×
UNCOV
204
                        return log_oom();
×
205
        }
206

UNCOV
207
        if (strdup_to(&context.component, component) < 0)
×
UNCOV
208
                return log_oom();
×
209

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

UNCOV
216
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
×
UNCOV
217
        return 0;
×
218
}
219

220
/* Stores any long-running server state which needs to persist between varlink calls, such as state for
221
 * pending polkit requests */
222
typedef struct Server {
223
        sd_bus *system_bus;
224
        Hashmap *polkit_registry;
225
} Server;
226

227
#define SERVER_NULL \
228
        (Server) { \
229
                /* all fields fine with being initialised to NULL */ \
230
        }
231

232
static void server_done(Server *s) {
212✔
233
        assert(s);
212✔
234

235
        s->polkit_registry = hashmap_free(s->polkit_registry);
212✔
236
        s->system_bus = sd_bus_flush_close_unref(s->system_bus);
212✔
237
}
212✔
238

UNCOV
239
static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free);
×
240

241
static int read_features(
1,299✔
242
                Context *c,
243
                const char **dirs) {
244

245
        int r;
1,299✔
246

247
        assert(c);
1,299✔
248

249
        ConfFile **files = NULL;
1,299✔
250
        size_t n_files = 0;
1,299✔
251
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,299✔
252

253
        r = conf_files_list_strv_full(
2,598✔
254
                        ".feature",
255
                        c->root,
1,299✔
256
                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
257
                        dirs,
258
                        &files,
259
                        &n_files);
260
        if (r < 0)
1,299✔
261
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m");
×
262

263
        FOREACH_ARRAY(i, files, n_files) {
2,531✔
264
                ConfFile *e = *i;
1,232✔
265

266
                _cleanup_(feature_unrefp) Feature *f = feature_new();
1,232✔
267
                if (!f)
1,232✔
UNCOV
268
                        return log_oom();
×
269

270
                r = feature_read_definition(f, c->root, e->result, dirs);
1,232✔
271
                if (r < 0)
1,232✔
272
                        return r;
273

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

278
                TAKE_PTR(f);
1,232✔
279
        }
280

281
        return 0;
282
}
283

284
static int read_transfers(
1,306✔
285
                Context *c,
286
                const char **dirs,
287
                const char *suffix,
288
                const char *node) {
289

290
        ConfFile **files = NULL;
1,306✔
291
        Transfer **transfers = NULL, **disabled = NULL;
1,306✔
292
        size_t n_files = 0, n_transfers = 0, n_disabled = 0;
1,306✔
293
        int r;
1,306✔
294

295
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,306✔
296
        CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array);
1,306✔
297
        CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array);
1,306✔
298

299
        assert(c);
1,306✔
300
        assert(dirs);
1,306✔
301
        assert(suffix);
1,306✔
302

303
        r = conf_files_list_strv_full(suffix, c->root,
1,306✔
304
                                      CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
305
                                      dirs, &files, &n_files);
306
        if (r < 0)
1,306✔
UNCOV
307
                return log_error_errno(r, "Failed to enumerate sysupdate.d/*%s definitions: %m", suffix);
×
308

309
        FOREACH_ARRAY(i, files, n_files) {
11,229✔
UNCOV
310
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
311
                Transfer **appended;
9,923✔
312
                ConfFile *e = *i;
9,923✔
313

314
                t = transfer_new(c);
9,923✔
315
                if (!t)
9,923✔
UNCOV
316
                        return log_oom();
×
317

318
                r = transfer_read_definition(t, e->result, dirs, c->features);
9,923✔
319
                if (r < 0)
9,923✔
320
                        return r;
321

322
                r = transfer_resolve_paths(t, c->root, node);
9,923✔
323
                if (r < 0)
9,923✔
324
                        return r;
325

326
                if (t->enabled)
9,923✔
327
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
8,791✔
328
                else
329
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
1,132✔
330
                if (!appended)
9,923✔
UNCOV
331
                        return log_oom();
×
332
                TAKE_PTR(t);
9,923✔
333
        }
334

335
        c->transfers = TAKE_PTR(transfers);
1,306✔
336
        c->n_transfers = n_transfers;
1,306✔
337
        c->disabled_transfers = TAKE_PTR(disabled);
1,306✔
338
        c->n_disabled_transfers = n_disabled;
1,306✔
339
        return 0;
1,306✔
340
}
341

342
static int read_component(Context *c) {
1,299✔
343
        int r;
1,299✔
344

345
        assert(c);
1,299✔
346

347
        /* Read a component description file, but only if we actually operate on a component */
348
        if (c->definitions || !c->component)
1,299✔
349
                return 0;
1,299✔
350

351
        _cleanup_free_ char *j = strjoin("sysupdate.", c->component, ".component");
76✔
352
        if (!j)
38✔
UNCOV
353
                return log_oom();
×
354

355
        ConfigTableItem table[] = {
38✔
356
                { "Component", "Description",                config_parse_string,              0,                             &c->component_description   },
38✔
357
                { "Component", "Documentation",              config_parse_url_specifiers_many, 0,                             &c->component_documentation },
38✔
358
                { "Component", "Enabled",                    config_parse_bool,                0,                             &c->component_enabled       },
38✔
359
                { "Component", "Suggest",                    config_parse_tristate,            0,                             &c->component_suggest       },
38✔
360
                { "Component", "SuggestOnArchitecture",      config_parse_condition,           CONDITION_ARCHITECTURE,        &c->component_suggest_on    },
38✔
361
                { "Component", "SuggestOnFirmware",          config_parse_condition,           CONDITION_FIRMWARE,            &c->component_suggest_on    },
362
                { "Component", "SuggestOnVirtualization",    config_parse_condition,           CONDITION_VIRTUALIZATION,      &c->component_suggest_on    },
363
                { "Component", "SuggestOnHost",              config_parse_condition,           CONDITION_HOST,                &c->component_suggest_on    },
364
                { "Component", "SuggestOnFraction",          config_parse_condition,           CONDITION_FRACTION,            &c->component_suggest_on    },
365
                { "Component", "SuggestOnKernelCommandLine", config_parse_condition,           CONDITION_KERNEL_COMMAND_LINE, &c->component_suggest_on    },
366
                { "Component", "SuggestOnVersion",           config_parse_condition,           CONDITION_VERSION,             &c->component_suggest_on    },
367
                { "Component", "SuggestOnCredential",        config_parse_condition,           CONDITION_CREDENTIAL,          &c->component_suggest_on    },
368
                { "Component", "SuggestOnSecurity",          config_parse_condition,           CONDITION_SECURITY,            &c->component_suggest_on    },
369
                { "Component", "SuggestOnOSRelease",         config_parse_condition,           CONDITION_OS_RELEASE,          &c->component_suggest_on    },
370
                { "Component", "SuggestOnMachineTag",        config_parse_condition,           CONDITION_MACHINE_TAG,         &c->component_suggest_on    },
371
                {}
372
        };
373

374
        r = config_parse_standard_file_with_dropins_full(
76✔
375
                        c->root,
376
                        /* root_fd= */ -EBADF,
377
                        j,
378
                        "Component\0",
379
                        config_item_table_lookup, table,
380
                        CONFIG_PARSE_WARN,
381
                        /* userdata= */ (void *) c->root,
38✔
382
                        /* ret_stats_by_path= */ NULL,
383
                        /* ret_dropin_files= */ NULL);
384
        if (r < 0)
38✔
UNCOV
385
                return r;
×
386

387
        return 0;
388
}
389

390
typedef enum ReadDefinitionsFlags {
391
        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0, /* fail unless there's at least one enabled transfer */
392
        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS     = 1 << 1, /* fail unless there's at least one transfer */
393
        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT = 1 << 2, /* fail if component is disabled */
394
} ReadDefinitionsFlags;
395

396
static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
1,299✔
397
        _cleanup_strv_free_ char **dirs = NULL;
1,299✔
398
        int r;
1,299✔
399

400
        assert(c);
1,299✔
401

402
        if (c->definitions)
1,299✔
403
                dirs = strv_new(c->definitions);
5✔
404
        else if (c->component) {
1,294✔
405
                char **l = CONF_PATHS_STRV("");
38✔
406
                size_t i = 0;
38✔
407

408
                dirs = new0(char*, strv_length(l) + 1);
38✔
409
                if (!dirs)
38✔
UNCOV
410
                        return log_oom();
×
411

412
                STRV_FOREACH(dir, l) {
190✔
413
                        char *j;
152✔
414

415
                        j = strjoin(*dir, "sysupdate.", c->component, ".d");
152✔
416
                        if (!j)
152✔
UNCOV
417
                                return log_oom();
×
418

419
                        dirs[i++] = j;
152✔
420
                }
421
        } else
422
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
1,256✔
423
        if (!dirs)
1,299✔
UNCOV
424
                return log_oom();
×
425

426
        r = read_component(c);
1,299✔
427
        if (r < 0)
1,299✔
428
                return r;
429

430
        r = read_features(c, (const char**) dirs);
1,299✔
431
        if (r < 0)
1,299✔
432
                return r;
433

434
        r = read_transfers(c, (const char**) dirs, ".transfer", node);
1,299✔
435
        if (r < 0)
1,299✔
436
                return r;
437

438
        if (c->n_transfers + c->n_disabled_transfers == 0) {
1,299✔
439
                /* Backwards-compat: If no .transfer defs are found, fall back to trying .conf! */
440
                r = read_transfers(c, (const char**) dirs, ".conf", node);
7✔
441
                if (r < 0)
7✔
442
                        return r;
443

444
                if (c->n_transfers + c->n_disabled_transfers > 0)
7✔
UNCOV
445
                        log_warning("As of v257, transfer definitions should have the '.transfer' extension.");
×
446
        }
447

448
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS) &&
1,299✔
449
            c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) {
1,115✔
UNCOV
450
                if (c->component)
×
UNCOV
451
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
452
                                               "No transfer definitions for component '%s' found.",
453
                                               c->component);
454

UNCOV
455
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
456
                                       "No transfer definitions found.");
457
        }
458

459
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT) && !c->component_enabled)
1,299✔
UNCOV
460
                return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Component is disabled.");
×
461

462
        return 0;
463
}
464

465
static int context_load_installed_instances(Context *c) {
1,299✔
466
        int r;
1,299✔
467

468
        assert(c);
1,299✔
469

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

472
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
10,090✔
473
                Transfer *t = *tr;
8,791✔
474

475
                r = resource_load_instances(
8,791✔
476
                                &t->target,
477
                                c->verify >= 0 ? c->verify : t->verify,
8,791✔
478
                                &c->web_cache);
479
                if (r < 0)
8,791✔
480
                        return r;
481
        }
482

483
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
2,431✔
484
                Transfer *t = *tr;
1,132✔
485

486
                r = resource_load_instances(
1,132✔
487
                                &t->target,
488
                                c->verify >= 0 ? c->verify : t->verify,
1,132✔
489
                                &c->web_cache);
490
                if (r < 0)
1,132✔
491
                        return r;
492
        }
493

494
        return 0;
495
}
496

497
static int context_load_available_instances(Context *c) {
839✔
498
        int r;
839✔
499

500
        assert(c);
839✔
501

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

504
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
6,436✔
505
                Transfer *t = *tr;
5,617✔
506

507
                r = resource_load_instances(
5,617✔
508
                                &t->source,
509
                                c->verify >= 0 ? c->verify : t->verify,
5,617✔
510
                                &c->web_cache);
511
                if (r < 0)
5,617✔
512
                        return r;
513
        }
514

515
        return 0;
516
}
517

518
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
1,850✔
519
        _cleanup_free_ char *boundary = NULL;
1,850✔
520
        bool newest_found = false;
1,850✔
521
        int r;
1,850✔
522

523
        assert(c);
1,850✔
524
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
1,850✔
525

526
        for (;;) {
9,840✔
UNCOV
527
                _cleanup_free_ Instance **cursor_instances = NULL;
×
528
                bool skip = false;
9,840✔
529
                UpdateSetFlags extra_flags = 0;
9,840✔
530
                _cleanup_free_ char *cursor = NULL;
7,990✔
531
                UpdateSet *us = NULL;
9,840✔
532

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

537
                        assert(*tr);
63,257✔
538

539
                        if (flags == UPDATE_AVAILABLE)
63,257✔
540
                                rr = &(*tr)->source;
32,235✔
541
                        else {
542
                                assert(flags == UPDATE_INSTALLED);
31,022✔
543
                                rr = &(*tr)->target;
31,022✔
544
                        }
545

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

549
                                assert(i);
183,419✔
550

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

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

559
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
7,990✔
UNCOV
560
                                        return log_oom();
×
561

562
                                break; /* All subsequent instances will be older than this one */
563
                        }
564

565
                        if (flags == UPDATE_AVAILABLE && !cursor)
63,257✔
566
                                break; /* This transfer didn't have a version older than the boundary,
567
                                        * so any older-than-boundary version that might exist in a different
568
                                        * transfer must always be incomplete. For reasons described below,
569
                                        * we don't include incomplete versions for AVAILABLE updates. So we
570
                                        * are completely done looking. */
571
                }
572

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

576
                cursor_instances = new0(Instance*, c->n_transfers);
7,990✔
577
                if (!cursor_instances)
7,990✔
UNCOV
578
                        return log_oom();
×
579

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

585
                        assert(t);
54,119✔
586

587
                        if (flags == UPDATE_AVAILABLE) {
54,119✔
588
                                match = resource_find_instance(&t->source, cursor);
30,106✔
589
                                if (!match) {
30,106✔
590
                                        /* When we're looking for updates to download, we don't offer
591
                                         * incomplete versions at all. The server wants to send us an update
592
                                         * with parts of the OS missing. For robustness sake, let's not do
593
                                         * that. */
594
                                        skip = true;
595
                                        break;
596
                                }
597
                        } else {
598
                                assert(flags == UPDATE_INSTALLED);
24,013✔
599

600
                                match = resource_find_instance(&t->target, cursor);
24,013✔
601
                                if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING)))
24,013✔
602
                                        /* When we're looking for installed versions, let's be robust and treat
603
                                         * an incomplete installation as an installation. Otherwise, there are
604
                                         * situations that can lead to sysupdate wiping the currently booted OS.
605
                                         * See https://github.com/systemd/systemd/issues/33339 */
606
                                        extra_flags |= UPDATE_INCOMPLETE;
7,444✔
607
                        }
608

609
                        cursor_instances[k] = match;
53,490✔
610

611
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
53,490✔
UNCOV
612
                                extra_flags |= UPDATE_OBSOLETE;
×
613

614
                        if (strv_contains(t->protected_versions, cursor))
53,490✔
UNCOV
615
                                extra_flags |= UPDATE_PROTECTED;
×
616

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

622
                        if (match && match->is_partial)
45,866✔
623
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
36✔
624

625
                        if (match && match->is_pending)
45,866✔
626
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
748✔
627
                }
628

629
                r = free_and_strdup_warn(&boundary, cursor);
7,990✔
630
                if (r < 0)
7,990✔
631
                        return r;
632

633
                if (skip)
7,990✔
634
                        continue;
629✔
635

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

640
                        if (strverscmp_improved(u->version, cursor) != 0)
16,467✔
641
                                continue;
13,799✔
642

643
                        /* Merge in what we've learned and continue onto the next version */
644

645
                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
2,668✔
646
                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
1,324✔
647
                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
1,308✔
648
                                assert(u->n_instances == c->n_transfers);
1,392✔
649

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

653
                                for (size_t j = 0; j < u->n_instances; j++) {
11,328✔
654
                                        if (!u->instances[j])
9,936✔
655
                                                u->instances[j] = cursor_instances[j];
5,792✔
656

657
                                        /* Make sure that the list is full if the update is AVAILABLE */
658
                                        assert(flags != UPDATE_AVAILABLE || u->instances[j]);
9,936✔
659
                                }
660
                        }
661

662
                        u->flags |= flags | extra_flags;
2,668✔
663

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

671
                        skip = true;
672
                        newest_found = true;
673
                        break;
674
                }
675

676
                if (skip)
64✔
677
                        continue;
2,668✔
678

679
                /* Doesn't exist yet, let's add it */
680
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
4,693✔
UNCOV
681
                        return log_oom();
×
682

683
                us = new(UpdateSet, 1);
4,693✔
684
                if (!us)
4,693✔
UNCOV
685
                        return log_oom();
×
686

687
                *us = (UpdateSet) {
4,693✔
688
                        .flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
4,693✔
689
                        .version = TAKE_PTR(cursor),
4,693✔
690
                        .instances = TAKE_PTR(cursor_instances),
4,693✔
691
                        .n_instances = c->n_transfers,
4,693✔
692
                };
693

694
                c->update_sets[c->n_update_sets++] = us;
4,693✔
695

696
                newest_found = true;
4,693✔
697

698
                /* Remember which one is the newest installed */
699
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
4,693✔
700
                        c->newest_installed = us;
984✔
701

702
                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
703
                 * It may be partial or pending. */
704
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
4,693✔
705
                        c->candidate = us;
321✔
706
        }
707

708
        /* Newest installed is newer than or equal to candidate? Then suppress the candidate */
709
        if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
1,850✔
710
            c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
1,596✔
711
                c->candidate = NULL;
48✔
712

713
        /* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */
714
        if (c->newest_installed &&
1,850✔
715
            (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) &&
1,756✔
716
            !c->candidate)
196✔
717
                c->candidate = c->newest_installed;
196✔
718

719
        return 0;
720
}
721

722
static int context_discover_update_sets(Context *c) {
1,031✔
723
        int r;
1,031✔
724

725
        assert(c);
1,031✔
726

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

729
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
1,031✔
730
        if (r < 0)
1,031✔
731
                return r;
732

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

736
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
819✔
737
                if (r < 0)
819✔
738
                        return r;
739
        }
740

741
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
1,031✔
742
        return 0;
1,031✔
743
}
744

UNCOV
745
static int context_show_table(Context *c) {
×
746
        _cleanup_(table_unrefp) Table *t = NULL;
×
UNCOV
747
        int r;
×
748

UNCOV
749
        assert(c);
×
750

UNCOV
751
        t = table_new("", "version", "installed", "available", "assessment");
×
UNCOV
752
        if (!t)
×
UNCOV
753
                return log_oom();
×
754

UNCOV
755
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
UNCOV
756
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
757
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
758

UNCOV
759
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
UNCOV
760
                UpdateSet *us = *update_set;
×
761
                const char *color;
×
762

UNCOV
763
                color = update_set_flags_to_color(us->flags);
×
764

UNCOV
765
                r = table_add_many(t,
×
766
                                   TABLE_STRING,    update_set_flags_to_glyph(us->flags),
767
                                   TABLE_SET_COLOR, color,
768
                                   TABLE_STRING,    us->version,
769
                                   TABLE_SET_COLOR, color,
770
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
771
                                   TABLE_SET_COLOR, color,
772
                                   TABLE_STRING,    glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
773
                                   TABLE_SET_COLOR, color,
774
                                   TABLE_STRING,    update_set_flags_to_string(us->flags),
775
                                   TABLE_SET_COLOR, color);
UNCOV
776
                if (r < 0)
×
UNCOV
777
                        return table_log_add_error(r);
×
778
        }
779

UNCOV
780
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
781
}
782

783
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
208✔
784
        assert(c);
208✔
785
        assert(version);
208✔
786

787
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
368✔
788
                if (streq((*update_set)->version, version))
368✔
789
                        return *update_set;
790

791
        return NULL;
792
}
793

794
static int context_show_version(Context *c, const char *version) {
208✔
795
        bool show_fs_columns = false, show_partition_columns = false,
208✔
796
                have_fs_attributes = false, have_partition_attributes = false,
208✔
797
                have_size = false, have_tries = false, have_no_auto = false,
208✔
798
                have_read_only = false, have_growfs = false, have_sha256 = false;
208✔
799
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
208✔
800
        _cleanup_(table_unrefp) Table *t = NULL;
208✔
801
        _cleanup_strv_free_ char **changelog_urls = NULL;
208✔
802
        UpdateSet *us;
208✔
803
        int r;
208✔
804

805
        assert(c);
208✔
806
        assert(version);
208✔
807

808
        us = context_update_set_by_version(c, version);
208✔
809
        if (!us)
208✔
UNCOV
810
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
811

812
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
208✔
813
                pager_open(arg_pager_flags);
112✔
814

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

830
        t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
208✔
831
        if (!t)
208✔
UNCOV
832
                return log_oom();
×
833

834
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
208✔
835
        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
208✔
836
        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
208✔
837
        (void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
208✔
838
        (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
208✔
839
        (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
208✔
840
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
208✔
841

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

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

851
                if (tr->target.type == RESOURCE_PARTITION)
1,488✔
852
                        show_partition_columns = true;
416✔
853
                if (RESOURCE_IS_FILESYSTEM(tr->target.type))
1,488✔
854
                        show_fs_columns = true;
1,072✔
855

856
                STRV_FOREACH(changelog, tr->changelog) {
1,488✔
UNCOV
857
                        assert(*changelog);
×
858

UNCOV
859
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
UNCOV
860
                        if (!changelog_url)
×
UNCOV
861
                                return log_oom();
×
862

863
                        /* Avoid duplicates */
UNCOV
864
                        if (strv_contains(changelog_urls, changelog_url))
×
UNCOV
865
                                continue;
×
866

867
                        /* changelog_urls takes ownership of expanded changelog_url */
UNCOV
868
                        r = strv_consume(&changelog_urls, TAKE_PTR(changelog_url));
×
UNCOV
869
                        if (r < 0)
×
UNCOV
870
                                return log_oom();
×
871
                }
872
        }
873

874
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
1,696✔
875
                Instance *i = *inst;
1,488✔
876

877
                if (!i) {
1,488✔
878
                        assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING));
112✔
879
                        continue;
112✔
880
                }
881

882
                r = table_add_many(t,
1,376✔
883
                                   TABLE_STRING, resource_type_to_string(i->resource->type),
884
                                   TABLE_PATH, i->path);
885
                if (r < 0)
1,376✔
UNCOV
886
                        return table_log_add_error(r);
×
887

888
                if (i->metadata.partition_uuid_set) {
1,376✔
889
                        have_partition_attributes = true;
288✔
890
                        r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
288✔
891
                } else
892
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
893
                if (r < 0)
1,376✔
UNCOV
894
                        return table_log_add_error(r);
×
895

896
                if (i->metadata.partition_flags_set) {
1,376✔
897
                        have_partition_attributes = true;
288✔
898
                        r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
288✔
899
                } else
900
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
901
                if (r < 0)
1,376✔
902
                        return table_log_add_error(r);
×
903

904
                if (i->metadata.mtime != USEC_INFINITY) {
1,376✔
905
                        have_fs_attributes = true;
1,056✔
906
                        r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
1,056✔
907
                } else
908
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
320✔
909
                if (r < 0)
1,376✔
UNCOV
910
                        return table_log_add_error(r);
×
911

912
                if (i->metadata.mode != MODE_INVALID) {
1,376✔
913
                        have_fs_attributes = true;
1,056✔
914
                        r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
1,056✔
915
                } else
916
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
320✔
917
                if (r < 0)
1,376✔
UNCOV
918
                        return table_log_add_error(r);
×
919

920
                if (i->metadata.size != UINT64_MAX) {
1,376✔
UNCOV
921
                        have_size = true;
×
UNCOV
922
                        r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
×
923
                } else
924
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
925
                if (r < 0)
1,376✔
926
                        return table_log_add_error(r);
×
927

928
                if (i->metadata.tries_done != UINT64_MAX) {
1,376✔
929
                        have_tries = true;
128✔
930
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
128✔
931
                } else
932
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,248✔
933
                if (r < 0)
1,376✔
934
                        return table_log_add_error(r);
×
935

936
                if (i->metadata.tries_left != UINT64_MAX) {
1,376✔
937
                        have_tries = true;
128✔
938
                        r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
128✔
939
                } else
940
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,248✔
941
                if (r < 0)
1,376✔
UNCOV
942
                        return table_log_add_error(r);
×
943

944
                if (i->metadata.no_auto >= 0) {
1,376✔
UNCOV
945
                        bool b;
×
946

947
                        have_no_auto = true;
×
UNCOV
948
                        b = i->metadata.no_auto;
×
949
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
950
                } else
951
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
952
                if (r < 0)
1,376✔
UNCOV
953
                        return table_log_add_error(r);
×
954
                if (i->metadata.read_only >= 0) {
1,376✔
955
                        bool b;
288✔
956

957
                        have_read_only = true;
288✔
958
                        b = i->metadata.read_only;
288✔
959
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
288✔
960
                } else
961
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,088✔
962
                if (r < 0)
1,376✔
UNCOV
963
                        return table_log_add_error(r);
×
964

965
                if (i->metadata.growfs >= 0) {
1,376✔
UNCOV
966
                        bool b;
×
967

UNCOV
968
                        have_growfs = true;
×
UNCOV
969
                        b = i->metadata.growfs;
×
970
                        r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
×
971
                } else
972
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,376✔
973
                if (r < 0)
1,376✔
UNCOV
974
                        return table_log_add_error(r);
×
975

976
                if (i->metadata.sha256sum_set) {
1,376✔
977
                        _cleanup_free_ char *formatted = NULL;
32✔
978

979
                        have_sha256 = true;
32✔
980

981
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
32✔
982
                        if (!formatted)
32✔
UNCOV
983
                                return log_oom();
×
984

985
                        r = table_add_cell(t, NULL, TABLE_STRING, formatted);
32✔
986
                } else
987
                        r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
1,344✔
988
                if (r < 0)
1,376✔
UNCOV
989
                        return table_log_add_error(r);
×
990
        }
991

992
        /* Hide the fs/partition columns if we don't have any data to show there */
993
        if (!have_fs_attributes)
208✔
994
                show_fs_columns = false;
16✔
995
        if (!have_partition_attributes)
208✔
996
                show_partition_columns = false;
997

998
        if (!show_partition_columns)
144✔
999
                (void) table_hide_column_from_display(t, 2, 3);
64✔
1000
        if (!show_fs_columns)
208✔
1001
                (void) table_hide_column_from_display(t, 4, 5);
16✔
1002
        if (!have_size)
208✔
1003
                (void) table_hide_column_from_display(t, 6);
208✔
1004
        if (!have_tries)
208✔
1005
                (void) table_hide_column_from_display(t, 7, 8);
80✔
1006
        if (!have_no_auto)
208✔
1007
                (void) table_hide_column_from_display(t, 9);
208✔
1008
        if (!have_read_only)
208✔
1009
                (void) table_hide_column_from_display(t, 10);
64✔
1010
        if (!have_growfs)
208✔
1011
                (void) table_hide_column_from_display(t, 11);
208✔
1012
        if (!have_sha256)
208✔
1013
                (void) table_hide_column_from_display(t, 12);
192✔
1014

1015
        if (!sd_json_format_enabled(arg_json_format_flags)) {
208✔
1016
                printf("%s%s%s Version: %s\n"
1,008✔
1017
                       "    State: %s%s%s\n"
1018
                       "Installed: %s%s%s%s%s%s%s\n"
1019
                       "Available: %s%s\n"
1020
                       "Protected: %s%s%s\n"
1021
                       " Obsolete: %s%s%s\n",
1022
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
112✔
1023
                       strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
112✔
1024
                       yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
128✔
1025
                       FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
144✔
1026
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
208✔
1027
                       yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
208✔
1028
                       FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
112✔
1029
                       us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
112✔
1030

1031
                STRV_FOREACH(url, changelog_urls) {
112✔
UNCOV
1032
                        _cleanup_free_ char *changelog_link = NULL;
×
UNCOV
1033
                        r = terminal_urlify(*url, NULL, &changelog_link);
×
UNCOV
1034
                        if (r < 0)
×
UNCOV
1035
                                return log_oom();
×
UNCOV
1036
                        printf("ChangeLog: %s\n", changelog_link);
×
1037
                }
1038
                printf("\n");
112✔
1039

1040
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
112✔
1041
        } else {
1042
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *t_json = NULL;
96✔
1043

1044
                r = table_to_json(t, &t_json);
96✔
1045
                if (r < 0)
96✔
UNCOV
1046
                        return log_error_errno(r, "failed to convert table to JSON: %m");
×
1047

1048
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("version", us->version),
96✔
1049
                                          SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
1050
                                          SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
1051
                                          SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
1052
                                          SD_JSON_BUILD_PAIR_BOOLEAN("partial", FLAGS_SET(us->flags, UPDATE_PARTIAL)),
1053
                                          SD_JSON_BUILD_PAIR_BOOLEAN("pending", FLAGS_SET(us->flags, UPDATE_PENDING)),
1054
                                          SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
1055
                                          SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
1056
                                          SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
1057
                                          SD_JSON_BUILD_PAIR_STRV("changelogUrls", changelog_urls),
1058
                                          SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
1059
                if (r < 0)
96✔
UNCOV
1060
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1061

1062
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
96✔
1063
                if (r < 0)
96✔
UNCOV
1064
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1065

1066
                return 0;
1067
        }
1068
}
1069

1070
static int context_vacuum(
224✔
1071
                Context *c,
1072
                uint64_t space,
1073
                const char *extra_protected_version) {
1074

1075
        size_t disabled_count = 0;
224✔
1076
        int r, count = 0;
224✔
1077

1078
        assert(c);
224✔
1079

1080
        if (space == 0)
224✔
1081
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
64✔
1082
        else
1083
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
384✔
1084

1085
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
1,718✔
1086
                Transfer *t = *tr;
1,494✔
1087

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

1092
                r = transfer_vacuum(t, space, extra_protected_version);
1,286✔
1093
                if (r < 0)
1,286✔
1094
                        return r;
1095

1096
                count = MAX(count, r);
1,286✔
1097
        }
1098

1099
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
416✔
1100
                r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
192✔
1101
                if (r < 0)
192✔
1102
                        return r;
1103
                if (r > 0)
192✔
1104
                        disabled_count++;
16✔
1105
        }
1106

1107
        if (!sd_json_format_enabled(arg_json_format_flags)) {
224✔
1108
                if (count > 0 && disabled_count > 0)
172✔
UNCOV
1109
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
1110
                else if (count > 0)
172✔
1111
                        log_info("Removed %i instances.", count);
76✔
1112
                else if (disabled_count > 0)
96✔
1113
                        log_info("Removed %zu disabled transfers.", disabled_count);
16✔
1114
                else
1115
                        log_info("Found nothing to remove.");
80✔
1116
        } else {
1117
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
52✔
1118

1119
                r = sd_json_buildo(&json,
52✔
1120
                                   SD_JSON_BUILD_PAIR_INTEGER("removed", count),
1121
                                   SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
1122
                if (r < 0)
52✔
UNCOV
1123
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1124

1125
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
52✔
1126
                if (r < 0)
52✔
UNCOV
1127
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1128
        }
1129

1130
        return 0;
1131
}
1132

1133
typedef enum ProcessImageFlags {
1134
        PROCESS_IMAGE_READ_ONLY = 1 << 0,
1135
} ProcessImageFlags;
1136

1137
static int context_process_image(
1,299✔
1138
                Context *c,
1139
                ProcessImageFlags flags) {
1140

1141
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1,299✔
1142
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1,299✔
1143
        int r;
1,299✔
1144

1145
        assert(c);
1,299✔
1146

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

UNCOV
1150
        assert(!c->mounted_dir);
×
UNCOV
1151
        assert(!c->loop_device);
×
1152

1153
        r = mount_image_privately_interactively(
×
1154
                        c->image,
UNCOV
1155
                        c->image_policy,
×
1156
                        (FLAGS_SET(flags, PROCESS_IMAGE_READ_ONLY) ? DISSECT_IMAGE_READ_ONLY : 0) |
×
1157
                        DISSECT_IMAGE_FSCK |
1158
                        DISSECT_IMAGE_MKDIR |
1159
                        DISSECT_IMAGE_GROWFS |
1160
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1161
                        DISSECT_IMAGE_USR_NO_ROOT |
1162
                        DISSECT_IMAGE_GENERIC_ROOT |
1163
                        DISSECT_IMAGE_REQUIRE_ROOT |
1164
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
1165
                        &mounted_dir,
1166
                        /* ret_dir_fd= */ NULL,
1167
                        &loop_device);
UNCOV
1168
        if (r < 0)
×
1169
                return r;
1170

UNCOV
1171
        c->root = strdup(mounted_dir);
×
UNCOV
1172
        if (!c->root)
×
UNCOV
1173
                return log_oom();
×
1174

UNCOV
1175
        c->mounted_dir = TAKE_PTR(mounted_dir);
×
UNCOV
1176
        c->loop_device = TAKE_PTR(loop_device);
×
1177

UNCOV
1178
        return 0;
×
1179
}
1180

1181
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component);
1182

1183
static int context_load_offline(
1,299✔
1184
                Context *context,
1185
                ProcessImageFlags process_image_flags,
1186
                ReadDefinitionsFlags read_definitions_flags) {
1187
        int r;
1,299✔
1188

1189
        assert(context);
1,299✔
1190

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

1194
        r = context_process_image(context, process_image_flags);
1,299✔
1195
        if (r < 0)
1,299✔
1196
                return r;
1197

1198
        r = context_read_definitions(context, context->loop_device ? context->loop_device->node : NULL, read_definitions_flags);
1,299✔
1199
        if (r < 0)
1,299✔
1200
                return r;
1201

1202
        r = context_load_installed_instances(context);
1,299✔
1203
        if (r < 0)
1,299✔
UNCOV
1204
                return r;
×
1205

1206
        return 0;
1207
}
1208

1209
static int context_load_online(
1,051✔
1210
                Context *context,
1211
                ProcessImageFlags process_image_flags,
1212
                ReadDefinitionsFlags read_definitions_flags) {
1213

1214
        int r;
1,051✔
1215

1216
        assert(context);
1,051✔
1217

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

1221
        r = context_load_offline(
1,051✔
1222
                        context,
1223
                        process_image_flags,
1224
                        read_definitions_flags);
1225
        if (r < 0)
1,051✔
1226
                return r;
1227

1228
        if (!context->offline) {
1,051✔
1229
                r = context_load_available_instances(context);
839✔
1230
                if (r < 0)
839✔
1231
                        return r;
1232
        }
1233

1234
        r = context_discover_update_sets(context);
1,031✔
1235
        if (r < 0)
1,031✔
UNCOV
1236
                return r;
×
1237

1238
        return 0;
1239
}
1240

1241
static bool image_type_can_sysupdate(ImageType image_type) {
×
1242
        /* systemd-sysupdate doesn't support mstack images yet */
1243
        return IN_SET(image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME, IMAGE_RAW, IMAGE_BLOCK);
×
1244
}
1245

UNCOV
1246
static int context_load_paths_from_image(Context *context, Image *image) {
×
1247
        assert(context);
×
1248
        assert(image);
×
1249

UNCOV
1250
        assert(!context->root);
×
UNCOV
1251
        assert(!context->image);
×
1252

UNCOV
1253
        switch (image->type) {
×
UNCOV
1254
        case IMAGE_DIRECTORY:
×
1255
        case IMAGE_SUBVOLUME:
UNCOV
1256
                context->root = strdup(image->path);
×
UNCOV
1257
                if (!context->root)
×
UNCOV
1258
                        return log_oom();
×
1259
                return 0;
UNCOV
1260
        case IMAGE_RAW:
×
1261
        case IMAGE_BLOCK:
UNCOV
1262
                context->image = strdup(image->path);
×
UNCOV
1263
                if (!context->image)
×
UNCOV
1264
                        return log_oom();
×
1265
                return 0;
UNCOV
1266
        default:
×
UNCOV
1267
                assert_not_reached();
×
1268
        }
1269
}
1270

1271
/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been
1272
 * syntactically validated by dispatch_target_identifier(), but might still point to components which don’t
1273
 * exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier
1274
 * against an enumerated list of known targets, which are safe to update without additional permissions. */
1275
static int context_load_online_from_target(
192✔
1276
                Context *context,
1277
                ProcessImageFlags process_image_flags,
1278
                ReadDefinitionsFlags read_definitions_flags) {
1279
        int r;
192✔
1280

1281
        assert(context);
192✔
1282
        assert(context->target_identifier.class != _TARGET_CLASS_INVALID);
192✔
1283

1284
        /* These shouldn’t have been set up some other way first */
1285
        assert(!context->component);
192✔
1286
        assert(!context->root);
192✔
1287
        assert(!context->image);
192✔
1288

1289
        switch (context->target_identifier.class) {
192✔
1290
        case TARGET_MACHINE:
×
1291
        case TARGET_PORTABLE:
1292
        case TARGET_SYSEXT:
1293
        case TARGET_CONFEXT: {
1294
                _cleanup_hashmap_free_ Hashmap *images = NULL;
×
UNCOV
1295
                Image *image, *selected_image = NULL;
×
1296

1297
                /* These are all image-based target classes, so first find the corresponding image. */
UNCOV
1298
                r = image_discover(RUNTIME_SCOPE_SYSTEM, (ImageClass) context->target_identifier.class, NULL, &images);
×
UNCOV
1299
                if (r < 0)
×
1300
                        return r;
1301

1302
                HASHMAP_FOREACH(image, images) {
×
1303
                        bool have = false;
×
UNCOV
1304
                        _cleanup_(context_done) Context image_context = CONTEXT_NULL;
×
1305

1306
                        if (image_is_host(image))
×
1307
                                continue; /* We already enroll the host ourselves */
×
1308

1309
                        if (!image_type_can_sysupdate(image->type))
×
1310
                                continue;
×
1311

UNCOV
1312
                        if (!streq(image->name, context->target_identifier.name))
×
UNCOV
1313
                                continue;
×
1314

1315
                        r = context_load_paths_from_image(&image_context, image);
×
1316
                        if (r < 0)
×
1317
                                return r;
1318

1319
                        /* Load the components in a separate Context specific to the given Image before
1320
                         * committing to loading that state to the main Context. */
UNCOV
1321
                        r = context_load_offline(&image_context, 0, 0);
×
1322
                        if (r < 0)
×
1323
                                return r;
1324

UNCOV
1325
                        r = context_list_components(&image_context, /* ret_component_names= */ NULL, &have);
×
1326
                        if (r < 0)
×
1327
                                return r;
UNCOV
1328
                        if (!have) {
×
UNCOV
1329
                                log_debug("Skipping %s because it has no default component", image->path);
×
UNCOV
1330
                                continue;
×
1331
                        }
1332

1333
                        /* This is the match we were looking for */
UNCOV
1334
                        selected_image = image;
×
UNCOV
1335
                        break;
×
1336
                }
1337

UNCOV
1338
                if (!selected_image)
×
1339
                        return -ENOENT;
1340

1341
                r = context_load_paths_from_image(context, selected_image);
×
1342
                if (r < 0)
×
1343
                        return r;
1344

UNCOV
1345
                break;
×
1346
        }
1347
        case TARGET_HOST:
1348
                /* No additional setup needed */
1349
                break;
1350
        case TARGET_COMPONENT: {
8✔
1351
                _cleanup_strv_free_ char **component_names = NULL;
8✔
1352

1353
                r = context_list_components(context, &component_names, /* ret_has_default_component= */ NULL);
8✔
1354
                if (r < 0)
8✔
1355
                        return r;
1356

1357
                if (!strv_contains(component_names, context->target_identifier.name))
8✔
1358
                        return -ENOENT;
1359

UNCOV
1360
                context->component = strdup(context->target_identifier.name);
×
UNCOV
1361
                if (!context->component)
×
UNCOV
1362
                        return log_oom();
×
UNCOV
1363
                break;
×
1364
        }
UNCOV
1365
        default:
×
UNCOV
1366
                assert_not_reached();
×
1367
        }
1368

1369
        return context_load_online(context, process_image_flags, read_definitions_flags);
184✔
1370
}
1371

1372
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
825✔
1373
        const Context *c = ASSERT_PTR(t->context);
825✔
1374
        size_t i, n = c->n_transfers;
825✔
1375
        uint64_t base, scaled;
825✔
1376
        unsigned overall;
825✔
1377

1378
        for (i = 0; i < n; i++)
2,833✔
1379
                if (c->transfers[i] == t)
2,833✔
1380
                        break;
1381
        assert(i < n); /* We should have found the index */
825✔
1382

1383
        base = (100 * 100 * i) / n;
825✔
1384
        scaled = (100 * percentage) / n;
825✔
1385
        overall = (unsigned) ((base + scaled) / 100);
825✔
1386
        assert(overall <= 100);
825✔
1387

1388
        log_debug("Transfer %zu/%zu is %u%% complete (%u%% overall).", i+1, n, percentage, overall);
825✔
1389
        return sd_notifyf(/* unset_environment= */ false, "X_SYSUPDATE_PROGRESS=%u\n"
1,650✔
1390
                                              "X_SYSUPDATE_TRANSFERS_LEFT=%zu\n"
1391
                                              "X_SYSUPDATE_TRANSFERS_DONE=%zu\n"
1392
                                              "STATUS=Updating to '%s' (%u%% complete).",
1393
                                              overall, n - i, i, inst->metadata.version, overall);
825✔
1394
}
1395

1396
static int context_process_partial_and_pending(Context *c, const char *version);
1397

1398
static int context_acquire(
290✔
1399
                Context *c,
1400
                const char *version) {
1401

1402
        UpdateSet *us = NULL;
290✔
1403
        int r;
290✔
1404

1405
        assert(c);
290✔
1406

1407
        if (version) {
290✔
UNCOV
1408
                us = context_update_set_by_version(c, version);
×
UNCOV
1409
                if (!us)
×
1410
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
290✔
1411
        } else {
1412
                if (!c->candidate) {
290✔
1413
                        log_info("No update needed.");
50✔
1414

1415
                        return 0;
1416
                }
1417

1418
                us = c->candidate;
1419
        }
1420

1421
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
240✔
1422
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
32✔
1423
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
208✔
1424
                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✔
1425
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
192✔
1426
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
32✔
1427

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

1432
                return 0;
1433
        }
1434

1435
        if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
192✔
UNCOV
1436
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, refusing.", us->version);
×
1437
        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
192✔
UNCOV
1438
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
×
1439

1440
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
192✔
UNCOV
1441
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1442
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
192✔
UNCOV
1443
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1444

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

1447
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
384✔
1448
        if (!metadata)
192✔
UNCOV
1449
                return log_oom();
×
1450

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

1457
                assert(inst);
1,270✔
1458

1459
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
1,270✔
1460
                if (r < 0)
1,270✔
1461
                        return r;
1462
        }
1463

1464
        (void) sd_notifyf(/* unset_environment= */ false,
192✔
1465
                          "READY=1\n"
1466
                          "X_SYSUPDATE_VERSION=%s\n"
1467
                          "STATUS=Making room for '%s'.", us->version, us->version);
1468

1469
        /* Let's make some room. We make sure for each transfer we have one free space to fill. While
1470
         * removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
1471
         * download succeeded already, in which case we shouldn't remove it just to acquire it again */
1472
        r = context_vacuum(
384✔
1473
                        c,
1474
                        /* space= */ 1,
1475
                        /* extra_protected_version= */ us->version);
192✔
1476
        if (r < 0)
192✔
1477
                return r;
1478

1479
        if (c->sync)
192✔
1480
                sync();
192✔
1481

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

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

1488
        for (size_t i = 0; i < c->n_transfers; i++) {
1,366✔
1489
                Instance *inst = us->instances[i];
1,190✔
1490
                Transfer *t = c->transfers[i];
1,190✔
1491

1492
                assert(inst); /* ditto */
1,190✔
1493

1494
                if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
1,190✔
1495
                        assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
208✔
1496
                        continue;
208✔
1497
                }
1498

1499
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
982✔
1500
                if (r < 0)
982✔
1501
                        return r;
1502
        }
1503

1504
        if (c->sync)
176✔
1505
                sync();
176✔
1506

1507
        return 1;
1508
}
1509

1510
/* Check to see if we have an update set acquired and pending installation. */
1511
static int context_process_partial_and_pending(
116✔
1512
                Context *c,
1513
                const char *version) {
1514

1515
        UpdateSet *us = NULL;
116✔
1516
        int r;
116✔
1517

1518
        assert(c);
116✔
1519

1520
        if (version) {
116✔
UNCOV
1521
                us = context_update_set_by_version(c, version);
×
UNCOV
1522
                if (!us)
×
UNCOV
1523
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1524
        } else {
1525
                if (!c->candidate) {
116✔
1526
                        log_info("No update needed.");
×
1527

1528
                        return 0;
1529
                }
1530

1531
                us = c->candidate;
1532
        }
1533

1534
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
116✔
UNCOV
1535
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
×
1536
        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
116✔
UNCOV
1537
                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
×
1538

1539
                return 0;
1540
        }
1541

1542
        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
116✔
1543
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
4✔
1544
        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
112✔
UNCOV
1545
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
×
1546

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

1550
        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
112✔
UNCOV
1551
                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
×
1552
        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
112✔
UNCOV
1553
                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
×
1554

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

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

1560
        for (size_t i = 0; i < c->n_transfers; i++) {
908✔
1561
                Instance *inst = us->instances[i];
796✔
1562
                Transfer *t = c->transfers[i];
796✔
1563

1564
                assert(inst);
796✔
1565

1566
                r = transfer_process_partial_and_pending_instance(t, inst);
796✔
1567
                if (r < 0)
796✔
1568
                        return r;
1569
        }
1570

1571
        return 1;
1572
}
1573

1574
static int notify_subscribers_reply(
177✔
1575
                sd_varlink *link,
1576
                sd_json_variant *reply,
1577
                const char *error_id,
1578
                sd_varlink_reply_flags_t flags,
1579
                void *userdata) {
1580

1581
        assert(link);
177✔
1582

1583
        if (error_id)
177✔
UNCOV
1584
                log_warning("Notification subscriber '%s' returned error, ignoring: %s",
×
1585
                            strna(sd_varlink_get_description(link)), error_id);
1586

1587
        return 0;
177✔
1588
}
1589

1590
static int context_notify_subscribers(Context *c, UpdateSet *us) {
176✔
1591
        int r;
176✔
1592

1593
        assert(c);
176✔
1594

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

1598
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *resources = NULL;
176✔
1599
        if (us)
176✔
1600
                for (size_t i = 0; i < c->n_transfers; i++) {
1,334✔
1601
                        Instance *inst = us->instances[i];
1,158✔
1602
                        Transfer *t = c->transfers[i];
1,158✔
1603

1604
                        if (inst->resource == &t->target &&
1,158✔
1605
                            !inst->is_pending)
920✔
1606
                                continue;
208✔
1607

1608
                        /* Report where the resource was installed *to* (not the source it came from): the
1609
                         * final on-disk path for filesystem targets, the partition device node for partition
1610
                         * targets. */
1611
                        const char *target_path =
256✔
1612
                                RESOURCE_IS_FILESYSTEM(t->target.type) ? t->final_path :
950✔
1613
                                t->target.type == RESOURCE_PARTITION ? t->partition_info.device :
1614
                                NULL;
1615

1616
                        r = sd_json_variant_append_arraybo(
950✔
1617
                                        &resources,
1618
                                        SD_JSON_BUILD_PAIR_STRING("transfer", t->id),
1619
                                        SD_JSON_BUILD_PAIR_CONDITION(!!target_path, "path", SD_JSON_BUILD_STRING(target_path)));
1620
                        if (r < 0)
950✔
UNCOV
1621
                                return log_warning_errno(r, "Failed to build sysupdate notify resources list, skipping notification: %m");
×
1622
                }
1623

1624
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL;
176✔
1625
        r = sd_json_buildo(
176✔
1626
                        &params,
1627
                        SD_JSON_BUILD_PAIR_CONDITION(!!c->component, "component", SD_JSON_BUILD_STRING(c->component)),
1628
                        SD_JSON_BUILD_PAIR_CONDITION(!!us, "version", SD_JSON_BUILD_STRING(us ? us->version : NULL)),
1629
                        SD_JSON_BUILD_PAIR_CONDITION(!!resources, "resources", SD_JSON_BUILD_VARIANT(resources)));
1630
        if (r < 0)
176✔
UNCOV
1631
                return log_warning_errno(r, "Failed to build sysupdate notify parameters, skipping notification: %m");
×
1632

1633
        ssize_t n = varlink_execute_directory(
176✔
1634
                        VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK,
1635
                        "io.systemd.SysUpdate.Notify.OnCompletedUpdate",
1636
                        params,
1637
                        /* more= */ false,
1638
                        /* timeout_usec= */ 5 * USEC_PER_MINUTE,
1639
                        notify_subscribers_reply,
1640
                        /* userdata= */ NULL);
1641
        if (n < 0)
176✔
1642
                log_debug_errno(n, "Failed to dispatch sysupdate notification to %s, ignoring: %m",
176✔
1643
                                VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
1644
        else if (n > 0)
176✔
1645
                log_debug("Dispatched sysupdate notification to %zi subscribers in %s.", n, VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
176✔
1646

1647
        return 0;
1648
}
1649

1650
static int context_install(
176✔
1651
                Context *c,
1652
                const char *version,
1653
                UpdateSet **ret_applied) {
1654

1655
        UpdateSet *us = NULL;
176✔
1656
        int r;
176✔
1657

1658
        assert(c);
176✔
1659

1660
        if (version) {
176✔
UNCOV
1661
                us = context_update_set_by_version(c, version);
×
UNCOV
1662
                if (!us)
×
UNCOV
1663
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
×
1664
        } else {
1665
                if (!c->candidate) {
176✔
UNCOV
1666
                        log_info("No update needed.");
×
1667

1668
                        return 0;
1669
                }
1670

1671
                us = c->candidate;
1672
        }
1673

1674
        (void) sd_notifyf(/* unset_environment=*/ false,
176✔
1675
                          "READY=1\n"
1676
                          "X_SYSUPDATE_VERSION=%s\n"
1677
                          "STATUS=Installing '%s'.", us->version, us->version);
1678

1679
        for (size_t i = 0; i < c->n_transfers; i++) {
1,510✔
1680
                Instance *inst = us->instances[i];
1,158✔
1681
                Transfer *t = c->transfers[i];
1,158✔
1682

1683
                if (inst->resource == &t->target &&
1,158✔
1684
                    !inst->is_pending)
920✔
1685
                        continue;
208✔
1686

1687
                r = transfer_install_instance(t, inst, c->root);
950✔
1688
                if (r < 0)
950✔
1689
                        return r;
1690
        }
1691

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

1694
        if (!c->root)
176✔
1695
                (void) context_notify_subscribers(c, us);
176✔
1696

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

1700
        if (ret_applied)
176✔
1701
                *ret_applied = us;
176✔
1702

1703
        return 1;
1704
}
1705

1706
static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string);
200✔
1707

1708
static int dispatch_target_identifier(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
200✔
1709
        TargetIdentifier *t = ASSERT_PTR(userdata);
200✔
1710
        static const sd_json_dispatch_field dispatch[] = {
200✔
1711
                { "class", SD_JSON_VARIANT_STRING, dispatch_target_class,   voffsetof(*t, class), SD_JSON_MANDATORY },
1712
                { "name",  SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(*t, name),  SD_JSON_NULLABLE  },
1713
                {}
1714
        };
1715
        int r;
200✔
1716

1717
        r = sd_json_dispatch(variant, dispatch, flags, t);
200✔
1718
        if (r < 0)
200✔
1719
                return r;
1720

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

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

1728
        return 0;
1729
}
1730

1731
static int verify_polkit(Context *context, sd_varlink *link, const char *action, const char **details) {
192✔
1732
        int r;
192✔
1733
        Server *s = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link)));
192✔
1734

1735
        assert(context);
192✔
1736

1737
        if (!s->system_bus) {
192✔
1738
                r = sd_bus_open_system_with_description(&s->system_bus, "sysupdate-system");
192✔
1739
                if (r < 0)
192✔
UNCOV
1740
                        return log_error_errno(r, "Failed to get system bus connection: %m");
×
1741

1742
                r = sd_bus_attach_event(s->system_bus, sd_varlink_get_event(link), SD_EVENT_PRIORITY_NORMAL);
192✔
1743
                if (r < 0)
192✔
UNCOV
1744
                        return log_error_errno(r, "Failed to attach system bus to event loop: %m");
×
1745
        }
1746

1747
        return varlink_verify_polkit_async(link,
192✔
1748
                        s->system_bus,
1749
                        action,
1750
                        details,
1751
                        &s->polkit_registry);
1752
}
1753

1754
VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT,
1755
     "Show installed and available versions");
1756
static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
256✔
1757
        _cleanup_(context_done) Context context = CONTEXT_NULL;
256✔
1758
        _cleanup_strv_free_ char **appstream_urls = NULL;
256✔
1759
        const char *version;
256✔
1760
        int r;
256✔
1761

1762
        assert(argc <= 2);
256✔
1763
        version = argc >= 2 ? argv[1] : NULL;
256✔
1764

1765
        r = context_from_cmdline(&context);
256✔
1766
        if (r < 0)
256✔
1767
                return r;
1768

1769
        if (context.feature_select != SELECT_EXPLICIT)
256✔
UNCOV
1770
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
1771
        if (context.component_select != SELECT_EXPLICIT)
256✔
UNCOV
1772
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
×
1773

1774
        r = context_load_online(
256✔
1775
                        &context,
1776
                        PROCESS_IMAGE_READ_ONLY,
1777
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
1778
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1779
        if (r < 0)
256✔
1780
                return r;
1781

1782
        if (version)
256✔
1783
                return context_show_version(&context, version);
208✔
1784
        else if (!sd_json_format_enabled(arg_json_format_flags))
48✔
UNCOV
1785
                return context_show_table(&context);
×
1786
        else {
1787
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
48✔
1788
                _cleanup_strv_free_ char **versions = NULL;
48✔
1789
                const char *current = NULL;
48✔
1790
                bool current_is_pending = false;
48✔
1791

1792
                FOREACH_ARRAY(update_set, context.update_sets, context.n_update_sets) {
224✔
1793
                        UpdateSet *us = *update_set;
176✔
1794

1795
                        if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
176✔
1796
                            FLAGS_SET(us->flags, UPDATE_NEWEST)) {
160✔
1797
                                current = us->version;
48✔
1798
                                current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
48✔
1799
                        }
1800

1801
                        r = strv_extend(&versions, us->version);
176✔
1802
                        if (r < 0)
176✔
UNCOV
1803
                                return log_oom();
×
1804
                }
1805

1806
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers)
288✔
1807
                        STRV_FOREACH(appstream_url, (*tr)->appstream) {
240✔
1808
                                /* Avoid duplicates */
UNCOV
1809
                                if (strv_contains(appstream_urls, *appstream_url))
×
UNCOV
1810
                                        continue;
×
1811

UNCOV
1812
                                r = strv_extend(&appstream_urls, *appstream_url);
×
UNCOV
1813
                                if (r < 0)
×
UNCOV
1814
                                        return log_oom();
×
1815
                        }
1816

1817
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current),
96✔
1818
                                          SD_JSON_BUILD_PAIR_STRV("all", versions),
1819
                                          SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
1820
                if (r < 0)
48✔
UNCOV
1821
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1822

1823
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
48✔
1824
                if (r < 0)
48✔
UNCOV
1825
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1826

1827
                return 0;
1828
        }
1829
}
1830

1831
VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0,
1832
     "Show optional features");
1833
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
1834
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
1835
        _cleanup_(table_unrefp) Table *table = NULL;
32✔
1836
        const char *feature_id;
32✔
1837
        Feature *f;
32✔
1838
        int r;
32✔
1839

1840
        assert(argc <= 2);
32✔
1841
        feature_id = argc >= 2 ? argv[1] : NULL;
32✔
1842

1843
        r = context_from_cmdline(&context);
32✔
1844
        if (r < 0)
32✔
1845
                return r;
1846

1847
        if (context.feature_select != SELECT_EXPLICIT)
32✔
UNCOV
1848
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
1849
        if (context.component_select != SELECT_EXPLICIT)
32✔
UNCOV
1850
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
×
1851

1852
        r = context_load_offline(
32✔
1853
                        &context,
1854
                        PROCESS_IMAGE_READ_ONLY,
1855
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1856
        if (r < 0)
32✔
1857
                return r;
1858

1859
        if (feature_id) {
32✔
1860
                _cleanup_strv_free_ char **transfers = NULL;
16✔
1861

1862
                f = hashmap_get(context.features, feature_id);
16✔
1863
                if (!f)
16✔
1864
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
1865
                                               "Optional feature not found: %s",
1866
                                               feature_id);
1867

1868
                table = table_new_vertical();
16✔
1869
                if (!table)
16✔
UNCOV
1870
                        return log_oom();
×
1871

1872
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers) {
128✔
1873
                        Transfer *t = *tr;
112✔
1874

1875
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
112✔
1876
                                continue;
112✔
1877

UNCOV
1878
                        r = strv_extend(&transfers, t->id);
×
UNCOV
1879
                        if (r < 0)
×
UNCOV
1880
                                return log_oom();
×
1881
                }
1882

1883
                FOREACH_ARRAY(tr, context.disabled_transfers, context.n_disabled_transfers) {
32✔
1884
                        Transfer *t = *tr;
16✔
1885

1886
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
16✔
UNCOV
1887
                                continue;
×
1888

1889
                        r = strv_extend(&transfers, t->id);
16✔
1890
                        if (r < 0)
16✔
1891
                                return log_oom();
×
1892
                }
1893

1894
                r = table_add_many(table,
16✔
1895
                                   TABLE_FIELD, "Name",
1896
                                   TABLE_STRING, f->id,
1897
                                   TABLE_FIELD, "Enabled",
1898
                                   TABLE_BOOLEAN, f->enabled);
1899
                if (r < 0)
16✔
1900
                        return table_log_add_error(r);
×
1901

1902
                r = feature_is_suggested(f);
16✔
1903
                if (r < 0) {
16✔
UNCOV
1904
                        errno = -r; /* Let's make %m below show this error */
×
UNCOV
1905
                        _cleanup_free_ char *k = asprintf_safe("error (%m)");
×
1906
                        r = table_add_many(table,
×
1907
                                           TABLE_FIELD, "Suggested",
1908
                                           TABLE_STRING, k);
1909
                } else
1910
                        r = table_add_many(table,
16✔
1911
                                           TABLE_FIELD, "Suggested",
1912
                                           TABLE_BOOLEAN, r);
1913
                if (r < 0)
16✔
UNCOV
1914
                        return table_log_add_error(r);
×
1915

1916
                if (f->description) {
16✔
1917
                        r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
16✔
1918
                        if (r < 0)
16✔
UNCOV
1919
                                return table_log_add_error(r);
×
1920
                }
1921

1922
                if (f->documentation) {
16✔
UNCOV
1923
                        r = table_add_many(table,
×
1924
                                           TABLE_FIELD, "Documentation",
1925
                                           TABLE_STRING, f->documentation,
1926
                                           TABLE_SET_URL, f->documentation);
UNCOV
1927
                        if (r < 0)
×
UNCOV
1928
                                return table_log_add_error(r);
×
1929
                }
1930

1931
                if (f->appstream) {
16✔
1932
                        r = table_add_many(table,
×
1933
                                           TABLE_FIELD, "AppStream",
1934
                                           TABLE_STRING, f->appstream,
1935
                                           TABLE_SET_URL, f->appstream);
UNCOV
1936
                        if (r < 0)
×
UNCOV
1937
                                return table_log_add_error(r);
×
1938
                }
1939

1940
                if (!strv_isempty(transfers)) {
16✔
1941
                        r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
16✔
1942
                        if (r < 0)
16✔
1943
                                return table_log_add_error(r);
×
1944
                }
1945

1946
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
16✔
1947
        } else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
16✔
1948
                table = table_new("enabled", "suggested", "feature", "description", "documentation");
16✔
1949
                if (!table)
16✔
1950
                        return log_oom();
×
1951

1952
                table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
16✔
1953

1954
                HASHMAP_FOREACH(f, context.features) {
32✔
1955
                        r = table_add_many(table,
16✔
1956
                                           TABLE_BOOLEAN_CHECKMARK, f->enabled,
1957
                                           TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled));
1958
                        if (r < 0)
16✔
UNCOV
1959
                                return table_log_add_error(r);
×
1960

1961
                        r = feature_is_suggested(f);
16✔
1962
                        if (r < 0)
16✔
1963
                                r = table_add_many(table, TABLE_EMPTY);
×
1964
                        else
1965
                                r = table_add_many(table, TABLE_BOOLEAN_CHECKMARK, r);
16✔
1966
                        if (r < 0)
16✔
1967
                                return table_log_add_error(r);
×
1968

1969
                        r = table_add_many(table,
16✔
1970
                                           TABLE_STRING, f->id,
1971
                                           TABLE_STRING, f->description,
1972
                                           TABLE_STRING, f->documentation,
1973
                                           TABLE_SET_URL, f->documentation);
1974
                        if (r < 0)
16✔
UNCOV
1975
                                return table_log_add_error(r);
×
1976
                }
1977

1978
                return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
16✔
1979
        } else {
UNCOV
1980
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1981
                _cleanup_strv_free_ char **features = NULL;
×
1982

1983
                HASHMAP_FOREACH(f, context.features) {
×
UNCOV
1984
                        r = strv_extend(&features, f->id);
×
1985
                        if (r < 0)
×
1986
                                return log_oom();
×
1987
                }
1988

UNCOV
1989
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
×
1990
                if (r < 0)
×
1991
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1992

1993
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
×
UNCOV
1994
                if (r < 0)
×
UNCOV
1995
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1996
        }
1997

UNCOV
1998
        return 0;
×
1999
}
2000

2001
static int make_dropin_dir(Context *c, char **ret) {
×
2002
        _cleanup_free_ char *dir = NULL;
×
2003

UNCOV
2004
        assert(c);
×
2005
        assert(ret);
×
2006

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

2011
        if (c->definitions)
×
UNCOV
2012
                dir = strdup(c->definitions); /* --root= is not supported for this for now */
×
UNCOV
2013
        else if (c->component) {
×
2014
                _cleanup_free_ char *n = strjoin("sysupdate.", c->component, ".d");
×
2015
                if (!n)
×
2016
                        return log_oom();
×
2017

UNCOV
2018
                dir = path_join(c->root, SYSCONF_DIR, n);
×
2019
        } else
UNCOV
2020
                dir = path_join(c->root, SYSCONF_DIR "/sysupdate.d");
×
UNCOV
2021
        if (!dir)
×
UNCOV
2022
                return log_oom();
×
2023

UNCOV
2024
        *ret = TAKE_PTR(dir);
×
UNCOV
2025
        return 0;
×
2026
}
2027

2028
static const char *context_component_display(const Context *c) {
×
2029
        assert(c);
×
2030

2031
        return c->component ?: "<default>";
×
2032
}
2033

UNCOV
2034
static int context_enable_feature(
×
2035
                Context *c,
2036
                const char *argv0,
2037
                bool enable,
2038
                char **features) {
2039

UNCOV
2040
        int r;
×
2041

UNCOV
2042
        assert(c);
×
UNCOV
2043
        assert(argv0);
×
2044

UNCOV
2045
        _cleanup_free_ char **l = NULL;
×
UNCOV
2046
        switch (c->feature_select) {
×
2047

2048
        case SELECT_EXPLICIT:
2049
                break;
2050

UNCOV
2051
        case SELECT_ALL: {
×
UNCOV
2052
                assert(!features);
×
2053

UNCOV
2054
                Feature *f;
×
UNCOV
2055
                HASHMAP_FOREACH(f, c->features)
×
UNCOV
2056
                        if (strv_push(&l, f->id) < 0)
×
UNCOV
2057
                                return log_oom();
×
2058

UNCOV
2059
                features = l;
×
UNCOV
2060
                break;
×
2061
        }
2062

UNCOV
2063
        case SELECT_SUGGESTED: {
×
UNCOV
2064
                assert(!features);
×
2065

UNCOV
2066
                Feature *f;
×
UNCOV
2067
                HASHMAP_FOREACH(f, c->features) {
×
UNCOV
2068
                        r = feature_is_suggested(f);
×
UNCOV
2069
                        if (r < 0)
×
UNCOV
2070
                                return log_error_errno(r, "Failed to determine if feature '%s' of component '%s' shall be enabled: %m", f->id, context_component_display(c));
×
2071
                        if (!!r != !!enable) {
×
UNCOV
2072
                                log_debug("Skipping feature '%s' of component '%s'.", f->id, context_component_display(c));
×
UNCOV
2073
                                continue;
×
2074
                        }
2075

UNCOV
2076
                        log_info("%s feature '%s' of component '%s'.", enable ? "Enabling" : "Disabling", f->id, context_component_display(c));
×
2077

UNCOV
2078
                        if (strv_push(&l, f->id) < 0)
×
2079
                                return log_oom();
×
2080
                }
2081

UNCOV
2082
                features = l;
×
UNCOV
2083
                break;
×
2084
        }
2085

UNCOV
2086
        default:
×
UNCOV
2087
                assert_not_reached();
×
2088
        }
2089

UNCOV
2090
        if (strv_isempty(features)) {
×
UNCOV
2091
                log_debug("No features selected.");
×
2092
                return 0;
2093
        }
2094

UNCOV
2095
        _cleanup_free_ char *dropin_dir = NULL;
×
UNCOV
2096
        r = make_dropin_dir(c, &dropin_dir);
×
UNCOV
2097
        if (r < 0)
×
2098
                return r;
2099

2100
        int ret = 0;
UNCOV
2101
        STRV_FOREACH(name, features) {
×
UNCOV
2102
                if (!hashmap_contains(c->features, *name)) {
×
UNCOV
2103
                        RET_GATHER(ret, log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Optional feature not found in component '%s': %s", context_component_display(c), *name));
×
UNCOV
2104
                        continue;
×
2105
                }
2106

UNCOV
2107
                _cleanup_free_ char *fname = strjoin(*name, ".feature");
×
UNCOV
2108
                if (!fname)
×
UNCOV
2109
                        return log_oom();
×
2110

2111
                /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
UNCOV
2112
                r = write_drop_in_format(
×
2113
                                dropin_dir,
2114
                                fname,
2115
                                50,
2116
                                "systemd-sysupdate-enabled",
2117
                                "# Generated via 'systemd-sysupdate %s'\n\n"
2118
                                "[Feature]\n"
2119
                                "Enabled=%s\n",
2120
                                argv0,
2121
                                yes_no(enable));
UNCOV
2122
                if (r < 0) {
×
UNCOV
2123
                        RET_GATHER(ret, log_error_errno(r, "Failed to write drop-in for feature '%s': %m", *name));
×
UNCOV
2124
                        continue;
×
2125
                }
2126

UNCOV
2127
                log_info("Feature '%s' %s.", *name, enabled_disabled(enable));
×
2128
        }
2129

2130
        return ret;
2131
}
2132

2133
VERB(verb_enable_feature, "enable-feature", "FEATURE…", 1, VERB_ANY, 0,
2134
     "Enable optional feature");
2135
VERB(verb_enable_feature, "disable-feature", "FEATURE…", 1, VERB_ANY, 0,
2136
     "Disable optional feature");
UNCOV
2137
static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
UNCOV
2138
        bool enable = streq(argv[0], "enable-feature");
×
UNCOV
2139
        int r;
×
2140

2141
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
UNCOV
2142
        r = context_from_cmdline(&context);
×
UNCOV
2143
        if (r < 0)
×
2144
                return r;
2145

UNCOV
2146
        if (!IN_SET(context.component_select, SELECT_EXPLICIT, SELECT_ALL))
×
UNCOV
2147
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--component-suggested is not supported for '%s'.", argv[0]);
×
UNCOV
2148
        if (context.definitions)
×
UNCOV
2149
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2150
                                       "The --definitions= switch may not be combined with '%s'.", argv[0]);
2151

UNCOV
2152
        char **features;
×
UNCOV
2153
        if (argc > 1 && context.feature_select == SELECT_EXPLICIT) {
×
UNCOV
2154
                features = strv_skip(argv, 1);
×
2155

UNCOV
2156
                STRV_FOREACH(name, features)
×
UNCOV
2157
                        if (!feature_name_valid(*name))
×
2158
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Feature name invalid: %s", *name);
×
2159

UNCOV
2160
        } else if (argc <= 1 && context.feature_select != SELECT_EXPLICIT)
×
2161
                features = NULL;
2162
        else
UNCOV
2163
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either specify features as positional parameter or via --feature-all/--feature-suggested, not both.");
×
2164

2165
        r = context_load_offline(
×
2166
                        &context,
2167
                        /* process_image_flags= */ 0,
2168
                        /* read_definitions_flags= */ 0);
UNCOV
2169
        if (r < 0)
×
2170
                return r;
2171

UNCOV
2172
        switch (context.component_select) {
×
2173

UNCOV
2174
        case SELECT_EXPLICIT:
×
UNCOV
2175
                return context_enable_feature(&context, argv[0], enable, features);
×
2176

UNCOV
2177
        case SELECT_ALL: {
×
UNCOV
2178
                _cleanup_strv_free_ char **component_names = NULL;
×
UNCOV
2179
                bool has_default_component;
×
UNCOV
2180
                r = context_list_components(&context, &component_names, &has_default_component);
×
UNCOV
2181
                if (r < 0)
×
2182
                        return r;
2183

UNCOV
2184
                if (strv_isempty(component_names) && !has_default_component) {
×
UNCOV
2185
                        log_debug("No components selected.");
×
2186
                        return 0;
2187
                }
2188

UNCOV
2189
                int ret = 0;
×
UNCOV
2190
                if (has_default_component)
×
UNCOV
2191
                        RET_GATHER(ret, context_enable_feature(&context, argv[0], enable, features));
×
2192

UNCOV
2193
                STRV_FOREACH(name, component_names) {
×
UNCOV
2194
                        _cleanup_(context_done) Context cc = CONTEXT_NULL;
×
2195

UNCOV
2196
                        r = context_from_base_with_component(&context, *name, &cc);
×
UNCOV
2197
                        if (r < 0) {
×
UNCOV
2198
                                RET_GATHER(ret, r);
×
UNCOV
2199
                                continue;
×
2200
                        }
2201

UNCOV
2202
                        r = context_load_offline(
×
2203
                                        &cc,
2204
                                        /* process_image_flags= */ 0,
2205
                                        /* read_definitions_flags= */ 0);
UNCOV
2206
                        if (r < 0) {
×
UNCOV
2207
                                RET_GATHER(ret, r);
×
2208
                                continue;
×
2209
                        }
2210

UNCOV
2211
                        RET_GATHER(ret, context_enable_feature(&cc, argv[0], enable, features));
×
2212
                }
2213

2214
                return ret;
2215
        }
2216

UNCOV
2217
        default:
×
UNCOV
2218
                assert_not_reached();
×
2219
        }
2220
}
2221

2222
VERB_NOARG(verb_check_new, "check-new",
2223
           "Check if there's a new version available");
2224
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
218✔
2225
        int r;
218✔
2226

2227
        assert(argc <= 1);
218✔
2228

2229
        _cleanup_(context_done) Context context = CONTEXT_NULL;
218✔
2230
        r = context_from_cmdline(&context);
218✔
2231
        if (r < 0)
218✔
2232
                return r;
2233

2234
        if (context.feature_select != SELECT_EXPLICIT)
218✔
UNCOV
2235
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
2236
        if (context.component_select != SELECT_EXPLICIT)
218✔
UNCOV
2237
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
×
2238

2239
        r = context_load_online(
218✔
2240
                        &context,
2241
                        PROCESS_IMAGE_READ_ONLY,
2242
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2243
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2244
        if (r < 0)
218✔
2245
                return r;
2246

2247
        if (!sd_json_format_enabled(arg_json_format_flags)) {
217✔
2248
                if (!context.candidate) {
185✔
2249
                        log_debug("No candidate found.");
96✔
2250
                        return EXIT_FAILURE;
2251
                }
2252

2253
                puts(context.candidate->version);
89✔
2254
        } else {
2255
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
32✔
2256

2257
                if (context.candidate)
32✔
UNCOV
2258
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
×
2259
                else
2260
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
32✔
2261
                if (r < 0)
32✔
UNCOV
2262
                        return log_error_errno(r, "Failed to create JSON: %m");
×
2263

2264
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
32✔
2265
                if (r < 0)
32✔
UNCOV
2266
                        return log_error_errno(r, "Failed to print JSON: %m");
×
2267
        }
2268

2269
        return EXIT_SUCCESS;
2270
}
2271

2272
static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
200✔
2273
        _cleanup_(context_done) Context context = CONTEXT_NULL;
200✔
2274
        int r;
200✔
2275

2276
        assert(link);
200✔
2277

2278
        static const sd_json_dispatch_field dispatch_table[] = {
200✔
2279
                { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY },
2280
                VARLINK_DISPATCH_POLKIT_FIELD,
2281
                {},
2282
        };
2283

2284
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &context);
200✔
2285
        if (r != 0)
200✔
2286
                return r;
2287

2288
        r = verify_polkit(&context, link, "org.freedesktop.sysupdate1.check",
576✔
2289
                        (const char**) STRV_MAKE(
376✔
2290
                                        "class", target_class_to_string(context.target_identifier.class),
2291
                                        "offline", "0",
2292
                                        context.target_identifier.name ? "name" : NULL, context.target_identifier.name));
2293
        if (r <= 0)
192✔
2294
                return r;
2295

2296
        if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
192✔
2297
                context.verify = 0;
192✔
2298

2299
        /* CheckNew is always online */
2300
        context.offline = false;
192✔
2301

2302
        r = context_load_online_from_target(
192✔
2303
                        &context,
2304
                        PROCESS_IMAGE_READ_ONLY,
2305
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2306
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2307
        if (r == -ENOENT)
192✔
2308
                return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
8✔
2309
        if (r < 0)
184✔
2310
                return r;
2311

2312
        if (context.candidate)
184✔
2313
                r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
88✔
2314
        else
2315
                r = sd_varlink_error(link, "io.systemd.SysUpdate.NoUpdateNeeded", NULL);
96✔
2316
        if (r < 0)
184✔
2317
                return r;
96✔
2318

2319
        return 0;
2320
}
2321

2322
typedef enum {
2323
        UPDATE_ACTION_ACQUIRE = 1 << 0,
2324
        UPDATE_ACTION_INSTALL = 1 << 1,
2325
} UpdateActionFlags;
2326

2327
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
394✔
2328
        _cleanup_free_ char *booted_version = NULL;
394✔
2329
        UpdateSet *applied = NULL;
394✔
2330
        const char *version;
394✔
2331
        int r;
394✔
2332

2333
        assert(argc <= 2);
394✔
2334
        version = argc >= 2 ? argv[1] : NULL;
394✔
2335

2336
        _cleanup_(context_done) Context context = CONTEXT_NULL;
394✔
2337
        r = context_from_cmdline(&context);
394✔
2338
        if (r < 0)
394✔
2339
                return r;
2340

2341
        if (context.feature_select != SELECT_EXPLICIT)
394✔
UNCOV
2342
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
2343
        if (context.component_select != SELECT_EXPLICIT)
394✔
2344
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
1✔
2345

2346
        if (context.instances_max < 2)
393✔
2347
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2348
                                      "The --instances-max= argument must be >= 2 while updating");
2349

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

UNCOV
2353
                r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
×
UNCOV
2354
                if (r < 0)
×
UNCOV
2355
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
UNCOV
2356
                if (!booted_version)
×
UNCOV
2357
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
2358
        }
2359

2360
        bool installed = false;
393✔
2361
        int ret = 0;
393✔
2362

2363
        r = context_load_online(
393✔
2364
                        &context,
2365
                        /* process_image_flags= */ 0,
2366
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2367
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
2368
                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
2369
        if (r < 0) {
393✔
2370
                if (r != -ENOENT)
19✔
2371
                        return r;
2372

2373
                /* No transfer files found. In that case, still do the installdb cleanup below */
2374
                RET_GATHER(ret, r);
2375
        } else {
2376
                if (action_flags & UPDATE_ACTION_ACQUIRE)
374✔
2377
                        r = context_acquire(&context, version);
290✔
2378
                else
2379
                        r = context_process_partial_and_pending(&context, version);
84✔
2380
                if (r < 0)
374✔
2381
                        return r;
2382

2383
                if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */
338✔
2384
                        r = context_install(&context, version, &applied);
176✔
2385
                        if (r < 0)
176✔
2386
                                return r;
2387

2388
                        installed = r > 0;
176✔
2389
                }
2390

2391
                /* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If
2392
                 * nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a
2393
                 * resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */
2394
                if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) {
226✔
2395
                        int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY");
50✔
2396
                        if (f < 0 && f != -ENXIO)
50✔
UNCOV
2397
                                log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m");
×
2398
                        if (f > 0)
50✔
2399
                                (void) context_notify_subscribers(&context, /* us= */ NULL);
×
2400
                }
2401
        }
2402

2403
        if (context.cleanup > 0)
338✔
2404
                RET_GATHER(ret, installdb_cleanup_component(&context));
2✔
2405

2406
        if (installed) {
338✔
2407
                /* We installed something, yay */
2408

2409
                if (context.reboot) {
176✔
UNCOV
2410
                        assert(applied);
×
UNCOV
2411
                        assert(booted_version);
×
2412

UNCOV
2413
                        if (strverscmp_improved(applied->version, booted_version) > 0) {
×
UNCOV
2414
                                log_notice("Newly installed version is newer than booted version, rebooting.");
×
UNCOV
2415
                                RET_GATHER(ret, reboot_now());
×
UNCOV
2416
                        } else if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
UNCOV
2417
                                   FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
UNCOV
2418
                                log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
UNCOV
2419
                                RET_GATHER(ret, reboot_now());
×
2420
                        } else
UNCOV
2421
                                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
2422
                }
2423
        }
2424

2425
        return ret;
2426
}
2427

2428
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
2429
     "Install new version now");
2430
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
258✔
2431
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
258✔
2432

2433
        if (!arg_offline)
258✔
2434
                flags |= UPDATE_ACTION_ACQUIRE;
174✔
2435

2436
        return verb_update_impl(argc, argv, flags);
258✔
2437
}
2438

2439
VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
2440
     "Acquire (download) new version now");
2441
static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
136✔
2442
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
136✔
2443
}
2444

2445
VERB_NOARG(verb_vacuum, "vacuum",
2446
           "Make room, by deleting old versions");
2447
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
2448
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
2449
        int r;
32✔
2450

2451
        assert(argc <= 1);
32✔
2452

2453
        r = context_from_cmdline(&context);
32✔
2454
        if (r < 0)
32✔
2455
                return r;
2456

2457
        if (context.feature_select != SELECT_EXPLICIT)
32✔
UNCOV
2458
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
2459
        if (context.component_select != SELECT_EXPLICIT)
32✔
UNCOV
2460
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
×
2461

2462
        if (context.instances_max < 1)
32✔
UNCOV
2463
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2464
                                      "The --instances-max= argument must be >= 1 while vacuuming");
2465

2466
        r = context_load_offline(
32✔
2467
                        &context,
2468
                        /* process_image_flags= */ 0,
2469
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2470
        if (r < 0)
32✔
2471
                return r;
2472

2473
        return context_vacuum(&context, 0, NULL);
32✔
2474
}
2475

2476
VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files");
2477
static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) {
7✔
2478
        _cleanup_(context_done) Context context = CONTEXT_NULL;
7✔
2479
        int r;
7✔
2480

2481
        assert(argc <= 1);
7✔
2482

2483
        r = context_from_cmdline(&context);
7✔
2484
        if (r < 0)
7✔
2485
                return r;
2486

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

2490
        if (context.feature_select != SELECT_EXPLICIT)
6✔
2491
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
2492
        if (!IN_SET(context.component_select, SELECT_EXPLICIT, SELECT_ALL))
6✔
2493
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-suggested currently not supported for '%s'.", argv[0]);
×
2494

2495
        r = context_load_offline(
6✔
2496
                        &context,
2497
                        /* process_image_flags= */ 0,
2498
                        /* read_definitions_flags= */ 0);
2499
        if (r < 0)
6✔
2500
                return r;
2501

2502
        int ret = 0;
6✔
2503
        RET_GATHER(ret, installdb_cleanup_component(&context));
6✔
2504

2505
        if (context.component_select == SELECT_ALL) {
6✔
2506
                _cleanup_strv_free_ char **z = NULL;
2✔
2507
                r = installdb_list_components(&context, &z);
2✔
2508
                if (r < 0)
2✔
2509
                        return log_error_errno(r, "Failed to enumerate components: %m");
×
2510

2511
                STRV_FOREACH(i, z) {
6✔
UNCOV
2512
                        _cleanup_(context_done) Context component_context = CONTEXT_NULL;
×
2513

2514
                        r = context_from_cmdline(&component_context);
4✔
2515
                        if (r < 0)
4✔
2516
                                return r;
2517

2518
                        /* Override the component with our iter. This needs to be done in a fresh Context
2519
                         * as the installdb_fd and other state are specific to the component. */
2520
                        r = free_and_strdup_warn(&component_context.component, *i);
4✔
2521
                        if (r < 0)
4✔
2522
                                return r;
2523

2524
                        r = context_load_offline(
4✔
2525
                                        &component_context,
2526
                                        /* process_image_flags= */ 0,
2527
                                        /* read_definitions_flags= */ 0);
2528
                        if (r < 0)
4✔
2529
                                return r;
2530

2531
                        RET_GATHER(ret, installdb_cleanup_component(&component_context));
4✔
2532
                }
2533
        }
2534

2535
        return ret;
2536
}
2537

2538
VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
2539
     "Report whether a newer version is installed than currently booted");
2540
VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
2541
     "Reboot if a newer version is installed than booted");
2542
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
2✔
UNCOV
2543
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2544
        _cleanup_free_ char *booted_version = NULL;
2✔
2545
        int r;
2✔
2546

2547
        assert(argc == 1);
2✔
2548

2549
        r = context_from_cmdline(&context);
2✔
2550
        if (r < 0)
2✔
2551
                return r;
2552

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

2557
        if (context.feature_select != SELECT_EXPLICIT)
2✔
2558
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
2559
        if (context.component || context.component_select != SELECT_EXPLICIT)
2✔
2560
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2✔
2561
                                       "The --component=, --component-all and --component-suggested switches may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]);
2562

UNCOV
2563
        r = context_load_offline(
×
2564
                        &context,
2565
                        /* process_image_flags= */ 0,
2566
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2567
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
2568
                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
2569
        if (r < 0)
×
2570
                return r;
2571

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

UNCOV
2574
        r = context_discover_update_sets_by_flag(&context, UPDATE_INSTALLED);
×
UNCOV
2575
        if (r < 0)
×
2576
                return r;
2577
        if (!context.newest_installed)
×
2578
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
2579

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

2587
        r = strverscmp_improved(context.newest_installed->version, booted_version);
×
2588
        if (r > 0) {
×
2589
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
2590
                           context.newest_installed->version, booted_version,
2591
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
2592

UNCOV
2593
                if (streq(argv[0], "reboot"))
×
UNCOV
2594
                        return reboot_now();
×
2595

2596
                return EXIT_SUCCESS;
UNCOV
2597
        } else if (r == 0)
×
UNCOV
2598
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
2599
                         context.newest_installed->version, booted_version);
2600
        else
2601
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
2602
                            context.newest_installed->version, booted_version);
2603

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

2607
        return EXIT_SUCCESS;
2608
}
2609

2610
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component) {
182✔
2611
        int r;
182✔
2612

2613
        assert(context);
182✔
2614

2615
        _cleanup_strv_free_ char **z = NULL;
182✔
2616
        r = get_component_list(context->root, &z);
182✔
2617
        if (r < 0)
182✔
2618
                return r;
2619

2620
        if (ret_component_names)
182✔
2621
                *ret_component_names = TAKE_PTR(z);
182✔
2622

2623
        /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a
2624
         * TARGET_HOST? See target_get_argument() in sysupdated.c */
2625
        if (ret_has_default_component)
182✔
2626
                *ret_has_default_component =
348✔
2627
                        !context->definitions &&
348✔
2628
                        !context->component &&
174✔
2629
                        !context->root &&
174✔
2630
                        !context->image &&
348✔
2631
                        context->n_transfers > 0;
176✔
2632

2633
        return 0;
2634
}
2635

2636
static int context_component_is_suggested(Context *c) {
×
2637
        assert(c);
×
2638

2639
        /* Only applies to components, not to the main system */
2640
        if (!c->component)
×
2641
                return -ENOTTY;
2642

UNCOV
2643
        if (c->component_suggest >= 0)
×
2644
                return c->component_suggest;
2645

UNCOV
2646
        if (!c->component_suggest_on) /* no condition → false */
×
2647
                return false;
2648

2649
        return condition_test_list(c->component_suggest_on, environ, suggest_on_type_to_string, /* logger= */ NULL, /* userdata= */ NULL);
×
2650
}
2651

2652
VERB_NOARG(verb_components, "components",
2653
           "Show list of components");
2654
static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
174✔
2655
        int r;
174✔
2656

2657
        assert(argc <= 1);
174✔
2658

2659
        _cleanup_(context_done) Context context = CONTEXT_NULL;
174✔
2660
        r = context_from_cmdline(&context);
174✔
2661
        if (r < 0)
174✔
2662
                return r;
2663

2664
        if (context.feature_select != SELECT_EXPLICIT)
174✔
2665
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
2666
        if (context.component_select != SELECT_EXPLICIT)
174✔
UNCOV
2667
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all/--component-suggested currently not supported for '%s'.", argv[0]);
×
2668
        if (context.definitions)
174✔
UNCOV
2669
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2670
                                       "The --definitions= switch may not be combined with '%s'.", argv[0]);
2671

2672
        r = context_load_offline(
174✔
2673
                        &context,
2674
                        /* process_image_flags= */ 0,
2675
                        /* read_definitions_flags= */ 0);
2676
        if (r < 0)
174✔
2677
                return r;
2678

2679
        _cleanup_strv_free_ char **component_names = NULL;
174✔
2680
        bool has_default_component = false;
174✔
2681
        r = context_list_components(&context, &component_names, &has_default_component);
174✔
2682
        if (r < 0)
174✔
UNCOV
2683
                return log_error_errno(r, "Failed to enumerate components: %m");
×
2684

2685
        if (!sd_json_format_enabled(arg_json_format_flags)) {
174✔
UNCOV
2686
                if (!has_default_component && strv_isempty(component_names)) {
×
UNCOV
2687
                        log_info("No components defined.");
×
UNCOV
2688
                        return 0;
×
2689
                }
2690

UNCOV
2691
                _cleanup_(table_unrefp) Table *t = table_new("enabled", "suggested", "component", "description", "documentation");
×
UNCOV
2692
                if (!t)
×
UNCOV
2693
                        return log_oom();
×
2694

UNCOV
2695
                table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
×
2696

UNCOV
2697
                if (has_default_component) {
×
UNCOV
2698
                        r = table_add_many(
×
2699
                                        t,
2700
                                        TABLE_EMPTY,
2701
                                        TABLE_EMPTY,
2702
                                        TABLE_STRING, "<default>",
2703
                                        TABLE_SET_COLOR, ansi_highlight(),
2704
                                        TABLE_STRING, "Default Component",
2705
                                        TABLE_EMPTY);
UNCOV
2706
                        if (r < 0)
×
UNCOV
2707
                                return table_log_add_error(r);
×
2708
                }
2709

UNCOV
2710
                STRV_FOREACH(i, component_names) {
×
2711
                        _cleanup_(context_done) Context cc = CONTEXT_NULL;
×
2712

UNCOV
2713
                        r = context_from_base_with_component(
×
2714
                                        &context,
2715
                                        *i,
2716
                                        &cc);
UNCOV
2717
                        if (r < 0)
×
2718
                                return r;
2719

UNCOV
2720
                        r = context_load_offline(
×
2721
                                        &cc,
2722
                                        /* process_image_flags= */ 0,
2723
                                        /* read_definitions_flags= */ 0);
UNCOV
2724
                        if (r == -ENOMEM)
×
2725
                                return r;
2726
                        if (r < 0)
×
UNCOV
2727
                                continue;
×
2728

UNCOV
2729
                        r = table_add_many(
×
2730
                                        t,
2731
                                        TABLE_BOOLEAN_CHECKMARK, cc.component_enabled,
2732
                                        TABLE_SET_COLOR, ansi_highlight_green_red(cc.component_enabled));
2733
                        if (r < 0)
×
2734
                                return table_log_add_error(r);
×
2735

UNCOV
2736
                        r = context_component_is_suggested(&cc);
×
UNCOV
2737
                        if (r < 0)
×
UNCOV
2738
                                r = table_add_many(t, TABLE_EMPTY);
×
2739
                        else
UNCOV
2740
                                r = table_add_many(t, TABLE_BOOLEAN_CHECKMARK, r);
×
2741
                        if (r < 0)
×
2742
                                return table_log_add_error(r);
×
2743

UNCOV
2744
                        const char *doc = cc.component_documentation ? cc.component_documentation[0] : NULL;
×
2745

UNCOV
2746
                        r = table_add_many(
×
2747
                                        t,
2748
                                        TABLE_STRING, *i,
2749
                                        TABLE_STRING, cc.component_description,
2750
                                        TABLE_STRING, doc,
2751
                                        TABLE_SET_URL, doc);
2752
                        if (r < 0)
×
UNCOV
2753
                                return table_log_add_error(r);
×
2754
                }
2755

UNCOV
2756
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
2757
        } else {
UNCOV
2758
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
2759

2760
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
174✔
2761
                                          SD_JSON_BUILD_PAIR_STRV("components", component_names));
2762
                if (r < 0)
174✔
UNCOV
2763
                        return log_error_errno(r, "Failed to create JSON: %m");
×
2764

2765
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
174✔
2766
                if (r < 0)
174✔
UNCOV
2767
                        return log_error_errno(r, "Failed to print JSON: %m");
×
2768
        }
2769

2770
        return 0;
174✔
2771
}
2772

2773
VERB(verb_enable_component, "enable-component", "COMPONENT…", 1, VERB_ANY, 0,
2774
     "Enable component");
2775
VERB(verb_enable_component, "disable-component", "COMPONENT…", 1, VERB_ANY, 0,
2776
     "Disable component");
UNCOV
2777
static int verb_enable_component(int argc, char *argv[], uintptr_t _data, void *userdata) {
×
UNCOV
2778
        bool enable = streq(argv[0], "enable-component");
×
2779
        int r;
×
2780

2781
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
UNCOV
2782
        r = context_from_cmdline(&context);
×
2783
        if (r < 0)
×
2784
                return r;
2785

UNCOV
2786
        if (context.feature_select != SELECT_EXPLICIT)
×
UNCOV
2787
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
×
UNCOV
2788
        if (context.definitions)
×
UNCOV
2789
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2790
                                       "The --definitions= switch may not be combined with '%s'.", argv[0]);
2791

UNCOV
2792
        char **arguments, *array[2];
×
UNCOV
2793
        if (argc > 1) {
×
UNCOV
2794
                if (context.component || context.component_select != SELECT_EXPLICIT)
×
UNCOV
2795
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either specify component names as positional parameter or via --component=/--component-all/--component-suggested, not both.");
×
2796

UNCOV
2797
                arguments = strv_skip(argv, 1);
×
2798

2799
        } else if (context.component_select == SELECT_EXPLICIT) {
×
2800
                if (!context.component)
×
UNCOV
2801
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No component specified.");
×
2802

2803
                array[0] = context.component;
×
UNCOV
2804
                array[1] = NULL;
×
UNCOV
2805
                arguments = array;
×
2806
        } else {
UNCOV
2807
                assert(!context.component);
×
2808
                arguments = NULL;
2809
        }
2810

UNCOV
2811
        STRV_FOREACH(name, arguments)
×
UNCOV
2812
                if (!component_name_valid(*name))
×
UNCOV
2813
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", *name);
×
2814

UNCOV
2815
        r = context_load_offline(
×
2816
                        &context,
2817
                        /* process_image_flags= */ 0,
2818
                        /* read_definitions_flags= */ 0);
UNCOV
2819
        if (r < 0)
×
2820
                return r;
2821

UNCOV
2822
        _cleanup_strv_free_ char **component_names = NULL;
×
UNCOV
2823
        r = context_list_components(&context, &component_names, /* ret_has_default_component= */ NULL);
×
UNCOV
2824
        if (r < 0)
×
2825
                return r;
2826

UNCOV
2827
        _cleanup_strv_free_ char **suggested = NULL;
×
2828

UNCOV
2829
        switch (context.component_select) {
×
2830

2831
        case SELECT_EXPLICIT:
UNCOV
2832
                STRV_FOREACH(name, arguments)
×
UNCOV
2833
                        if (!strv_contains(component_names, *name))
×
UNCOV
2834
                                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Component not found: %s", *name);
×
2835
                break;
2836

UNCOV
2837
        case SELECT_ALL:
×
UNCOV
2838
                assert(!arguments);
×
UNCOV
2839
                arguments = component_names;
×
UNCOV
2840
                break;
×
2841

UNCOV
2842
        case SELECT_SUGGESTED:
×
UNCOV
2843
                assert(!arguments);
×
UNCOV
2844
                STRV_FOREACH(name, component_names) {
×
2845
                        _cleanup_(context_done) Context cc = CONTEXT_NULL;
×
2846

UNCOV
2847
                        r = context_from_base_with_component(&context, *name, &cc);
×
UNCOV
2848
                        if (r < 0)
×
2849
                                return r;
2850

UNCOV
2851
                        r = context_load_offline(
×
2852
                                        &cc,
2853
                                        /* process_image_flags= */ 0,
2854
                                        /* read_definitions_flags= */ 0);
UNCOV
2855
                        if (r < 0)
×
UNCOV
2856
                                continue;
×
2857

UNCOV
2858
                        r = context_component_is_suggested(&cc);
×
UNCOV
2859
                        if (r < 0) {
×
UNCOV
2860
                                log_warning_errno(r, "Failed to determine whether '%s' shall be enabled, skipping: %m", *name);
×
UNCOV
2861
                                continue;
×
2862
                        }
2863

2864
                        /* This reconciles the system with the suggestions: on 'enable-component' we act on
2865
                         * the components that are suggested, on 'disable-component' we act on the ones that
2866
                         * are not. Hence pick the components whose suggestion state matches the operation. */
UNCOV
2867
                        if (!!r != !!enable) {
×
UNCOV
2868
                                log_debug("Skipping '%s'.", *name);
×
UNCOV
2869
                                continue;
×
2870
                        }
2871

UNCOV
2872
                        log_info("%s '%s'.", enable ? "Enabling" : "Disabling", *name);
×
2873

UNCOV
2874
                        if (strv_extend(&suggested, *name) < 0)
×
UNCOV
2875
                                return log_oom();
×
2876
                }
2877

UNCOV
2878
                arguments = suggested;
×
UNCOV
2879
                break;
×
2880

UNCOV
2881
        default:
×
UNCOV
2882
                assert_not_reached();
×
2883
        }
2884

UNCOV
2885
        if (strv_isempty(arguments)) {
×
UNCOV
2886
                log_info("No components selected.");
×
2887
                return 0;
2888
        }
2889

2890
        /* Component definition files live directly below the configuration directories, hence the drop-in
2891
         * goes right next to them below /etc. */
UNCOV
2892
        _cleanup_free_ char *dropin_dir = path_join(context.root, SYSCONF_DIR);
×
UNCOV
2893
        if (!dropin_dir)
×
UNCOV
2894
                return log_oom();
×
2895

UNCOV
2896
        STRV_FOREACH(name, arguments) {
×
UNCOV
2897
                _cleanup_free_ char *fname = strjoin("sysupdate.", *name, ".component");
×
UNCOV
2898
                if (!fname)
×
UNCOV
2899
                        return log_oom();
×
2900

2901
                /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
UNCOV
2902
                r = write_drop_in_format(
×
2903
                                dropin_dir,
2904
                                fname,
2905
                                50, "systemd-sysupdate-enabled",
2906
                                "# Generated via 'systemd-sysupdate %s'\n\n"
2907
                                "[Component]\n"
2908
                                "Enabled=%s\n",
2909
                                argv[0],
2910
                                yes_no(enable));
UNCOV
2911
                if (r < 0)
×
UNCOV
2912
                        return log_error_errno(r, "Failed to write drop-in for component '%s': %m", *name);
×
2913

UNCOV
2914
                log_info("Component '%s' %s.", *name, enable ? "enabled" : "disabled");
×
2915
        }
2916

2917
        return 0;
2918
}
2919

UNCOV
2920
static int help(void) {
×
UNCOV
2921
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
UNCOV
2922
        int r;
×
2923

UNCOV
2924
        r = verbs_get_help_table(&verbs);
×
UNCOV
2925
        if (r < 0)
×
2926
                return r;
2927

UNCOV
2928
        r = option_parser_get_help_table(&common_options);
×
UNCOV
2929
        if (r < 0)
×
2930
                return r;
2931

UNCOV
2932
        r = option_parser_get_help_table_group("Options", &options);
×
UNCOV
2933
        if (r < 0)
×
2934
                return r;
2935

UNCOV
2936
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
2937

UNCOV
2938
        help_cmdline("[OPTIONS…] [VERSION]");
×
UNCOV
2939
        help_abstract("Update OS images.");
×
2940

UNCOV
2941
        help_section("Commands");
×
UNCOV
2942
        r = table_print_or_warn(verbs);
×
UNCOV
2943
        if (r < 0)
×
2944
                return r;
2945

UNCOV
2946
        r = table_print_or_warn(common_options);
×
UNCOV
2947
        if (r < 0)
×
2948
                return r;
2949

UNCOV
2950
        help_section("Options");
×
UNCOV
2951
        r = table_print_or_warn(options);
×
UNCOV
2952
        if (r < 0)
×
2953
                return r;
2954

UNCOV
2955
        help_man_page_reference("systemd-sysupdate", "8");
×
2956
        return 0;
2957
}
2958

UNCOV
2959
VERB_COMMON_HELP_HIDDEN(help);
×
2960

2961
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
1,336✔
2962
        assert(argc >= 0);
1,336✔
2963
        assert(argv);
1,336✔
2964
        assert(remaining_args);
1,336✔
2965

2966
        OptionParser opts = { argc, argv };
1,336✔
2967
        int r;
1,336✔
2968

2969
        FOREACH_OPTION_OR_RETURN(c, &opts)
4,152✔
2970
                switch (c) {
1,488✔
2971

UNCOV
2972
                OPTION_COMMON_HELP:
×
UNCOV
2973
                        return help();
×
2974

UNCOV
2975
                OPTION_COMMON_VERSION:
×
UNCOV
2976
                        return version();
×
2977

2978
                OPTION_GROUP("Options"):
2979
                        break;
2980

2981
                OPTION('C', "component", "NAME",
45✔
2982
                       "Select component to update"):
2983
                        if (isempty(opts.arg)) {
45✔
UNCOV
2984
                                arg_component = mfree(arg_component);
×
UNCOV
2985
                                arg_component_select = SELECT_EXPLICIT;
×
UNCOV
2986
                                break;
×
2987
                        }
2988

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

2992
                        r = free_and_strdup_warn(&arg_component, opts.arg);
37✔
2993
                        if (r < 0)
37✔
2994
                                return r;
2995

2996
                        arg_component_select = SELECT_EXPLICIT;
37✔
2997
                        break;
37✔
2998

2999
                OPTION('A', "component-all", NULL, "Select all components"):
3✔
3000
                        arg_component = mfree(arg_component);
3✔
3001
                        arg_component_select = SELECT_ALL;
3✔
3002
                        break;
3✔
3003

UNCOV
3004
                OPTION('S', "component-suggested", NULL, "Select all suggested components"):
×
UNCOV
3005
                        arg_component = mfree(arg_component);
×
UNCOV
3006
                        arg_component_select = SELECT_SUGGESTED;
×
UNCOV
3007
                        break;
×
3008

UNCOV
3009
                OPTION('a', "feature-all", NULL, "Select all features"):
×
UNCOV
3010
                        arg_feature_select = SELECT_ALL;
×
UNCOV
3011
                        break;
×
3012

UNCOV
3013
                OPTION('s', "feature-suggested", NULL, "Select all suggested features"):
×
UNCOV
3014
                        arg_feature_select = SELECT_SUGGESTED;
×
UNCOV
3015
                        break;
×
3016

3017
                OPTION_LONG("definitions", "DIR",
5✔
3018
                            "Find transfer definitions in specified directory"):
3019
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions);
5✔
3020
                        if (r < 0)
5✔
3021
                                return r;
3022
                        break;
3023

UNCOV
3024
                OPTION_LONG("root", "PATH",
×
3025
                            "Operate on an alternate filesystem root"):
UNCOV
3026
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
UNCOV
3027
                        if (r < 0)
×
3028
                                return r;
3029
                        break;
3030

UNCOV
3031
                OPTION_LONG("image", "PATH",
×
3032
                            "Operate on disk image as filesystem root"):
UNCOV
3033
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
UNCOV
3034
                        if (r < 0)
×
3035
                                return r;
3036
                        break;
3037

UNCOV
3038
                OPTION_LONG("image-policy", "POLICY",
×
3039
                            "Specify disk image dissection policy"):
UNCOV
3040
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
UNCOV
3041
                        if (r < 0)
×
3042
                                return r;
3043
                        break;
3044

UNCOV
3045
                OPTION_LONG("transfer-source", "PATH",
×
3046
                            "Specify the directory to transfer sources from"):
UNCOV
3047
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_transfer_source);
×
UNCOV
3048
                        if (r < 0)
×
3049
                                return r;
3050

3051
                        break;
3052

UNCOV
3053
                OPTION('m', "instances-max", "INT",
×
3054
                       "How many instances to maintain"):
UNCOV
3055
                        r = safe_atou64(opts.arg, &arg_instances_max);
×
UNCOV
3056
                        if (r < 0)
×
UNCOV
3057
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", opts.arg);
×
3058

3059
                        break;
3060

UNCOV
3061
                OPTION_LONG("sync", "BOOL",
×
3062
                            "Controls whether to sync data to disk"):
UNCOV
3063
                        r = parse_boolean_argument("--sync=", opts.arg, &arg_sync);
×
UNCOV
3064
                        if (r < 0)
×
3065
                                return r;
3066
                        break;
3067

3068
                OPTION_LONG("verify", "BOOL",
744✔
3069
                            "Force signature verification on or off"): {
3070
                        bool b;
744✔
3071

3072
                        r = parse_boolean_argument("--verify=", opts.arg, &b);
744✔
3073
                        if (r < 0)
744✔
UNCOV
3074
                                return r;
×
3075

3076
                        arg_verify = b;
744✔
3077
                        break;
744✔
3078
                }
3079

3080
                OPTION_LONG("reboot", NULL,
1✔
3081
                            "Reboot after updating to newer version"):
3082
                        arg_reboot = true;
1✔
3083
                        break;
1✔
3084

3085
                OPTION_LONG("offline", NULL,
228✔
3086
                            "Do not fetch metadata from the network"):
3087
                        arg_offline = true;
228✔
3088
                        break;
228✔
3089

3090
                OPTION_LONG("cleanup", "BOOL", "Clean up orphaned files after completing update"): {
4✔
3091
                        bool b;
4✔
3092

3093
                        r = parse_boolean_argument("--cleanup=", opts.arg, &b);
4✔
3094
                        if (r < 0)
4✔
UNCOV
3095
                                return r;
×
3096

3097
                        arg_cleanup = b;
4✔
3098
                        break;
4✔
3099
                }
3100

UNCOV
3101
                OPTION_COMMON_NO_PAGER:
×
UNCOV
3102
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
3103
                        break;
×
3104

UNCOV
3105
                OPTION_COMMON_NO_LEGEND:
×
UNCOV
3106
                        arg_legend = false;
×
UNCOV
3107
                        break;
×
3108

3109
                OPTION_COMMON_JSON:
458✔
3110
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
458✔
3111
                        if (r <= 0)
458✔
3112
                                return r;
3113

3114
                        break;
3115
                }
3116

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

3120
        if (arg_image || arg_root) {
1,328✔
UNCOV
3121
                if (arg_reboot)
×
UNCOV
3122
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
3123

UNCOV
3124
                if (arg_definitions)
×
UNCOV
3125
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= switch may not be combined with --root= or --image=.");
×
3126
        }
3127

3128
        if ((arg_component || arg_component_select != SELECT_EXPLICIT)) {
1,328✔
3129
                if (arg_reboot)
40✔
3130
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --component=/--component-all/--component-suggested, as automatic reboots only apply to the booted OS version.");
1✔
3131

3132
                if (arg_definitions)
39✔
UNCOV
3133
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component=/--component-all/--component-suggested switches may not be combined.");
×
3134
        }
3135

3136
        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
1,327✔
3137
        if (r < 0)
1,327✔
UNCOV
3138
                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
×
3139
        if (r > 0)
1,327✔
3140
                arg_varlink = true;
212✔
3141

3142
        *remaining_args = option_parser_get_args(&opts);
1,327✔
3143
        return 1;
1,327✔
3144
}
3145

3146
static int vl_server(void) {
212✔
3147
        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
212✔
3148
        _cleanup_(server_done) Server server = SERVER_NULL;
212✔
3149
        int r;
212✔
3150

3151
        r = varlink_server_new(&varlink_server,
212✔
3152
                               SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
3153
                               &server);
3154
        if (r < 0)
212✔
UNCOV
3155
                return log_error_errno(r, "Failed to allocate Varlink server: %m");
×
3156

3157
        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysUpdate);
212✔
3158
        if (r < 0)
212✔
UNCOV
3159
                return log_error_errno(r, "Failed to add Varlink interface: %m");
×
3160

3161
        r = sd_varlink_server_bind_method_many(
212✔
3162
                        varlink_server,
3163
                        "io.systemd.SysUpdate.CheckNew", vl_method_check_new);
3164
        if (r < 0)
212✔
UNCOV
3165
                return log_error_errno(r, "Failed to bind Varlink method: %m");
×
3166

3167
        r = sd_varlink_server_loop_auto(varlink_server);
212✔
3168
        if (r < 0)
212✔
UNCOV
3169
                return log_error_errno(r, "Failed to run Varlink event loop: %m");
×
3170

3171
        return 0;
3172
}
3173

3174
static int run(int argc, char *argv[]) {
1,336✔
3175
        int r;
1,336✔
3176

3177
        LIBAPPARMOR_NOTE(recommended);
1,336✔
3178
        LIBAUDIT_NOTE(recommended);
1,336✔
3179
        LIBBLKID_NOTE(recommended);
1,336✔
3180
        LIBCRYPTO_NOTE(suggested);
1,336✔
3181
        LIBCRYPTSETUP_NOTE(suggested);
1,336✔
3182
        LIBMOUNT_NOTE(recommended);
1,336✔
3183
        LIBSELINUX_NOTE(recommended);
1,336✔
3184
        LIBTSS2_ESYS_NOTE(suggested);
1,336✔
3185
        LIBTSS2_MU_NOTE(suggested);
1,336✔
3186
        LIBTSS2_RC_NOTE(suggested);
1,336✔
3187

3188
        log_setup();
1,336✔
3189

3190
        char **args = NULL;
1,336✔
3191
        r = parse_argv(argc, argv, &args);
1,336✔
3192
        if (r <= 0)
1,336✔
3193
                return r;
1,336✔
3194

3195
        if (arg_varlink)
1,327✔
3196
                return vl_server(); /* Invocation as Varlink service */
212✔
3197

3198
        return dispatch_verb(args, NULL);
1,115✔
3199
}
3200

3201
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