github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/internal/mounttree/switchroot_linux.go (about)

     1  package mounttree // import "github.com/docker/docker/internal/mounttree"
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/moby/sys/mount"
     9  	"github.com/moby/sys/mountinfo"
    10  	"golang.org/x/sys/unix"
    11  )
    12  
    13  // SwitchRoot changes path to be the root of the mount tree and changes the
    14  // current working directory to the new root.
    15  //
    16  // This function bind-mounts onto path; it is the caller's responsibility to set
    17  // the desired propagation mode of path's parent mount beforehand to prevent
    18  // unwanted propagation into different mount namespaces.
    19  func SwitchRoot(path string) error {
    20  	if mounted, _ := mountinfo.Mounted(path); !mounted {
    21  		if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil {
    22  			return realChroot(path)
    23  		}
    24  	}
    25  
    26  	// setup oldRoot for pivot_root
    27  	pivotDir, err := os.MkdirTemp(path, ".pivot_root")
    28  	if err != nil {
    29  		return fmt.Errorf("Error setting up pivot dir: %v", err)
    30  	}
    31  
    32  	var mounted bool
    33  	defer func() {
    34  		if mounted {
    35  			// make sure pivotDir is not mounted before we try to remove it
    36  			if errCleanup := unix.Unmount(pivotDir, unix.MNT_DETACH); errCleanup != nil {
    37  				if err == nil {
    38  					err = errCleanup
    39  				}
    40  				return
    41  			}
    42  		}
    43  
    44  		errCleanup := os.Remove(pivotDir)
    45  		// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
    46  		// because we already cleaned it up on failed pivot_root
    47  		if errCleanup != nil && !os.IsNotExist(errCleanup) {
    48  			errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
    49  			if err == nil {
    50  				err = errCleanup
    51  			}
    52  		}
    53  	}()
    54  
    55  	if err := unix.PivotRoot(path, pivotDir); err != nil {
    56  		// If pivot fails, fall back to the normal chroot after cleaning up temp dir
    57  		if err := os.Remove(pivotDir); err != nil {
    58  			return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
    59  		}
    60  		return realChroot(path)
    61  	}
    62  	mounted = true
    63  
    64  	// This is the new path for where the old root (prior to the pivot) has been moved to
    65  	// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
    66  	pivotDir = filepath.Join("/", filepath.Base(pivotDir))
    67  
    68  	if err := unix.Chdir("/"); err != nil {
    69  		return fmt.Errorf("Error changing to new root: %v", err)
    70  	}
    71  
    72  	// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
    73  	if err := unix.Mount("", pivotDir, "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil {
    74  		return fmt.Errorf("Error making old root private after pivot: %v", err)
    75  	}
    76  
    77  	// Now unmount the old root so it's no longer visible from the new root
    78  	if err := unix.Unmount(pivotDir, unix.MNT_DETACH); err != nil {
    79  		return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
    80  	}
    81  	mounted = false
    82  
    83  	return nil
    84  }
    85  
    86  func realChroot(path string) error {
    87  	if err := unix.Chroot(path); err != nil {
    88  		return fmt.Errorf("Error after fallback to chroot: %v", err)
    89  	}
    90  	if err := unix.Chdir("/"); err != nil {
    91  		return fmt.Errorf("Error changing to new root after chroot: %v", err)
    92  	}
    93  	return nil
    94  }