]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
41669317 | 2 | |
41669317 | 3 | #include <errno.h> |
07630cea | 4 | #include <fcntl.h> |
a8fbdf54 | 5 | #include <limits.h> |
07630cea | 6 | #include <stdbool.h> |
41669317 | 7 | #include <sys/mount.h> |
07630cea | 8 | #include <sys/stat.h> |
41669317 | 9 | #include <unistd.h> |
41669317 | 10 | |
971ff8c7 | 11 | #include "base-filesystem.h" |
f461a28d | 12 | #include "chase.h" |
3ffd4af2 | 13 | #include "fd-util.h" |
baa6a42d | 14 | #include "initrd-util.h" |
a8fbdf54 | 15 | #include "log.h" |
f5947a5e | 16 | #include "missing_syscall.h" |
35cd0ba5 | 17 | #include "mkdir-label.h" |
e5b42203 | 18 | #include "mount-util.h" |
049af8ad | 19 | #include "mountpoint-util.h" |
07630cea LP |
20 | #include "path-util.h" |
21 | #include "rm-rf.h" | |
d054f0a4 | 22 | #include "stdio-util.h" |
07630cea | 23 | #include "string-util.h" |
e5b42203 | 24 | #include "strv.h" |
c6878637 | 25 | #include "switch-root.h" |
ee104e11 | 26 | #include "user-util.h" |
41669317 | 27 | |
e5b42203 | 28 | int switch_root(const char *new_root, |
f2c1d491 LP |
29 | const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */ |
30 | unsigned long mount_flags) { /* MS_MOVE or MS_BIND used for /proc/, /dev/, /run/, /sys/ */ | |
41669317 | 31 | |
f2c1d491 | 32 | _cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF; |
e5b42203 | 33 | _cleanup_free_ char *resolved_old_root_after = NULL; |
f2c1d491 | 34 | int r, istmp; |
e5b42203 LP |
35 | |
36 | assert(new_root); | |
f2c1d491 | 37 | assert(IN_SET(mount_flags, MS_MOVE, MS_BIND)); |
41669317 LP |
38 | |
39 | if (path_equal(new_root, "/")) | |
40 | return 0; | |
41 | ||
e5b42203 | 42 | /* Check if we shall remove the contents of the old root */ |
f2c1d491 | 43 | old_root_fd = open("/", O_DIRECTORY|O_CLOEXEC); |
a940f507 ZJS |
44 | if (old_root_fd < 0) |
45 | return log_error_errno(errno, "Failed to open root directory: %m"); | |
f2c1d491 LP |
46 | |
47 | istmp = fd_is_temporary_fs(old_root_fd); | |
48 | if (istmp < 0) | |
49 | return log_error_errno(istmp, "Failed to stat root directory: %m"); | |
50 | if (istmp > 0) | |
a940f507 | 51 | log_debug("Root directory is on tmpfs, will do cleanup later."); |
f2c1d491 LP |
52 | |
53 | new_root_fd = open(new_root, O_DIRECTORY|O_CLOEXEC); | |
54 | if (new_root_fd < 0) | |
55 | return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); | |
56 | ||
57 | if (old_root_after) { | |
58 | /* Determine where we shall place the old root after the transition */ | |
59 | r = chase(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL); | |
60 | if (r < 0) | |
61 | return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, old_root_after); | |
62 | if (r == 0) /* Doesn't exist yet. Let's create it */ | |
63 | (void) mkdir_p_label(resolved_old_root_after, 0755); | |
64 | } | |
65 | ||
66 | /* Work-around for kernel design: the kernel refuses MS_MOVE if any file systems are mounted | |
67 | * MS_SHARED. Hence remount them MS_PRIVATE here as a work-around. | |
f47fc355 LP |
68 | * |
69 | * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */ | |
70 | if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) | |
e5b42203 LP |
71 | return log_error_errno(errno, "Failed to set \"/\" mount propagation to private: %m"); |
72 | ||
5980d463 | 73 | FOREACH_STRING(path, "/sys", "/dev", "/run", "/proc") { |
e5b42203 LP |
74 | _cleanup_free_ char *chased = NULL; |
75 | ||
f461a28d | 76 | r = chase(path, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL); |
e5b42203 | 77 | if (r < 0) |
5980d463 | 78 | return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, path); |
e5b42203 LP |
79 | if (r > 0) { |
80 | /* Already exists. Let's see if it is a mount point already. */ | |
81 | r = path_is_mount_point(chased, NULL, 0); | |
82 | if (r < 0) | |
83 | return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased); | |
84 | if (r > 0) /* If it is already mounted, then do nothing */ | |
85 | continue; | |
86 | } else | |
87 | /* Doesn't exist yet? */ | |
88 | (void) mkdir_p_label(chased, 0755); | |
89 | ||
5980d463 ZJS |
90 | if (mount(path, chased, NULL, mount_flags, NULL) < 0) |
91 | return log_error_errno(errno, "Failed to mount %s to %s: %m", path, chased); | |
41669317 LP |
92 | } |
93 | ||
e5b42203 LP |
94 | /* Do not fail if base_filesystem_create() fails. Not all switch roots are like base_filesystem_create() wants |
95 | * them to look like. They might even boot, if they are RO and don't have the FS layout. Just ignore the error | |
96 | * and switch_root() nevertheless. */ | |
8aefedce | 97 | (void) base_filesystem_create_fd(new_root_fd, new_root, UID_INVALID, GID_INVALID); |
971ff8c7 | 98 | |
f2c1d491 | 99 | if (fchdir(new_root_fd) < 0) |
4a62c710 | 100 | return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); |
41669317 | 101 | |
e5b42203 LP |
102 | /* We first try a pivot_root() so that we can umount the old root dir. In many cases (i.e. where rootfs is /), |
103 | * that's not possible however, and hence we simply overmount root */ | |
f2c1d491 LP |
104 | if (resolved_old_root_after) |
105 | r = RET_NERRNO(pivot_root(".", resolved_old_root_after)); | |
106 | else { | |
107 | r = RET_NERRNO(pivot_root(".", ".")); | |
108 | if (r >= 0) { | |
109 | /* Now unmount the upper of the two stacked file systems */ | |
110 | if (umount2(".", MNT_DETACH) < 0) | |
111 | return log_error_errno(errno, "Failed to unmount the old root: %m"); | |
e5b42203 | 112 | } |
f2c1d491 LP |
113 | } |
114 | if (r < 0) { | |
115 | log_debug_errno(r, "Pivoting root file system failed, moving mounts instead: %m"); | |
891a4918 | 116 | |
f2c1d491 LP |
117 | if (mount(".", "/", NULL, MS_MOVE, NULL) < 0) |
118 | return log_error_errno(errno, "Failed to move %s to /: %m", new_root); | |
41669317 | 119 | |
f2c1d491 LP |
120 | if (chroot(".") < 0) |
121 | return log_error_errno(errno, "Failed to change root: %m"); | |
41669317 | 122 | |
f2c1d491 LP |
123 | if (chdir(".") < 0) |
124 | return log_error_errno(errno, "Failed to change directory: %m"); | |
125 | } | |
ac0930c8 | 126 | |
f2c1d491 | 127 | if (istmp) { |
41669317 LP |
128 | struct stat rb; |
129 | ||
130 | if (fstat(old_root_fd, &rb) < 0) | |
a940f507 | 131 | return log_error_errno(errno, "Failed to stat old root directory: %m"); |
f2c1d491 | 132 | |
84b4c785 LP |
133 | /* Note: the below won't operate on non-memory file systems (i.e. only on tmpfs, ramfs), and |
134 | * it will stop at mount boundaries */ | |
a940f507 | 135 | (void) rm_rf_children(TAKE_FD(old_root_fd), 0, &rb); /* takes possession of the dir fd, even on failure */ |
41669317 LP |
136 | } |
137 | ||
03e334a1 | 138 | return 0; |
41669317 | 139 | } |