]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/firstboot/firstboot.c
ask-password: derive pollfd array from enum
[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
9f555bba
BE
26#ifdef HAVE_CRYPT_H
27/* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
28 * removed from glibc at some point. As part of the removal, defines for
29 * crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
30 *
31 * Newer versions of glibc (v2.0+) already ship crypt.h with a definition
32 * of crypt(3) as well, so we simply include it if it is present. MariaDB,
33 * MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
34 * same way since ages without any problems.
35 */
36# include <crypt.h>
37#endif
38
dccca82b
LP
39#include "sd-id128.h"
40
b5efdb8a 41#include "alloc-util.h"
3f6fd1ba 42#include "ask-password-api.h"
418b9be5 43#include "copy.h"
6bedfcbb 44#include "fd-util.h"
3f6fd1ba 45#include "fileio.h"
f4f15635 46#include "fs-util.h"
3f6fd1ba
LP
47#include "hostname-util.h"
48#include "locale-util.h"
418b9be5 49#include "mkdir.h"
6bedfcbb 50#include "parse-util.h"
418b9be5 51#include "path-util.h"
f582cbca 52#include "proc-cmdline.h"
3df3e884 53#include "random-util.h"
6bedfcbb 54#include "string-util.h"
3f6fd1ba 55#include "strv.h"
288a74cc 56#include "terminal-util.h"
3f6fd1ba 57#include "time-util.h"
affb60b1 58#include "umask-util.h"
e929bee0 59#include "user-util.h"
418b9be5
LP
60
61static char *arg_root = NULL;
62static char *arg_locale = NULL; /* $LANG */
ed457f13 63static char *arg_keymap = NULL;
418b9be5
LP
64static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
65static char *arg_timezone = NULL;
66static char *arg_hostname = NULL;
67static sd_id128_t arg_machine_id = {};
68static char *arg_root_password = NULL;
69static bool arg_prompt_locale = false;
ed457f13 70static bool arg_prompt_keymap = false;
418b9be5
LP
71static bool arg_prompt_timezone = false;
72static bool arg_prompt_hostname = false;
73static bool arg_prompt_root_password = false;
74static bool arg_copy_locale = false;
ed457f13 75static bool arg_copy_keymap = false;
418b9be5
LP
76static bool arg_copy_timezone = false;
77static bool arg_copy_root_password = false;
78
418b9be5
LP
79static bool press_any_key(void) {
80 char k = 0;
81 bool need_nl = true;
82
83 printf("-- Press any key to proceed --");
84 fflush(stdout);
85
94956f8f 86 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
418b9be5
LP
87
88 if (need_nl)
89 putchar('\n');
90
91 return k != 'q';
92}
93
94static void print_welcome(void) {
95 _cleanup_free_ char *pretty_name = NULL;
96 const char *os_release = NULL;
97 static bool done = false;
98 int r;
99
100 if (done)
101 return;
102
1d13f648 103 os_release = prefix_roota(arg_root, "/etc/os-release");
418b9be5
LP
104 r = parse_env_file(os_release, NEWLINE,
105 "PRETTY_NAME", &pretty_name,
106 NULL);
107 if (r == -ENOENT) {
108
1d13f648 109 os_release = prefix_roota(arg_root, "/usr/lib/os-release");
418b9be5
LP
110 r = parse_env_file(os_release, NEWLINE,
111 "PRETTY_NAME", &pretty_name,
112 NULL);
113 }
114
115 if (r < 0 && r != -ENOENT)
da927ba9 116 log_warning_errno(r, "Failed to read os-release file: %m");
418b9be5
LP
117
118 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
119 isempty(pretty_name) ? "Linux" : pretty_name);
120
121 press_any_key();
122
123 done = true;
124}
125
126static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
127 unsigned n, per_column, i, j;
128 unsigned break_lines, break_modulo;
129
130 assert(n_columns > 0);
131
132 n = strv_length(x);
133 per_column = (n + n_columns - 1) / n_columns;
134
135 break_lines = lines();
136 if (break_lines > 2)
137 break_lines--;
138
139 /* The first page gets two extra lines, since we want to show
140 * a title */
141 break_modulo = break_lines;
142 if (break_modulo > 3)
143 break_modulo -= 3;
144
145 for (i = 0; i < per_column; i++) {
146
147 for (j = 0; j < n_columns; j ++) {
148 _cleanup_free_ char *e = NULL;
149
150 if (j * per_column + i >= n)
151 break;
152
153 e = ellipsize(x[j * per_column + i], width, percentage);
154 if (!e)
155 return log_oom();
156
157 printf("%4u) %-*s", j * per_column + i + 1, width, e);
158 }
159
160 putchar('\n');
161
162 /* on the first screen we reserve 2 extra lines for the title */
163 if (i % break_lines == break_modulo) {
164 if (!press_any_key())
165 return 0;
166 }
167 }
168
169 return 0;
170}
171
172static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) {
173 int r;
174
175 assert(text);
176 assert(is_valid);
177 assert(ret);
178
179 for (;;) {
180 _cleanup_free_ char *p = NULL;
181 unsigned u;
182
323b7dc9 183 r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
23bbb0de
MS
184 if (r < 0)
185 return log_error_errno(r, "Failed to query user: %m");
418b9be5
LP
186
187 if (isempty(p)) {
188 log_warning("No data entered, skipping.");
189 return 0;
190 }
191
192 r = safe_atou(p, &u);
193 if (r >= 0) {
194 char *c;
195
196 if (u <= 0 || u > strv_length(l)) {
197 log_error("Specified entry number out of range.");
198 continue;
199 }
200
201 log_info("Selected '%s'.", l[u-1]);
202
203 c = strdup(l[u-1]);
204 if (!c)
205 return log_oom();
206
207 free(*ret);
208 *ret = c;
209 return 0;
210 }
211
212 if (!is_valid(p)) {
213 log_error("Entered data invalid.");
214 continue;
215 }
216
217 free(*ret);
218 *ret = p;
219 p = 0;
220 return 0;
221 }
222}
223
224static int prompt_locale(void) {
225 _cleanup_strv_free_ char **locales = NULL;
226 int r;
227
228 if (arg_locale || arg_locale_messages)
229 return 0;
230
231 if (!arg_prompt_locale)
232 return 0;
233
234 r = get_locales(&locales);
23bbb0de
MS
235 if (r < 0)
236 return log_error_errno(r, "Cannot query locales list: %m");
418b9be5
LP
237
238 print_welcome();
239
240 printf("\nAvailable Locales:\n\n");
241 r = show_menu(locales, 3, 22, 60);
242 if (r < 0)
243 return r;
244
245 putchar('\n');
246
247 r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
248 if (r < 0)
249 return r;
250
251 if (isempty(arg_locale))
252 return 0;
253
254 r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
255 if (r < 0)
256 return r;
257
258 return 0;
259}
260
261static int process_locale(void) {
262 const char *etc_localeconf;
263 char* locales[3];
264 unsigned i = 0;
265 int r;
266
1d13f648 267 etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
b3cd687d 268 if (laccess(etc_localeconf, F_OK) >= 0)
418b9be5
LP
269 return 0;
270
271 if (arg_copy_locale && arg_root) {
272
273 mkdir_parents(etc_localeconf, 0755);
1c876927 274 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, COPY_REFLINK);
418b9be5 275 if (r != -ENOENT) {
23bbb0de
MS
276 if (r < 0)
277 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
418b9be5
LP
278
279 log_info("%s copied.", etc_localeconf);
280 return 0;
281 }
282 }
283
284 r = prompt_locale();
285 if (r < 0)
286 return r;
287
288 if (!isempty(arg_locale))
63c372cb 289 locales[i++] = strjoina("LANG=", arg_locale);
418b9be5 290 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
63c372cb 291 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
418b9be5
LP
292
293 if (i == 0)
294 return 0;
295
296 locales[i] = NULL;
297
298 mkdir_parents(etc_localeconf, 0755);
299 r = write_env_file(etc_localeconf, locales);
23bbb0de
MS
300 if (r < 0)
301 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
418b9be5
LP
302
303 log_info("%s written.", etc_localeconf);
304 return 0;
305}
306
ed457f13
TB
307static int prompt_keymap(void) {
308 _cleanup_strv_free_ char **kmaps = NULL;
309 int r;
310
311 if (arg_keymap)
312 return 0;
313
314 if (!arg_prompt_keymap)
315 return 0;
316
317 r = get_keymaps(&kmaps);
318 if (r == -ENOENT) /* no keymaps installed */
319 return r;
320 if (r < 0)
321 return log_error_errno(r, "Failed to read keymaps: %m");
322
323 print_welcome();
324
325 printf("\nAvailable keymaps:\n\n");
326 r = show_menu(kmaps, 3, 22, 60);
327 if (r < 0)
328 return r;
329
330 putchar('\n');
331
8700b4da
ZJS
332 return prompt_loop("Please enter system keymap name or number",
333 kmaps, keymap_is_valid, &arg_keymap);
ed457f13
TB
334}
335
336static int process_keymap(void) {
337 const char *etc_vconsoleconf;
338 char **keymap;
339 int r;
340
341 etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
342 if (laccess(etc_vconsoleconf, F_OK) >= 0)
343 return 0;
344
345 if (arg_copy_keymap && arg_root) {
346
347 mkdir_parents(etc_vconsoleconf, 0755);
348 r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, COPY_REFLINK);
349 if (r != -ENOENT) {
350 if (r < 0)
351 return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
352
353 log_info("%s copied.", etc_vconsoleconf);
354 return 0;
355 }
356 }
357
358 r = prompt_keymap();
359 if (r == -ENOENT)
360 return 0; /* don't fail if no keymaps are installed */
361 if (r < 0)
362 return r;
363
a7353b4d 364 if (isempty(arg_keymap))
ed457f13
TB
365 return 0;
366
a7353b4d
YW
367 keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
368
369 r = mkdir_parents(etc_vconsoleconf, 0755);
370 if (r < 0)
371 return log_error_errno(r, "Failed to create the parent directory of %s: %m", etc_vconsoleconf);
372
ed457f13
TB
373 r = write_env_file(etc_vconsoleconf, keymap);
374 if (r < 0)
375 return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
376
377 log_info("%s written.", etc_vconsoleconf);
378 return 0;
379}
380
418b9be5
LP
381static int prompt_timezone(void) {
382 _cleanup_strv_free_ char **zones = NULL;
383 int r;
384
385 if (arg_timezone)
386 return 0;
387
388 if (!arg_prompt_timezone)
389 return 0;
390
391 r = get_timezones(&zones);
23bbb0de
MS
392 if (r < 0)
393 return log_error_errno(r, "Cannot query timezone list: %m");
418b9be5
LP
394
395 print_welcome();
396
397 printf("\nAvailable Time Zones:\n\n");
398 r = show_menu(zones, 3, 22, 30);
399 if (r < 0)
400 return r;
401
402 putchar('\n');
403
404 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
405 if (r < 0)
406 return r;
407
408 return 0;
409}
410
411static int process_timezone(void) {
412 const char *etc_localtime, *e;
413 int r;
414
1d13f648 415 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
b3cd687d 416 if (laccess(etc_localtime, F_OK) >= 0)
418b9be5
LP
417 return 0;
418
419 if (arg_copy_timezone && arg_root) {
420 _cleanup_free_ char *p = NULL;
421
422 r = readlink_malloc("/etc/localtime", &p);
423 if (r != -ENOENT) {
23bbb0de
MS
424 if (r < 0)
425 return log_error_errno(r, "Failed to read host timezone: %m");
418b9be5
LP
426
427 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
428 if (symlink(p, etc_localtime) < 0)
429 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
430
431 log_info("%s copied.", etc_localtime);
432 return 0;
433 }
434 }
435
436 r = prompt_timezone();
437 if (r < 0)
438 return r;
439
440 if (isempty(arg_timezone))
441 return 0;
442
63c372cb 443 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
418b9be5
LP
444
445 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
446 if (symlink(e, etc_localtime) < 0)
447 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
448
449 log_info("%s written", etc_localtime);
450 return 0;
451}
452
453static int prompt_hostname(void) {
454 int r;
455
456 if (arg_hostname)
457 return 0;
458
459 if (!arg_prompt_hostname)
460 return 0;
461
462 print_welcome();
463 putchar('\n');
464
465 for (;;) {
466 _cleanup_free_ char *h = NULL;
467
323b7dc9 468 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
23bbb0de
MS
469 if (r < 0)
470 return log_error_errno(r, "Failed to query hostname: %m");
418b9be5
LP
471
472 if (isempty(h)) {
473 log_warning("No hostname entered, skipping.");
474 break;
475 }
476
34ad6090 477 if (!hostname_is_valid(h, true)) {
418b9be5
LP
478 log_error("Specified hostname invalid.");
479 continue;
480 }
481
34ad6090 482 /* Get rid of the trailing dot that we allow, but don't want to see */
ae691c1d 483 arg_hostname = hostname_cleanup(h);
418b9be5
LP
484 h = NULL;
485 break;
486 }
487
488 return 0;
489}
490
491static int process_hostname(void) {
492 const char *etc_hostname;
493 int r;
494
1d13f648 495 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
b3cd687d 496 if (laccess(etc_hostname, F_OK) >= 0)
418b9be5
LP
497 return 0;
498
499 r = prompt_hostname();
500 if (r < 0)
501 return r;
502
503 if (isempty(arg_hostname))
504 return 0;
505
506 mkdir_parents(etc_hostname, 0755);
0675e94a
AJ
507 r = write_string_file(etc_hostname, arg_hostname,
508 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
509 if (r < 0)
510 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
418b9be5
LP
511
512 log_info("%s written.", etc_hostname);
513 return 0;
514}
515
516static int process_machine_id(void) {
517 const char *etc_machine_id;
518 char id[SD_ID128_STRING_MAX];
519 int r;
520
1d13f648 521 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
b3cd687d 522 if (laccess(etc_machine_id, F_OK) >= 0)
418b9be5
LP
523 return 0;
524
3bbaff3e 525 if (sd_id128_is_null(arg_machine_id))
418b9be5
LP
526 return 0;
527
528 mkdir_parents(etc_machine_id, 0755);
0675e94a
AJ
529 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
530 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
531 if (r < 0)
532 return log_error_errno(r, "Failed to write machine id: %m");
418b9be5
LP
533
534 log_info("%s written.", etc_machine_id);
535 return 0;
536}
537
538static int prompt_root_password(void) {
539 const char *msg1, *msg2, *etc_shadow;
540 int r;
541
542 if (arg_root_password)
543 return 0;
544
545 if (!arg_prompt_root_password)
546 return 0;
547
1d13f648 548 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 549 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
550 return 0;
551
552 print_welcome();
553 putchar('\n');
554
323b7dc9
ZJS
555 msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
556 msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: ");
418b9be5
LP
557
558 for (;;) {
ab84f5b9 559 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
418b9be5 560
e287086b 561 r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
23bbb0de
MS
562 if (r < 0)
563 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
564
565 if (isempty(a)) {
566 log_warning("No password entered, skipping.");
567 break;
568 }
569
e287086b 570 r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
ab84f5b9 571 if (r < 0)
00843602 572 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
573
574 if (!streq(a, b)) {
575 log_error("Entered passwords did not match, please try again.");
418b9be5
LP
576 continue;
577 }
578
418b9be5
LP
579 arg_root_password = a;
580 a = NULL;
581 break;
582 }
583
584 return 0;
585}
586
587static int write_root_shadow(const char *path, const struct spwd *p) {
588 _cleanup_fclose_ FILE *f = NULL;
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
597 errno = 0;
598 if (putspent(p, f) != 0)
f5e5c28f 599 return errno > 0 ? -errno : -EIO;
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}