github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/uefivars/boot/boot.go (about)

     1  // Copyright 2015-2020 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  // SPDX-License-Identifier: BSD-3-Clause
     6  //
     7  
     8  package boot
     9  
    10  import (
    11  	"encoding/binary"
    12  	"fmt"
    13  	"log"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/mvdan/u-root-coreutils/pkg/uefivars"
    18  )
    19  
    20  const (
    21  	BootUUID = "8be4df61-93ca-11d2-aa0d-00e098032b8c"
    22  )
    23  
    24  // BootEntryVar is a boot entry. It will have the name BootXXXX where XXXX is
    25  // hexadecimal.
    26  type BootEntryVar struct {
    27  	Number uint16 // from the var name
    28  	EfiLoadOption
    29  }
    30  
    31  // EfiLoadOption defines the data struct used for vars such as BootXXXX.
    32  //
    33  // As defined in UEFI spec v2.8A:
    34  //
    35  //	typedef struct _EFI_LOAD_OPTION {
    36  //	    UINT32 Attributes;
    37  //	    UINT16 FilePathListLength;
    38  //	    // CHAR16 Description[];
    39  //	    // EFI_DEVICE_PATH_PROTOCOL FilePathList[];
    40  //	    // UINT8 OptionalData[];
    41  //	} EFI_LOAD_OPTION;
    42  type EfiLoadOption struct {
    43  	Attributes         uint32
    44  	FilePathListLength uint16
    45  	Description        string
    46  	FilePathList       EfiDevicePathProtocolList
    47  	OptionalData       []byte
    48  }
    49  type BootEntryVars []*BootEntryVar
    50  
    51  // Gets BootXXXX var, if it exists
    52  func ReadBootVar(num uint16) (*BootEntryVar, error) {
    53  	v, err := uefivars.ReadVar(BootUUID, fmt.Sprintf("Boot%04X", num))
    54  	if err != nil {
    55  		return nil, fmt.Errorf("reading var Boot%04X: %w", num, err)
    56  	}
    57  	return BootVar(v), nil
    58  }
    59  
    60  // Reads BootCurrent, and from there gets the BootXXXX var referenced.
    61  func ReadCurrentBootVar() (*BootEntryVar, error) {
    62  	curr := ReadBootCurrent()
    63  	if curr == nil {
    64  		return nil, nil
    65  	}
    66  	return ReadBootVar(curr.Current)
    67  }
    68  
    69  func (b BootEntryVar) String() string {
    70  	opts, err := uefivars.DecodeUTF16(b.OptionalData)
    71  	if err != nil {
    72  		opts = string(b.OptionalData)
    73  	}
    74  	return fmt.Sprintf("Boot%04X: attrs=0x%x, desc=%q, path=%s, opts=%x", b.Number, b.Attributes, b.Description, b.FilePathList.String(), opts)
    75  }
    76  
    77  // AllBootEntryVars returns list of boot entries (BootXXXX). Note that
    78  // BootCurrent, BootOptionSupport, BootNext, BootOrder, etc do not count as
    79  // boot entries.
    80  func AllBootEntryVars() BootEntryVars {
    81  	// BootEntries() is somewhat redundant, but parses the vars into BootEntryVar{}
    82  	return BootEntries(uefivars.ReadVars(BootEntryFilter))
    83  }
    84  
    85  // AllBootVars returns all uefi vars that use the boot UUID and whose names begin with Boot.
    86  //
    87  // These include:
    88  //
    89  //   - BootXXXX (individual boot entries, XXXX is hex)
    90  //   - BootCurrent (marks whichever BootXXXX entry was used this boot)
    91  //   - BootOptionSupport
    92  //   - BootNext (can specify a particular entry to use next boot)
    93  //   - BootOrder (the order in which entries are tried)
    94  func AllBootVars() uefivars.EfiVars {
    95  	return uefivars.ReadVars(BootVarFilter)
    96  }
    97  
    98  // A VarNameFilter passing boot-related vars. These are a superset of those
    99  // returned by BootEntryFilter.
   100  func BootVarFilter(uuid, name string) bool {
   101  	return uuid == BootUUID && strings.HasPrefix(name, "Boot")
   102  }
   103  
   104  // A VarNameFilter passing boot entries. These are a subset of the vars
   105  // returned by BootVarFilter.
   106  func BootEntryFilter(uuid, name string) bool {
   107  	if !BootVarFilter(uuid, name) {
   108  		return false
   109  	}
   110  	// Boot entries begin with BootXXXX-, where XXXX is hex.
   111  	// First, check for the dash.
   112  	if len(name) != 8 {
   113  		return false
   114  	}
   115  	// Try to parse XXXX as hex. If it parses, it's a boot entry.
   116  	_, err := strconv.ParseUint(name[4:], 16, 16)
   117  	return err == nil
   118  }
   119  
   120  // BootVar decodes an efivar as a boot entry. use IsBootEntry() to screen first.
   121  func BootVar(v uefivars.EfiVar) (b *BootEntryVar) {
   122  	num, err := strconv.ParseUint(v.Name[4:], 16, 16)
   123  	if err != nil {
   124  		log.Printf("error parsing boot var %s: %s", v.Name, err)
   125  	}
   126  	b = new(BootEntryVar)
   127  	b.Number = uint16(num)
   128  	b.Attributes = binary.LittleEndian.Uint32(v.Data[:4])
   129  	b.FilePathListLength = binary.LittleEndian.Uint16(v.Data[4:6])
   130  
   131  	// Description is null-terminated utf16
   132  	var i uint16
   133  	for i = 6; ; i += 2 {
   134  		if v.Data[i] == 0 {
   135  			break
   136  		}
   137  	}
   138  	b.Description, err = uefivars.DecodeUTF16(v.Data[6:i])
   139  	if err != nil {
   140  		log.Printf("reading description: %s (%d -> %x)", err, i, v.Data[6:i])
   141  	}
   142  	b.OptionalData = v.Data[i+2+b.FilePathListLength:]
   143  
   144  	b.FilePathList, err = ParseFilePathList(v.Data[i+2 : i+2+b.FilePathListLength])
   145  	if err != nil {
   146  		log.Printf("parsing FilePathList in %s: %s", b.String(), err)
   147  	}
   148  	return
   149  }
   150  
   151  // BootCurrentVar represents the UEFI BootCurrent var.
   152  type BootCurrentVar struct {
   153  	uefivars.EfiVar
   154  	Current uint16
   155  }
   156  
   157  // BootCurrent returns the BootCurrent var, if any, from the given list.
   158  func BootCurrent(vars uefivars.EfiVars) *BootCurrentVar {
   159  	for _, v := range vars {
   160  		if v.Name == "BootCurrent" {
   161  			return &BootCurrentVar{
   162  				EfiVar:  v,
   163  				Current: uint16(v.Data[1])<<8 | uint16(v.Data[0]),
   164  			}
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  // ReadBootCurrent reads and returns the BootCurrent var.
   171  func ReadBootCurrent() *BootCurrentVar {
   172  	v, err := uefivars.ReadVar(BootUUID, "BootCurrent")
   173  	if err != nil {
   174  		log.Printf("reading uefi BootCurrent var: %s", err)
   175  		return nil
   176  	}
   177  	return &BootCurrentVar{
   178  		EfiVar:  v,
   179  		Current: uint16(v.Data[1])<<8 | uint16(v.Data[0]),
   180  	}
   181  }
   182  
   183  // BootEntries takes a list of efi vars and parses any that are boot entries,
   184  // returning a list of them.
   185  func BootEntries(vars uefivars.EfiVars) (bootvars BootEntryVars) {
   186  	for _, v := range vars {
   187  		if IsBootEntry(v) {
   188  			bootvars = append(bootvars, BootVar(v))
   189  		}
   190  	}
   191  	return
   192  }
   193  
   194  // IsBootEntry returns true if the given var is a boot entry.
   195  func IsBootEntry(e uefivars.EfiVar) bool {
   196  	if e.UUID != BootUUID || len(e.Name) != 8 || e.Name[:4] != "Boot" {
   197  		return false
   198  	}
   199  	_, err := strconv.ParseUint(e.Name[4:], 16, 16)
   200  	return err == nil
   201  }