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