github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/cmds/boot/localboot/main.go (about)

     1  // Copyright 2017-2019 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 main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"path"
    13  	"syscall"
    14  
    15  	"github.com/u-root/u-root/pkg/bootconfig"
    16  	"github.com/u-root/u-root/pkg/storage"
    17  )
    18  
    19  // TODO backward compatibility for BIOS mode with partition type 0xee
    20  // TODO use a proper parser for grub config (see grub.go)
    21  
    22  var (
    23  	flagBaseMountPoint = flag.String("m", "/mnt", "Base mount point where to mount partitions")
    24  	flagDryRun         = flag.Bool("dryrun", false, "Do not actually kexec into the boot config")
    25  	flagDebug          = flag.Bool("d", false, "Print debug output")
    26  	flagConfigIdx      = flag.Int("config", -1, "Specify the index of the configuration to boot. The order is determined by the menu entries in the Grub config")
    27  	flagGrubMode       = flag.Bool("grub", false, "Use GRUB mode, i.e. look for valid Grub/Grub2 configuration in default locations to boot a kernel. GRUB mode ignores -kernel/-initramfs/-cmdline")
    28  	flagKernelPath     = flag.String("kernel", "", "Specify the path of the kernel to execute. If using -grub, this argument is ignored")
    29  	flagInitramfsPath  = flag.String("initramfs", "", "Specify the path of the initramfs to load. If using -grub, this argument is ignored")
    30  	flagKernelCmdline  = flag.String("cmdline", "", "Specify the kernel command line. If using -grub, this argument is ignored")
    31  	flagDeviceGUID     = flag.String("guid", "", "GUID of the device where the kernel (and optionally initramfs) are located. Ignored if -grub is set or if -kernel is not specified")
    32  )
    33  
    34  var debug = func(string, ...interface{}) {}
    35  
    36  // mountByGUID looks for a partition with the given GUID, and tries to mount it
    37  // in a subdirectory under the specified mount point. The subdirectory has the
    38  // same name of the device (e.g. /your/base/mountpoint/sda1).
    39  // The specified filesystems will be used in the mount attempts.
    40  // If more than one partition is found with the given GUID, the first that is
    41  // found is used.
    42  // This function returns a storage.Mountpoint object, or an error if any.
    43  func mountByGUID(devices []storage.BlockDev, filesystems []string, guid, baseMountpoint string) (*storage.Mountpoint, error) {
    44  	log.Printf("Looking for partition with GUID %s", guid)
    45  	partitions, err := storage.PartitionsByGUID(devices, guid)
    46  	if err != nil || len(partitions) == 0 {
    47  		return nil, fmt.Errorf("Error looking up for partition with GUID %s", guid)
    48  	}
    49  	log.Printf("Partitions with GUID %s: %+v", guid, partitions)
    50  	if len(partitions) > 1 {
    51  		log.Printf("Warning: more than one partition found with the given GUID. Using the first one")
    52  	}
    53  	dev := partitions[0]
    54  	mountpath := path.Join(baseMountpoint, dev.Name)
    55  	devname := path.Join("/dev", dev.Name)
    56  	mountpoint, err := storage.Mount(devname, mountpath, filesystems)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("mountByGUID: cannot mount %s (GUID %s) on %s: %v", devname, guid, mountpath, err)
    59  	}
    60  	return mountpoint, nil
    61  }
    62  
    63  // BootGrubMode tries to boot a kernel in GRUB mode. GRUB mode means:
    64  // * look for the partition with the specified GUID, and mount it
    65  // * if no GUID is specified, mount all of the specified devices
    66  // * try to mount the device(s) using any of the kernel-supported filesystems
    67  // * look for a GRUB configuration in various well-known locations
    68  // * build a list of valid boot configurations from the found GRUB configuration files
    69  // * try to boot every valid boot configuration until one succeeds
    70  //
    71  // The first parameter, `devices` is a list of storage.BlockDev . The function
    72  // will look for bootable configurations on these devices
    73  // The second parameter, `baseMountPoint`, is the directory where the mount
    74  // points for each device will be created.
    75  // The third parameter, `guid`, is the partition GUID to look for. If it is an
    76  // empty string, will search boot configurations on all of the specified devices
    77  // instead.
    78  // The fourth parameter, `dryrun`, will not boot the found configurations if set
    79  // to true.
    80  func BootGrubMode(devices []storage.BlockDev, baseMountpoint string, guid string, dryrun bool, configIdx int) error {
    81  	// get a list of supported file systems for real devices (i.e. skip nodev)
    82  	debug("Getting list of supported filesystems")
    83  	filesystems, err := storage.GetSupportedFilesystems()
    84  	if err != nil {
    85  		log.Fatal(err)
    86  	}
    87  	debug("Supported file systems: %v", filesystems)
    88  
    89  	var mounted []storage.Mountpoint
    90  	if guid == "" {
    91  		// try mounting all the available devices, with all the supported file
    92  		// systems
    93  		debug("trying to mount all the available block devices with all the supported file system types")
    94  		mounted = make([]storage.Mountpoint, 0)
    95  		for _, dev := range devices {
    96  			devname := path.Join("/dev", dev.Name)
    97  			mountpath := path.Join(baseMountpoint, dev.Name)
    98  			if mountpoint, err := storage.Mount(devname, mountpath, filesystems); err != nil {
    99  				debug("Failed to mount %s on %s: %v", devname, mountpath, err)
   100  			} else {
   101  				mounted = append(mounted, *mountpoint)
   102  			}
   103  		}
   104  		log.Printf("mounted: %+v", mounted)
   105  		defer func() {
   106  			// clean up
   107  			for _, mountpoint := range mounted {
   108  				syscall.Unmount(mountpoint.Path, syscall.MNT_DETACH)
   109  			}
   110  		}()
   111  	} else {
   112  		mount, err := mountByGUID(devices, filesystems, guid, baseMountpoint)
   113  		if err != nil {
   114  			return err
   115  		}
   116  		mounted = []storage.Mountpoint{*mount}
   117  	}
   118  
   119  	// search for a valid grub config and extracts the boot configuration
   120  	bootconfigs := make([]bootconfig.BootConfig, 0)
   121  	for _, mountpoint := range mounted {
   122  		bootconfigs = append(bootconfigs, ScanGrubConfigs(devices, mountpoint.Path)...)
   123  	}
   124  	if len(bootconfigs) == 0 {
   125  		return fmt.Errorf("No boot configuration found")
   126  	}
   127  	log.Printf("Found %d boot configs", len(bootconfigs))
   128  	for _, cfg := range bootconfigs {
   129  		debug("%+v", cfg)
   130  	}
   131  	for n, cfg := range bootconfigs {
   132  		log.Printf("  %d: %s\n", n, cfg.Name)
   133  	}
   134  	if configIdx > -1 {
   135  		for n, cfg := range bootconfigs {
   136  			if configIdx == n {
   137  				if dryrun {
   138  					debug("Dry-run mode: will not boot the found configuration")
   139  					debug("Boot configuration: %+v", cfg)
   140  					return nil
   141  				}
   142  				if err := cfg.Boot(); err != nil {
   143  					log.Printf("Failed to boot kernel %s: %v", cfg.Kernel, err)
   144  				}
   145  			}
   146  		}
   147  		log.Printf("Invalid arg -config %d: there are only %d bootconfigs available\n", configIdx, len(bootconfigs))
   148  		return nil
   149  	}
   150  	if dryrun {
   151  		cfg := bootconfigs[0]
   152  		debug("Dry-run mode: will not boot the found configuration")
   153  		debug("Boot configuration: %+v", cfg)
   154  		return nil
   155  	}
   156  
   157  	// try to kexec into every boot config kernel until one succeeds
   158  	for _, cfg := range bootconfigs {
   159  		debug("Trying boot configuration %+v", cfg)
   160  		if err := cfg.Boot(); err != nil {
   161  			log.Printf("Failed to boot kernel %s: %v", cfg.Kernel, err)
   162  		}
   163  	}
   164  	// if we reach this point, no boot configuration succeeded
   165  	log.Print("No boot configuration succeeded")
   166  
   167  	return nil
   168  }
   169  
   170  // BootPathMode tries to boot a kernel in PATH mode. This means:
   171  // * look for a partition with the given GUID and mount it
   172  // * look for the kernel and initramfs in the provided locations
   173  // * boot the kernel with the provided command line
   174  //
   175  // The first parameter, `devices` is a list of storage.BlockDev . The function
   176  // will look for bootable configurations on these devices
   177  // The second parameter, `baseMountPoint`, is the directory where the mount
   178  // points for each device will be created.
   179  // The third parameter, `guid`, is the partition GUID to look for.
   180  // The fourth parameter, `dryrun`, will not boot the found configurations if set
   181  // to true.
   182  func BootPathMode(devices []storage.BlockDev, baseMountpoint string, guid string, dryrun bool) error {
   183  	debug("Getting list of supported filesystems")
   184  	filesystems, err := storage.GetSupportedFilesystems()
   185  	if err != nil {
   186  		log.Fatal(err)
   187  	}
   188  	debug("Supported file systems: %v", filesystems)
   189  
   190  	mount, err := mountByGUID(devices, filesystems, guid, baseMountpoint)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	fullKernelPath := path.Join(mount.Path, *flagKernelPath)
   196  	fullInitramfsPath := path.Join(mount.Path, *flagInitramfsPath)
   197  	cfg := bootconfig.BootConfig{
   198  		Kernel:     fullKernelPath,
   199  		Initramfs:  fullInitramfsPath,
   200  		KernelArgs: *flagKernelCmdline,
   201  	}
   202  	debug("Trying boot configuration %+v", cfg)
   203  	if dryrun {
   204  		log.Printf("Dry-run, will not actually boot")
   205  	} else {
   206  		if err := cfg.Boot(); err != nil {
   207  			return fmt.Errorf("Failed to boot kernel %s: %v", cfg.Kernel, err)
   208  		}
   209  	}
   210  	return nil
   211  }
   212  
   213  func main() {
   214  	flag.Parse()
   215  	if *flagGrubMode && *flagKernelPath != "" {
   216  		log.Fatal("Options -grub and -kernel are mutually exclusive")
   217  	}
   218  	if *flagDebug {
   219  		debug = log.Printf
   220  	}
   221  
   222  	// Get all the available block devices
   223  	devices, err := storage.GetBlockStats()
   224  	if err != nil {
   225  		log.Fatal(err)
   226  	}
   227  	// print partition info
   228  	if *flagDebug {
   229  		for _, dev := range devices {
   230  			log.Printf("Device: %+v", dev)
   231  			table, err := storage.GetGPTTable(dev)
   232  			if err != nil {
   233  				continue
   234  			}
   235  			log.Printf("  Table: %+v", table)
   236  			for _, part := range table.Partitions {
   237  				log.Printf("    Partition: %+v\n", part)
   238  				if !part.IsEmpty() {
   239  					log.Printf("      UUID: %s\n", part.Type.String())
   240  				}
   241  			}
   242  		}
   243  	}
   244  
   245  	// TODO boot from EFI system partitions. See storage.FilterEFISystemPartitions
   246  
   247  	if *flagGrubMode {
   248  		if err := BootGrubMode(devices, *flagBaseMountPoint, *flagDeviceGUID, *flagDryRun, *flagConfigIdx); err != nil {
   249  			log.Fatal(err)
   250  		}
   251  	} else if *flagKernelPath != "" {
   252  		if err := BootPathMode(devices, *flagBaseMountPoint, *flagDeviceGUID, *flagDryRun); err != nil {
   253  			log.Fatal(err)
   254  		}
   255  	} else {
   256  		log.Fatal("You must specify either -grub or -kernel")
   257  	}
   258  	os.Exit(1)
   259  }