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