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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

49.08
/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);
UNCOV
398
        if (r < 0)
×
399
                return r;
400
        if (r == 0)
×
UNCOV
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);
×
UNCOV
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✔
UNCOV
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);
×
UNCOV
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);
×
UNCOV
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);
×
UNCOV
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 */
UNCOV
508
                read_space_metrics(/* v= */ NULL, min_size, current_size, need_free);
×
509

510
                if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent"))
×
UNCOV
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)
×
UNCOV
518
                        return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
×
519

UNCOV
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

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

UNCOV
533
        assert(ret_erase);
×
534

UNCOV
535
        putchar('\n');
×
536

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

539
        _cleanup_free_ char *reply = NULL;
×
UNCOV
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;
UNCOV
557
        if (r == 0)
×
558
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
559

560
        if (streq(reply, "erase"))
×
561
                *ret_erase = true;
×
UNCOV
562
        else if (streq(reply, "keep"))
×
563
                *ret_erase = false;
×
564
        else
UNCOV
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

UNCOV
580
        _cleanup_free_ char *reply = NULL;
×
UNCOV
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

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

UNCOV
603
        arg_touch_variables = r;
×
604

UNCOV
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

UNCOV
614
        putchar('\n');
×
615

616
        char **l = STRV_MAKE("yes", "no");
×
617

UNCOV
618
        _cleanup_free_ char *reply = NULL;
×
UNCOV
619
        r = prompt_loop(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" :
×
620
                                      "Please type 'yes' to begin the installation",
621
                        GLYPH_WARNING_SIGN,
622
                        /* prefill= */ NULL,
623
                        /* menu= */ l,
624
                        /* accepted= */ l,
625
                        /* ellipsize_percentage= */ 20,
626
                        /* n_columns= */ 2,
627
                        /* column_width= */ 40,
628
                        /* is_valid= */ NULL,
629
                        /* refresh= */ NULL,
630
                        /* userdata= */ NULL,
631
                        PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT,
632
                        &reply);
UNCOV
633
        if (r < 0)
×
634
                return r;
635
        if (r == 0)
×
UNCOV
636
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled.");
×
637

UNCOV
638
        if (!streq(reply, "yes"))
×
UNCOV
639
                return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation not confirmed, cancelling.");
×
640

641
        return 0;
642
}
643

644
static int validate_run(sd_varlink **repart_link, const char *node) {
1✔
645
        int r;
1✔
646

647
        assert(repart_link);
1✔
648
        assert(node);
1✔
649

650
        /* First loop: either with explicitly configured --erase= value, or false. A second loop only if not configured explicitly. */
651
        bool try_erase = arg_erase > 0, conflicting_disk_label = false;
1✔
652
        for (;;) {
1✔
653
                uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX;
1✔
654
                r = invoke_repart(
1✔
655
                                repart_link,
656
                                node,
657
                                try_erase,
658
                                /* dry_run= */ true,
659
                                &min_size,
660
                                &current_size,
661
                                &need_free);
662
                if (r == -ENOSPC) {
1✔
663
                        /* The disk is large enough, but there's not enough unallocated space. Hence proceed, but require erasing */
664
                        if (try_erase || arg_erase >= 0)
×
665
                                return log_error_errno(r, "The selected disk is big enough for the installation but does not have enough free space.");
×
666

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

671
                        try_erase = true;
672
                } else if (r == -E2BIG) {
1✔
673
                        /* Won't fit, whatever we do */
UNCOV
674
                        log_error_errno(r, "The selected disk is not large enough for an OS installation.");
×
UNCOV
675
                        if (current_size != UINT64_MAX)
×
UNCOV
676
                                log_info("The size of the selected disk is %s, but a minimal size of %s is required.",
×
677
                                         FORMAT_BYTES(current_size),
678
                                         FORMAT_BYTES(min_size));
679
                        return r;
680
                } else if (r == -EHWPOISON) {
1✔
681
                        if (try_erase || arg_erase >= 0)
×
682
                                return log_error_errno(r, "The selected disk contains a conflicting disk label, refusing.");
×
683

UNCOV
684
                        log_debug("Disk contains a conflicting disk label, checking if we could install the OS after erasing it.");
×
UNCOV
685
                        try_erase = true;
×
UNCOV
686
                        conflicting_disk_label = true;
×
UNCOV
687
                        continue;
×
688
                } else if (r < 0)
1✔
689
                        /* invoke_repart() already logged about all other errors */
690
                        return r;
691
                else
692
                        /* Nice, we can add the OS to the disk, without erasing anything. */
693
                        log_info("The selected disk has enough free space for an installation of the OS.");
1✔
694

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

698
                if (arg_erase < 0) {
1✔
UNCOV
699
                        r = prompt_erase(/* can_add= */ !try_erase, &arg_erase);
×
UNCOV
700
                        if (r < 0)
×
UNCOV
701
                                return r;
×
702
                }
703

704
                return 0;
705
        }
706
}
707

708
static int show_summary(void) {
1✔
709
        int r;
1✔
710

711
        if (!arg_summary)
1✔
712
                return 0;
1✔
713

714
        printf("\n"
×
715
               "%sSummary:%s\n", ansi_underline(), ansi_normal());
716

717
        _cleanup_(table_unrefp) Table *table = table_new_vertical();
×
UNCOV
718
        if (!table)
×
UNCOV
719
                return log_oom();
×
720

UNCOV
721
        r = table_add_many(
×
722
                        table,
723
                        TABLE_FIELD, "Selected Disk",
724
                        TABLE_STRING, arg_node,
725
                        TABLE_FIELD, "Erase Disk",
726
                        TABLE_BOOLEAN, arg_erase,
727
                        TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL,
728
                        TABLE_FIELD, "Register in Firmware",
729
                        TABLE_BOOLEAN, arg_touch_variables);
UNCOV
730
        if (r < 0)
×
UNCOV
731
                return table_log_add_error(r);
×
732

733
        static const char * const map[] = {
734
                "firstboot.keymap",          "Keyboard Map",
735
                "firstboot.locale",          "Locale",
736
                "firstboot.locale-messages", "Locale (Messages)",
737
                "firstboot.timezone",        "Timezone",
738
                NULL
739
        };
740

UNCOV
741
        STRV_FOREACH_PAIR(id, text, map) {
×
742
                MachineCredential *c = machine_credential_find(&arg_credentials, *id);
×
743
                if (!c)
×
744
                        continue;
×
745

746
                _cleanup_free_ char *escaped = cescape_length(c->data, c->size);
×
UNCOV
747
                if (!escaped)
×
UNCOV
748
                        return log_oom();
×
749

750
                r = table_add_many(
×
751
                                table,
752
                                TABLE_FIELD, *text,
753
                                TABLE_STRING, escaped);
754
                if (r < 0)
×
755
                        return table_log_add_error(r);
×
756
        }
757

758
        unsigned n_extra_credentials = 0;
×
759
        FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
×
UNCOV
760
                bool covered = false;
×
761

UNCOV
762
                STRV_FOREACH_PAIR(id, text, map)
×
UNCOV
763
                        if (streq(*id, cred->id)) {
×
764
                                covered = true;
765
                                break;
766
                        }
767

768
                if (!covered)
×
769
                        n_extra_credentials++;
×
770
        }
771

UNCOV
772
        if (n_extra_credentials > 0) {
×
773
                r = table_add_many(
×
774
                                table,
775
                                TABLE_FIELD, "Extra Credentials",
776
                                TABLE_UINT, n_extra_credentials);
777
                if (r < 0)
×
778
                        return table_log_add_error(r);
×
779
        }
780

UNCOV
781
        r = table_print(table);
×
UNCOV
782
        if (r < 0)
×
UNCOV
783
                return r;
×
784

785
        return 0;
786
}
787

788
static int find_current_kernel(
×
789
                char **ret_filename,
790
                int *ret_fd) {
791

792
        int r;
×
793

794
        sd_id128_t uuid;
×
795
        r = efi_stub_get_device_part_uuid(&uuid);
×
UNCOV
796
        if (r == -ENOENT)
×
797
                return log_error_errno(r, "Cannot find current kernel, no stub partition UUID passed via EFI variables.");
×
798
        if (r < 0)
×
799
                return log_error_errno(r, "Unable to determine stub partition UUID: %m");
×
800

801
        _cleanup_free_ char *image = NULL;
×
802
        r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &image);
×
UNCOV
803
        if (r == -ENOENT)
×
UNCOV
804
                return log_error_errno(r, "Cannot find current kernel, no stub EFI binary path passed.");
×
UNCOV
805
        if (r < 0)
×
UNCOV
806
                return log_error_errno(r, "Unable to determine stub EFI binary path: %m");
×
807

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

UNCOV
811
        _cleanup_free_ char *partition_path = NULL;
×
UNCOV
812
        _cleanup_close_ int partition_fd = -EBADF;
×
UNCOV
813
        sd_id128_t partition_uuid;
×
UNCOV
814
        r = find_esp_and_warn_full(
×
815
                        /* root= */ NULL,
816
                        /* path= */ NULL,
817
                        /* unprivileged_mode= */ false,
818
                        &partition_path,
819
                        &partition_fd,
820
                        /* ret_part= */ NULL,
821
                        /* ret_pstart= */ NULL,
822
                        /* ret_psize= */ NULL,
823
                        &partition_uuid,
824
                        /* ret_devid= */ NULL);
825
        if (r < 0 && r != -ENOKEY)
×
826
                return r;
827
        if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) {
×
UNCOV
828
                partition_path = mfree(partition_path);
×
UNCOV
829
                partition_fd = safe_close(partition_fd);
×
830

UNCOV
831
                r = find_xbootldr_and_warn_full(
×
832
                                /* root= */ NULL,
833
                                /* path= */ NULL,
834
                                /* unprivileged_mode= */ false,
835
                                &partition_path,
836
                                &partition_fd,
837
                                &partition_uuid,
838
                                /* ret_devid= */ NULL);
839
                if (r < 0 && r != -ENOKEY)
×
840
                        return r;
841

842
                if (r < 0 || !sd_id128_equal(uuid, partition_uuid))
×
843
                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Unable to find UKI on ESP/XBOOTLDR partitions.");
×
844
        }
845

UNCOV
846
        _cleanup_free_ char *resolved = NULL;
×
UNCOV
847
        _cleanup_close_ int fd = chase_and_openat(
×
848
                        /* root_fd= */ partition_fd,
849
                        /* dir_fd= */ partition_fd,
850
                        image,
851
                        CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR,
852
                        O_RDONLY|O_CLOEXEC,
853
                        &resolved);
854
        if (fd < 0)
×
855
                return log_error_errno(fd, "Failed to find EFI binary '%s' on partition '%s': %m", image, partition_path);
×
856

UNCOV
857
        _cleanup_free_ char *fn = NULL;
×
858
        r = path_extract_filename(resolved, &fn);
×
859
        if (r < 0)
×
860
                return log_error_errno(r, "Failed to extract UKI file name from '%s': %m", resolved);
×
861

UNCOV
862
        if (ret_filename)
×
UNCOV
863
                *ret_filename = TAKE_PTR(fn);
×
UNCOV
864
        if (ret_fd)
×
UNCOV
865
                *ret_fd = TAKE_FD(fd);
×
866

867
        return 0;
868
}
869

870
static int connect_to_bootctl(sd_varlink **link) {
2✔
871
        int r;
2✔
872

873
        assert(link);
2✔
874

875
        if (*link)
2✔
876
                return 0;
2✔
877

878
        _cleanup_close_ int fd = -EBADF;
2✔
879
        _cleanup_free_ char *bootctl = NULL;
1✔
880
        fd = pin_callout_binary("bootctl", &bootctl);
1✔
881
        if (fd < 0)
1✔
882
                return log_error_errno(fd, "Failed to find bootctl binary: %m");
×
883

884
        r = sd_varlink_connect_exec(link, bootctl, /* argv= */ NULL);
1✔
885
        if (r < 0)
1✔
886
                return log_error_errno(r, "Failed to connect to bootctl: %m");
×
887

888
        r = sd_varlink_set_allow_fd_passing_output(*link, true);
1✔
889
        if (r < 0)
1✔
UNCOV
890
                return log_error_errno(r, "Failed to enable fd passing to bootctl: %m");
×
891

892
        return 1;
893
}
894

895
static int invoke_bootctl_install(
1✔
896
                sd_varlink **link,
897
                const char *root_dir,
898
                int root_fd) {
899
        int r;
1✔
900

901
        assert(link);
1✔
902
        assert(root_dir);
1✔
903
        assert(root_fd >= 0);
1✔
904

905
        r = connect_to_bootctl(link);
1✔
906
        if (r < 0)
1✔
907
                return r;
1✔
908

909
        int fd_idx = sd_varlink_push_dup_fd(*link, root_fd);
1✔
910
        if (fd_idx < 0)
1✔
UNCOV
911
                return log_error_errno(fd_idx, "Failed to submit root fd onto Varlink connection: %m");
×
912

913
        const char *error_id = NULL;
1✔
914
        r = varlink_callbo_and_log(
1✔
915
                        *link,
916
                        "io.systemd.BootControl.Install",
917
                        /* reply= */ NULL,
918
                        &error_id,
919
                        SD_JSON_BUILD_PAIR_STRING("operation", "new"),
920
                        SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx),
921
                        SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
922
                        SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables));
923
        if (r < 0)
1✔
UNCOV
924
                return r;
×
925

926
        return 0;
927
}
928

929
static int invoke_bootctl_link(
1✔
930
                sd_varlink **link,
931
                const char *root_dir,
932
                int root_fd,
933
                char **encrypted_credentials) {
934
        int r;
1✔
935

936
        assert(link);
1✔
937
        assert(root_dir);
1✔
938
        assert(root_fd >= 0);
1✔
939

940
        r = connect_to_bootctl(link);
1✔
941
        if (r < 0)
1✔
942
                return r;
1✔
943

944
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
1✔
945
        STRV_FOREACH_PAIR(name, value, encrypted_credentials) {
2✔
946
                _cleanup_free_ char *j = strjoin(*name, ".cred");
2✔
947
                if (!j)
1✔
UNCOV
948
                        return log_oom();
×
949

950
                r = sd_json_variant_append_arraybo(
1✔
951
                                &array,
952
                                SD_JSON_BUILD_PAIR_STRING("filename", j),
953
                                SD_JSON_BUILD_PAIR_BASE64("data", *value, strlen(*value)));
954
                if (r < 0)
1✔
UNCOV
955
                        return log_error_errno(r, "Failed to append credential to message: %m");
×
956
        }
957

958
        int root_fd_idx = sd_varlink_push_dup_fd(*link, root_fd);
1✔
959
        if (root_fd_idx < 0)
1✔
UNCOV
960
                return log_error_errno(root_fd_idx, "Failed to submit root fd onto Varlink connection: %m");
×
961

962
        _cleanup_free_ char *kernel_filename = NULL;
1✔
963
        _cleanup_close_ int kernel_fd = -EBADF;
1✔
964
        if (arg_kernel_image) {
1✔
965
                r = path_extract_filename(arg_kernel_image, &kernel_filename);
1✔
966
                if (r < 0)
1✔
UNCOV
967
                        return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image);
×
968
                if (r == O_DIRECTORY)
1✔
969
                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image);
×
970

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

975
        } else {
UNCOV
976
                r = find_current_kernel(&kernel_filename, &kernel_fd);
×
UNCOV
977
                if (r < 0)
×
978
                        return r;
979
        }
980

981
        int kernel_fd_idx = sd_varlink_push_dup_fd(*link, kernel_fd);
1✔
982
        if (kernel_fd_idx < 0)
1✔
UNCOV
983
                return log_error_errno(kernel_fd_idx, "Failed to submit kernel fd onto Varlink connection: %m");
×
984

985
        const char *error_id = NULL;
1✔
986
        r = varlink_callbo_and_log(
1✔
987
                        *link,
988
                        "io.systemd.BootControl.Link",
989
                        /* reply= */ NULL,
990
                        &error_id,
991
                        SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", root_fd_idx),
992
                        SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
993
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("kernelFilename", kernel_filename),
994
                        SD_JSON_BUILD_PAIR_INTEGER("kernelFileDescriptor", kernel_fd_idx),
995
                        SD_JSON_BUILD_PAIR_CONDITION(!!array, "extraFiles", SD_JSON_BUILD_VARIANT(array)));
996
        if (r < 0)
1✔
UNCOV
997
                return r;
×
998

999
        return 0;
1000
}
1001

1002
static int maybe_reboot(void) {
1✔
1003
        int r;
1✔
1004

1005
        if (!arg_reboot)
1✔
1006
                return 0;
1✔
1007

UNCOV
1008
        log_notice("%s%sSystem will reboot now.",
×
1009
                   emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : "");
1010

UNCOV
1011
        if (!any_key_to_proceed())
×
1012
                return 0;
1013

1014
        log_notice("%s%sInitiating reboot.",
×
1015
                   emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : "");
1016

1017
        _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
1✔
1018
        r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.Shutdown");
×
1019
        if (r < 0)
×
1020
                return log_error_errno(r, "Failed to connect to systemd-logind: %m");
×
1021

UNCOV
1022
        sd_json_variant *reply = NULL;
×
UNCOV
1023
        const char *error_id = NULL;
×
UNCOV
1024
        r = varlink_callbo_and_log(
×
1025
                        link,
1026
                        "io.systemd.Shutdown.Reboot",
1027
                        &reply,
1028
                        &error_id);
UNCOV
1029
        if (r < 0)
×
UNCOV
1030
                return r;
×
1031

1032
        return 0;
1033
}
1034

1035
static int read_credential_locale(void) {
1✔
1036
        int r;
1✔
1037

1038
        if (!arg_copy_locale)
1✔
1039
                return 0;
1040

UNCOV
1041
        if (machine_credential_find(&arg_credentials, "firstboot.locale") ||
×
UNCOV
1042
            machine_credential_find(&arg_credentials, "firstboot.locale-messages"))
×
1043
                return 0;
1044

1045
        /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since
1046
         * it seems to be one of the most fundamental ones, and is not LC_MESSAGES for which we have a
1047
         * separate setting after all */
1048
        const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL);
×
UNCOV
1049
        if (l) {
×
UNCOV
1050
                r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
×
1051
                if (r < 0)
×
1052
                        return log_oom();
×
1053
        }
1054

1055
        const char *m = setlocale(LC_MESSAGES, NULL);
×
UNCOV
1056
        if (m && !streq_ptr(m, l)) {
×
UNCOV
1057
                r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
×
UNCOV
1058
                if (r < 0)
×
UNCOV
1059
                        return log_oom();
×
1060
        }
1061

1062
        return 0;
1063
}
1064

1065
static int read_credential_keymap(void) {
1✔
1066
        int r;
1✔
1067

1068
        if (!arg_copy_keymap)
1✔
1069
                return 0;
1✔
1070

1071
        if (machine_credential_find(&arg_credentials, "firstboot.keymap"))
×
1072
                return 0;
1073

UNCOV
1074
        _cleanup_free_ char *keymap = NULL;
×
1075
        r = parse_env_file(
×
1076
                        /* f= */ NULL,
1077
                        etc_vconsole_conf(),
1078
                        "KEYMAP", &keymap);
1079
        if (r < 0 && r != -ENOENT)
×
1080
                return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf());
×
1081

UNCOV
1082
        if (!isempty(keymap)) {
×
UNCOV
1083
                r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
×
UNCOV
1084
                if (r < 0)
×
UNCOV
1085
                        return log_oom();
×
1086
        }
1087

1088
        return 0;
1089
}
1090

1091
static int read_credential_timezone(void) {
1✔
1092
        int r;
1✔
1093

1094
        if (!arg_copy_timezone)
1✔
1095
                return 0;
1✔
1096

1097
        if (machine_credential_find(&arg_credentials, "firstboot.timezone"))
×
1098
                return 0;
1099

UNCOV
1100
        _cleanup_free_ char *tz = NULL;
×
1101
        r = get_timezone_prefer_env(&tz);
×
1102
        if (r < 0)
×
1103
                log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m");
×
1104
        else {
UNCOV
1105
                r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
×
UNCOV
1106
                if (r < 0)
×
UNCOV
1107
                        return log_oom();
×
1108
        }
1109

1110
        return 0;
1111
}
1112

1113
static int read_credentials(void) {
1✔
1114
        int r;
1✔
1115

1116
        r = read_credential_locale();
1✔
1117
        if (r < 0)
1✔
1118
                return r;
1119

1120
        r = read_credential_keymap();
1✔
1121
        if (r < 0)
1✔
1122
                return r;
1123

1124
        r = read_credential_timezone();
1✔
1125
        if (r < 0)
1✔
UNCOV
1126
                return r;
×
1127

1128
        return 0;
1129
}
1130

1131
static int connect_to_creds(sd_varlink **link) {
1✔
1132
        int r;
1✔
1133

1134
        assert(link);
1✔
1135

1136
        if (*link)
1✔
1137
                return 0;
1✔
1138

1139
        _cleanup_close_ int fd = -EBADF;
1✔
1140
        _cleanup_free_ char *creds = NULL;
1✔
1141
        fd = pin_callout_binary("systemd-creds", &creds);
1✔
1142
        if (fd < 0)
1✔
1143
                return log_error_errno(fd, "Failed to find systemd-creds binary: %m");
×
1144

1145
        r = sd_varlink_connect_exec(link, creds, /* argv= */ NULL);
1✔
1146
        if (r < 0)
1✔
UNCOV
1147
                return log_error_errno(r, "Failed to connect to systemd-creds: %m");
×
1148

1149
        return 1;
1150
}
1151

1152
static int encrypt_one_credential(sd_varlink **link, const MachineCredential *input, char ***encrypted) {
1✔
1153
        int r;
1✔
1154

1155
        assert(link);
1✔
1156
        assert(input);
1✔
1157
        assert(encrypted);
1✔
1158

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

1161
        r = connect_to_creds(link);
1✔
1162
        if (r < 0)
1✔
1163
                return r;
1✔
1164

1165
        sd_json_variant *reply = NULL;
1✔
1166
        const char *error_id = NULL;
1✔
1167
        r = varlink_callbo_and_log(
1✔
1168
                        *link,
1169
                        "io.systemd.Credentials.Encrypt",
1170
                        &reply,
1171
                        &error_id,
1172
                        SD_JSON_BUILD_PAIR_STRING("name", input->id),
1173
                        SD_JSON_BUILD_PAIR_BASE64("data", input->data, input->size),
1174
                        SD_JSON_BUILD_PAIR_STRING("scope", "system"),
1175
                        /* We pick the 'auto_initrd' key for this, since we want TPM if available, but are fine with NULL if not */
1176
                        SD_JSON_BUILD_PAIR_STRING("withKey", "auto_initrd"));
1177
        if (r < 0)
1✔
1178
                return r;
1179

1180
        static const sd_json_dispatch_field dispatch_table[] = {
1✔
1181
                { "blob", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
1182
                {}
1183
        };
1184

1185
        const char *blob = NULL;
1✔
1186
        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &blob);
1✔
1187
        if (r < 0)
1✔
1188
                return r;
1189

1190
        r = strv_extend_many(encrypted, input->id, blob);
1✔
1191
        if (r < 0)
1✔
UNCOV
1192
                return r;
×
1193

1194
        return 0;
1195
}
1196

1197
static int encrypt_credentials(sd_varlink **link, char ***encrypted) {
1✔
1198
        int r;
1✔
1199

1200
        assert(link);
1✔
1201
        assert(encrypted);
1✔
1202

1203
        FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
2✔
1204
                r = encrypt_one_credential(link, cred, encrypted);
1✔
1205
                if (r < 0)
1✔
1206
                        return r;
1207
        }
1208

1209
        return 0;
1210
}
1211

1212
static const ImagePolicy image_policy = {
1213
        .n_policies = 4,
1214
        .policies = {
1215
                /* We mount / and /usr/ so that we can get access to /etc/machine-id and /etc/kernel/ */
1216
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1217
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1218
                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1219
                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
1220
        },
1221
        .default_flags = PARTITION_POLICY_IGNORE,
1222
};
1223

1224
static int settle_definitions(void) {
1✔
1225
        int r;
1✔
1226

1227
        if (arg_definitions)
1✔
1228
                return 0;
1✔
1229

1230
        /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition
1231
         * files */
1232

UNCOV
1233
        _cleanup_strv_free_ char **files = NULL;
×
UNCOV
1234
        r = conf_files_list_strv(
×
1235
                        &files,
1236
                        ".conf",
1237
                        /* root= */ NULL,
1238
                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN|CONF_FILES_DONT_PREFIX_ROOT,
1239
                        (const char**) CONF_PATHS_STRV("repart.sysinstall.d"));
×
1240
        if (r < 0)
×
1241
                return log_error_errno(r, "Failed to enumerate *.conf files: %m");
×
1242

UNCOV
1243
        if (!strv_isempty(files)) {
×
UNCOV
1244
                arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
×
UNCOV
1245
                if (!arg_definitions)
×
UNCOV
1246
                        return log_oom();
×
1247
        }
1248

1249
        return 0;
1250
}
1251

1252
static void end_marker(void) {
1✔
1253

1254
        if (!arg_welcome)
1✔
1255
                return;
1256

UNCOV
1257
        printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal());
×
UNCOV
1258
        fflush(stdout);
×
1259
}
1260

1261
static int run(int argc, char *argv[]) {
1✔
1262
        int r;
1✔
1263

1264
        setlocale(LC_ALL, "");
1✔
1265

1266
        r = parse_argv(argc, argv);
1✔
1267
        if (r <= 0)
1✔
1268
                return r;
1✔
1269

1270
        log_setup();
1✔
1271

1272
        r = settle_definitions();
1✔
1273
        if (r < 0)
1✔
1274
                return r;
1275

1276
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL;
1✔
1277
        if (arg_welcome) {
1✔
UNCOV
1278
                if (arg_mute_console)
×
UNCOV
1279
                        (void) mute_console(&mute_console_link);
×
1280

UNCOV
1281
                (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
×
1282

UNCOV
1283
                if (arg_chrome)
×
UNCOV
1284
                        chrome_show("Operating System Installer", /* bottom= */ NULL);
×
1285
        }
1286

1287
        DEFER_VOID_CALL(end_marker);
1✔
1288
        DEFER_VOID_CALL(chrome_hide);
×
1289

1290
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *repart_link = NULL;
1✔
1291
        if (arg_node) {
1✔
1292
                r = print_welcome(&mute_console_link);
1✔
1293
                if (r < 0)
1✔
1294
                        return r;
1295

1296
                r = validate_run(&repart_link, arg_node);
1✔
1297
                if (r < 0)
1✔
1298
                        return r;
1299
        } else {
1300
                /* Determine the minimum disk size */
UNCOV
1301
                uint64_t min_size = UINT64_MAX;
×
UNCOV
1302
                r = invoke_repart(
×
1303
                                &repart_link,
1304
                                /* node= */ NULL,
1305
                                /* erase= */ true,
1306
                                /* dry_run= */ true,
1307
                                &min_size,
1308
                                /* current_size= */ NULL,
1309
                                /* need_free= */ NULL);
UNCOV
1310
                if (r < 0)
×
1311
                        return r;
×
1312

1313
                r = print_welcome(&mute_console_link);
×
1314
                if (r < 0)
×
1315
                        return r;
1316

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

UNCOV
1319
                for (;;) {
×
UNCOV
1320
                        _cleanup_free_ char *node = NULL;
×
UNCOV
1321
                        r = prompt_block_device(&repart_link, &node);
×
UNCOV
1322
                        if (r < 0)
×
1323
                                return r;
1324

UNCOV
1325
                        r = validate_run(&repart_link, node);
×
UNCOV
1326
                        if (IN_SET(r, -ENOSPC, -E2BIG, -EHWPOISON)) /* Device is no fit, pick other */
×
UNCOV
1327
                                continue;
×
UNCOV
1328
                        if (r < 0)
×
1329
                                return r;
1330

UNCOV
1331
                        arg_node = TAKE_PTR(node);
×
UNCOV
1332
                        break;
×
1333
                }
1334
        }
1335

1336
        r = prompt_touch_variables();
1✔
1337
        if (r < 0)
1✔
1338
                return r;
1339

1340
        r = read_credentials();
1✔
1341
        if (r < 0)
1✔
1342
                return r;
1343

1344
        /* Verify we have everything we need */
1345
        assert(arg_node);
1✔
1346
        assert(arg_erase >= 0);
1✔
1347
        assert(arg_touch_variables >= 0);
1✔
1348

1349
        r = show_summary();
1✔
1350
        if (r < 0)
1✔
1351
                return r;
1352

1353
        r = prompt_confirm();
1✔
1354
        if (r < 0)
1✔
1355
                return r;
1356

1357
        putchar('\n');
1✔
1358

1359
        log_notice("%s%sEncrypting credentials...",
2✔
1360
                   emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : "");
1361

1362
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
1✔
1363
        _cleanup_strv_free_ char **encrypted_credentials = NULL;
1✔
1364
        r = encrypt_credentials(&creds_link, &encrypted_credentials);
1✔
1365
        if (r < 0)
1✔
1366
                return r;
1367

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

1371
        /* Do the main part of the installation */
1372
        r = invoke_repart(
1✔
1373
                        &repart_link,
1374
                        arg_node,
1375
                        arg_erase,
1376
                        /* dry_run= */ false,
1377
                        /* min_size= */ NULL,
1378
                        /* current_size= */ NULL,
1379
                        /* need_free= */ NULL);
1380
        if (r < 0)
1✔
1381
                return r;
1382

1383
        log_notice("%s%sMounting partitions...",
2✔
1384
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1385

1386
        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1✔
UNCOV
1387
        _cleanup_(umount_and_freep) char *root_dir = NULL;
×
1388
        _cleanup_close_ int root_fd = -EBADF;
1✔
1389
        r = mount_image_privately_interactively(
1✔
1390
                        arg_node,
1391
                        &image_policy,
1392
                        DISSECT_IMAGE_REQUIRE_ROOT |
1393
                        DISSECT_IMAGE_RELAX_VAR_CHECK |
1394
                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
1395
                        DISSECT_IMAGE_DISCARD_ANY |
1396
                        DISSECT_IMAGE_GPT_ONLY |
1397
                        DISSECT_IMAGE_FSCK |
1398
                        DISSECT_IMAGE_USR_NO_ROOT |
1399
                        DISSECT_IMAGE_ADD_PARTITION_DEVICES |
1400
                        DISSECT_IMAGE_PIN_PARTITION_DEVICES,
1401
                        &root_dir,
1402
                        &root_fd,
1403
                        &loop_device);
1404
        if (r < 0)
1✔
UNCOV
1405
                return log_error_errno(r, "Failed to mount new image: %m");
×
1406

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

1410
        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
1✔
1411
        r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, encrypted_credentials);
1✔
1412
        if (r < 0)
1✔
1413
                return r;
1414

1415
        log_notice("%s%sInstalling boot loader...",
2✔
1416
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1417

1418
        r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd);
1✔
1419
        if (r < 0)
1✔
1420
                return r;
1421

1422
        log_notice("%s%sUnmounting partitions...",
2✔
1423
                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
1424

1425
        root_fd = safe_close(root_fd);
1✔
1426
        r = umount_recursive(root_dir, /* flags= */ 0);
1✔
1427
        if (r < 0)
1✔
UNCOV
1428
                log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
×
1429
        loop_device = loop_device_unref(loop_device);
1✔
1430
        sync();
1✔
1431

1432
        log_notice("%s%sInstallation succeeded.",
2✔
1433
                   emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : "");
1434

1435
        r = maybe_reboot();
1✔
1436
        if (r < 0)
1✔
UNCOV
1437
                return r;
×
1438

1439
        return 0;
1440
}
1441

1442
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