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

systemd / systemd / 28689257516

03 Jul 2026 11:32PM UTC coverage: 72.9% (+0.3%) from 72.618%
28689257516

push

github

bluca
hwdb: map mic-mute key on Logitech K950 (Bluetooth)

The mic-mute key on the Logitech K950 keyboard (046D:B388, Bluetooth)
emits BTN_0 (scancode 0x100e1) instead of a usable key, so it does
nothing under GNOME/KDE. Map it to KEY_MICMUTE, its intended function.

Scancode determined with evtest on the device.

343301 of 470919 relevant lines covered (72.9%)

1344559.44 hits per line

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

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

3
#include <locale.h>
4
#include <sys/stat.h>
5
#include <unistd.h>
6

7
#include "sd-varlink.h"
8

9
#include "alloc-util.h"
10
#include "ansi-color.h"
11
#include "blockdev-list.h"
12
#include "build.h"
13
#include "build-path.h"
14
#include "bus-polkit.h"
15
#include "chase.h"
16
#include "conf-files.h"
17
#include "constants.h"
18
#include "efi-loader.h"
19
#include "efivars.h"
20
#include "env-file.h"
21
#include "escape.h"
22
#include "fd-util.h"
23
#include "find-esp.h"
24
#include "format-table.h"
25
#include "format-util.h"
26
#include "fs-util.h"
27
#include "glyph-util.h"
28
#include "hashmap.h"
29
#include "help-util.h"
30
#include "image-policy.h"
31
#include "json-util.h"
32
#include "locale-setup.h"
33
#include "log.h"
34
#include "loop-util.h"
35
#include "machine-credential.h"
36
#include "main-func.h"
37
#include "mount-util.h"
38
#include "options.h"
39
#include "os-util.h"
40
#include "parse-argument.h"
41
#include "parse-util.h"
42
#include "path-util.h"
43
#include "prompt-util.h"
44
#include "string-table.h"
45
#include "strv.h"
46
#include "terminal-util.h"
47
#include "varlink-io.systemd.SysInstall.h"
48
#include "varlink-util.h"
49

50
static char *arg_node = NULL;
51
static bool arg_welcome = true;
52
static int arg_erase = -1;            /* tri-state */
53
static bool arg_confirm = true;
54
static bool arg_summary = true;
55
static char **arg_definitions = NULL;
56
static char *arg_kernel_image = NULL;
57
static bool arg_reboot = false;
58
static int arg_touch_variables = -1;  /* tri-state */
59
static MachineCredentialContext arg_credentials = {};
60
static bool arg_copy_locale = true;
61
static bool arg_copy_keymap = true;
62
static bool arg_copy_timezone = true;
63
static bool arg_chrome = true;
64
static bool arg_mute_console = false;
65
static bool arg_varlink = false;
66

67
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
14✔
68
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
14✔
69
STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep);
14✔
70
STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
14✔
71

72
typedef enum ProgressPhase {
73
        PROGRESS_ENCRYPT_CREDENTIALS,
74
        PROGRESS_INSTALL_PARTITIONS,
75
        PROGRESS_MOUNT_PARTITIONS,
76
        PROGRESS_INSTALL_KERNEL,
77
        PROGRESS_INSTALL_BOOTLOADER,
78
        PROGRESS_UNMOUNT_PARTITIONS,
79
        _PROGRESS_PHASE_MAX,
80
        _PROGRESS_PHASE_INVALID = -EINVAL,
81
} ProgressPhase;
82

83
static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = {
84
        [PROGRESS_ENCRYPT_CREDENTIALS] = "encrypt-credentials",
85
        [PROGRESS_INSTALL_PARTITIONS]  = "install-partitions",
86
        [PROGRESS_MOUNT_PARTITIONS]    = "mount-partitions",
87
        [PROGRESS_INSTALL_KERNEL]      = "install-kernel",
88
        [PROGRESS_INSTALL_BOOTLOADER]  = "install-bootloader",
89
        [PROGRESS_UNMOUNT_PARTITIONS]  = "unmount-partitions",
90
};
91

92
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase);
14✔
93

94
static const char *progress_phase_log_table[_PROGRESS_PHASE_MAX] = {
95
        [PROGRESS_ENCRYPT_CREDENTIALS] = "Encrypting credentials...",
96
        [PROGRESS_INSTALL_PARTITIONS]  = "Installing partitions...",
97
        [PROGRESS_MOUNT_PARTITIONS]    = "Mounting partitions...",
98
        [PROGRESS_INSTALL_KERNEL]      = "Installing kernel...",
99
        [PROGRESS_INSTALL_BOOTLOADER]  = "Installing boot loader...",
100
        [PROGRESS_UNMOUNT_PARTITIONS]  = "Unmounting partitions...",
101
};
102

103
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase_log, ProgressPhase);
20✔
104

105
typedef enum DeviceFit {
106
        DEVICE_FIT_ENOUGH_FREE_SPACE,
107
        DEVICE_FIT_INSUFFICIENT_FREE_SPACE,
108
        DEVICE_FIT_DISK_TOO_SMALL,
109
        DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT,
110
        _DEVICE_FIT_MAX,
111
        _DEVICE_FIT_INVALID = -EINVAL,
112
} DeviceFit;
113

114
static const char *device_fit_table[_DEVICE_FIT_MAX] = {
115
        [DEVICE_FIT_ENOUGH_FREE_SPACE]              = "enough-free-space",
116
        [DEVICE_FIT_INSUFFICIENT_FREE_SPACE]        = "insufficient-free-space",
117
        [DEVICE_FIT_DISK_TOO_SMALL]                 = "disk-too-small",
118
        [DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT] = "conflicting-disk-label-present",
119
};
120

121
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(device_fit, DeviceFit);
×
122

123
typedef struct SysInstallContext {
124
        bool copy_locale;
125
        bool copy_keymap;
126
        bool copy_timezone;
127
        MachineCredentialContext credentials;
128
        char **definitions;
129
        bool erase;
130
        char *node;
131
        char *kernel_filename;
132
        int kernel_fd;
133
        bool touch_variables;
134

135
        sd_varlink *repart_link;
136

137
        sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */
138
} SysInstallContext;
139

140
static void sysinstall_context_done(SysInstallContext *c) {
2✔
141
        assert(c);
2✔
142

143
        strv_free(c->definitions);
2✔
144

145
        free(c->node);
2✔
146

147
        free(c->kernel_filename);
2✔
148
        safe_close(c->kernel_fd);
2✔
149

150
        machine_credential_context_done(&c->credentials);
2✔
151

152
        sd_varlink_flush_close_unref(c->repart_link);
2✔
153

154
        sd_varlink_unref(c->link);
2✔
155
}
2✔
156

157
static int help(void) {
×
158
        int r;
×
159

160
        _cleanup_(table_unrefp) Table *options = NULL;
×
161
        r = option_parser_get_help_table(&options);
×
162
        if (r < 0)
×
163
                return r;
164

165
        help_cmdline("[OPTIONS...] [DEVICE]");
×
166
        help_abstract("Installs the OS to another block device.");
×
167
        help_section("Options:");
×
168

169
        r = table_print_or_warn(options);
×
170
        if (r < 0)
×
171
                return r;
172

173
        help_man_page_reference("systemd-sysinstall", "8");
×
174

175
        return 0;
176
}
177

178
static int parse_argv(int argc, char *argv[]) {
14✔
179
        int r;
14✔
180

181
        assert(argc >= 0);
14✔
182
        assert(argv);
14✔
183

184
        OptionParser opts = { argc, argv };
14✔
185

186
        FOREACH_OPTION_OR_RETURN(c, &opts)
56✔
187
                switch (c) {
14✔
188

189
                OPTION_COMMON_HELP:
×
190
                        return help();
×
191

192
                OPTION_COMMON_VERSION:
×
193
                        return version();
×
194

195
                OPTION_LONG("welcome", "no", "Disable the welcome text"):
1✔
196
                        r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome);
1✔
197
                        if (r < 0)
1✔
198
                                return r;
199

200
                        break;
201

202
                OPTION_LONG("erase", "BOOL", "Whether to erase the target disk"):
1✔
203
                        r = parse_tristate_argument_with_auto("--erase=", opts.arg, &arg_erase);
1✔
204
                        if (r < 0)
1✔
205
                                return r;
206
                        break;
207

208
                OPTION_LONG("confirm", "no", "Disable query for confirmation"):
1✔
209
                        r = parse_boolean_argument("--confirm=", opts.arg, &arg_confirm);
1✔
210
                        if (r < 0)
1✔
211
                                return r;
212
                        break;
213

214
                OPTION_LONG("summary", "no", "Disable summary before beginning operation"):
1✔
215
                        r = parse_boolean_argument("--summary=", opts.arg, &arg_summary);
1✔
216
                        if (r < 0)
1✔
217
                                return r;
218
                        break;
219

220
                OPTION_LONG("definitions", "DIR", "Find partition definitions in specified directory"): {
1✔
221
                        _cleanup_free_ char *path = NULL;
1✔
222
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &path);
1✔
223
                        if (r < 0)
1✔
224
                                return r;
225
                        if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0)
1✔
226
                                return log_oom();
×
227
                        break;
1✔
228
                }
229

230
                OPTION_LONG("reboot", "BOOL", "Whether to reboot after installation is complete"):
1✔
231
                        r = parse_boolean_argument("--reboot=", opts.arg, &arg_reboot);
1✔
232
                        if (r < 0)
1✔
233
                                return r;
234
                        break;
235

236
                OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"):
1✔
237
                        r = parse_tristate_argument_with_auto("--variables=", opts.arg, &arg_touch_variables);
1✔
238
                        if (r < 0)
1✔
239
                                return r;
240
                        break;
241

242
                OPTION_LONG("kernel", "IMAGE", "Explicitly pick kernel image to install"):
1✔
243
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_kernel_image);
1✔
244
                        if (r < 0)
1✔
245
                                return r;
246
                        break;
247

248
                OPTION_LONG("set-credential", "ID:VALUE", "Install a credential with literal value to target system"):
1✔
249
                        r = machine_credential_set(&arg_credentials, opts.arg);
1✔
250
                        if (r < 0)
1✔
251
                                return r;
252
                        break;
253

254
                OPTION_LONG("load-credential", "ID:PATH", "Load a credential to install to new system from file or AF_UNIX stream socket"):
×
255
                        r = machine_credential_load(&arg_credentials, opts.arg);
×
256
                        if (r < 0)
×
257
                                return r;
258

259
                        break;
260

261
                OPTION_LONG("copy-locale", "no", "Don't copy current locale to target system"):
1✔
262
                        r = parse_boolean_argument("--copy-locale=", opts.arg, &arg_copy_locale);
1✔
263
                        if (r < 0)
1✔
264
                                return r;
265
                        break;
266

267
                OPTION_LONG("copy-keymap", "no", "Don't copy current keymap to target system"):
1✔
268
                        r = parse_boolean_argument("--copy-keymap=", opts.arg, &arg_copy_keymap);
1✔
269
                        if (r < 0)
1✔
270
                                return r;
271
                        break;
272

273
                OPTION_LONG("copy-timezone", "no", "Don't copy current timezone to target system"):
1✔
274
                        r = parse_boolean_argument("--copy-timezone=", opts.arg, &arg_copy_timezone);
1✔
275
                        if (r < 0)
1✔
276
                                return r;
277
                        break;
278

279
                OPTION_LONG("chrome", "no", "Whether to show a color bar at top and bottom of terminal"):
1✔
280
                        r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome);
1✔
281
                        if (r < 0)
1✔
282
                                return r;
283

284
                        break;
285

286
                OPTION_LONG("mute-console", "BOOL", "Whether to disallow kernel/PID 1 writes to the console while running"):
1✔
287
                        r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console);
1✔
288
                        if (r < 0)
1✔
289
                                return r;
290
                        break;
291
                }
292

293
        char **args = option_parser_get_args(&opts);
14✔
294

295
        if (strv_length(args) > 1)
14✔
296
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
×
297
        if (!strv_isempty(args)) {
14✔
298
                arg_node = strdup(args[0]);
1✔
299
                if (!arg_node)
1✔
300
                        return log_oom();
×
301
        }
302

303
        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
14✔
304
        if (r < 0)
14✔
305
                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
×
306
        if (r > 0)
14✔
307
                arg_varlink = true;
13✔
308

309
        return 1;
310
}
311

312
static int print_welcome(sd_varlink **mute_console_link) {
1✔
313
        _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL;
1✔
314
        const char *pn, *ac;
1✔
315
        int r;
1✔
316

317
        assert(mute_console_link);
1✔
318

319
        if (!*mute_console_link && arg_mute_console)
1✔
320
                (void) mute_console(mute_console_link);
×
321

322
        if (!arg_welcome)
1✔
323
                return 0;
324

325
        r = parse_os_release(
×
326
                        /* root= */ NULL,
327
                        "PRETTY_NAME", &pretty_name,
328
                        "FANCY_NAME",  &fancy_name,
329
                        "NAME",        &os_name,
330
                        "ANSI_COLOR",  &ansi_color);
331
        if (r < 0)
×
332
                log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
×
333
                               "Failed to read os-release file, ignoring: %m");
334

335
        pn = os_release_pretty_name(pretty_name, os_name);
×
336
        ac = isempty(ansi_color) ? "0" : ansi_color;
×
337

338
        if (use_fancy_name(unescape_fancy_name(&fancy_name)))
×
339
                printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", fancy_name);
×
340
        else if (colors_enabled())
×
341
                printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", ac, pn);
×
342
        else
343
                printf("Welcome to the %s Installer!\n", pn);
×
344

345
        putchar('\n');
1✔
346

347
        return 0;
348
}
349

350
static int connect_to_repart(sd_varlink **link) {
3✔
351
        int r;
3✔
352

353
        assert(link);
3✔
354

355
        if (*link) {
3✔
356
                /* Reset the time-out to default here, since we are reusing the connection, but might enqueue
357
                 * a different operation */
358
                r = sd_varlink_set_relative_timeout(*link, 0);
×
359
                if (r < 0)
×
360
                        return r;
3✔
361

362
                return 0;
×
363
        }
364

365
        _cleanup_close_ int fd = -EBADF;
3✔
366
        _cleanup_free_ char *repart = NULL;
3✔
367
        fd = pin_callout_binary("systemd-repart", &repart);
3✔
368
        if (fd < 0)
3✔
369
                return log_error_errno(fd, "Failed to find systemd-repart binary: %m");
×
370

371
        r = sd_varlink_connect_exec(link, repart, /* argv= */ NULL);
3✔
372
        if (r < 0)
3✔
373
                return log_error_errno(r, "Failed to connect to systemd-repart: %m");
×
374

375
        return 1;
376
}
377

378
static int acquire_device_list(
×
379
                sd_varlink **link,
380
                char ***ret_menu,
381
                char ***ret_accepted) {
382
        int r;
×
383

384
        r = connect_to_repart(link);
×
385
        if (r < 0)
×
386
                return r;
×
387

388
        _cleanup_strv_free_ char **menu = NULL, **accepted = NULL;
×
389

390
        sd_json_variant *reply = NULL;
×
391
        const char *error_id = NULL;
×
392
        r = sd_varlink_collectbo(
×
393
                        *link,
394
                        "io.systemd.Repart.ListCandidateDevices",
395
                        &reply,
396
                        &error_id,
397
                        SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true));
398
        if (r < 0)
×
399
                return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
×
400
        if (streq_ptr(error_id, "io.systemd.Repart.NoCandidateDevices"))
×
401
                log_debug("No candidate devices found.");
×
402
        else if (error_id) {
×
403
                r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */
×
404
                if (r != -EBADR)
×
405
                        return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
×
406

407
                return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %s", error_id);
×
408
        } else {
409
                sd_json_variant *i;
×
410
                JSON_VARIANT_ARRAY_FOREACH(i, reply) {
×
411
                        _cleanup_(block_device_done) BlockDevice bd = BLOCK_DEVICE_NULL;
×
412

413
                        static const sd_json_dispatch_field dispatch_table[] = {
×
414
                                { "node",     SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(BlockDevice, node),     SD_JSON_MANDATORY },
415
                                { "symlinks", SD_JSON_VARIANT_ARRAY,  sd_json_dispatch_strv,   offsetof(BlockDevice, symlinks), 0                 },
416
                                {}
417
                        };
418

419
                        r = sd_json_dispatch(i, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &bd);
×
420
                        if (r < 0)
×
421
                                return r;
422

423
                        if (strv_extend(&accepted, bd.node) < 0)
×
424
                                return log_oom();
×
425
                        if (strv_extend_strv(&accepted, bd.symlinks, /* filter_duplicates= */ true) < 0)
×
426
                                return log_oom();
×
427

428
                        /* Prefer the by-id and by-loop-ref because they typically contain the strings most
429
                         * directly understood by the user */
430
                        const char *n = strv_find_prefix(bd.symlinks, "/dev/disk/by-id/");
×
431
                        if (!n)
×
432
                                n = strv_find_prefix(bd.symlinks, "/dev/disk/by-loop-ref/");
×
433
                        if (!n)
×
434
                                n = bd.node;
×
435

436
                        if (strv_extend(&menu, n) < 0)
×
437
                                return log_oom();
×
438
                }
439
        }
440

441
        *ret_menu = TAKE_PTR(menu);
×
442
        *ret_accepted = TAKE_PTR(accepted);
×
443
        return 0;
×
444
}
445

446
static int device_is_valid(const char *node, void *userdata) {
×
447

448
        if (!path_is_valid(node) || !path_is_absolute(node)) {
×
449
                log_error("Not a valid absolute file system path, refusing: %s", node);
×
450
                return false;
×
451
        }
452

453
        struct stat st;
×
454
        if (stat(node, &st) < 0) {
×
455
                log_error_errno(errno, "Failed to check if '%s' is a valid block device node: %m", node);
×
456
                return false;
457
        }
458
        if (!S_ISBLK(st.st_mode)) {
×
459
                log_error("Path '%s' does not refer to a valid block device node, refusing.", node);
×
460
                return false;
461
        }
462

463
        return true;
464
}
465

466
static int refresh_devices(char ***ret_menu, char ***ret_accepted, void *userdata) {
×
467
        sd_varlink **repart_link = ASSERT_PTR(userdata);
×
468

469
        (void) acquire_device_list(repart_link, ret_menu, ret_accepted);
×
470
        return 0;
×
471
}
472

473
static int prompt_block_device(sd_varlink **repart_link, char **ret_node) {
×
474
        int r;
×
475

476
        putchar('\n');
×
477

478
        _cleanup_strv_free_ char **menu = NULL, **accepted = NULL;
×
479
        (void) acquire_device_list(repart_link, &menu, &accepted);
×
480

481
        r = prompt_loop("Please enter target disk device",
×
482
                        GLYPH_COMPUTER_DISK,
483
                        /* prefill= */ NULL,
484
                        menu,
485
                        accepted,
486
                        /* ellipsize_percentage= */ 20,
487
                        /* n_columns= */ 1,
488
                        /* column_width= */ 80,
489
                        device_is_valid,
490
                        refresh_devices,
491
                        /* userdata= */ repart_link,
492
                        PROMPT_SHOW_MENU|PROMPT_SHOW_MENU_NOW|PROMPT_MAY_SKIP|PROMPT_HIDE_SKIP_HINT|PROMPT_HIDE_MENU_HINT,
493
                        ret_node);
494
        if (r < 0)
×
495
                return r;
496
        if (r == 0)
×
497
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
498

499
        return 0;
500
}
501

502
static int sysinstall_context_notify(
20✔
503
                SysInstallContext *context,
504
                ProgressPhase phase,
505
                const char *object,
506
                unsigned percent) {
507

508
        int r;
20✔
509

510
        assert(context);
20✔
511
        assert(phase >= 0);
20✔
512
        assert(phase < _PROGRESS_PHASE_MAX);
20✔
513

514
        log_notice("%s%s%s",
40✔
515
                   emoji_enabled() ? phase == PROGRESS_ENCRYPT_CREDENTIALS ?  glyph(GLYPH_LOCK_AND_KEY) : glyph(GLYPH_COMPUTER_DISK) : "",
516
                   emoji_enabled() ? " " : "",
517
                   progress_phase_log_to_string(phase));
518

519
        if (context->link) {
20✔
520
                r = sd_varlink_notifybo(
14✔
521
                                context->link,
522
                                JSON_BUILD_PAIR_ENUM("phase", progress_phase_to_string(phase)),
523
                                JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object),
524
                                JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX));
525
                if (r < 0)
14✔
526
                        log_debug_errno(r, "Failed to send varlink notify progress notification, ignoring: %m");
×
527

528
                r = sd_varlink_flush(context->link);
14✔
529
                if (r < 0)
14✔
530
                        log_debug_errno(r, "Failed to flush varlink notify progress notification, ignoring: %m");
×
531
        }
532

533
        return 0;
20✔
534
}
535

536
typedef struct RepartResult {
537
        int ret;
538
        SysInstallContext *context;
539
} RepartResult;
540

541
static int handle_repart_reply(
28✔
542
                sd_varlink *link,
543
                sd_json_variant *reply,
544
                const char *error_id,
545
                sd_varlink_reply_flags_t flags,
546
                void *userdata) {
547

548
        RepartResult *result = userdata;
28✔
549
        int r;
28✔
550

551
        assert(result);
28✔
552

553
        struct {
28✔
554
                uint64_t min_size;
555
                uint64_t current_size;
556
                uint64_t need_free;
557

558
                const char *phase;
559
                const char *object;
560
                unsigned progress;
561
        } p = {
28✔
562
                .min_size = UINT64_MAX,
563
                .current_size = UINT64_MAX,
564
                .need_free = UINT64_MAX,
565
                .progress = UINT_MAX,
566
        };
567

568
        static const sd_json_dispatch_field dispatch_table[] = {
28✔
569
                { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, min_size),     0 },
570
                { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, current_size), 0 },
571
                { "needFreeBytes",    _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, need_free),    0 },
572
                { "phase",            _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, phase),        0 },
573
                { "object",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, object),       0 },
574
                { "progress",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,         voffsetof(p, progress),     0 },
575
                {}
576
        };
577

578
        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
28✔
579
        if (r < 0)
28✔
580
                return result->ret = r;
×
581

582
        if (error_id) {
28✔
583
                const char *sysinstall_error_id = NULL;
×
584

585
                if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) {
×
586
                        sysinstall_error_id = "io.systemd.SysInstall.InsufficientFreeSpace";
×
587
                        result->ret = log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "Not enough free space on disk, cannot install.");
×
588
                } else if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) {
×
589
                        sysinstall_error_id = "io.systemd.SysInstall.DiskTooSmall";
×
590

591
                        result->ret = log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Disk too small for installation, cannot install.");
×
592
                } else if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) {
×
593
                        sysinstall_error_id = "io.systemd.SysInstall.ConflictingDiskLabelPresent";
×
594

595
                        result->ret = log_error_errno(
×
596
                                        SYNTHETIC_ERRNO(EHWPOISON),
597
                                        "A conflicting disk label is already present on the target disk, cannot install unless disk is erased.");
598
                }
599

600
                if (sysinstall_error_id && result->context->link) {
×
601
                        r = sd_varlink_errorbo(
×
602
                                        result->context->link,
603
                                        sysinstall_error_id,
604
                                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", p.current_size, UINT64_MAX),
605
                                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", p.need_free, UINT64_MAX),
606
                                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", p.min_size, UINT64_MAX));
607

608
                        if (r < 0)
×
609
                                return result->ret = r;
×
610

611
                        return result->ret;
×
612
                }
613

614
                r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */
×
615
                if (r != -EBADR)
×
616
                        return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
×
617

618
                return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id);
×
619
        }
620

621
        if ((p.progress != UINT_MAX || p.object) && result->context->link)
28✔
622
                (void) sysinstall_context_notify(result->context, PROGRESS_INSTALL_PARTITIONS, p.object, p.progress);
8✔
623

624
        return result->ret = 0;
28✔
625
}
626

627
static int sysinstall_context_invoke_repart_run(SysInstallContext *context) {
2✔
628

629
        int r;
2✔
630

631
        assert(context);
2✔
632

633
        r = connect_to_repart(&context->repart_link);
2✔
634
        if (r < 0)
2✔
635
                return r;
2✔
636

637
        /* Seeding the partitions might be very slow, disable timeout */
638
        r = sd_varlink_set_relative_timeout(context->repart_link, UINT64_MAX);
2✔
639
        if (r < 0)
2✔
640
                return log_error_errno(r, "Failed to disable IPC timeout: %m");
×
641

642
        RepartResult result = {
2✔
643
                .context = context,
644
        };
645

646
        sd_varlink_set_userdata(context->repart_link, &result);
2✔
647

648
        r = sd_varlink_bind_reply(context->repart_link, handle_repart_reply);
2✔
649
        if (r < 0)
2✔
650
                return log_error_errno(r, "Failed to bind repart reply callback: %m");
×
651

652
        r = sd_varlink_observebo(
2✔
653
                        context->repart_link,
654
                        "io.systemd.Repart.Run",
655
                        SD_JSON_BUILD_PAIR_STRING("node", context->node),
656
                        SD_JSON_BUILD_PAIR_STRING("empty", context->erase ? "force" : "allow"),
657
                        SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", false),
658
                        SD_JSON_BUILD_PAIR_CONDITION(!!context->definitions, "definitions", SD_JSON_BUILD_STRV(context->definitions)),
659
                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
660
                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
661
        if (r < 0)
2✔
662
                return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
×
663

664
        for (;;) {
101✔
665
                r = sd_varlink_is_idle(context->repart_link);
101✔
666
                if (r < 0)
101✔
667
                        return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
×
668
                if (r > 0)
101✔
669
                        break;
670

671
                r = sd_varlink_process(context->repart_link);
99✔
672
                if (r < 0)
99✔
673
                        return log_error_errno(r, "Failed to process varlink connection: %m");
×
674
                if (r != 0)
99✔
675
                        continue;
79✔
676

677
                r = sd_varlink_wait(context->repart_link, USEC_INFINITY);
20✔
678
                if (r < 0)
20✔
679
                        return log_error_errno(r, "Failed to wait for varlink connection events: %m");
×
680
        }
681

682
        sd_varlink_set_userdata(context->repart_link, NULL);
2✔
683

684
        r = sd_varlink_bind_reply(context->repart_link, NULL);
2✔
685
        if (r < 0)
2✔
686
                return log_error_errno(r, "Failed to unbind repart reply callback: %m");
×
687

688
        return result.ret;
2✔
689
}
690

691
static int read_space_metrics(
1✔
692
                sd_json_variant *v,
693
                uint64_t *min_size,
694
                uint64_t *current_size,
695
                uint64_t *need_free) {
696

697
        int r;
1✔
698

699
        struct {
1✔
700
                uint64_t min_size;
701
                uint64_t current_size;
702
                uint64_t need_free;
703
        } p = {
1✔
704
                .min_size = UINT64_MAX,
705
                .current_size = UINT64_MAX,
706
                .need_free = UINT64_MAX,
707
        };
708

709
        static const sd_json_dispatch_field dispatch_table[] = {
1✔
710
                { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size),     0 },
711
                { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 },
712
                { "needFreeBytes",    _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free),    0 },
713
                {}
714
        };
715

716
        r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
1✔
717
        if (r < 0)
1✔
718
                return r;
1✔
719

720
        if (min_size)
1✔
721
                *min_size = p.min_size;
1✔
722
        if (current_size)
1✔
723
                *current_size = p.current_size;
1✔
724
        if (need_free)
1✔
725
                *need_free = p.need_free;
1✔
726

727
        return 0;
728
}
729

730
static int invoke_repart(
1✔
731
                sd_varlink **link,
732
                const char *node,
733
                bool erase,
734
                bool dry_run,
735
                char **definitions,
736
                uint64_t *min_size,        /* initialized both on success and error */
737
                uint64_t *current_size,    /* ditto */
738
                uint64_t *need_free) {     /* ditto */
739

740
        int r;
1✔
741

742
        assert(link);
1✔
743

744
        /* Note, if dry_run is true, then ENOSPC, E2BIG, EHWPOISON will not be logged about beyond LOG_DEBUG,
745
         * but all other errors will be */
746

747
        r = connect_to_repart(link);
1✔
748
        if (r < 0) {
1✔
749
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
750
                return r;
×
751
        }
752

753
        if (!dry_run) {
1✔
754
                /* Seeding the partitions might be very slow, disable timeout */
755
                r = sd_varlink_set_relative_timeout(*link, UINT64_MAX);
×
756
                if (r < 0)
×
757
                        return log_error_errno(r, "Failed to disable IPC timeout: %m");
×
758
        }
759

760
        sd_json_variant *reply = NULL;
1✔
761
        const char *error_id = NULL;
1✔
762
        r = sd_varlink_callbo(
1✔
763
                        *link,
764
                        "io.systemd.Repart.Run",
765
                        &reply,
766
                        &error_id,
767
                        SD_JSON_BUILD_PAIR_STRING("node", node),
768
                        SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"),
769
                        SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run),
770
                        SD_JSON_BUILD_PAIR_CONDITION(!!definitions, "definitions", SD_JSON_BUILD_STRV(definitions)),
771
                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
772
                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
773
        if (r < 0) {
1✔
774
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
775
                return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
×
776
        }
777
        if (error_id) {
1✔
778
                if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) {
×
779
                        (void) read_space_metrics(reply, min_size, current_size, need_free);
×
780
                        return log_full_errno(
×
781
                                        dry_run ? LOG_DEBUG : LOG_ERR,
782
                                        SYNTHETIC_ERRNO(ENOSPC),
783
                                        "Not enough free space on disk, cannot install.");
784
                }
785
                if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) {
×
786
                        (void) read_space_metrics(reply, min_size, current_size, need_free);
×
787
                        return log_full_errno(
×
788
                                        dry_run ? LOG_DEBUG : LOG_ERR,
789
                                        SYNTHETIC_ERRNO(E2BIG),
790
                                        "Disk too small for installation, cannot install.");
791
                }
792

793
                /* For all other errors reset the metrics */
794
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
795

796
                if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent"))
×
797
                        return log_full_errno(
×
798
                                        dry_run ? LOG_DEBUG : LOG_ERR,
799
                                        SYNTHETIC_ERRNO(EHWPOISON),
800
                                        "A conflicting disk label is already present on the target disk, cannot install unless disk is erased.");
801

802
                r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */
×
803
                if (r != -EBADR)
×
804
                        return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
×
805

806
                return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id);
×
807
        }
808

809
        (void) read_space_metrics(reply, min_size, current_size, need_free);
1✔
810

811
        return 0;
812
}
813

814
static int prompt_erase(
×
815
                bool can_add,
816
                int *ret_erase) {
817
        int r;
×
818

819
        assert(ret_erase);
×
820

821
        putchar('\n');
×
822

823
        char **l = can_add ? STRV_MAKE("keep", "erase") : STRV_MAKE("erase");
×
824

825
        _cleanup_free_ char *reply = NULL;
×
826
        r = prompt_loop(can_add ?
×
827
                        "Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk" :
828
                        "Please type 'erase' to confirm that all data on the disk shall be erased",
829
                        GLYPH_BROOM,
830
                        /* prefill= */ NULL,
831
                        /* menu= */ l,
832
                        /* accepted= */ l,
833
                        /* ellipsize_percentage= */ 20,
834
                        /* n_columns= */ 2,
835
                        /* column_width= */ 40,
836
                        /* is_valid= */ NULL,
837
                        /* refresh= */ NULL,
838
                        /* userdata= */ NULL,
839
                        PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
840
                        &reply);
841
        if (r < 0)
×
842
                return r;
843
        if (r == 0)
×
844
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
845

846
        if (streq(reply, "erase"))
×
847
                *ret_erase = true;
×
848
        else if (streq(reply, "keep"))
×
849
                *ret_erase = false;
×
850
        else
851
                assert_not_reached();
×
852

853
        return 0;
854
}
855

856
static int prompt_touch_variables(void) {
1✔
857
        int r;
1✔
858

859
        if (arg_touch_variables >= 0)
1✔
860
                return 0;
1✔
861

862
        putchar('\n');
×
863

864
        char **l = STRV_MAKE("yes", "no");
×
865

866
        _cleanup_free_ char *reply = NULL;
×
867
        r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise",
×
868
                        GLYPH_ROCKET,
869
                        /* prefill= */ "yes",
870
                        /* menu= */ l,
871
                        /* accepted= */ l,
872
                        /* ellipsize_percentage= */ 20,
873
                        /* n_columns= */ 2,
874
                        /* column_width= */ 40,
875
                        /* is_valid= */ NULL,
876
                        /* refresh= */ NULL,
877
                        /* userdata= */ NULL,
878
                        PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
879
                        &reply);
880
        if (r < 0)
×
881
                return r;
882
        if (r == 0)
×
883
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
884

885
        r = parse_boolean(reply);
×
886
        if (r < 0)
×
887
                return log_error_errno(r, "Failed to parse reply: %s", reply);
×
888

889
        arg_touch_variables = r;
×
890

891
        return 0;
×
892
}
893

894
static int prompt_confirm(void) {
1✔
895
        int r;
1✔
896

897
        if (!arg_confirm)
1✔
898
                return 0;
1✔
899

900
        putchar('\n');
×
901

902
        bool yes;
×
903
        r = prompt_loop_yes_no(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" :
×
904
                                             "Please type 'yes' to begin the installation",
905
                               /* def= */ false,
906
                               &yes);
907
        if (r < 0)
×
908
                return r;
909
        if (!yes)
×
910
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation not confirmed, cancelling.");
×
911

912
        return 0;
913
}
914

915
static int validate_run(sd_varlink **repart_link, const char *node) {
1✔
916
        int r;
1✔
917

918
        assert(repart_link);
1✔
919
        assert(node);
1✔
920

921
        /* First loop: either with explicitly configured --erase= value, or false. A second loop only if not configured explicitly. */
922
        bool try_erase = arg_erase > 0, conflicting_disk_label = false;
1✔
923
        for (;;) {
1✔
924
                uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX;
1✔
925
                r = invoke_repart(
1✔
926
                                repart_link,
927
                                node,
928
                                try_erase,
929
                                /* dry_run= */ true,
930
                                arg_definitions,
931
                                &min_size,
932
                                &current_size,
933
                                &need_free);
934
                if (r == -ENOSPC) {
1✔
935
                        /* The disk is large enough, but there's not enough unallocated space. Hence proceed, but require erasing */
936
                        if (try_erase || arg_erase >= 0)
×
937
                                return log_error_errno(r, "The selected disk is big enough for the installation but does not have enough free space.");
×
938

939
                        log_notice("The selected disk is big enough for the installation but does not have enough free space. Installation will require erasing.");
×
940
                        if (need_free != UINT64_MAX)
×
941
                                log_info("Required free space is %s.", FORMAT_BYTES(need_free));
×
942

943
                        try_erase = true;
944
                } else if (r == -E2BIG) {
1✔
945
                        /* Won't fit, whatever we do */
946
                        log_error_errno(r, "The selected disk is not large enough for an OS installation.");
×
947
                        if (current_size != UINT64_MAX)
×
948
                                log_info("The size of the selected disk is %s, but a minimal size of %s is required.",
×
949
                                         FORMAT_BYTES(current_size),
950
                                         FORMAT_BYTES(min_size));
951
                        return r;
952
                } else if (r == -EHWPOISON) {
1✔
953
                        if (try_erase || arg_erase >= 0)
×
954
                                return log_error_errno(r, "The selected disk contains a conflicting disk label, refusing.");
×
955

956
                        log_debug("Disk contains a conflicting disk label, checking if we could install the OS after erasing it.");
×
957
                        try_erase = true;
×
958
                        conflicting_disk_label = true;
×
959
                        continue;
×
960
                } else if (r < 0)
1✔
961
                        /* invoke_repart() already logged about all other errors */
962
                        return r;
963
                else
964
                        /* Nice, we can add the OS to the disk, without erasing anything. */
965
                        log_info("The selected disk has enough free space for an installation of the OS.");
1✔
966

967
                if (conflicting_disk_label)
1✔
968
                        log_warning("A conflicting disk label has been found, and must be erased for installation.");
×
969

970
                if (arg_erase < 0) {
1✔
971
                        r = prompt_erase(/* can_add= */ !try_erase, &arg_erase);
×
972
                        if (r < 0)
×
973
                                return r;
×
974
                }
975

976
                return 0;
977
        }
978
}
979

980
static int sysinstall_context_show_summary(SysInstallContext *context) {
×
981
        int r;
×
982

983
        printf("\n"
×
984
               "%sSummary:%s\n", ansi_underline(), ansi_normal());
985

986
        _cleanup_(table_unrefp) Table *table = table_new_vertical();
×
987
        if (!table)
×
988
                return log_oom();
×
989

990
        r = table_add_many(
×
991
                        table,
992
                        TABLE_FIELD, "Selected Disk",
993
                        TABLE_STRING, context->node,
994
                        TABLE_FIELD, "Erase Disk",
995
                        TABLE_BOOLEAN, context->erase,
996
                        TABLE_SET_COLOR, context->erase ? ansi_highlight_red() : NULL,
997
                        TABLE_FIELD, "Register in Firmware",
998
                        TABLE_BOOLEAN, context->touch_variables);
999
        if (r < 0)
×
1000
                return table_log_add_error(r);
×
1001

1002
        static const char * const map[] = {
1003
                "firstboot.keymap",          "Keyboard Map",
1004
                "firstboot.locale",          "Locale",
1005
                "firstboot.locale-messages", "Locale (Messages)",
1006
                "firstboot.timezone",        "Timezone",
1007
                NULL
1008
        };
1009

1010
        STRV_FOREACH_PAIR(id, text, map) {
×
1011
                MachineCredential *c = machine_credential_find(&context->credentials, *id);
×
1012
                if (!c)
×
1013
                        continue;
×
1014

1015
                _cleanup_free_ char *escaped = cescape_length(c->data, c->size);
×
1016
                if (!escaped)
×
1017
                        return log_oom();
×
1018

1019
                r = table_add_many(
×
1020
                                table,
1021
                                TABLE_FIELD, *text,
1022
                                TABLE_STRING, escaped);
1023
                if (r < 0)
×
1024
                        return table_log_add_error(r);
×
1025
        }
1026

1027
        unsigned n_extra_credentials = 0;
×
1028
        FOREACH_ARRAY(cred, context->credentials.credentials, context->credentials.n_credentials) {
×
1029
                bool covered = false;
×
1030

1031
                STRV_FOREACH_PAIR(id, text, map)
×
1032
                        if (streq(*id, cred->id)) {
×
1033
                                covered = true;
1034
                                break;
1035
                        }
1036

1037
                if (!covered)
×
1038
                        n_extra_credentials++;
×
1039
        }
1040

1041
        if (n_extra_credentials > 0) {
×
1042
                r = table_add_many(
×
1043
                                table,
1044
                                TABLE_FIELD, "Extra Credentials",
1045
                                TABLE_UINT, n_extra_credentials);
1046
                if (r < 0)
×
1047
                        return table_log_add_error(r);
×
1048
        }
1049

1050
        r = table_print(table);
×
1051
        if (r < 0)
×
1052
                return r;
×
1053

1054
        return 0;
1055
}
1056

1057
static int find_current_kernel(
×
1058
                char **ret_filename,
1059
                int *ret_fd) {
1060

1061
        int r;
×
1062

1063
        sd_id128_t uuid;
×
1064
        r = efi_stub_get_device_part_uuid(&uuid);
×
1065
        if (r == -ENOENT)
×
1066
                return log_error_errno(r, "Cannot find current kernel, no stub partition UUID passed via EFI variables.");
×
1067
        if (r < 0)
×
1068
                return log_error_errno(r, "Unable to determine stub partition UUID: %m");
×
1069

1070
        _cleanup_free_ char *image = NULL;
×
1071
        r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &image);
×
1072
        if (r == -ENOENT)
×
1073
                return log_error_errno(r, "Cannot find current kernel, no stub EFI binary path passed.");
×
1074
        if (r < 0)
×
1075
                return log_error_errno(r, "Unable to determine stub EFI binary path: %m");
×
1076

1077
        /* Note: we search for the *host* ESP here (i.e. the one the current EFI paths relate to), not the
1078
         * one of the target image */
1079

1080
        _cleanup_free_ char *partition_path = NULL;
×
1081
        _cleanup_close_ int partition_fd = -EBADF;
×
1082
        sd_id128_t partition_uuid;
×
1083
        r = find_esp_and_warn_full(
×
1084
                        /* root= */ NULL,
1085
                        /* path= */ NULL,
1086
                        /* unprivileged_mode= */ false,
1087
                        &partition_path,
1088
                        &partition_fd,
1089
                        /* ret_part= */ NULL,
1090
                        /* ret_pstart= */ NULL,
1091
                        /* ret_psize= */ NULL,
1092
                        &partition_uuid,
1093
                        /* ret_devid= */ NULL);
1094
        if (r < 0 && r != -ENOKEY)
×
1095
                return r;
1096
        if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) {
×
1097
                partition_path = mfree(partition_path);
×
1098
                partition_fd = safe_close(partition_fd);
×
1099

1100
                r = find_xbootldr_and_warn_full(
×
1101
                                /* root= */ NULL,
1102
                                /* path= */ NULL,
1103
                                /* unprivileged_mode= */ false,
1104
                                &partition_path,
1105
                                &partition_fd,
1106
                                &partition_uuid,
1107
                                /* ret_devid= */ NULL);
1108
                if (r < 0 && r != -ENOKEY)
×
1109
                        return r;
1110

1111
                if (r < 0 || !sd_id128_equal(uuid, partition_uuid))
×
1112
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Unable to find UKI on ESP/XBOOTLDR partitions.");
×
1113
        }
1114

1115
        _cleanup_free_ char *resolved = NULL;
×
1116
        _cleanup_close_ int fd = chase_and_openat(
×
1117
                        /* root_fd= */ partition_fd,
1118
                        /* dir_fd= */ partition_fd,
1119
                        image,
1120
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR,
1121
                        O_RDONLY|O_CLOEXEC,
1122
                        &resolved);
1123
        if (fd < 0)
×
1124
                return log_error_errno(fd, "Failed to find EFI binary '%s' on partition '%s': %m", image, partition_path);
×
1125

1126
        _cleanup_free_ char *fn = NULL;
×
1127
        r = path_extract_filename(resolved, &fn);
×
1128
        if (r < 0)
×
1129
                return log_error_errno(r, "Failed to extract UKI file name from '%s': %m", resolved);
×
1130

1131
        if (ret_filename)
×
1132
                *ret_filename = TAKE_PTR(fn);
×
1133
        if (ret_fd)
×
1134
                *ret_fd = TAKE_FD(fd);
×
1135

1136
        return 0;
1137
}
1138

1139
static int connect_to_bootctl(sd_varlink **link) {
4✔
1140
        int r;
4✔
1141

1142
        assert(link);
4✔
1143

1144
        if (*link)
4✔
1145
                return 0;
4✔
1146

1147
        _cleanup_close_ int fd = -EBADF;
4✔
1148
        _cleanup_free_ char *bootctl = NULL;
2✔
1149
        fd = pin_callout_binary("bootctl", &bootctl);
2✔
1150
        if (fd < 0)
2✔
1151
                return log_error_errno(fd, "Failed to find bootctl binary: %m");
×
1152

1153
        r = sd_varlink_connect_exec(link, bootctl, /* argv= */ NULL);
2✔
1154
        if (r < 0)
2✔
1155
                return log_error_errno(r, "Failed to connect to bootctl: %m");
×
1156

1157
        r = sd_varlink_set_allow_fd_passing_output(*link, true);
2✔
1158
        if (r < 0)
2✔
1159
                return log_error_errno(r, "Failed to enable fd passing to bootctl: %m");
×
1160

1161
        return 1;
1162
}
1163

1164
static int invoke_bootctl_install(
2✔
1165
                sd_varlink **link,
1166
                bool variables,
1167
                const char *root_dir,
1168
                int root_fd) {
1169
        int r;
2✔
1170

1171
        assert(link);
2✔
1172
        assert(root_dir);
2✔
1173
        assert(root_fd >= 0);
2✔
1174

1175
        r = connect_to_bootctl(link);
2✔
1176
        if (r < 0)
2✔
1177
                return r;
2✔
1178

1179
        int fd_idx = sd_varlink_push_dup_fd(*link, root_fd);
2✔
1180
        if (fd_idx < 0)
2✔
1181
                return log_error_errno(fd_idx, "Failed to submit root fd onto Varlink connection: %m");
×
1182

1183
        const char *error_id = NULL;
2✔
1184
        r = varlink_callbo_and_log(
2✔
1185
                        *link,
1186
                        "io.systemd.BootControl.Install",
1187
                        /* reply= */ NULL,
1188
                        &error_id,
1189
                        SD_JSON_BUILD_PAIR_STRING("operation", "new"),
1190
                        SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx),
1191
                        SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
1192
                        SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", variables));
1193
        if (r < 0)
2✔
1194
                return r;
×
1195

1196
        return 0;
1197
}
1198

1199
static int invoke_bootctl_link(
2✔
1200
                sd_varlink **link,
1201
                const char *root_dir,
1202
                int root_fd,
1203
                const char *kernel_filename,
1204
                int kernel_fd,
1205
                char **encrypted_credentials) {
1206
        int r;
2✔
1207

1208
        assert(link);
2✔
1209
        assert(root_dir);
2✔
1210
        assert(root_fd >= 0);
2✔
1211
        assert(kernel_filename);
2✔
1212
        assert(kernel_fd >= 0);
2✔
1213

1214
        r = connect_to_bootctl(link);
2✔
1215
        if (r < 0)
2✔
1216
                return r;
2✔
1217

1218
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
2✔
1219
        STRV_FOREACH_PAIR(name, value, encrypted_credentials) {
4✔
1220
                _cleanup_free_ char *j = strjoin(*name, ".cred");
4✔
1221
                if (!j)
2✔
1222
                        return log_oom();
×
1223

1224
                r = sd_json_variant_append_arraybo(
2✔
1225
                                &array,
1226
                                SD_JSON_BUILD_PAIR_STRING("filename", j),
1227
                                SD_JSON_BUILD_PAIR_BASE64("data", *value, strlen(*value)));
1228
                if (r < 0)
2✔
1229
                        return log_error_errno(r, "Failed to append credential to message: %m");
×
1230
        }
1231

1232
        int root_fd_idx = sd_varlink_push_dup_fd(*link, root_fd);
2✔
1233
        if (root_fd_idx < 0)
2✔
1234
                return log_error_errno(root_fd_idx, "Failed to submit root fd onto Varlink connection: %m");
×
1235

1236
        int kernel_fd_idx = sd_varlink_push_dup_fd(*link, kernel_fd);
2✔
1237
        if (kernel_fd_idx < 0)
2✔
1238
                return log_error_errno(kernel_fd_idx, "Failed to submit kernel fd onto Varlink connection: %m");
×
1239

1240
        const char *error_id = NULL;
2✔
1241
        r = varlink_callbo_and_log(
2✔
1242
                        *link,
1243
                        "io.systemd.BootControl.Link",
1244
                        /* reply= */ NULL,
1245
                        &error_id,
1246
                        SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", root_fd_idx),
1247
                        SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
1248
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("kernelFilename", kernel_filename),
1249
                        SD_JSON_BUILD_PAIR_INTEGER("kernelFileDescriptor", kernel_fd_idx),
1250
                        SD_JSON_BUILD_PAIR_CONDITION(!!array, "extraFiles", SD_JSON_BUILD_VARIANT(array)));
1251
        if (r < 0)
2✔
1252
                return r;
×
1253

1254
        return 0;
1255
}
1256

1257
static int maybe_reboot(void) {
1✔
1258
        int r;
1✔
1259

1260
        if (!arg_reboot)
1✔
1261
                return 0;
1✔
1262

1263
        log_notice("%s%sSystem will reboot now.",
×
1264
                   emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : "");
1265

1266
        if (!any_key_to_proceed())
×
1267
                return 0;
1268

1269
        log_notice("%s%sInitiating reboot.",
×
1270
                   emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : "");
1271

1272
        _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
1✔
1273
        r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.Shutdown");
×
1274
        if (r < 0)
×
1275
                return log_error_errno(r, "Failed to connect to systemd-logind: %m");
×
1276

1277
        sd_json_variant *reply = NULL;
×
1278
        const char *error_id = NULL;
×
1279
        r = varlink_callbo_and_log(
×
1280
                        link,
1281
                        "io.systemd.Shutdown.Reboot",
1282
                        &reply,
1283
                        &error_id);
1284
        if (r < 0)
×
1285
                return r;
×
1286

1287
        return 0;
1288
}
1289

1290
static int read_credential_locale(MachineCredentialContext *credentials) {
×
1291
        int r;
×
1292

1293
        if (machine_credential_find(credentials, "firstboot.locale") ||
×
1294
            machine_credential_find(credentials, "firstboot.locale-messages"))
×
1295
                return 0;
1296

1297
        /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since
1298
         * it seems to be one of the most fundamental ones, and is not LC_MESSAGES for which we have a
1299
         * separate setting after all */
1300
        const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL);
×
1301
        if (l) {
×
1302
                r = machine_credential_add(credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
×
1303
                if (r < 0)
×
1304
                        return log_oom();
×
1305
        }
1306

1307
        const char *m = setlocale(LC_MESSAGES, NULL);
×
1308
        if (m && !streq_ptr(m, l)) {
×
1309
                r = machine_credential_add(credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
×
1310
                if (r < 0)
×
1311
                        return log_oom();
×
1312
        }
1313

1314
        return 0;
1315
}
1316

1317
static int read_credential_keymap(MachineCredentialContext *credentials) {
×
1318
        int r;
×
1319

1320
        if (machine_credential_find(credentials, "firstboot.keymap"))
×
1321
                return 0;
×
1322

1323
        _cleanup_free_ char *keymap = NULL;
×
1324
        r = parse_env_file(
×
1325
                        /* f= */ NULL,
1326
                        etc_vconsole_conf(),
1327
                        "KEYMAP", &keymap);
1328
        if (r < 0 && r != -ENOENT)
×
1329
                return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf());
×
1330

1331
        if (!isempty(keymap)) {
×
1332
                r = machine_credential_add(credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
×
1333
                if (r < 0)
×
1334
                        return log_oom();
×
1335
        }
1336

1337
        return 0;
1338
}
1339

1340
static int read_credential_timezone(MachineCredentialContext *credentials) {
×
1341
        int r;
×
1342

1343
        if (machine_credential_find(credentials, "firstboot.timezone"))
×
1344
                return 0;
×
1345

1346
        _cleanup_free_ char *tz = NULL;
×
1347
        r = get_timezone_prefer_env(&tz);
×
1348
        if (r < 0)
×
1349
                log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m");
×
1350
        else {
1351
                r = machine_credential_add(credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
×
1352
                if (r < 0)
×
1353
                        return log_oom();
×
1354
        }
1355

1356
        return 0;
1357
}
1358

1359
static int sysinstall_context_read_credentials(SysInstallContext *context) {
2✔
1360
        int r;
2✔
1361

1362
        if (context->copy_locale) {
2✔
1363
                r = read_credential_locale(&context->credentials);
×
1364
                if (r < 0)
×
1365
                        return r;
1366
        }
1367

1368
        if (context->copy_keymap) {
2✔
1369
                r = read_credential_keymap(&context->credentials);
×
1370
                if (r < 0)
×
1371
                        return r;
1372
        }
1373

1374
        if (context->copy_timezone) {
2✔
1375
                r = read_credential_timezone(&context->credentials);
×
1376
                if (r < 0)
×
1377
                        return r;
×
1378
        }
1379

1380
        return 0;
1381
}
1382

1383
static int connect_to_creds(sd_varlink **link) {
2✔
1384
        int r;
2✔
1385

1386
        assert(link);
2✔
1387

1388
        if (*link)
2✔
1389
                return 0;
2✔
1390

1391
        _cleanup_close_ int fd = -EBADF;
2✔
1392
        _cleanup_free_ char *creds = NULL;
2✔
1393
        fd = pin_callout_binary("systemd-creds", &creds);
2✔
1394
        if (fd < 0)
2✔
1395
                return log_error_errno(fd, "Failed to find systemd-creds binary: %m");
×
1396

1397
        r = sd_varlink_connect_exec(link, creds, /* argv= */ NULL);
2✔
1398
        if (r < 0)
2✔
1399
                return log_error_errno(r, "Failed to connect to systemd-creds: %m");
×
1400

1401
        return 1;
1402
}
1403

1404
static int encrypt_one_credential(sd_varlink **link, const MachineCredential *input, char ***encrypted) {
2✔
1405
        int r;
2✔
1406

1407
        assert(link);
2✔
1408
        assert(input);
2✔
1409
        assert(encrypted);
2✔
1410

1411
        log_info("Encrypting credential '%s'...", input->id);
2✔
1412

1413
        r = connect_to_creds(link);
2✔
1414
        if (r < 0)
2✔
1415
                return r;
2✔
1416

1417
        sd_json_variant *reply = NULL;
2✔
1418
        const char *error_id = NULL;
2✔
1419
        r = varlink_callbo_and_log(
2✔
1420
                        *link,
1421
                        "io.systemd.Credentials.Encrypt",
1422
                        &reply,
1423
                        &error_id,
1424
                        SD_JSON_BUILD_PAIR_STRING("name", input->id),
1425
                        SD_JSON_BUILD_PAIR_BASE64("data", input->data, input->size),
1426
                        SD_JSON_BUILD_PAIR_STRING("scope", "system"),
1427
                        /* We pick the 'auto_initrd' key for this, since we want TPM if available, but are fine with NULL if not */
1428
                        SD_JSON_BUILD_PAIR_STRING("withKey", "auto_initrd"));
1429
        if (r < 0)
2✔
1430
                return r;
1431

1432
        static const sd_json_dispatch_field dispatch_table[] = {
2✔
1433
                { "blob", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
1434
                {}
1435
        };
1436

1437
        const char *blob = NULL;
2✔
1438
        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &blob);
2✔
1439
        if (r < 0)
2✔
1440
                return r;
1441

1442
        r = strv_extend_many(encrypted, input->id, blob);
2✔
1443
        if (r < 0)
2✔
1444
                return r;
×
1445

1446
        return 0;
1447
}
1448

1449
static int encrypt_credentials(sd_varlink **link, MachineCredentialContext *credentials, char ***encrypted) {
2✔
1450
        int r;
2✔
1451

1452
        assert(link);
2✔
1453
        assert(encrypted);
2✔
1454

1455
        FOREACH_ARRAY(cred, credentials->credentials, credentials->n_credentials) {
4✔
1456
                r = encrypt_one_credential(link, cred, encrypted);
2✔
1457
                if (r < 0)
2✔
1458
                        return r;
1459
        }
1460

1461
        return 0;
1462
}
1463

1464
static const ImagePolicy image_policy = {
1465
        .n_policies = 4,
1466
        .policies = {
1467
                /* We mount / and /usr/ so that we can get access to /etc/machine-id and /etc/kernel/ */
1468
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1469
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1470
                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1471
                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1472
        },
1473
        .default_flags = PARTITION_POLICY_IGNORE,
1474
};
1475

1476
static int settle_definitions(char **definitions, char ***ret_definitions) {
2✔
1477

1478
        _cleanup_strv_free_ char **d = NULL;
2✔
1479
        int r;
2✔
1480

1481
        assert(ret_definitions);
2✔
1482

1483
        if (definitions) {
2✔
1484
                d = strv_copy(definitions);
2✔
1485
                if (!d)
2✔
1486
                        return log_oom();
×
1487

1488
                *ret_definitions = TAKE_PTR(d);
2✔
1489

1490
                return 0;
2✔
1491
        }
1492

1493
        /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition
1494
         * files */
1495

1496
        _cleanup_strv_free_ char **files = NULL;
×
1497
        r = conf_files_list_strv(
×
1498
                        &files,
1499
                        ".conf",
1500
                        /* root= */ NULL,
1501
                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN|CONF_FILES_DONT_PREFIX_ROOT,
1502
                        (const char**) CONF_PATHS_STRV("repart.sysinstall.d"));
×
1503
        if (r < 0)
×
1504
                return log_error_errno(r, "Failed to enumerate *.conf files: %m");
×
1505

1506
        if (!strv_isempty(files)) {
×
1507
                d = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
×
1508
                if (!d)
×
1509
                        return log_oom();
×
1510

1511
                *ret_definitions = TAKE_PTR(d);
×
1512
        }
1513

1514
        return 0;
1515
}
1516

1517
static int sysinstall_context_settle_definitions(SysInstallContext *context,
2✔
1518
                                                 char **definitions) {
1519

1520
        return settle_definitions(definitions, &context->definitions);
2✔
1521
}
1522

1523
static int sysinstall_context_settle_kernel_image(SysInstallContext *context,
2✔
1524
                                                  const char *kernel_image) {
1525

1526
        _cleanup_free_ char *kernel_filename = NULL;
2✔
1527
        _cleanup_close_ int kernel_fd = -EBADF;
2✔
1528
        int r;
2✔
1529

1530
        assert(context->kernel_fd < 0);
2✔
1531
        assert(!context->kernel_filename);
2✔
1532

1533
        if (kernel_image) {
2✔
1534
                r = path_extract_filename(kernel_image, &kernel_filename);
2✔
1535
                if (r < 0)
2✔
1536
                        return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel_image);
×
1537
                if (r == O_DIRECTORY)
2✔
1538
                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", kernel_image);
×
1539

1540
                kernel_fd = xopenat_full(XAT_FDROOT, kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
2✔
1541
                if (kernel_fd < 0)
2✔
1542
                        return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", kernel_image);
×
1543

1544
        } else {
1545
                r = find_current_kernel(&kernel_filename, &kernel_fd);
×
1546
                if (r < 0)
×
1547
                        return r;
1548
        }
1549

1550
        context->kernel_filename = TAKE_PTR(kernel_filename);
2✔
1551
        context->kernel_fd = TAKE_FD(kernel_fd);
2✔
1552

1553
        return 0;
2✔
1554
}
1555

1556
static int sysinstall_context_run(SysInstallContext *context) {
2✔
1557

1558
        int r;
2✔
1559

1560
        assert(context);
2✔
1561

1562
        (void) sysinstall_context_notify(context, PROGRESS_ENCRYPT_CREDENTIALS, NULL, UINT_MAX);
2✔
1563

1564
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
2✔
1565
        _cleanup_strv_free_ char **encrypted_credentials = NULL;
2✔
1566
        r = encrypt_credentials(&creds_link, &context->credentials, &encrypted_credentials);
2✔
1567
        if (r < 0)
2✔
1568
                return r;
1569

1570
        (void) sysinstall_context_notify(context, PROGRESS_INSTALL_PARTITIONS, NULL, UINT_MAX);
2✔
1571

1572
        /* Do the main part of the installation */
1573

1574
        r = sysinstall_context_invoke_repart_run(context);
2✔
1575
        if (r < 0)
2✔
1576
                return r;
1577

1578
        (void) sysinstall_context_notify(context, PROGRESS_MOUNT_PARTITIONS, NULL, UINT_MAX);
2✔
1579

1580
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
2✔
1581
        _cleanup_(umount_and_freep) char *root_dir = NULL;
×
1582
        _cleanup_close_ int root_fd = -EBADF;
2✔
1583
        r = mount_image_privately_interactively(
4✔
1584
                        context->node,
2✔
1585
                        &image_policy,
1586
                        DISSECT_IMAGE_REQUIRE_ROOT |
1587
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1588
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
1589
                        DISSECT_IMAGE_DISCARD_ANY |
1590
                        DISSECT_IMAGE_GPT_ONLY |
1591
                        DISSECT_IMAGE_FSCK |
1592
                        DISSECT_IMAGE_USR_NO_ROOT |
1593
                        DISSECT_IMAGE_ADD_PARTITION_DEVICES |
1594
                        DISSECT_IMAGE_PIN_PARTITION_DEVICES,
1595
                        &root_dir,
1596
                        &root_fd,
1597
                        &loop_device);
1598
        if (r < 0)
2✔
1599
                return log_error_errno(r, "Failed to mount new image: %m");
×
1600

1601
        (void) sysinstall_context_notify(context, PROGRESS_INSTALL_KERNEL, NULL, UINT_MAX);
2✔
1602

1603
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
2✔
1604
        r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, context->kernel_filename, context->kernel_fd, encrypted_credentials);
2✔
1605
        if (r < 0)
2✔
1606
                return r;
1607

1608
        (void) sysinstall_context_notify(context, PROGRESS_INSTALL_BOOTLOADER, NULL, UINT_MAX);
2✔
1609

1610
        r = invoke_bootctl_install(&bootctl_link, context->touch_variables, root_dir, root_fd);
2✔
1611
        if (r < 0)
2✔
1612
                return r;
1613

1614
        (void) sysinstall_context_notify(context, PROGRESS_UNMOUNT_PARTITIONS, NULL, UINT_MAX);
2✔
1615

1616
        root_fd = safe_close(root_fd);
2✔
1617
        r = umount_recursive(root_dir, /* flags= */ 0);
2✔
1618
        if (r < 0)
2✔
1619
                log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
×
1620
        loop_device = loop_device_unref(loop_device);
2✔
1621
        sync();
2✔
1622

1623
        return 0;
2✔
1624
}
1625

1626
typedef struct ListCandidateDevicesContext {
1627
        char **definitions;
1628
        bool subscribe;
1629

1630
        sd_varlink *repart_link; /* A repart connection to get candidate devices */
1631
        sd_varlink *dry_run_repart_link; /* A second repart connection to perform a dry run on each node */
1632

1633
        sd_varlink *link;
1634
} ListCandidateDevicesContext;
1635

1636
static ListCandidateDevicesContext* list_candidate_devices_context_new(void) {
×
1637
        ListCandidateDevicesContext *context = new(ListCandidateDevicesContext, 1);
×
1638

1639
        if (!context)
×
1640
                return NULL;
1641

1642
        *context = (ListCandidateDevicesContext) {};
×
1643

1644
        return context;
×
1645
}
1646

1647
static ListCandidateDevicesContext* list_candidate_devices_context_free(ListCandidateDevicesContext *context) {
×
1648
        if (!context)
×
1649
                return NULL;
1650

1651
        strv_free(context->definitions);
×
1652

1653
        context->repart_link = sd_varlink_flush_close_unref(context->repart_link);
×
1654
        context->dry_run_repart_link = sd_varlink_flush_close_unref(context->dry_run_repart_link);
×
1655
        context->link = sd_varlink_unref(context->link);
×
1656

1657
        return mfree(context);
×
1658
}
1659

1660
DEFINE_TRIVIAL_CLEANUP_FUNC(ListCandidateDevicesContext*, list_candidate_devices_context_free);
×
1661

1662
static int list_candidate_devices_context_settle_definitions(ListCandidateDevicesContext *context,
×
1663
                                                             char **definitions) {
1664

1665
        return settle_definitions(definitions, &context->definitions);
×
1666
}
1667

1668
static void vl_on_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) {
×
1669
        assert(server);
×
1670
        assert(link);
×
1671

1672
        list_candidate_devices_context_free(sd_varlink_set_userdata(link, NULL));
×
1673
}
×
1674

1675
typedef struct DevicesResponse {
1676
        const char *node;
1677
        char **symlinks;
1678
        uint64_t diskseq;
1679
        uint64_t size;
1680
        const char *model;
1681
        const char *vendor;
1682
        const char *subsystem;
1683
        const char *action;
1684
} DevicesResponse;
1685

1686
static void devices_response_done(DevicesResponse *p) {
×
1687
        assert(p);
×
1688

1689
        p->symlinks = strv_free(p->symlinks);
×
1690
}
×
1691

1692
static int fetch_candidate_devices_reply(
×
1693
                sd_varlink *repart_link,
1694
                sd_json_variant *reply,
1695
                const char *error_id,
1696
                sd_varlink_reply_flags_t flags,
1697
                void *userdata) {
1698

1699
        int r;
×
1700
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1701
        ListCandidateDevicesContext *context = ASSERT_PTR(userdata);
×
1702

1703
        if (error_id) {
×
1704
                if (streq(error_id, "io.systemd.Repart.NoCandidateDevices"))
×
1705
                        return sd_varlink_error(context->link, "io.systemd.SysInstall.NoCandidateDevices", NULL);
×
1706

1707
                return sd_varlink_error(context->link, error_id, NULL);
×
1708
        }
1709

1710
        static const sd_json_dispatch_field dispatch_table[] = {
×
1711
                { "node",      SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, node),      0 },
1712
                { "symlinks",  SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,         offsetof(DevicesResponse, symlinks),  0 },
1713
                { "diskseq",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       offsetof(DevicesResponse, diskseq),   0 },
1714
                { "sizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       offsetof(DevicesResponse, size),      0 },
1715
                { "model",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, model),     0 },
1716
                { "vendor",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, vendor),    0 },
1717
                { "subsystem", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, subsystem), 0 },
1718
                { "action",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, action),    0 },
1719
                {}
1720
        };
1721

1722
        _cleanup_(devices_response_done) DevicesResponse p = {
×
1723
                .diskseq = UINT64_MAX,
1724
                .size = UINT64_MAX,
1725
        };
1726
        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
×
1727
        if (r < 0)
×
1728
                return r;
1729

1730
        if (context->subscribe) {
×
1731
                /* The action needs to be ready, remove or add else we don't support the action */
1732
                if (streq(p.action, "ready"))
×
1733
                        return sd_varlink_notifybo(context->link, SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("ready")));
×
1734

1735
                if (streq(p.action, "remove"))
×
1736
                        return sd_varlink_notifybo(context->link,
×
1737
                                        SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("remove")),
1738
                                        SD_JSON_BUILD_PAIR_STRING("node", p.node));
1739

1740
                if (!streq(p.action, "add")) {
×
1741
                        log_debug("Skip unsupported action '%s' while fetching candidate devices.", p.action);
×
1742
                        return 0;
1743
                }
1744
        }
1745

1746
        uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX;
×
1747
        r = invoke_repart(
×
1748
                        &context->dry_run_repart_link,
1749
                        p.node,
1750
                        /* erase= */ false,
1751
                        /* dry_run= */ true,
1752
                        context->definitions,
1753
                        &min_size,
1754
                        &current_size,
1755
                        &need_free);
1756

1757
        DeviceFit fit;
×
1758
        if (r < 0) {
×
1759
                if (r == -ENOSPC)
×
1760
                        fit = DEVICE_FIT_INSUFFICIENT_FREE_SPACE;
1761
                else if (r == -E2BIG)
×
1762
                        fit = DEVICE_FIT_DISK_TOO_SMALL;
1763
                else if (r == -EHWPOISON)
×
1764
                        fit = DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT;
1765
                else
1766
                        return r;
1767
        } else
1768
                fit = DEVICE_FIT_ENOUGH_FREE_SPACE;
1769

1770
        r = sd_json_buildo(&v,
×
1771
                        SD_JSON_BUILD_PAIR_STRING("node", p.node),
1772
                        JSON_BUILD_PAIR_STRV_NON_EMPTY("symlinks", p.symlinks),
1773
                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("diskseq", p.diskseq, UINT64_MAX),
1774
                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", p.size, UINT64_MAX),
1775
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("model", p.model),
1776
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", p.vendor),
1777
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("subsystem", p.subsystem),
1778
                        JSON_BUILD_PAIR_ENUM("fit", device_fit_to_string(fit)),
1779
                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", current_size, UINT64_MAX),
1780
                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", need_free, UINT64_MAX),
1781
                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", min_size, UINT64_MAX),
1782
                        SD_JSON_BUILD_PAIR_CONDITION(context->subscribe, "action", JSON_BUILD_CONST_STRING("add")));
1783
        if (r < 0)
×
1784
                return r;
1785

1786
        if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
×
1787
                return sd_varlink_notify(context->link, v);
×
1788

1789
        return sd_varlink_reply(context->link, v);
×
1790
}
1791

1792
typedef struct ListCandidateDevicesParameters {
1793
        char **definitions;
1794
        bool subscribe;
1795
} ListCandidateDevicesParameters;
1796

1797
static void list_candidate_devices_parameters_done(ListCandidateDevicesParameters *p) {
×
1798
        assert(p);
×
1799

1800
        p->definitions = strv_free(p->definitions);
×
1801
}
×
1802

1803
static int vl_method_list_candidate_devices(
×
1804
                sd_varlink *link,
1805
                sd_json_variant *parameters,
1806
                sd_varlink_method_flags_t flags,
1807
                void *userdata) {
1808
        int r;
×
1809

1810
        assert(link);
×
1811

1812
        sd_varlink_server *varlink_server = sd_varlink_get_server(link);
×
1813
        sd_event *event = sd_varlink_server_get_event(varlink_server);
×
1814
        Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server));
×
1815

1816
        assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
×
1817

1818
        r = varlink_verify_polkit_async(
×
1819
                        link,
1820
                        /* bus= */ NULL,
1821
                        "io.systemd.sysinstall.ListCandidateDevices",
1822
                        /* details= */ NULL,
1823
                        polkit_registry);
1824
        if (r <= 0)
×
1825
                return r;
×
1826

1827
        static const sd_json_dispatch_field dispatch_table[] = {
×
1828
                { "definitions", SD_JSON_VARIANT_ARRAY,   json_dispatch_strv_path,  offsetof(ListCandidateDevicesParameters, definitions), SD_JSON_STRICT },
1829
                { "subscribe",   SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(ListCandidateDevicesParameters, subscribe),   0              },
1830
                {}
1831
        };
1832

1833
        _cleanup_(list_candidate_devices_parameters_done) ListCandidateDevicesParameters p = {};
×
1834
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
×
1835
        if (r != 0)
×
1836
                return r;
1837

1838
        _cleanup_(list_candidate_devices_context_freep) ListCandidateDevicesContext* context = list_candidate_devices_context_new();
×
1839
        if (!context)
×
1840
                return log_oom();
×
1841

1842
        context->subscribe = p.subscribe;
×
1843
        r = list_candidate_devices_context_settle_definitions(context, p.definitions);
×
1844
        if (r < 0)
×
1845
                return r;
1846

1847
        context->link = sd_varlink_ref(link);
×
1848

1849
        r = connect_to_repart(&context->repart_link);
×
1850
        if (r < 0)
×
1851
                return r;
1852

1853
        r = sd_varlink_attach_event(context->repart_link, event, SD_EVENT_PRIORITY_NORMAL);
×
1854
        if (r < 0)
×
1855
                return log_error_errno(
×
1856
                                r,
1857
                                "Failed to attach io.systemd.Repart.ListCandidateDevices() varlink connection to event loop: %m");
1858

1859
        r = sd_varlink_bind_reply(context->repart_link, fetch_candidate_devices_reply);
×
1860
        if (r < 0)
×
1861
                return r;
1862

1863
        r = sd_varlink_observebo(
×
1864
                        context->repart_link,
1865
                        "io.systemd.Repart.ListCandidateDevices",
1866
                        SD_JSON_BUILD_PAIR_BOOLEAN("subscribe", context->subscribe),
1867
                        SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true));
1868

1869
        if (r < 0)
×
1870
                return log_error_errno(
×
1871
                                r,
1872
                                "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
1873

1874
        r = sd_varlink_server_bind_disconnect(varlink_server, vl_on_disconnect);
×
1875
        if (r < 0)
×
1876
                return r;
1877

1878
        /* The context is freed in vl_on_disconnect() */
1879
        sd_varlink_set_userdata(context->repart_link, context);
×
1880
        sd_varlink_set_userdata(link, TAKE_PTR(context));
×
1881

1882
        return 0;
1883
}
1884

1885
typedef struct RunParameters {
1886
        char *node;
1887
        char **definitions;
1888
        bool erase;
1889
        bool variables;
1890
        char *kernel_image;
1891
        bool copy_locale;
1892
        bool copy_keymap;
1893
        bool copy_timezone;
1894
        sd_json_variant *credentials;
1895
} RunParameters;
1896

1897
static void run_parameters_done(RunParameters *p) {
1✔
1898
        assert(p);
1✔
1899

1900
        p->node = mfree(p->node);
1✔
1901
        p->definitions = strv_free(p->definitions);
1✔
1902
        p->kernel_image = mfree(p->kernel_image);
1✔
1903
        p->credentials = sd_json_variant_unref(p->credentials);
1✔
1904
}
1✔
1905

1906
typedef struct CredentialParameters {
1907
        const char *id;
1908
        struct iovec value;
1909
} CredentialParameters;
1910

1911
static void credential_parameters_done(CredentialParameters *p) {
1✔
1912
        assert(p);
1✔
1913

1914
        iovec_done_erase(&p->value);
1✔
1915
}
1✔
1916

1917
static int credentials_from_json_array(MachineCredentialContext *credentials, sd_json_variant *v) {
1✔
1918

1919
        int r;
1✔
1920
        sd_json_variant *credential;
1✔
1921

1922
        assert(credentials);
1✔
1923

1924
        static const sd_json_dispatch_field dispatch_table[] = {
1✔
1925
                { "id",    SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialParameters, id),    SD_JSON_MANDATORY },
1926
                { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec,  offsetof(CredentialParameters, value), SD_JSON_MANDATORY },
1927
                {}
1928
        };
1929

1930
        JSON_VARIANT_ARRAY_FOREACH(credential, v) {
2✔
1931
                _cleanup_(credential_parameters_done) CredentialParameters p = {};
1✔
1932

1933
                r = sd_json_dispatch(credential, dispatch_table, /* flags= */ 0, &p);
1✔
1934
                if (r < 0)
1✔
1935
                        return r;
1936

1937
                r = machine_credential_add(credentials, p.id, p.value.iov_base, p.value.iov_len);
1✔
1938
                if (r < 0)
1✔
1939
                        return r;
1940
        }
1941

1942
        return 0;
1✔
1943
}
1944

1945
static int vl_method_run(
1✔
1946
                sd_varlink *link,
1947
                sd_json_variant *parameters,
1948
                sd_varlink_method_flags_t flags,
1949
                void *userdata) {
1950

1951
        static const sd_json_dispatch_field dispatch_table[] = {
1✔
1952
                { "node",            SD_JSON_VARIANT_STRING,  json_dispatch_path,       offsetof(RunParameters, node),          SD_JSON_MANDATORY | SD_JSON_STRICT },
1953
                { "definitions",     SD_JSON_VARIANT_ARRAY,   json_dispatch_strv_path,  offsetof(RunParameters, definitions),   SD_JSON_NULLABLE | SD_JSON_STRICT  },
1954
                { "erase",           SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, erase),         SD_JSON_MANDATORY                  },
1955
                { "variables",       SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, variables),     SD_JSON_NULLABLE                   },
1956
                { "kernelImagePath", SD_JSON_VARIANT_STRING,  json_dispatch_path,       offsetof(RunParameters, kernel_image),  SD_JSON_NULLABLE | SD_JSON_STRICT  },
1957
                { "copyLocale",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_locale),   SD_JSON_NULLABLE                   },
1958
                { "copyKeymap",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_keymap),   SD_JSON_NULLABLE                   },
1959
                { "copyTimezone",    SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_timezone), SD_JSON_NULLABLE                   },
1960
                { "credentials",     SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_variant, offsetof(RunParameters, credentials),   SD_JSON_NULLABLE                   },
1961
                {}
1962
        };
1963

1964
        int r;
1✔
1965

1966
        assert(link);
1✔
1967

1968
        sd_varlink_server *varlink_server = sd_varlink_get_server(link);
1✔
1969
        Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server));
1✔
1970

1971
        r = varlink_verify_polkit_async(
1✔
1972
                        link,
1973
                        /* bus= */ NULL,
1974
                        "io.systemd.sysinstall.Run",
1975
                        /* details= */ NULL,
1976
                        polkit_registry);
1977
        if (r <= 0)
1✔
1978
                return r;
1✔
1979

1980
        _cleanup_(run_parameters_done) RunParameters p = {};
1✔
1981
        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
1✔
1982
        if (r != 0)
1✔
1983
                return r;
1984

1985
        _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) {
1✔
1986
                .copy_locale = p.copy_locale,
1✔
1987
                .copy_keymap = p.copy_keymap,
1✔
1988
                .copy_timezone = p.copy_timezone,
1✔
1989
                .erase = p.erase,
1✔
1990
                .touch_variables = p.variables,
1✔
1991
                .node = TAKE_PTR(p.node),
1✔
1992
                .kernel_fd = -EBADF,
1993
        };
1994

1995
        r = sysinstall_context_settle_definitions(&context, p.definitions);
1✔
1996
        if (r < 0)
1✔
1997
                return r;
1998

1999
        if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
1✔
2000
                context.link = sd_varlink_ref(link);
1✔
2001

2002
        r = credentials_from_json_array(&context.credentials, p.credentials);
1✔
2003
        if (r < 0)
1✔
2004
                return r;
2005

2006
        r = sysinstall_context_read_credentials(&context);
1✔
2007
        if (r < 0)
1✔
2008
                return r;
2009

2010
        r = sysinstall_context_settle_kernel_image(&context, p.kernel_image);
1✔
2011
        if (r < 0)
1✔
2012
                return r;
2013

2014
        r = sysinstall_context_run(&context);
1✔
2015
        if (r < 0)
1✔
2016
                return r;
2017

2018
        return sd_varlink_reply(link, NULL);
1✔
2019
}
2020

2021
static int vl_server(void) {
13✔
2022
        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
13✔
2023
        _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
13✔
2024
        int r;
13✔
2025

2026
        /* Invocation as Varlink service */
2027

2028
        r = varlink_server_new(
13✔
2029
                        &varlink_server,
2030
                        0,
2031
                        /* userdata= */ &polkit_registry);
2032
        if (r < 0)
13✔
2033
                return log_error_errno(r, "Failed to allocate Varlink server: %m");
×
2034

2035
        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysInstall);
13✔
2036
        if (r < 0)
13✔
2037
                return log_error_errno(r, "Failed to add Varlink interface: %m");
×
2038

2039
        r = sd_varlink_server_bind_method_many(
13✔
2040
                        varlink_server,
2041
                        "io.systemd.SysInstall.ListCandidateDevices", vl_method_list_candidate_devices,
2042
                        "io.systemd.SysInstall.Run",                  vl_method_run);
2043
        if (r < 0)
13✔
2044
                return log_error_errno(r, "Failed to bind Varlink methods: %m");
×
2045

2046
        r = sd_varlink_server_loop_auto(varlink_server);
13✔
2047
        if (r < 0)
13✔
2048
                return log_error_errno(r, "Failed to run Varlink event loop: %m");
×
2049

2050
        return 0;
2051
}
2052

2053
static void end_marker(void) {
1✔
2054

2055
        if (!arg_welcome)
1✔
2056
                return;
2057

2058
        printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal());
×
2059
        fflush(stdout);
×
2060
}
2061

2062
static int run(int argc, char *argv[]) {
14✔
2063
        int r;
14✔
2064

2065
        setlocale(LC_ALL, "");
14✔
2066

2067
        r = parse_argv(argc, argv);
14✔
2068
        if (r <= 0)
14✔
2069
                return r;
14✔
2070

2071
        log_setup();
14✔
2072

2073
        if (arg_varlink)
14✔
2074
                return vl_server();
13✔
2075

2076
        _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) {
×
2077
                .copy_locale = arg_copy_locale,
2078
                .copy_keymap = arg_copy_keymap,
2079
                .copy_timezone = arg_copy_timezone,
2080
                .credentials = TAKE_STRUCT(arg_credentials),
1✔
2081
                .kernel_fd = -EBADF,
2082
        };
2083

2084
        r = sysinstall_context_settle_definitions(&context, arg_definitions);
1✔
2085
        if (r < 0)
1✔
2086
                return r;
2087

2088
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL;
1✔
2089
        if (arg_welcome) {
1✔
2090
                if (arg_mute_console)
×
2091
                        (void) mute_console(&mute_console_link);
×
2092

2093
                (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
×
2094

2095
                if (arg_chrome)
×
2096
                        chrome_show("Operating System Installer", /* bottom= */ NULL);
×
2097
        }
2098

2099
        DEFER_VOID_CALL(end_marker);
1✔
2100
        DEFER_VOID_CALL(chrome_hide);
×
2101

2102
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *repart_link = NULL;
1✔
2103
        if (arg_node) {
1✔
2104
                r = print_welcome(&mute_console_link);
1✔
2105
                if (r < 0)
1✔
2106
                        return r;
2107

2108
                r = validate_run(&repart_link, arg_node);
1✔
2109
                if (r < 0)
1✔
2110
                        return r;
2111
        } else {
2112
                /* Determine the minimum disk size */
2113
                uint64_t min_size = UINT64_MAX;
×
2114
                r = invoke_repart(
×
2115
                                &repart_link,
2116
                                /* node= */ NULL,
2117
                                /* erase= */ true,
2118
                                /* dry_run= */ true,
2119
                                arg_definitions,
2120
                                &min_size,
2121
                                /* current_size= */ NULL,
2122
                                /* need_free= */ NULL);
2123
                if (r < 0)
×
2124
                        return r;
×
2125

2126
                r = print_welcome(&mute_console_link);
×
2127
                if (r < 0)
×
2128
                        return r;
2129

2130
                log_info("Required minimal installation disk size is %s.", FORMAT_BYTES(min_size));
×
2131

2132
                for (;;) {
×
2133
                        _cleanup_free_ char *node = NULL;
×
2134
                        r = prompt_block_device(&repart_link, &node);
×
2135
                        if (r < 0)
×
2136
                                return r;
2137

2138
                        r = validate_run(&repart_link, node);
×
2139
                        if (IN_SET(r, -ENOSPC, -E2BIG, -EHWPOISON)) /* Device is no fit, pick other */
×
2140
                                continue;
×
2141
                        if (r < 0)
×
2142
                                return r;
2143

2144
                        arg_node = TAKE_PTR(node);
×
2145
                        break;
×
2146
                }
2147
        }
2148

2149
        r = prompt_touch_variables();
1✔
2150
        if (r < 0)
1✔
2151
                return r;
2152

2153
        r = sysinstall_context_read_credentials(&context);
1✔
2154
        if (r < 0)
1✔
2155
                return r;
2156

2157
        r = sysinstall_context_settle_kernel_image(&context, arg_kernel_image);
1✔
2158
        if (r < 0)
1✔
2159
                return r;
2160

2161
        /* Verify we have everything we need */
2162
        assert(arg_node);
1✔
2163
        assert(arg_erase >= 0);
1✔
2164
        assert(arg_touch_variables >= 0);
1✔
2165

2166
        context.node = TAKE_PTR(arg_node);
1✔
2167
        context.touch_variables = arg_touch_variables;
1✔
2168
        context.erase = arg_erase;
1✔
2169

2170
        if (arg_summary) {
1✔
2171
                r = sysinstall_context_show_summary(&context);
×
2172
                if (r < 0)
×
2173
                        return r;
2174
        }
2175

2176
        r = prompt_confirm();
1✔
2177
        if (r < 0)
1✔
2178
                return r;
2179

2180
        putchar('\n');
1✔
2181

2182
        r = sysinstall_context_run(&context);
1✔
2183
        if (r < 0)
1✔
2184
                return r;
2185

2186
        log_notice("%s%sInstallation succeeded.",
2✔
2187
                   emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : "");
2188

2189
        r = maybe_reboot();
1✔
2190
        if (r < 0)
1✔
2191
                return r;
×
2192

2193
        return 0;
2194
}
2195

2196
DEFINE_MAIN_FUNCTION(run);
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc