]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/firstboot/firstboot.c
boot/efi: TPM V2 fix for GetEventLog EFI function
[thirdparty/systemd.git] / src / firstboot / firstboot.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
418b9be5
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
418b9be5 21#include <fcntl.h>
418b9be5
LP
22#include <getopt.h>
23#include <shadow.h>
3f6fd1ba 24#include <unistd.h>
418b9be5 25
dccca82b
LP
26#include "sd-id128.h"
27
b5efdb8a 28#include "alloc-util.h"
3f6fd1ba 29#include "ask-password-api.h"
418b9be5 30#include "copy.h"
6bedfcbb 31#include "fd-util.h"
3f6fd1ba 32#include "fileio.h"
f4f15635 33#include "fs-util.h"
3f6fd1ba
LP
34#include "hostname-util.h"
35#include "locale-util.h"
418b9be5 36#include "mkdir.h"
6bedfcbb 37#include "parse-util.h"
418b9be5 38#include "path-util.h"
f582cbca 39#include "proc-cmdline.h"
3df3e884 40#include "random-util.h"
6bedfcbb 41#include "string-util.h"
3f6fd1ba 42#include "strv.h"
288a74cc 43#include "terminal-util.h"
3f6fd1ba 44#include "time-util.h"
affb60b1 45#include "umask-util.h"
e929bee0 46#include "user-util.h"
418b9be5
LP
47
48static char *arg_root = NULL;
49static char *arg_locale = NULL; /* $LANG */
ed457f13 50static char *arg_keymap = NULL;
418b9be5
LP
51static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
52static char *arg_timezone = NULL;
53static char *arg_hostname = NULL;
54static sd_id128_t arg_machine_id = {};
55static char *arg_root_password = NULL;
56static bool arg_prompt_locale = false;
ed457f13 57static bool arg_prompt_keymap = false;
418b9be5
LP
58static bool arg_prompt_timezone = false;
59static bool arg_prompt_hostname = false;
60static bool arg_prompt_root_password = false;
61static bool arg_copy_locale = false;
ed457f13 62static bool arg_copy_keymap = false;
418b9be5
LP
63static bool arg_copy_timezone = false;
64static bool arg_copy_root_password = false;
65
418b9be5
LP
66static bool press_any_key(void) {
67 char k = 0;
68 bool need_nl = true;
69
70 printf("-- Press any key to proceed --");
71 fflush(stdout);
72
94956f8f 73 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
418b9be5
LP
74
75 if (need_nl)
76 putchar('\n');
77
78 return k != 'q';
79}
80
81static void print_welcome(void) {
82 _cleanup_free_ char *pretty_name = NULL;
83 const char *os_release = NULL;
84 static bool done = false;
85 int r;
86
87 if (done)
88 return;
89
1d13f648 90 os_release = prefix_roota(arg_root, "/etc/os-release");
418b9be5
LP
91 r = parse_env_file(os_release, NEWLINE,
92 "PRETTY_NAME", &pretty_name,
93 NULL);
94 if (r == -ENOENT) {
95
1d13f648 96 os_release = prefix_roota(arg_root, "/usr/lib/os-release");
418b9be5
LP
97 r = parse_env_file(os_release, NEWLINE,
98 "PRETTY_NAME", &pretty_name,
99 NULL);
100 }
101
102 if (r < 0 && r != -ENOENT)
da927ba9 103 log_warning_errno(r, "Failed to read os-release file: %m");
418b9be5
LP
104
105 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
106 isempty(pretty_name) ? "Linux" : pretty_name);
107
108 press_any_key();
109
110 done = true;
111}
112
113static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
114 unsigned n, per_column, i, j;
115 unsigned break_lines, break_modulo;
116
117 assert(n_columns > 0);
118
119 n = strv_length(x);
120 per_column = (n + n_columns - 1) / n_columns;
121
122 break_lines = lines();
123 if (break_lines > 2)
124 break_lines--;
125
126 /* The first page gets two extra lines, since we want to show
127 * a title */
128 break_modulo = break_lines;
129 if (break_modulo > 3)
130 break_modulo -= 3;
131
132 for (i = 0; i < per_column; i++) {
133
134 for (j = 0; j < n_columns; j ++) {
135 _cleanup_free_ char *e = NULL;
136
137 if (j * per_column + i >= n)
138 break;
139
140 e = ellipsize(x[j * per_column + i], width, percentage);
141 if (!e)
142 return log_oom();
143
144 printf("%4u) %-*s", j * per_column + i + 1, width, e);
145 }
146
147 putchar('\n');
148
149 /* on the first screen we reserve 2 extra lines for the title */
150 if (i % break_lines == break_modulo) {
151 if (!press_any_key())
152 return 0;
153 }
154 }
155
156 return 0;
157}
158
159static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) {
160 int r;
161
162 assert(text);
163 assert(is_valid);
164 assert(ret);
165
166 for (;;) {
167 _cleanup_free_ char *p = NULL;
168 unsigned u;
169
323b7dc9 170 r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
23bbb0de
MS
171 if (r < 0)
172 return log_error_errno(r, "Failed to query user: %m");
418b9be5
LP
173
174 if (isempty(p)) {
175 log_warning("No data entered, skipping.");
176 return 0;
177 }
178
179 r = safe_atou(p, &u);
180 if (r >= 0) {
181 char *c;
182
183 if (u <= 0 || u > strv_length(l)) {
184 log_error("Specified entry number out of range.");
185 continue;
186 }
187
188 log_info("Selected '%s'.", l[u-1]);
189
190 c = strdup(l[u-1]);
191 if (!c)
192 return log_oom();
193
194 free(*ret);
195 *ret = c;
196 return 0;
197 }
198
199 if (!is_valid(p)) {
200 log_error("Entered data invalid.");
201 continue;
202 }
203
204 free(*ret);
205 *ret = p;
206 p = 0;
207 return 0;
208 }
209}
210
211static int prompt_locale(void) {
212 _cleanup_strv_free_ char **locales = NULL;
213 int r;
214
215 if (arg_locale || arg_locale_messages)
216 return 0;
217
218 if (!arg_prompt_locale)
219 return 0;
220
221 r = get_locales(&locales);
23bbb0de
MS
222 if (r < 0)
223 return log_error_errno(r, "Cannot query locales list: %m");
418b9be5
LP
224
225 print_welcome();
226
227 printf("\nAvailable Locales:\n\n");
228 r = show_menu(locales, 3, 22, 60);
229 if (r < 0)
230 return r;
231
232 putchar('\n');
233
234 r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
235 if (r < 0)
236 return r;
237
238 if (isempty(arg_locale))
239 return 0;
240
241 r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
242 if (r < 0)
243 return r;
244
245 return 0;
246}
247
248static int process_locale(void) {
249 const char *etc_localeconf;
250 char* locales[3];
251 unsigned i = 0;
252 int r;
253
1d13f648 254 etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
b3cd687d 255 if (laccess(etc_localeconf, F_OK) >= 0)
418b9be5
LP
256 return 0;
257
258 if (arg_copy_locale && arg_root) {
259
260 mkdir_parents(etc_localeconf, 0755);
1c876927 261 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, COPY_REFLINK);
418b9be5 262 if (r != -ENOENT) {
23bbb0de
MS
263 if (r < 0)
264 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
418b9be5
LP
265
266 log_info("%s copied.", etc_localeconf);
267 return 0;
268 }
269 }
270
271 r = prompt_locale();
272 if (r < 0)
273 return r;
274
275 if (!isempty(arg_locale))
63c372cb 276 locales[i++] = strjoina("LANG=", arg_locale);
418b9be5 277 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
63c372cb 278 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
418b9be5
LP
279
280 if (i == 0)
281 return 0;
282
283 locales[i] = NULL;
284
285 mkdir_parents(etc_localeconf, 0755);
286 r = write_env_file(etc_localeconf, locales);
23bbb0de
MS
287 if (r < 0)
288 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
418b9be5
LP
289
290 log_info("%s written.", etc_localeconf);
291 return 0;
292}
293
ed457f13
TB
294static int prompt_keymap(void) {
295 _cleanup_strv_free_ char **kmaps = NULL;
296 int r;
297
298 if (arg_keymap)
299 return 0;
300
301 if (!arg_prompt_keymap)
302 return 0;
303
304 r = get_keymaps(&kmaps);
305 if (r == -ENOENT) /* no keymaps installed */
306 return r;
307 if (r < 0)
308 return log_error_errno(r, "Failed to read keymaps: %m");
309
310 print_welcome();
311
312 printf("\nAvailable keymaps:\n\n");
313 r = show_menu(kmaps, 3, 22, 60);
314 if (r < 0)
315 return r;
316
317 putchar('\n');
318
8700b4da
ZJS
319 return prompt_loop("Please enter system keymap name or number",
320 kmaps, keymap_is_valid, &arg_keymap);
ed457f13
TB
321}
322
323static int process_keymap(void) {
324 const char *etc_vconsoleconf;
325 char **keymap;
326 int r;
327
328 etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
329 if (laccess(etc_vconsoleconf, F_OK) >= 0)
330 return 0;
331
332 if (arg_copy_keymap && arg_root) {
333
334 mkdir_parents(etc_vconsoleconf, 0755);
335 r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, COPY_REFLINK);
336 if (r != -ENOENT) {
337 if (r < 0)
338 return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
339
340 log_info("%s copied.", etc_vconsoleconf);
341 return 0;
342 }
343 }
344
345 r = prompt_keymap();
346 if (r == -ENOENT)
347 return 0; /* don't fail if no keymaps are installed */
348 if (r < 0)
349 return r;
350
a7353b4d 351 if (isempty(arg_keymap))
ed457f13
TB
352 return 0;
353
a7353b4d
YW
354 keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
355
356 r = mkdir_parents(etc_vconsoleconf, 0755);
357 if (r < 0)
358 return log_error_errno(r, "Failed to create the parent directory of %s: %m", etc_vconsoleconf);
359
ed457f13
TB
360 r = write_env_file(etc_vconsoleconf, keymap);
361 if (r < 0)
362 return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
363
364 log_info("%s written.", etc_vconsoleconf);
365 return 0;
366}
367
418b9be5
LP
368static int prompt_timezone(void) {
369 _cleanup_strv_free_ char **zones = NULL;
370 int r;
371
372 if (arg_timezone)
373 return 0;
374
375 if (!arg_prompt_timezone)
376 return 0;
377
378 r = get_timezones(&zones);
23bbb0de
MS
379 if (r < 0)
380 return log_error_errno(r, "Cannot query timezone list: %m");
418b9be5
LP
381
382 print_welcome();
383
384 printf("\nAvailable Time Zones:\n\n");
385 r = show_menu(zones, 3, 22, 30);
386 if (r < 0)
387 return r;
388
389 putchar('\n');
390
391 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
392 if (r < 0)
393 return r;
394
395 return 0;
396}
397
398static int process_timezone(void) {
399 const char *etc_localtime, *e;
400 int r;
401
1d13f648 402 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
b3cd687d 403 if (laccess(etc_localtime, F_OK) >= 0)
418b9be5
LP
404 return 0;
405
406 if (arg_copy_timezone && arg_root) {
407 _cleanup_free_ char *p = NULL;
408
409 r = readlink_malloc("/etc/localtime", &p);
410 if (r != -ENOENT) {
23bbb0de
MS
411 if (r < 0)
412 return log_error_errno(r, "Failed to read host timezone: %m");
418b9be5
LP
413
414 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
415 if (symlink(p, etc_localtime) < 0)
416 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
417
418 log_info("%s copied.", etc_localtime);
419 return 0;
420 }
421 }
422
423 r = prompt_timezone();
424 if (r < 0)
425 return r;
426
427 if (isempty(arg_timezone))
428 return 0;
429
63c372cb 430 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
418b9be5
LP
431
432 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
433 if (symlink(e, etc_localtime) < 0)
434 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
435
436 log_info("%s written", etc_localtime);
437 return 0;
438}
439
440static int prompt_hostname(void) {
441 int r;
442
443 if (arg_hostname)
444 return 0;
445
446 if (!arg_prompt_hostname)
447 return 0;
448
449 print_welcome();
450 putchar('\n');
451
452 for (;;) {
453 _cleanup_free_ char *h = NULL;
454
323b7dc9 455 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
23bbb0de
MS
456 if (r < 0)
457 return log_error_errno(r, "Failed to query hostname: %m");
418b9be5
LP
458
459 if (isempty(h)) {
460 log_warning("No hostname entered, skipping.");
461 break;
462 }
463
34ad6090 464 if (!hostname_is_valid(h, true)) {
418b9be5
LP
465 log_error("Specified hostname invalid.");
466 continue;
467 }
468
34ad6090 469 /* Get rid of the trailing dot that we allow, but don't want to see */
ae691c1d 470 arg_hostname = hostname_cleanup(h);
418b9be5
LP
471 h = NULL;
472 break;
473 }
474
475 return 0;
476}
477
478static int process_hostname(void) {
479 const char *etc_hostname;
480 int r;
481
1d13f648 482 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
b3cd687d 483 if (laccess(etc_hostname, F_OK) >= 0)
418b9be5
LP
484 return 0;
485
486 r = prompt_hostname();
487 if (r < 0)
488 return r;
489
490 if (isempty(arg_hostname))
491 return 0;
492
493 mkdir_parents(etc_hostname, 0755);
0675e94a
AJ
494 r = write_string_file(etc_hostname, arg_hostname,
495 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
496 if (r < 0)
497 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
418b9be5
LP
498
499 log_info("%s written.", etc_hostname);
500 return 0;
501}
502
503static int process_machine_id(void) {
504 const char *etc_machine_id;
505 char id[SD_ID128_STRING_MAX];
506 int r;
507
1d13f648 508 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
b3cd687d 509 if (laccess(etc_machine_id, F_OK) >= 0)
418b9be5
LP
510 return 0;
511
3bbaff3e 512 if (sd_id128_is_null(arg_machine_id))
418b9be5
LP
513 return 0;
514
515 mkdir_parents(etc_machine_id, 0755);
0675e94a
AJ
516 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
517 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
518 if (r < 0)
519 return log_error_errno(r, "Failed to write machine id: %m");
418b9be5
LP
520
521 log_info("%s written.", etc_machine_id);
522 return 0;
523}
524
525static int prompt_root_password(void) {
526 const char *msg1, *msg2, *etc_shadow;
527 int r;
528
529 if (arg_root_password)
530 return 0;
531
532 if (!arg_prompt_root_password)
533 return 0;
534
1d13f648 535 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 536 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
537 return 0;
538
539 print_welcome();
540 putchar('\n');
541
323b7dc9
ZJS
542 msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
543 msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: ");
418b9be5
LP
544
545 for (;;) {
ab84f5b9 546 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
418b9be5 547
e287086b 548 r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
23bbb0de
MS
549 if (r < 0)
550 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
551
552 if (isempty(a)) {
553 log_warning("No password entered, skipping.");
554 break;
555 }
556
e287086b 557 r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
ab84f5b9 558 if (r < 0)
00843602 559 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
560
561 if (!streq(a, b)) {
562 log_error("Entered passwords did not match, please try again.");
418b9be5
LP
563 continue;
564 }
565
418b9be5
LP
566 arg_root_password = a;
567 a = NULL;
568 break;
569 }
570
571 return 0;
572}
573
574static int write_root_shadow(const char *path, const struct spwd *p) {
575 _cleanup_fclose_ FILE *f = NULL;
576 assert(path);
577 assert(p);
578
3250929b
LP
579 RUN_WITH_UMASK(0777)
580 f = fopen(path, "wex");
418b9be5
LP
581 if (!f)
582 return -errno;
583
584 errno = 0;
585 if (putspent(p, f) != 0)
f5e5c28f 586 return errno > 0 ? -errno : -EIO;
418b9be5 587
0675e94a 588 return fflush_sync_and_check(f);
418b9be5
LP
589}
590
591static int process_root_password(void) {
592
593 static const char table[] =
594 "abcdefghijklmnopqrstuvwxyz"
595 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
596 "0123456789"
597 "./";
598
599 struct spwd item = {
600 .sp_namp = (char*) "root",
ad525df8
IS
601 .sp_min = -1,
602 .sp_max = -1,
603 .sp_warn = -1,
418b9be5
LP
604 .sp_inact = -1,
605 .sp_expire = -1,
606 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
607 };
45035609
LP
608
609 _cleanup_close_ int lock = -1;
418b9be5
LP
610 char salt[3+16+1+1];
611 uint8_t raw[16];
612 unsigned i;
613 char *j;
614
615 const char *etc_shadow;
616 int r;
617
1d13f648 618 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 619 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
620 return 0;
621
45035609
LP
622 mkdir_parents(etc_shadow, 0755);
623
e929bee0 624 lock = take_etc_passwd_lock(arg_root);
45035609 625 if (lock < 0)
db260eed 626 return log_error_errno(lock, "Failed to take a lock: %m");
45035609 627
418b9be5
LP
628 if (arg_copy_root_password && arg_root) {
629 struct spwd *p;
630
631 errno = 0;
632 p = getspnam("root");
633 if (p || errno != ENOENT) {
634 if (!p) {
635 if (!errno)
636 errno = EIO;
637
e1427b13 638 return log_error_errno(errno, "Failed to find shadow entry for root: %m");
418b9be5
LP
639 }
640
641 r = write_root_shadow(etc_shadow, p);
23bbb0de
MS
642 if (r < 0)
643 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
644
645 log_info("%s copied.", etc_shadow);
646 return 0;
647 }
648 }
649
650 r = prompt_root_password();
651 if (r < 0)
652 return r;
653
654 if (!arg_root_password)
655 return 0;
656
f0d09059 657 r = acquire_random_bytes(raw, 16, true);
23bbb0de
MS
658 if (r < 0)
659 return log_error_errno(r, "Failed to get salt: %m");
418b9be5
LP
660
661 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
662 assert_cc(sizeof(table) == 64 + 1);
663 j = stpcpy(salt, "$6$");
664 for (i = 0; i < 16; i++)
665 j[i] = table[raw[i] & 63];
666 j[i++] = '$';
667 j[i] = 0;
668
669 errno = 0;
670 item.sp_pwdp = crypt(arg_root_password, salt);
671 if (!item.sp_pwdp) {
672 if (!errno)
4546c341 673 errno = EINVAL;
418b9be5 674
e1427b13 675 return log_error_errno(errno, "Failed to encrypt password: %m");
418b9be5
LP
676 }
677
678 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
679
680 r = write_root_shadow(etc_shadow, &item);
23bbb0de
MS
681 if (r < 0)
682 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
683
684 log_info("%s written.", etc_shadow);
685 return 0;
686}
687
601185b4 688static void help(void) {
418b9be5
LP
689 printf("%s [OPTIONS...]\n\n"
690 "Configures basic settings of the system.\n\n"
691 " -h --help Show this help\n"
692 " --version Show package version\n"
693 " --root=PATH Operate on an alternate filesystem root\n"
694 " --locale=LOCALE Set primary locale (LANG=)\n"
695 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
ed457f13 696 " --keymap=KEYMAP Set keymap\n"
418b9be5
LP
697 " --timezone=TIMEZONE Set timezone\n"
698 " --hostname=NAME Set host name\n"
699 " --machine-ID=ID Set machine ID\n"
700 " --root-password=PASSWORD Set root password\n"
701 " --root-password-file=FILE Set root password from file\n"
702 " --prompt-locale Prompt the user for locale settings\n"
ed457f13 703 " --prompt-keymap Prompt the user for keymap settings\n"
418b9be5
LP
704 " --prompt-timezone Prompt the user for timezone\n"
705 " --prompt-hostname Prompt the user for hostname\n"
706 " --prompt-root-password Prompt the user for root password\n"
b57b0625 707 " --prompt Prompt for all of the above\n"
418b9be5 708 " --copy-locale Copy locale from host\n"
ed457f13 709 " --copy-keymap Copy keymap from host\n"
418b9be5
LP
710 " --copy-timezone Copy timezone from host\n"
711 " --copy-root-password Copy root password from host\n"
ed457f13 712 " --copy Copy locale, keymap, timezone, root password\n"
601185b4
ZJS
713 " --setup-machine-id Generate a new random machine ID\n"
714 , program_invocation_short_name);
418b9be5
LP
715}
716
717static int parse_argv(int argc, char *argv[]) {
718
719 enum {
720 ARG_VERSION = 0x100,
721 ARG_ROOT,
722 ARG_LOCALE,
723 ARG_LOCALE_MESSAGES,
ed457f13 724 ARG_KEYMAP,
418b9be5
LP
725 ARG_TIMEZONE,
726 ARG_HOSTNAME,
727 ARG_MACHINE_ID,
728 ARG_ROOT_PASSWORD,
729 ARG_ROOT_PASSWORD_FILE,
730 ARG_PROMPT,
731 ARG_PROMPT_LOCALE,
ed457f13 732 ARG_PROMPT_KEYMAP,
418b9be5
LP
733 ARG_PROMPT_TIMEZONE,
734 ARG_PROMPT_HOSTNAME,
735 ARG_PROMPT_ROOT_PASSWORD,
736 ARG_COPY,
737 ARG_COPY_LOCALE,
ed457f13 738 ARG_COPY_KEYMAP,
418b9be5
LP
739 ARG_COPY_TIMEZONE,
740 ARG_COPY_ROOT_PASSWORD,
741 ARG_SETUP_MACHINE_ID,
742 };
743
744 static const struct option options[] = {
745 { "help", no_argument, NULL, 'h' },
746 { "version", no_argument, NULL, ARG_VERSION },
747 { "root", required_argument, NULL, ARG_ROOT },
748 { "locale", required_argument, NULL, ARG_LOCALE },
749 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
ed457f13 750 { "keymap", required_argument, NULL, ARG_KEYMAP },
418b9be5
LP
751 { "timezone", required_argument, NULL, ARG_TIMEZONE },
752 { "hostname", required_argument, NULL, ARG_HOSTNAME },
753 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
754 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
755 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
756 { "prompt", no_argument, NULL, ARG_PROMPT },
757 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
ed457f13 758 { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
418b9be5
LP
759 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
760 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
761 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
762 { "copy", no_argument, NULL, ARG_COPY },
763 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
ed457f13 764 { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
418b9be5
LP
765 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
766 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
767 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
768 {}
769 };
770
771 int r, c;
772
773 assert(argc >= 0);
774 assert(argv);
775
601185b4 776 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
418b9be5
LP
777
778 switch (c) {
779
780 case 'h':
601185b4
ZJS
781 help();
782 return 0;
418b9be5
LP
783
784 case ARG_VERSION:
3f6fd1ba 785 return version();
418b9be5
LP
786
787 case ARG_ROOT:
0f03c2a4 788 r = parse_path_argument_and_warn(optarg, true, &arg_root);
0f474365 789 if (r < 0)
0f03c2a4 790 return r;
418b9be5
LP
791 break;
792
793 case ARG_LOCALE:
794 if (!locale_is_valid(optarg)) {
795 log_error("Locale %s is not valid.", optarg);
796 return -EINVAL;
797 }
798
2fc09a9c
DM
799 r = free_and_strdup(&arg_locale, optarg);
800 if (r < 0)
418b9be5
LP
801 return log_oom();
802
803 break;
804
805 case ARG_LOCALE_MESSAGES:
806 if (!locale_is_valid(optarg)) {
807 log_error("Locale %s is not valid.", optarg);
808 return -EINVAL;
809 }
810
2fc09a9c
DM
811 r = free_and_strdup(&arg_locale_messages, optarg);
812 if (r < 0)
418b9be5
LP
813 return log_oom();
814
815 break;
816
ed457f13
TB
817 case ARG_KEYMAP:
818 if (!keymap_is_valid(optarg)) {
819 log_error("Keymap %s is not valid.", optarg);
820 return -EINVAL;
821 }
822
823 r = free_and_strdup(&arg_keymap, optarg);
824 if (r < 0)
825 return log_oom();
826
827 break;
828
418b9be5
LP
829 case ARG_TIMEZONE:
830 if (!timezone_is_valid(optarg)) {
831 log_error("Timezone %s is not valid.", optarg);
832 return -EINVAL;
833 }
834
2fc09a9c
DM
835 r = free_and_strdup(&arg_timezone, optarg);
836 if (r < 0)
418b9be5
LP
837 return log_oom();
838
839 break;
840
841 case ARG_ROOT_PASSWORD:
2fc09a9c
DM
842 r = free_and_strdup(&arg_root_password, optarg);
843 if (r < 0)
418b9be5 844 return log_oom();
418b9be5
LP
845 break;
846
847 case ARG_ROOT_PASSWORD_FILE:
0da16248 848 arg_root_password = mfree(arg_root_password);
418b9be5
LP
849
850 r = read_one_line_file(optarg, &arg_root_password);
23bbb0de
MS
851 if (r < 0)
852 return log_error_errno(r, "Failed to read %s: %m", optarg);
418b9be5
LP
853
854 break;
855
856 case ARG_HOSTNAME:
34ad6090 857 if (!hostname_is_valid(optarg, true)) {
418b9be5
LP
858 log_error("Host name %s is not valid.", optarg);
859 return -EINVAL;
860 }
861
ae691c1d 862 hostname_cleanup(optarg);
2fc09a9c
DM
863 r = free_and_strdup(&arg_hostname, optarg);
864 if (r < 0)
418b9be5
LP
865 return log_oom();
866
867 break;
868
869 case ARG_MACHINE_ID:
870 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
871 log_error("Failed to parse machine id %s.", optarg);
872 return -EINVAL;
873 }
874
875 break;
876
877 case ARG_PROMPT:
ed457f13 878 arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
418b9be5
LP
879 break;
880
881 case ARG_PROMPT_LOCALE:
882 arg_prompt_locale = true;
883 break;
884
ed457f13
TB
885 case ARG_PROMPT_KEYMAP:
886 arg_prompt_keymap = true;
887 break;
888
418b9be5
LP
889 case ARG_PROMPT_TIMEZONE:
890 arg_prompt_timezone = true;
891 break;
892
893 case ARG_PROMPT_HOSTNAME:
894 arg_prompt_hostname = true;
895 break;
896
897 case ARG_PROMPT_ROOT_PASSWORD:
898 arg_prompt_root_password = true;
899 break;
900
901 case ARG_COPY:
ed457f13 902 arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true;
e926f647 903 break;
418b9be5
LP
904
905 case ARG_COPY_LOCALE:
906 arg_copy_locale = true;
907 break;
908
ed457f13
TB
909 case ARG_COPY_KEYMAP:
910 arg_copy_keymap = true;
911 break;
912
418b9be5
LP
913 case ARG_COPY_TIMEZONE:
914 arg_copy_timezone = true;
915 break;
916
917 case ARG_COPY_ROOT_PASSWORD:
918 arg_copy_root_password = true;
919 break;
920
921 case ARG_SETUP_MACHINE_ID:
922
923 r = sd_id128_randomize(&arg_machine_id);
23bbb0de
MS
924 if (r < 0)
925 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
418b9be5
LP
926
927 break;
928
929 case '?':
930 return -EINVAL;
931
932 default:
933 assert_not_reached("Unhandled option");
934 }
418b9be5
LP
935
936 return 1;
937}
938
939int main(int argc, char *argv[]) {
1d84ad94 940 bool enabled;
418b9be5
LP
941 int r;
942
943 r = parse_argv(argc, argv);
944 if (r <= 0)
945 goto finish;
946
947 log_set_target(LOG_TARGET_AUTO);
948 log_parse_environment();
949 log_open();
950
951 umask(0022);
952
1d84ad94
LP
953 r = proc_cmdline_get_bool("systemd.firstboot", &enabled);
954 if (r < 0) {
955 log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
956 goto finish;
957 }
958 if (r > 0 && !enabled) {
959 r = 0; /* disabled */
960 goto finish;
f582cbca
LP
961 }
962
418b9be5
LP
963 r = process_locale();
964 if (r < 0)
965 goto finish;
966
ed457f13
TB
967 r = process_keymap();
968 if (r < 0)
969 goto finish;
970
418b9be5
LP
971 r = process_timezone();
972 if (r < 0)
973 goto finish;
974
975 r = process_hostname();
976 if (r < 0)
977 goto finish;
978
979 r = process_machine_id();
980 if (r < 0)
981 goto finish;
982
983 r = process_root_password();
984 if (r < 0)
985 goto finish;
986
987finish:
988 free(arg_root);
989 free(arg_locale);
990 free(arg_locale_messages);
ed457f13 991 free(arg_keymap);
418b9be5
LP
992 free(arg_timezone);
993 free(arg_hostname);
1602b008 994 string_erase(arg_root_password);
418b9be5
LP
995 free(arg_root_password);
996
997 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
998}