github.com/rminnich/u-root@v7.0.0+incompatible/pkg/mount/switch_root_linux.go (about)

     1  // Copyright 2012-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mount
     6  
     7  import (
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  
    13  	"golang.org/x/sys/unix"
    14  )
    15  
    16  // getDev returns the device (as returned by the FSTAT syscall) for the given file descriptor.
    17  func getDev(fd int) (dev uint64, err error) {
    18  	var stat unix.Stat_t
    19  	if err := unix.Fstat(fd, &stat); err != nil {
    20  		return 0, err
    21  	}
    22  	return uint64(stat.Dev), nil
    23  }
    24  
    25  // recursiveDelete deletes a directory identified by `fd` and everything in it.
    26  //
    27  // This function allows deleting directories no longer referenceable by
    28  // any file name. This function does not descend into mounts.
    29  func recursiveDelete(fd int) error {
    30  	parentDev, err := getDev(fd)
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	// The file descriptor is already open, but allocating a os.File
    36  	// here makes reading the files in the dir so much nicer.
    37  	dir := os.NewFile(uintptr(fd), "__ignored__")
    38  	defer dir.Close()
    39  	names, err := dir.Readdirnames(-1)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	for _, name := range names {
    45  		// Loop here, but handle loop in separate function to make defer work as expected.
    46  		if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
    47  			return err
    48  		}
    49  	}
    50  	return nil
    51  }
    52  
    53  // recusiveDeleteInner is called from recursiveDelete and either deletes
    54  // or recurses into the given file or directory
    55  //
    56  // There should be no need to call this function directly.
    57  func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
    58  	// O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all symlinks (even when pointing to a dir).
    59  	// We need to filter out symlinks because getDev later follows them.
    60  	childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
    61  	if err != nil {
    62  		// childName points to either a file or a symlink, delete in any case.
    63  		if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
    64  			return err
    65  		}
    66  	} else {
    67  		// Open succeeded, which means childName points to a real directory.
    68  		defer unix.Close(childFd)
    69  
    70  		// Don't descent into other file systems.
    71  		if childFdDev, err := getDev(childFd); err != nil {
    72  			return err
    73  		} else if childFdDev != parentDev {
    74  			// This means continue in recursiveDelete.
    75  			return nil
    76  		}
    77  
    78  		if err := recursiveDelete(childFd); err != nil {
    79  			return err
    80  		}
    81  		// Back from recursion, the directory is now empty, delete.
    82  		if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
    83  			return err
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  // MoveMount moves a mount from oldPath to newPath.
    90  //
    91  // This function is just a wrapper around the MOUNT syscall with the
    92  // MOVE flag supplied.
    93  func MoveMount(oldPath string, newPath string) error {
    94  	return unix.Mount(oldPath, newPath, "", unix.MS_MOVE, "")
    95  }
    96  
    97  // addSpecialMounts moves the 'special' mounts to the given target path
    98  //
    99  // 'special' in this context refers to the following non-blockdevice backed
   100  // mounts that are almost always used: /dev, /proc, /sys, and /run.
   101  // This function will create the target directories, if necessary.
   102  // If the target directories already exist, they must be empty.
   103  // This function skips missing mounts.
   104  func addSpecialMounts(newRoot string) error {
   105  	var mounts = []string{"/dev", "/proc", "/sys", "/run"}
   106  
   107  	for _, mount := range mounts {
   108  		path := filepath.Join(newRoot, mount)
   109  		// Skip all mounting if the directory does not exist.
   110  		if _, err := os.Stat(mount); os.IsNotExist(err) {
   111  			log.Printf("switch_root: Skipping %q as the dir does not exist", mount)
   112  			continue
   113  		} else if err != nil {
   114  			return err
   115  		}
   116  		// Also skip if not currently a mount point
   117  		if same, err := SameFilesystem("/", mount); err != nil {
   118  			return err
   119  		} else if same {
   120  			log.Printf("switch_root: Skipping %q as it is not a mount", mount)
   121  			continue
   122  		}
   123  		// Make sure the target dir exists.
   124  		if err := os.MkdirAll(path, 0755); err != nil {
   125  			return err
   126  		}
   127  		if err := MoveMount(mount, path); err != nil {
   128  			return err
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  // SameFilesystem returns true if both paths reside in the same filesystem.
   135  // This is achieved by comparing Stat_t.Dev, which contains the fs device's
   136  // major/minor numbers.
   137  func SameFilesystem(path1, path2 string) (bool, error) {
   138  	var stat1, stat2 unix.Stat_t
   139  	if err := unix.Stat(path1, &stat1); err != nil {
   140  		return false, err
   141  	}
   142  	if err := unix.Stat(path2, &stat2); err != nil {
   143  		return false, err
   144  	}
   145  	return stat1.Dev == stat2.Dev, nil
   146  }
   147  
   148  // SwitchRoot makes newRootDir the new root directory of the system.
   149  //
   150  // To be exact, it makes newRootDir the new root directory of the calling
   151  // process's mount namespace.
   152  //
   153  // It moves special mounts (dev, proc, sys, run) to the new directory, then
   154  // does a chroot, moves the root mount to the new directory and finally
   155  // DELETES EVERYTHING in the old root and execs the given init.
   156  func SwitchRoot(newRootDir string, init string) error {
   157  	err := newRoot(newRootDir)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	return execInit(init)
   162  }
   163  
   164  // newRoot is the "first half" of SwitchRoot - that is, it creates special mounts
   165  // in newRoot, chroot's there, and RECURSIVELY DELETES everything in the old root.
   166  func newRoot(newRootDir string) error {
   167  	log.Printf("switch_root: moving mounts")
   168  	if err := addSpecialMounts(newRootDir); err != nil {
   169  		return fmt.Errorf("switch_root: moving mounts failed %v", err)
   170  	}
   171  
   172  	log.Printf("switch_root: Changing directory")
   173  	if err := unix.Chdir(newRootDir); err != nil {
   174  		return fmt.Errorf("switch_root: failed change directory to new_root %v", err)
   175  	}
   176  
   177  	// Open "/" now, we need the file descriptor later.
   178  	oldRoot, err := os.Open("/")
   179  	if err != nil {
   180  		return err
   181  	}
   182  	defer oldRoot.Close()
   183  
   184  	log.Printf("switch_root: Moving /")
   185  	if err := MoveMount(newRootDir, "/"); err != nil {
   186  		return err
   187  	}
   188  
   189  	log.Printf("switch_root: Changing root!")
   190  	if err := unix.Chroot("."); err != nil {
   191  		return fmt.Errorf("switch_root: fatal chroot error %v", err)
   192  	}
   193  
   194  	log.Printf("switch_root: Deleting old /")
   195  	return recursiveDelete(int(oldRoot.Fd()))
   196  }
   197  
   198  // execInit is generally only useful as part of SwitchRoot or similar.
   199  // It exec's the given binary in place of the current binary, necessary so that
   200  // the new binary can be pid 1.
   201  func execInit(init string) error {
   202  	log.Printf("switch_root: executing init")
   203  	if err := unix.Exec(init, []string{init}, []string{}); err != nil {
   204  		return fmt.Errorf("switch_root: exec failed %v", err)
   205  	}
   206  	return nil
   207  }