github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/systembooter/localbooter.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 systembooter
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"os/exec"
    13  )
    14  
    15  // LocalBooter implements the Booter interface for booting from local storage.
    16  type LocalBooter struct {
    17  	Type       string `json:"type"`
    18  	Method     string `json:"method"`
    19  	DeviceGUID string `json:"device_guid"`
    20  	Kernel     string `json:"kernel,omitempty"`
    21  	KernelArgs string `json:"kernel_args,omitempty"`
    22  	Initramfs  string `json:"ramfs,omitempty"`
    23  }
    24  
    25  // NewLocalBooter parses a boot entry config and returns a Booter instance, or
    26  // an error if any
    27  func NewLocalBooter(config []byte) (Booter, error) {
    28  	/*
    29  		The configuration format for a LocalBooter entry is a JSON with the following structure:
    30  
    31  		{
    32  			"type": "localboot",
    33  			"method": "<method>",
    34  			"device_guid": "<device GUID or empty>"
    35  			"kernel": "<kernel path or empty>",
    36  			"kernel_args": "<kernel args or empty>",
    37  			"ramfs": "<ramfs path or empty>",
    38  		}
    39  
    40  		`type` is always set to "localboot"
    41  		`method` can be either "grub" or "path".
    42  		    The "grub" method will look for grub.cfg or grub2.cfg on the specified device.
    43  		    If no device is specified, it will look on all the attached storage devices,
    44  		    sorted alphabetically as found in /dev. The first grub configuration that is
    45  		    found is parsed, and kernel, kernel args and ramfs are extracted. Then the
    46  		    kernel will be kexec'ed. If this fails, the next entry will NOT be tried,
    47  		    and no other grub configs will be scanned. In case a grub config has no
    48  		    valid boot entries, it is ignored and the next config will be used tried.
    49  		    The "path" method requires a device GUID and kernel path to be specified. If
    50  		    specified, it will also use kernel args and ramfs path. This method will look
    51  		    for the given kernel on the given device, and will kexec the kernel using the
    52  		    given, optional, kernel args and ramfs.
    53  		`device_guid` is the GUID of the device to look for grub config or kernel and ramfs
    54  		`kernel` is the path, relative to the device specified by `device_guid`, of the
    55  		    kernel to be kexec'ed
    56  		`kernel_args` is the optional string of kernel arguments to be passed.
    57  		`ramfs` is the path, relative to the device specified by `device_guid`, of the ramfs
    58  		    to be used for kexec'ing into the target kernel.
    59  	*/
    60  	log.Printf("Trying LocalBooter...")
    61  	log.Printf("Config: %s", string(config))
    62  	lb := LocalBooter{}
    63  	if err := json.Unmarshal(config, &lb); err != nil {
    64  		return nil, err
    65  	}
    66  	log.Printf("LocalBooter: %+v", lb)
    67  	if lb.Type != "localboot" {
    68  		return nil, fmt.Errorf("wrong type for LocalBooter: %s", lb.Type)
    69  	}
    70  	// the actual arguments validation is done in `Boot` to avoid duplicate code
    71  	return &lb, nil
    72  }
    73  
    74  // Boot will run the boot procedure. In the case of LocalBooter, it will call
    75  // the `localboot` command
    76  func (lb *LocalBooter) Boot(debugEnabled bool) error {
    77  	var bootcmd []string
    78  	if debugEnabled {
    79  		bootcmd = []string{"localboot", "-d"}
    80  	} else {
    81  		bootcmd = []string{"localboot"}
    82  	}
    83  
    84  	// validate arguments
    85  	if lb.Method == "grub" {
    86  		bootcmd = append(bootcmd, "-grub")
    87  	} else if lb.Method == "path" {
    88  		bootcmd = append(bootcmd, []string{"-kernel", lb.Kernel}...)
    89  		bootcmd = append(bootcmd, []string{"-guid", lb.DeviceGUID}...)
    90  		if lb.Initramfs != "" {
    91  			bootcmd = append(bootcmd, []string{"-initramfs", lb.Initramfs}...)
    92  		}
    93  		if lb.KernelArgs != "" {
    94  			bootcmd = append(bootcmd, []string{"-cmdline", lb.KernelArgs}...)
    95  		}
    96  	} else {
    97  		return fmt.Errorf("unknown boot method %s", lb.Method)
    98  	}
    99  
   100  	log.Printf("Executing command: %v", bootcmd)
   101  	cmd := exec.Command(bootcmd[0], bootcmd[1:]...)
   102  	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
   103  	if err := cmd.Run(); err != nil {
   104  		log.Printf("Error executing %v: %v", cmd, err)
   105  	}
   106  	return nil
   107  }
   108  
   109  // TypeName returns the name of the booter type
   110  func (lb *LocalBooter) TypeName() string {
   111  	return lb.Type
   112  }