mirror of https://github.com/docker/docs.git
				
				
				
			
		
			
				
	
	
		
			96 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			96 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
package chrootarchive
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"syscall"
 | 
						|
)
 | 
						|
 | 
						|
// 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) {
 | 
						|
	// Create new mount namespace so mounts don't leak
 | 
						|
	if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
 | 
						|
		return fmt.Errorf("Error creating mount namespace before pivot: %v", err)
 | 
						|
	}
 | 
						|
	// path must be a different fs for pivot_root, so bind-mount to itself to ensure this
 | 
						|
	if err := syscall.Mount(path, path, "", syscall.MS_BIND, ""); err != nil {
 | 
						|
		return fmt.Errorf("Error mounting pivot dir before pivot: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
			if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil {
 | 
						|
				if err == nil {
 | 
						|
					err = errCleanup
 | 
						|
				}
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		errCleanup := os.Remove(pivotDir)
 | 
						|
		// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
 | 
						|
		// but we already cleaned it up on failed pivot_root
 | 
						|
		if errCleanup != nil && !os.IsNotExist(errCleanup) {
 | 
						|
			errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
 | 
						|
			if err == nil {
 | 
						|
				err = errCleanup
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	if err := syscall.PivotRoot(path, pivotDir); err != nil {
 | 
						|
		// If pivot fails, fall back to the normal chroot after cleaning up temp dir for pivot_root
 | 
						|
		if err := os.Remove(pivotDir); err != nil {
 | 
						|
			return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
 | 
						|
		}
 | 
						|
		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))
 | 
						|
 | 
						|
	if err := syscall.Chdir("/"); err != nil {
 | 
						|
		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
 | 
						|
	if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
 | 
						|
		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
 | 
						|
	if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
 | 
						|
		return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
 | 
						|
	}
 | 
						|
	mounted = false
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func realChroot(path string) error {
 | 
						|
	if err := syscall.Chroot(path); err != nil {
 | 
						|
		return fmt.Errorf("Error after fallback to chroot: %v", err)
 | 
						|
	}
 | 
						|
	if err := syscall.Chdir("/"); err != nil {
 | 
						|
		return fmt.Errorf("Error changing to new root after chroot: %v", err)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |