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

systemd / systemd / 28341416412

28 Jun 2026 09:25AM UTC coverage: 72.714% (-0.2%) from 72.925%
28341416412

push

github

bluca
Revert "mkosi: Grow the root partition on boot"

This reverts commit 7f33ee8bb.

The file is located outside mkosi/ subdirectory, hence currently unused.
If this is moved to mkosi/ subdirectory, the config conflicts with
TEST-58-REPART. Let's remove it at least now, and reintroduce it later
at correct place with test adjustment if this is really useful.

341139 of 469154 relevant lines covered (72.71%)

1450250.85 hits per line

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

49.31
/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 "chase.h"
15
#include "conf-files.h"
16
#include "constants.h"
17
#include "efi-loader.h"
18
#include "efivars.h"
19
#include "env-file.h"
20
#include "escape.h"
21
#include "fd-util.h"
22
#include "find-esp.h"
23
#include "format-table.h"
24
#include "format-util.h"
25
#include "fs-util.h"
26
#include "glyph-util.h"
27
#include "help-util.h"
28
#include "image-policy.h"
29
#include "json-util.h"
30
#include "locale-setup.h"
31
#include "log.h"
32
#include "loop-util.h"
33
#include "machine-credential.h"
34
#include "main-func.h"
35
#include "mount-util.h"
36
#include "options.h"
37
#include "os-util.h"
38
#include "parse-argument.h"
39
#include "parse-util.h"
40
#include "path-util.h"
41
#include "prompt-util.h"
42
#include "strv.h"
43
#include "terminal-util.h"
44
#include "varlink-util.h"
45

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

62
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
1✔
63
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
1✔
64
STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep);
1✔
65
STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
1✔
66

67
static int help(void) {
×
68
        int r;
×
69

70
        _cleanup_(table_unrefp) Table *options = NULL;
×
71
        r = option_parser_get_help_table(&options);
×
72
        if (r < 0)
×
73
                return r;
74

75
        help_cmdline("[OPTIONS...] [DEVICE]");
×
76
        help_abstract("Installs the OS to another block device.");
×
77
        help_section("Options:");
×
78

79
        r = table_print_or_warn(options);
×
80
        if (r < 0)
×
81
                return r;
82

83
        help_man_page_reference("systemd-sysinstall", "8");
×
84

85
        return 0;
86
}
87

88
static int parse_argv(int argc, char *argv[]) {
1✔
89
        int r;
1✔
90

91
        assert(argc >= 0);
1✔
92
        assert(argv);
1✔
93

94
        OptionParser opts = { argc, argv };
1✔
95

96
        FOREACH_OPTION_OR_RETURN(c, &opts)
16✔
97
                switch (c) {
14✔
98

99
                OPTION_COMMON_HELP:
×
100
                        return help();
×
101

102
                OPTION_COMMON_VERSION:
×
103
                        return version();
×
104

105
                OPTION_LONG("welcome", "no", "Disable the welcome text"):
1✔
106
                        r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome);
1✔
107
                        if (r < 0)
1✔
108
                                return r;
109

110
                        break;
111

112
                OPTION_LONG("erase", "BOOL", "Whether to erase the target disk"):
1✔
113
                        r = parse_tristate_argument_with_auto("--erase=", opts.arg, &arg_erase);
1✔
114
                        if (r < 0)
1✔
115
                                return r;
116
                        break;
117

118
                OPTION_LONG("confirm", "no", "Disable query for confirmation"):
1✔
119
                        r = parse_boolean_argument("--confirm=", opts.arg, &arg_confirm);
1✔
120
                        if (r < 0)
1✔
121
                                return r;
122
                        break;
123

124
                OPTION_LONG("summary", "no", "Disable summary before beginning operation"):
1✔
125
                        r = parse_boolean_argument("--summary=", opts.arg, &arg_summary);
1✔
126
                        if (r < 0)
1✔
127
                                return r;
128
                        break;
129

130
                OPTION_LONG("definitions", "DIR", "Find partition definitions in specified directory"): {
1✔
131
                        _cleanup_free_ char *path = NULL;
1✔
132
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &path);
1✔
133
                        if (r < 0)
1✔
134
                                return r;
135
                        if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0)
1✔
136
                                return log_oom();
×
137
                        break;
1✔
138
                }
139

140
                OPTION_LONG("reboot", "BOOL", "Whether to reboot after installation is complete"):
1✔
141
                        r = parse_boolean_argument("--reboot=", opts.arg, &arg_reboot);
1✔
142
                        if (r < 0)
1✔
143
                                return r;
144
                        break;
145

146
                OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"):
1✔
147
                        r = parse_tristate_argument_with_auto("--variables=", opts.arg, &arg_touch_variables);
1✔
148
                        if (r < 0)
1✔
149
                                return r;
150
                        break;
151

152
                OPTION_LONG("kernel", "IMAGE", "Explicitly pick kernel image to install"):
1✔
153
                        r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_kernel_image);
1✔
154
                        if (r < 0)
1✔
155
                                return r;
156
                        break;
157

158
                OPTION_LONG("set-credential", "ID:VALUE", "Install a credential with literal value to target system"):
1✔
159
                        r = machine_credential_set(&arg_credentials, opts.arg);
1✔
160
                        if (r < 0)
1✔
161
                                return r;
162
                        break;
163

164
                OPTION_LONG("load-credential", "ID:PATH", "Load a credential to install to new system from file or AF_UNIX stream socket"):
×
165
                        r = machine_credential_load(&arg_credentials, opts.arg);
×
166
                        if (r < 0)
×
167
                                return r;
168

169
                        break;
170

171
                OPTION_LONG("copy-locale", "no", "Don't copy current locale to target system"):
1✔
172
                        r = parse_boolean_argument("--copy-locale=", opts.arg, &arg_copy_locale);
1✔
173
                        if (r < 0)
1✔
174
                                return r;
175
                        break;
176

177
                OPTION_LONG("copy-keymap", "no", "Don't copy current keymap to target system"):
1✔
178
                        r = parse_boolean_argument("--copy-keymap=", opts.arg, &arg_copy_keymap);
1✔
179
                        if (r < 0)
1✔
180
                                return r;
181
                        break;
182

183
                OPTION_LONG("copy-timezone", "no", "Don't copy current timezone to target system"):
1✔
184
                        r = parse_boolean_argument("--copy-timezone=", opts.arg, &arg_copy_timezone);
1✔
185
                        if (r < 0)
1✔
186
                                return r;
187
                        break;
188

189
                OPTION_LONG("chrome", "no", "Whether to show a color bar at top and bottom of terminal"):
1✔
190
                        r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome);
1✔
191
                        if (r < 0)
1✔
192
                                return r;
193

194
                        break;
195

196
                OPTION_LONG("mute-console", "BOOL", "Whether to disallow kernel/PID 1 writes to the console while running"):
1✔
197
                        r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console);
1✔
198
                        if (r < 0)
1✔
199
                                return r;
200
                        break;
201
                }
202

203
        char **args = option_parser_get_args(&opts);
1✔
204

205
        if (strv_length(args) > 1)
1✔
206
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
×
207
        if (!strv_isempty(args)) {
2✔
208
                arg_node = strdup(args[0]);
1✔
209
                if (!arg_node)
1✔
210
                        return log_oom();
×
211
        }
212

213
        return 1;
214
}
215

216
static int print_welcome(sd_varlink **mute_console_link) {
1✔
217
        _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL;
1✔
218
        const char *pn, *ac;
1✔
219
        int r;
1✔
220

221
        assert(mute_console_link);
1✔
222

223
        if (!*mute_console_link && arg_mute_console)
1✔
224
                (void) mute_console(mute_console_link);
×
225

226
        if (!arg_welcome)
1✔
227
                return 0;
228

229
        r = parse_os_release(
×
230
                        /* root= */ NULL,
231
                        "PRETTY_NAME", &pretty_name,
232
                        "FANCY_NAME",  &fancy_name,
233
                        "NAME",        &os_name,
234
                        "ANSI_COLOR",  &ansi_color);
235
        if (r < 0)
×
236
                log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
×
237
                               "Failed to read os-release file, ignoring: %m");
238

239
        pn = os_release_pretty_name(pretty_name, os_name);
×
240
        ac = isempty(ansi_color) ? "0" : ansi_color;
×
241

242
        if (use_fancy_name(unescape_fancy_name(&fancy_name)))
×
243
                printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", fancy_name);
×
244
        else if (colors_enabled())
×
245
                printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", ac, pn);
×
246
        else
247
                printf("Welcome to the %s Installer!\n", pn);
×
248

249
        putchar('\n');
1✔
250

251
        return 0;
252
}
253

254
static int connect_to_repart(sd_varlink **link) {
2✔
255
        int r;
2✔
256

257
        assert(link);
2✔
258

259
        if (*link) {
2✔
260
                /* Reset the time-out to default here, since we are reusing the connection, but might enqueue
261
                 * a different operation */
262
                r = sd_varlink_set_relative_timeout(*link, 0);
1✔
263
                if (r < 0)
1✔
264
                        return r;
2✔
265

266
                return 0;
1✔
267
        }
268

269
        _cleanup_close_ int fd = -EBADF;
2✔
270
        _cleanup_free_ char *repart = NULL;
1✔
271
        fd = pin_callout_binary("systemd-repart", &repart);
1✔
272
        if (fd < 0)
1✔
273
                return log_error_errno(fd, "Failed to find systemd-repart binary: %m");
×
274

275
        r = sd_varlink_connect_exec(link, repart, /* argv= */ NULL);
1✔
276
        if (r < 0)
1✔
277
                return log_error_errno(r, "Failed to connect to systemd-repart: %m");
×
278

279
        return 1;
280
}
281

282
static int acquire_device_list(
×
283
                sd_varlink **link,
284
                char ***ret_menu,
285
                char ***ret_accepted) {
286
        int r;
×
287

288
        r = connect_to_repart(link);
×
289
        if (r < 0)
×
290
                return r;
×
291

292
        _cleanup_strv_free_ char **menu = NULL, **accepted = NULL;
×
293

294
        sd_json_variant *reply = NULL;
×
295
        const char *error_id = NULL;
×
296
        r = sd_varlink_collectbo(
×
297
                        *link,
298
                        "io.systemd.Repart.ListCandidateDevices",
299
                        &reply,
300
                        &error_id,
301
                        SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true));
302
        if (r < 0)
×
303
                return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
×
304
        if (streq_ptr(error_id, "io.systemd.Repart.NoCandidateDevices"))
×
305
                log_debug("No candidate devices found.");
×
306
        else if (error_id) {
×
307
                r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */
×
308
                if (r != -EBADR)
×
309
                        return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
×
310

311
                return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %s", error_id);
×
312
        } else {
313
                sd_json_variant *i;
×
314
                JSON_VARIANT_ARRAY_FOREACH(i, reply) {
×
315
                        _cleanup_(block_device_done) BlockDevice bd = BLOCK_DEVICE_NULL;
×
316

317
                        static const sd_json_dispatch_field dispatch_table[] = {
×
318
                                { "node",     SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(BlockDevice, node),     SD_JSON_MANDATORY },
319
                                { "symlinks", SD_JSON_VARIANT_ARRAY,  sd_json_dispatch_strv,   offsetof(BlockDevice, symlinks), 0                 },
320
                                {}
321
                        };
322

323
                        r = sd_json_dispatch(i, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &bd);
×
324
                        if (r < 0)
×
325
                                return r;
326

327
                        if (strv_extend(&accepted, bd.node) < 0)
×
328
                                return log_oom();
×
329
                        if (strv_extend_strv(&accepted, bd.symlinks, /* filter_duplicates= */ true) < 0)
×
330
                                return log_oom();
×
331

332
                        /* Prefer the by-id and by-loop-ref because they typically contain the strings most
333
                         * directly understood by the user */
334
                        const char *n = strv_find_prefix(bd.symlinks, "/dev/disk/by-id/");
×
335
                        if (!n)
×
336
                                n = strv_find_prefix(bd.symlinks, "/dev/disk/by-loop-ref/");
×
337
                        if (!n)
×
338
                                n = bd.node;
×
339

340
                        if (strv_extend(&menu, n) < 0)
×
341
                                return log_oom();
×
342
                }
343
        }
344

345
        *ret_menu = TAKE_PTR(menu);
×
346
        *ret_accepted = TAKE_PTR(accepted);
×
347
        return 0;
×
348
}
349

350
static int device_is_valid(const char *node, void *userdata) {
×
351

352
        if (!path_is_valid(node) || !path_is_absolute(node)) {
×
353
                log_error("Not a valid absolute file system path, refusing: %s", node);
×
354
                return false;
×
355
        }
356

357
        struct stat st;
×
358
        if (stat(node, &st) < 0) {
×
359
                log_error_errno(errno, "Failed to check if '%s' is a valid block device node: %m", node);
×
360
                return false;
361
        }
362
        if (!S_ISBLK(st.st_mode)) {
×
363
                log_error("Path '%s' does not refer to a valid block device node, refusing.", node);
×
364
                return false;
365
        }
366

367
        return true;
368
}
369

370
static int refresh_devices(char ***ret_menu, char ***ret_accepted, void *userdata) {
×
371
        sd_varlink **repart_link = ASSERT_PTR(userdata);
×
372

373
        (void) acquire_device_list(repart_link, ret_menu, ret_accepted);
×
374
        return 0;
×
375
}
376

377
static int prompt_block_device(sd_varlink **repart_link, char **ret_node) {
×
378
        int r;
×
379

380
        putchar('\n');
×
381

382
        _cleanup_strv_free_ char **menu = NULL, **accepted = NULL;
×
383
        (void) acquire_device_list(repart_link, &menu, &accepted);
×
384

385
        r = prompt_loop("Please enter target disk device",
×
386
                        GLYPH_COMPUTER_DISK,
387
                        /* prefill= */ NULL,
388
                        menu,
389
                        accepted,
390
                        /* ellipsize_percentage= */ 20,
391
                        /* n_columns= */ 1,
392
                        /* column_width= */ 80,
393
                        device_is_valid,
394
                        refresh_devices,
395
                        /* userdata= */ repart_link,
396
                        PROMPT_SHOW_MENU|PROMPT_SHOW_MENU_NOW|PROMPT_MAY_SKIP|PROMPT_HIDE_SKIP_HINT|PROMPT_HIDE_MENU_HINT,
397
                        ret_node);
398
        if (r < 0)
×
399
                return r;
400
        if (r == 0)
×
401
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
402

403
        return 0;
404
}
405

406
static int read_space_metrics(
2✔
407
                sd_json_variant *v,
408
                uint64_t *min_size,
409
                uint64_t *current_size,
410
                uint64_t *need_free) {
411

412
        int r;
2✔
413

414
        struct {
2✔
415
                uint64_t min_size;
416
                uint64_t current_size;
417
                uint64_t need_free;
418
        } p = {
2✔
419
                .min_size = UINT64_MAX,
420
                .current_size = UINT64_MAX,
421
                .need_free = UINT64_MAX,
422
        };
423

424
        static const sd_json_dispatch_field dispatch_table[] = {
2✔
425
                { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size),     0 },
426
                { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 },
427
                { "needFreeBytes",    _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free),    0 },
428
                {}
429
        };
430

431
        r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
2✔
432
        if (r < 0)
2✔
433
                return r;
2✔
434

435
        if (min_size)
2✔
436
                *min_size = p.min_size;
1✔
437
        if (current_size)
2✔
438
                *current_size = p.current_size;
1✔
439
        if (need_free)
2✔
440
                *need_free = p.need_free;
1✔
441

442
        return 0;
443
}
444

445
static int invoke_repart(
2✔
446
                sd_varlink **link,
447
                const char *node,
448
                bool erase,
449
                bool dry_run,
450
                uint64_t *min_size,        /* initialized both on success and error */
451
                uint64_t *current_size,    /* ditto */
452
                uint64_t *need_free) {     /* ditto */
453

454
        int r;
2✔
455

456
        assert(link);
2✔
457

458
        /* Note, if dry_run is true, then ENOSPC, E2BIG, EHWPOISON will not be logged about beyond LOG_DEBUG,
459
         * but all other errors will be */
460

461
        r = connect_to_repart(link);
2✔
462
        if (r < 0) {
2✔
463
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
464
                return r;
×
465
        }
466

467
        if (!dry_run) {
2✔
468
                /* Seeding the partitions might be very slow, disable timeout */
469
                r = sd_varlink_set_relative_timeout(*link, UINT64_MAX);
1✔
470
                if (r < 0)
1✔
471
                        return log_error_errno(r, "Failed to disable IPC timeout: %m");
×
472
        }
473

474
        sd_json_variant *reply = NULL;
2✔
475
        const char *error_id = NULL;
2✔
476
        r = sd_varlink_callbo(
2✔
477
                        *link,
478
                        "io.systemd.Repart.Run",
479
                        &reply,
480
                        &error_id,
481
                        SD_JSON_BUILD_PAIR_STRING("node", node),
482
                        SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"),
483
                        SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run),
484
                        SD_JSON_BUILD_PAIR_CONDITION(!!arg_definitions, "definitions", SD_JSON_BUILD_STRV(arg_definitions)),
485
                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
486
                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
487
        if (r < 0) {
2✔
488
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
489
                return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
×
490
        }
491
        if (error_id) {
2✔
492
                if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) {
×
493
                        (void) read_space_metrics(reply, min_size, current_size, need_free);
×
494
                        return log_full_errno(
×
495
                                        dry_run ? LOG_DEBUG : LOG_ERR,
496
                                        SYNTHETIC_ERRNO(ENOSPC),
497
                                        "Not enough free space on disk, cannot install.");
498
                }
499
                if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) {
×
500
                        (void) read_space_metrics(reply, min_size, current_size, need_free);
×
501
                        return log_full_errno(
×
502
                                        dry_run ? LOG_DEBUG : LOG_ERR,
503
                                        SYNTHETIC_ERRNO(E2BIG),
504
                                        "Disk too small for installation, cannot install.");
505
                }
506

507
                /* For all other errors reset the metrics */
508
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
509

510
                if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent"))
×
511
                        return log_full_errno(
×
512
                                        dry_run ? LOG_DEBUG : LOG_ERR,
513
                                        SYNTHETIC_ERRNO(EHWPOISON),
514
                                        "A conflicting disk label is already present on the target disk, cannot install unless disk is erased.");
515

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

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

523
        (void) read_space_metrics(reply, min_size, current_size, need_free);
2✔
524

525
        return 0;
526
}
527

528
static int prompt_erase(
×
529
                bool can_add,
530
                int *ret_erase) {
531
        int r;
×
532

533
        assert(ret_erase);
×
534

535
        putchar('\n');
×
536

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

539
        _cleanup_free_ char *reply = NULL;
×
540
        r = prompt_loop(can_add ?
×
541
                        "Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk" :
542
                        "Please type 'erase' to confirm that all data on the disk shall be erased",
543
                        GLYPH_BROOM,
544
                        /* prefill= */ NULL,
545
                        /* menu= */ l,
546
                        /* accepted= */ l,
547
                        /* ellipsize_percentage= */ 20,
548
                        /* n_columns= */ 2,
549
                        /* column_width= */ 40,
550
                        /* is_valid= */ NULL,
551
                        /* refresh= */ NULL,
552
                        /* userdata= */ NULL,
553
                        PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
554
                        &reply);
555
        if (r < 0)
×
556
                return r;
557
        if (r == 0)
×
558
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
559

560
        if (streq(reply, "erase"))
×
561
                *ret_erase = true;
×
562
        else if (streq(reply, "keep"))
×
563
                *ret_erase = false;
×
564
        else
565
                assert_not_reached();
×
566

567
        return 0;
568
}
569

570
static int prompt_touch_variables(void) {
1✔
571
        int r;
1✔
572

573
        if (arg_touch_variables >= 0)
1✔
574
                return 0;
1✔
575

576
        putchar('\n');
×
577

578
        char **l = STRV_MAKE("yes", "no");
×
579

580
        _cleanup_free_ char *reply = NULL;
×
581
        r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise",
×
582
                        GLYPH_ROCKET,
583
                        /* prefill= */ "yes",
584
                        /* menu= */ l,
585
                        /* accepted= */ l,
586
                        /* ellipsize_percentage= */ 20,
587
                        /* n_columns= */ 2,
588
                        /* column_width= */ 40,
589
                        /* is_valid= */ NULL,
590
                        /* refresh= */ NULL,
591
                        /* userdata= */ NULL,
592
                        PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
593
                        &reply);
594
        if (r < 0)
×
595
                return r;
596
        if (r == 0)
×
597
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
598

599
        r = parse_boolean(reply);
×
600
        if (r < 0)
×
601
                return log_error_errno(r, "Failed to parse reply: %s", reply);
×
602

603
        arg_touch_variables = r;
×
604

605
        return 0;
×
606
}
607

608
static int prompt_confirm(void) {
1✔
609
        int r;
1✔
610

611
        if (!arg_confirm)
1✔
612
                return 0;
1✔
613

614
        putchar('\n');
×
615

616
        bool yes;
×
617
        r = prompt_loop_yes_no(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" :
×
618
                                             "Please type 'yes' to begin the installation",
619
                               /* def= */ false,
620
                               &yes);
621
        if (r < 0)
×
622
                return r;
623
        if (!yes)
×
624
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation not confirmed, cancelling.");
×
625

626
        return 0;
627
}
628

629
static int validate_run(sd_varlink **repart_link, const char *node) {
1✔
630
        int r;
1✔
631

632
        assert(repart_link);
1✔
633
        assert(node);
1✔
634

635
        /* First loop: either with explicitly configured --erase= value, or false. A second loop only if not configured explicitly. */
636
        bool try_erase = arg_erase > 0, conflicting_disk_label = false;
1✔
637
        for (;;) {
1✔
638
                uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX;
1✔
639
                r = invoke_repart(
1✔
640
                                repart_link,
641
                                node,
642
                                try_erase,
643
                                /* dry_run= */ true,
644
                                &min_size,
645
                                &current_size,
646
                                &need_free);
647
                if (r == -ENOSPC) {
1✔
648
                        /* The disk is large enough, but there's not enough unallocated space. Hence proceed, but require erasing */
649
                        if (try_erase || arg_erase >= 0)
×
650
                                return log_error_errno(r, "The selected disk is big enough for the installation but does not have enough free space.");
×
651

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

656
                        try_erase = true;
657
                } else if (r == -E2BIG) {
1✔
658
                        /* Won't fit, whatever we do */
659
                        log_error_errno(r, "The selected disk is not large enough for an OS installation.");
×
660
                        if (current_size != UINT64_MAX)
×
661
                                log_info("The size of the selected disk is %s, but a minimal size of %s is required.",
×
662
                                         FORMAT_BYTES(current_size),
663
                                         FORMAT_BYTES(min_size));
664
                        return r;
665
                } else if (r == -EHWPOISON) {
1✔
666
                        if (try_erase || arg_erase >= 0)
×
667
                                return log_error_errno(r, "The selected disk contains a conflicting disk label, refusing.");
×
668

669
                        log_debug("Disk contains a conflicting disk label, checking if we could install the OS after erasing it.");
×
670
                        try_erase = true;
×
671
                        conflicting_disk_label = true;
×
672
                        continue;
×
673
                } else if (r < 0)
1✔
674
                        /* invoke_repart() already logged about all other errors */
675
                        return r;
676
                else
677
                        /* Nice, we can add the OS to the disk, without erasing anything. */
678
                        log_info("The selected disk has enough free space for an installation of the OS.");
1✔
679

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

683
                if (arg_erase < 0) {
1✔
684
                        r = prompt_erase(/* can_add= */ !try_erase, &arg_erase);
×
685
                        if (r < 0)
×
686
                                return r;
×
687
                }
688

689
                return 0;
690
        }
691
}
692

693
static int show_summary(void) {
1✔
694
        int r;
1✔
695

696
        if (!arg_summary)
1✔
697
                return 0;
1✔
698

699
        printf("\n"
×
700
               "%sSummary:%s\n", ansi_underline(), ansi_normal());
701

702
        _cleanup_(table_unrefp) Table *table = table_new_vertical();
×
703
        if (!table)
×
704
                return log_oom();
×
705

706
        r = table_add_many(
×
707
                        table,
708
                        TABLE_FIELD, "Selected Disk",
709
                        TABLE_STRING, arg_node,
710
                        TABLE_FIELD, "Erase Disk",
711
                        TABLE_BOOLEAN, arg_erase,
712
                        TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL,
713
                        TABLE_FIELD, "Register in Firmware",
714
                        TABLE_BOOLEAN, arg_touch_variables);
715
        if (r < 0)
×
716
                return table_log_add_error(r);
×
717

718
        static const char * const map[] = {
719
                "firstboot.keymap",          "Keyboard Map",
720
                "firstboot.locale",          "Locale",
721
                "firstboot.locale-messages", "Locale (Messages)",
722
                "firstboot.timezone",        "Timezone",
723
                NULL
724
        };
725

726
        STRV_FOREACH_PAIR(id, text, map) {
×
727
                MachineCredential *c = machine_credential_find(&arg_credentials, *id);
×
728
                if (!c)
×
729
                        continue;
×
730

731
                _cleanup_free_ char *escaped = cescape_length(c->data, c->size);
×
732
                if (!escaped)
×
733
                        return log_oom();
×
734

735
                r = table_add_many(
×
736
                                table,
737
                                TABLE_FIELD, *text,
738
                                TABLE_STRING, escaped);
739
                if (r < 0)
×
740
                        return table_log_add_error(r);
×
741
        }
742

743
        unsigned n_extra_credentials = 0;
×
744
        FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
×
745
                bool covered = false;
×
746

747
                STRV_FOREACH_PAIR(id, text, map)
×
748
                        if (streq(*id, cred->id)) {
×
749
                                covered = true;
750
                                break;
751
                        }
752

753
                if (!covered)
×
754
                        n_extra_credentials++;
×
755
        }
756

757
        if (n_extra_credentials > 0) {
×
758
                r = table_add_many(
×
759
                                table,
760
                                TABLE_FIELD, "Extra Credentials",
761
                                TABLE_UINT, n_extra_credentials);
762
                if (r < 0)
×
763
                        return table_log_add_error(r);
×
764
        }
765

766
        r = table_print(table);
×
767
        if (r < 0)
×
768
                return r;
×
769

770
        return 0;
771
}
772

773
static int find_current_kernel(
×
774
                char **ret_filename,
775
                int *ret_fd) {
776

777
        int r;
×
778

779
        sd_id128_t uuid;
×
780
        r = efi_stub_get_device_part_uuid(&uuid);
×
781
        if (r == -ENOENT)
×
782
                return log_error_errno(r, "Cannot find current kernel, no stub partition UUID passed via EFI variables.");
×
783
        if (r < 0)
×
784
                return log_error_errno(r, "Unable to determine stub partition UUID: %m");
×
785

786
        _cleanup_free_ char *image = NULL;
×
787
        r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &image);
×
788
        if (r == -ENOENT)
×
789
                return log_error_errno(r, "Cannot find current kernel, no stub EFI binary path passed.");
×
790
        if (r < 0)
×
791
                return log_error_errno(r, "Unable to determine stub EFI binary path: %m");
×
792

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

796
        _cleanup_free_ char *partition_path = NULL;
×
797
        _cleanup_close_ int partition_fd = -EBADF;
×
798
        sd_id128_t partition_uuid;
×
799
        r = find_esp_and_warn_full(
×
800
                        /* root= */ NULL,
801
                        /* path= */ NULL,
802
                        /* unprivileged_mode= */ false,
803
                        &partition_path,
804
                        &partition_fd,
805
                        /* ret_part= */ NULL,
806
                        /* ret_pstart= */ NULL,
807
                        /* ret_psize= */ NULL,
808
                        &partition_uuid,
809
                        /* ret_devid= */ NULL);
810
        if (r < 0 && r != -ENOKEY)
×
811
                return r;
812
        if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) {
×
813
                partition_path = mfree(partition_path);
×
814
                partition_fd = safe_close(partition_fd);
×
815

816
                r = find_xbootldr_and_warn_full(
×
817
                                /* root= */ NULL,
818
                                /* path= */ NULL,
819
                                /* unprivileged_mode= */ false,
820
                                &partition_path,
821
                                &partition_fd,
822
                                &partition_uuid,
823
                                /* ret_devid= */ NULL);
824
                if (r < 0 && r != -ENOKEY)
×
825
                        return r;
826

827
                if (r < 0 || !sd_id128_equal(uuid, partition_uuid))
×
828
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Unable to find UKI on ESP/XBOOTLDR partitions.");
×
829
        }
830

831
        _cleanup_free_ char *resolved = NULL;
×
832
        _cleanup_close_ int fd = chase_and_openat(
×
833
                        /* root_fd= */ partition_fd,
834
                        /* dir_fd= */ partition_fd,
835
                        image,
836
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR,
837
                        O_RDONLY|O_CLOEXEC,
838
                        &resolved);
839
        if (fd < 0)
×
840
                return log_error_errno(fd, "Failed to find EFI binary '%s' on partition '%s': %m", image, partition_path);
×
841

842
        _cleanup_free_ char *fn = NULL;
×
843
        r = path_extract_filename(resolved, &fn);
×
844
        if (r < 0)
×
845
                return log_error_errno(r, "Failed to extract UKI file name from '%s': %m", resolved);
×
846

847
        if (ret_filename)
×
848
                *ret_filename = TAKE_PTR(fn);
×
849
        if (ret_fd)
×
850
                *ret_fd = TAKE_FD(fd);
×
851

852
        return 0;
853
}
854

855
static int connect_to_bootctl(sd_varlink **link) {
2✔
856
        int r;
2✔
857

858
        assert(link);
2✔
859

860
        if (*link)
2✔
861
                return 0;
2✔
862

863
        _cleanup_close_ int fd = -EBADF;
2✔
864
        _cleanup_free_ char *bootctl = NULL;
1✔
865
        fd = pin_callout_binary("bootctl", &bootctl);
1✔
866
        if (fd < 0)
1✔
867
                return log_error_errno(fd, "Failed to find bootctl binary: %m");
×
868

869
        r = sd_varlink_connect_exec(link, bootctl, /* argv= */ NULL);
1✔
870
        if (r < 0)
1✔
871
                return log_error_errno(r, "Failed to connect to bootctl: %m");
×
872

873
        r = sd_varlink_set_allow_fd_passing_output(*link, true);
1✔
874
        if (r < 0)
1✔
875
                return log_error_errno(r, "Failed to enable fd passing to bootctl: %m");
×
876

877
        return 1;
878
}
879

880
static int invoke_bootctl_install(
1✔
881
                sd_varlink **link,
882
                const char *root_dir,
883
                int root_fd) {
884
        int r;
1✔
885

886
        assert(link);
1✔
887
        assert(root_dir);
1✔
888
        assert(root_fd >= 0);
1✔
889

890
        r = connect_to_bootctl(link);
1✔
891
        if (r < 0)
1✔
892
                return r;
1✔
893

894
        int fd_idx = sd_varlink_push_dup_fd(*link, root_fd);
1✔
895
        if (fd_idx < 0)
1✔
896
                return log_error_errno(fd_idx, "Failed to submit root fd onto Varlink connection: %m");
×
897

898
        const char *error_id = NULL;
1✔
899
        r = varlink_callbo_and_log(
1✔
900
                        *link,
901
                        "io.systemd.BootControl.Install",
902
                        /* reply= */ NULL,
903
                        &error_id,
904
                        SD_JSON_BUILD_PAIR_STRING("operation", "new"),
905
                        SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx),
906
                        SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
907
                        SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables));
908
        if (r < 0)
1✔
909
                return r;
×
910

911
        return 0;
912
}
913

914
static int invoke_bootctl_link(
1✔
915
                sd_varlink **link,
916
                const char *root_dir,
917
                int root_fd,
918
                char **encrypted_credentials) {
919
        int r;
1✔
920

921
        assert(link);
1✔
922
        assert(root_dir);
1✔
923
        assert(root_fd >= 0);
1✔
924

925
        r = connect_to_bootctl(link);
1✔
926
        if (r < 0)
1✔
927
                return r;
1✔
928

929
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
1✔
930
        STRV_FOREACH_PAIR(name, value, encrypted_credentials) {
2✔
931
                _cleanup_free_ char *j = strjoin(*name, ".cred");
2✔
932
                if (!j)
1✔
933
                        return log_oom();
×
934

935
                r = sd_json_variant_append_arraybo(
1✔
936
                                &array,
937
                                SD_JSON_BUILD_PAIR_STRING("filename", j),
938
                                SD_JSON_BUILD_PAIR_BASE64("data", *value, strlen(*value)));
939
                if (r < 0)
1✔
940
                        return log_error_errno(r, "Failed to append credential to message: %m");
×
941
        }
942

943
        int root_fd_idx = sd_varlink_push_dup_fd(*link, root_fd);
1✔
944
        if (root_fd_idx < 0)
1✔
945
                return log_error_errno(root_fd_idx, "Failed to submit root fd onto Varlink connection: %m");
×
946

947
        _cleanup_free_ char *kernel_filename = NULL;
1✔
948
        _cleanup_close_ int kernel_fd = -EBADF;
1✔
949
        if (arg_kernel_image) {
1✔
950
                r = path_extract_filename(arg_kernel_image, &kernel_filename);
1✔
951
                if (r < 0)
1✔
952
                        return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image);
×
953
                if (r == O_DIRECTORY)
1✔
954
                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image);
×
955

956
                kernel_fd = xopenat_full(XAT_FDROOT, arg_kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
1✔
957
                if (kernel_fd < 0)
1✔
958
                        return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", arg_kernel_image);
×
959

960
        } else {
961
                r = find_current_kernel(&kernel_filename, &kernel_fd);
×
962
                if (r < 0)
×
963
                        return r;
964
        }
965

966
        int kernel_fd_idx = sd_varlink_push_dup_fd(*link, kernel_fd);
1✔
967
        if (kernel_fd_idx < 0)
1✔
968
                return log_error_errno(kernel_fd_idx, "Failed to submit kernel fd onto Varlink connection: %m");
×
969

970
        const char *error_id = NULL;
1✔
971
        r = varlink_callbo_and_log(
1✔
972
                        *link,
973
                        "io.systemd.BootControl.Link",
974
                        /* reply= */ NULL,
975
                        &error_id,
976
                        SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", root_fd_idx),
977
                        SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
978
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("kernelFilename", kernel_filename),
979
                        SD_JSON_BUILD_PAIR_INTEGER("kernelFileDescriptor", kernel_fd_idx),
980
                        SD_JSON_BUILD_PAIR_CONDITION(!!array, "extraFiles", SD_JSON_BUILD_VARIANT(array)));
981
        if (r < 0)
1✔
982
                return r;
×
983

984
        return 0;
985
}
986

987
static int maybe_reboot(void) {
1✔
988
        int r;
1✔
989

990
        if (!arg_reboot)
1✔
991
                return 0;
1✔
992

993
        log_notice("%s%sSystem will reboot now.",
×
994
                   emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : "");
995

996
        if (!any_key_to_proceed())
×
997
                return 0;
998

999
        log_notice("%s%sInitiating reboot.",
×
1000
                   emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : "");
1001

1002
        _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
1✔
1003
        r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.Shutdown");
×
1004
        if (r < 0)
×
1005
                return log_error_errno(r, "Failed to connect to systemd-logind: %m");
×
1006

1007
        sd_json_variant *reply = NULL;
×
1008
        const char *error_id = NULL;
×
1009
        r = varlink_callbo_and_log(
×
1010
                        link,
1011
                        "io.systemd.Shutdown.Reboot",
1012
                        &reply,
1013
                        &error_id);
1014
        if (r < 0)
×
1015
                return r;
×
1016

1017
        return 0;
1018
}
1019

1020
static int read_credential_locale(void) {
1✔
1021
        int r;
1✔
1022

1023
        if (!arg_copy_locale)
1✔
1024
                return 0;
1025

1026
        if (machine_credential_find(&arg_credentials, "firstboot.locale") ||
×
1027
            machine_credential_find(&arg_credentials, "firstboot.locale-messages"))
×
1028
                return 0;
1029

1030
        /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since
1031
         * it seems to be one of the most fundamental ones, and is not LC_MESSAGES for which we have a
1032
         * separate setting after all */
1033
        const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL);
×
1034
        if (l) {
×
1035
                r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
×
1036
                if (r < 0)
×
1037
                        return log_oom();
×
1038
        }
1039

1040
        const char *m = setlocale(LC_MESSAGES, NULL);
×
1041
        if (m && !streq_ptr(m, l)) {
×
1042
                r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
×
1043
                if (r < 0)
×
1044
                        return log_oom();
×
1045
        }
1046

1047
        return 0;
1048
}
1049

1050
static int read_credential_keymap(void) {
1✔
1051
        int r;
1✔
1052

1053
        if (!arg_copy_keymap)
1✔
1054
                return 0;
1✔
1055

1056
        if (machine_credential_find(&arg_credentials, "firstboot.keymap"))
×
1057
                return 0;
1058

1059
        _cleanup_free_ char *keymap = NULL;
×
1060
        r = parse_env_file(
×
1061
                        /* f= */ NULL,
1062
                        etc_vconsole_conf(),
1063
                        "KEYMAP", &keymap);
1064
        if (r < 0 && r != -ENOENT)
×
1065
                return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf());
×
1066

1067
        if (!isempty(keymap)) {
×
1068
                r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
×
1069
                if (r < 0)
×
1070
                        return log_oom();
×
1071
        }
1072

1073
        return 0;
1074
}
1075

1076
static int read_credential_timezone(void) {
1✔
1077
        int r;
1✔
1078

1079
        if (!arg_copy_timezone)
1✔
1080
                return 0;
1✔
1081

1082
        if (machine_credential_find(&arg_credentials, "firstboot.timezone"))
×
1083
                return 0;
1084

1085
        _cleanup_free_ char *tz = NULL;
×
1086
        r = get_timezone_prefer_env(&tz);
×
1087
        if (r < 0)
×
1088
                log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m");
×
1089
        else {
1090
                r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
×
1091
                if (r < 0)
×
1092
                        return log_oom();
×
1093
        }
1094

1095
        return 0;
1096
}
1097

1098
static int read_credentials(void) {
1✔
1099
        int r;
1✔
1100

1101
        r = read_credential_locale();
1✔
1102
        if (r < 0)
1✔
1103
                return r;
1104

1105
        r = read_credential_keymap();
1✔
1106
        if (r < 0)
1✔
1107
                return r;
1108

1109
        r = read_credential_timezone();
1✔
1110
        if (r < 0)
1✔
1111
                return r;
×
1112

1113
        return 0;
1114
}
1115

1116
static int connect_to_creds(sd_varlink **link) {
1✔
1117
        int r;
1✔
1118

1119
        assert(link);
1✔
1120

1121
        if (*link)
1✔
1122
                return 0;
1✔
1123

1124
        _cleanup_close_ int fd = -EBADF;
1✔
1125
        _cleanup_free_ char *creds = NULL;
1✔
1126
        fd = pin_callout_binary("systemd-creds", &creds);
1✔
1127
        if (fd < 0)
1✔
1128
                return log_error_errno(fd, "Failed to find systemd-creds binary: %m");
×
1129

1130
        r = sd_varlink_connect_exec(link, creds, /* argv= */ NULL);
1✔
1131
        if (r < 0)
1✔
1132
                return log_error_errno(r, "Failed to connect to systemd-creds: %m");
×
1133

1134
        return 1;
1135
}
1136

1137
static int encrypt_one_credential(sd_varlink **link, const MachineCredential *input, char ***encrypted) {
1✔
1138
        int r;
1✔
1139

1140
        assert(link);
1✔
1141
        assert(input);
1✔
1142
        assert(encrypted);
1✔
1143

1144
        log_info("Encrypting credential '%s'...", input->id);
1✔
1145

1146
        r = connect_to_creds(link);
1✔
1147
        if (r < 0)
1✔
1148
                return r;
1✔
1149

1150
        sd_json_variant *reply = NULL;
1✔
1151
        const char *error_id = NULL;
1✔
1152
        r = varlink_callbo_and_log(
1✔
1153
                        *link,
1154
                        "io.systemd.Credentials.Encrypt",
1155
                        &reply,
1156
                        &error_id,
1157
                        SD_JSON_BUILD_PAIR_STRING("name", input->id),
1158
                        SD_JSON_BUILD_PAIR_BASE64("data", input->data, input->size),
1159
                        SD_JSON_BUILD_PAIR_STRING("scope", "system"),
1160
                        /* We pick the 'auto_initrd' key for this, since we want TPM if available, but are fine with NULL if not */
1161
                        SD_JSON_BUILD_PAIR_STRING("withKey", "auto_initrd"));
1162
        if (r < 0)
1✔
1163
                return r;
1164

1165
        static const sd_json_dispatch_field dispatch_table[] = {
1✔
1166
                { "blob", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
1167
                {}
1168
        };
1169

1170
        const char *blob = NULL;
1✔
1171
        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &blob);
1✔
1172
        if (r < 0)
1✔
1173
                return r;
1174

1175
        r = strv_extend_many(encrypted, input->id, blob);
1✔
1176
        if (r < 0)
1✔
1177
                return r;
×
1178

1179
        return 0;
1180
}
1181

1182
static int encrypt_credentials(sd_varlink **link, char ***encrypted) {
1✔
1183
        int r;
1✔
1184

1185
        assert(link);
1✔
1186
        assert(encrypted);
1✔
1187

1188
        FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
2✔
1189
                r = encrypt_one_credential(link, cred, encrypted);
1✔
1190
                if (r < 0)
1✔
1191
                        return r;
1192
        }
1193

1194
        return 0;
1195
}
1196

1197
static const ImagePolicy image_policy = {
1198
        .n_policies = 4,
1199
        .policies = {
1200
                /* We mount / and /usr/ so that we can get access to /etc/machine-id and /etc/kernel/ */
1201
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1202
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1203
                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1204
                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1205
        },
1206
        .default_flags = PARTITION_POLICY_IGNORE,
1207
};
1208

1209
static int settle_definitions(void) {
1✔
1210
        int r;
1✔
1211

1212
        if (arg_definitions)
1✔
1213
                return 0;
1✔
1214

1215
        /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition
1216
         * files */
1217

1218
        _cleanup_strv_free_ char **files = NULL;
×
1219
        r = conf_files_list_strv(
×
1220
                        &files,
1221
                        ".conf",
1222
                        /* root= */ NULL,
1223
                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN|CONF_FILES_DONT_PREFIX_ROOT,
1224
                        (const char**) CONF_PATHS_STRV("repart.sysinstall.d"));
×
1225
        if (r < 0)
×
1226
                return log_error_errno(r, "Failed to enumerate *.conf files: %m");
×
1227

1228
        if (!strv_isempty(files)) {
×
1229
                arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
×
1230
                if (!arg_definitions)
×
1231
                        return log_oom();
×
1232
        }
1233

1234
        return 0;
1235
}
1236

1237
static void end_marker(void) {
1✔
1238

1239
        if (!arg_welcome)
1✔
1240
                return;
1241

1242
        printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal());
×
1243
        fflush(stdout);
×
1244
}
1245

1246
static int run(int argc, char *argv[]) {
1✔
1247
        int r;
1✔
1248

1249
        setlocale(LC_ALL, "");
1✔
1250

1251
        r = parse_argv(argc, argv);
1✔
1252
        if (r <= 0)
1✔
1253
                return r;
1✔
1254

1255
        log_setup();
1✔
1256

1257
        r = settle_definitions();
1✔
1258
        if (r < 0)
1✔
1259
                return r;
1260

1261
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL;
1✔
1262
        if (arg_welcome) {
1✔
1263
                if (arg_mute_console)
×
1264
                        (void) mute_console(&mute_console_link);
×
1265

1266
                (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
×
1267

1268
                if (arg_chrome)
×
1269
                        chrome_show("Operating System Installer", /* bottom= */ NULL);
×
1270
        }
1271

1272
        DEFER_VOID_CALL(end_marker);
1✔
1273
        DEFER_VOID_CALL(chrome_hide);
×
1274

1275
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *repart_link = NULL;
1✔
1276
        if (arg_node) {
1✔
1277
                r = print_welcome(&mute_console_link);
1✔
1278
                if (r < 0)
1✔
1279
                        return r;
1280

1281
                r = validate_run(&repart_link, arg_node);
1✔
1282
                if (r < 0)
1✔
1283
                        return r;
1284
        } else {
1285
                /* Determine the minimum disk size */
1286
                uint64_t min_size = UINT64_MAX;
×
1287
                r = invoke_repart(
×
1288
                                &repart_link,
1289
                                /* node= */ NULL,
1290
                                /* erase= */ true,
1291
                                /* dry_run= */ true,
1292
                                &min_size,
1293
                                /* current_size= */ NULL,
1294
                                /* need_free= */ NULL);
1295
                if (r < 0)
×
1296
                        return r;
×
1297

1298
                r = print_welcome(&mute_console_link);
×
1299
                if (r < 0)
×
1300
                        return r;
1301

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

1304
                for (;;) {
×
1305
                        _cleanup_free_ char *node = NULL;
×
1306
                        r = prompt_block_device(&repart_link, &node);
×
1307
                        if (r < 0)
×
1308
                                return r;
1309

1310
                        r = validate_run(&repart_link, node);
×
1311
                        if (IN_SET(r, -ENOSPC, -E2BIG, -EHWPOISON)) /* Device is no fit, pick other */
×
1312
                                continue;
×
1313
                        if (r < 0)
×
1314
                                return r;
1315

1316
                        arg_node = TAKE_PTR(node);
×
1317
                        break;
×
1318
                }
1319
        }
1320

1321
        r = prompt_touch_variables();
1✔
1322
        if (r < 0)
1✔
1323
                return r;
1324

1325
        r = read_credentials();
1✔
1326
        if (r < 0)
1✔
1327
                return r;
1328

1329
        /* Verify we have everything we need */
1330
        assert(arg_node);
1✔
1331
        assert(arg_erase >= 0);
1✔
1332
        assert(arg_touch_variables >= 0);
1✔
1333

1334
        r = show_summary();
1✔
1335
        if (r < 0)
1✔
1336
                return r;
1337

1338
        r = prompt_confirm();
1✔
1339
        if (r < 0)
1✔
1340
                return r;
1341

1342
        putchar('\n');
1✔
1343

1344
        log_notice("%s%sEncrypting credentials...",
2✔
1345
                   emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : "");
1346

1347
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
1✔
1348
        _cleanup_strv_free_ char **encrypted_credentials = NULL;
1✔
1349
        r = encrypt_credentials(&creds_link, &encrypted_credentials);
1✔
1350
        if (r < 0)
1✔
1351
                return r;
1352

1353
        log_notice("%s%sInstalling partitions...",
2✔
1354
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1355

1356
        /* Do the main part of the installation */
1357
        r = invoke_repart(
1✔
1358
                        &repart_link,
1359
                        arg_node,
1360
                        arg_erase,
1361
                        /* dry_run= */ false,
1362
                        /* min_size= */ NULL,
1363
                        /* current_size= */ NULL,
1364
                        /* need_free= */ NULL);
1365
        if (r < 0)
1✔
1366
                return r;
1367

1368
        log_notice("%s%sMounting partitions...",
2✔
1369
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1370

1371
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1✔
1372
        _cleanup_(umount_and_freep) char *root_dir = NULL;
×
1373
        _cleanup_close_ int root_fd = -EBADF;
1✔
1374
        r = mount_image_privately_interactively(
1✔
1375
                        arg_node,
1376
                        &image_policy,
1377
                        DISSECT_IMAGE_REQUIRE_ROOT |
1378
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1379
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
1380
                        DISSECT_IMAGE_DISCARD_ANY |
1381
                        DISSECT_IMAGE_GPT_ONLY |
1382
                        DISSECT_IMAGE_FSCK |
1383
                        DISSECT_IMAGE_USR_NO_ROOT |
1384
                        DISSECT_IMAGE_ADD_PARTITION_DEVICES |
1385
                        DISSECT_IMAGE_PIN_PARTITION_DEVICES,
1386
                        &root_dir,
1387
                        &root_fd,
1388
                        &loop_device);
1389
        if (r < 0)
1✔
1390
                return log_error_errno(r, "Failed to mount new image: %m");
×
1391

1392
        log_notice("%s%sInstalling kernel...",
2✔
1393
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1394

1395
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
1✔
1396
        r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, encrypted_credentials);
1✔
1397
        if (r < 0)
1✔
1398
                return r;
1399

1400
        log_notice("%s%sInstalling boot loader...",
2✔
1401
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1402

1403
        r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd);
1✔
1404
        if (r < 0)
1✔
1405
                return r;
1406

1407
        log_notice("%s%sUnmounting partitions...",
2✔
1408
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1409

1410
        root_fd = safe_close(root_fd);
1✔
1411
        r = umount_recursive(root_dir, /* flags= */ 0);
1✔
1412
        if (r < 0)
1✔
1413
                log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
×
1414
        loop_device = loop_device_unref(loop_device);
1✔
1415
        sync();
1✔
1416

1417
        log_notice("%s%sInstallation succeeded.",
2✔
1418
                   emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : "");
1419

1420
        r = maybe_reboot();
1✔
1421
        if (r < 0)
1✔
1422
                return r;
×
1423

1424
        return 0;
1425
}
1426

1427
DEFINE_MAIN_FUNCTION(run);
1✔
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