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  }