github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/pkg/chrootarchive/chroot_linux.go (about) 1 package chrootarchive 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "syscall" 9 10 "github.com/docker/docker/pkg/mount" 11 rsystem "github.com/opencontainers/runc/libcontainer/system" 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 := syscall.Unshare(syscall.CLONE_NEWNS); err != nil { 26 return fmt.Errorf("Error creating mount namespace before pivot: %v", err) 27 } 28 29 // make everything in new ns private 30 if err := mount.MakeRPrivate("/"); err != nil { 31 return err 32 } 33 34 if mounted, _ := mount.Mounted(path); !mounted { 35 if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil { 36 return realChroot(path) 37 } 38 } 39 40 // setup oldRoot for pivot_root 41 pivotDir, err := ioutil.TempDir(path, ".pivot_root") 42 if err != nil { 43 return fmt.Errorf("Error setting up pivot dir: %v", err) 44 } 45 46 var mounted bool 47 defer func() { 48 if mounted { 49 // make sure pivotDir is not mounted before we try to remove it 50 if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil { 51 if err == nil { 52 err = errCleanup 53 } 54 return 55 } 56 } 57 58 errCleanup := os.Remove(pivotDir) 59 // pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful 60 // because we already cleaned it up on failed pivot_root 61 if errCleanup != nil && !os.IsNotExist(errCleanup) { 62 errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup) 63 if err == nil { 64 err = errCleanup 65 } 66 } 67 }() 68 69 if err := syscall.PivotRoot(path, pivotDir); err != nil { 70 // If pivot fails, fall back to the normal chroot after cleaning up temp dir 71 if err := os.Remove(pivotDir); err != nil { 72 return fmt.Errorf("Error cleaning up after failed pivot: %v", err) 73 } 74 return realChroot(path) 75 } 76 mounted = true 77 78 // This is the new path for where the old root (prior to the pivot) has been moved to 79 // This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction 80 pivotDir = filepath.Join("/", filepath.Base(pivotDir)) 81 82 if err := syscall.Chdir("/"); err != nil { 83 return fmt.Errorf("Error changing to new root: %v", err) 84 } 85 86 // Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host 87 if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil { 88 return fmt.Errorf("Error making old root private after pivot: %v", err) 89 } 90 91 // Now unmount the old root so it's no longer visible from the new root 92 if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { 93 return fmt.Errorf("Error while unmounting old root after pivot: %v", err) 94 } 95 mounted = false 96 97 return nil 98 } 99 100 func realChroot(path string) error { 101 if err := syscall.Chroot(path); err != nil { 102 return fmt.Errorf("Error after fallback to chroot: %v", err) 103 } 104 if err := syscall.Chdir("/"); err != nil { 105 return fmt.Errorf("Error changing to new root after chroot: %v", err) 106 } 107 return nil 108 }