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