github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/internal/mounttree/switchroot_linux.go (about) 1 package mounttree // import "github.com/docker/docker/internal/mounttree" 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 8 "github.com/moby/sys/mount" 9 "github.com/moby/sys/mountinfo" 10 "golang.org/x/sys/unix" 11 ) 12 13 // SwitchRoot changes path to be the root of the mount tree and changes the 14 // current working directory to the new root. 15 // 16 // This function bind-mounts onto path; it is the caller's responsibility to set 17 // the desired propagation mode of path's parent mount beforehand to prevent 18 // unwanted propagation into different mount namespaces. 19 func SwitchRoot(path string) error { 20 if mounted, _ := mountinfo.Mounted(path); !mounted { 21 if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil { 22 return realChroot(path) 23 } 24 } 25 26 // setup oldRoot for pivot_root 27 pivotDir, err := os.MkdirTemp(path, ".pivot_root") 28 if err != nil { 29 return fmt.Errorf("Error setting up pivot dir: %v", err) 30 } 31 32 var mounted bool 33 defer func() { 34 if mounted { 35 // make sure pivotDir is not mounted before we try to remove it 36 if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil { 37 if err == nil { 38 err = errCleanup 39 } 40 return 41 } 42 } 43 44 errCleanup := os.Remove(pivotDir) 45 // pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful 46 // because we already cleaned it up on failed pivot_root 47 if errCleanup != nil && !os.IsNotExist(errCleanup) { 48 errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup) 49 if err == nil { 50 err = errCleanup 51 } 52 } 53 }() 54 55 if err := unix.PivotRoot(path, pivotDir); err != nil { 56 // If pivot fails, fall back to the normal chroot after cleaning up temp dir 57 if err := os.Remove(pivotDir); err != nil { 58 return fmt.Errorf("Error cleaning up after failed pivot: %v", err) 59 } 60 return realChroot(path) 61 } 62 mounted = true 63 64 // This is the new path for where the old root (prior to the pivot) has been moved to 65 // This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction 66 pivotDir = filepath.Join("/", filepath.Base(pivotDir)) 67 68 if err := unix.Chdir("/"); err != nil { 69 return fmt.Errorf("Error changing to new root: %v", err) 70 } 71 72 // Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host 73 if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil { 74 return fmt.Errorf("Error making old root private after pivot: %v", err) 75 } 76 77 // Now unmount the old root so it's no longer visible from the new root 78 if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil { 79 return fmt.Errorf("Error while unmounting old root after pivot: %v", err) 80 } 81 mounted = false 82 83 return nil 84 } 85 86 func realChroot(path string) error { 87 if err := unix.Chroot(path); err != nil { 88 return fmt.Errorf("Error after fallback to chroot: %v", err) 89 } 90 if err := unix.Chdir("/"); err != nil { 91 return fmt.Errorf("Error changing to new root after chroot: %v", err) 92 } 93 return nil 94 }