github.com/portworx/docker@v1.12.1/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  )
    12  
    13  // chroot on linux uses pivot_root instead of chroot
    14  // pivot_root takes a new root and an old root.
    15  // Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root.
    16  // New root is where the new rootfs is set to.
    17  // Old root is removed after the call to pivot_root so it is no longer available under the new root.
    18  // This is similar to how libcontainer sets up a container's rootfs
    19  func chroot(path string) (err error) {
    20  	if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
    21  		return fmt.Errorf("Error creating mount namespace before pivot: %v", err)
    22  	}
    23  
    24  	if err := mount.MakeRPrivate(path); err != nil {
    25  		return err
    26  	}
    27  
    28  	// setup oldRoot for pivot_root
    29  	pivotDir, err := ioutil.TempDir(path, ".pivot_root")
    30  	if err != nil {
    31  		return fmt.Errorf("Error setting up pivot dir: %v", err)
    32  	}
    33  
    34  	var mounted bool
    35  	defer func() {
    36  		if mounted {
    37  			// make sure pivotDir is not mounted before we try to remove it
    38  			if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil {
    39  				if err == nil {
    40  					err = errCleanup
    41  				}
    42  				return
    43  			}
    44  		}
    45  
    46  		errCleanup := os.Remove(pivotDir)
    47  		// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
    48  		// because we already cleaned it up on failed pivot_root
    49  		if errCleanup != nil && !os.IsNotExist(errCleanup) {
    50  			errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
    51  			if err == nil {
    52  				err = errCleanup
    53  			}
    54  		}
    55  
    56  		if errCleanup := syscall.Unmount("/", syscall.MNT_DETACH); errCleanup != nil {
    57  			if err == nil {
    58  				err = fmt.Errorf("error unmounting root: %v", errCleanup)
    59  			}
    60  			return
    61  		}
    62  	}()
    63  
    64  	if err := syscall.PivotRoot(path, pivotDir); err != nil {
    65  		// If pivot fails, fall back to the normal chroot after cleaning up temp dir
    66  		if err := os.Remove(pivotDir); err != nil {
    67  			return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
    68  		}
    69  		return realChroot(path)
    70  	}
    71  	mounted = true
    72  
    73  	// This is the new path for where the old root (prior to the pivot) has been moved to
    74  	// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
    75  	pivotDir = filepath.Join("/", filepath.Base(pivotDir))
    76  
    77  	if err := syscall.Chdir("/"); err != nil {
    78  		return fmt.Errorf("Error changing to new root: %v", err)
    79  	}
    80  
    81  	// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
    82  	if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
    83  		return fmt.Errorf("Error making old root private after pivot: %v", err)
    84  	}
    85  
    86  	// Now unmount the old root so it's no longer visible from the new root
    87  	if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
    88  		return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
    89  	}
    90  	mounted = false
    91  
    92  	return nil
    93  }
    94  
    95  func realChroot(path string) error {
    96  	if err := syscall.Chroot(path); err != nil {
    97  		return fmt.Errorf("Error after fallback to chroot: %v", err)
    98  	}
    99  	if err := syscall.Chdir("/"); err != nil {
   100  		return fmt.Errorf("Error changing to new root after chroot: %v", err)
   101  	}
   102  	return nil
   103  }