Skip to content

Commit

Permalink
WIP: create-ffs (linuxboot#239)
Browse files Browse the repository at this point in the history
create-ffs:
A very rudimentary create-ffs command

Signed-off-by: Gan Shun Lim <[email protected]>
  • Loading branch information
GanShun authored Nov 5, 2021
1 parent 43cb739 commit 11c2c1a
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ cmds/futk/futk
cmds/glzma/glzma
cmds/guid2english/guid2english
cmds/utk/utk
cmds/create-ffs/create-ffs
cmds/futk/futk
cmds/fspinfo/fspinfo
.vimrc
Expand Down
4 changes: 4 additions & 0 deletions cmds/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore binaries (files without extensions)
*
!*.*
!*/
212 changes: 212 additions & 0 deletions cmds/create-ffs/create-ffs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright 2019 the LinuxBoot Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"crypto/sha1"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"

"github.com/linuxboot/fiano/pkg/guid"
"github.com/linuxboot/fiano/pkg/uefi"
"github.com/linuxboot/fiano/pkg/visitors"
)

var (
debug = flag.Bool("d", false, "Enable debug prints")
outfile = flag.String("o", "", "output file, default is stdout")
name = flag.String("name", "", "Name to include in UI section")
filetype = flag.String("type", "DRIVER", "UEFI filetype")
version = flag.String("version", "1.0", "File version")
guidString = flag.String("guid", "", "File GUID")
depex = flag.String("depex", "", "Space or comma separated protocol guid dependencies or TRUE")
compress = flag.Bool("compress", false, "Wrap section data in a compressed section")
auto = flag.Bool("auto", false, "Attempt to determine section types from file extensions")

printf = func(string, ...interface{}) {}
)

const (
usageString = "Usage: create-ffs [flags] file.efi"
)

func createDepExes(deps string) ([]uefi.DepExOp, error) {
var err error
ops := []uefi.DepExOp{}

if strings.ToUpper(deps) == "TRUE" {
// Create just "TRUE" and "END" for now, but this feels unnecessary to me.
ops = append(ops, uefi.DepExOp{OpCode: "TRUE"})
ops = append(ops, uefi.DepExOp{OpCode: "END"})
return ops, nil
}

// we expect a space or comma separated list of GUIDs, split by both.
guidSpace := strings.Split(deps, " ")
guids := []string{}
for _, g := range guidSpace {
guids = append(guids, strings.Split(g, ",")...)
}

for _, guidStr := range guids {
var g *guid.GUID

if g, err = guid.Parse(guidStr); err != nil {
return nil, err
}
printf("depex guid requested: %v", *g)
ops = append(ops, uefi.DepExOp{OpCode: "PUSH", GUID: g})
}

// Append an "AND" for n-1 pushes.
for i := 1; i < len(guids); i++ {
ops = append(ops, uefi.DepExOp{OpCode: "AND"})
}

ops = append(ops, uefi.DepExOp{OpCode: "END"})
return ops, nil
}

func parseFlags() (fType uefi.FVFileType, fGUID *guid.GUID, depOps []uefi.DepExOp, err error) {
var ok bool

if *debug {
printf = log.Printf
}

// Check filetypes
fType, ok = uefi.NamesToFileType[*filetype]
if !ok {
validTypes := []string{}
for k := range uefi.NamesToFileType {
validTypes = append(validTypes, k)
}
err = fmt.Errorf("unable to get EFI File type, got %v, expected values in\n{%v}",
*filetype, strings.Join(validTypes, ", "))
return
}

if *guidString != "" {
fGUID, err = guid.Parse(*guidString)
if err != nil {
return
}
} else if *name != "" {
// We sha1 the name to get a reproducible GUID.
fGUID = &guid.GUID{}
sum := sha1.Sum([]byte(*name))
copy(fGUID[:], sum[:guid.Size])
} else {
err = errors.New("no GUID or name provided, please provide at least one")
return
}

if *outfile == "" {
err = errors.New("we don't currently support dumping to stdout, please specify an output file")
return
}

if *depex != "" {
depOps, err = createDepExes(*depex)
if err != nil {
err = fmt.Errorf("can't parse depex guids, got %v", err)
}
}

return
}

func usage() {
log.Print(usageString)
flag.PrintDefaults()
os.Exit(1)
}

func main() {
var fType uefi.FVFileType
var fGUID *guid.GUID
var depOps []uefi.DepExOp
var err error

flag.Parse()
if fType, fGUID, depOps, err = parseFlags(); err != nil {
log.Fatal(err)
}

if flag.NArg() != 1 {
usage()
}

secData, err := ioutil.ReadFile(flag.Arg(0))
if err != nil {
log.Fatal(err)
}

printf("type requested: %v", fType)
printf("name requested: %v", *name)
printf("version requested: %v", *version)
printf("guid: %v", fGUID)

secType := uefi.SectionTypeRaw
switch fType {
case uefi.FVFileTypeApplication, uefi.FVFileTypeCombinedSMMDXE,
uefi.FVFileTypeCombinedPEIMDriver, uefi.FVFileTypeDriver:
secType = uefi.SectionTypePE32
}

file := &uefi.File{}
file.Header.Type = fType
file.Header.GUID = *fGUID
file.Header.State = 0xF8
file.Header.Attributes = 0x40

mainSection := &uefi.Section{}
mainSection.SetType(secType)
mainSection.SetBuf(secData)
mainSection.GenSecHeader()
file.Sections = append(file.Sections, mainSection)
printf("selected section type: %v", mainSection.Header.Type)

if *name != "" {
s := &uefi.Section{}
s.SetType(uefi.SectionTypeUserInterface)
s.Name = *name
file.Sections = append(file.Sections, s)
}

if *version != "" {
s := &uefi.Section{}
s.SetType(uefi.SectionTypeVersion)
s.Version = *version
file.Sections = append(file.Sections, s)
}

if *depex != "" {
s := &uefi.Section{}
s.SetType(uefi.SectionTypeDXEDepEx)
s.DepEx = depOps
file.Sections = append(file.Sections, s)
}

save := &visitors.Save{DirPath: *outfile}

err = file.Apply(save)
if err != nil {
log.Fatal(err)
}

if *debug {
// Dump file json for checking
jsonv := &visitors.JSON{W: os.Stdout}
if err = file.Apply(jsonv); err != nil {
log.Fatal(err)
}
}
}
95 changes: 95 additions & 0 deletions cmds/create-ffs/create-ffs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2019 the LinuxBoot Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"testing"

"github.com/linuxboot/fiano/pkg/guid"
"github.com/linuxboot/fiano/pkg/uefi"
)

func compareErrors(expected string, err error) error {
if expected != "" {
if err == nil {
return fmt.Errorf("error was not returned, expected %v", expected)
}
if expected != err.Error() {
return fmt.Errorf("mismatched error returned, expected \n%v\n got \n%v",
expected, err.Error())
}
} else if err != nil {
return fmt.Errorf("error was not expected, got %v", err)
}
return nil
}

func compareOps(expectedOps []uefi.DepExOp, ops []uefi.DepExOp) error {
if expectedOps != nil {
if ops == nil {
return fmt.Errorf("expected ops: %v, got nil", expectedOps)
}
if elen, olen := len(expectedOps), len(ops); elen != olen {
return fmt.Errorf("different lenghts of depexes expected \n%v\n got \n%v",
elen, olen)
}
for i := range expectedOps {
if expectedOps[i].OpCode != ops[i].OpCode {
return fmt.Errorf("different opcodes! expected %v, got %v",
expectedOps[i].OpCode, ops[i].OpCode)
}
if expectedOps[i].GUID != nil {
if ops[i].GUID == nil {
return fmt.Errorf("expected GUID %v, got nil",
*expectedOps[i].GUID)
}
if *expectedOps[i].GUID != *ops[i].GUID {
return fmt.Errorf("mismatched GUID, expected %v, got %v",
*expectedOps[i].GUID, *ops[i].GUID)
}
} else if ops[i].GUID != nil {
return fmt.Errorf("expected no GUIDs, got %v", *ops[i].GUID)
}
}
} else if ops != nil {
return fmt.Errorf("expected no ops, got %v", ops)
}
return nil
}

func TestCreateDepExes(t *testing.T) {
var tests = []struct {
name string
input string
ops []uefi.DepExOp
msg string
}{
{"trueDepEx", "TRUE", []uefi.DepExOp{{OpCode: "TRUE"}, {OpCode: "END"}}, ""},
{"pushone", "01234567-89AB-CDEF-0123-456789ABCDEF",
[]uefi.DepExOp{
{OpCode: "PUSH", GUID: guid.MustParse("01234567-89AB-CDEF-0123-456789ABCDEF")},
{OpCode: "END"}}, ""},
{"pushtwo", "01234567-89AB-CDEF-0123-456789ABCDEF 01234567-89AB-CDEF-0123-456789ABCDEF",
[]uefi.DepExOp{
{OpCode: "PUSH", GUID: guid.MustParse("01234567-89AB-CDEF-0123-456789ABCDEF")},
{OpCode: "PUSH", GUID: guid.MustParse("01234567-89AB-CDEF-0123-456789ABCDEF")},
{OpCode: "AND"},
{OpCode: "END"}}, ""},
{"badGUID", "ABC", nil, "guid string not correct, need string of the format \n01234567-89AB-CDEF-0123-456789ABCDEF" +
"\n, got \nABC"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
outputs, err := createDepExes(test.input)
if err = compareErrors(test.msg, err); err != nil {
t.Fatal(err)
}
if err = compareOps(test.ops, outputs); err != nil {
t.Fatal(err)
}
})
}
}
13 changes: 12 additions & 1 deletion pkg/uefi/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"encoding/binary"
"fmt"
"strings"

"github.com/linuxboot/fiano/pkg/guid"
"github.com/linuxboot/fiano/pkg/log"
Expand Down Expand Up @@ -83,6 +84,16 @@ var fileTypeNames = map[FVFileType]string{
FVFileTypeSMMCoreStandalone: "EFI_FV_FILETYPE_MM_CORE_STANDALONE",
}

// NamesToFileType maps from common file type strings to the actual type.
var NamesToFileType map[string]FVFileType

func init() {
NamesToFileType = make(map[string]FVFileType)
for k, v := range fileTypeNames {
NamesToFileType[strings.TrimPrefix(v, "EFI_FV_FILETYPE_")] = k
}
}

// String creates a string representation for the file type.
func (f FVFileType) String() string {
switch {
Expand Down Expand Up @@ -111,7 +122,7 @@ var (

// FileAlignments specifies the correct alignments based on the field in the file header.
var fileAlignments = []uint64{
// These alignments not computable, we have to look them up.
// These alignments are not computable, we have to look them up.
1,
16,
128,
Expand Down
2 changes: 1 addition & 1 deletion pkg/uefi/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var sectionTypeNames = map[SectionType]string{
SectionMMDepEx: "EFI_SECTION_MM_DEPEX",
}

// String creates a string representation for the file type.
// String creates a string representation for the section type.
func (s SectionType) String() string {
if t, ok := sectionTypeNames[s]; ok {
return t
Expand Down
6 changes: 3 additions & 3 deletions pkg/visitors/assemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ func (v *Assemble) Visit(f uefi.Firmware) error {
}

// Otherwise, we reconstruct the entire file from the sections and the
// file header using data from the JSON. This means that some JSON values
// are now respected, including GUID changes. However file lengths and
// checksums will be recalculated.
// file header using data from the JSON/existing header struct. This means
// that some JSON values are now respected, including GUID changes.
// However file lengths and checksums will be recalculated.

// Assemble all sections so we know the final file size. We need to do this
// to know if we need to use the extended header.
Expand Down

0 comments on commit 11c2c1a

Please sign in to comment.