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

systemd / systemd / 29060049768

09 Jul 2026 08:09PM UTC coverage: 72.702% (-0.1%) from 72.832%
29060049768

push

github

web-flow
Translations update from Fedora Weblate (#42956)

Translations update from [Fedora
Weblate](https://translate.fedoraproject.org) for
[systemd/main](https://translate.fedoraproject.org/projects/systemd/main/).



Current translation status:

![Weblate translation
status](https://translate.fedoraproject.org/widget/systemd/main/matrix-auto.svg)

342955 of 471728 relevant lines covered (72.7%)

1410039.18 hits per line

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

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

3
#include <unistd.h>
4

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

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

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

71
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
1,328✔
72
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
1,328✔
73
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
1,328✔
74
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
1,328✔
75
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
1,328✔
76
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
1,328✔
77

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

89
void context_done(Context *c) {
2,422✔
90
        assert(c);
2,422✔
91

92
        c->mounted_dir = umount_and_rmdir_and_free(c->mounted_dir);
2,422✔
93
        c->loop_device = loop_device_unref(c->loop_device);
2,422✔
94

95
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers)
11,202✔
96
                transfer_free(*tr);
8,780✔
97
        c->transfers = mfree(c->transfers);
2,422✔
98
        c->n_transfers = 0;
2,422✔
99

100
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
3,554✔
101
                transfer_free(*tr);
1,132✔
102
        c->disabled_transfers = mfree(c->disabled_transfers);
2,422✔
103
        c->n_disabled_transfers = 0;
2,422✔
104

105
        c->features = hashmap_free(c->features);
2,422✔
106

107
        FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
7,105✔
108
                update_set_free(*us);
4,683✔
109
        c->update_sets = mfree(c->update_sets);
2,422✔
110
        c->n_update_sets = 0;
2,422✔
111

112
        c->web_cache = hashmap_free(c->web_cache);
2,422✔
113

114
        c->installdb_fd = safe_close(c->installdb_fd);
2,422✔
115

116
        c->definitions = mfree(c->definitions);
2,422✔
117
        c->root = mfree(c->root);
2,422✔
118
        c->image = mfree(c->image);
2,422✔
119
        c->component = mfree(c->component);
2,422✔
120
        c->component_description = mfree(c->component_description);
2,422✔
121
        c->component_documentation = strv_free(c->component_documentation);
2,422✔
122
        c->image_policy = image_policy_free(c->image_policy);
2,422✔
123
        c->transfer_source = mfree(c->transfer_source);
2,422✔
124

125
        target_identifier_done(&c->target_identifier);
2,422✔
126
}
2,422✔
127

128
static int context_from_cmdline(Context *ret) {
1,111✔
129
        assert(ret);
1,111✔
130

131
        _cleanup_(context_done) Context context = CONTEXT_NULL;
1,111✔
132

133
        context.instances_max = arg_instances_max;
1,111✔
134
        context.sync = arg_sync;
1,111✔
135
        context.reboot = arg_reboot;
1,111✔
136
        context.verify = arg_verify;
1,111✔
137
        context.offline = arg_offline;
1,111✔
138
        context.cleanup = arg_cleanup;
1,111✔
139
        context.component_all = arg_component_all;
1,111✔
140

141
        if (strdup_to(&context.definitions, arg_definitions) < 0)
1,111✔
142
                return log_oom();
×
143

144
        if (strdup_to(&context.root, arg_root) < 0)
1,111✔
145
                return log_oom();
×
146

147
        if (strdup_to(&context.image, arg_image) < 0)
1,111✔
148
                return log_oom();
×
149

150
        if (strdup_to(&context.component, arg_component) < 0)
1,111✔
151
                return log_oom();
×
152

153
        if (strdup_to(&context.transfer_source, arg_transfer_source) < 0)
1,111✔
154
                return log_oom();
×
155

156
        if (arg_image_policy) {
1,111✔
157
                context.image_policy = image_policy_copy(arg_image_policy);
×
158
                if (!context.image_policy)
×
159
                        return log_oom();
×
160
        }
161

162
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
1,111✔
163
        return 0;
1,111✔
164
}
165

166
static int context_from_base_with_component(const Context *base, const char *component, Context *ret) {
×
167
        assert(base);
×
168
        assert(component);
×
169
        assert(ret);
×
170

171
        /* Copies the specified context, but changes the component to the specified one */
172

173
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
174

175
        context.instances_max = base->instances_max;
×
176
        context.sync = base->sync;
×
177
        context.reboot = base->reboot;
×
178
        context.verify = base->verify;
×
179
        context.offline = base->offline;
×
180
        context.cleanup = base->cleanup;
×
181

182
        if (strdup_to(&context.root, base->root) < 0)
×
183
                return log_oom();
×
184

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

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

191
        if (base->image_policy) {
×
192
                context.image_policy = image_policy_copy(base->image_policy);
×
193
                if (!context.image_policy)
×
194
                        return log_oom();
×
195
        }
196

197
        if (strdup_to(&context.component, component) < 0)
×
198
                return log_oom();
×
199

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

206
        *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL);
×
207
        return 0;
×
208
}
209

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

217
#define SERVER_NULL \
218
        (Server) { \
219
                /* all fields fine with being initialised to NULL */ \
220
        }
221

222
static void server_done(Server *s) {
212✔
223
        assert(s);
212✔
224

225
        s->polkit_registry = hashmap_free(s->polkit_registry);
212✔
226
        s->system_bus = sd_bus_flush_close_unref(s->system_bus);
212✔
227
}
212✔
228

229
static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free);
×
230

231
static int read_features(
1,291✔
232
                Context *c,
233
                const char **dirs) {
234

235
        int r;
1,291✔
236

237
        assert(c);
1,291✔
238

239
        ConfFile **files = NULL;
1,291✔
240
        size_t n_files = 0;
1,291✔
241
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,291✔
242

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

253
        FOREACH_ARRAY(i, files, n_files) {
2,523✔
254
                ConfFile *e = *i;
1,232✔
255

256
                _cleanup_(feature_unrefp) Feature *f = feature_new();
1,232✔
257
                if (!f)
1,232✔
258
                        return log_oom();
×
259

260
                r = feature_read_definition(f, c->root, e->result, dirs);
1,232✔
261
                if (r < 0)
1,232✔
262
                        return r;
263

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

268
                TAKE_PTR(f);
1,232✔
269
        }
270

271
        return 0;
272
}
273

274
static int read_transfers(
1,298✔
275
                Context *c,
276
                const char **dirs,
277
                const char *suffix,
278
                const char *node) {
279

280
        ConfFile **files = NULL;
1,298✔
281
        Transfer **transfers = NULL, **disabled = NULL;
1,298✔
282
        size_t n_files = 0, n_transfers = 0, n_disabled = 0;
1,298✔
283
        int r;
1,298✔
284

285
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
1,298✔
286
        CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array);
1,298✔
287
        CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array);
1,298✔
288

289
        assert(c);
1,298✔
290
        assert(dirs);
1,298✔
291
        assert(suffix);
1,298✔
292

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

299
        FOREACH_ARRAY(i, files, n_files) {
11,210✔
300
                _cleanup_(transfer_freep) Transfer *t = NULL;
×
301
                Transfer **appended;
9,912✔
302
                ConfFile *e = *i;
9,912✔
303

304
                t = transfer_new(c);
9,912✔
305
                if (!t)
9,912✔
306
                        return log_oom();
×
307

308
                r = transfer_read_definition(t, e->result, dirs, c->features);
9,912✔
309
                if (r < 0)
9,912✔
310
                        return r;
311

312
                r = transfer_resolve_paths(t, c->root, node);
9,912✔
313
                if (r < 0)
9,912✔
314
                        return r;
315

316
                if (t->enabled)
9,912✔
317
                        appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
8,780✔
318
                else
319
                        appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
1,132✔
320
                if (!appended)
9,912✔
321
                        return log_oom();
×
322
                TAKE_PTR(t);
9,912✔
323
        }
324

325
        c->transfers = TAKE_PTR(transfers);
1,298✔
326
        c->n_transfers = n_transfers;
1,298✔
327
        c->disabled_transfers = TAKE_PTR(disabled);
1,298✔
328
        c->n_disabled_transfers = n_disabled;
1,298✔
329
        return 0;
1,298✔
330
}
331

332
static int read_component(Context *c) {
1,291✔
333
        int r;
1,291✔
334

335
        assert(c);
1,291✔
336

337
        /* Read a component description file, but only if we actually operate on a component */
338
        if (c->definitions || !c->component)
1,291✔
339
                return 0;
1,291✔
340

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

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

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

365
        return 0;
366
}
367

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

374
static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
1,291✔
375
        _cleanup_strv_free_ char **dirs = NULL;
1,291✔
376
        int r;
1,291✔
377

378
        assert(c);
1,291✔
379

380
        if (c->definitions)
1,291✔
381
                dirs = strv_new(c->definitions);
5✔
382
        else if (c->component) {
1,286✔
383
                char **l = CONF_PATHS_STRV("");
38✔
384
                size_t i = 0;
38✔
385

386
                dirs = new0(char*, strv_length(l) + 1);
38✔
387
                if (!dirs)
38✔
388
                        return log_oom();
×
389

390
                STRV_FOREACH(dir, l) {
190✔
391
                        char *j;
152✔
392

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

397
                        dirs[i++] = j;
152✔
398
                }
399
        } else
400
                dirs = strv_new(CONF_PATHS("sysupdate.d"));
1,248✔
401
        if (!dirs)
1,291✔
402
                return log_oom();
×
403

404
        r = read_component(c);
1,291✔
405
        if (r < 0)
1,291✔
406
                return r;
407

408
        r = read_features(c, (const char**) dirs);
1,291✔
409
        if (r < 0)
1,291✔
410
                return r;
411

412
        r = read_transfers(c, (const char**) dirs, ".transfer", node);
1,291✔
413
        if (r < 0)
1,291✔
414
                return r;
415

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

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

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

433
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
434
                                       "No transfer definitions found.");
435
        }
436

437
        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT) && !c->component_enabled)
1,291✔
438
                return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Component is disabled.");
×
439

440
        return 0;
441
}
442

443
static int context_load_installed_instances(Context *c) {
1,291✔
444
        int r;
1,291✔
445

446
        assert(c);
1,291✔
447

448
        log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
2,582✔
449

450
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
10,071✔
451
                Transfer *t = *tr;
8,780✔
452

453
                r = resource_load_instances(
8,780✔
454
                                &t->target,
455
                                c->verify >= 0 ? c->verify : t->verify,
8,780✔
456
                                &c->web_cache);
457
                if (r < 0)
8,780✔
458
                        return r;
459
        }
460

461
        FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
2,423✔
462
                Transfer *t = *tr;
1,132✔
463

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

472
        return 0;
473
}
474

475
static int context_load_available_instances(Context *c) {
831✔
476
        int r;
831✔
477

478
        assert(c);
831✔
479

480
        log_info("Discovering available instances%s", glyph(GLYPH_ELLIPSIS));
1,662✔
481

482
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
6,419✔
483
                Transfer *t = *tr;
5,606✔
484

485
                r = resource_load_instances(
5,606✔
486
                                &t->source,
487
                                c->verify >= 0 ? c->verify : t->verify,
5,606✔
488
                                &c->web_cache);
489
                if (r < 0)
5,606✔
490
                        return r;
491
        }
492

493
        return 0;
494
}
495

496
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
1,838✔
497
        _cleanup_free_ char *boundary = NULL;
1,838✔
498
        bool newest_found = false;
1,838✔
499
        int r;
1,838✔
500

501
        assert(c);
1,838✔
502
        assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
1,838✔
503

504
        for (;;) {
9,817✔
505
                _cleanup_free_ Instance **cursor_instances = NULL;
×
506
                bool skip = false;
9,817✔
507
                UpdateSetFlags extra_flags = 0;
9,817✔
508
                _cleanup_free_ char *cursor = NULL;
7,979✔
509
                UpdateSet *us = NULL;
9,817✔
510

511
                /* First, let's find the newest version that's older than the boundary. */
512
                FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
72,229✔
513
                        Resource *rr;
63,225✔
514

515
                        assert(*tr);
63,225✔
516

517
                        if (flags == UPDATE_AVAILABLE)
63,225✔
518
                                rr = &(*tr)->source;
32,212✔
519
                        else {
520
                                assert(flags == UPDATE_INSTALLED);
31,013✔
521
                                rr = &(*tr)->target;
31,013✔
522
                        }
523

524
                        FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
198,593✔
525
                                Instance *i = *inst; /* Sorted newest-to-oldest */
183,384✔
526

527
                                assert(i);
183,384✔
528

529
                                if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
183,384✔
530
                                        continue; /* Not older than the boundary */
135,368✔
531

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

537
                                if (free_and_strdup(&cursor, i->metadata.version) < 0)
7,979✔
538
                                        return log_oom();
×
539

540
                                break; /* All subsequent instances will be older than this one */
541
                        }
542

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

551
                if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
9,817✔
552
                        break;
553

554
                cursor_instances = new0(Instance*, c->n_transfers);
7,979✔
555
                if (!cursor_instances)
7,979✔
556
                        return log_oom();
×
557

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

563
                        assert(t);
54,104✔
564

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

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

587
                        cursor_instances[k] = match;
53,476✔
588

589
                        if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
53,476✔
590
                                extra_flags |= UPDATE_OBSOLETE;
×
591

592
                        if (strv_contains(t->protected_versions, cursor))
53,476✔
593
                                extra_flags |= UPDATE_PROTECTED;
×
594

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

600
                        if (match && match->is_partial)
45,852✔
601
                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
36✔
602

603
                        if (match && match->is_pending)
45,852✔
604
                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
748✔
605
                }
606

607
                r = free_and_strdup_warn(&boundary, cursor);
7,979✔
608
                if (r < 0)
7,979✔
609
                        return r;
610

611
                if (skip)
7,979✔
612
                        continue;
628✔
613

614
                /* See if we already have this update set in our table */
615
                FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
21,146✔
616
                        UpdateSet *u = *update_set;
16,463✔
617

618
                        if (strverscmp_improved(u->version, cursor) != 0)
16,463✔
619
                                continue;
13,795✔
620

621
                        /* Merge in what we've learned and continue onto the next version */
622

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

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

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

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

640
                        u->flags |= flags | extra_flags;
2,668✔
641

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

649
                        skip = true;
650
                        newest_found = true;
651
                        break;
652
                }
653

654
                if (skip)
64✔
655
                        continue;
2,668✔
656

657
                /* Doesn't exist yet, let's add it */
658
                if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
4,683✔
659
                        return log_oom();
×
660

661
                us = new(UpdateSet, 1);
4,683✔
662
                if (!us)
4,683✔
663
                        return log_oom();
×
664

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

672
                c->update_sets[c->n_update_sets++] = us;
4,683✔
673

674
                newest_found = true;
4,683✔
675

676
                /* Remember which one is the newest installed */
677
                if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
4,683✔
678
                        c->newest_installed = us;
984✔
679

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

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

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

697
        return 0;
698
}
699

700
static int context_discover_update_sets(Context *c) {
1,025✔
701
        int r;
1,025✔
702

703
        assert(c);
1,025✔
704

705
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
2,050✔
706

707
        r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
1,025✔
708
        if (r < 0)
1,025✔
709
                return r;
710

711
        if (!c->offline) {
1,025✔
712
                log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS));
1,626✔
713

714
                r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
813✔
715
                if (r < 0)
813✔
716
                        return r;
717
        }
718

719
        typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
1,025✔
720
        return 0;
1,025✔
721
}
722

723
static int context_show_table(Context *c) {
×
724
        _cleanup_(table_unrefp) Table *t = NULL;
×
725
        int r;
×
726

727
        assert(c);
×
728

729
        t = table_new("", "version", "installed", "available", "assessment");
×
730
        if (!t)
×
731
                return log_oom();
×
732

733
        (void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
×
734
        (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
×
735
        (void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
×
736

737
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
×
738
                UpdateSet *us = *update_set;
×
739
                const char *color;
×
740

741
                color = update_set_flags_to_color(us->flags);
×
742

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

758
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
759
}
760

761
static UpdateSet* context_update_set_by_version(Context *c, const char *version) {
208✔
762
        assert(c);
208✔
763
        assert(version);
208✔
764

765
        FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets)
368✔
766
                if (streq((*update_set)->version, version))
368✔
767
                        return *update_set;
768

769
        return NULL;
770
}
771

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

783
        assert(c);
208✔
784
        assert(version);
208✔
785

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

790
        if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
208✔
791
                pager_open(arg_pager_flags);
112✔
792

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

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

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

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

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

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

834
                STRV_FOREACH(changelog, tr->changelog) {
1,488✔
835
                        assert(*changelog);
×
836

837
                        _cleanup_free_ char *changelog_url = strreplace(*changelog, "@v", version);
×
838
                        if (!changelog_url)
×
839
                                return log_oom();
×
840

841
                        /* Avoid duplicates */
842
                        if (strv_contains(changelog_urls, changelog_url))
×
843
                                continue;
×
844

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

852
        FOREACH_ARRAY(inst, us->instances, us->n_instances) {
1,696✔
853
                Instance *i = *inst;
1,488✔
854

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

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

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

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

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

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

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

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

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

922
                if (i->metadata.no_auto >= 0) {
1,376✔
923
                        bool b;
×
924

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

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

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

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

954
                if (i->metadata.sha256sum_set) {
1,376✔
955
                        _cleanup_free_ char *formatted = NULL;
32✔
956

957
                        have_sha256 = true;
32✔
958

959
                        formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
32✔
960
                        if (!formatted)
32✔
961
                                return log_oom();
×
962

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

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

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

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

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

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

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

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

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

1044
                return 0;
1045
        }
1046
}
1047

1048
static int context_vacuum(
218✔
1049
                Context *c,
1050
                uint64_t space,
1051
                const char *extra_protected_version) {
1052

1053
        size_t disabled_count = 0;
218✔
1054
        int r, count = 0;
218✔
1055

1056
        assert(c);
218✔
1057

1058
        if (space == 0)
218✔
1059
                log_info("Making room%s", glyph(GLYPH_ELLIPSIS));
64✔
1060
        else
1061
                log_info("Making room for %" PRIu64 " updates%s", space, glyph(GLYPH_ELLIPSIS));
372✔
1062

1063
        FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
1,703✔
1064
                Transfer *t = *tr;
1,485✔
1065

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

1070
                r = transfer_vacuum(t, space, extra_protected_version);
1,277✔
1071
                if (r < 0)
1,277✔
1072
                        return r;
1073

1074
                count = MAX(count, r);
1,277✔
1075
        }
1076

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

1085
        if (!sd_json_format_enabled(arg_json_format_flags)) {
218✔
1086
                if (count > 0 && disabled_count > 0)
166✔
1087
                        log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
×
1088
                else if (count > 0)
166✔
1089
                        log_info("Removed %i instances.", count);
76✔
1090
                else if (disabled_count > 0)
90✔
1091
                        log_info("Removed %zu disabled transfers.", disabled_count);
16✔
1092
                else
1093
                        log_info("Found nothing to remove.");
74✔
1094
        } else {
1095
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
52✔
1096

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

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

1108
        return 0;
1109
}
1110

1111
typedef enum ProcessImageFlags {
1112
        PROCESS_IMAGE_READ_ONLY = 1 << 0,
1113
} ProcessImageFlags;
1114

1115
static int context_process_image(
1,291✔
1116
                Context *c,
1117
                ProcessImageFlags flags) {
1118

1119
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1,291✔
1120
        _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
1,291✔
1121
        int r;
1,291✔
1122

1123
        assert(c);
1,291✔
1124

1125
        if (c->root || !c->image) /* Idempotent or nothing to do */
1,291✔
1126
                return 0;
1127

1128
        assert(!c->mounted_dir);
×
1129
        assert(!c->loop_device);
×
1130

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

1149
        c->root = strdup(mounted_dir);
×
1150
        if (!c->root)
×
1151
                return log_oom();
×
1152

1153
        c->mounted_dir = TAKE_PTR(mounted_dir);
×
1154
        c->loop_device = TAKE_PTR(loop_device);
×
1155

1156
        return 0;
×
1157
}
1158

1159
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component);
1160

1161
static int context_load_offline(
1,291✔
1162
                Context *context,
1163
                ProcessImageFlags process_image_flags,
1164
                ReadDefinitionsFlags read_definitions_flags) {
1165
        int r;
1,291✔
1166

1167
        assert(context);
1,291✔
1168

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

1172
        r = context_process_image(context, process_image_flags);
1,291✔
1173
        if (r < 0)
1,291✔
1174
                return r;
1175

1176
        r = context_read_definitions(context, context->loop_device ? context->loop_device->node : NULL, read_definitions_flags);
1,291✔
1177
        if (r < 0)
1,291✔
1178
                return r;
1179

1180
        r = context_load_installed_instances(context);
1,291✔
1181
        if (r < 0)
1,291✔
1182
                return r;
×
1183

1184
        return 0;
1185
}
1186

1187
static int context_load_online(
1,043✔
1188
                Context *context,
1189
                ProcessImageFlags process_image_flags,
1190
                ReadDefinitionsFlags read_definitions_flags) {
1191

1192
        int r;
1,043✔
1193

1194
        assert(context);
1,043✔
1195

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

1199
        r = context_load_offline(
1,043✔
1200
                        context,
1201
                        process_image_flags,
1202
                        read_definitions_flags);
1203
        if (r < 0)
1,043✔
1204
                return r;
1205

1206
        if (!context->offline) {
1,043✔
1207
                r = context_load_available_instances(context);
831✔
1208
                if (r < 0)
831✔
1209
                        return r;
1210
        }
1211

1212
        r = context_discover_update_sets(context);
1,025✔
1213
        if (r < 0)
1,025✔
1214
                return r;
×
1215

1216
        return 0;
1217
}
1218

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

1224
static int context_load_paths_from_image(Context *context, Image *image) {
×
1225
        assert(context);
×
1226
        assert(image);
×
1227

1228
        assert(!context->root);
×
1229
        assert(!context->image);
×
1230

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

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

1259
        assert(context);
192✔
1260
        assert(context->target_identifier.class != _TARGET_CLASS_INVALID);
192✔
1261

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

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

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

1280
                HASHMAP_FOREACH(image, images) {
×
1281
                        bool have = false;
×
1282
                        _cleanup_(context_done) Context image_context = CONTEXT_NULL;
×
1283

1284
                        if (image_is_host(image))
×
1285
                                continue; /* We already enroll the host ourselves */
×
1286

1287
                        if (!image_type_can_sysupdate(image->type))
×
1288
                                continue;
×
1289

1290
                        if (!streq(image->name, context->target_identifier.name))
×
1291
                                continue;
×
1292

1293
                        r = context_load_paths_from_image(&image_context, image);
×
1294
                        if (r < 0)
×
1295
                                return r;
1296

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

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

1311
                        /* This is the match we were looking for */
1312
                        selected_image = image;
×
1313
                        break;
×
1314
                }
1315

1316
                if (!selected_image)
×
1317
                        return -ENOENT;
1318

1319
                r = context_load_paths_from_image(context, selected_image);
×
1320
                if (r < 0)
×
1321
                        return r;
1322

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

1331
                r = context_list_components(context, &component_names, /* ret_has_default_component= */ NULL);
8✔
1332
                if (r < 0)
8✔
1333
                        return r;
1334

1335
                if (!strv_contains(component_names, context->target_identifier.name))
8✔
1336
                        return -ENOENT;
1337

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

1347
        return context_load_online(context, process_image_flags, read_definitions_flags);
184✔
1348
}
1349

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

1356
        for (i = 0; i < n; i++)
3,004✔
1357
                if (c->transfers[i] == t)
3,004✔
1358
                        break;
1359
        assert(i < n); /* We should have found the index */
843✔
1360

1361
        base = (100 * 100 * i) / n;
843✔
1362
        scaled = (100 * percentage) / n;
843✔
1363
        overall = (unsigned) ((base + scaled) / 100);
843✔
1364
        assert(overall <= 100);
843✔
1365

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

1374
static int context_process_partial_and_pending(Context *c, const char *version);
1375

1376
static int context_acquire(
284✔
1377
                Context *c,
1378
                const char *version) {
1379

1380
        UpdateSet *us = NULL;
284✔
1381
        int r;
284✔
1382

1383
        assert(c);
284✔
1384

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

1393
                        return 0;
1394
                }
1395

1396
                us = c->candidate;
1397
        }
1398

1399
        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
234✔
1400
                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
32✔
1401
        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
202✔
1402
                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✔
1403
        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
186✔
1404
                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
32✔
1405

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

1410
                return 0;
1411
        }
1412

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

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

1423
        log_info("Selected update '%s' for install.", us->version);
186✔
1424

1425
        _cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
372✔
1426
        if (!metadata)
186✔
1427
                return log_oom();
×
1428

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

1435
                assert(inst);
1,261✔
1436

1437
                r = transfer_compute_temporary_paths(t, inst, metadata + i);
1,261✔
1438
                if (r < 0)
1,261✔
1439
                        return r;
1440
        }
1441

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

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

1457
        if (c->sync)
186✔
1458
                sync();
186✔
1459

1460
        (void) sd_notifyf(/* unset_environment= */ false,
186✔
1461
                          "STATUS=Updating to '%s'.", us->version);
1462

1463
        /* There should now be one instance picked for each transfer, and the order is the same */
1464
        assert(us->n_instances == c->n_transfers);
186✔
1465

1466
        for (size_t i = 0; i < c->n_transfers; i++) {
1,351✔
1467
                Instance *inst = us->instances[i];
1,181✔
1468
                Transfer *t = c->transfers[i];
1,181✔
1469

1470
                assert(inst); /* ditto */
1,181✔
1471

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

1477
                r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
973✔
1478
                if (r < 0)
973✔
1479
                        return r;
1480
        }
1481

1482
        if (c->sync)
170✔
1483
                sync();
170✔
1484

1485
        return 1;
1486
}
1487

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

1493
        UpdateSet *us = NULL;
116✔
1494
        int r;
116✔
1495

1496
        assert(c);
116✔
1497

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

1506
                        return 0;
1507
                }
1508

1509
                us = c->candidate;
1510
        }
1511

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

1517
                return 0;
1518
        }
1519

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

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

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

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

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

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

1542
                assert(inst);
796✔
1543

1544
                r = transfer_process_partial_and_pending_instance(t, inst);
796✔
1545
                if (r < 0)
796✔
1546
                        return r;
1547
        }
1548

1549
        return 1;
1550
}
1551

1552
static int notify_subscribers_reply(
171✔
1553
                sd_varlink *link,
1554
                sd_json_variant *reply,
1555
                const char *error_id,
1556
                sd_varlink_reply_flags_t flags,
1557
                void *userdata) {
1558

1559
        assert(link);
171✔
1560

1561
        if (error_id)
171✔
1562
                log_warning("Notification subscriber '%s' returned error, ignoring: %s",
×
1563
                            strna(sd_varlink_get_description(link)), error_id);
1564

1565
        return 0;
171✔
1566
}
1567

1568
static int context_notify_subscribers(Context *c, UpdateSet *us) {
170✔
1569
        int r;
170✔
1570

1571
        assert(c);
170✔
1572

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

1576
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *resources = NULL;
170✔
1577
        if (us)
170✔
1578
                for (size_t i = 0; i < c->n_transfers; i++) {
1,319✔
1579
                        Instance *inst = us->instances[i];
1,149✔
1580
                        Transfer *t = c->transfers[i];
1,149✔
1581

1582
                        if (inst->resource == &t->target &&
1,149✔
1583
                            !inst->is_pending)
920✔
1584
                                continue;
208✔
1585

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

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

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

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

1625
        return 0;
1626
}
1627

1628
static int context_install(
170✔
1629
                Context *c,
1630
                const char *version,
1631
                UpdateSet **ret_applied) {
1632

1633
        UpdateSet *us = NULL;
170✔
1634
        int r;
170✔
1635

1636
        assert(c);
170✔
1637

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

1646
                        return 0;
1647
                }
1648

1649
                us = c->candidate;
1650
        }
1651

1652
        (void) sd_notifyf(/* unset_environment=*/ false,
170✔
1653
                          "READY=1\n"
1654
                          "X_SYSUPDATE_VERSION=%s\n"
1655
                          "STATUS=Installing '%s'.", us->version, us->version);
1656

1657
        for (size_t i = 0; i < c->n_transfers; i++) {
1,489✔
1658
                Instance *inst = us->instances[i];
1,149✔
1659
                Transfer *t = c->transfers[i];
1,149✔
1660

1661
                if (inst->resource == &t->target &&
1,149✔
1662
                    !inst->is_pending)
920✔
1663
                        continue;
208✔
1664

1665
                r = transfer_install_instance(t, inst, c->root);
941✔
1666
                if (r < 0)
941✔
1667
                        return r;
1668
        }
1669

1670
        log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
170✔
1671

1672
        if (!c->root)
170✔
1673
                (void) context_notify_subscribers(c, us);
170✔
1674

1675
        (void) sd_notifyf(/* unset_environment= */ false,
170✔
1676
                          "STATUS=Installed '%s'.", us->version);
1677

1678
        if (ret_applied)
170✔
1679
                *ret_applied = us;
170✔
1680

1681
        return 1;
1682
}
1683

1684
static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string);
200✔
1685

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

1695
        r = sd_json_dispatch(variant, dispatch, flags, t);
200✔
1696
        if (r < 0)
200✔
1697
                return r;
1698

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

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

1706
        return 0;
1707
}
1708

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

1713
        assert(context);
192✔
1714

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

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

1725
        return varlink_verify_polkit_async(link,
192✔
1726
                        s->system_bus,
1727
                        action,
1728
                        details,
1729
                        &s->polkit_registry);
1730
}
1731

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

1740
        assert(argc <= 2);
256✔
1741
        version = argc >= 2 ? argv[1] : NULL;
256✔
1742

1743
        r = context_from_cmdline(&context);
256✔
1744
        if (r < 0)
256✔
1745
                return r;
1746

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

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

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

1768
                FOREACH_ARRAY(update_set, context.update_sets, context.n_update_sets) {
224✔
1769
                        UpdateSet *us = *update_set;
176✔
1770

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

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

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

1788
                                r = strv_extend(&appstream_urls, *appstream_url);
×
1789
                                if (r < 0)
×
1790
                                        return log_oom();
×
1791
                        }
1792

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

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

1803
                return 0;
1804
        }
1805
}
1806

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

1816
        assert(argc <= 2);
32✔
1817
        feature_id = argc >= 2 ? argv[1] : NULL;
32✔
1818

1819
        r = context_from_cmdline(&context);
32✔
1820
        if (r < 0)
32✔
1821
                return r;
1822

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

1826
        r = context_load_offline(
32✔
1827
                        &context,
1828
                        PROCESS_IMAGE_READ_ONLY,
1829
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1830
        if (r < 0)
32✔
1831
                return r;
1832

1833
        if (feature_id) {
32✔
1834
                _cleanup_strv_free_ char **transfers = NULL;
16✔
1835

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

1842
                table = table_new_vertical();
16✔
1843
                if (!table)
16✔
1844
                        return log_oom();
×
1845

1846
                FOREACH_ARRAY(tr, context.transfers, context.n_transfers) {
128✔
1847
                        Transfer *t = *tr;
112✔
1848

1849
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
112✔
1850
                                continue;
112✔
1851

1852
                        r = strv_extend(&transfers, t->id);
×
1853
                        if (r < 0)
×
1854
                                return log_oom();
×
1855
                }
1856

1857
                FOREACH_ARRAY(tr, context.disabled_transfers, context.n_disabled_transfers) {
32✔
1858
                        Transfer *t = *tr;
16✔
1859

1860
                        if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
16✔
1861
                                continue;
×
1862

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

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

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

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

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

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

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

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

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

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

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

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

1944
        return 0;
×
1945
}
1946

1947
VERB_NOARG(verb_check_new, "check-new",
1948
           "Check if there's a new version available");
1949
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
218✔
1950
        _cleanup_(context_done) Context context = CONTEXT_NULL;
218✔
1951
        int r;
218✔
1952

1953
        assert(argc <= 1);
218✔
1954

1955
        r = context_from_cmdline(&context);
218✔
1956
        if (r < 0)
218✔
1957
                return r;
1958

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

1962
        r = context_load_online(
218✔
1963
                        &context,
1964
                        PROCESS_IMAGE_READ_ONLY,
1965
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
1966
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
1967
        if (r < 0)
218✔
1968
                return r;
1969

1970
        if (!sd_json_format_enabled(arg_json_format_flags)) {
217✔
1971
                if (!context.candidate) {
185✔
1972
                        log_debug("No candidate found.");
96✔
1973
                        return EXIT_FAILURE;
1974
                }
1975

1976
                puts(context.candidate->version);
89✔
1977
        } else {
1978
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
32✔
1979

1980
                if (context.candidate)
32✔
1981
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
×
1982
                else
1983
                        r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available"));
32✔
1984
                if (r < 0)
32✔
1985
                        return log_error_errno(r, "Failed to create JSON: %m");
×
1986

1987
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
32✔
1988
                if (r < 0)
32✔
1989
                        return log_error_errno(r, "Failed to print JSON: %m");
×
1990
        }
1991

1992
        return EXIT_SUCCESS;
1993
}
1994

1995
static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
200✔
1996
        _cleanup_(context_done) Context context = CONTEXT_NULL;
200✔
1997
        int r;
200✔
1998

1999
        assert(link);
200✔
2000

2001
        static const sd_json_dispatch_field dispatch_table[] = {
200✔
2002
                { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY },
2003
                VARLINK_DISPATCH_POLKIT_FIELD,
2004
                {},
2005
        };
2006

2007
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &context);
200✔
2008
        if (r != 0)
200✔
2009
                return r;
2010

2011
        r = verify_polkit(&context, link, "org.freedesktop.sysupdate1.check",
576✔
2012
                        (const char**) STRV_MAKE(
376✔
2013
                                        "class", target_class_to_string(context.target_identifier.class),
2014
                                        "offline", "0",
2015
                                        context.target_identifier.name ? "name" : NULL, context.target_identifier.name));
2016
        if (r <= 0)
192✔
2017
                return r;
2018

2019
        if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
192✔
2020
                context.verify = 0;
192✔
2021

2022
        /* CheckNew is always online */
2023
        context.offline = false;
192✔
2024

2025
        r = context_load_online_from_target(
192✔
2026
                        &context,
2027
                        PROCESS_IMAGE_READ_ONLY,
2028
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2029
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2030
        if (r == -ENOENT)
192✔
2031
                return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
8✔
2032
        if (r < 0)
184✔
2033
                return r;
2034

2035
        if (context.candidate)
184✔
2036
                r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version));
88✔
2037
        else
2038
                r = sd_varlink_error(link, "io.systemd.SysUpdate.NoUpdateNeeded", NULL);
96✔
2039
        if (r < 0)
184✔
2040
                return r;
96✔
2041

2042
        return 0;
2043
}
2044

2045
typedef enum {
2046
        UPDATE_ACTION_ACQUIRE = 1 << 0,
2047
        UPDATE_ACTION_INSTALL = 1 << 1,
2048
} UpdateActionFlags;
2049

2050
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
386✔
2051
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2052
        _cleanup_free_ char *booted_version = NULL;
386✔
2053
        UpdateSet *applied = NULL;
386✔
2054
        const char *version;
386✔
2055
        int r;
386✔
2056

2057
        assert(argc <= 2);
386✔
2058
        version = argc >= 2 ? argv[1] : NULL;
386✔
2059

2060
        r = context_from_cmdline(&context);
386✔
2061
        if (r < 0)
386✔
2062
                return r;
2063

2064
        if (context.component_all)
386✔
2065
                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
1✔
2066

2067
        if (context.instances_max < 2)
385✔
2068
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2069
                                      "The --instances-max argument must be >= 2 while updating");
2070

2071
        if (context.reboot) {
385✔
2072
                /* If automatic reboot on completion is requested, let's first determine the currently booted image */
2073

2074
                r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version);
×
2075
                if (r < 0)
×
2076
                        return log_error_errno(r, "Failed to parse /etc/os-release: %m");
×
2077
                if (!booted_version)
×
2078
                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
×
2079
        }
2080

2081
        bool installed = false;
385✔
2082
        int ret = 0;
385✔
2083

2084
        r = context_load_online(
385✔
2085
                        &context,
2086
                        /* process_image_flags= */ 0,
2087
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2088
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
2089
                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
2090
        if (r < 0) {
385✔
2091
                if (r != -ENOENT)
17✔
2092
                        return r;
2093

2094
                /* No transfer files found. In that case, still do the installdb cleanup below */
2095
                RET_GATHER(ret, r);
2096
        } else {
2097
                if (action_flags & UPDATE_ACTION_ACQUIRE)
368✔
2098
                        r = context_acquire(&context, version);
284✔
2099
                else
2100
                        r = context_process_partial_and_pending(&context, version);
84✔
2101
                if (r < 0)
368✔
2102
                        return r;
2103

2104
                if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */
332✔
2105
                        r = context_install(&context, version, &applied);
170✔
2106
                        if (r < 0)
170✔
2107
                                return r;
2108

2109
                        installed = r > 0;
170✔
2110
                }
2111

2112
                /* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If
2113
                 * nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a
2114
                 * resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */
2115
                if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) {
220✔
2116
                        int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY");
50✔
2117
                        if (f < 0 && f != -ENXIO)
50✔
2118
                                log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m");
×
2119
                        if (f > 0)
50✔
2120
                                (void) context_notify_subscribers(&context, /* us= */ NULL);
×
2121
                }
2122
        }
2123

2124
        if (context.cleanup > 0)
332✔
2125
                RET_GATHER(ret, installdb_cleanup_component(&context));
2✔
2126

2127
        if (installed) {
332✔
2128
                /* We installed something, yay */
2129

2130
                if (context.reboot) {
170✔
2131
                        assert(applied);
×
2132
                        assert(booted_version);
×
2133

2134
                        if (strverscmp_improved(applied->version, booted_version) > 0) {
×
2135
                                log_notice("Newly installed version is newer than booted version, rebooting.");
×
2136
                                RET_GATHER(ret, reboot_now());
×
2137
                        } else if (strverscmp_improved(applied->version, booted_version) == 0 &&
×
2138
                                   FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
×
2139
                                log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
×
2140
                                RET_GATHER(ret, reboot_now());
×
2141
                        } else
2142
                                log_info("Booted version is newer or identical to newly installed version, not rebooting.");
×
2143
                }
2144
        }
2145

2146
        return ret;
2147
}
2148

2149
VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
2150
     "Install new version now");
2151
static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
250✔
2152
        UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
250✔
2153

2154
        if (!arg_offline)
250✔
2155
                flags |= UPDATE_ACTION_ACQUIRE;
166✔
2156

2157
        return verb_update_impl(argc, argv, flags);
250✔
2158
}
2159

2160
VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
2161
     "Acquire (download) new version now");
2162
static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
136✔
2163
        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
136✔
2164
}
2165

2166
VERB_NOARG(verb_vacuum, "vacuum",
2167
           "Make room, by deleting old versions");
2168
static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
32✔
2169
        _cleanup_(context_done) Context context = CONTEXT_NULL;
32✔
2170
        int r;
32✔
2171

2172
        assert(argc <= 1);
32✔
2173

2174
        r = context_from_cmdline(&context);
32✔
2175
        if (r < 0)
32✔
2176
                return r;
2177

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

2181
        if (context.instances_max < 1)
32✔
2182
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
2183
                                      "The --instances-max argument must be >= 1 while vacuuming");
2184

2185
        r = context_load_offline(
32✔
2186
                        &context,
2187
                        /* process_image_flags= */ 0,
2188
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
2189
        if (r < 0)
32✔
2190
                return r;
2191

2192
        return context_vacuum(&context, 0, NULL);
32✔
2193
}
2194

2195
VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
2196
     "Report whether a newer version is installed than currently booted");
2197
VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
2198
     "Reboot if a newer version is installed than booted");
2199
static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
2✔
2200
        _cleanup_(context_done) Context context = CONTEXT_NULL;
×
2201
        _cleanup_free_ char *booted_version = NULL;
2✔
2202
        int r;
2✔
2203

2204
        assert(argc == 1);
2✔
2205

2206
        r = context_from_cmdline(&context);
2✔
2207
        if (r < 0)
2✔
2208
                return r;
2209

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

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

2218
        r = context_load_offline(
×
2219
                        &context,
2220
                        /* process_image_flags= */ 0,
2221
                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
2222
                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
2223
                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
2224
        if (r < 0)
×
2225
                return r;
2226

2227
        log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS));
×
2228

2229
        r = context_discover_update_sets_by_flag(&context, UPDATE_INSTALLED);
×
2230
        if (r < 0)
×
2231
                return r;
2232
        if (!context.newest_installed)
×
2233
                return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
×
2234

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

2242
        r = strverscmp_improved(context.newest_installed->version, booted_version);
×
2243
        if (r > 0) {
×
2244
                log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
×
2245
                           context.newest_installed->version, booted_version,
2246
                           streq(argv[0], "pending") ? " Reboot recommended." : "");
2247

2248
                if (streq(argv[0], "reboot"))
×
2249
                        return reboot_now();
×
2250

2251
                return EXIT_SUCCESS;
2252
        } else if (r == 0)
×
2253
                log_info("Newest installed version '%s' matches booted version '%s'.",
×
2254
                         context.newest_installed->version, booted_version);
2255
        else
2256
                log_warning("Newest installed version '%s' is older than booted version '%s'.",
×
2257
                            context.newest_installed->version, booted_version);
2258

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

2262
        return EXIT_SUCCESS;
2263
}
2264

2265
static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component) {
182✔
2266
        int r;
182✔
2267

2268
        assert(context);
182✔
2269

2270
        _cleanup_strv_free_ char **z = NULL;
182✔
2271
        r = get_component_list(context->root, &z);
182✔
2272
        if (r < 0)
182✔
2273
                return r;
2274

2275
        if (ret_component_names)
182✔
2276
                *ret_component_names = TAKE_PTR(z);
182✔
2277

2278
        /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a
2279
         * TARGET_HOST? See target_get_argument() in sysupdated.c */
2280
        if (ret_has_default_component)
182✔
2281
                *ret_has_default_component =
348✔
2282
                        !context->definitions &&
348✔
2283
                        !context->component &&
174✔
2284
                        !context->root &&
174✔
2285
                        !context->image &&
348✔
2286
                        context->n_transfers > 0;
176✔
2287

2288
        return 0;
2289
}
2290

2291
VERB_NOARG(verb_components, "components",
2292
           "Show list of components");
2293
static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
174✔
2294
        _cleanup_(context_done) Context context = CONTEXT_NULL;
174✔
2295
        _cleanup_strv_free_ char **component_names = NULL;
174✔
2296
        bool has_default_component = false;
174✔
2297
        int r;
174✔
2298

2299
        assert(argc <= 1);
174✔
2300

2301
        r = context_from_cmdline(&context);
174✔
2302
        if (r < 0)
174✔
2303
                return r;
2304

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

2308
        r = context_load_offline(
174✔
2309
                        &context,
2310
                        /* process_image_flags= */ 0,
2311
                        /* read_definitions_flags= */ 0);
2312
        if (r < 0)
174✔
2313
                return r;
2314

2315
        r = context_list_components(&context, &component_names, &has_default_component);
174✔
2316
        if (r < 0)
174✔
2317
                return log_error_errno(r, "Failed to enumerate components: %m");
×
2318

2319
        if (!sd_json_format_enabled(arg_json_format_flags)) {
174✔
2320
                if (!has_default_component && strv_isempty(component_names)) {
×
2321
                        log_info("No components defined.");
×
2322
                        return 0;
×
2323
                }
2324

2325
                _cleanup_(table_unrefp) Table *t = table_new("", "component", "description", "documentation");
×
2326
                if (!t)
×
2327
                        return log_oom();
×
2328

2329
                table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
×
2330

2331
                if (has_default_component) {
×
2332
                        r = table_add_many(
×
2333
                                        t,
2334
                                        TABLE_EMPTY,
2335
                                        TABLE_STRING, "<default>",
2336
                                        TABLE_SET_COLOR, ansi_highlight(),
2337
                                        TABLE_STRING, "Default Component",
2338
                                        TABLE_EMPTY);
2339
                        if (r < 0)
×
2340
                                return table_log_add_error(r);
×
2341
                }
2342

2343
                STRV_FOREACH(i, component_names) {
×
2344
                        _cleanup_(context_done) Context cc = CONTEXT_NULL;
×
2345

2346
                        r = context_from_base_with_component(
×
2347
                                        &context,
2348
                                        *i,
2349
                                        &cc);
2350
                        if (r < 0)
×
2351
                                return r;
2352

2353
                        r = context_load_offline(
×
2354
                                        &cc,
2355
                                        /* process_image_flags= */ 0,
2356
                                        /* read_definitions_flags= */ 0);
2357
                        if (r == -ENOMEM)
×
2358
                                return r;
2359
                        if (r < 0)
×
2360
                                continue;
×
2361

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

2364
                        r = table_add_many(
×
2365
                                        t,
2366
                                        TABLE_BOOLEAN_CHECKMARK, cc.component_enabled,
2367
                                        TABLE_SET_COLOR, ansi_highlight_green_red(cc.component_enabled),
2368
                                        TABLE_STRING, *i,
2369
                                        TABLE_STRING, cc.component_description,
2370
                                        TABLE_STRING, doc,
2371
                                        TABLE_SET_URL, doc);
2372
                        if (r < 0)
×
2373
                                return table_log_add_error(r);
×
2374
                }
2375

2376
                return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
×
2377
        } else {
2378
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
2379

2380
                r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
174✔
2381
                                          SD_JSON_BUILD_PAIR_STRV("components", component_names));
2382
                if (r < 0)
174✔
2383
                        return log_error_errno(r, "Failed to create JSON: %m");
×
2384

2385
                r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
174✔
2386
                if (r < 0)
174✔
2387
                        return log_error_errno(r, "Failed to print JSON: %m");
×
2388
        }
2389

2390
        return 0;
174✔
2391
}
2392

2393
VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files");
2394
static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) {
7✔
2395
        _cleanup_(context_done) Context context = CONTEXT_NULL;
7✔
2396
        int r;
7✔
2397

2398
        assert(argc <= 1);
7✔
2399

2400
        r = context_from_cmdline(&context);
7✔
2401
        if (r < 0)
7✔
2402
                return r;
2403

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

2407
        r = context_load_offline(
6✔
2408
                        &context,
2409
                        /* process_image_flags= */ 0,
2410
                        /* read_definitions_flags= */ 0);
2411
        if (r < 0)
6✔
2412
                return r;
2413

2414
        int ret = 0;
6✔
2415
        RET_GATHER(ret, installdb_cleanup_component(&context));
6✔
2416

2417
        if (context.component_all) {
6✔
2418
                _cleanup_strv_free_ char **z = NULL;
2✔
2419
                r = installdb_list_components(&context, &z);
2✔
2420
                if (r < 0)
2✔
2421
                        return log_error_errno(r, "Failed to enumerate components: %m");
×
2422

2423
                STRV_FOREACH(i, z) {
6✔
2424
                        _cleanup_(context_done) Context component_context = CONTEXT_NULL;
×
2425

2426
                        r = context_from_cmdline(&component_context);
4✔
2427
                        if (r < 0)
4✔
2428
                                return r;
2429

2430
                        /* Override the component with our iter. This needs to be done in a fresh Context
2431
                         * as the installdb_fd and other state are specific to the component. */
2432
                        r = free_and_strdup_warn(&component_context.component, *i);
4✔
2433
                        if (r < 0)
4✔
2434
                                return r;
2435

2436
                        r = context_load_offline(
4✔
2437
                                        &component_context,
2438
                                        /* process_image_flags= */ 0,
2439
                                        /* read_definitions_flags= */ 0);
2440
                        if (r < 0)
4✔
2441
                                return r;
2442

2443
                        RET_GATHER(ret, installdb_cleanup_component(&component_context));
4✔
2444
                }
2445
        }
2446

2447
        return ret;
2448
}
2449

2450
static int help(void) {
×
2451
        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
×
2452
        int r;
×
2453

2454
        r = verbs_get_help_table(&verbs);
×
2455
        if (r < 0)
×
2456
                return r;
2457

2458
        r = option_parser_get_help_table(&common_options);
×
2459
        if (r < 0)
×
2460
                return r;
2461

2462
        r = option_parser_get_help_table_group("Options", &options);
×
2463
        if (r < 0)
×
2464
                return r;
2465

2466
        (void) table_sync_column_widths(0, verbs, common_options, options);
×
2467

2468
        help_cmdline("[OPTIONS…] [VERSION]");
×
2469
        help_abstract("Update OS images.");
×
2470

2471
        help_section("Commands");
×
2472
        r = table_print_or_warn(verbs);
×
2473
        if (r < 0)
×
2474
                return r;
2475

2476
        r = table_print_or_warn(common_options);
×
2477
        if (r < 0)
×
2478
                return r;
2479

2480
        help_section("Options");
×
2481
        r = table_print_or_warn(options);
×
2482
        if (r < 0)
×
2483
                return r;
2484

2485
        help_man_page_reference("systemd-sysupdate", "8");
×
2486
        return 0;
2487
}
2488

2489
VERB_COMMON_HELP_HIDDEN(help);
×
2490

2491
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
1,328✔
2492
        assert(argc >= 0);
1,328✔
2493
        assert(argv);
1,328✔
2494
        assert(remaining_args);
1,328✔
2495

2496
        OptionParser opts = { argc, argv };
1,328✔
2497
        int r;
1,328✔
2498

2499
        FOREACH_OPTION_OR_RETURN(c, &opts)
4,128✔
2500
                switch (c) {
1,480✔
2501

2502
                OPTION_COMMON_HELP:
×
2503
                        return help();
×
2504

2505
                OPTION_COMMON_VERSION:
×
2506
                        return version();
×
2507

2508
                OPTION_GROUP("Options"):
2509
                        break;
2510

2511
                OPTION('C', "component", "NAME",
45✔
2512
                       "Select component to update"):
2513
                        if (isempty(opts.arg)) {
45✔
2514
                                arg_component = mfree(arg_component);
×
2515
                                arg_component_all = false;
×
2516
                                break;
×
2517
                        }
2518

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

2522
                        r = free_and_strdup_warn(&arg_component, opts.arg);
37✔
2523
                        if (r < 0)
37✔
2524
                                return r;
2525

2526
                        arg_component_all = false;
37✔
2527
                        break;
37✔
2528

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

2531
                        arg_component = mfree(arg_component);
3✔
2532
                        arg_component_all = true;
3✔
2533
                        break;
3✔
2534

2535
                OPTION_LONG("definitions", "DIR",
5✔
2536
                            "Find transfer definitions in specified directory"):
2537
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_definitions);
5✔
2538
                        if (r < 0)
5✔
2539
                                return r;
2540
                        break;
2541

2542
                OPTION_LONG("root", "PATH",
×
2543
                            "Operate on an alternate filesystem root"):
2544
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root);
×
2545
                        if (r < 0)
×
2546
                                return r;
2547
                        break;
2548

2549
                OPTION_LONG("image", "PATH",
×
2550
                            "Operate on disk image as filesystem root"):
2551
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image);
×
2552
                        if (r < 0)
×
2553
                                return r;
2554
                        break;
2555

2556
                OPTION_LONG("image-policy", "POLICY",
×
2557
                            "Specify disk image dissection policy"):
2558
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
2559
                        if (r < 0)
×
2560
                                return r;
2561
                        break;
2562

2563
                OPTION_LONG("transfer-source", "PATH",
×
2564
                            "Specify the directory to transfer sources from"):
2565
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_transfer_source);
×
2566
                        if (r < 0)
×
2567
                                return r;
2568

2569
                        break;
2570

2571
                OPTION('m', "instances-max", "INT",
×
2572
                       "How many instances to maintain"):
2573
                        r = safe_atou64(opts.arg, &arg_instances_max);
×
2574
                        if (r < 0)
×
2575
                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", opts.arg);
×
2576

2577
                        break;
2578

2579
                OPTION_LONG("sync", "BOOL",
×
2580
                            "Controls whether to sync data to disk"):
2581
                        r = parse_boolean_argument("--sync=", opts.arg, &arg_sync);
×
2582
                        if (r < 0)
×
2583
                                return r;
2584
                        break;
2585

2586
                OPTION_LONG("verify", "BOOL",
736✔
2587
                            "Force signature verification on or off"): {
2588
                        bool b;
736✔
2589

2590
                        r = parse_boolean_argument("--verify=", opts.arg, &b);
736✔
2591
                        if (r < 0)
736✔
2592
                                return r;
×
2593

2594
                        arg_verify = b;
736✔
2595
                        break;
736✔
2596
                }
2597

2598
                OPTION_LONG("reboot", NULL,
1✔
2599
                            "Reboot after updating to newer version"):
2600
                        arg_reboot = true;
1✔
2601
                        break;
1✔
2602

2603
                OPTION_LONG("offline", NULL,
228✔
2604
                            "Do not fetch metadata from the network"):
2605
                        arg_offline = true;
228✔
2606
                        break;
228✔
2607

2608
                OPTION_LONG("cleanup", "BOOL", "Clean up orphaned files after completing update"): {
4✔
2609
                        bool b;
4✔
2610

2611
                        r = parse_boolean_argument("--cleanup=", opts.arg, &b);
4✔
2612
                        if (r < 0)
4✔
2613
                                return r;
×
2614

2615
                        arg_cleanup = b;
4✔
2616
                        break;
4✔
2617
                }
2618

2619
                OPTION_COMMON_NO_PAGER:
×
2620
                        arg_pager_flags |= PAGER_DISABLE;
×
2621
                        break;
×
2622

2623
                OPTION_COMMON_NO_LEGEND:
×
2624
                        arg_legend = false;
×
2625
                        break;
×
2626

2627
                OPTION_COMMON_JSON:
458✔
2628
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
458✔
2629
                        if (r <= 0)
458✔
2630
                                return r;
2631

2632
                        break;
2633
                }
2634

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

2638
        if ((arg_image || arg_root) && arg_reboot)
1,320✔
2639
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
×
2640

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

2644
        if (arg_definitions && arg_component)
1,319✔
2645
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
×
2646

2647
        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
1,319✔
2648
        if (r < 0)
1,319✔
2649
                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
×
2650
        if (r > 0)
1,319✔
2651
                arg_varlink = true;
212✔
2652

2653
        *remaining_args = option_parser_get_args(&opts);
1,319✔
2654
        return 1;
1,319✔
2655
}
2656

2657
static int vl_server(void) {
212✔
2658
        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
212✔
2659
        _cleanup_(server_done) Server server = SERVER_NULL;
212✔
2660
        int r;
212✔
2661

2662
        r = varlink_server_new(&varlink_server,
212✔
2663
                               SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
2664
                               &server);
2665
        if (r < 0)
212✔
2666
                return log_error_errno(r, "Failed to allocate Varlink server: %m");
×
2667

2668
        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysUpdate);
212✔
2669
        if (r < 0)
212✔
2670
                return log_error_errno(r, "Failed to add Varlink interface: %m");
×
2671

2672
        r = sd_varlink_server_bind_method_many(
212✔
2673
                        varlink_server,
2674
                        "io.systemd.SysUpdate.CheckNew", vl_method_check_new);
2675
        if (r < 0)
212✔
2676
                return log_error_errno(r, "Failed to bind Varlink method: %m");
×
2677

2678
        r = sd_varlink_server_loop_auto(varlink_server);
212✔
2679
        if (r < 0)
212✔
2680
                return log_error_errno(r, "Failed to run Varlink event loop: %m");
×
2681

2682
        return 0;
2683
}
2684

2685
static int run(int argc, char *argv[]) {
1,328✔
2686
        int r;
1,328✔
2687

2688
        log_setup();
1,328✔
2689

2690
        char **args = NULL;
1,328✔
2691
        r = parse_argv(argc, argv, &args);
1,328✔
2692
        if (r <= 0)
1,328✔
2693
                return r;
1,328✔
2694

2695
        if (arg_varlink)
1,319✔
2696
                return vl_server(); /* Invocation as Varlink service */
212✔
2697

2698
        return dispatch_verb(args, NULL);
1,107✔
2699
}
2700

2701
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
1,328✔
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