]>
Commit | Line | Data |
---|---|---|
c4da2407 HH |
1 | /* |
2 | * switch_root.c | |
3 | * | |
4 | * Code to switch from initramfs to system root. | |
5 | * Based on nash.c from mkinitrd | |
6 | * | |
7 | * Copyright 2002-2009 Red Hat, Inc. All rights reserved. | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
21 | * | |
22 | * Author(s): Erik Troan <ewt@redhat.com> | |
23 | * Jeremy Katz <katzj@redhat.com> | |
24 | * Peter Jones <pjones@redhat.com> | |
25 | * Harald Hoyer <harald@redhat.com> | |
26 | */ | |
27 | ||
28 | #define _GNU_SOURCE 1 | |
29 | #include <sys/mount.h> | |
30 | #include <sys/types.h> | |
31 | #include <sys/stat.h> | |
32 | #include <unistd.h> | |
33 | #include <stdio.h> | |
34 | #include <dirent.h> | |
35 | #include <alloca.h> | |
36 | #include <string.h> | |
37 | #include <errno.h> | |
38 | #include <mntent.h> | |
39 | #include <stdlib.h> | |
40 | #include <ctype.h> | |
41 | #include <sys/mount.h> | |
42 | #include <fcntl.h> | |
43 | #include <linux/fs.h> | |
44 | ||
45 | #ifndef MNT_FORCE | |
46 | #define MNT_FORCE 0x1 | |
47 | #endif | |
48 | ||
49 | #ifndef MNT_DETACH | |
50 | #define MNT_DETACH 0x2 | |
51 | #endif | |
52 | ||
53 | ||
54 | #define asprintfa(str, fmt, ...) ({ \ | |
55 | char *_tmp = NULL; \ | |
56 | int _rc; \ | |
57 | _rc = asprintf((str), (fmt), __VA_ARGS__); \ | |
58 | if (_rc != -1) { \ | |
59 | _tmp = strdupa(*(str)); \ | |
60 | if (!_tmp) { \ | |
61 | _rc = -1; \ | |
62 | } else { \ | |
63 | free(*(str)); \ | |
64 | *(str) = _tmp; \ | |
65 | } \ | |
66 | } \ | |
67 | _rc; \ | |
68 | }) | |
69 | ||
70 | ||
71 | ||
72 | static inline int | |
73 | setFdCoe(int fd, int enable) | |
74 | { | |
75 | int rc; | |
76 | long flags = 0; | |
77 | ||
78 | rc = fcntl(fd, F_GETFD, &flags); | |
79 | if (rc < 0) | |
80 | return rc; | |
81 | ||
82 | if (enable) | |
83 | flags |= FD_CLOEXEC; | |
84 | else | |
85 | flags &= ~FD_CLOEXEC; | |
86 | ||
87 | rc = fcntl(fd, F_SETFD, flags); | |
88 | return rc; | |
89 | } | |
90 | ||
91 | static char * | |
92 | getArg(char * cmd, char * end, char ** arg) | |
93 | { | |
94 | char quote = '\0'; | |
95 | ||
96 | if (!cmd || cmd >= end) | |
97 | return NULL; | |
98 | ||
99 | while (isspace(*cmd) && cmd < end) | |
100 | cmd++; | |
101 | if (cmd >= end) | |
102 | return NULL; | |
103 | ||
104 | if (*cmd == '"') | |
105 | cmd++, quote = '"'; | |
106 | else if (*cmd == '\'') | |
107 | cmd++, quote = '\''; | |
108 | ||
109 | if (quote) { | |
110 | *arg = cmd; | |
111 | ||
112 | /* This doesn't support \ escapes */ | |
113 | while (cmd < end && *cmd != quote) | |
114 | cmd++; | |
115 | ||
116 | if (cmd == end) { | |
117 | printf("error: quote mismatch for %s\n", *arg); | |
118 | return NULL; | |
119 | } | |
120 | ||
121 | *cmd = '\0'; | |
122 | cmd++; | |
123 | } else { | |
124 | *arg = cmd; | |
125 | while (!isspace(*cmd) && cmd < end) | |
126 | cmd++; | |
127 | *cmd = '\0'; | |
128 | if (**arg == '$') | |
129 | *arg = getenv(*arg+1); | |
130 | if (*arg == NULL) | |
131 | *arg = ""; | |
132 | } | |
133 | ||
134 | cmd++; | |
135 | ||
136 | while (isspace(*cmd)) | |
137 | cmd++; | |
138 | ||
139 | return cmd; | |
140 | } | |
141 | ||
142 | static int | |
143 | mountCommand(char * cmd, char * end) | |
144 | { | |
145 | char * fsType = NULL; | |
146 | char * device, *spec; | |
147 | char * mntPoint; | |
148 | char * opts = NULL; | |
149 | int rc = 0; | |
150 | int flags = MS_MGC_VAL; | |
151 | char * newOpts; | |
152 | ||
153 | if (!(cmd = getArg(cmd, end, &spec))) { | |
154 | printf( | |
155 | "usage: mount [--ro] [-o <opts>] -t <type> <device> <mntpoint>\n"); | |
156 | return 1; | |
157 | } | |
158 | ||
159 | while (cmd && *spec == '-') { | |
160 | if (!strcmp(spec, "--ro")) { | |
161 | flags |= MS_RDONLY; | |
162 | } else if (!strcmp(spec, "--bind")) { | |
163 | flags = MS_BIND; | |
164 | fsType = "none"; | |
165 | } else if (!strcmp(spec, "--move")) { | |
166 | flags = MS_MOVE; | |
167 | fsType = "none"; | |
168 | } else if (!strcmp(spec, "-o")) { | |
169 | cmd = getArg(cmd, end, &opts); | |
170 | if (!cmd) { | |
171 | printf("mount: -o requires arguments\n"); | |
172 | return 1; | |
173 | } | |
174 | } else if (!strcmp(spec, "-t")) { | |
175 | if (!(cmd = getArg(cmd, end, &fsType))) { | |
176 | printf("mount: missing filesystem type\n"); | |
177 | return 1; | |
178 | } | |
179 | } | |
180 | ||
181 | cmd = getArg(cmd, end, &spec); | |
182 | } | |
183 | ||
184 | if (!cmd) { | |
185 | printf("mount: missing device or mountpoint\n"); | |
186 | return 1; | |
187 | } | |
188 | ||
189 | if (!(cmd = getArg(cmd, end, &mntPoint))) { | |
190 | struct mntent *mnt; | |
191 | FILE *fstab; | |
192 | ||
193 | fstab = fopen("/etc/fstab", "r"); | |
194 | if (!fstab) { | |
195 | printf("mount: missing mount point\n"); | |
196 | return 1; | |
197 | } | |
198 | do { | |
199 | if (!(mnt = getmntent(fstab))) { | |
200 | printf("mount: missing mount point\n"); | |
201 | fclose(fstab); | |
202 | return 1; | |
203 | } | |
204 | if (!strcmp(mnt->mnt_dir, spec)) { | |
205 | spec = mnt->mnt_fsname; | |
206 | mntPoint = mnt->mnt_dir; | |
207 | ||
208 | if (!strcmp(mnt->mnt_type, "bind")) { | |
209 | flags |= MS_BIND; | |
210 | fsType = "none"; | |
211 | } else | |
212 | fsType = mnt->mnt_type; | |
213 | ||
214 | opts = mnt->mnt_opts; | |
215 | break; | |
216 | } | |
217 | } while(1); | |
218 | ||
219 | fclose(fstab); | |
220 | } | |
221 | ||
222 | if (!fsType) { | |
223 | printf("mount: filesystem type expected\n"); | |
224 | return 1; | |
225 | } | |
226 | ||
227 | if (cmd && cmd < end) { | |
228 | printf("mount: unexpected arguments\n"); | |
229 | return 1; | |
230 | } | |
231 | ||
232 | /* need to deal with options */ | |
233 | if (opts) { | |
234 | char * end; | |
235 | char * start = opts; | |
236 | ||
237 | newOpts = alloca(strlen(opts) + 1); | |
238 | *newOpts = '\0'; | |
239 | ||
240 | while (*start) { | |
241 | end = strchr(start, ','); | |
242 | if (!end) { | |
243 | end = start + strlen(start); | |
244 | } else { | |
245 | *end = '\0'; | |
246 | end++; | |
247 | } | |
248 | ||
249 | if (!strcmp(start, "ro")) | |
250 | flags |= MS_RDONLY; | |
251 | else if (!strcmp(start, "rw")) | |
252 | flags &= ~MS_RDONLY; | |
253 | else if (!strcmp(start, "nosuid")) | |
254 | flags |= MS_NOSUID; | |
255 | else if (!strcmp(start, "suid")) | |
256 | flags &= ~MS_NOSUID; | |
257 | else if (!strcmp(start, "nodev")) | |
258 | flags |= MS_NODEV; | |
259 | else if (!strcmp(start, "dev")) | |
260 | flags &= ~MS_NODEV; | |
261 | else if (!strcmp(start, "noexec")) | |
262 | flags |= MS_NOEXEC; | |
263 | else if (!strcmp(start, "exec")) | |
264 | flags &= ~MS_NOEXEC; | |
265 | else if (!strcmp(start, "sync")) | |
266 | flags |= MS_SYNCHRONOUS; | |
267 | else if (!strcmp(start, "async")) | |
268 | flags &= ~MS_SYNCHRONOUS; | |
269 | else if (!strcmp(start, "nodiratime")) | |
270 | flags |= MS_NODIRATIME; | |
271 | else if (!strcmp(start, "diratime")) | |
272 | flags &= ~MS_NODIRATIME; | |
273 | else if (!strcmp(start, "noatime")) | |
274 | flags |= MS_NOATIME; | |
275 | else if (!strcmp(start, "atime")) | |
276 | flags &= ~MS_NOATIME; | |
277 | else if (!strcmp(start, "relatime")) | |
278 | flags |= MS_RELATIME; | |
279 | else if (!strcmp(start, "norelatime")) | |
280 | flags &= ~MS_RELATIME; | |
281 | else if (!strcmp(start, "remount")) | |
282 | flags |= MS_REMOUNT; | |
283 | else if (!strcmp(start, "bind")) | |
284 | flags |= MS_BIND; | |
285 | else if (!strcmp(start, "defaults")) | |
286 | ; | |
287 | else { | |
288 | if (*newOpts) | |
289 | strcat(newOpts, ","); | |
290 | strcat(newOpts, start); | |
291 | } | |
292 | ||
293 | start = end; | |
294 | } | |
295 | ||
296 | opts = newOpts; | |
297 | } | |
298 | ||
299 | device = strdupa(spec); | |
300 | ||
301 | if (!device) { | |
302 | printf("mount: could not find filesystem '%s'\n", spec); | |
303 | return 1; | |
304 | } | |
305 | ||
306 | { | |
307 | char *mount_opts = NULL; | |
308 | mount_opts = opts; | |
309 | if (mount(device, mntPoint, fsType, flags, mount_opts) < 0) { | |
310 | printf("mount: error mounting %s on %s as %s: %m\n", | |
311 | device, mntPoint, fsType); | |
312 | rc = 1; | |
313 | } | |
314 | } | |
315 | ||
316 | return rc; | |
317 | } | |
318 | ||
319 | /* remove all files/directories below dirName -- don't cross mountpoints */ | |
320 | static int | |
321 | recursiveRemove(char * dirName) | |
322 | { | |
323 | struct stat sb,rb; | |
324 | DIR * dir; | |
325 | struct dirent * d; | |
326 | char * strBuf = alloca(strlen(dirName) + 1024); | |
327 | ||
328 | if (!(dir = opendir(dirName))) { | |
329 | printf("error opening %s: %m\n", dirName); | |
330 | return 0; | |
331 | } | |
332 | ||
333 | if (fstat(dirfd(dir),&rb)) { | |
334 | printf("unable to stat %s: %m\n", dirName); | |
335 | closedir(dir); | |
336 | return 0; | |
337 | } | |
338 | ||
339 | errno = 0; | |
340 | while ((d = readdir(dir))) { | |
341 | errno = 0; | |
342 | ||
343 | if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) { | |
344 | errno = 0; | |
345 | continue; | |
346 | } | |
347 | ||
348 | strcpy(strBuf, dirName); | |
349 | strcat(strBuf, "/"); | |
350 | strcat(strBuf, d->d_name); | |
351 | ||
352 | if (lstat(strBuf, &sb)) { | |
353 | printf("failed to stat %s: %m\n", strBuf); | |
354 | errno = 0; | |
355 | continue; | |
356 | } | |
357 | ||
358 | /* only descend into subdirectories if device is same as dir */ | |
359 | if (S_ISDIR(sb.st_mode)) { | |
360 | if (sb.st_dev == rb.st_dev) { | |
361 | recursiveRemove(strBuf); | |
362 | if (rmdir(strBuf)) | |
363 | printf("failed to rmdir %s: %m\n", strBuf); | |
364 | } | |
365 | errno = 0; | |
366 | continue; | |
367 | } | |
368 | ||
369 | if (unlink(strBuf)) { | |
370 | printf("failed to remove %s: %m\n", strBuf); | |
371 | errno = 0; | |
372 | continue; | |
373 | } | |
374 | } | |
375 | ||
376 | if (errno) { | |
377 | closedir(dir); | |
378 | printf("error reading from %s: %m\n", dirName); | |
379 | return 1; | |
380 | } | |
381 | ||
382 | closedir(dir); | |
383 | ||
384 | return 0; | |
385 | } | |
386 | ||
387 | static void | |
388 | mountMntEnt(const struct mntent *mnt) | |
389 | { | |
390 | char *start = NULL, *end; | |
391 | char *target = NULL; | |
392 | struct stat sb; | |
393 | ||
394 | printf("mounting %s\n", mnt->mnt_dir); | |
395 | if (asprintfa(&target, ".%s", mnt->mnt_dir) < 0) { | |
396 | printf("setuproot: out of memory while mounting %s\n", | |
397 | mnt->mnt_dir); | |
398 | return; | |
399 | } | |
400 | ||
401 | if (stat(target, &sb) < 0) | |
402 | return; | |
403 | ||
404 | if (asprintf(&start, "-o %s -t %s %s .%s\n", | |
405 | mnt->mnt_opts, mnt->mnt_type, mnt->mnt_fsname, | |
406 | mnt->mnt_dir) < 0) { | |
407 | printf("setuproot: out of memory while mounting %s\n", | |
408 | mnt->mnt_dir); | |
409 | return; | |
410 | } | |
411 | ||
412 | end = start + 1; | |
413 | while (*end && (*end != '\n')) | |
414 | end++; | |
415 | /* end points to the \n at the end of the command */ | |
416 | ||
417 | if (mountCommand(start, end) != 0) | |
418 | printf("setuproot: mount returned error\n"); | |
419 | } | |
420 | ||
421 | static int | |
422 | setuprootCommand(char *new) | |
423 | { | |
424 | FILE *fp; | |
425 | ||
426 | printf("Setting up new root fs\n"); | |
427 | ||
428 | if (chdir(new)) { | |
429 | printf("setuproot: chdir(%s) failed: %m\n", new); | |
430 | return 1; | |
431 | } | |
432 | ||
433 | if (mount("/dev", "./dev", NULL, MS_BIND, NULL) < 0) | |
434 | printf("setuproot: moving /dev failed: %m\n"); | |
435 | ||
436 | fp = setmntent("./etc/fstab.sys", "r"); | |
437 | if (fp) | |
438 | printf("using fstab.sys from mounted FS\n"); | |
439 | else { | |
440 | fp = setmntent("/etc/fstab.sys", "r"); | |
441 | if (fp) | |
442 | printf("using fstab.sys from initrd\n"); | |
443 | } | |
444 | if (fp) { | |
445 | struct mntent *mnt; | |
446 | ||
447 | while((mnt = getmntent(fp))) | |
448 | mountMntEnt(mnt); | |
449 | endmntent(fp); | |
450 | } else { | |
451 | struct { | |
452 | char *source; | |
453 | char *target; | |
454 | char *type; | |
455 | int flags; | |
456 | void *data; | |
457 | int raise; | |
458 | } fstab[] = { | |
459 | { "/proc", "./proc", "proc", 0, NULL }, | |
460 | { "/sys", "./sys", "sysfs", 0, NULL }, | |
461 | #if 0 | |
462 | { "/dev/pts", "./dev/pts", "devpts", 0, "gid=5,mode=620" }, | |
463 | { "/dev/shm", "./dev/shm", "tmpfs", 0, NULL }, | |
464 | { "/selinux", "/selinux", "selinuxfs", 0, NULL }, | |
465 | #endif | |
466 | { NULL, } | |
467 | }; | |
468 | int i = 0; | |
469 | ||
470 | printf("no fstab.sys, mounting internal defaults\n"); | |
471 | for (; fstab[i].source != NULL; i++) { | |
472 | if (mount(fstab[i].source, fstab[i].target, fstab[i].type, | |
473 | fstab[i].flags, fstab[i].data) < 0) | |
474 | printf("setuproot: error mounting %s: %m\n", | |
475 | fstab[i].source); | |
476 | } | |
477 | } | |
478 | ||
479 | chdir("/"); | |
480 | return 0; | |
481 | } | |
482 | ||
483 | int main(int argc, char **argv) | |
484 | { | |
485 | /* Don't try to unmount the old "/", there's no way to do it. */ | |
486 | const char *umounts[] = { "/dev", "/proc", "/sys", NULL }; | |
487 | char *new = NULL; | |
488 | int fd, i = 0; | |
489 | ||
490 | argv++; | |
491 | new = argv[0]; | |
492 | argv++; | |
aedead80 | 493 | printf("Switching to root: %s\n", new); |
c4da2407 HH |
494 | |
495 | setuprootCommand(new); | |
496 | ||
497 | fd = open("/", O_RDONLY); | |
498 | for (; umounts[i] != NULL; i++) { | |
499 | printf("unmounting old %s\n", umounts[i]); | |
500 | if (umount2(umounts[i], MNT_DETACH) < 0) { | |
501 | printf("ERROR unmounting old %s: %m\n",umounts[i]); | |
502 | printf("forcing unmount of %s\n", umounts[i]); | |
503 | umount2(umounts[i], MNT_FORCE); | |
504 | } | |
505 | } | |
506 | i=0; | |
507 | ||
508 | chdir(new); | |
509 | ||
510 | recursiveRemove("/"); | |
511 | ||
512 | if (mount(new, "/", NULL, MS_MOVE, NULL) < 0) { | |
513 | printf("switchroot: mount failed: %m\n"); | |
514 | close(fd); | |
515 | return 1; | |
516 | } | |
517 | ||
518 | if (chroot(".")) { | |
519 | printf("switchroot: chroot() failed: %m\n"); | |
520 | close(fd); | |
521 | return 1; | |
522 | } | |
523 | ||
524 | /* release the old "/" */ | |
525 | close(fd); | |
526 | ||
527 | close(3); | |
528 | if ((fd = open("/dev/console", O_RDWR)) < 0) { | |
529 | printf("ERROR opening /dev/console: %m\n"); | |
530 | printf("Trying to use fd 0 instead.\n"); | |
531 | fd = dup2(0, 3); | |
532 | } else { | |
533 | setFdCoe(fd, 0); | |
534 | if (fd != 3) { | |
535 | dup2(fd, 3); | |
536 | close(fd); | |
537 | fd = 3; | |
538 | } | |
539 | } | |
540 | close(0); | |
541 | dup2(fd, 0); | |
542 | close(1); | |
543 | dup2(fd, 1); | |
544 | close(2); | |
545 | dup2(fd, 2); | |
546 | close(fd); | |
547 | ||
548 | if (access(argv[0], X_OK)) { | |
549 | printf("WARNING: can't access %s\n", argv[0]); | |
550 | } | |
551 | ||
552 | execv(argv[0], argv); | |
553 | ||
554 | printf("exec of init (%s) failed!!!: %m\n", argv[0]); | |
555 | return 1; | |
556 | } |