pkg/chrootarchive/chroot_linux.go
85988b33
 package chrootarchive
 
 import (
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
7d22887b
 
 	"github.com/docker/docker/pkg/mount"
dc950567
 	rsystem "github.com/opencontainers/runc/libcontainer/system"
069fdc8a
 	"golang.org/x/sys/unix"
85988b33
 )
 
 // chroot on linux uses pivot_root instead of chroot
 // pivot_root takes a new root and an old root.
 // Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root.
 // New root is where the new rootfs is set to.
 // Old root is removed after the call to pivot_root so it is no longer available under the new root.
 // This is similar to how libcontainer sets up a container's rootfs
 func chroot(path string) (err error) {
dc950567
 	// if the engine is running in a user namespace we need to use actual chroot
 	if rsystem.RunningInUserNS() {
 		return realChroot(path)
 	}
069fdc8a
 	if err := unix.Unshare(unix.CLONE_NEWNS); err != nil {
85988b33
 		return fmt.Errorf("Error creating mount namespace before pivot: %v", err)
 	}
7d22887b
 
b511d1f0
 	// make everything in new ns private
 	if err := mount.MakeRPrivate("/"); err != nil {
 		return err
 	}
e6eef7eb
 
 	if mounted, _ := mount.Mounted(path); !mounted {
 		if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil {
 			return realChroot(path)
 		}
85988b33
 	}
 
 	// setup oldRoot for pivot_root
 	pivotDir, err := ioutil.TempDir(path, ".pivot_root")
 	if err != nil {
 		return fmt.Errorf("Error setting up pivot dir: %v", err)
 	}
 
 	var mounted bool
 	defer func() {
 		if mounted {
 			// make sure pivotDir is not mounted before we try to remove it
069fdc8a
 			if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil {
85988b33
 				if err == nil {
 					err = errCleanup
 				}
 				return
 			}
 		}
 
 		errCleanup := os.Remove(pivotDir)
5248f5c3
 		// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
7d22887b
 		// because we already cleaned it up on failed pivot_root
5248f5c3
 		if errCleanup != nil && !os.IsNotExist(errCleanup) {
85988b33
 			errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
 			if err == nil {
 				err = errCleanup
 			}
 		}
 	}()
 
069fdc8a
 	if err := unix.PivotRoot(path, pivotDir); err != nil {
7d22887b
 		// If pivot fails, fall back to the normal chroot after cleaning up temp dir
5248f5c3
 		if err := os.Remove(pivotDir); err != nil {
 			return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
 		}
85988b33
 		return realChroot(path)
 	}
 	mounted = true
 
 	// This is the new path for where the old root (prior to the pivot) has been moved to
 	// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
 	pivotDir = filepath.Join("/", filepath.Base(pivotDir))
 
069fdc8a
 	if err := unix.Chdir("/"); err != nil {
85988b33
 		return fmt.Errorf("Error changing to new root: %v", err)
 	}
 
 	// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
069fdc8a
 	if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil {
85988b33
 		return fmt.Errorf("Error making old root private after pivot: %v", err)
 	}
 
 	// Now unmount the old root so it's no longer visible from the new root
069fdc8a
 	if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil {
85988b33
 		return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
 	}
 	mounted = false
 
 	return nil
 }
 
 func realChroot(path string) error {
069fdc8a
 	if err := unix.Chroot(path); err != nil {
85988b33
 		return fmt.Errorf("Error after fallback to chroot: %v", err)
 	}
069fdc8a
 	if err := unix.Chdir("/"); err != nil {
5248f5c3
 		return fmt.Errorf("Error changing to new root after chroot: %v", err)
85988b33
 	}
 	return nil
 }