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