]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/firstboot/firstboot.c
sd-bus: drop bloom stuff, it's not needed anymore since kdbus is gone
[thirdparty/systemd.git] / src / firstboot / firstboot.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
418b9be5
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
418b9be5 21#include <fcntl.h>
418b9be5
LP
22#include <getopt.h>
23#include <shadow.h>
3f6fd1ba 24#include <unistd.h>
418b9be5 25
b5efdb8a 26#include "alloc-util.h"
3f6fd1ba 27#include "ask-password-api.h"
418b9be5 28#include "copy.h"
6bedfcbb 29#include "fd-util.h"
3f6fd1ba 30#include "fileio.h"
f4f15635 31#include "fs-util.h"
3f6fd1ba
LP
32#include "hostname-util.h"
33#include "locale-util.h"
418b9be5 34#include "mkdir.h"
6bedfcbb 35#include "parse-util.h"
418b9be5 36#include "path-util.h"
f582cbca 37#include "proc-cmdline.h"
3df3e884 38#include "random-util.h"
6bedfcbb 39#include "string-util.h"
3f6fd1ba 40#include "strv.h"
288a74cc 41#include "terminal-util.h"
3f6fd1ba 42#include "time-util.h"
affb60b1 43#include "umask-util.h"
e929bee0 44#include "user-util.h"
418b9be5
LP
45
46static char *arg_root = NULL;
47static char *arg_locale = NULL; /* $LANG */
ed457f13 48static char *arg_keymap = NULL;
418b9be5
LP
49static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
50static char *arg_timezone = NULL;
51static char *arg_hostname = NULL;
52static sd_id128_t arg_machine_id = {};
53static char *arg_root_password = NULL;
54static bool arg_prompt_locale = false;
ed457f13 55static bool arg_prompt_keymap = false;
418b9be5
LP
56static bool arg_prompt_timezone = false;
57static bool arg_prompt_hostname = false;
58static bool arg_prompt_root_password = false;
59static bool arg_copy_locale = false;
ed457f13 60static bool arg_copy_keymap = false;
418b9be5
LP
61static bool arg_copy_timezone = false;
62static bool arg_copy_root_password = false;
63
418b9be5
LP
64static 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
94956f8f 71 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
418b9be5
LP
72
73 if (need_nl)
74 putchar('\n');
75
76 return k != 'q';
77}
78
79static 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
1d13f648 88 os_release = prefix_roota(arg_root, "/etc/os-release");
418b9be5
LP
89 r = parse_env_file(os_release, NEWLINE,
90 "PRETTY_NAME", &pretty_name,
91 NULL);
92 if (r == -ENOENT) {
93
1d13f648 94 os_release = prefix_roota(arg_root, "/usr/lib/os-release");
418b9be5
LP
95 r = parse_env_file(os_release, NEWLINE,
96 "PRETTY_NAME", &pretty_name,
97 NULL);
98 }
99
100 if (r < 0 && r != -ENOENT)
da927ba9 101 log_warning_errno(r, "Failed to read os-release file: %m");
418b9be5
LP
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
111static 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
157static 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
323b7dc9 168 r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
23bbb0de
MS
169 if (r < 0)
170 return log_error_errno(r, "Failed to query user: %m");
418b9be5
LP
171
172 if (isempty(p)) {
173 log_warning("No data entered, skipping.");
174 return 0;
175 }
176
177 r = safe_atou(p, &u);
178 if (r >= 0) {
179 char *c;
180
181 if (u <= 0 || u > strv_length(l)) {
182 log_error("Specified entry number out of range.");
183 continue;
184 }
185
186 log_info("Selected '%s'.", l[u-1]);
187
188 c = strdup(l[u-1]);
189 if (!c)
190 return log_oom();
191
192 free(*ret);
193 *ret = c;
194 return 0;
195 }
196
197 if (!is_valid(p)) {
198 log_error("Entered data invalid.");
199 continue;
200 }
201
202 free(*ret);
203 *ret = p;
204 p = 0;
205 return 0;
206 }
207}
208
209static int prompt_locale(void) {
210 _cleanup_strv_free_ char **locales = NULL;
211 int r;
212
213 if (arg_locale || arg_locale_messages)
214 return 0;
215
216 if (!arg_prompt_locale)
217 return 0;
218
219 r = get_locales(&locales);
23bbb0de
MS
220 if (r < 0)
221 return log_error_errno(r, "Cannot query locales list: %m");
418b9be5
LP
222
223 print_welcome();
224
225 printf("\nAvailable Locales:\n\n");
226 r = show_menu(locales, 3, 22, 60);
227 if (r < 0)
228 return r;
229
230 putchar('\n');
231
232 r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
233 if (r < 0)
234 return r;
235
236 if (isempty(arg_locale))
237 return 0;
238
239 r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
240 if (r < 0)
241 return r;
242
243 return 0;
244}
245
246static int process_locale(void) {
247 const char *etc_localeconf;
248 char* locales[3];
249 unsigned i = 0;
250 int r;
251
1d13f648 252 etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
b3cd687d 253 if (laccess(etc_localeconf, F_OK) >= 0)
418b9be5
LP
254 return 0;
255
256 if (arg_copy_locale && arg_root) {
257
258 mkdir_parents(etc_localeconf, 0755);
1c876927 259 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, COPY_REFLINK);
418b9be5 260 if (r != -ENOENT) {
23bbb0de
MS
261 if (r < 0)
262 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
418b9be5
LP
263
264 log_info("%s copied.", etc_localeconf);
265 return 0;
266 }
267 }
268
269 r = prompt_locale();
270 if (r < 0)
271 return r;
272
273 if (!isempty(arg_locale))
63c372cb 274 locales[i++] = strjoina("LANG=", arg_locale);
418b9be5 275 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
63c372cb 276 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
418b9be5
LP
277
278 if (i == 0)
279 return 0;
280
281 locales[i] = NULL;
282
283 mkdir_parents(etc_localeconf, 0755);
284 r = write_env_file(etc_localeconf, locales);
23bbb0de
MS
285 if (r < 0)
286 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
418b9be5
LP
287
288 log_info("%s written.", etc_localeconf);
289 return 0;
290}
291
ed457f13
TB
292static int prompt_keymap(void) {
293 _cleanup_strv_free_ char **kmaps = NULL;
294 int r;
295
296 if (arg_keymap)
297 return 0;
298
299 if (!arg_prompt_keymap)
300 return 0;
301
302 r = get_keymaps(&kmaps);
303 if (r == -ENOENT) /* no keymaps installed */
304 return r;
305 if (r < 0)
306 return log_error_errno(r, "Failed to read keymaps: %m");
307
308 print_welcome();
309
310 printf("\nAvailable keymaps:\n\n");
311 r = show_menu(kmaps, 3, 22, 60);
312 if (r < 0)
313 return r;
314
315 putchar('\n');
316
8700b4da
ZJS
317 return prompt_loop("Please enter system keymap name or number",
318 kmaps, keymap_is_valid, &arg_keymap);
ed457f13
TB
319}
320
321static int process_keymap(void) {
322 const char *etc_vconsoleconf;
323 char **keymap;
324 int r;
325
326 etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
327 if (laccess(etc_vconsoleconf, F_OK) >= 0)
328 return 0;
329
330 if (arg_copy_keymap && arg_root) {
331
332 mkdir_parents(etc_vconsoleconf, 0755);
333 r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, COPY_REFLINK);
334 if (r != -ENOENT) {
335 if (r < 0)
336 return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
337
338 log_info("%s copied.", etc_vconsoleconf);
339 return 0;
340 }
341 }
342
343 r = prompt_keymap();
344 if (r == -ENOENT)
345 return 0; /* don't fail if no keymaps are installed */
346 if (r < 0)
347 return r;
348
a7353b4d 349 if (isempty(arg_keymap))
ed457f13
TB
350 return 0;
351
a7353b4d
YW
352 keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
353
354 r = mkdir_parents(etc_vconsoleconf, 0755);
355 if (r < 0)
356 return log_error_errno(r, "Failed to create the parent directory of %s: %m", etc_vconsoleconf);
357
ed457f13
TB
358 r = write_env_file(etc_vconsoleconf, keymap);
359 if (r < 0)
360 return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
361
362 log_info("%s written.", etc_vconsoleconf);
363 return 0;
364}
365
418b9be5
LP
366static int prompt_timezone(void) {
367 _cleanup_strv_free_ char **zones = NULL;
368 int r;
369
370 if (arg_timezone)
371 return 0;
372
373 if (!arg_prompt_timezone)
374 return 0;
375
376 r = get_timezones(&zones);
23bbb0de
MS
377 if (r < 0)
378 return log_error_errno(r, "Cannot query timezone list: %m");
418b9be5
LP
379
380 print_welcome();
381
382 printf("\nAvailable Time Zones:\n\n");
383 r = show_menu(zones, 3, 22, 30);
384 if (r < 0)
385 return r;
386
387 putchar('\n');
388
389 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
390 if (r < 0)
391 return r;
392
393 return 0;
394}
395
396static int process_timezone(void) {
397 const char *etc_localtime, *e;
398 int r;
399
1d13f648 400 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
b3cd687d 401 if (laccess(etc_localtime, F_OK) >= 0)
418b9be5
LP
402 return 0;
403
404 if (arg_copy_timezone && arg_root) {
405 _cleanup_free_ char *p = NULL;
406
407 r = readlink_malloc("/etc/localtime", &p);
408 if (r != -ENOENT) {
23bbb0de
MS
409 if (r < 0)
410 return log_error_errno(r, "Failed to read host timezone: %m");
418b9be5
LP
411
412 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
413 if (symlink(p, etc_localtime) < 0)
414 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
415
416 log_info("%s copied.", etc_localtime);
417 return 0;
418 }
419 }
420
421 r = prompt_timezone();
422 if (r < 0)
423 return r;
424
425 if (isempty(arg_timezone))
426 return 0;
427
63c372cb 428 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
418b9be5
LP
429
430 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
431 if (symlink(e, etc_localtime) < 0)
432 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
433
434 log_info("%s written", etc_localtime);
435 return 0;
436}
437
438static int prompt_hostname(void) {
439 int r;
440
441 if (arg_hostname)
442 return 0;
443
444 if (!arg_prompt_hostname)
445 return 0;
446
447 print_welcome();
448 putchar('\n');
449
450 for (;;) {
451 _cleanup_free_ char *h = NULL;
452
323b7dc9 453 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
23bbb0de
MS
454 if (r < 0)
455 return log_error_errno(r, "Failed to query hostname: %m");
418b9be5
LP
456
457 if (isempty(h)) {
458 log_warning("No hostname entered, skipping.");
459 break;
460 }
461
34ad6090 462 if (!hostname_is_valid(h, true)) {
418b9be5
LP
463 log_error("Specified hostname invalid.");
464 continue;
465 }
466
34ad6090 467 /* Get rid of the trailing dot that we allow, but don't want to see */
ae691c1d 468 arg_hostname = hostname_cleanup(h);
418b9be5
LP
469 h = NULL;
470 break;
471 }
472
473 return 0;
474}
475
476static int process_hostname(void) {
477 const char *etc_hostname;
478 int r;
479
1d13f648 480 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
b3cd687d 481 if (laccess(etc_hostname, F_OK) >= 0)
418b9be5
LP
482 return 0;
483
484 r = prompt_hostname();
485 if (r < 0)
486 return r;
487
488 if (isempty(arg_hostname))
489 return 0;
490
491 mkdir_parents(etc_hostname, 0755);
0675e94a
AJ
492 r = write_string_file(etc_hostname, arg_hostname,
493 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
494 if (r < 0)
495 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
418b9be5
LP
496
497 log_info("%s written.", etc_hostname);
498 return 0;
499}
500
501static int process_machine_id(void) {
502 const char *etc_machine_id;
503 char id[SD_ID128_STRING_MAX];
504 int r;
505
1d13f648 506 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
b3cd687d 507 if (laccess(etc_machine_id, F_OK) >= 0)
418b9be5
LP
508 return 0;
509
3bbaff3e 510 if (sd_id128_is_null(arg_machine_id))
418b9be5
LP
511 return 0;
512
513 mkdir_parents(etc_machine_id, 0755);
0675e94a
AJ
514 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
515 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
516 if (r < 0)
517 return log_error_errno(r, "Failed to write machine id: %m");
418b9be5
LP
518
519 log_info("%s written.", etc_machine_id);
520 return 0;
521}
522
523static int prompt_root_password(void) {
524 const char *msg1, *msg2, *etc_shadow;
525 int r;
526
527 if (arg_root_password)
528 return 0;
529
530 if (!arg_prompt_root_password)
531 return 0;
532
1d13f648 533 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 534 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
535 return 0;
536
537 print_welcome();
538 putchar('\n');
539
323b7dc9
ZJS
540 msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
541 msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: ");
418b9be5
LP
542
543 for (;;) {
ab84f5b9 544 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
418b9be5 545
e287086b 546 r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
23bbb0de
MS
547 if (r < 0)
548 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
549
550 if (isempty(a)) {
551 log_warning("No password entered, skipping.");
552 break;
553 }
554
e287086b 555 r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
ab84f5b9 556 if (r < 0)
00843602 557 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
558
559 if (!streq(a, b)) {
560 log_error("Entered passwords did not match, please try again.");
418b9be5
LP
561 continue;
562 }
563
418b9be5
LP
564 arg_root_password = a;
565 a = NULL;
566 break;
567 }
568
569 return 0;
570}
571
572static int write_root_shadow(const char *path, const struct spwd *p) {
573 _cleanup_fclose_ FILE *f = NULL;
574 assert(path);
575 assert(p);
576
3250929b
LP
577 RUN_WITH_UMASK(0777)
578 f = fopen(path, "wex");
418b9be5
LP
579 if (!f)
580 return -errno;
581
582 errno = 0;
583 if (putspent(p, f) != 0)
f5e5c28f 584 return errno > 0 ? -errno : -EIO;
418b9be5 585
0675e94a 586 return fflush_sync_and_check(f);
418b9be5
LP
587}
588
589static int process_root_password(void) {
590
591 static const char table[] =
592 "abcdefghijklmnopqrstuvwxyz"
593 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
594 "0123456789"
595 "./";
596
597 struct spwd item = {
598 .sp_namp = (char*) "root",
ad525df8
IS
599 .sp_min = -1,
600 .sp_max = -1,
601 .sp_warn = -1,
418b9be5
LP
602 .sp_inact = -1,
603 .sp_expire = -1,
604 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
605 };
45035609
LP
606
607 _cleanup_close_ int lock = -1;
418b9be5
LP
608 char salt[3+16+1+1];
609 uint8_t raw[16];
610 unsigned i;
611 char *j;
612
613 const char *etc_shadow;
614 int r;
615
1d13f648 616 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 617 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
618 return 0;
619
45035609
LP
620 mkdir_parents(etc_shadow, 0755);
621
e929bee0 622 lock = take_etc_passwd_lock(arg_root);
45035609 623 if (lock < 0)
db260eed 624 return log_error_errno(lock, "Failed to take a lock: %m");
45035609 625
418b9be5
LP
626 if (arg_copy_root_password && arg_root) {
627 struct spwd *p;
628
629 errno = 0;
630 p = getspnam("root");
631 if (p || errno != ENOENT) {
632 if (!p) {
633 if (!errno)
634 errno = EIO;
635
e1427b13 636 return log_error_errno(errno, "Failed to find shadow entry for root: %m");
418b9be5
LP
637 }
638
639 r = write_root_shadow(etc_shadow, p);
23bbb0de
MS
640 if (r < 0)
641 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
642
643 log_info("%s copied.", etc_shadow);
644 return 0;
645 }
646 }
647
648 r = prompt_root_password();
649 if (r < 0)
650 return r;
651
652 if (!arg_root_password)
653 return 0;
654
f0d09059 655 r = acquire_random_bytes(raw, 16, true);
23bbb0de
MS
656 if (r < 0)
657 return log_error_errno(r, "Failed to get salt: %m");
418b9be5
LP
658
659 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
660 assert_cc(sizeof(table) == 64 + 1);
661 j = stpcpy(salt, "$6$");
662 for (i = 0; i < 16; i++)
663 j[i] = table[raw[i] & 63];
664 j[i++] = '$';
665 j[i] = 0;
666
667 errno = 0;
668 item.sp_pwdp = crypt(arg_root_password, salt);
669 if (!item.sp_pwdp) {
670 if (!errno)
4546c341 671 errno = EINVAL;
418b9be5 672
e1427b13 673 return log_error_errno(errno, "Failed to encrypt password: %m");
418b9be5
LP
674 }
675
676 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
677
678 r = write_root_shadow(etc_shadow, &item);
23bbb0de
MS
679 if (r < 0)
680 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
681
682 log_info("%s written.", etc_shadow);
683 return 0;
684}
685
601185b4 686static void help(void) {
418b9be5
LP
687 printf("%s [OPTIONS...]\n\n"
688 "Configures basic settings of the system.\n\n"
689 " -h --help Show this help\n"
690 " --version Show package version\n"
691 " --root=PATH Operate on an alternate filesystem root\n"
692 " --locale=LOCALE Set primary locale (LANG=)\n"
693 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
ed457f13 694 " --keymap=KEYMAP Set keymap\n"
418b9be5
LP
695 " --timezone=TIMEZONE Set timezone\n"
696 " --hostname=NAME Set host name\n"
697 " --machine-ID=ID Set machine ID\n"
698 " --root-password=PASSWORD Set root password\n"
699 " --root-password-file=FILE Set root password from file\n"
700 " --prompt-locale Prompt the user for locale settings\n"
ed457f13 701 " --prompt-keymap Prompt the user for keymap settings\n"
418b9be5
LP
702 " --prompt-timezone Prompt the user for timezone\n"
703 " --prompt-hostname Prompt the user for hostname\n"
704 " --prompt-root-password Prompt the user for root password\n"
b57b0625 705 " --prompt Prompt for all of the above\n"
418b9be5 706 " --copy-locale Copy locale from host\n"
ed457f13 707 " --copy-keymap Copy keymap from host\n"
418b9be5
LP
708 " --copy-timezone Copy timezone from host\n"
709 " --copy-root-password Copy root password from host\n"
ed457f13 710 " --copy Copy locale, keymap, timezone, root password\n"
601185b4
ZJS
711 " --setup-machine-id Generate a new random machine ID\n"
712 , program_invocation_short_name);
418b9be5
LP
713}
714
715static int parse_argv(int argc, char *argv[]) {
716
717 enum {
718 ARG_VERSION = 0x100,
719 ARG_ROOT,
720 ARG_LOCALE,
721 ARG_LOCALE_MESSAGES,
ed457f13 722 ARG_KEYMAP,
418b9be5
LP
723 ARG_TIMEZONE,
724 ARG_HOSTNAME,
725 ARG_MACHINE_ID,
726 ARG_ROOT_PASSWORD,
727 ARG_ROOT_PASSWORD_FILE,
728 ARG_PROMPT,
729 ARG_PROMPT_LOCALE,
ed457f13 730 ARG_PROMPT_KEYMAP,
418b9be5
LP
731 ARG_PROMPT_TIMEZONE,
732 ARG_PROMPT_HOSTNAME,
733 ARG_PROMPT_ROOT_PASSWORD,
734 ARG_COPY,
735 ARG_COPY_LOCALE,
ed457f13 736 ARG_COPY_KEYMAP,
418b9be5
LP
737 ARG_COPY_TIMEZONE,
738 ARG_COPY_ROOT_PASSWORD,
739 ARG_SETUP_MACHINE_ID,
740 };
741
742 static const struct option options[] = {
743 { "help", no_argument, NULL, 'h' },
744 { "version", no_argument, NULL, ARG_VERSION },
745 { "root", required_argument, NULL, ARG_ROOT },
746 { "locale", required_argument, NULL, ARG_LOCALE },
747 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
ed457f13 748 { "keymap", required_argument, NULL, ARG_KEYMAP },
418b9be5
LP
749 { "timezone", required_argument, NULL, ARG_TIMEZONE },
750 { "hostname", required_argument, NULL, ARG_HOSTNAME },
751 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
752 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
753 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
754 { "prompt", no_argument, NULL, ARG_PROMPT },
755 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
ed457f13 756 { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
418b9be5
LP
757 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
758 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
759 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
760 { "copy", no_argument, NULL, ARG_COPY },
761 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
ed457f13 762 { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
418b9be5
LP
763 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
764 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
765 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
766 {}
767 };
768
769 int r, c;
770
771 assert(argc >= 0);
772 assert(argv);
773
601185b4 774 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
418b9be5
LP
775
776 switch (c) {
777
778 case 'h':
601185b4
ZJS
779 help();
780 return 0;
418b9be5
LP
781
782 case ARG_VERSION:
3f6fd1ba 783 return version();
418b9be5
LP
784
785 case ARG_ROOT:
0f03c2a4 786 r = parse_path_argument_and_warn(optarg, true, &arg_root);
0f474365 787 if (r < 0)
0f03c2a4 788 return r;
418b9be5
LP
789 break;
790
791 case ARG_LOCALE:
792 if (!locale_is_valid(optarg)) {
793 log_error("Locale %s is not valid.", optarg);
794 return -EINVAL;
795 }
796
2fc09a9c
DM
797 r = free_and_strdup(&arg_locale, optarg);
798 if (r < 0)
418b9be5
LP
799 return log_oom();
800
801 break;
802
803 case ARG_LOCALE_MESSAGES:
804 if (!locale_is_valid(optarg)) {
805 log_error("Locale %s is not valid.", optarg);
806 return -EINVAL;
807 }
808
2fc09a9c
DM
809 r = free_and_strdup(&arg_locale_messages, optarg);
810 if (r < 0)
418b9be5
LP
811 return log_oom();
812
813 break;
814
ed457f13
TB
815 case ARG_KEYMAP:
816 if (!keymap_is_valid(optarg)) {
817 log_error("Keymap %s is not valid.", optarg);
818 return -EINVAL;
819 }
820
821 r = free_and_strdup(&arg_keymap, optarg);
822 if (r < 0)
823 return log_oom();
824
825 break;
826
418b9be5
LP
827 case ARG_TIMEZONE:
828 if (!timezone_is_valid(optarg)) {
829 log_error("Timezone %s is not valid.", optarg);
830 return -EINVAL;
831 }
832
2fc09a9c
DM
833 r = free_and_strdup(&arg_timezone, optarg);
834 if (r < 0)
418b9be5
LP
835 return log_oom();
836
837 break;
838
839 case ARG_ROOT_PASSWORD:
2fc09a9c
DM
840 r = free_and_strdup(&arg_root_password, optarg);
841 if (r < 0)
418b9be5 842 return log_oom();
418b9be5
LP
843 break;
844
845 case ARG_ROOT_PASSWORD_FILE:
0da16248 846 arg_root_password = mfree(arg_root_password);
418b9be5
LP
847
848 r = read_one_line_file(optarg, &arg_root_password);
23bbb0de
MS
849 if (r < 0)
850 return log_error_errno(r, "Failed to read %s: %m", optarg);
418b9be5
LP
851
852 break;
853
854 case ARG_HOSTNAME:
34ad6090 855 if (!hostname_is_valid(optarg, true)) {
418b9be5
LP
856 log_error("Host name %s is not valid.", optarg);
857 return -EINVAL;
858 }
859
ae691c1d 860 hostname_cleanup(optarg);
2fc09a9c
DM
861 r = free_and_strdup(&arg_hostname, optarg);
862 if (r < 0)
418b9be5
LP
863 return log_oom();
864
865 break;
866
867 case ARG_MACHINE_ID:
868 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
869 log_error("Failed to parse machine id %s.", optarg);
870 return -EINVAL;
871 }
872
873 break;
874
875 case ARG_PROMPT:
ed457f13 876 arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
418b9be5
LP
877 break;
878
879 case ARG_PROMPT_LOCALE:
880 arg_prompt_locale = true;
881 break;
882
ed457f13
TB
883 case ARG_PROMPT_KEYMAP:
884 arg_prompt_keymap = true;
885 break;
886
418b9be5
LP
887 case ARG_PROMPT_TIMEZONE:
888 arg_prompt_timezone = true;
889 break;
890
891 case ARG_PROMPT_HOSTNAME:
892 arg_prompt_hostname = true;
893 break;
894
895 case ARG_PROMPT_ROOT_PASSWORD:
896 arg_prompt_root_password = true;
897 break;
898
899 case ARG_COPY:
ed457f13 900 arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true;
e926f647 901 break;
418b9be5
LP
902
903 case ARG_COPY_LOCALE:
904 arg_copy_locale = true;
905 break;
906
ed457f13
TB
907 case ARG_COPY_KEYMAP:
908 arg_copy_keymap = true;
909 break;
910
418b9be5
LP
911 case ARG_COPY_TIMEZONE:
912 arg_copy_timezone = true;
913 break;
914
915 case ARG_COPY_ROOT_PASSWORD:
916 arg_copy_root_password = true;
917 break;
918
919 case ARG_SETUP_MACHINE_ID:
920
921 r = sd_id128_randomize(&arg_machine_id);
23bbb0de
MS
922 if (r < 0)
923 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
418b9be5
LP
924
925 break;
926
927 case '?':
928 return -EINVAL;
929
930 default:
931 assert_not_reached("Unhandled option");
932 }
418b9be5
LP
933
934 return 1;
935}
936
937int main(int argc, char *argv[]) {
1d84ad94 938 bool enabled;
418b9be5
LP
939 int r;
940
941 r = parse_argv(argc, argv);
942 if (r <= 0)
943 goto finish;
944
945 log_set_target(LOG_TARGET_AUTO);
946 log_parse_environment();
947 log_open();
948
949 umask(0022);
950
1d84ad94
LP
951 r = proc_cmdline_get_bool("systemd.firstboot", &enabled);
952 if (r < 0) {
953 log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
954 goto finish;
955 }
956 if (r > 0 && !enabled) {
957 r = 0; /* disabled */
958 goto finish;
f582cbca
LP
959 }
960
418b9be5
LP
961 r = process_locale();
962 if (r < 0)
963 goto finish;
964
ed457f13
TB
965 r = process_keymap();
966 if (r < 0)
967 goto finish;
968
418b9be5
LP
969 r = process_timezone();
970 if (r < 0)
971 goto finish;
972
973 r = process_hostname();
974 if (r < 0)
975 goto finish;
976
977 r = process_machine_id();
978 if (r < 0)
979 goto finish;
980
981 r = process_root_password();
982 if (r < 0)
983 goto finish;
984
985finish:
986 free(arg_root);
987 free(arg_locale);
988 free(arg_locale_messages);
ed457f13 989 free(arg_keymap);
418b9be5
LP
990 free(arg_timezone);
991 free(arg_hostname);
1602b008 992 string_erase(arg_root_password);
418b9be5
LP
993 free(arg_root_password);
994
995 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
996}