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