github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/mount/switch_root_linux.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 package mount 6 7 import ( 8 "fmt" 9 "log" 10 "os" 11 "path/filepath" 12 13 "golang.org/x/sys/unix" 14 ) 15 16 // getDev returns the device (as returned by the FSTAT syscall) for the given file descriptor. 17 func getDev(fd int) (dev uint64, err error) { 18 var stat unix.Stat_t 19 if err := unix.Fstat(fd, &stat); err != nil { 20 return 0, err 21 } 22 return uint64(stat.Dev), nil 23 } 24 25 // recursiveDelete deletes a directory identified by `fd` and everything in it. 26 // 27 // This function allows deleting directories no longer referenceable by 28 // any file name. This function does not descend into mounts. 29 func recursiveDelete(fd int) error { 30 parentDev, err := getDev(fd) 31 if err != nil { 32 return err 33 } 34 35 // The file descriptor is already open, but allocating a os.File 36 // here makes reading the files in the dir so much nicer. 37 dir := os.NewFile(uintptr(fd), "__ignored__") 38 defer dir.Close() 39 names, err := dir.Readdirnames(-1) 40 if err != nil { 41 return err 42 } 43 44 for _, name := range names { 45 // Loop here, but handle loop in separate function to make defer work as expected. 46 if err := recusiveDeleteInner(fd, parentDev, name); err != nil { 47 return err 48 } 49 } 50 return nil 51 } 52 53 // recusiveDeleteInner is called from recursiveDelete and either deletes 54 // or recurses into the given file or directory 55 // 56 // There should be no need to call this function directly. 57 func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error { 58 // O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all symlinks (even when pointing to a dir). 59 // We need to filter out symlinks because getDev later follows them. 60 childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR) 61 if err != nil { 62 // childName points to either a file or a symlink, delete in any case. 63 if err := unix.Unlinkat(parentFd, childName, 0); err != nil { 64 return err 65 } 66 } else { 67 // Open succeeded, which means childName points to a real directory. 68 defer unix.Close(childFd) 69 70 // Don't descent into other file systems. 71 if childFdDev, err := getDev(childFd); err != nil { 72 return err 73 } else if childFdDev != parentDev { 74 // This means continue in recursiveDelete. 75 return nil 76 } 77 78 if err := recursiveDelete(childFd); err != nil { 79 return err 80 } 81 // Back from recursion, the directory is now empty, delete. 82 if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil { 83 return err 84 } 85 } 86 return nil 87 } 88 89 // MoveMount moves a mount from oldPath to newPath. 90 // 91 // This function is just a wrapper around the MOUNT syscall with the 92 // MOVE flag supplied. 93 func MoveMount(oldPath string, newPath string) error { 94 return unix.Mount(oldPath, newPath, "", unix.MS_MOVE, "") 95 } 96 97 // addSpecialMounts moves the 'special' mounts to the given target path 98 // 99 // 'special' in this context refers to the following non-blockdevice backed 100 // mounts that are almost always used: /dev, /proc, /sys, and /run. 101 // This function will create the target directories, if necessary. 102 // If the target directories already exist, they must be empty. 103 // This function skips missing mounts. 104 func addSpecialMounts(newRoot string) error { 105 var mounts = []string{"/dev", "/proc", "/sys", "/run"} 106 107 for _, mount := range mounts { 108 path := filepath.Join(newRoot, mount) 109 // Skip all mounting if the directory does not exist. 110 if _, err := os.Stat(mount); os.IsNotExist(err) { 111 log.Printf("switch_root: Skipping %q as the dir does not exist", mount) 112 continue 113 } else if err != nil { 114 return err 115 } 116 // Also skip if not currently a mount point 117 if same, err := SameFilesystem("/", mount); err != nil { 118 return err 119 } else if same { 120 log.Printf("switch_root: Skipping %q as it is not a mount", mount) 121 continue 122 } 123 // Make sure the target dir exists. 124 if err := os.MkdirAll(path, 0755); err != nil { 125 return err 126 } 127 if err := MoveMount(mount, path); err != nil { 128 return err 129 } 130 } 131 return nil 132 } 133 134 // SameFilesystem returns true if both paths reside in the same filesystem. 135 // This is achieved by comparing Stat_t.Dev, which contains the fs device's 136 // major/minor numbers. 137 func SameFilesystem(path1, path2 string) (bool, error) { 138 var stat1, stat2 unix.Stat_t 139 if err := unix.Stat(path1, &stat1); err != nil { 140 return false, err 141 } 142 if err := unix.Stat(path2, &stat2); err != nil { 143 return false, err 144 } 145 return stat1.Dev == stat2.Dev, nil 146 } 147 148 // SwitchRoot makes newRootDir the new root directory of the system. 149 // 150 // To be exact, it makes newRootDir the new root directory of the calling 151 // process's mount namespace. 152 // 153 // It moves special mounts (dev, proc, sys, run) to the new directory, then 154 // does a chroot, moves the root mount to the new directory and finally 155 // DELETES EVERYTHING in the old root and execs the given init. 156 func SwitchRoot(newRootDir string, init string) error { 157 err := newRoot(newRootDir) 158 if err != nil { 159 return err 160 } 161 return execInit(init) 162 } 163 164 // newRoot is the "first half" of SwitchRoot - that is, it creates special mounts 165 // in newRoot, chroot's there, and RECURSIVELY DELETES everything in the old root. 166 func newRoot(newRootDir string) error { 167 log.Printf("switch_root: moving mounts") 168 if err := addSpecialMounts(newRootDir); err != nil { 169 return fmt.Errorf("switch_root: moving mounts failed %v", err) 170 } 171 172 log.Printf("switch_root: Changing directory") 173 if err := unix.Chdir(newRootDir); err != nil { 174 return fmt.Errorf("switch_root: failed change directory to new_root %v", err) 175 } 176 177 // Open "/" now, we need the file descriptor later. 178 oldRoot, err := os.Open("/") 179 if err != nil { 180 return err 181 } 182 defer oldRoot.Close() 183 184 log.Printf("switch_root: Moving /") 185 if err := MoveMount(newRootDir, "/"); err != nil { 186 return err 187 } 188 189 log.Printf("switch_root: Changing root!") 190 if err := unix.Chroot("."); err != nil { 191 return fmt.Errorf("switch_root: fatal chroot error %v", err) 192 } 193 194 log.Printf("switch_root: Deleting old /") 195 return recursiveDelete(int(oldRoot.Fd())) 196 } 197 198 // execInit is generally only useful as part of SwitchRoot or similar. 199 // It exec's the given binary in place of the current binary, necessary so that 200 // the new binary can be pid 1. 201 func execInit(init string) error { 202 log.Printf("switch_root: executing init") 203 if err := unix.Exec(init, []string{init}, []string{}); err != nil { 204 return fmt.Errorf("switch_root: exec failed %v", err) 205 } 206 return nil 207 }