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