2 * switchroot.c - switch to new root directory and start init.
4 * Copyright 2002-2009 Red Hat, Inc. All rights reserved.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * Peter Jones <pjones@redhat.com>
21 * Jeremy Katz <katzj@redhat.com>
23 #include <sys/mount.h>
24 #include <sys/types.h>
26 #include <sys/param.h>
42 /* find the enclosing mount point of a path, by examining the backing device
43 * of parent directories until we reach / or find a parent with a differing
45 * result must be freed.
47 static char *get_parent_mount(const char *path
)
57 warn("failed to stat %s", path
);
60 inner_dev
= sb
.st_dev
;
62 /* dirname has some annoying properties of modifying the input... */
63 strncpy(dir
, path
, PATH_MAX
);
64 dir
[PATH_MAX
- 1] = 0; /* for safety */
69 strncpy(tmp
, dir
, PATH_MAX
);
70 tmp
[PATH_MAX
- 1] = 0;
71 parent
= dirname(tmp
);
73 r
= stat(parent
, &sb
);
75 warn("failed to stat %s", parent
);
79 /* if the parent directory's device differs then we have found a mount
81 if (sb
.st_dev
!= inner_dev
)
84 strncpy(dir
, parent
, PATH_MAX
);
85 dir
[PATH_MAX
- 1] = 0;
87 /* maybe we've reached / */
93 /* remove all files/directories below dirName -- don't cross mountpoints */
94 static int recursiveRemove(char *dirName
)
101 if (!(dir
= opendir(dirName
))) {
102 warn("failed to open %s", dirName
);
108 if (fstat(dfd
, &rb
)) {
109 warn("failed to stat %s", dirName
);
117 if (!(d
= readdir(dir
))) {
119 warn("failed to read %s", dirName
);
122 break; /* end of directory */
125 if (!strcmp(d
->d_name
, ".") || !strcmp(d
->d_name
, ".."))
128 if (d
->d_type
== DT_DIR
) {
131 if (fstatat(dfd
, d
->d_name
, &sb
, AT_SYMLINK_NOFOLLOW
)) {
132 warn("failed to stat %s/%s", dirName
, d
->d_name
);
136 /* remove subdirectories if device is same as dir */
137 if (sb
.st_dev
== rb
.st_dev
) {
138 char subdir
[ strlen(dirName
) +
139 strlen(d
->d_name
) + 2 ];
141 sprintf(subdir
, "%s/%s", dirName
, d
->d_name
);
142 recursiveRemove(subdir
);
147 if (unlinkat(dfd
, d
->d_name
,
148 d
->d_type
== DT_DIR
? AT_REMOVEDIR
: 0))
149 warn("failed to unlink %s/%s", dirName
, d
->d_name
);
152 rc
= 0; /* success */
160 static int switchroot(const char *newroot
)
162 /* Don't try to unmount the old "/", there's no way to do it. */
163 const char *umounts
[] = { "/dev", "/proc", "/sys", NULL
};
165 const char *chroot_path
= NULL
;
169 for (i
= 0; umounts
[i
] != NULL
; i
++) {
170 char newmount
[PATH_MAX
];
172 snprintf(newmount
, sizeof(newmount
), "%s%s", newroot
, umounts
[i
]);
174 if (mount(umounts
[i
], newmount
, NULL
, MS_MOVE
, NULL
) < 0) {
175 warn("failed to mount moving %s to %s",
176 umounts
[i
], newmount
);
177 warnx("forcing unmount of %s", umounts
[i
]);
178 umount2(umounts
[i
], MNT_FORCE
);
182 if (chdir(newroot
)) {
183 warn("failed to change directory to %s", newroot
);
187 recursiveRemove("/");
189 newroot_mnt
= get_parent_mount(newroot
);
190 if (newroot_mnt
&& strcmp(newroot
, newroot_mnt
)) {
191 /* newroot is not a mount point, so we have to MS_MOVE the parent
192 * mount point and then chroot in to the "subroot" */
193 chroot_path
= newroot
+ strlen(newroot_mnt
);
194 newroot
= newroot_mnt
;
196 if (chdir(newroot
)) {
197 warn("failed to chdir to newroot mount %s", newroot
);
202 if (mount(newroot
, "/", NULL
, MS_MOVE
, NULL
) < 0) {
203 warn("failed to mount moving %s to /", newroot
);
208 warn("failed to change root");
213 if (chdir(chroot_path
)) {
214 warn("failed to chdir to subroot %s", chroot_path
);
219 warn("failed to change root to subroot");
231 static void usage(FILE *output
)
233 fprintf(output
, "usage: %s <newrootdir> <init> <args to init>\n",
234 program_invocation_short_name
);
235 exit(output
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
238 static void version(void)
240 fprintf(stdout
, "%s from %s\n", program_invocation_short_name
,
245 int main(int argc
, char *argv
[])
247 char *newroot
, *init
, **initargs
;
249 if (argv
[1] && (!strcmp(argv
[1], "--help") || !strcmp(argv
[1], "-h")))
251 if (argv
[1] && (!strcmp(argv
[1], "--version") || !strcmp(argv
[1], "-V")))
260 if (!*newroot
|| !*init
)
263 if (switchroot(newroot
))
264 errx(EXIT_FAILURE
, "failed. Sorry.");
266 if (access(init
, X_OK
))
267 warn("cannot access %s", init
);
269 /* get session leader */
272 /* set controlling terminal */
273 if (ioctl (0, TIOCSCTTY
, 1))
274 warn("failed to TIOCSCTTY");
276 execv(init
, initargs
);
277 err(EXIT_FAILURE
, "failed to execute %s", init
);