github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/cmds/switch_root/switch_root.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  // +build linux
     6  
     7  package main
     8  
     9  import (
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  
    17  	"golang.org/x/sys/unix"
    18  )
    19  
    20  var (
    21  	help    = flag.Bool("h", false, "Help")
    22  	version = flag.Bool("V", false, "Version")
    23  )
    24  
    25  func usage() string {
    26  	return "switch_root [-h] [-V]\nswitch_root newroot init"
    27  }
    28  
    29  // getDev returns the device (as returned by the FSTAT syscall) for the given file descriptor.
    30  func getDev(fd int) (dev uint64, err error) {
    31  	var stat unix.Stat_t
    32  
    33  	if err := unix.Fstat(fd, &stat); err != nil {
    34  		return 0, err
    35  	}
    36  
    37  	return stat.Dev, nil
    38  }
    39  
    40  // recursiveDelete deletes a directory identified by `fd` and everything in it.
    41  //
    42  // This function allows deleting directories no longer referenceable by
    43  // any file name. This function does not descend into mounts.
    44  func recursiveDelete(fd int) error {
    45  	parentDev, err := getDev(fd)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	// The file descriptor is already open, but allocating a os.File
    51  	// here makes reading the files in the dir so much nicer.
    52  	dir := os.NewFile(uintptr(fd), "__ignored__")
    53  	defer dir.Close()
    54  	names, err := dir.Readdirnames(-1)
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	for _, name := range names {
    60  		// Loop here, but handle loop in separate function to make defer work as expected.
    61  		if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
    62  			return err
    63  		}
    64  	}
    65  	return nil
    66  }
    67  
    68  // recusiveDeleteInner is called from recursiveDelete and either deletes
    69  // or recurses into the given file or directory
    70  //
    71  // There should be no need to call this function directly.
    72  func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
    73  	// O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all symlinks (even when pointing to a dir).
    74  	// We need to filter out symlinks because getDev later follows them.
    75  	childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
    76  	if err != nil {
    77  		// childName points to either a file or a symlink, delete in any case.
    78  		if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
    79  			return err
    80  		}
    81  	} else {
    82  		// Open succeeded, which means childName points to a real directory.
    83  		defer unix.Close(childFd)
    84  
    85  		// Don't descent into other file systems.
    86  		if childFdDev, err := getDev(childFd); err != nil {
    87  			return err
    88  		} else if childFdDev != parentDev {
    89  			// This means continue in recursiveDelete.
    90  			return nil
    91  		}
    92  
    93  		if err := recursiveDelete(childFd); err != nil {
    94  			return err
    95  		}
    96  		// Back from recursion, the directory is now empty, delete.
    97  		if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
    98  			return err
    99  		}
   100  	}
   101  	return nil
   102  }
   103  
   104  // execCommand execs into the given command.
   105  //
   106  // In order to preserve whatever PID this program is running with,
   107  // the implementation does an actual EXEC syscall without forking.
   108  func execCommand(path string) error {
   109  	return unix.Exec(path, []string{path}, []string{})
   110  }
   111  
   112  // isEmpty returns true if the directory with the given path is empty.
   113  func isEmpty(name string) (bool, error) {
   114  	f, err := os.Open(name)
   115  	if err != nil {
   116  		return false, err
   117  	}
   118  	defer f.Close()
   119  
   120  	if _, err := f.Readdirnames(1); err == io.EOF {
   121  		return true, nil
   122  	}
   123  	return false, err
   124  }
   125  
   126  // moveMount moves mount
   127  //
   128  // This function is just a wrapper around the MOUNT syscall with the
   129  // MOVE flag supplied.
   130  func moveMount(oldPath string, newPath string) error {
   131  	return unix.Mount(oldPath, newPath, "", unix.MS_MOVE, "")
   132  }
   133  
   134  // specialFS moves the 'special' mounts to the given target path
   135  //
   136  // 'special' in this context refers to the following non-blockdevice backed
   137  // mounts that are almost always used: /dev, /proc, /sys, and /run.
   138  // This function will create the target directories, if necessary.
   139  // If the target directories already exists, they must be empty.
   140  // This function skips missing mounts.
   141  func specialFS(newRoot string) error {
   142  	var mounts = []string{"/dev", "/proc", "/sys", "/run"}
   143  
   144  	for _, mount := range mounts {
   145  		path := filepath.Join(newRoot, mount)
   146  		// Skip all mounting if the directory does not exists.
   147  		if _, err := os.Stat(mount); os.IsNotExist(err) {
   148  			fmt.Println("switch_root: Skipping", mount)
   149  			continue
   150  		} else if err != nil {
   151  			return err
   152  		}
   153  		// Make sure the target dir exists and is empty.
   154  		if _, err := os.Stat(path); os.IsNotExist(err) {
   155  			if err := unix.Mkdir(path, 0); err != nil {
   156  				return err
   157  			}
   158  		} else if err != nil {
   159  			return err
   160  		}
   161  		if empty, err := isEmpty(path); err != nil {
   162  			return err
   163  		} else if !empty {
   164  			return fmt.Errorf("%v must be empty", path)
   165  		}
   166  		if err := moveMount(mount, path); err != nil {
   167  			return err
   168  		}
   169  	}
   170  	return nil
   171  }
   172  
   173  // switchroot moves special mounts (dev, proc, sys, run) to the new directory,
   174  // then does a chroot, moves the root mount to the new directory and finally
   175  // DELETES EVERYTHING in the old root and execs the given init.
   176  func switchRoot(newRoot string, init string) error {
   177  	log.Printf("switch_root: moving mounts")
   178  	if err := specialFS(newRoot); err != nil {
   179  		return fmt.Errorf("switch_root: moving mounts failed %v", err)
   180  	}
   181  
   182  	log.Printf("switch_root: Changing directory")
   183  	if err := unix.Chdir(newRoot); err != nil {
   184  		return fmt.Errorf("switch_root: failed change directory to new_root %v", err)
   185  	}
   186  
   187  	// Open "/" now, we need the file descriptor later.
   188  	oldRoot, err := os.Open("/")
   189  	if err != nil {
   190  		return err
   191  	}
   192  	defer oldRoot.Close()
   193  
   194  	log.Printf("switch_root: Moving /")
   195  	if err := moveMount(newRoot, "/"); err != nil {
   196  		return err
   197  	}
   198  
   199  	log.Printf("switch_root: Changing root!")
   200  	if err := unix.Chroot("."); err != nil {
   201  		return fmt.Errorf("switch_root: fatal chroot error %v", err)
   202  	}
   203  
   204  	log.Printf("switch_root: Deleting old /")
   205  	if err := recursiveDelete(int(oldRoot.Fd())); err != nil {
   206  		panic(err)
   207  	}
   208  
   209  	log.Printf("switch_root: executing init")
   210  	if err := execCommand(init); err != nil {
   211  		return fmt.Errorf("switch_root: exec failed %v", err)
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  func main() {
   218  	flag.Parse()
   219  
   220  	if len(flag.Args()) == 0 {
   221  		fmt.Println(usage())
   222  		os.Exit(0)
   223  	}
   224  
   225  	if *help {
   226  		fmt.Println(usage())
   227  		os.Exit(0)
   228  	}
   229  
   230  	if *version {
   231  		fmt.Println("Version XX")
   232  		os.Exit(0)
   233  	}
   234  
   235  	newRoot := flag.Args()[0]
   236  	init := flag.Args()[1]
   237  
   238  	if err := switchRoot(newRoot, init); err != nil {
   239  		log.Fatalf("switch_root failed %v\n", err)
   240  	}
   241  }