]>
Commit | Line | Data |
---|---|---|
711ea730 KZ |
1 | /* |
2 | * switchroot.c - switch to new root directory and start init. | |
3 | * | |
8f24e52e | 4 | * Copyright 2002-2009 Red Hat, Inc. All rights reserved. |
711ea730 KZ |
5 | * |
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. | |
10 | * | |
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. | |
15 | * | |
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/>. | |
18 | * | |
19 | * Authors: | |
20 | * Peter Jones <pjones@redhat.com> | |
21 | * Jeremy Katz <katzj@redhat.com> | |
22 | */ | |
711ea730 KZ |
23 | #include <sys/mount.h> |
24 | #include <sys/types.h> | |
25 | #include <sys/stat.h> | |
c7832fb8 | 26 | #include <sys/statfs.h> |
711ea730 KZ |
27 | #include <sys/param.h> |
28 | #include <fcntl.h> | |
29 | #include <stdio.h> | |
30 | #include <stdlib.h> | |
31 | #include <unistd.h> | |
32 | #include <string.h> | |
33 | #include <errno.h> | |
34 | #include <ctype.h> | |
35 | #include <dirent.h> | |
105bb857 | 36 | #include <getopt.h> |
ed8d2938 | 37 | |
eb76ca98 | 38 | #include "c.h" |
ed8d2938 | 39 | #include "nls.h" |
efb8854f | 40 | #include "closestream.h" |
c7832fb8 | 41 | #include "statfs_magic.h" |
711ea730 KZ |
42 | |
43 | #ifndef MS_MOVE | |
44 | #define MS_MOVE 8192 | |
45 | #endif | |
46 | ||
944de78b HH |
47 | #ifndef MNT_DETACH |
48 | #define MNT_DETACH 0x00000002 /* Just detach from the tree */ | |
49 | #endif | |
50 | ||
711ea730 | 51 | /* remove all files/directories below dirName -- don't cross mountpoints */ |
82476a90 | 52 | static int recursiveRemove(int fd) |
3ddbe4d2 | 53 | { |
a6fc8b07 KZ |
54 | struct stat rb; |
55 | DIR *dir; | |
56 | int rc = -1; | |
57 | int dfd; | |
3ddbe4d2 | 58 | |
82476a90 | 59 | if (!(dir = fdopendir(fd))) { |
0fbd4c85 | 60 | warn(_("failed to open directory")); |
a6fc8b07 | 61 | goto done; |
3ddbe4d2 KZ |
62 | } |
63 | ||
82476a90 | 64 | /* fdopendir() precludes us from continuing to use the input fd */ |
a6fc8b07 | 65 | dfd = dirfd(dir); |
a6fc8b07 | 66 | if (fstat(dfd, &rb)) { |
add1b8af | 67 | warn(_("stat failed")); |
a6fc8b07 | 68 | goto done; |
3ddbe4d2 KZ |
69 | } |
70 | ||
a6fc8b07 KZ |
71 | while(1) { |
72 | struct dirent *d; | |
7ad19a3f | 73 | int isdir = 0; |
3ddbe4d2 | 74 | |
3ddbe4d2 | 75 | errno = 0; |
a6fc8b07 KZ |
76 | if (!(d = readdir(dir))) { |
77 | if (errno) { | |
0fbd4c85 | 78 | warn(_("failed to read directory")); |
a6fc8b07 KZ |
79 | goto done; |
80 | } | |
81 | break; /* end of directory */ | |
82 | } | |
3ddbe4d2 | 83 | |
a6fc8b07 | 84 | if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) |
3ddbe4d2 | 85 | continue; |
7ad19a3f KZ |
86 | #ifdef _DIRENT_HAVE_D_TYPE |
87 | if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN) | |
88 | #endif | |
89 | { | |
a6fc8b07 | 90 | struct stat sb; |
3ddbe4d2 | 91 | |
a6fc8b07 | 92 | if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) { |
fc14ceba | 93 | warn(_("stat of %s failed"), d->d_name); |
a6fc8b07 KZ |
94 | continue; |
95 | } | |
3ddbe4d2 | 96 | |
85bfb519 PS |
97 | /* skip if device is not the same */ |
98 | if (sb.st_dev != rb.st_dev) | |
99 | continue; | |
100 | ||
101 | /* remove subdirectories */ | |
102 | if (S_ISDIR(sb.st_mode)) { | |
82476a90 | 103 | int cfd; |
a6fc8b07 | 104 | |
82476a90 | 105 | cfd = openat(dfd, d->d_name, O_RDONLY); |
d3e58a8e KZ |
106 | if (cfd >= 0) |
107 | recursiveRemove(cfd); /* it closes cfd too */ | |
7ad19a3f | 108 | isdir = 1; |
85bfb519 | 109 | } |
3ddbe4d2 | 110 | } |
3ddbe4d2 | 111 | |
7ad19a3f | 112 | if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0)) |
0fbd4c85 | 113 | warn(_("failed to unlink %s"), d->d_name); |
3ddbe4d2 KZ |
114 | } |
115 | ||
a6fc8b07 | 116 | rc = 0; /* success */ |
a6fc8b07 KZ |
117 | done: |
118 | if (dir) | |
119 | closedir(dir); | |
d3e58a8e KZ |
120 | else |
121 | close(fd); | |
a6fc8b07 | 122 | return rc; |
3ddbe4d2 | 123 | } |
711ea730 KZ |
124 | |
125 | static int switchroot(const char *newroot) | |
126 | { | |
127 | /* Don't try to unmount the old "/", there's no way to do it. */ | |
acb03ad4 | 128 | const char *umounts[] = { "/dev", "/proc", "/sys", "/run", NULL }; |
711ea730 | 129 | int i; |
d3e58a8e | 130 | int cfd = -1; |
037c7816 TD |
131 | struct stat newroot_stat, oldroot_stat, sb; |
132 | ||
133 | if (stat("/", &oldroot_stat) != 0) { | |
134 | warn(_("stat of %s failed"), "/"); | |
135 | return -1; | |
136 | } | |
acb03ad4 HH |
137 | |
138 | if (stat(newroot, &newroot_stat) != 0) { | |
fc14ceba | 139 | warn(_("stat of %s failed"), newroot); |
acb03ad4 HH |
140 | return -1; |
141 | } | |
711ea730 KZ |
142 | |
143 | for (i = 0; umounts[i] != NULL; i++) { | |
144 | char newmount[PATH_MAX]; | |
8f24e52e KZ |
145 | |
146 | snprintf(newmount, sizeof(newmount), "%s%s", newroot, umounts[i]); | |
147 | ||
037c7816 TD |
148 | if ((stat(umounts[i], &sb) == 0) && sb.st_dev == oldroot_stat.st_dev) { |
149 | /* mount point to move seems to be a normal directory or stat failed */ | |
150 | continue; | |
151 | } | |
152 | ||
acb03ad4 HH |
153 | if ((stat(newmount, &sb) != 0) || (sb.st_dev != newroot_stat.st_dev)) { |
154 | /* mount point seems to be mounted already or stat failed */ | |
944de78b | 155 | umount2(umounts[i], MNT_DETACH); |
acb03ad4 HH |
156 | continue; |
157 | } | |
158 | ||
711ea730 | 159 | if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) { |
0fbd4c85 | 160 | warn(_("failed to mount moving %s to %s"), |
711ea730 | 161 | umounts[i], newmount); |
0fbd4c85 | 162 | warnx(_("forcing unmount of %s"), umounts[i]); |
711ea730 KZ |
163 | umount2(umounts[i], MNT_FORCE); |
164 | } | |
165 | } | |
166 | ||
fcb495b1 | 167 | if (chdir(newroot)) { |
0fbd4c85 | 168 | warn(_("failed to change directory to %s"), newroot); |
558417e8 | 169 | return -1; |
711ea730 | 170 | } |
fcb495b1 | 171 | |
82476a90 | 172 | cfd = open("/", O_RDONLY); |
17df84df KZ |
173 | if (cfd < 0) { |
174 | warn(_("cannot open %s"), "/"); | |
d3e58a8e | 175 | goto fail; |
17df84df | 176 | } |
fcb495b1 | 177 | |
711ea730 | 178 | if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) { |
0fbd4c85 | 179 | warn(_("failed to mount moving %s to /"), newroot); |
d3e58a8e | 180 | goto fail; |
711ea730 KZ |
181 | } |
182 | ||
183 | if (chroot(".")) { | |
0fbd4c85 | 184 | warn(_("failed to change root")); |
d3e58a8e | 185 | goto fail; |
711ea730 | 186 | } |
2a7ccc65 | 187 | |
12558a4c | 188 | if (chdir("/")) { |
12558a4c | 189 | warn(_("cannot change directory to %s"), "/"); |
d3e58a8e | 190 | goto fail; |
12558a4c SK |
191 | } |
192 | ||
d3e58a8e KZ |
193 | switch (fork()) { |
194 | case 0: /* child */ | |
195 | { | |
341154da SK |
196 | struct statfs stfs; |
197 | ||
198 | if (fstatfs(cfd, &stfs) == 0 && | |
199 | (F_TYPE_EQUAL(stfs.f_type, STATFS_RAMFS_MAGIC) || | |
200 | F_TYPE_EQUAL(stfs.f_type, STATFS_TMPFS_MAGIC))) | |
201 | recursiveRemove(cfd); | |
d3e58a8e | 202 | else { |
341154da | 203 | warn(_("old root filesystem is not an initramfs")); |
d3e58a8e KZ |
204 | close(cfd); |
205 | } | |
206 | exit(EXIT_SUCCESS); | |
2a7ccc65 | 207 | } |
d3e58a8e KZ |
208 | case -1: /* error */ |
209 | break; | |
341154da | 210 | |
d3e58a8e KZ |
211 | default: /* parent */ |
212 | close(cfd); | |
213 | return 0; | |
214 | } | |
215 | ||
216 | fail: | |
217 | if (cfd >= 0) | |
218 | close(cfd); | |
219 | return -1; | |
711ea730 KZ |
220 | } |
221 | ||
6e1eda6f | 222 | static void __attribute__((__noreturn__)) usage(void) |
711ea730 | 223 | { |
6e1eda6f | 224 | FILE *output = stdout; |
ed8d2938 | 225 | fputs(USAGE_HEADER, output); |
0fbd4c85 | 226 | fprintf(output, _(" %s [options] <newrootdir> <init> <args to init>\n"), |
ed8d2938 | 227 | program_invocation_short_name); |
451dbcfa BS |
228 | |
229 | fputs(USAGE_SEPARATOR, output); | |
230 | fputs(_("Switch to another filesystem as the root of the mount tree.\n"), output); | |
231 | ||
ed8d2938 | 232 | fputs(USAGE_OPTIONS, output); |
bad4c729 MY |
233 | fprintf(output, USAGE_HELP_OPTIONS(16)); |
234 | fprintf(output, USAGE_MAN_TAIL("switch_root(8)")); | |
8b6457d0 | 235 | |
6e1eda6f | 236 | exit(EXIT_SUCCESS); |
711ea730 KZ |
237 | } |
238 | ||
239 | int main(int argc, char *argv[]) | |
240 | { | |
8b6457d0 | 241 | char *newroot, *init, **initargs; |
105bb857 SK |
242 | int c; |
243 | static const struct option longopts[] = { | |
244 | {"version", no_argument, NULL, 'V'}, | |
245 | {"help", no_argument, NULL, 'h'}, | |
246 | {NULL, 0, NULL, 0} | |
247 | }; | |
248 | ||
2c308875 | 249 | close_stdout_atexit(); |
711ea730 | 250 | |
9737a167 | 251 | while ((c = getopt_long(argc, argv, "+Vh", longopts, NULL)) != -1) |
105bb857 SK |
252 | switch (c) { |
253 | case 'V': | |
2c308875 | 254 | print_version(EXIT_SUCCESS); |
105bb857 | 255 | case 'h': |
6e1eda6f | 256 | usage(); |
105bb857 SK |
257 | default: |
258 | errtryhelp(EXIT_FAILURE); | |
259 | } | |
6e1eda6f RM |
260 | if (argc < 3) { |
261 | warnx(_("not enough arguments")); | |
262 | errtryhelp(EXIT_FAILURE); | |
263 | } | |
8b6457d0 KZ |
264 | |
265 | newroot = argv[1]; | |
266 | init = argv[2]; | |
267 | initargs = &argv[2]; | |
268 | ||
6e1eda6f RM |
269 | if (!*newroot || !*init) { |
270 | warnx(_("bad usage")); | |
271 | errtryhelp(EXIT_FAILURE); | |
272 | } | |
711ea730 | 273 | |
fcb495b1 | 274 | if (switchroot(newroot)) |
0fbd4c85 | 275 | errx(EXIT_FAILURE, _("failed. Sorry.")); |
fcb495b1 | 276 | |
8b6457d0 | 277 | if (access(init, X_OK)) |
0fbd4c85 | 278 | warn(_("cannot access %s"), init); |
711ea730 | 279 | |
8b6457d0 | 280 | execv(init, initargs); |
fd777151 | 281 | errexec(init); |
711ea730 | 282 | } |