]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/firstboot/firstboot.c
update CODING_STYLE
[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) {
da927ba9 480 log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
481 clear_string(a);
482 return r;
483 }
484
485 if (!streq(a, b)) {
486 log_error("Entered passwords did not match, please try again.");
487 clear_string(a);
488 clear_string(b);
489 continue;
490 }
491
492 clear_string(b);
493 arg_root_password = a;
494 a = NULL;
495 break;
496 }
497
498 return 0;
499}
500
501static int write_root_shadow(const char *path, const struct spwd *p) {
502 _cleanup_fclose_ FILE *f = NULL;
503 assert(path);
504 assert(p);
505
3250929b
LP
506 RUN_WITH_UMASK(0777)
507 f = fopen(path, "wex");
418b9be5
LP
508 if (!f)
509 return -errno;
510
511 errno = 0;
512 if (putspent(p, f) != 0)
513 return errno ? -errno : -EIO;
514
515 return fflush_and_check(f);
516}
517
518static int process_root_password(void) {
519
520 static const char table[] =
521 "abcdefghijklmnopqrstuvwxyz"
522 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
523 "0123456789"
524 "./";
525
526 struct spwd item = {
527 .sp_namp = (char*) "root",
ad525df8
IS
528 .sp_min = -1,
529 .sp_max = -1,
530 .sp_warn = -1,
418b9be5
LP
531 .sp_inact = -1,
532 .sp_expire = -1,
533 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
534 };
45035609
LP
535
536 _cleanup_close_ int lock = -1;
418b9be5
LP
537 char salt[3+16+1+1];
538 uint8_t raw[16];
539 unsigned i;
540 char *j;
541
542 const char *etc_shadow;
543 int r;
544
1d13f648 545 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
418b9be5
LP
546 if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
547 return 0;
548
45035609
LP
549 mkdir_parents(etc_shadow, 0755);
550
551 lock = take_password_lock(arg_root);
552 if (lock < 0)
553 return lock;
554
418b9be5
LP
555 if (arg_copy_root_password && arg_root) {
556 struct spwd *p;
557
558 errno = 0;
559 p = getspnam("root");
560 if (p || errno != ENOENT) {
561 if (!p) {
562 if (!errno)
563 errno = EIO;
564
56f64d95 565 log_error_errno(errno, "Failed to find shadow entry for root: %m");
418b9be5
LP
566 return -errno;
567 }
568
569 r = write_root_shadow(etc_shadow, p);
23bbb0de
MS
570 if (r < 0)
571 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
572
573 log_info("%s copied.", etc_shadow);
574 return 0;
575 }
576 }
577
578 r = prompt_root_password();
579 if (r < 0)
580 return r;
581
582 if (!arg_root_password)
583 return 0;
584
585 r = dev_urandom(raw, 16);
23bbb0de
MS
586 if (r < 0)
587 return log_error_errno(r, "Failed to get salt: %m");
418b9be5
LP
588
589 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
590 assert_cc(sizeof(table) == 64 + 1);
591 j = stpcpy(salt, "$6$");
592 for (i = 0; i < 16; i++)
593 j[i] = table[raw[i] & 63];
594 j[i++] = '$';
595 j[i] = 0;
596
597 errno = 0;
598 item.sp_pwdp = crypt(arg_root_password, salt);
599 if (!item.sp_pwdp) {
600 if (!errno)
601 errno = -EINVAL;
602
56f64d95 603 log_error_errno(errno, "Failed to encrypt password: %m");
418b9be5
LP
604 return -errno;
605 }
606
607 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
608
609 r = write_root_shadow(etc_shadow, &item);
23bbb0de
MS
610 if (r < 0)
611 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
612
613 log_info("%s written.", etc_shadow);
614 return 0;
615}
616
601185b4 617static void help(void) {
418b9be5
LP
618 printf("%s [OPTIONS...]\n\n"
619 "Configures basic settings of the system.\n\n"
620 " -h --help Show this help\n"
621 " --version Show package version\n"
622 " --root=PATH Operate on an alternate filesystem root\n"
623 " --locale=LOCALE Set primary locale (LANG=)\n"
624 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
625 " --timezone=TIMEZONE Set timezone\n"
626 " --hostname=NAME Set host name\n"
627 " --machine-ID=ID Set machine ID\n"
628 " --root-password=PASSWORD Set root password\n"
629 " --root-password-file=FILE Set root password from file\n"
630 " --prompt-locale Prompt the user for locale settings\n"
631 " --prompt-timezone Prompt the user for timezone\n"
632 " --prompt-hostname Prompt the user for hostname\n"
633 " --prompt-root-password Prompt the user for root password\n"
b57b0625 634 " --prompt Prompt for all of the above\n"
418b9be5
LP
635 " --copy-locale Copy locale from host\n"
636 " --copy-timezone Copy timezone from host\n"
637 " --copy-root-password Copy root password from host\n"
638 " --copy Copy locale, timezone, root password\n"
601185b4
ZJS
639 " --setup-machine-id Generate a new random machine ID\n"
640 , program_invocation_short_name);
418b9be5
LP
641}
642
643static int parse_argv(int argc, char *argv[]) {
644
645 enum {
646 ARG_VERSION = 0x100,
647 ARG_ROOT,
648 ARG_LOCALE,
649 ARG_LOCALE_MESSAGES,
650 ARG_TIMEZONE,
651 ARG_HOSTNAME,
652 ARG_MACHINE_ID,
653 ARG_ROOT_PASSWORD,
654 ARG_ROOT_PASSWORD_FILE,
655 ARG_PROMPT,
656 ARG_PROMPT_LOCALE,
657 ARG_PROMPT_TIMEZONE,
658 ARG_PROMPT_HOSTNAME,
659 ARG_PROMPT_ROOT_PASSWORD,
660 ARG_COPY,
661 ARG_COPY_LOCALE,
662 ARG_COPY_TIMEZONE,
663 ARG_COPY_ROOT_PASSWORD,
664 ARG_SETUP_MACHINE_ID,
665 };
666
667 static const struct option options[] = {
668 { "help", no_argument, NULL, 'h' },
669 { "version", no_argument, NULL, ARG_VERSION },
670 { "root", required_argument, NULL, ARG_ROOT },
671 { "locale", required_argument, NULL, ARG_LOCALE },
672 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
673 { "timezone", required_argument, NULL, ARG_TIMEZONE },
674 { "hostname", required_argument, NULL, ARG_HOSTNAME },
675 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
676 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
677 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
678 { "prompt", no_argument, NULL, ARG_PROMPT },
679 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
680 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
681 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
682 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
683 { "copy", no_argument, NULL, ARG_COPY },
684 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
685 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
686 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
687 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
688 {}
689 };
690
691 int r, c;
692
693 assert(argc >= 0);
694 assert(argv);
695
601185b4 696 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
418b9be5
LP
697
698 switch (c) {
699
700 case 'h':
601185b4
ZJS
701 help();
702 return 0;
418b9be5
LP
703
704 case ARG_VERSION:
3f6fd1ba 705 return version();
418b9be5
LP
706
707 case ARG_ROOT:
708 free(arg_root);
709 arg_root = path_make_absolute_cwd(optarg);
710 if (!arg_root)
711 return log_oom();
712
713 path_kill_slashes(arg_root);
714
97b11eed
DH
715 if (path_equal(arg_root, "/"))
716 arg_root = mfree(arg_root);
418b9be5
LP
717
718 break;
719
720 case ARG_LOCALE:
721 if (!locale_is_valid(optarg)) {
722 log_error("Locale %s is not valid.", optarg);
723 return -EINVAL;
724 }
725
2fc09a9c
DM
726 r = free_and_strdup(&arg_locale, optarg);
727 if (r < 0)
418b9be5
LP
728 return log_oom();
729
730 break;
731
732 case ARG_LOCALE_MESSAGES:
733 if (!locale_is_valid(optarg)) {
734 log_error("Locale %s is not valid.", optarg);
735 return -EINVAL;
736 }
737
2fc09a9c
DM
738 r = free_and_strdup(&arg_locale_messages, optarg);
739 if (r < 0)
418b9be5
LP
740 return log_oom();
741
742 break;
743
744 case ARG_TIMEZONE:
745 if (!timezone_is_valid(optarg)) {
746 log_error("Timezone %s is not valid.", optarg);
747 return -EINVAL;
748 }
749
2fc09a9c
DM
750 r = free_and_strdup(&arg_timezone, optarg);
751 if (r < 0)
418b9be5
LP
752 return log_oom();
753
754 break;
755
756 case ARG_ROOT_PASSWORD:
2fc09a9c
DM
757 r = free_and_strdup(&arg_root_password, optarg);
758 if (r < 0)
418b9be5 759 return log_oom();
418b9be5
LP
760 break;
761
762 case ARG_ROOT_PASSWORD_FILE:
0da16248 763 arg_root_password = mfree(arg_root_password);
418b9be5
LP
764
765 r = read_one_line_file(optarg, &arg_root_password);
23bbb0de
MS
766 if (r < 0)
767 return log_error_errno(r, "Failed to read %s: %m", optarg);
418b9be5
LP
768
769 break;
770
771 case ARG_HOSTNAME:
34ad6090 772 if (!hostname_is_valid(optarg, true)) {
418b9be5
LP
773 log_error("Host name %s is not valid.", optarg);
774 return -EINVAL;
775 }
776
ae691c1d 777 hostname_cleanup(optarg);
2fc09a9c
DM
778 r = free_and_strdup(&arg_hostname, optarg);
779 if (r < 0)
418b9be5
LP
780 return log_oom();
781
782 break;
783
784 case ARG_MACHINE_ID:
785 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
786 log_error("Failed to parse machine id %s.", optarg);
787 return -EINVAL;
788 }
789
790 break;
791
792 case ARG_PROMPT:
793 arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
794 break;
795
796 case ARG_PROMPT_LOCALE:
797 arg_prompt_locale = true;
798 break;
799
800 case ARG_PROMPT_TIMEZONE:
801 arg_prompt_timezone = true;
802 break;
803
804 case ARG_PROMPT_HOSTNAME:
805 arg_prompt_hostname = true;
806 break;
807
808 case ARG_PROMPT_ROOT_PASSWORD:
809 arg_prompt_root_password = true;
810 break;
811
812 case ARG_COPY:
813 arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true;
e926f647 814 break;
418b9be5
LP
815
816 case ARG_COPY_LOCALE:
817 arg_copy_locale = true;
818 break;
819
820 case ARG_COPY_TIMEZONE:
821 arg_copy_timezone = true;
822 break;
823
824 case ARG_COPY_ROOT_PASSWORD:
825 arg_copy_root_password = true;
826 break;
827
828 case ARG_SETUP_MACHINE_ID:
829
830 r = sd_id128_randomize(&arg_machine_id);
23bbb0de
MS
831 if (r < 0)
832 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
418b9be5
LP
833
834 break;
835
836 case '?':
837 return -EINVAL;
838
839 default:
840 assert_not_reached("Unhandled option");
841 }
418b9be5
LP
842
843 return 1;
844}
845
846int main(int argc, char *argv[]) {
847 int r;
848
849 r = parse_argv(argc, argv);
850 if (r <= 0)
851 goto finish;
852
853 log_set_target(LOG_TARGET_AUTO);
854 log_parse_environment();
855 log_open();
856
857 umask(0022);
858
859 r = process_locale();
860 if (r < 0)
861 goto finish;
862
863 r = process_timezone();
864 if (r < 0)
865 goto finish;
866
867 r = process_hostname();
868 if (r < 0)
869 goto finish;
870
871 r = process_machine_id();
872 if (r < 0)
873 goto finish;
874
875 r = process_root_password();
876 if (r < 0)
877 goto finish;
878
879finish:
880 free(arg_root);
881 free(arg_locale);
882 free(arg_locale_messages);
883 free(arg_timezone);
884 free(arg_hostname);
885 clear_string(arg_root_password);
886 free(arg_root_password);
887
888 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
889}