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