Skip to content

Commit 53e1fbc

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 8c33d71 commit 53e1fbc

File tree

3 files changed

+218
-1
lines changed

3 files changed

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

0 commit comments

Comments
 (0)