]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/firstboot/firstboot.c
test: skip DELEGATE test if the kernel can't do cgroupv2 (#7445)
[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
317 r = prompt_loop("Please enter system keymap name or number", kmaps, keymap_is_valid, &arg_keymap);
318 if (r < 0)
319 return r;
320
321 if (isempty(arg_keymap))
322 return 0;
323
324 return 0;
325}
326
327static int process_keymap(void) {
328 const char *etc_vconsoleconf;
329 char **keymap;
330 int r;
331
332 etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
333 if (laccess(etc_vconsoleconf, F_OK) >= 0)
334 return 0;
335
336 if (arg_copy_keymap && arg_root) {
337
338 mkdir_parents(etc_vconsoleconf, 0755);
339 r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, COPY_REFLINK);
340 if (r != -ENOENT) {
341 if (r < 0)
342 return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
343
344 log_info("%s copied.", etc_vconsoleconf);
345 return 0;
346 }
347 }
348
349 r = prompt_keymap();
350 if (r == -ENOENT)
351 return 0; /* don't fail if no keymaps are installed */
352 if (r < 0)
353 return r;
354
355 if (!isempty(arg_keymap))
356 keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
357
358 if (!keymap)
359 return 0;
360
361 mkdir_parents(etc_vconsoleconf, 0755);
362 r = write_env_file(etc_vconsoleconf, keymap);
363 if (r < 0)
364 return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
365
366 log_info("%s written.", etc_vconsoleconf);
367 return 0;
368}
369
418b9be5
LP
370static int prompt_timezone(void) {
371 _cleanup_strv_free_ char **zones = NULL;
372 int r;
373
374 if (arg_timezone)
375 return 0;
376
377 if (!arg_prompt_timezone)
378 return 0;
379
380 r = get_timezones(&zones);
23bbb0de
MS
381 if (r < 0)
382 return log_error_errno(r, "Cannot query timezone list: %m");
418b9be5
LP
383
384 print_welcome();
385
386 printf("\nAvailable Time Zones:\n\n");
387 r = show_menu(zones, 3, 22, 30);
388 if (r < 0)
389 return r;
390
391 putchar('\n');
392
393 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
394 if (r < 0)
395 return r;
396
397 return 0;
398}
399
400static int process_timezone(void) {
401 const char *etc_localtime, *e;
402 int r;
403
1d13f648 404 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
b3cd687d 405 if (laccess(etc_localtime, F_OK) >= 0)
418b9be5
LP
406 return 0;
407
408 if (arg_copy_timezone && arg_root) {
409 _cleanup_free_ char *p = NULL;
410
411 r = readlink_malloc("/etc/localtime", &p);
412 if (r != -ENOENT) {
23bbb0de
MS
413 if (r < 0)
414 return log_error_errno(r, "Failed to read host timezone: %m");
418b9be5
LP
415
416 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
417 if (symlink(p, etc_localtime) < 0)
418 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
419
420 log_info("%s copied.", etc_localtime);
421 return 0;
422 }
423 }
424
425 r = prompt_timezone();
426 if (r < 0)
427 return r;
428
429 if (isempty(arg_timezone))
430 return 0;
431
63c372cb 432 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
418b9be5
LP
433
434 mkdir_parents(etc_localtime, 0755);
4a62c710
MS
435 if (symlink(e, etc_localtime) < 0)
436 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
418b9be5
LP
437
438 log_info("%s written", etc_localtime);
439 return 0;
440}
441
442static int prompt_hostname(void) {
443 int r;
444
445 if (arg_hostname)
446 return 0;
447
448 if (!arg_prompt_hostname)
449 return 0;
450
451 print_welcome();
452 putchar('\n');
453
454 for (;;) {
455 _cleanup_free_ char *h = NULL;
456
323b7dc9 457 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
23bbb0de
MS
458 if (r < 0)
459 return log_error_errno(r, "Failed to query hostname: %m");
418b9be5
LP
460
461 if (isempty(h)) {
462 log_warning("No hostname entered, skipping.");
463 break;
464 }
465
34ad6090 466 if (!hostname_is_valid(h, true)) {
418b9be5
LP
467 log_error("Specified hostname invalid.");
468 continue;
469 }
470
34ad6090 471 /* Get rid of the trailing dot that we allow, but don't want to see */
ae691c1d 472 arg_hostname = hostname_cleanup(h);
418b9be5
LP
473 h = NULL;
474 break;
475 }
476
477 return 0;
478}
479
480static int process_hostname(void) {
481 const char *etc_hostname;
482 int r;
483
1d13f648 484 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
b3cd687d 485 if (laccess(etc_hostname, F_OK) >= 0)
418b9be5
LP
486 return 0;
487
488 r = prompt_hostname();
489 if (r < 0)
490 return r;
491
492 if (isempty(arg_hostname))
493 return 0;
494
495 mkdir_parents(etc_hostname, 0755);
0675e94a
AJ
496 r = write_string_file(etc_hostname, arg_hostname,
497 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
498 if (r < 0)
499 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
418b9be5
LP
500
501 log_info("%s written.", etc_hostname);
502 return 0;
503}
504
505static int process_machine_id(void) {
506 const char *etc_machine_id;
507 char id[SD_ID128_STRING_MAX];
508 int r;
509
1d13f648 510 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
b3cd687d 511 if (laccess(etc_machine_id, F_OK) >= 0)
418b9be5
LP
512 return 0;
513
3bbaff3e 514 if (sd_id128_is_null(arg_machine_id))
418b9be5
LP
515 return 0;
516
517 mkdir_parents(etc_machine_id, 0755);
0675e94a
AJ
518 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
519 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
23bbb0de
MS
520 if (r < 0)
521 return log_error_errno(r, "Failed to write machine id: %m");
418b9be5
LP
522
523 log_info("%s written.", etc_machine_id);
524 return 0;
525}
526
527static int prompt_root_password(void) {
528 const char *msg1, *msg2, *etc_shadow;
529 int r;
530
531 if (arg_root_password)
532 return 0;
533
534 if (!arg_prompt_root_password)
535 return 0;
536
1d13f648 537 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 538 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
539 return 0;
540
541 print_welcome();
542 putchar('\n');
543
323b7dc9
ZJS
544 msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
545 msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: ");
418b9be5
LP
546
547 for (;;) {
ab84f5b9 548 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
418b9be5 549
e287086b 550 r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
23bbb0de
MS
551 if (r < 0)
552 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
553
554 if (isempty(a)) {
555 log_warning("No password entered, skipping.");
556 break;
557 }
558
e287086b 559 r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
ab84f5b9 560 if (r < 0)
00843602 561 return log_error_errno(r, "Failed to query root password: %m");
418b9be5
LP
562
563 if (!streq(a, b)) {
564 log_error("Entered passwords did not match, please try again.");
418b9be5
LP
565 continue;
566 }
567
418b9be5
LP
568 arg_root_password = a;
569 a = NULL;
570 break;
571 }
572
573 return 0;
574}
575
576static int write_root_shadow(const char *path, const struct spwd *p) {
577 _cleanup_fclose_ FILE *f = NULL;
578 assert(path);
579 assert(p);
580
3250929b
LP
581 RUN_WITH_UMASK(0777)
582 f = fopen(path, "wex");
418b9be5
LP
583 if (!f)
584 return -errno;
585
586 errno = 0;
587 if (putspent(p, f) != 0)
f5e5c28f 588 return errno > 0 ? -errno : -EIO;
418b9be5 589
0675e94a 590 return fflush_sync_and_check(f);
418b9be5
LP
591}
592
593static int process_root_password(void) {
594
595 static const char table[] =
596 "abcdefghijklmnopqrstuvwxyz"
597 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
598 "0123456789"
599 "./";
600
601 struct spwd item = {
602 .sp_namp = (char*) "root",
ad525df8
IS
603 .sp_min = -1,
604 .sp_max = -1,
605 .sp_warn = -1,
418b9be5
LP
606 .sp_inact = -1,
607 .sp_expire = -1,
608 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
609 };
45035609
LP
610
611 _cleanup_close_ int lock = -1;
418b9be5
LP
612 char salt[3+16+1+1];
613 uint8_t raw[16];
614 unsigned i;
615 char *j;
616
617 const char *etc_shadow;
618 int r;
619
1d13f648 620 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
b3cd687d 621 if (laccess(etc_shadow, F_OK) >= 0)
418b9be5
LP
622 return 0;
623
45035609
LP
624 mkdir_parents(etc_shadow, 0755);
625
e929bee0 626 lock = take_etc_passwd_lock(arg_root);
45035609 627 if (lock < 0)
db260eed 628 return log_error_errno(lock, "Failed to take a lock: %m");
45035609 629
418b9be5
LP
630 if (arg_copy_root_password && arg_root) {
631 struct spwd *p;
632
633 errno = 0;
634 p = getspnam("root");
635 if (p || errno != ENOENT) {
636 if (!p) {
637 if (!errno)
638 errno = EIO;
639
e1427b13 640 return log_error_errno(errno, "Failed to find shadow entry for root: %m");
418b9be5
LP
641 }
642
643 r = write_root_shadow(etc_shadow, p);
23bbb0de
MS
644 if (r < 0)
645 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
646
647 log_info("%s copied.", etc_shadow);
648 return 0;
649 }
650 }
651
652 r = prompt_root_password();
653 if (r < 0)
654 return r;
655
656 if (!arg_root_password)
657 return 0;
658
f0d09059 659 r = acquire_random_bytes(raw, 16, true);
23bbb0de
MS
660 if (r < 0)
661 return log_error_errno(r, "Failed to get salt: %m");
418b9be5
LP
662
663 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
664 assert_cc(sizeof(table) == 64 + 1);
665 j = stpcpy(salt, "$6$");
666 for (i = 0; i < 16; i++)
667 j[i] = table[raw[i] & 63];
668 j[i++] = '$';
669 j[i] = 0;
670
671 errno = 0;
672 item.sp_pwdp = crypt(arg_root_password, salt);
673 if (!item.sp_pwdp) {
674 if (!errno)
4546c341 675 errno = EINVAL;
418b9be5 676
e1427b13 677 return log_error_errno(errno, "Failed to encrypt password: %m");
418b9be5
LP
678 }
679
680 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
681
682 r = write_root_shadow(etc_shadow, &item);
23bbb0de
MS
683 if (r < 0)
684 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
418b9be5
LP
685
686 log_info("%s written.", etc_shadow);
687 return 0;
688}
689
601185b4 690static void help(void) {
418b9be5
LP
691 printf("%s [OPTIONS...]\n\n"
692 "Configures basic settings of the system.\n\n"
693 " -h --help Show this help\n"
694 " --version Show package version\n"
695 " --root=PATH Operate on an alternate filesystem root\n"
696 " --locale=LOCALE Set primary locale (LANG=)\n"
697 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
ed457f13 698 " --keymap=KEYMAP Set keymap\n"
418b9be5
LP
699 " --timezone=TIMEZONE Set timezone\n"
700 " --hostname=NAME Set host name\n"
701 " --machine-ID=ID Set machine ID\n"
702 " --root-password=PASSWORD Set root password\n"
703 " --root-password-file=FILE Set root password from file\n"
704 " --prompt-locale Prompt the user for locale settings\n"
ed457f13 705 " --prompt-keymap Prompt the user for keymap settings\n"
418b9be5
LP
706 " --prompt-timezone Prompt the user for timezone\n"
707 " --prompt-hostname Prompt the user for hostname\n"
708 " --prompt-root-password Prompt the user for root password\n"
b57b0625 709 " --prompt Prompt for all of the above\n"
418b9be5 710 " --copy-locale Copy locale from host\n"
ed457f13 711 " --copy-keymap Copy keymap from host\n"
418b9be5
LP
712 " --copy-timezone Copy timezone from host\n"
713 " --copy-root-password Copy root password from host\n"
ed457f13 714 " --copy Copy locale, keymap, timezone, root password\n"
601185b4
ZJS
715 " --setup-machine-id Generate a new random machine ID\n"
716 , program_invocation_short_name);
418b9be5
LP
717}
718
719static int parse_argv(int argc, char *argv[]) {
720
721 enum {
722 ARG_VERSION = 0x100,
723 ARG_ROOT,
724 ARG_LOCALE,
725 ARG_LOCALE_MESSAGES,
ed457f13 726 ARG_KEYMAP,
418b9be5
LP
727 ARG_TIMEZONE,
728 ARG_HOSTNAME,
729 ARG_MACHINE_ID,
730 ARG_ROOT_PASSWORD,
731 ARG_ROOT_PASSWORD_FILE,
732 ARG_PROMPT,
733 ARG_PROMPT_LOCALE,
ed457f13 734 ARG_PROMPT_KEYMAP,
418b9be5
LP
735 ARG_PROMPT_TIMEZONE,
736 ARG_PROMPT_HOSTNAME,
737 ARG_PROMPT_ROOT_PASSWORD,
738 ARG_COPY,
739 ARG_COPY_LOCALE,
ed457f13 740 ARG_COPY_KEYMAP,
418b9be5
LP
741 ARG_COPY_TIMEZONE,
742 ARG_COPY_ROOT_PASSWORD,
743 ARG_SETUP_MACHINE_ID,
744 };
745
746 static const struct option options[] = {
747 { "help", no_argument, NULL, 'h' },
748 { "version", no_argument, NULL, ARG_VERSION },
749 { "root", required_argument, NULL, ARG_ROOT },
750 { "locale", required_argument, NULL, ARG_LOCALE },
751 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
ed457f13 752 { "keymap", required_argument, NULL, ARG_KEYMAP },
418b9be5
LP
753 { "timezone", required_argument, NULL, ARG_TIMEZONE },
754 { "hostname", required_argument, NULL, ARG_HOSTNAME },
755 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
756 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
757 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
758 { "prompt", no_argument, NULL, ARG_PROMPT },
759 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
ed457f13 760 { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
418b9be5
LP
761 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
762 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
763 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
764 { "copy", no_argument, NULL, ARG_COPY },
765 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
ed457f13 766 { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
418b9be5
LP
767 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
768 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
769 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
770 {}
771 };
772
773 int r, c;
774
775 assert(argc >= 0);
776 assert(argv);
777
601185b4 778 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
418b9be5
LP
779
780 switch (c) {
781
782 case 'h':
601185b4
ZJS
783 help();
784 return 0;
418b9be5
LP
785
786 case ARG_VERSION:
3f6fd1ba 787 return version();
418b9be5
LP
788
789 case ARG_ROOT:
0f03c2a4 790 r = parse_path_argument_and_warn(optarg, true, &arg_root);
0f474365 791 if (r < 0)
0f03c2a4 792 return r;
418b9be5
LP
793 break;
794
795 case ARG_LOCALE:
796 if (!locale_is_valid(optarg)) {
797 log_error("Locale %s is not valid.", optarg);
798 return -EINVAL;
799 }
800
2fc09a9c
DM
801 r = free_and_strdup(&arg_locale, optarg);
802 if (r < 0)
418b9be5
LP
803 return log_oom();
804
805 break;
806
807 case ARG_LOCALE_MESSAGES:
808 if (!locale_is_valid(optarg)) {
809 log_error("Locale %s is not valid.", optarg);
810 return -EINVAL;
811 }
812
2fc09a9c
DM
813 r = free_and_strdup(&arg_locale_messages, optarg);
814 if (r < 0)
418b9be5
LP
815 return log_oom();
816
817 break;
818
ed457f13
TB
819 case ARG_KEYMAP:
820 if (!keymap_is_valid(optarg)) {
821 log_error("Keymap %s is not valid.", optarg);
822 return -EINVAL;
823 }
824
825 r = free_and_strdup(&arg_keymap, optarg);
826 if (r < 0)
827 return log_oom();
828
829 break;
830
418b9be5
LP
831 case ARG_TIMEZONE:
832 if (!timezone_is_valid(optarg)) {
833 log_error("Timezone %s is not valid.", optarg);
834 return -EINVAL;
835 }
836
2fc09a9c
DM
837 r = free_and_strdup(&arg_timezone, optarg);
838 if (r < 0)
418b9be5
LP
839 return log_oom();
840
841 break;
842
843 case ARG_ROOT_PASSWORD:
2fc09a9c
DM
844 r = free_and_strdup(&arg_root_password, optarg);
845 if (r < 0)
418b9be5 846 return log_oom();
418b9be5
LP
847 break;
848
849 case ARG_ROOT_PASSWORD_FILE:
0da16248 850 arg_root_password = mfree(arg_root_password);
418b9be5
LP
851
852 r = read_one_line_file(optarg, &arg_root_password);
23bbb0de
MS
853 if (r < 0)
854 return log_error_errno(r, "Failed to read %s: %m", optarg);
418b9be5
LP
855
856 break;
857
858 case ARG_HOSTNAME:
34ad6090 859 if (!hostname_is_valid(optarg, true)) {
418b9be5
LP
860 log_error("Host name %s is not valid.", optarg);
861 return -EINVAL;
862 }
863
ae691c1d 864 hostname_cleanup(optarg);
2fc09a9c
DM
865 r = free_and_strdup(&arg_hostname, optarg);
866 if (r < 0)
418b9be5
LP
867 return log_oom();
868
869 break;
870
871 case ARG_MACHINE_ID:
872 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
873 log_error("Failed to parse machine id %s.", optarg);
874 return -EINVAL;
875 }
876
877 break;
878
879 case ARG_PROMPT:
ed457f13 880 arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
418b9be5
LP
881 break;
882
883 case ARG_PROMPT_LOCALE:
884 arg_prompt_locale = true;
885 break;
886
ed457f13
TB
887 case ARG_PROMPT_KEYMAP:
888 arg_prompt_keymap = true;
889 break;
890
418b9be5
LP
891 case ARG_PROMPT_TIMEZONE:
892 arg_prompt_timezone = true;
893 break;
894
895 case ARG_PROMPT_HOSTNAME:
896 arg_prompt_hostname = true;
897 break;
898
899 case ARG_PROMPT_ROOT_PASSWORD:
900 arg_prompt_root_password = true;
901 break;
902
903 case ARG_COPY:
ed457f13 904 arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true;
e926f647 905 break;
418b9be5
LP
906
907 case ARG_COPY_LOCALE:
908 arg_copy_locale = true;
909 break;
910
ed457f13
TB
911 case ARG_COPY_KEYMAP:
912 arg_copy_keymap = true;
913 break;
914
418b9be5
LP
915 case ARG_COPY_TIMEZONE:
916 arg_copy_timezone = true;
917 break;
918
919 case ARG_COPY_ROOT_PASSWORD:
920 arg_copy_root_password = true;
921 break;
922
923 case ARG_SETUP_MACHINE_ID:
924
925 r = sd_id128_randomize(&arg_machine_id);
23bbb0de
MS
926 if (r < 0)
927 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
418b9be5
LP
928
929 break;
930
931 case '?':
932 return -EINVAL;
933
934 default:
935 assert_not_reached("Unhandled option");
936 }
418b9be5
LP
937
938 return 1;
939}
940
941int main(int argc, char *argv[]) {
1d84ad94 942 bool enabled;
418b9be5
LP
943 int r;
944
945 r = parse_argv(argc, argv);
946 if (r <= 0)
947 goto finish;
948
949 log_set_target(LOG_TARGET_AUTO);
950 log_parse_environment();
951 log_open();
952
953 umask(0022);
954
1d84ad94
LP
955 r = proc_cmdline_get_bool("systemd.firstboot", &enabled);
956 if (r < 0) {
957 log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
958 goto finish;
959 }
960 if (r > 0 && !enabled) {
961 r = 0; /* disabled */
962 goto finish;
f582cbca
LP
963 }
964
418b9be5
LP
965 r = process_locale();
966 if (r < 0)
967 goto finish;
968
ed457f13
TB
969 r = process_keymap();
970 if (r < 0)
971 goto finish;
972
418b9be5
LP
973 r = process_timezone();
974 if (r < 0)
975 goto finish;
976
977 r = process_hostname();
978 if (r < 0)
979 goto finish;
980
981 r = process_machine_id();
982 if (r < 0)
983 goto finish;
984
985 r = process_root_password();
986 if (r < 0)
987 goto finish;
988
989finish:
990 free(arg_root);
991 free(arg_locale);
992 free(arg_locale_messages);
ed457f13 993 free(arg_keymap);
418b9be5
LP
994 free(arg_timezone);
995 free(arg_hostname);
1602b008 996 string_erase(arg_root_password);
418b9be5
LP
997 free(arg_root_password);
998
999 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1000}