Skip to content

Commit 8f2b1cf

Browse files
committed
feature/runtime: Use own utility to switch root
Since it is now possible to boot via compressed image and overlayfs, support for the ability to unmount root in initramfs is now required. Th switch_root from busybox or util-linux does not allow this. Moreover, if initramfs is not on ramfs/tmpfs these utilities will not remove the content and will leave it untouched. So we need our own utility that allows us to unmount old rootfs if possible. As an added benefit, it will be possible to do away with unnecessary actions when switching rootfs. Signed-off-by: Alexey Gladkov <[email protected]>
1 parent cca9445 commit 8f2b1cf

File tree

3 files changed

+220
-1
lines changed

3 files changed

+220
-1
lines changed

features/runtime/data/etc/rc.d/rc.sysexec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
msg "INIT: Running init ($INIT)"
1010

1111
# Run system init with arguments. Goodbye!
12-
exec runas /sbin/init /bin/environ -cf /.initrd/kernenv /sbin/switch_root "$rootmnt" "$INIT" "$@"
12+
exec runas /sbin/init /bin/environ -cf /.initrd/kernenv /sbin/sysexec "$rootmnt" "$INIT" "$@"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
sysexec_DEST = $(dest_data_sbindir)/sysexec
4+
sysexec_SRCS = $(runtime_srcdir)/sysexec/sysexec.c
5+
6+
PROGS += sysexec
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
#include "config.h"
4+
5+
#include <linux/magic.h>
6+
7+
#include <sys/syscall.h>
8+
#include <sys/mount.h>
9+
#include <sys/statfs.h>
10+
#include <sys/stat.h>
11+
12+
#include <unistd.h>
13+
#include <stdlib.h>
14+
#include <stdbool.h>
15+
#include <stdio.h>
16+
#include <fcntl.h>
17+
#include <dirent.h>
18+
#include <errno.h>
19+
#include <err.h>
20+
21+
/*
22+
* Because statfs.t_type can be int on some architectures, we have to cast the
23+
* const magic to the type, otherwise the compiler warns about signed/unsigned
24+
* comparison, because the magic can be 32 bit unsigned.
25+
*/
26+
#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b)
27+
28+
#define DIR_FLAGS O_RDONLY | O_DIRECTORY | O_CLOEXEC
29+
30+
#ifndef pivot_root
31+
static inline long pivot_root(const char *new_root, const char *put_old)
32+
{
33+
return syscall(SYS_pivot_root, new_root, put_old);
34+
}
35+
#endif
36+
37+
static int is_dot_dir(struct dirent *ent)
38+
{
39+
return ((ent->d_type == DT_DIR) &&
40+
(ent->d_name[0] == '.' && ((ent->d_name[1] == '\0') ||
41+
(ent->d_name[1] == '.' && ent->d_name[2] == '\0'))));
42+
}
43+
44+
/* remove all files/directories below dirName -- don't cross mountpoints */
45+
static int remove_recursive_at(int fd)
46+
{
47+
struct stat root_sb;
48+
int rc = -1;
49+
DIR *dir = fdopendir(fd);
50+
51+
if (!dir) {
52+
warn("failed to open directory");
53+
close(fd);
54+
return -1;
55+
}
56+
57+
/* fdopendir() precludes us from continuing to use the input fd */
58+
int dir_fd = dirfd(dir);
59+
60+
if (fstat(dir_fd, &root_sb)) {
61+
warn("stat failed");
62+
goto fail;
63+
}
64+
65+
while(1) {
66+
struct dirent *d;
67+
int isdir = 0;
68+
69+
errno = 0;
70+
if (!(d = readdir(dir))) {
71+
if (errno) {
72+
warn("failed to read directory");
73+
goto fail;
74+
}
75+
break;
76+
}
77+
78+
if (is_dot_dir(d))
79+
continue;
80+
81+
if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN) {
82+
struct stat dir_sb;
83+
84+
if (fstatat(dir_fd, d->d_name, &dir_sb, AT_SYMLINK_NOFOLLOW)) {
85+
warn("stat of %s failed", d->d_name);
86+
continue;
87+
}
88+
89+
/* skip if device is not the same */
90+
if (root_sb.st_dev != dir_sb.st_dev)
91+
continue;
92+
93+
/* remove subdirectories */
94+
if (S_ISDIR(dir_sb.st_mode)) {
95+
int cfd;
96+
97+
cfd = openat(dir_fd, d->d_name, DIR_FLAGS);
98+
99+
if (cfd >= 0)
100+
remove_recursive_at(cfd);
101+
102+
isdir = 1;
103+
}
104+
}
105+
106+
if (unlinkat(dir_fd, d->d_name, isdir ? AT_REMOVEDIR : 0))
107+
warn("failed to unlink %s", d->d_name);
108+
}
109+
110+
rc = 0;
111+
fail:
112+
closedir(dir);
113+
return rc;
114+
}
115+
116+
static bool is_temporary_fs(int fd)
117+
{
118+
struct statfs stfs;
119+
120+
if (fstatfs(fd, &stfs)) {
121+
warn("stat failed");
122+
return 0;
123+
}
124+
return (F_TYPE_EQUAL(stfs.f_type, RAMFS_MAGIC) ||
125+
F_TYPE_EQUAL(stfs.f_type, TMPFS_MAGIC));
126+
}
127+
128+
static void usage(int retcode)
129+
{
130+
printf("Usage: %s <newrootdir> <init> [<args ...>]\n", program_invocation_short_name);
131+
exit(retcode);
132+
}
133+
134+
int main(int argc, char *argv[])
135+
{
136+
char *newroot, *initprog, **initargs;
137+
138+
if (argc == 1) {
139+
usage(EXIT_SUCCESS);
140+
}
141+
142+
if (argc < 3) {
143+
warnx("not enough arguments");
144+
usage(EXIT_FAILURE);
145+
}
146+
147+
newroot = argv[1];
148+
initprog = argv[2];
149+
initargs = &argv[2];
150+
151+
if (!*newroot || !*initprog) {
152+
warnx("bad usage");
153+
usage(EXIT_FAILURE);
154+
}
155+
156+
int old_root_fd = open("/", DIR_FLAGS);
157+
158+
if (old_root_fd < 0)
159+
err(EXIT_FAILURE, "failed to open current root directory");
160+
161+
int new_root_fd = open(newroot, DIR_FLAGS);
162+
163+
if (new_root_fd < 0)
164+
err(EXIT_FAILURE, "failed to open new root directory: %s", newroot);
165+
166+
/*
167+
* Work-around for kernel design: the kernel refuses MS_MOVE if any file
168+
* systems are mounted MS_SHARED. Hence remount them MS_PRIVATE here as
169+
* a work-around.
170+
*
171+
* https://bugzilla.redhat.com/show_bug.cgi?id=847418
172+
*/
173+
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0)
174+
err(EXIT_FAILURE, "failed to set / mount propagation to private");
175+
176+
/*
177+
* Note: implementation details of pivot_root may change with
178+
* time. In order to ensure compatibility, the following points
179+
* should be observed:
180+
*
181+
* - before calling pivot_root, the current directory of the
182+
* invoking process should point to the new root directory
183+
* - use . as the first argument, and the _relative_ path of the
184+
* directory for the old root as the second argument
185+
* - a chroot program must be available under the old and the
186+
* new root
187+
* - chroot to the new root afterwards
188+
* - use relative paths for dev/console in the exec command
189+
*
190+
* Documentation/admin-guide/initrd.rst#n251
191+
*/
192+
if (fchdir(new_root_fd))
193+
err(EXIT_FAILURE, "failed to change directory to %s", newroot);
194+
195+
long ret = pivot_root(".", ".");
196+
if (ret >= 0) {
197+
/* unmount the upper of the two stacked file systems */
198+
if (umount2(".", MNT_DETACH))
199+
err(EXIT_FAILURE, "failed to unmount the old root");
200+
201+
} else if (mount(".", "/", NULL, MS_MOVE, NULL)) {
202+
err(EXIT_FAILURE, "failed to move new root to /");
203+
}
204+
205+
if (chroot(".") || chdir("/"))
206+
err(EXIT_FAILURE, "failed to chroot");
207+
208+
if (is_temporary_fs(old_root_fd))
209+
remove_recursive_at(old_root_fd);
210+
211+
execvp(initprog, initargs);
212+
err(EXIT_FAILURE, "failed to execute %s", initprog);
213+
}

0 commit comments

Comments
 (0)