]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/firstboot/firstboot.c
Merge pull request #9084 from yuwata/fix-8965
[thirdparty/systemd.git] / src / firstboot / firstboot.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6 ***/
7
8 #include <fcntl.h>
9 #include <getopt.h>
10 #include <unistd.h>
11
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
25 #include "sd-id128.h"
26
27 #include "alloc-util.h"
28 #include "ask-password-api.h"
29 #include "copy.h"
30 #include "fd-util.h"
31 #include "fileio.h"
32 #include "fs-util.h"
33 #include "hostname-util.h"
34 #include "locale-util.h"
35 #include "mkdir.h"
36 #include "os-util.h"
37 #include "parse-util.h"
38 #include "path-util.h"
39 #include "proc-cmdline.h"
40 #include "random-util.h"
41 #include "string-util.h"
42 #include "strv.h"
43 #include "terminal-util.h"
44 #include "time-util.h"
45 #include "umask-util.h"
46 #include "user-util.h"
47
48 static char *arg_root = NULL;
49 static char *arg_locale = NULL; /* $LANG */
50 static char *arg_keymap = NULL;
51 static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
52 static char *arg_timezone = NULL;
53 static char *arg_hostname = NULL;
54 static sd_id128_t arg_machine_id = {};
55 static char *arg_root_password = NULL;
56 static bool arg_prompt_locale = false;
57 static bool arg_prompt_keymap = false;
58 static bool arg_prompt_timezone = false;
59 static bool arg_prompt_hostname = false;
60 static bool arg_prompt_root_password = false;
61 static bool arg_copy_locale = false;
62 static bool arg_copy_keymap = false;
63 static bool arg_copy_timezone = false;
64 static bool arg_copy_root_password = false;
65
66 static 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
73 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
74
75 if (need_nl)
76 putchar('\n');
77
78 return k != 'q';
79 }
80
81 static void print_welcome(void) {
82 _cleanup_free_ char *pretty_name = NULL;
83 static bool done = false;
84 int r;
85
86 if (done)
87 return;
88
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");
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
102 static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
103 unsigned break_lines, break_modulo;
104 size_t n, per_column, i, j;
105
106 assert(n_columns > 0);
107
108 n = strv_length(x);
109 per_column = DIV_ROUND_UP(n, n_columns);
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
133 printf("%4zu) %-*s", j * per_column + i + 1, width, e);
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
148 static 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
159 r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
160 if (r < 0)
161 return log_error_errno(r, "Failed to query user: %m");
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
200 static 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);
211 if (r < 0)
212 return log_error_errno(r, "Cannot query locales list: %m");
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
237 static int process_locale(void) {
238 const char *etc_localeconf;
239 char* locales[3];
240 unsigned i = 0;
241 int r;
242
243 etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
244 if (laccess(etc_localeconf, F_OK) >= 0)
245 return 0;
246
247 if (arg_copy_locale && arg_root) {
248
249 mkdir_parents(etc_localeconf, 0755);
250 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, COPY_REFLINK);
251 if (r != -ENOENT) {
252 if (r < 0)
253 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
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))
265 locales[i++] = strjoina("LANG=", arg_locale);
266 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
267 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
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);
276 if (r < 0)
277 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
278
279 log_info("%s written.", etc_localeconf);
280 return 0;
281 }
282
283 static 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
308 return prompt_loop("Please enter system keymap name or number",
309 kmaps, keymap_is_valid, &arg_keymap);
310 }
311
312 static 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
340 if (isempty(arg_keymap))
341 return 0;
342
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
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
357 static bool timezone_is_valid_log_error(const char *name) {
358 return timezone_is_valid(name, LOG_ERR);
359 }
360
361 static 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);
372 if (r < 0)
373 return log_error_errno(r, "Cannot query timezone list: %m");
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
384 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid_log_error, &arg_timezone);
385 if (r < 0)
386 return r;
387
388 return 0;
389 }
390
391 static int process_timezone(void) {
392 const char *etc_localtime, *e;
393 int r;
394
395 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
396 if (laccess(etc_localtime, F_OK) >= 0)
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) {
404 if (r < 0)
405 return log_error_errno(r, "Failed to read host timezone: %m");
406
407 mkdir_parents(etc_localtime, 0755);
408 if (symlink(p, etc_localtime) < 0)
409 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
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
423 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
424
425 mkdir_parents(etc_localtime, 0755);
426 if (symlink(e, etc_localtime) < 0)
427 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
428
429 log_info("%s written", etc_localtime);
430 return 0;
431 }
432
433 static 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
448 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
449 if (r < 0)
450 return log_error_errno(r, "Failed to query hostname: %m");
451
452 if (isempty(h)) {
453 log_warning("No hostname entered, skipping.");
454 break;
455 }
456
457 if (!hostname_is_valid(h, true)) {
458 log_error("Specified hostname invalid.");
459 continue;
460 }
461
462 /* Get rid of the trailing dot that we allow, but don't want to see */
463 arg_hostname = hostname_cleanup(h);
464 h = NULL;
465 break;
466 }
467
468 return 0;
469 }
470
471 static int process_hostname(void) {
472 const char *etc_hostname;
473 int r;
474
475 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
476 if (laccess(etc_hostname, F_OK) >= 0)
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);
487 r = write_string_file(etc_hostname, arg_hostname,
488 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
489 if (r < 0)
490 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
491
492 log_info("%s written.", etc_hostname);
493 return 0;
494 }
495
496 static int process_machine_id(void) {
497 const char *etc_machine_id;
498 char id[SD_ID128_STRING_MAX];
499 int r;
500
501 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
502 if (laccess(etc_machine_id, F_OK) >= 0)
503 return 0;
504
505 if (sd_id128_is_null(arg_machine_id))
506 return 0;
507
508 mkdir_parents(etc_machine_id, 0755);
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);
511 if (r < 0)
512 return log_error_errno(r, "Failed to write machine id: %m");
513
514 log_info("%s written.", etc_machine_id);
515 return 0;
516 }
517
518 static 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
528 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
529 if (laccess(etc_shadow, F_OK) >= 0)
530 return 0;
531
532 print_welcome();
533 putchar('\n');
534
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: ");
537
538 for (;;) {
539 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
540
541 r = ask_password_tty(-1, msg1, NULL, 0, 0, NULL, &a);
542 if (r < 0)
543 return log_error_errno(r, "Failed to query root password: %m");
544
545 if (isempty(a)) {
546 log_warning("No password entered, skipping.");
547 break;
548 }
549
550 r = ask_password_tty(-1, msg2, NULL, 0, 0, NULL, &b);
551 if (r < 0)
552 return log_error_errno(r, "Failed to query root password: %m");
553
554 if (!streq(a, b)) {
555 log_error("Entered passwords did not match, please try again.");
556 continue;
557 }
558
559 arg_root_password = TAKE_PTR(a);
560 break;
561 }
562
563 return 0;
564 }
565
566 static int write_root_shadow(const char *path, const struct spwd *p) {
567 _cleanup_fclose_ FILE *f = NULL;
568 int r;
569
570 assert(path);
571 assert(p);
572
573 RUN_WITH_UMASK(0777)
574 f = fopen(path, "wex");
575 if (!f)
576 return -errno;
577
578 r = putspent_sane(p, f);
579 if (r < 0)
580 return r;
581
582 return fflush_sync_and_check(f);
583 }
584
585 static 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",
595 .sp_min = -1,
596 .sp_max = -1,
597 .sp_warn = -1,
598 .sp_inact = -1,
599 .sp_expire = -1,
600 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
601 };
602
603 _cleanup_close_ int lock = -1;
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
612 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
613 if (laccess(etc_shadow, F_OK) >= 0)
614 return 0;
615
616 mkdir_parents(etc_shadow, 0755);
617
618 lock = take_etc_passwd_lock(arg_root);
619 if (lock < 0)
620 return log_error_errno(lock, "Failed to take a lock: %m");
621
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
632 return log_error_errno(errno, "Failed to find shadow entry for root: %m");
633 }
634
635 r = write_root_shadow(etc_shadow, p);
636 if (r < 0)
637 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
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
651 r = acquire_random_bytes(raw, 16, true);
652 if (r < 0)
653 return log_error_errno(r, "Failed to get salt: %m");
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)
667 errno = EINVAL;
668
669 return log_error_errno(errno, "Failed to encrypt password: %m");
670 }
671
672 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
673
674 r = write_root_shadow(etc_shadow, &item);
675 if (r < 0)
676 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
677
678 log_info("%s written.", etc_shadow);
679 return 0;
680 }
681
682 static void help(void) {
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"
690 " --keymap=KEYMAP Set keymap\n"
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"
697 " --prompt-keymap Prompt the user for keymap settings\n"
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"
701 " --prompt Prompt for all of the above\n"
702 " --copy-locale Copy locale from host\n"
703 " --copy-keymap Copy keymap from host\n"
704 " --copy-timezone Copy timezone from host\n"
705 " --copy-root-password Copy root password from host\n"
706 " --copy Copy locale, keymap, timezone, root password\n"
707 " --setup-machine-id Generate a new random machine ID\n"
708 , program_invocation_short_name);
709 }
710
711 static int parse_argv(int argc, char *argv[]) {
712
713 enum {
714 ARG_VERSION = 0x100,
715 ARG_ROOT,
716 ARG_LOCALE,
717 ARG_LOCALE_MESSAGES,
718 ARG_KEYMAP,
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,
726 ARG_PROMPT_KEYMAP,
727 ARG_PROMPT_TIMEZONE,
728 ARG_PROMPT_HOSTNAME,
729 ARG_PROMPT_ROOT_PASSWORD,
730 ARG_COPY,
731 ARG_COPY_LOCALE,
732 ARG_COPY_KEYMAP,
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 },
744 { "keymap", required_argument, NULL, ARG_KEYMAP },
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 },
752 { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
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 },
758 { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
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
770 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
771
772 switch (c) {
773
774 case 'h':
775 help();
776 return 0;
777
778 case ARG_VERSION:
779 return version();
780
781 case ARG_ROOT:
782 r = parse_path_argument_and_warn(optarg, true, &arg_root);
783 if (r < 0)
784 return r;
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
793 r = free_and_strdup(&arg_locale, optarg);
794 if (r < 0)
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
805 r = free_and_strdup(&arg_locale_messages, optarg);
806 if (r < 0)
807 return log_oom();
808
809 break;
810
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
823 case ARG_TIMEZONE:
824 if (!timezone_is_valid(optarg, LOG_ERR)) {
825 log_error("Timezone %s is not valid.", optarg);
826 return -EINVAL;
827 }
828
829 r = free_and_strdup(&arg_timezone, optarg);
830 if (r < 0)
831 return log_oom();
832
833 break;
834
835 case ARG_ROOT_PASSWORD:
836 r = free_and_strdup(&arg_root_password, optarg);
837 if (r < 0)
838 return log_oom();
839 break;
840
841 case ARG_ROOT_PASSWORD_FILE:
842 arg_root_password = mfree(arg_root_password);
843
844 r = read_one_line_file(optarg, &arg_root_password);
845 if (r < 0)
846 return log_error_errno(r, "Failed to read %s: %m", optarg);
847
848 break;
849
850 case ARG_HOSTNAME:
851 if (!hostname_is_valid(optarg, true)) {
852 log_error("Host name %s is not valid.", optarg);
853 return -EINVAL;
854 }
855
856 hostname_cleanup(optarg);
857 r = free_and_strdup(&arg_hostname, optarg);
858 if (r < 0)
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:
872 arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
873 break;
874
875 case ARG_PROMPT_LOCALE:
876 arg_prompt_locale = true;
877 break;
878
879 case ARG_PROMPT_KEYMAP:
880 arg_prompt_keymap = true;
881 break;
882
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:
896 arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true;
897 break;
898
899 case ARG_COPY_LOCALE:
900 arg_copy_locale = true;
901 break;
902
903 case ARG_COPY_KEYMAP:
904 arg_copy_keymap = true;
905 break;
906
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);
918 if (r < 0)
919 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
920
921 break;
922
923 case '?':
924 return -EINVAL;
925
926 default:
927 assert_not_reached("Unhandled option");
928 }
929
930 return 1;
931 }
932
933 int main(int argc, char *argv[]) {
934 bool enabled;
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
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;
955 }
956
957 r = process_locale();
958 if (r < 0)
959 goto finish;
960
961 r = process_keymap();
962 if (r < 0)
963 goto finish;
964
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
981 finish:
982 free(arg_root);
983 free(arg_locale);
984 free(arg_locale_messages);
985 free(arg_keymap);
986 free(arg_timezone);
987 free(arg_hostname);
988 string_erase(arg_root_password);
989 free(arg_root_password);
990
991 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
992 }