github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/pkg/chrootarchive/chroot_linux.go (about) 1 package chrootarchive // import "github.com/docker/docker/pkg/chrootarchive" 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 9 "github.com/docker/docker/pkg/mount" 10 rsystem "github.com/opencontainers/runc/libcontainer/system" 11 "golang.org/x/sys/unix" 12 ) 13 14 // chroot on linux uses pivot_root instead of chroot 15 // pivot_root takes a new root and an old root. 16 // Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root. 17 // New root is where the new rootfs is set to. 18 // Old root is removed after the call to pivot_root so it is no longer available under the new root. 19 // This is similar to how libcontainer sets up a container's rootfs 20 func chroot(path string) (err error) { 21 // if the engine is running in a user namespace we need to use actual chroot 22 if rsystem.RunningInUserNS() { 23 return realChroot(path) 24 } 25 if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { 26 return fmt.Errorf("Error creating mount namespace before pivot: %v", err) 27 } 28 29 // Make everything in new ns slave. 30 // Don't use `private` here as this could race where the mountns gets a 31 // reference to a mount and an unmount from the host does not propagate, 32 // which could potentially cause transient errors for other operations, 33 // even though this should be relatively small window here `slave` should 34 // not cause any problems. 35 if err := mount.MakeRSlave("/"); err != nil { 36 return err 37 } 38 39 if mounted, _ := mount.Mounted(path); !mounted { 40 if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil { 41 return realChroot(path) 42 } 43 } 44 45 // setup oldRoot for pivot_root 46 pivotDir, err := ioutil.TempDir(path, ".pivot_root") 47 if err != nil { 48 return fmt.Errorf("Error setting up pivot dir: %v", err) 49 } 50 51 var mounted bool 52 defer func() { 53 if mounted { 54 // make sure pivotDir is not mounted before we try to remove it 55 if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil { 56 if err == nil { 57 err = errCleanup 58 } 59 return 60 } 61 } 62 63 errCleanup := os.Remove(pivotDir) 64 // pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful 65 // because we already cleaned it up on failed pivot_root 66 if errCleanup != nil && !os.IsNotExist(errCleanup) { 67 errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup) 68 if err == nil { 69 err = errCleanup 70 } 71 } 72 }() 73 74 if err := unix.PivotRoot(path, pivotDir); err != nil { 75 // If pivot fails, fall back to the normal chroot after cleaning up temp dir 76 if err := os.Remove(pivotDir); err != nil { 77 return fmt.Errorf("Error cleaning up after failed pivot: %v", err) 78 } 79 return realChroot(path) 80 } 81 mounted = true 82 83 // This is the new path for where the old root (prior to the pivot) has been moved to 84 // This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction 85 pivotDir = filepath.Join("/", filepath.Base(pivotDir)) 86 87 if err := unix.Chdir("/"); err != nil { 88 return fmt.Errorf("Error changing to new root: %v", err) 89 } 90 91 // Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host 92 if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil { 93 return fmt.Errorf("Error making old root private after pivot: %v", err) 94 } 95 96 // Now unmount the old root so it's no longer visible from the new root 97 if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil { 98 return fmt.Errorf("Error while unmounting old root after pivot: %v", err) 99 } 100 mounted = false 101 102 return nil 103 } 104 105 func realChroot(path string) error { 106 if err := unix.Chroot(path); err != nil { 107 return fmt.Errorf("Error after fallback to chroot: %v", err) 108 } 109 if err := unix.Chdir("/"); err != nil { 110 return fmt.Errorf("Error changing to new root after chroot: %v", err) 111 } 112 return nil 113 }