1 /* switch_root.c - Switch from rootfs/initramfs to another filesystem 2 * 3 * Copyright 2005 Rob Landley <rob (at) landley.net> 4 5 USE_SWITCH_ROOT(NEWTOY(switch_root, "<2c:h", TOYFLAG_SBIN)) 6 7 config SWITCH_ROOT 8 bool "switch_root" 9 default y 10 help 11 usage: switch_root [-c /dev/console] NEW_ROOT NEW_INIT... 12 13 Use from PID 1 under initramfs to free initramfs, chroot to NEW_ROOT, 14 and exec NEW_INIT. 15 16 -c Redirect console to device in NEW_ROOT 17 -h Hang instead of exiting on failure (avoids kernel panic) 18 */ 19 20 #define FOR_switch_root 21 #include "toys.h" 22 #include <sys/vfs.h> 23 24 GLOBALS( 25 char *c; 26 27 dev_t rootdev; 28 ) 29 30 static int del_node(struct dirtree *node) 31 { 32 if (node->st.st_dev == TT.rootdev && dirtree_notdotdot(node)) { 33 int flag = 0; 34 if (S_ISDIR(node->st.st_mode)) { 35 if (!node->again) return DIRTREE_COMEAGAIN; 36 flag = AT_REMOVEDIR; 37 } 38 unlinkat(dirtree_parentfd(node), node->name, flag); 39 } 40 41 return 0; 42 } 43 44 void switch_root_main(void) 45 { 46 char *newroot = *toys.optargs, **cmdline = toys.optargs+1; 47 struct stat st1, st2; 48 struct statfs stfs; 49 int console = console; // gcc's "may be used" warnings are broken. 50 51 if (getpid() != 1) error_exit("not pid 1"); 52 53 // Root filesystem we're leaving must be ramfs or tmpfs 54 if (statfs("/", &stfs) || 55 (stfs.f_type != 0x858458f6 && stfs.f_type != 0x01021994)) 56 { 57 error_msg("not ramfs"); 58 goto panic; 59 } 60 61 // New directory must be different filesystem instance 62 if (chdir(newroot) || stat(".", &st1) || stat("/", &st2) || 63 st1.st_dev == st2.st_dev) 64 { 65 error_msg("bad newroot '%s'", newroot); 66 goto panic; 67 } 68 TT.rootdev=st2.st_dev; 69 70 // trim any / characters from the init cmdline, as we want to test it with 71 // stat(), relative to newroot. *cmdline is also used below, but by that 72 // point we are in the chroot, so a relative path is still OK. 73 while (**cmdline == '/') (*cmdline)++; 74 75 // init program must exist and be an executable file 76 if (stat(*cmdline, &st1) || !S_ISREG(st1.st_mode) || !(st1.st_mode&0100)) { 77 error_msg("bad init"); 78 goto panic; 79 } 80 81 if (TT.c && -1 == (console = open(TT.c, O_RDWR))) { 82 perror_msg("bad console '%s'", TT.c); 83 goto panic; 84 } 85 86 // Ok, enough safety checks: wipe root partition. 87 dirtree_read("/", del_node); 88 89 // Fix the appearance of the mount table in the newroot chroot 90 if (mount(".", "/", NULL, MS_MOVE, NULL)) { 91 perror_msg("mount"); 92 goto panic; 93 } 94 95 // Enter the new root before starting init 96 if (chroot(".")) { 97 perror_msg("chroot"); 98 goto panic; 99 } 100 101 // Make sure cwd does not point outside of the chroot 102 if (chdir("/")) { 103 perror_msg("chdir"); 104 goto panic; 105 } 106 107 if (TT.c) { 108 int i; 109 for (i=0; i<3; i++) if (console != i) dup2(console, i); 110 if (console>2) close(console); 111 } 112 execv(*cmdline, cmdline); 113 perror_msg("Failed to exec '%s'", *cmdline); 114 panic: 115 if (toys.optflags & FLAG_h) for (;;) wait(NULL); 116 } 117