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