]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/firstboot/firstboot.c
Merge pull request #8530 from poettering/include-is-obsolete
[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 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
21 #include <fcntl.h>
22 #include <getopt.h>
23 #include <unistd.h>
24
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
38 #include "sd-id128.h"
39
40 #include "alloc-util.h"
41 #include "ask-password-api.h"
42 #include "copy.h"
43 #include "fd-util.h"
44 #include "fileio.h"
45 #include "fs-util.h"
46 #include "hostname-util.h"
47 #include "locale-util.h"
48 #include "mkdir.h"
49 #include "parse-util.h"
50 #include "path-util.h"
51 #include "proc-cmdline.h"
52 #include "random-util.h"
53 #include "string-util.h"
54 #include "strv.h"
55 #include "terminal-util.h"
56 #include "time-util.h"
57 #include "umask-util.h"
58 #include "user-util.h"
59
60 static char *arg_root = NULL;
61 static char *arg_locale = NULL; /* $LANG */
62 static char *arg_keymap = NULL;
63 static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
64 static char *arg_timezone = NULL;
65 static char *arg_hostname = NULL;
66 static sd_id128_t arg_machine_id = {};
67 static char *arg_root_password = NULL;
68 static bool arg_prompt_locale = false;
69 static bool arg_prompt_keymap = false;
70 static bool arg_prompt_timezone = false;
71 static bool arg_prompt_hostname = false;
72 static bool arg_prompt_root_password = false;
73 static bool arg_copy_locale = false;
74 static bool arg_copy_keymap = false;
75 static bool arg_copy_timezone = false;
76 static bool arg_copy_root_password = false;
77
78 static 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
85 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
86
87 if (need_nl)
88 putchar('\n');
89
90 return k != 'q';
91 }
92
93 static 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
102 os_release = prefix_roota(arg_root, "/etc/os-release");
103 r = parse_env_file(os_release, NEWLINE,
104 "PRETTY_NAME", &pretty_name,
105 NULL);
106 if (r == -ENOENT) {
107
108 os_release = prefix_roota(arg_root, "/usr/lib/os-release");
109 r = parse_env_file(os_release, NEWLINE,
110 "PRETTY_NAME", &pretty_name,
111 NULL);
112 }
113
114 if (r < 0 && r != -ENOENT)
115 log_warning_errno(r, "Failed to read os-release file: %m");
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
125 static 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);
132 per_column = DIV_ROUND_UP(n, n_columns);
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
171 static 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
182 r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
183 if (r < 0)
184 return log_error_errno(r, "Failed to query user: %m");
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
223 static 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);
234 if (r < 0)
235 return log_error_errno(r, "Cannot query locales list: %m");
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
260 static int process_locale(void) {
261 const char *etc_localeconf;
262 char* locales[3];
263 unsigned i = 0;
264 int r;
265
266 etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
267 if (laccess(etc_localeconf, F_OK) >= 0)
268 return 0;
269
270 if (arg_copy_locale && arg_root) {
271
272 mkdir_parents(etc_localeconf, 0755);
273 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, COPY_REFLINK);
274 if (r != -ENOENT) {
275 if (r < 0)
276 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
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))
288 locales[i++] = strjoina("LANG=", arg_locale);
289 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
290 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
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);
299 if (r < 0)
300 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
301
302 log_info("%s written.", etc_localeconf);
303 return 0;
304 }
305
306 static 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
331 return prompt_loop("Please enter system keymap name or number",
332 kmaps, keymap_is_valid, &arg_keymap);
333 }
334
335 static 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
363 if (isempty(arg_keymap))
364 return 0;
365
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
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
380 static 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);
391 if (r < 0)
392 return log_error_errno(r, "Cannot query timezone list: %m");
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
410 static int process_timezone(void) {
411 const char *etc_localtime, *e;
412 int r;
413
414 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
415 if (laccess(etc_localtime, F_OK) >= 0)
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) {
423 if (r < 0)
424 return log_error_errno(r, "Failed to read host timezone: %m");
425
426 mkdir_parents(etc_localtime, 0755);
427 if (symlink(p, etc_localtime) < 0)
428 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
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
442 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
443
444 mkdir_parents(etc_localtime, 0755);
445 if (symlink(e, etc_localtime) < 0)
446 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
447
448 log_info("%s written", etc_localtime);
449 return 0;
450 }
451
452 static 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
467 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
468 if (r < 0)
469 return log_error_errno(r, "Failed to query hostname: %m");
470
471 if (isempty(h)) {
472 log_warning("No hostname entered, skipping.");
473 break;
474 }
475
476 if (!hostname_is_valid(h, true)) {
477 log_error("Specified hostname invalid.");
478 continue;
479 }
480
481 /* Get rid of the trailing dot that we allow, but don't want to see */
482 arg_hostname = hostname_cleanup(h);
483 h = NULL;
484 break;
485 }
486
487 return 0;
488 }
489
490 static int process_hostname(void) {
491 const char *etc_hostname;
492 int r;
493
494 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
495 if (laccess(etc_hostname, F_OK) >= 0)
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);
506 r = write_string_file(etc_hostname, arg_hostname,
507 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
508 if (r < 0)
509 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
510
511 log_info("%s written.", etc_hostname);
512 return 0;
513 }
514
515 static int process_machine_id(void) {
516 const char *etc_machine_id;
517 char id[SD_ID128_STRING_MAX];
518 int r;
519
520 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
521 if (laccess(etc_machine_id, F_OK) >= 0)
522 return 0;
523
524 if (sd_id128_is_null(arg_machine_id))
525 return 0;
526
527 mkdir_parents(etc_machine_id, 0755);
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);
530 if (r < 0)
531 return log_error_errno(r, "Failed to write machine id: %m");
532
533 log_info("%s written.", etc_machine_id);
534 return 0;
535 }
536
537 static 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
547 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
548 if (laccess(etc_shadow, F_OK) >= 0)
549 return 0;
550
551 print_welcome();
552 putchar('\n');
553
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: ");
556
557 for (;;) {
558 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
559
560 r = ask_password_tty(-1, msg1, NULL, 0, 0, NULL, &a);
561 if (r < 0)
562 return log_error_errno(r, "Failed to query root password: %m");
563
564 if (isempty(a)) {
565 log_warning("No password entered, skipping.");
566 break;
567 }
568
569 r = ask_password_tty(-1, msg2, NULL, 0, 0, NULL, &b);
570 if (r < 0)
571 return log_error_errno(r, "Failed to query root password: %m");
572
573 if (!streq(a, b)) {
574 log_error("Entered passwords did not match, please try again.");
575 continue;
576 }
577
578 arg_root_password = TAKE_PTR(a);
579 break;
580 }
581
582 return 0;
583 }
584
585 static int write_root_shadow(const char *path, const struct spwd *p) {
586 _cleanup_fclose_ FILE *f = NULL;
587 int r;
588
589 assert(path);
590 assert(p);
591
592 RUN_WITH_UMASK(0777)
593 f = fopen(path, "wex");
594 if (!f)
595 return -errno;
596
597 r = putspent_sane(p, f);
598 if (r < 0)
599 return r;
600
601 return fflush_sync_and_check(f);
602 }
603
604 static 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",
614 .sp_min = -1,
615 .sp_max = -1,
616 .sp_warn = -1,
617 .sp_inact = -1,
618 .sp_expire = -1,
619 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
620 };
621
622 _cleanup_close_ int lock = -1;
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
631 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
632 if (laccess(etc_shadow, F_OK) >= 0)
633 return 0;
634
635 mkdir_parents(etc_shadow, 0755);
636
637 lock = take_etc_passwd_lock(arg_root);
638 if (lock < 0)
639 return log_error_errno(lock, "Failed to take a lock: %m");
640
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
651 return log_error_errno(errno, "Failed to find shadow entry for root: %m");
652 }
653
654 r = write_root_shadow(etc_shadow, p);
655 if (r < 0)
656 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
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
670 r = acquire_random_bytes(raw, 16, true);
671 if (r < 0)
672 return log_error_errno(r, "Failed to get salt: %m");
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)
686 errno = EINVAL;
687
688 return log_error_errno(errno, "Failed to encrypt password: %m");
689 }
690
691 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
692
693 r = write_root_shadow(etc_shadow, &item);
694 if (r < 0)
695 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
696
697 log_info("%s written.", etc_shadow);
698 return 0;
699 }
700
701 static void help(void) {
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"
709 " --keymap=KEYMAP Set keymap\n"
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"
716 " --prompt-keymap Prompt the user for keymap settings\n"
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"
720 " --prompt Prompt for all of the above\n"
721 " --copy-locale Copy locale from host\n"
722 " --copy-keymap Copy keymap from host\n"
723 " --copy-timezone Copy timezone from host\n"
724 " --copy-root-password Copy root password from host\n"
725 " --copy Copy locale, keymap, timezone, root password\n"
726 " --setup-machine-id Generate a new random machine ID\n"
727 , program_invocation_short_name);
728 }
729
730 static int parse_argv(int argc, char *argv[]) {
731
732 enum {
733 ARG_VERSION = 0x100,
734 ARG_ROOT,
735 ARG_LOCALE,
736 ARG_LOCALE_MESSAGES,
737 ARG_KEYMAP,
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,
745 ARG_PROMPT_KEYMAP,
746 ARG_PROMPT_TIMEZONE,
747 ARG_PROMPT_HOSTNAME,
748 ARG_PROMPT_ROOT_PASSWORD,
749 ARG_COPY,
750 ARG_COPY_LOCALE,
751 ARG_COPY_KEYMAP,
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 },
763 { "keymap", required_argument, NULL, ARG_KEYMAP },
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 },
771 { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
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 },
777 { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
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
789 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
790
791 switch (c) {
792
793 case 'h':
794 help();
795 return 0;
796
797 case ARG_VERSION:
798 return version();
799
800 case ARG_ROOT:
801 r = parse_path_argument_and_warn(optarg, true, &arg_root);
802 if (r < 0)
803 return r;
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
812 r = free_and_strdup(&arg_locale, optarg);
813 if (r < 0)
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
824 r = free_and_strdup(&arg_locale_messages, optarg);
825 if (r < 0)
826 return log_oom();
827
828 break;
829
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
842 case ARG_TIMEZONE:
843 if (!timezone_is_valid(optarg)) {
844 log_error("Timezone %s is not valid.", optarg);
845 return -EINVAL;
846 }
847
848 r = free_and_strdup(&arg_timezone, optarg);
849 if (r < 0)
850 return log_oom();
851
852 break;
853
854 case ARG_ROOT_PASSWORD:
855 r = free_and_strdup(&arg_root_password, optarg);
856 if (r < 0)
857 return log_oom();
858 break;
859
860 case ARG_ROOT_PASSWORD_FILE:
861 arg_root_password = mfree(arg_root_password);
862
863 r = read_one_line_file(optarg, &arg_root_password);
864 if (r < 0)
865 return log_error_errno(r, "Failed to read %s: %m", optarg);
866
867 break;
868
869 case ARG_HOSTNAME:
870 if (!hostname_is_valid(optarg, true)) {
871 log_error("Host name %s is not valid.", optarg);
872 return -EINVAL;
873 }
874
875 hostname_cleanup(optarg);
876 r = free_and_strdup(&arg_hostname, optarg);
877 if (r < 0)
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:
891 arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
892 break;
893
894 case ARG_PROMPT_LOCALE:
895 arg_prompt_locale = true;
896 break;
897
898 case ARG_PROMPT_KEYMAP:
899 arg_prompt_keymap = true;
900 break;
901
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:
915 arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true;
916 break;
917
918 case ARG_COPY_LOCALE:
919 arg_copy_locale = true;
920 break;
921
922 case ARG_COPY_KEYMAP:
923 arg_copy_keymap = true;
924 break;
925
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);
937 if (r < 0)
938 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
939
940 break;
941
942 case '?':
943 return -EINVAL;
944
945 default:
946 assert_not_reached("Unhandled option");
947 }
948
949 return 1;
950 }
951
952 int main(int argc, char *argv[]) {
953 bool enabled;
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
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;
974 }
975
976 r = process_locale();
977 if (r < 0)
978 goto finish;
979
980 r = process_keymap();
981 if (r < 0)
982 goto finish;
983
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
1000 finish:
1001 free(arg_root);
1002 free(arg_locale);
1003 free(arg_locale_messages);
1004 free(arg_keymap);
1005 free(arg_timezone);
1006 free(arg_hostname);
1007 string_erase(arg_root_password);
1008 free(arg_root_password);
1009
1010 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1011 }