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