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