github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/cmds/switch_root/switch_root.go (about) 1 // Copyright 2012-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build linux 6 7 package main 8 9 import ( 10 "flag" 11 "fmt" 12 "io" 13 "log" 14 "os" 15 "path/filepath" 16 17 "golang.org/x/sys/unix" 18 ) 19 20 var ( 21 help = flag.Bool("h", false, "Help") 22 version = flag.Bool("V", false, "Version") 23 ) 24 25 func usage() string { 26 return "switch_root [-h] [-V]\nswitch_root newroot init" 27 } 28 29 // getDev returns the device (as returned by the FSTAT syscall) for the given file descriptor. 30 func getDev(fd int) (dev uint64, err error) { 31 var stat unix.Stat_t 32 33 if err := unix.Fstat(fd, &stat); err != nil { 34 return 0, err 35 } 36 37 return stat.Dev, nil 38 } 39 40 // recursiveDelete deletes a directory identified by `fd` and everything in it. 41 // 42 // This function allows deleting directories no longer referenceable by 43 // any file name. This function does not descend into mounts. 44 func recursiveDelete(fd int) error { 45 parentDev, err := getDev(fd) 46 if err != nil { 47 return err 48 } 49 50 // The file descriptor is already open, but allocating a os.File 51 // here makes reading the files in the dir so much nicer. 52 dir := os.NewFile(uintptr(fd), "__ignored__") 53 defer dir.Close() 54 names, err := dir.Readdirnames(-1) 55 if err != nil { 56 return err 57 } 58 59 for _, name := range names { 60 // Loop here, but handle loop in separate function to make defer work as expected. 61 if err := recusiveDeleteInner(fd, parentDev, name); err != nil { 62 return err 63 } 64 } 65 return nil 66 } 67 68 // recusiveDeleteInner is called from recursiveDelete and either deletes 69 // or recurses into the given file or directory 70 // 71 // There should be no need to call this function directly. 72 func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error { 73 // O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all symlinks (even when pointing to a dir). 74 // We need to filter out symlinks because getDev later follows them. 75 childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR) 76 if err != nil { 77 // childName points to either a file or a symlink, delete in any case. 78 if err := unix.Unlinkat(parentFd, childName, 0); err != nil { 79 return err 80 } 81 } else { 82 // Open succeeded, which means childName points to a real directory. 83 defer unix.Close(childFd) 84 85 // Don't descent into other file systems. 86 if childFdDev, err := getDev(childFd); err != nil { 87 return err 88 } else if childFdDev != parentDev { 89 // This means continue in recursiveDelete. 90 return nil 91 } 92 93 if err := recursiveDelete(childFd); err != nil { 94 return err 95 } 96 // Back from recursion, the directory is now empty, delete. 97 if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil { 98 return err 99 } 100 } 101 return nil 102 } 103 104 // execCommand execs into the given command. 105 // 106 // In order to preserve whatever PID this program is running with, 107 // the implementation does an actual EXEC syscall without forking. 108 func execCommand(path string) error { 109 return unix.Exec(path, []string{path}, []string{}) 110 } 111 112 // isEmpty returns true if the directory with the given path is empty. 113 func isEmpty(name string) (bool, error) { 114 f, err := os.Open(name) 115 if err != nil { 116 return false, err 117 } 118 defer f.Close() 119 120 if _, err := f.Readdirnames(1); err == io.EOF { 121 return true, nil 122 } 123 return false, err 124 } 125 126 // moveMount moves mount 127 // 128 // This function is just a wrapper around the MOUNT syscall with the 129 // MOVE flag supplied. 130 func moveMount(oldPath string, newPath string) error { 131 return unix.Mount(oldPath, newPath, "", unix.MS_MOVE, "") 132 } 133 134 // specialFS moves the 'special' mounts to the given target path 135 // 136 // 'special' in this context refers to the following non-blockdevice backed 137 // mounts that are almost always used: /dev, /proc, /sys, and /run. 138 // This function will create the target directories, if necessary. 139 // If the target directories already exists, they must be empty. 140 // This function skips missing mounts. 141 func specialFS(newRoot string) error { 142 var mounts = []string{"/dev", "/proc", "/sys", "/run"} 143 144 for _, mount := range mounts { 145 path := filepath.Join(newRoot, mount) 146 // Skip all mounting if the directory does not exists. 147 if _, err := os.Stat(mount); os.IsNotExist(err) { 148 fmt.Println("switch_root: Skipping", mount) 149 continue 150 } else if err != nil { 151 return err 152 } 153 // Make sure the target dir exists and is empty. 154 if _, err := os.Stat(path); os.IsNotExist(err) { 155 if err := unix.Mkdir(path, 0); err != nil { 156 return err 157 } 158 } else if err != nil { 159 return err 160 } 161 if empty, err := isEmpty(path); err != nil { 162 return err 163 } else if !empty { 164 return fmt.Errorf("%v must be empty", path) 165 } 166 if err := moveMount(mount, path); err != nil { 167 return err 168 } 169 } 170 return nil 171 } 172 173 // switchroot moves special mounts (dev, proc, sys, run) to the new directory, 174 // then does a chroot, moves the root mount to the new directory and finally 175 // DELETES EVERYTHING in the old root and execs the given init. 176 func switchRoot(newRoot string, init string) error { 177 log.Printf("switch_root: moving mounts") 178 if err := specialFS(newRoot); err != nil { 179 return fmt.Errorf("switch_root: moving mounts failed %v", err) 180 } 181 182 log.Printf("switch_root: Changing directory") 183 if err := unix.Chdir(newRoot); err != nil { 184 return fmt.Errorf("switch_root: failed change directory to new_root %v", err) 185 } 186 187 // Open "/" now, we need the file descriptor later. 188 oldRoot, err := os.Open("/") 189 if err != nil { 190 return err 191 } 192 defer oldRoot.Close() 193 194 log.Printf("switch_root: Moving /") 195 if err := moveMount(newRoot, "/"); err != nil { 196 return err 197 } 198 199 log.Printf("switch_root: Changing root!") 200 if err := unix.Chroot("."); err != nil { 201 return fmt.Errorf("switch_root: fatal chroot error %v", err) 202 } 203 204 log.Printf("switch_root: Deleting old /") 205 if err := recursiveDelete(int(oldRoot.Fd())); err != nil { 206 panic(err) 207 } 208 209 log.Printf("switch_root: executing init") 210 if err := execCommand(init); err != nil { 211 return fmt.Errorf("switch_root: exec failed %v", err) 212 } 213 214 return nil 215 } 216 217 func main() { 218 flag.Parse() 219 220 if len(flag.Args()) == 0 { 221 fmt.Println(usage()) 222 os.Exit(0) 223 } 224 225 if *help { 226 fmt.Println(usage()) 227 os.Exit(0) 228 } 229 230 if *version { 231 fmt.Println("Version XX") 232 os.Exit(0) 233 } 234 235 newRoot := flag.Args()[0] 236 init := flag.Args()[1] 237 238 if err := switchRoot(newRoot, init); err != nil { 239 log.Fatalf("switch_root failed %v\n", err) 240 } 241 }