]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <fcntl.h> | |
4 | #include <getopt.h> | |
5 | #include <unistd.h> | |
6 | ||
7 | #include "sd-bus.h" | |
8 | #include "sd-id128.h" | |
9 | ||
10 | #include "alloc-util.h" | |
11 | #include "ask-password-api.h" | |
12 | #include "build.h" | |
13 | #include "bus-error.h" | |
14 | #include "bus-locator.h" | |
15 | #include "bus-unit-util.h" | |
16 | #include "bus-util.h" | |
17 | #include "bus-wait-for-jobs.h" | |
18 | #include "chase.h" | |
19 | #include "copy.h" | |
20 | #include "creds-util.h" | |
21 | #include "dissect-image.h" | |
22 | #include "env-file.h" | |
23 | #include "errno-util.h" | |
24 | #include "fd-util.h" | |
25 | #include "fileio.h" | |
26 | #include "fs-util.h" | |
27 | #include "glyph-util.h" | |
28 | #include "hostname-util.h" | |
29 | #include "image-policy.h" | |
30 | #include "kbd-util.h" | |
31 | #include "label.h" | |
32 | #include "label-util.h" | |
33 | #include "libcrypt-util.h" | |
34 | #include "locale-setup.h" | |
35 | #include "locale-util.h" | |
36 | #include "lock-util.h" | |
37 | #include "loop-util.h" | |
38 | #include "main-func.h" | |
39 | #include "memory-util.h" | |
40 | #include "mount-util.h" | |
41 | #include "os-util.h" | |
42 | #include "parse-argument.h" | |
43 | #include "parse-util.h" | |
44 | #include "password-quality-util.h" | |
45 | #include "path-util.h" | |
46 | #include "pretty-print.h" | |
47 | #include "proc-cmdline.h" | |
48 | #include "runtime-scope.h" | |
49 | #include "smack-util.h" | |
50 | #include "stat-util.h" | |
51 | #include "string-util.h" | |
52 | #include "strv.h" | |
53 | #include "terminal-util.h" | |
54 | #include "time-util.h" | |
55 | #include "tmpfile-util-label.h" | |
56 | #include "user-util.h" | |
57 | #include "vconsole-util.h" | |
58 | ||
59 | static char *arg_root = NULL; | |
60 | static char *arg_image = NULL; | |
61 | static char *arg_locale = NULL; /* $LANG */ | |
62 | static char *arg_locale_messages = NULL; /* $LC_MESSAGES */ | |
63 | static char *arg_keymap = NULL; | |
64 | static char *arg_timezone = NULL; | |
65 | static char *arg_hostname = NULL; | |
66 | static sd_id128_t arg_machine_id = {}; | |
67 | static char *arg_root_password = NULL; | |
68 | static char *arg_root_shell = NULL; | |
69 | static char *arg_kernel_cmdline = NULL; | |
70 | static bool arg_prompt_locale = false; | |
71 | static bool arg_prompt_keymap = false; | |
72 | static bool arg_prompt_timezone = false; | |
73 | static bool arg_prompt_hostname = false; | |
74 | static bool arg_prompt_root_password = false; | |
75 | static bool arg_prompt_root_shell = false; | |
76 | static bool arg_copy_locale = false; | |
77 | static bool arg_copy_keymap = false; | |
78 | static bool arg_copy_timezone = false; | |
79 | static bool arg_copy_root_password = false; | |
80 | static bool arg_copy_root_shell = false; | |
81 | static bool arg_force = false; | |
82 | static bool arg_delete_root_password = false; | |
83 | static bool arg_root_password_is_hashed = false; | |
84 | static bool arg_welcome = true; | |
85 | static bool arg_reset = false; | |
86 | static ImagePolicy *arg_image_policy = NULL; | |
87 | ||
88 | STATIC_DESTRUCTOR_REGISTER(arg_root, freep); | |
89 | STATIC_DESTRUCTOR_REGISTER(arg_image, freep); | |
90 | STATIC_DESTRUCTOR_REGISTER(arg_locale, freep); | |
91 | STATIC_DESTRUCTOR_REGISTER(arg_locale_messages, freep); | |
92 | STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep); | |
93 | STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep); | |
94 | STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep); | |
95 | STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep); | |
96 | STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep); | |
97 | STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); | |
98 | STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); | |
99 | ||
100 | static void print_welcome(int rfd) { | |
101 | _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; | |
102 | static bool done = false; | |
103 | const char *pn, *ac; | |
104 | int r; | |
105 | ||
106 | assert(rfd >= 0); | |
107 | ||
108 | if (!arg_welcome) | |
109 | return; | |
110 | ||
111 | if (done) { | |
112 | putchar('\n'); /* Add some breathing room between multiple prompts */ | |
113 | return; | |
114 | } | |
115 | ||
116 | r = parse_os_release_at(rfd, | |
117 | "PRETTY_NAME", &pretty_name, | |
118 | "NAME", &os_name, | |
119 | "ANSI_COLOR", &ansi_color); | |
120 | if (r < 0) | |
121 | log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, | |
122 | "Failed to read os-release file, ignoring: %m"); | |
123 | ||
124 | pn = os_release_pretty_name(pretty_name, os_name); | |
125 | ac = isempty(ansi_color) ? "0" : ansi_color; | |
126 | ||
127 | (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); | |
128 | ||
129 | if (colors_enabled()) | |
130 | printf("\n" | |
131 | ANSI_HIGHLIGHT "Welcome to your new installation of " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", ac, pn); | |
132 | else | |
133 | printf("\nWelcome to your new installation of %s!\n", pn); | |
134 | ||
135 | putchar('\n'); | |
136 | if (emoji_enabled()) { | |
137 | fputs(glyph(GLYPH_SPARKLES), stdout); | |
138 | putchar(' '); | |
139 | } | |
140 | printf("Please configure your new system!\n"); | |
141 | ||
142 | any_key_to_proceed(); | |
143 | ||
144 | done = true; | |
145 | } | |
146 | ||
147 | static int get_completions( | |
148 | const char *key, | |
149 | char ***ret_list, | |
150 | void *userdata) { | |
151 | ||
152 | int r; | |
153 | ||
154 | if (!userdata) { | |
155 | *ret_list = NULL; | |
156 | return 0; | |
157 | } | |
158 | ||
159 | _cleanup_strv_free_ char **copy = strv_copy(userdata); | |
160 | if (!copy) | |
161 | return -ENOMEM; | |
162 | ||
163 | r = strv_extend(©, "list"); | |
164 | if (r < 0) | |
165 | return r; | |
166 | ||
167 | *ret_list = TAKE_PTR(copy); | |
168 | return 0; | |
169 | } | |
170 | ||
171 | static int prompt_loop( | |
172 | int rfd, | |
173 | const char *text, | |
174 | char **l, | |
175 | unsigned ellipsize_percentage, | |
176 | bool (*is_valid)(int rfd, const char *name), | |
177 | char **ret) { | |
178 | ||
179 | int r; | |
180 | ||
181 | assert(text); | |
182 | assert(is_valid); | |
183 | assert(ret); | |
184 | ||
185 | for (;;) { | |
186 | _cleanup_free_ char *p = NULL; | |
187 | ||
188 | r = ask_string_full( | |
189 | &p, | |
190 | get_completions, | |
191 | l, | |
192 | strv_isempty(l) ? "%s %s (empty to skip): " | |
193 | : "%s %s (empty to skip, \"list\" to list options): ", | |
194 | glyph(GLYPH_TRIANGULAR_BULLET), text); | |
195 | if (r < 0) | |
196 | return log_error_errno(r, "Failed to query user: %m"); | |
197 | ||
198 | if (isempty(p)) { | |
199 | log_info("No data entered, skipping."); | |
200 | return 0; | |
201 | } | |
202 | ||
203 | if (!strv_isempty(l)) { | |
204 | if (streq(p, "list")) { | |
205 | r = show_menu(l, | |
206 | /* n_columns= */ 3, | |
207 | /* column_width= */ 20, | |
208 | ellipsize_percentage, | |
209 | /* grey_prefix= */ NULL, | |
210 | /* with_numbers= */ true); | |
211 | if (r < 0) | |
212 | return log_error_errno(r, "Failed to show menu: %m"); | |
213 | ||
214 | putchar('\n'); | |
215 | continue; | |
216 | } | |
217 | ||
218 | unsigned u; | |
219 | r = safe_atou(p, &u); | |
220 | if (r >= 0) { | |
221 | if (u <= 0 || u > strv_length(l)) { | |
222 | log_error("Specified entry number out of range."); | |
223 | continue; | |
224 | } | |
225 | ||
226 | log_info("Selected '%s'.", l[u-1]); | |
227 | return free_and_strdup_warn(ret, l[u-1]); | |
228 | } | |
229 | } | |
230 | ||
231 | if (is_valid(rfd, p)) | |
232 | return free_and_replace(*ret, p); | |
233 | ||
234 | /* Be more helpful to the user, and give a hint what the user might have wanted to type. */ | |
235 | const char *best_match = strv_find_closest(l, p); | |
236 | if (best_match) | |
237 | log_error("Invalid data '%s', did you mean '%s'?", p, best_match); | |
238 | else | |
239 | log_error("Invalid data '%s'.", p); | |
240 | } | |
241 | } | |
242 | ||
243 | static int should_configure(int dir_fd, const char *filename) { | |
244 | _cleanup_fclose_ FILE *passwd = NULL, *shadow = NULL; | |
245 | int r; | |
246 | ||
247 | assert(dir_fd >= 0); | |
248 | assert(filename); | |
249 | ||
250 | if (streq(filename, "passwd") && !arg_force) | |
251 | /* We may need to do additional checks, so open the file. */ | |
252 | r = xfopenat(dir_fd, filename, "re", O_NOFOLLOW, &passwd); | |
253 | else | |
254 | r = RET_NERRNO(faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW)); | |
255 | ||
256 | if (r == -ENOENT) | |
257 | return true; /* missing */ | |
258 | if (r < 0) | |
259 | return log_error_errno(r, "Failed to access %s: %m", filename); | |
260 | if (arg_force) | |
261 | return true; /* exists, but if --force was given we should still configure the file. */ | |
262 | ||
263 | if (!passwd) | |
264 | return false; | |
265 | ||
266 | /* In case of /etc/passwd, do an additional check for the root password field. | |
267 | * We first check that passwd redirects to shadow, and then we check shadow. | |
268 | */ | |
269 | struct passwd *i; | |
270 | while ((r = fgetpwent_sane(passwd, &i)) > 0) { | |
271 | if (!streq(i->pw_name, "root")) | |
272 | continue; | |
273 | ||
274 | if (streq_ptr(i->pw_passwd, PASSWORD_SEE_SHADOW)) | |
275 | break; | |
276 | log_debug("passwd: root account with non-shadow password found, treating root as configured"); | |
277 | return false; | |
278 | } | |
279 | if (r < 0) | |
280 | return log_error_errno(r, "Failed to read %s: %m", filename); | |
281 | if (r == 0) { | |
282 | log_debug("No root account found in %s, assuming root is not configured.", filename); | |
283 | return true; | |
284 | } | |
285 | ||
286 | r = xfopenat(dir_fd, "shadow", "re", O_NOFOLLOW, &shadow); | |
287 | if (r == -ENOENT) { | |
288 | log_debug("No shadow file found, assuming root is not configured."); | |
289 | return true; /* missing */ | |
290 | } | |
291 | if (r < 0) | |
292 | return log_error_errno(r, "Failed to access shadow: %m"); | |
293 | ||
294 | struct spwd *j; | |
295 | while ((r = fgetspent_sane(shadow, &j)) > 0) { | |
296 | if (!streq(j->sp_namp, "root")) | |
297 | continue; | |
298 | ||
299 | bool unprovisioned = streq_ptr(j->sp_pwdp, PASSWORD_UNPROVISIONED); | |
300 | log_debug("Root account found, %s.", | |
301 | unprovisioned ? "with unprovisioned password, treating root as not configured" : | |
302 | "treating root as configured"); | |
303 | return unprovisioned; | |
304 | } | |
305 | if (r < 0) | |
306 | return log_error_errno(r, "Failed to read shadow: %m"); | |
307 | assert(r == 0); | |
308 | log_debug("No root account found in shadow, assuming root is not configured."); | |
309 | return true; | |
310 | } | |
311 | ||
312 | static bool locale_is_installed_bool(const char *name) { | |
313 | return locale_is_installed(name) > 0; | |
314 | } | |
315 | ||
316 | static bool locale_is_ok(int rfd, const char *name) { | |
317 | int r; | |
318 | ||
319 | assert(rfd >= 0); | |
320 | ||
321 | r = dir_fd_is_root(rfd); | |
322 | if (r < 0) | |
323 | log_debug_errno(r, "Unable to determine if operating on host root directory, assuming we are: %m"); | |
324 | ||
325 | return r != 0 ? locale_is_installed_bool(name) : locale_is_valid(name); | |
326 | } | |
327 | ||
328 | static int prompt_locale(int rfd) { | |
329 | _cleanup_strv_free_ char **locales = NULL; | |
330 | bool acquired_from_creds = false; | |
331 | int r; | |
332 | ||
333 | assert(rfd >= 0); | |
334 | ||
335 | if (arg_locale || arg_locale_messages) | |
336 | return 0; | |
337 | ||
338 | r = read_credential("firstboot.locale", (void**) &arg_locale, NULL); | |
339 | if (r < 0) | |
340 | log_debug_errno(r, "Failed to read credential firstboot.locale, ignoring: %m"); | |
341 | else | |
342 | acquired_from_creds = true; | |
343 | ||
344 | r = read_credential("firstboot.locale-messages", (void**) &arg_locale_messages, NULL); | |
345 | if (r < 0) | |
346 | log_debug_errno(r, "Failed to read credential firstboot.locale-messages, ignoring: %m"); | |
347 | else | |
348 | acquired_from_creds = true; | |
349 | ||
350 | if (acquired_from_creds) { | |
351 | log_debug("Acquired locale from credentials."); | |
352 | return 0; | |
353 | } | |
354 | ||
355 | if (!arg_prompt_locale) { | |
356 | log_debug("Prompting for locale was not requested."); | |
357 | return 0; | |
358 | } | |
359 | ||
360 | r = get_locales(&locales); | |
361 | if (r < 0) | |
362 | return log_error_errno(r, "Cannot query locales list: %m"); | |
363 | ||
364 | if (strv_isempty(locales)) | |
365 | log_debug("No locales found, skipping locale selection."); | |
366 | else if (strv_length(locales) == 1) { | |
367 | ||
368 | if (streq(locales[0], SYSTEMD_DEFAULT_LOCALE)) | |
369 | log_debug("Only installed locale is default locale anyway, not setting locale explicitly."); | |
370 | else { | |
371 | log_debug("Only a single locale available (%s), selecting it as default.", locales[0]); | |
372 | ||
373 | arg_locale = strdup(locales[0]); | |
374 | if (!arg_locale) | |
375 | return log_oom(); | |
376 | ||
377 | /* Not setting arg_locale_message here, since it defaults to LANG anyway */ | |
378 | } | |
379 | } else { | |
380 | print_welcome(rfd); | |
381 | ||
382 | r = prompt_loop(rfd, "Please enter the new system locale name or number", | |
383 | locales, 60, locale_is_ok, &arg_locale); | |
384 | if (r < 0) | |
385 | return r; | |
386 | ||
387 | if (isempty(arg_locale)) | |
388 | return 0; | |
389 | ||
390 | r = prompt_loop(rfd, "Please enter the new system message locale name or number", | |
391 | locales, 60, locale_is_ok, &arg_locale_messages); | |
392 | if (r < 0) | |
393 | return r; | |
394 | ||
395 | /* Suppress the messages setting if it's the same as the main locale anyway */ | |
396 | if (streq_ptr(arg_locale, arg_locale_messages)) | |
397 | arg_locale_messages = mfree(arg_locale_messages); | |
398 | } | |
399 | ||
400 | return 0; | |
401 | } | |
402 | ||
403 | static int process_locale(int rfd) { | |
404 | _cleanup_close_ int pfd = -EBADF; | |
405 | _cleanup_free_ char *f = NULL; | |
406 | char* locales[3]; | |
407 | unsigned i = 0; | |
408 | int r; | |
409 | ||
410 | assert(rfd >= 0); | |
411 | ||
412 | pfd = chase_and_open_parent_at(rfd, etc_locale_conf(), | |
413 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, | |
414 | &f); | |
415 | if (pfd < 0) | |
416 | return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m"); | |
417 | ||
418 | r = should_configure(pfd, f); | |
419 | if (r == 0) | |
420 | log_debug("Found /etc/locale.conf, assuming locale information has been configured."); | |
421 | if (r <= 0) | |
422 | return r; | |
423 | ||
424 | r = dir_fd_is_root(rfd); | |
425 | if (r < 0) | |
426 | return log_error_errno(r, "Failed to check if directory file descriptor is root: %m"); | |
427 | ||
428 | if (arg_copy_locale && r == 0) { | |
429 | r = copy_file_atomic_at(AT_FDCWD, etc_locale_conf(), pfd, f, 0644, COPY_REFLINK); | |
430 | if (r != -ENOENT) { | |
431 | if (r < 0) | |
432 | return log_error_errno(r, "Failed to copy host's /etc/locale.conf: %m"); | |
433 | ||
434 | log_info("Copied host's /etc/locale.conf."); | |
435 | return 0; | |
436 | } | |
437 | } | |
438 | ||
439 | r = prompt_locale(rfd); | |
440 | if (r < 0) | |
441 | return r; | |
442 | ||
443 | if (!isempty(arg_locale)) | |
444 | locales[i++] = strjoina("LANG=", arg_locale); | |
445 | if (!isempty(arg_locale_messages) && !streq_ptr(arg_locale_messages, arg_locale)) | |
446 | locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages); | |
447 | ||
448 | if (i == 0) | |
449 | return 0; | |
450 | ||
451 | locales[i] = NULL; | |
452 | ||
453 | r = write_env_file( | |
454 | pfd, | |
455 | f, | |
456 | /* headers= */ NULL, | |
457 | locales, | |
458 | WRITE_ENV_FILE_LABEL); | |
459 | if (r < 0) | |
460 | return log_error_errno(r, "Failed to write /etc/locale.conf: %m"); | |
461 | ||
462 | log_info("/etc/locale.conf written."); | |
463 | return 1; | |
464 | } | |
465 | ||
466 | static bool keymap_exists_bool(const char *name) { | |
467 | return keymap_exists(name) > 0; | |
468 | } | |
469 | ||
470 | static bool keymap_is_ok(int rfd, const char* name) { | |
471 | int r; | |
472 | ||
473 | assert(rfd >= 0); | |
474 | ||
475 | r = dir_fd_is_root(rfd); | |
476 | if (r < 0) | |
477 | log_debug_errno(r, "Unable to determine if operating on host root directory, assuming we are: %m"); | |
478 | ||
479 | return r != 0 ? keymap_exists_bool(name) : keymap_is_valid(name); | |
480 | } | |
481 | ||
482 | static int prompt_keymap(int rfd) { | |
483 | _cleanup_strv_free_ char **kmaps = NULL; | |
484 | int r; | |
485 | ||
486 | assert(rfd >= 0); | |
487 | ||
488 | if (arg_keymap) | |
489 | return 0; | |
490 | ||
491 | r = read_credential("firstboot.keymap", (void**) &arg_keymap, NULL); | |
492 | if (r < 0) | |
493 | log_debug_errno(r, "Failed to read credential firstboot.keymap, ignoring: %m"); | |
494 | else { | |
495 | log_debug("Acquired keymap from credential."); | |
496 | return 0; | |
497 | } | |
498 | ||
499 | if (!arg_prompt_keymap) { | |
500 | log_debug("Prompting for keymap was not requested."); | |
501 | return 0; | |
502 | } | |
503 | ||
504 | r = get_keymaps(&kmaps); | |
505 | if (r == -ENOENT) /* no keymaps installed */ | |
506 | return log_debug_errno(r, "No keymaps are installed."); | |
507 | if (r < 0) | |
508 | return log_error_errno(r, "Failed to read keymaps: %m"); | |
509 | ||
510 | print_welcome(rfd); | |
511 | ||
512 | return prompt_loop(rfd, "Please enter the new keymap name or number", | |
513 | kmaps, 60, keymap_is_ok, &arg_keymap); | |
514 | } | |
515 | ||
516 | static int process_keymap(int rfd) { | |
517 | _cleanup_close_ int pfd = -EBADF; | |
518 | _cleanup_free_ char *f = NULL; | |
519 | _cleanup_strv_free_ char **keymap = NULL; | |
520 | int r; | |
521 | ||
522 | assert(rfd >= 0); | |
523 | ||
524 | pfd = chase_and_open_parent_at(rfd, etc_vconsole_conf(), | |
525 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, | |
526 | &f); | |
527 | if (pfd < 0) | |
528 | return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m"); | |
529 | ||
530 | r = should_configure(pfd, f); | |
531 | if (r == 0) | |
532 | log_debug("Found /etc/vconsole.conf, assuming console has been configured."); | |
533 | if (r <= 0) | |
534 | return r; | |
535 | ||
536 | r = dir_fd_is_root(rfd); | |
537 | if (r < 0) | |
538 | return log_error_errno(r, "Failed to check if directory file descriptor is root: %m"); | |
539 | ||
540 | if (arg_copy_keymap && r == 0) { | |
541 | r = copy_file_atomic_at(AT_FDCWD, etc_vconsole_conf(), pfd, f, 0644, COPY_REFLINK); | |
542 | if (r != -ENOENT) { | |
543 | if (r < 0) | |
544 | return log_error_errno(r, "Failed to copy host's /etc/vconsole.conf: %m"); | |
545 | ||
546 | log_info("Copied host's /etc/vconsole.conf."); | |
547 | return 0; | |
548 | } | |
549 | } | |
550 | ||
551 | r = prompt_keymap(rfd); | |
552 | if (r == -ENOENT) | |
553 | return 0; /* don't fail if no keymaps are installed */ | |
554 | if (r < 0) | |
555 | return r; | |
556 | ||
557 | if (isempty(arg_keymap)) | |
558 | return 0; | |
559 | ||
560 | VCContext vc = { | |
561 | .keymap = arg_keymap, | |
562 | }; | |
563 | _cleanup_(x11_context_clear) X11Context xc = {}; | |
564 | ||
565 | r = vconsole_convert_to_x11(&vc, /* verify= */ NULL, &xc); | |
566 | if (r < 0) | |
567 | return log_error_errno(r, "Failed to convert keymap data: %m"); | |
568 | ||
569 | r = vconsole_serialize(&vc, &xc, &keymap); | |
570 | if (r < 0) | |
571 | return log_error_errno(r, "Failed to serialize keymap data: %m"); | |
572 | ||
573 | r = write_vconsole_conf(pfd, f, keymap); | |
574 | if (r < 0) | |
575 | return log_error_errno(r, "Failed to write /etc/vconsole.conf: %m"); | |
576 | ||
577 | log_info("/etc/vconsole.conf written."); | |
578 | return 1; | |
579 | } | |
580 | ||
581 | static bool timezone_is_ok(int rfd, const char *name) { | |
582 | assert(rfd >= 0); | |
583 | ||
584 | return timezone_is_valid(name, LOG_DEBUG); | |
585 | } | |
586 | ||
587 | static int prompt_timezone(int rfd) { | |
588 | _cleanup_strv_free_ char **zones = NULL; | |
589 | int r; | |
590 | ||
591 | assert(rfd >= 0); | |
592 | ||
593 | if (arg_timezone) | |
594 | return 0; | |
595 | ||
596 | r = read_credential("firstboot.timezone", (void**) &arg_timezone, NULL); | |
597 | if (r < 0) | |
598 | log_debug_errno(r, "Failed to read credential firstboot.timezone, ignoring: %m"); | |
599 | else { | |
600 | log_debug("Acquired timezone from credential."); | |
601 | return 0; | |
602 | } | |
603 | ||
604 | if (!arg_prompt_timezone) { | |
605 | log_debug("Prompting for timezone was not requested."); | |
606 | return 0; | |
607 | } | |
608 | ||
609 | r = get_timezones(&zones); | |
610 | if (r < 0) | |
611 | return log_error_errno(r, "Cannot query timezone list: %m"); | |
612 | ||
613 | print_welcome(rfd); | |
614 | ||
615 | return prompt_loop(rfd, "Please enter the new timezone name or number", | |
616 | zones, 30, timezone_is_ok, &arg_timezone); | |
617 | } | |
618 | ||
619 | static int process_timezone(int rfd) { | |
620 | _cleanup_close_ int pfd = -EBADF; | |
621 | _cleanup_free_ char *f = NULL, *relpath = NULL; | |
622 | const char *e; | |
623 | int r; | |
624 | ||
625 | assert(rfd >= 0); | |
626 | ||
627 | pfd = chase_and_open_parent_at(rfd, etc_localtime(), | |
628 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, | |
629 | &f); | |
630 | if (pfd < 0) | |
631 | return log_error_errno(pfd, "Failed to chase /etc/localtime: %m"); | |
632 | ||
633 | r = should_configure(pfd, f); | |
634 | if (r == 0) | |
635 | log_debug("Found /etc/localtime, assuming timezone has been configured."); | |
636 | if (r <= 0) | |
637 | return r; | |
638 | ||
639 | r = dir_fd_is_root(rfd); | |
640 | if (r < 0) | |
641 | return log_error_errno(r, "Failed to check if directory file descriptor is root: %m"); | |
642 | ||
643 | if (arg_copy_timezone && r == 0) { | |
644 | _cleanup_free_ char *s = NULL; | |
645 | ||
646 | r = readlink_malloc(etc_localtime(), &s); | |
647 | if (r != -ENOENT) { | |
648 | if (r < 0) | |
649 | return log_error_errno(r, "Failed to read host's /etc/localtime: %m"); | |
650 | ||
651 | r = symlinkat_atomic_full(s, pfd, f, SYMLINK_LABEL); | |
652 | if (r < 0) | |
653 | return log_error_errno(r, "Failed to create /etc/localtime symlink: %m"); | |
654 | ||
655 | log_info("Copied host's /etc/localtime."); | |
656 | return 0; | |
657 | } | |
658 | } | |
659 | ||
660 | r = prompt_timezone(rfd); | |
661 | if (r < 0) | |
662 | return r; | |
663 | ||
664 | if (isempty(arg_timezone)) | |
665 | return 0; | |
666 | ||
667 | e = strjoina("/usr/share/zoneinfo/", arg_timezone); | |
668 | r = path_make_relative_parent(etc_localtime(), e, &relpath); | |
669 | if (r < 0) | |
670 | return r; | |
671 | ||
672 | r = symlinkat_atomic_full(relpath, pfd, f, SYMLINK_LABEL); | |
673 | if (r < 0) | |
674 | return log_error_errno(r, "Failed to create /etc/localtime symlink: %m"); | |
675 | ||
676 | log_info("/etc/localtime written"); | |
677 | return 0; | |
678 | } | |
679 | ||
680 | static bool hostname_is_ok(int rfd, const char *name) { | |
681 | assert(rfd >= 0); | |
682 | ||
683 | return hostname_is_valid(name, VALID_HOSTNAME_TRAILING_DOT); | |
684 | } | |
685 | ||
686 | static int prompt_hostname(int rfd) { | |
687 | int r; | |
688 | ||
689 | assert(rfd >= 0); | |
690 | ||
691 | if (arg_hostname) | |
692 | return 0; | |
693 | ||
694 | if (!arg_prompt_hostname) { | |
695 | log_debug("Prompting for hostname was not requested."); | |
696 | return 0; | |
697 | } | |
698 | ||
699 | print_welcome(rfd); | |
700 | ||
701 | r = prompt_loop(rfd, "Please enter the new hostname", | |
702 | NULL, 0, hostname_is_ok, &arg_hostname); | |
703 | if (r < 0) | |
704 | return r; | |
705 | ||
706 | if (arg_hostname) | |
707 | hostname_cleanup(arg_hostname); | |
708 | ||
709 | return 0; | |
710 | } | |
711 | ||
712 | static int process_hostname(int rfd) { | |
713 | _cleanup_close_ int pfd = -EBADF; | |
714 | _cleanup_free_ char *f = NULL; | |
715 | int r; | |
716 | ||
717 | assert(rfd >= 0); | |
718 | ||
719 | pfd = chase_and_open_parent_at(rfd, etc_hostname(), | |
720 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN, | |
721 | &f); | |
722 | if (pfd < 0) | |
723 | return log_error_errno(pfd, "Failed to chase /etc/hostname: %m"); | |
724 | ||
725 | r = should_configure(pfd, f); | |
726 | if (r == 0) | |
727 | log_debug("Found /etc/hostname, assuming hostname has been configured."); | |
728 | if (r <= 0) | |
729 | return r; | |
730 | ||
731 | r = prompt_hostname(rfd); | |
732 | if (r < 0) | |
733 | return r; | |
734 | ||
735 | if (isempty(arg_hostname)) | |
736 | return 0; | |
737 | ||
738 | r = write_string_file_at(pfd, f, arg_hostname, | |
739 | WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); | |
740 | if (r < 0) | |
741 | return log_error_errno(r, "Failed to write /etc/hostname: %m"); | |
742 | ||
743 | log_info("/etc/hostname written."); | |
744 | return 0; | |
745 | } | |
746 | ||
747 | static int process_machine_id(int rfd) { | |
748 | _cleanup_close_ int pfd = -EBADF; | |
749 | _cleanup_free_ char *f = NULL; | |
750 | int r; | |
751 | ||
752 | assert(rfd >= 0); | |
753 | ||
754 | pfd = chase_and_open_parent_at(rfd, "/etc/machine-id", | |
755 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, | |
756 | &f); | |
757 | if (pfd < 0) | |
758 | return log_error_errno(pfd, "Failed to chase /etc/machine-id: %m"); | |
759 | ||
760 | r = should_configure(pfd, f); | |
761 | if (r == 0) | |
762 | log_debug("Found /etc/machine-id, assuming machine-id has been configured."); | |
763 | if (r <= 0) | |
764 | return r; | |
765 | ||
766 | if (sd_id128_is_null(arg_machine_id)) { | |
767 | log_debug("Initialization of machine-id was not requested, skipping."); | |
768 | return 0; | |
769 | } | |
770 | ||
771 | r = write_string_file_at(pfd, "machine-id", SD_ID128_TO_STRING(arg_machine_id), | |
772 | WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); | |
773 | if (r < 0) | |
774 | return log_error_errno(r, "Failed to write /etc/machine-id: %m"); | |
775 | ||
776 | log_info("/etc/machine-id written."); | |
777 | return 0; | |
778 | } | |
779 | ||
780 | static int prompt_root_password(int rfd) { | |
781 | const char *msg1, *msg2; | |
782 | int r; | |
783 | ||
784 | assert(rfd >= 0); | |
785 | ||
786 | if (arg_root_password) | |
787 | return 0; | |
788 | ||
789 | if (get_credential_user_password("root", &arg_root_password, &arg_root_password_is_hashed) >= 0) | |
790 | return 0; | |
791 | ||
792 | if (!arg_prompt_root_password) { | |
793 | log_debug("Prompting for root password was not requested."); | |
794 | return 0; | |
795 | } | |
796 | ||
797 | print_welcome(rfd); | |
798 | ||
799 | msg1 = strjoina(glyph(GLYPH_TRIANGULAR_BULLET), " Please enter the new root password (empty to skip):"); | |
800 | msg2 = strjoina(glyph(GLYPH_TRIANGULAR_BULLET), " Please enter the new root password again:"); | |
801 | ||
802 | suggest_passwords(); | |
803 | ||
804 | for (;;) { | |
805 | _cleanup_strv_free_erase_ char **a = NULL, **b = NULL; | |
806 | _cleanup_free_ char *error = NULL; | |
807 | ||
808 | AskPasswordRequest req = { | |
809 | .tty_fd = -EBADF, | |
810 | .message = msg1, | |
811 | .until = USEC_INFINITY, | |
812 | .hup_fd = -EBADF, | |
813 | }; | |
814 | ||
815 | r = ask_password_tty(&req, /* flags= */ 0, &a); | |
816 | if (r < 0) | |
817 | return log_error_errno(r, "Failed to query root password: %m"); | |
818 | if (strv_length(a) != 1) | |
819 | return log_error_errno(SYNTHETIC_ERRNO(EIO), | |
820 | "Received multiple passwords, where we expected one."); | |
821 | ||
822 | if (isempty(*a)) { | |
823 | log_info("No password entered, skipping."); | |
824 | break; | |
825 | } | |
826 | ||
827 | r = check_password_quality(*a, /* old */ NULL, "root", &error); | |
828 | if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) | |
829 | log_warning("Password quality check is not supported, proceeding anyway."); | |
830 | else if (r < 0) | |
831 | return log_error_errno(r, "Failed to check password quality: %m"); | |
832 | else if (r == 0) | |
833 | log_warning("Password is weak, accepting anyway: %s", error); | |
834 | ||
835 | req.message = msg2; | |
836 | ||
837 | r = ask_password_tty(&req, /* flags= */ 0, &b); | |
838 | if (r < 0) | |
839 | return log_error_errno(r, "Failed to query root password: %m"); | |
840 | if (strv_length(b) != 1) | |
841 | return log_error_errno(SYNTHETIC_ERRNO(EIO), | |
842 | "Received multiple passwords, where we expected one."); | |
843 | ||
844 | if (!streq(*a, *b)) { | |
845 | log_error("Entered passwords did not match, please try again."); | |
846 | continue; | |
847 | } | |
848 | ||
849 | arg_root_password = TAKE_PTR(*a); | |
850 | break; | |
851 | } | |
852 | ||
853 | return 0; | |
854 | } | |
855 | ||
856 | static int find_shell(int rfd, const char *path) { | |
857 | int r; | |
858 | ||
859 | assert(path); | |
860 | ||
861 | if (!valid_shell(path)) | |
862 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path); | |
863 | ||
864 | r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); | |
865 | if (r < 0) | |
866 | return log_error_errno(r, "Failed to resolve shell %s: %m", path); | |
867 | ||
868 | return 0; | |
869 | } | |
870 | ||
871 | static bool shell_is_ok(int rfd, const char *path) { | |
872 | assert(rfd >= 0); | |
873 | ||
874 | return find_shell(rfd, path) >= 0; | |
875 | } | |
876 | ||
877 | static int prompt_root_shell(int rfd) { | |
878 | int r; | |
879 | ||
880 | assert(rfd >= 0); | |
881 | ||
882 | if (arg_root_shell) | |
883 | return 0; | |
884 | ||
885 | r = read_credential("passwd.shell.root", (void**) &arg_root_shell, NULL); | |
886 | if (r < 0) | |
887 | log_debug_errno(r, "Failed to read credential passwd.shell.root, ignoring: %m"); | |
888 | else { | |
889 | log_debug("Acquired root shell from credential."); | |
890 | return 0; | |
891 | } | |
892 | ||
893 | if (!arg_prompt_root_shell) { | |
894 | log_debug("Prompting for root shell was not requested."); | |
895 | return 0; | |
896 | } | |
897 | ||
898 | print_welcome(rfd); | |
899 | ||
900 | return prompt_loop(rfd, "Please enter the new root shell", | |
901 | NULL, 0, shell_is_ok, &arg_root_shell); | |
902 | } | |
903 | ||
904 | static int write_root_passwd(int rfd, int etc_fd, const char *password, const char *shell) { | |
905 | _cleanup_fclose_ FILE *original = NULL, *passwd = NULL; | |
906 | _cleanup_(unlink_and_freep) char *passwd_tmp = NULL; | |
907 | int r; | |
908 | bool found = false; | |
909 | ||
910 | r = fopen_temporary_at_label(etc_fd, "passwd", "passwd", &passwd, &passwd_tmp); | |
911 | if (r < 0) | |
912 | return r; | |
913 | ||
914 | r = xfopenat(etc_fd, "passwd", "re", O_NOFOLLOW, &original); | |
915 | if (r < 0 && r != -ENOENT) | |
916 | return r; | |
917 | ||
918 | if (original) { | |
919 | struct passwd *i; | |
920 | ||
921 | r = copy_rights(fileno(original), fileno(passwd)); | |
922 | if (r < 0) | |
923 | return r; | |
924 | ||
925 | while ((r = fgetpwent_sane(original, &i)) > 0) { | |
926 | ||
927 | if (streq(i->pw_name, "root")) { | |
928 | if (password) | |
929 | i->pw_passwd = (char *) password; | |
930 | if (shell) | |
931 | i->pw_shell = (char *) shell; | |
932 | found = true; | |
933 | } | |
934 | ||
935 | r = putpwent_sane(i, passwd); | |
936 | if (r < 0) | |
937 | return r; | |
938 | } | |
939 | if (r < 0) | |
940 | return r; | |
941 | ||
942 | } else { | |
943 | r = fchmod(fileno(passwd), 0644); | |
944 | if (r < 0) | |
945 | return -errno; | |
946 | } | |
947 | ||
948 | if (!found) { | |
949 | struct passwd root = { | |
950 | .pw_name = (char *) "root", | |
951 | .pw_passwd = (char *) (password ?: PASSWORD_SEE_SHADOW), | |
952 | .pw_uid = 0, | |
953 | .pw_gid = 0, | |
954 | .pw_gecos = (char *) "Super User", | |
955 | .pw_dir = (char *) "/root", | |
956 | .pw_shell = (char *) (shell ?: default_root_shell_at(rfd)), | |
957 | }; | |
958 | ||
959 | if (errno != ENOENT) | |
960 | return -errno; | |
961 | ||
962 | r = putpwent_sane(&root, passwd); | |
963 | if (r < 0) | |
964 | return r; | |
965 | } | |
966 | ||
967 | r = fflush_sync_and_check(passwd); | |
968 | if (r < 0) | |
969 | return r; | |
970 | ||
971 | r = renameat_and_apply_smack_floor_label(etc_fd, passwd_tmp, etc_fd, "passwd"); | |
972 | if (r < 0) | |
973 | return r; | |
974 | ||
975 | return 0; | |
976 | } | |
977 | ||
978 | static int write_root_shadow(int etc_fd, const char *hashed_password) { | |
979 | _cleanup_fclose_ FILE *original = NULL, *shadow = NULL; | |
980 | _cleanup_(unlink_and_freep) char *shadow_tmp = NULL; | |
981 | int r; | |
982 | bool found = false; | |
983 | ||
984 | r = fopen_temporary_at_label(etc_fd, "shadow", "shadow", &shadow, &shadow_tmp); | |
985 | if (r < 0) | |
986 | return r; | |
987 | ||
988 | r = xfopenat(etc_fd, "shadow", "re", O_NOFOLLOW, &original); | |
989 | if (r < 0 && r != -ENOENT) | |
990 | return r; | |
991 | ||
992 | if (original) { | |
993 | struct spwd *i; | |
994 | ||
995 | r = copy_rights(fileno(original), fileno(shadow)); | |
996 | if (r < 0) | |
997 | return r; | |
998 | ||
999 | while ((r = fgetspent_sane(original, &i)) > 0) { | |
1000 | ||
1001 | if (streq(i->sp_namp, "root")) { | |
1002 | if (hashed_password) { | |
1003 | i->sp_pwdp = (char *) hashed_password; | |
1004 | i->sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); | |
1005 | } | |
1006 | found = true; | |
1007 | } | |
1008 | ||
1009 | r = putspent_sane(i, shadow); | |
1010 | if (r < 0) | |
1011 | return r; | |
1012 | } | |
1013 | if (r < 0) | |
1014 | return r; | |
1015 | ||
1016 | } else { | |
1017 | r = fchmod(fileno(shadow), 0000); | |
1018 | if (r < 0) | |
1019 | return -errno; | |
1020 | } | |
1021 | ||
1022 | if (!found) { | |
1023 | struct spwd root = { | |
1024 | .sp_namp = (char*) "root", | |
1025 | .sp_pwdp = (char *) (hashed_password ?: PASSWORD_LOCKED_AND_INVALID), | |
1026 | .sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY), | |
1027 | .sp_min = -1, | |
1028 | .sp_max = -1, | |
1029 | .sp_warn = -1, | |
1030 | .sp_inact = -1, | |
1031 | .sp_expire = -1, | |
1032 | .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */ | |
1033 | }; | |
1034 | ||
1035 | if (errno != ENOENT) | |
1036 | return -errno; | |
1037 | ||
1038 | r = putspent_sane(&root, shadow); | |
1039 | if (r < 0) | |
1040 | return r; | |
1041 | } | |
1042 | ||
1043 | r = fflush_sync_and_check(shadow); | |
1044 | if (r < 0) | |
1045 | return r; | |
1046 | ||
1047 | r = renameat_and_apply_smack_floor_label(etc_fd, shadow_tmp, etc_fd, "shadow"); | |
1048 | if (r < 0) | |
1049 | return r; | |
1050 | ||
1051 | return 0; | |
1052 | } | |
1053 | ||
1054 | static int process_root_account(int rfd) { | |
1055 | _cleanup_close_ int pfd = -EBADF; | |
1056 | _cleanup_(release_lock_file) LockFile lock = LOCK_FILE_INIT; | |
1057 | _cleanup_(erase_and_freep) char *_hashed_password = NULL; | |
1058 | const char *password, *hashed_password; | |
1059 | int k = 0, r; | |
1060 | ||
1061 | assert(rfd >= 0); | |
1062 | ||
1063 | pfd = chase_and_open_parent_at(rfd, "/etc/passwd", | |
1064 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, | |
1065 | NULL); | |
1066 | if (pfd < 0) | |
1067 | return log_error_errno(pfd, "Failed to chase /etc/passwd: %m"); | |
1068 | ||
1069 | /* Ensure that passwd and shadow are in the same directory and are not symlinks. */ | |
1070 | ||
1071 | FOREACH_STRING(s, "passwd", "shadow") { | |
1072 | r = verify_regular_at(pfd, s, /* follow = */ false); | |
1073 | if (r < 0 && r != -ENOENT) | |
1074 | return log_error_errno(r, "Verification of /etc/%s being regular file failed: %m", s); | |
1075 | ||
1076 | r = should_configure(pfd, s); | |
1077 | if (r < 0) | |
1078 | return r; | |
1079 | ||
1080 | k += r; | |
1081 | } | |
1082 | ||
1083 | if (k == 0) { | |
1084 | log_debug("Found /etc/passwd and /etc/shadow, assuming root account has been initialized."); | |
1085 | return 0; | |
1086 | } | |
1087 | ||
1088 | r = make_lock_file_at(pfd, ETC_PASSWD_LOCK_FILENAME, LOCK_EX, &lock); | |
1089 | if (r < 0) | |
1090 | return log_error_errno(r, "Failed to take a lock on /etc/passwd: %m"); | |
1091 | ||
1092 | k = dir_fd_is_root(rfd); | |
1093 | if (k < 0) | |
1094 | return log_error_errno(k, "Failed to check if directory file descriptor is root: %m"); | |
1095 | ||
1096 | if (arg_copy_root_shell && k == 0) { | |
1097 | _cleanup_free_ struct passwd *p = NULL; | |
1098 | ||
1099 | r = getpwnam_malloc("root", &p); | |
1100 | if (r < 0) | |
1101 | return log_error_errno(r, "Failed to find passwd entry for root: %m"); | |
1102 | ||
1103 | r = free_and_strdup(&arg_root_shell, p->pw_shell); | |
1104 | if (r < 0) | |
1105 | return log_oom(); | |
1106 | } | |
1107 | ||
1108 | r = prompt_root_shell(rfd); | |
1109 | if (r < 0) | |
1110 | return r; | |
1111 | ||
1112 | if (arg_copy_root_password && k == 0) { | |
1113 | struct spwd *p; | |
1114 | ||
1115 | errno = 0; | |
1116 | p = getspnam("root"); | |
1117 | if (!p) | |
1118 | return log_error_errno(errno_or_else(EIO), "Failed to find shadow entry for root: %m"); | |
1119 | ||
1120 | r = free_and_strdup(&arg_root_password, p->sp_pwdp); | |
1121 | if (r < 0) | |
1122 | return log_oom(); | |
1123 | ||
1124 | arg_root_password_is_hashed = true; | |
1125 | } | |
1126 | ||
1127 | r = prompt_root_password(rfd); | |
1128 | if (r < 0) | |
1129 | return r; | |
1130 | ||
1131 | if (arg_root_password && arg_root_password_is_hashed) { | |
1132 | password = PASSWORD_SEE_SHADOW; | |
1133 | hashed_password = arg_root_password; | |
1134 | } else if (arg_root_password) { | |
1135 | r = hash_password(arg_root_password, &_hashed_password); | |
1136 | if (r < 0) | |
1137 | return log_error_errno(r, "Failed to hash password: %m"); | |
1138 | ||
1139 | password = PASSWORD_SEE_SHADOW; | |
1140 | hashed_password = _hashed_password; | |
1141 | ||
1142 | } else if (arg_delete_root_password) { | |
1143 | password = PASSWORD_SEE_SHADOW; | |
1144 | hashed_password = PASSWORD_NONE; | |
1145 | } else if (!arg_root_password && arg_prompt_root_password) { | |
1146 | /* If the user was prompted, but no password was supplied, lock the account. */ | |
1147 | password = PASSWORD_SEE_SHADOW; | |
1148 | hashed_password = PASSWORD_LOCKED_AND_INVALID; | |
1149 | } else | |
1150 | /* Leave the password as is. */ | |
1151 | password = hashed_password = NULL; | |
1152 | ||
1153 | /* Don't create/modify passwd and shadow if there's nothing to do. */ | |
1154 | if (!(password || hashed_password || arg_root_shell)) { | |
1155 | log_debug("Initialization of root account was not requested, skipping."); | |
1156 | return 0; | |
1157 | } | |
1158 | ||
1159 | r = write_root_passwd(rfd, pfd, password, arg_root_shell); | |
1160 | if (r < 0) | |
1161 | return log_error_errno(r, "Failed to write /etc/passwd: %m"); | |
1162 | ||
1163 | log_info("/etc/passwd written."); | |
1164 | ||
1165 | r = write_root_shadow(pfd, hashed_password); | |
1166 | if (r < 0) | |
1167 | return log_error_errno(r, "Failed to write /etc/shadow: %m"); | |
1168 | ||
1169 | log_info("/etc/shadow written."); | |
1170 | return 0; | |
1171 | } | |
1172 | ||
1173 | static int process_kernel_cmdline(int rfd) { | |
1174 | _cleanup_close_ int pfd = -EBADF; | |
1175 | _cleanup_free_ char *f = NULL; | |
1176 | int r; | |
1177 | ||
1178 | assert(rfd >= 0); | |
1179 | ||
1180 | pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline", | |
1181 | CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, | |
1182 | &f); | |
1183 | if (pfd < 0) | |
1184 | return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m"); | |
1185 | ||
1186 | r = should_configure(pfd, f); | |
1187 | if (r == 0) | |
1188 | log_debug("Found /etc/kernel/cmdline, assuming kernel command line has been configured."); | |
1189 | if (r <= 0) | |
1190 | return r; | |
1191 | ||
1192 | if (!arg_kernel_cmdline) { | |
1193 | log_debug("Creation of /etc/kernel/cmdline was not requested, skipping."); | |
1194 | return 0; | |
1195 | } | |
1196 | ||
1197 | r = write_string_file_at(pfd, "cmdline", arg_kernel_cmdline, | |
1198 | WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); | |
1199 | if (r < 0) | |
1200 | return log_error_errno(r, "Failed to write /etc/kernel/cmdline: %m"); | |
1201 | ||
1202 | log_info("/etc/kernel/cmdline written."); | |
1203 | return 0; | |
1204 | } | |
1205 | ||
1206 | static int reset_one(int rfd, const char *path) { | |
1207 | _cleanup_close_ int pfd = -EBADF; | |
1208 | _cleanup_free_ char *f = NULL; | |
1209 | ||
1210 | assert(rfd >= 0); | |
1211 | assert(path); | |
1212 | ||
1213 | pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f); | |
1214 | if (pfd == -ENOENT) | |
1215 | return 0; | |
1216 | if (pfd < 0) | |
1217 | return log_error_errno(pfd, "Failed to resolve %s: %m", path); | |
1218 | ||
1219 | if (unlinkat(pfd, f, 0) < 0) | |
1220 | return errno == ENOENT ? 0 : log_error_errno(errno, "Failed to remove %s: %m", path); | |
1221 | ||
1222 | log_info("Removed %s", path); | |
1223 | return 0; | |
1224 | } | |
1225 | ||
1226 | static int process_reset(int rfd) { | |
1227 | int r; | |
1228 | ||
1229 | assert(rfd >= 0); | |
1230 | ||
1231 | if (!arg_reset) | |
1232 | return 0; | |
1233 | ||
1234 | FOREACH_STRING(p, | |
1235 | etc_locale_conf(), | |
1236 | etc_vconsole_conf(), | |
1237 | etc_hostname(), | |
1238 | "/etc/machine-id", | |
1239 | "/etc/kernel/cmdline", | |
1240 | etc_localtime()) { | |
1241 | r = reset_one(rfd, p); | |
1242 | if (r < 0) | |
1243 | return r; | |
1244 | } | |
1245 | ||
1246 | return 0; | |
1247 | } | |
1248 | ||
1249 | static int help(void) { | |
1250 | _cleanup_free_ char *link = NULL; | |
1251 | int r; | |
1252 | ||
1253 | r = terminal_urlify_man("systemd-firstboot", "1", &link); | |
1254 | if (r < 0) | |
1255 | return log_oom(); | |
1256 | ||
1257 | printf("%s [OPTIONS...]\n\n" | |
1258 | "Configures basic settings of the system.\n\n" | |
1259 | " -h --help Show this help\n" | |
1260 | " --version Show package version\n" | |
1261 | " --root=PATH Operate on an alternate filesystem root\n" | |
1262 | " --image=PATH Operate on disk image as filesystem root\n" | |
1263 | " --image-policy=POLICY Specify disk image dissection policy\n" | |
1264 | " --locale=LOCALE Set primary locale (LANG=)\n" | |
1265 | " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" | |
1266 | " --keymap=KEYMAP Set keymap\n" | |
1267 | " --timezone=TIMEZONE Set timezone\n" | |
1268 | " --hostname=NAME Set hostname\n" | |
1269 | " --setup-machine-id Set a random machine ID\n" | |
1270 | " --machine-id=ID Set specified machine ID\n" | |
1271 | " --root-password=PASSWORD Set root password from plaintext password\n" | |
1272 | " --root-password-file=FILE Set root password from file\n" | |
1273 | " --root-password-hashed=HASH Set root password from hashed password\n" | |
1274 | " --root-shell=SHELL Set root shell\n" | |
1275 | " --kernel-command-line=CMDLINE\n" | |
1276 | " Set kernel command line\n" | |
1277 | " --prompt-locale Prompt the user for locale settings\n" | |
1278 | " --prompt-keymap Prompt the user for keymap settings\n" | |
1279 | " --prompt-timezone Prompt the user for timezone\n" | |
1280 | " --prompt-hostname Prompt the user for hostname\n" | |
1281 | " --prompt-root-password Prompt the user for root password\n" | |
1282 | " --prompt-root-shell Prompt the user for root shell\n" | |
1283 | " --prompt Prompt for all of the above\n" | |
1284 | " --copy-locale Copy locale from host\n" | |
1285 | " --copy-keymap Copy keymap from host\n" | |
1286 | " --copy-timezone Copy timezone from host\n" | |
1287 | " --copy-root-password Copy root password from host\n" | |
1288 | " --copy-root-shell Copy root shell from host\n" | |
1289 | " --copy Copy locale, keymap, timezone, root password\n" | |
1290 | " --force Overwrite existing files\n" | |
1291 | " --delete-root-password Delete root password\n" | |
1292 | " --welcome=no Disable the welcome text\n" | |
1293 | " --reset Remove existing files\n" | |
1294 | "\nSee the %s for details.\n", | |
1295 | program_invocation_short_name, | |
1296 | link); | |
1297 | ||
1298 | return 0; | |
1299 | } | |
1300 | ||
1301 | static int parse_argv(int argc, char *argv[]) { | |
1302 | ||
1303 | enum { | |
1304 | ARG_VERSION = 0x100, | |
1305 | ARG_ROOT, | |
1306 | ARG_IMAGE, | |
1307 | ARG_IMAGE_POLICY, | |
1308 | ARG_LOCALE, | |
1309 | ARG_LOCALE_MESSAGES, | |
1310 | ARG_KEYMAP, | |
1311 | ARG_TIMEZONE, | |
1312 | ARG_HOSTNAME, | |
1313 | ARG_SETUP_MACHINE_ID, | |
1314 | ARG_MACHINE_ID, | |
1315 | ARG_ROOT_PASSWORD, | |
1316 | ARG_ROOT_PASSWORD_FILE, | |
1317 | ARG_ROOT_PASSWORD_HASHED, | |
1318 | ARG_ROOT_SHELL, | |
1319 | ARG_KERNEL_COMMAND_LINE, | |
1320 | ARG_PROMPT, | |
1321 | ARG_PROMPT_LOCALE, | |
1322 | ARG_PROMPT_KEYMAP, | |
1323 | ARG_PROMPT_TIMEZONE, | |
1324 | ARG_PROMPT_HOSTNAME, | |
1325 | ARG_PROMPT_ROOT_PASSWORD, | |
1326 | ARG_PROMPT_ROOT_SHELL, | |
1327 | ARG_COPY, | |
1328 | ARG_COPY_LOCALE, | |
1329 | ARG_COPY_KEYMAP, | |
1330 | ARG_COPY_TIMEZONE, | |
1331 | ARG_COPY_ROOT_PASSWORD, | |
1332 | ARG_COPY_ROOT_SHELL, | |
1333 | ARG_FORCE, | |
1334 | ARG_DELETE_ROOT_PASSWORD, | |
1335 | ARG_WELCOME, | |
1336 | ARG_RESET, | |
1337 | }; | |
1338 | ||
1339 | static const struct option options[] = { | |
1340 | { "help", no_argument, NULL, 'h' }, | |
1341 | { "version", no_argument, NULL, ARG_VERSION }, | |
1342 | { "root", required_argument, NULL, ARG_ROOT }, | |
1343 | { "image", required_argument, NULL, ARG_IMAGE }, | |
1344 | { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, | |
1345 | { "locale", required_argument, NULL, ARG_LOCALE }, | |
1346 | { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, | |
1347 | { "keymap", required_argument, NULL, ARG_KEYMAP }, | |
1348 | { "timezone", required_argument, NULL, ARG_TIMEZONE }, | |
1349 | { "hostname", required_argument, NULL, ARG_HOSTNAME }, | |
1350 | { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, | |
1351 | { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, | |
1352 | { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, | |
1353 | { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, | |
1354 | { "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED }, | |
1355 | { "root-shell", required_argument, NULL, ARG_ROOT_SHELL }, | |
1356 | { "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE }, | |
1357 | { "prompt", no_argument, NULL, ARG_PROMPT }, | |
1358 | { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, | |
1359 | { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP }, | |
1360 | { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, | |
1361 | { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, | |
1362 | { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, | |
1363 | { "prompt-root-shell", no_argument, NULL, ARG_PROMPT_ROOT_SHELL }, | |
1364 | { "copy", no_argument, NULL, ARG_COPY }, | |
1365 | { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, | |
1366 | { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP }, | |
1367 | { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, | |
1368 | { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, | |
1369 | { "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL }, | |
1370 | { "force", no_argument, NULL, ARG_FORCE }, | |
1371 | { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, | |
1372 | { "welcome", required_argument, NULL, ARG_WELCOME }, | |
1373 | { "reset", no_argument, NULL, ARG_RESET }, | |
1374 | {} | |
1375 | }; | |
1376 | ||
1377 | int r, c; | |
1378 | ||
1379 | assert(argc >= 0); | |
1380 | assert(argv); | |
1381 | ||
1382 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) | |
1383 | ||
1384 | switch (c) { | |
1385 | ||
1386 | case 'h': | |
1387 | return help(); | |
1388 | ||
1389 | case ARG_VERSION: | |
1390 | return version(); | |
1391 | ||
1392 | case ARG_ROOT: | |
1393 | r = parse_path_argument(optarg, true, &arg_root); | |
1394 | if (r < 0) | |
1395 | return r; | |
1396 | break; | |
1397 | ||
1398 | case ARG_IMAGE: | |
1399 | r = parse_path_argument(optarg, false, &arg_image); | |
1400 | if (r < 0) | |
1401 | return r; | |
1402 | break; | |
1403 | ||
1404 | case ARG_IMAGE_POLICY: | |
1405 | r = parse_image_policy_argument(optarg, &arg_image_policy); | |
1406 | if (r < 0) | |
1407 | return r; | |
1408 | break; | |
1409 | ||
1410 | case ARG_LOCALE: | |
1411 | r = free_and_strdup(&arg_locale, optarg); | |
1412 | if (r < 0) | |
1413 | return log_oom(); | |
1414 | ||
1415 | break; | |
1416 | ||
1417 | case ARG_LOCALE_MESSAGES: | |
1418 | r = free_and_strdup(&arg_locale_messages, optarg); | |
1419 | if (r < 0) | |
1420 | return log_oom(); | |
1421 | ||
1422 | break; | |
1423 | ||
1424 | case ARG_KEYMAP: | |
1425 | if (!keymap_is_valid(optarg)) | |
1426 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1427 | "Keymap %s is not valid.", optarg); | |
1428 | ||
1429 | r = free_and_strdup(&arg_keymap, optarg); | |
1430 | if (r < 0) | |
1431 | return log_oom(); | |
1432 | ||
1433 | break; | |
1434 | ||
1435 | case ARG_TIMEZONE: | |
1436 | if (!timezone_is_valid(optarg, LOG_ERR)) | |
1437 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1438 | "Timezone %s is not valid.", optarg); | |
1439 | ||
1440 | r = free_and_strdup(&arg_timezone, optarg); | |
1441 | if (r < 0) | |
1442 | return log_oom(); | |
1443 | ||
1444 | break; | |
1445 | ||
1446 | case ARG_ROOT_PASSWORD: | |
1447 | r = free_and_strdup(&arg_root_password, optarg); | |
1448 | if (r < 0) | |
1449 | return log_oom(); | |
1450 | ||
1451 | arg_root_password_is_hashed = false; | |
1452 | break; | |
1453 | ||
1454 | case ARG_ROOT_PASSWORD_FILE: | |
1455 | arg_root_password = mfree(arg_root_password); | |
1456 | ||
1457 | r = read_one_line_file(optarg, &arg_root_password); | |
1458 | if (r < 0) | |
1459 | return log_error_errno(r, "Failed to read %s: %m", optarg); | |
1460 | ||
1461 | arg_root_password_is_hashed = false; | |
1462 | break; | |
1463 | ||
1464 | case ARG_ROOT_PASSWORD_HASHED: | |
1465 | r = free_and_strdup(&arg_root_password, optarg); | |
1466 | if (r < 0) | |
1467 | return log_oom(); | |
1468 | ||
1469 | arg_root_password_is_hashed = true; | |
1470 | break; | |
1471 | ||
1472 | case ARG_ROOT_SHELL: | |
1473 | r = free_and_strdup(&arg_root_shell, optarg); | |
1474 | if (r < 0) | |
1475 | return log_oom(); | |
1476 | ||
1477 | break; | |
1478 | ||
1479 | case ARG_HOSTNAME: | |
1480 | if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) | |
1481 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1482 | "Host name %s is not valid.", optarg); | |
1483 | ||
1484 | r = free_and_strdup(&arg_hostname, optarg); | |
1485 | if (r < 0) | |
1486 | return log_oom(); | |
1487 | ||
1488 | hostname_cleanup(arg_hostname); | |
1489 | break; | |
1490 | ||
1491 | case ARG_SETUP_MACHINE_ID: | |
1492 | r = sd_id128_randomize(&arg_machine_id); | |
1493 | if (r < 0) | |
1494 | return log_error_errno(r, "Failed to generate randomized machine ID: %m"); | |
1495 | ||
1496 | break; | |
1497 | ||
1498 | case ARG_MACHINE_ID: | |
1499 | r = sd_id128_from_string(optarg, &arg_machine_id); | |
1500 | if (r < 0) | |
1501 | return log_error_errno(r, "Failed to parse machine id %s.", optarg); | |
1502 | ||
1503 | break; | |
1504 | ||
1505 | case ARG_KERNEL_COMMAND_LINE: | |
1506 | r = free_and_strdup(&arg_kernel_cmdline, optarg); | |
1507 | if (r < 0) | |
1508 | return log_oom(); | |
1509 | ||
1510 | break; | |
1511 | ||
1512 | case ARG_PROMPT: | |
1513 | arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = | |
1514 | arg_prompt_root_password = arg_prompt_root_shell = true; | |
1515 | break; | |
1516 | ||
1517 | case ARG_PROMPT_LOCALE: | |
1518 | arg_prompt_locale = true; | |
1519 | break; | |
1520 | ||
1521 | case ARG_PROMPT_KEYMAP: | |
1522 | arg_prompt_keymap = true; | |
1523 | break; | |
1524 | ||
1525 | case ARG_PROMPT_TIMEZONE: | |
1526 | arg_prompt_timezone = true; | |
1527 | break; | |
1528 | ||
1529 | case ARG_PROMPT_HOSTNAME: | |
1530 | arg_prompt_hostname = true; | |
1531 | break; | |
1532 | ||
1533 | case ARG_PROMPT_ROOT_PASSWORD: | |
1534 | arg_prompt_root_password = true; | |
1535 | break; | |
1536 | ||
1537 | case ARG_PROMPT_ROOT_SHELL: | |
1538 | arg_prompt_root_shell = true; | |
1539 | break; | |
1540 | ||
1541 | case ARG_COPY: | |
1542 | arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = | |
1543 | arg_copy_root_shell = true; | |
1544 | break; | |
1545 | ||
1546 | case ARG_COPY_LOCALE: | |
1547 | arg_copy_locale = true; | |
1548 | break; | |
1549 | ||
1550 | case ARG_COPY_KEYMAP: | |
1551 | arg_copy_keymap = true; | |
1552 | break; | |
1553 | ||
1554 | case ARG_COPY_TIMEZONE: | |
1555 | arg_copy_timezone = true; | |
1556 | break; | |
1557 | ||
1558 | case ARG_COPY_ROOT_PASSWORD: | |
1559 | arg_copy_root_password = true; | |
1560 | break; | |
1561 | ||
1562 | case ARG_COPY_ROOT_SHELL: | |
1563 | arg_copy_root_shell = true; | |
1564 | break; | |
1565 | ||
1566 | case ARG_FORCE: | |
1567 | arg_force = true; | |
1568 | break; | |
1569 | ||
1570 | case ARG_DELETE_ROOT_PASSWORD: | |
1571 | arg_delete_root_password = true; | |
1572 | break; | |
1573 | ||
1574 | case ARG_WELCOME: | |
1575 | r = parse_boolean(optarg); | |
1576 | if (r < 0) | |
1577 | return log_error_errno(r, "Failed to parse --welcome= argument: %s", optarg); | |
1578 | ||
1579 | arg_welcome = r; | |
1580 | break; | |
1581 | ||
1582 | case ARG_RESET: | |
1583 | arg_reset = true; | |
1584 | break; | |
1585 | ||
1586 | case '?': | |
1587 | return -EINVAL; | |
1588 | ||
1589 | default: | |
1590 | assert_not_reached(); | |
1591 | } | |
1592 | ||
1593 | if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password)) | |
1594 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1595 | "--delete-root-password cannot be combined with other root password options."); | |
1596 | ||
1597 | if (arg_image && arg_root) | |
1598 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1599 | "--root= and --image= cannot be used together."); | |
1600 | ||
1601 | if (!sd_id128_is_null(arg_machine_id) && !(arg_image || arg_root) && !arg_force) | |
1602 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1603 | "--machine-id=/--setup-machine-id only works with --root= or --image=."); | |
1604 | ||
1605 | return 1; | |
1606 | } | |
1607 | ||
1608 | static int reload_system_manager(sd_bus **bus) { | |
1609 | int r; | |
1610 | ||
1611 | assert(bus); | |
1612 | ||
1613 | if (!*bus) { | |
1614 | r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, RUNTIME_SCOPE_SYSTEM, bus); | |
1615 | if (r < 0) | |
1616 | return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL, RUNTIME_SCOPE_SYSTEM); | |
1617 | } | |
1618 | ||
1619 | r = bus_service_manager_reload(*bus); | |
1620 | if (r < 0) | |
1621 | return r; | |
1622 | ||
1623 | log_info("Requested manager reload to apply locale configuration."); | |
1624 | return 0; | |
1625 | } | |
1626 | ||
1627 | static int reload_vconsole(sd_bus **bus) { | |
1628 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
1629 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
1630 | _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; | |
1631 | const char *object; | |
1632 | int r; | |
1633 | ||
1634 | assert(bus); | |
1635 | ||
1636 | if (!*bus) { | |
1637 | r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, RUNTIME_SCOPE_SYSTEM, bus); | |
1638 | if (r < 0) | |
1639 | return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL, RUNTIME_SCOPE_SYSTEM); | |
1640 | } | |
1641 | ||
1642 | r = bus_wait_for_jobs_new(*bus, &w); | |
1643 | if (r < 0) | |
1644 | return log_error_errno(r, "Could not watch jobs: %m"); | |
1645 | ||
1646 | r = bus_call_method(*bus, bus_systemd_mgr, "RestartUnit", &error, &reply, | |
1647 | "ss", "systemd-vconsole-setup.service", "replace"); | |
1648 | if (r < 0) | |
1649 | return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); | |
1650 | ||
1651 | r = sd_bus_message_read(reply, "o", &object); | |
1652 | if (r < 0) | |
1653 | return bus_log_parse_error(r); | |
1654 | ||
1655 | r = bus_wait_for_jobs_one(w, object, BUS_WAIT_JOBS_LOG_ERROR, NULL); | |
1656 | if (r < 0) | |
1657 | return log_error_errno(r, "Failed to wait for systemd-vconsole-setup.service/restart: %m"); | |
1658 | return 0; | |
1659 | } | |
1660 | ||
1661 | static int run(int argc, char *argv[]) { | |
1662 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
1663 | _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; | |
1664 | _cleanup_(umount_and_freep) char *mounted_dir = NULL; | |
1665 | _cleanup_close_ int rfd = -EBADF; | |
1666 | int r; | |
1667 | ||
1668 | r = parse_argv(argc, argv); | |
1669 | if (r <= 0) | |
1670 | return r; | |
1671 | ||
1672 | log_setup(); | |
1673 | ||
1674 | umask(0022); | |
1675 | ||
1676 | bool offline = arg_root || arg_image; | |
1677 | ||
1678 | if (!offline) { | |
1679 | /* If we are called without --root=/--image= let's honour the systemd.firstboot kernel | |
1680 | * command line option, because we are called to provision the host with basic settings (as | |
1681 | * opposed to some other file system tree/image) */ | |
1682 | ||
1683 | bool enabled; | |
1684 | r = proc_cmdline_get_bool("systemd.firstboot", /* flags = */ 0, &enabled); | |
1685 | if (r < 0) | |
1686 | return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); | |
1687 | if (r > 0 && !enabled) { | |
1688 | log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); | |
1689 | arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = arg_prompt_root_shell = false; | |
1690 | } | |
1691 | } | |
1692 | ||
1693 | r = mac_init(); | |
1694 | if (r < 0) | |
1695 | return r; | |
1696 | ||
1697 | if (arg_image) { | |
1698 | assert(!arg_root); | |
1699 | ||
1700 | r = mount_image_privately_interactively( | |
1701 | arg_image, | |
1702 | arg_image_policy, | |
1703 | DISSECT_IMAGE_GENERIC_ROOT | | |
1704 | DISSECT_IMAGE_REQUIRE_ROOT | | |
1705 | DISSECT_IMAGE_VALIDATE_OS | | |
1706 | DISSECT_IMAGE_RELAX_VAR_CHECK | | |
1707 | DISSECT_IMAGE_FSCK | | |
1708 | DISSECT_IMAGE_GROWFS | | |
1709 | DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, | |
1710 | &mounted_dir, | |
1711 | &rfd, | |
1712 | &loop_device); | |
1713 | if (r < 0) | |
1714 | return r; | |
1715 | ||
1716 | arg_root = strdup(mounted_dir); | |
1717 | if (!arg_root) | |
1718 | return log_oom(); | |
1719 | } else { | |
1720 | rfd = open(empty_to_root(arg_root), O_DIRECTORY|O_CLOEXEC); | |
1721 | if (rfd < 0) | |
1722 | return log_error_errno(errno, "Failed to open %s: %m", empty_to_root(arg_root)); | |
1723 | } | |
1724 | ||
1725 | LOG_SET_PREFIX(arg_image ?: arg_root); | |
1726 | ||
1727 | /* We check these conditions here instead of in parse_argv() so that we can take the root directory | |
1728 | * into account. */ | |
1729 | ||
1730 | if (arg_keymap && !keymap_is_ok(rfd, arg_keymap)) | |
1731 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Keymap %s is not installed.", arg_keymap); | |
1732 | if (arg_locale && !locale_is_ok(rfd, arg_locale)) | |
1733 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale); | |
1734 | if (arg_locale_messages && !locale_is_ok(rfd, arg_locale_messages)) | |
1735 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages); | |
1736 | ||
1737 | if (arg_root_shell) { | |
1738 | r = find_shell(rfd, arg_root_shell); | |
1739 | if (r < 0) | |
1740 | return r; | |
1741 | } | |
1742 | ||
1743 | r = process_reset(rfd); | |
1744 | if (r < 0) | |
1745 | return r; | |
1746 | ||
1747 | r = process_locale(rfd); | |
1748 | if (r < 0) | |
1749 | return r; | |
1750 | if (r > 0 && !offline) | |
1751 | (void) reload_system_manager(&bus); | |
1752 | ||
1753 | r = process_keymap(rfd); | |
1754 | if (r < 0) | |
1755 | return r; | |
1756 | if (r > 0 && !offline) | |
1757 | (void) reload_vconsole(&bus); | |
1758 | ||
1759 | r = process_timezone(rfd); | |
1760 | if (r < 0) | |
1761 | return r; | |
1762 | ||
1763 | r = process_hostname(rfd); | |
1764 | if (r < 0) | |
1765 | return r; | |
1766 | ||
1767 | r = process_root_account(rfd); | |
1768 | if (r < 0) | |
1769 | return r; | |
1770 | ||
1771 | r = process_kernel_cmdline(rfd); | |
1772 | if (r < 0) | |
1773 | return r; | |
1774 | ||
1775 | r = process_machine_id(rfd); | |
1776 | if (r < 0) | |
1777 | return r; | |
1778 | ||
1779 | return 0; | |
1780 | } | |
1781 | ||
1782 | DEFINE_MAIN_FUNCTION(run); |