Skip to content

Commit

Permalink
expand variable (un)marshaling and add BootOrder
Browse files Browse the repository at this point in the history
  • Loading branch information
0x5a17ed committed May 8, 2022
1 parent cd64c3c commit 0705b9c
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 44 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ func main() {
- Simple API
- Reading individual Boot options
- Setting next Boot option
- Managing Boot order
30 changes: 29 additions & 1 deletion cmd/example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,29 @@ var (
bootRe = regexp.MustCompile(`^Boot([\da-fA-F]{4})$`)
)

func ListBootOrder(c efivario.Context) error {
_, value, err := efivars.BootOrder.Get(c)
if err != nil {
return err
}

for i, index := range value {
_, lo, err := efivars.Boot(index).Get(c)
if err != nil {
return fmt.Errorf("entry %d (%04[1]X): %w", index, err)
}

pp.Println(map[string]any{
"Order": i,
"Index": index,
"Description": efireader.UTF16NullBytesToString(lo.Description),
"Path": lo.FilePathList.AllText(),
})
}

return nil
}

func ListAllVariables(c efivario.Context) error {
iter, err := c.VariableNames()
if err != nil {
Expand Down Expand Up @@ -76,7 +99,7 @@ func ReadBootEntries(c efivario.Context) error {

fmt.Printf("\nEntry Boot%04X(%[1]d):\n", value)

attrs, lo, err := efivars.Boot(int(value)).Get(c)
attrs, lo, err := efivars.Boot(uint16(int(value))).Get(c)
if err != nil {
return err
}
Expand Down Expand Up @@ -105,6 +128,9 @@ func Run(args []string) error {
var listBootEntries bool
fset.BoolVar(&listBootEntries, "list-boot", false, "list boot entries")

var listBootOrder bool
fset.BoolVar(&listBootOrder, "list-boot-order", false, "list boot order")

var setNextBoot bool
fset.BoolVar(&setNextBoot, "set-next", false, "set next boot option")

Expand All @@ -119,6 +145,8 @@ func Run(args []string) error {

var err error
switch {
case listBootOrder:
err = ListBootOrder(c)
case listBootEntries:
err = ReadBootEntries(c)
case listAllVariables:
Expand Down
4 changes: 2 additions & 2 deletions efi/efivario/readall.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func ReadAll(c Context, name string, guid efiguid.GUID) (
out []byte,
err error,
) {
var hint int64
if hint, err = c.GetSizeHint(name, guid); err != nil {
hint, err := c.GetSizeHint(name, guid)
if err != nil || hint < 0 {
hint = 8
}

Expand Down
35 changes: 28 additions & 7 deletions efi/efivars/bootvariables.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import (
"fmt"

"github.com/0x5a17ed/uefi/efi/efitypes"
"github.com/0x5a17ed/uefi/efi/efivario"
)

const (
BootNextName = "BootNext"
BootCurrentName = "BootCurrent"
BootOrderName = "BootOrder"
)

var (
Expand All @@ -33,7 +33,9 @@ var (
BootNext = Variable[uint16]{
name: BootNextName,
guid: GlobalVariable,
defaultAttrs: efivario.NonVolatile | efivario.BootServiceAccess | efivario.RuntimeAccess,
defaultAttrs: defaultAttrs,
marshal: primitiveMarshaller[uint16],
unmarshal: primitiveUnmarshaller[uint16],
}

// BootCurrent defines the Boot#### option that was selected
Expand All @@ -43,17 +45,36 @@ var (
BootCurrent = Variable[uint16]{
name: BootCurrentName,
guid: GlobalVariable,
defaultAttrs: efivario.NonVolatile | efivario.BootServiceAccess | efivario.RuntimeAccess,
defaultAttrs: defaultAttrs,
marshal: primitiveMarshaller[uint16],
unmarshal: primitiveUnmarshaller[uint16],
}

// BootOrder is an ordered list of the Boot#### options.
//
// The first element in the array is the value for the first
// logical boot option, the second element is the value for
// the second logical boot option, etc. The BootOrder order
// list is used by the firmware’s boot manager as the default
// boot order.
//
// <https://uefi.org/sites/default/files/resources/UEFI_Spec_2_9_2021_03_18.pdf#G7.1346720>
BootOrder = Variable[[]uint16]{
name: BootOrderName,
guid: GlobalVariable,
defaultAttrs: defaultAttrs,
unmarshal: sliceUnmarshaller[uint16],
}
)

// Boot returns an EFI Variable pointing to the boot LoadOption
// for the given index.
//
// <https://uefi.org/sites/default/files/resources/UEFI_Spec_2_9_2021_03_18.pdf#G7.1346720>
func Boot(i int) Variable[efitypes.LoadOption] {
return Variable[efitypes.LoadOption]{
name: fmt.Sprintf("Boot%04X", i),
guid: GlobalVariable,
func Boot(i uint16) Variable[*efitypes.LoadOption] {
return Variable[*efitypes.LoadOption]{
name: fmt.Sprintf("Boot%04X", i),
guid: GlobalVariable,
unmarshal: structUnmarshaller[efitypes.LoadOption],
}
}
81 changes: 81 additions & 0 deletions efi/efivars/marshalling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2022 Arthur Skowronek <[email protected]> and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// <https://www.apache.org/licenses/LICENSE-2.0>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package efivars

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
)

func primitiveUnmarshaller[T any](r io.Reader) (out T, err error) {
err = binary.Read(r, binary.LittleEndian, &out)
return
}

func primitiveMarshaller[T any](w io.Writer, inp T) error {
return binary.Write(w, binary.LittleEndian, inp)
}

func sliceUnmarshaller[T any](r io.Reader) (out []T, err error) {
for i := 0; ; i += 1 {
var item T
err = binary.Read(r, binary.LittleEndian, &item)
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
} else {
err = fmt.Errorf("item #%d: %w", i, err)
}
return
}
out = append(out, item)
}
}

func sliceMarshaller[T any](w io.Writer, inp []T) (err error) {
var buf bytes.Buffer

for i, item := range inp {
err := binary.Write(&buf, binary.LittleEndian, item)
if err != nil {
return fmt.Errorf("item #%d: %w", i, err)
}
}

_, err = buf.WriteTo(w)
return
}

type readerFrom[T any] interface {
io.ReaderFrom
*T
}

func structUnmarshaller[T any, PT readerFrom[T]](r io.Reader) (out *T, err error) {
var value T
_, err = PT(&value).ReadFrom(r)
if err == nil {
out = &value
}
return
}

func structMarshaller[T io.WriterTo](w io.Writer, inp T) (err error) {
_, err = inp.WriteTo(w)
return
}
47 changes: 25 additions & 22 deletions efi/efivars/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,59 @@ package efivars

import (
"bytes"
"encoding/binary"
"fmt"
"io"

"github.com/0x5a17ed/uefi/efi/efiguid"
"github.com/0x5a17ed/uefi/efi/efivario"
)

const (
globalAccess = efivario.BootServiceAccess | efivario.RuntimeAccess

defaultAttrs = efivario.NonVolatile | globalAccess
)

type MarshalFn[T any] func(w io.Writer, inp T) error
type UnmarshalFn[T any] func(r io.Reader) (T, error)

type Variable[T any] struct {
name string
guid efiguid.GUID
defaultAttrs efivario.Attributes

marshal MarshalFn[T]
unmarshal UnmarshalFn[T]
}

func (e Variable[T]) Get(c efivario.Context) (attrs efivario.Attributes, value T, err error) {
if e.unmarshal == nil {
err = fmt.Errorf("efivars/get(%s): unsupported", e.name)
return
}

attrs, data, err := efivario.ReadAll(c, e.name, e.guid)
if err != nil {
err = fmt.Errorf("efivars/get(%s): load: %w", e.name, err)
return
}

buf := bytes.NewReader(data)

var valueInterface any = &value
if reader, ok := (valueInterface).(io.ReaderFrom); ok {
_, err = reader.ReadFrom(buf)
} else {
err = binary.Read(buf, binary.LittleEndian, &value)
}
value, err = e.unmarshal(bytes.NewReader(data))
if err != nil {
err = fmt.Errorf("efivars/get(%s): parse: %w", e.name, err)
return
}

return
}

func (e Variable[T]) SetWithAttributes(c efivario.Context, attrs efivario.Attributes, value T) (err error) {
var buf bytes.Buffer

var valueInterface any = &value
if writer, ok := (valueInterface).(io.WriterTo); ok {
_, err = writer.WriteTo(&buf)
} else {
err = binary.Write(&buf, binary.LittleEndian, value)
}
if err != nil {
return fmt.Errorf("efivars/set(%s): %w", e.name, err)
func (e Variable[T]) SetWithAttributes(c efivario.Context, attrs efivario.Attributes, value T) error {
if e.marshal == nil {
return fmt.Errorf("efivars/set(%s): unsupported", e.name)
}

var buf bytes.Buffer
if err := e.marshal(&buf, value); err != nil {
return fmt.Errorf("efivars/set(%s): write: %w", e.name, err)
}
return c.Set(e.name, e.guid, attrs, buf.Bytes())
}

Expand Down
Loading

0 comments on commit 0705b9c

Please sign in to comment.