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