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