diff --git a/cmds/cbfs/cbfs.go b/cmds/cbfs/cbfs.go index 8bd6295e..0371944c 100644 --- a/cmds/cbfs/cbfs.go +++ b/cmds/cbfs/cbfs.go @@ -6,7 +6,9 @@ package main import ( "encoding/json" + "errors" "fmt" + "io" "log" "os" "path/filepath" @@ -18,41 +20,42 @@ import ( var debug = flag.BoolP("debug", "d", false, "enable debug prints") -func main() { - flag.Parse() +var errUsage = errors.New("usage: cbfs >") +var errMissingDirectory = errors.New("provide a directory name") - if *debug { +func run(stdout io.Writer, debug bool, args []string) error { + if debug { cbfs.Debug = log.Printf } - a := flag.Args() - if len(a) < 2 { - log.Fatal("Usage: cbfs >") + if len(args) < 2 { + return errUsage } - i, err := cbfs.Open(a[0]) + i, err := cbfs.Open(args[0]) if err != nil { - log.Fatal(err) + return err } - switch a[1] { + switch args[1] { case "list": - fmt.Printf("%s", i.String()) + fmt.Fprintf(stdout, "%s", i.String()) case "json": j, err := json.MarshalIndent(i, " ", " ") if err != nil { - log.Fatal(err) + return err } - fmt.Printf("%s", string(j)) + fmt.Fprintf(stdout, "%s", string(j)) case "extract": - if len(a) != 3 { - log.Fatal("provide a directory name") + if len(args) != 3 { + return errMissingDirectory } - dir := filepath.Join(".", a[2]) + dir := filepath.Join(".", args[2]) err := os.MkdirAll(dir, os.ModePerm) if err != nil { - log.Fatal(err) + return err } + base := i.Area.Offset log.Printf("FMAP base at %x", base) for s := range i.Segs { @@ -64,19 +67,27 @@ func main() { log.Printf("Skipping empty/deleted file at 0x%x", o) } else { log.Printf("Extracting %v from 0x%x, compression: %v", n, o, c) - fpath := filepath.Join(dir, strings.Replace(n, "/", "_", -1)) + fpath := filepath.Join(dir, strings.ReplaceAll(n, "/", "_")) d, err := f.Decompress() if err != nil { - log.Fatal(err) + return err } err = os.WriteFile(fpath, d, 0644) if err != nil { - log.Fatal(err) + return err } } } default: - log.Fatal("?") + return errUsage } + return nil +} + +func main() { + flag.Parse() + if err := run(os.Stdout, *debug, flag.Args()); err != nil { + log.Fatal(err) + } } diff --git a/cmds/cbfs/cbfs_test.go b/cmds/cbfs/cbfs_test.go new file mode 100644 index 00000000..5178b027 --- /dev/null +++ b/cmds/cbfs/cbfs_test.go @@ -0,0 +1,88 @@ +// Copyright 2024 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 ( + "bytes" + "encoding/json" + "errors" + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCBFS(t *testing.T) { + t.Run("usage error", func(t *testing.T) { + err := run(io.Discard, false, []string{"list"}) + if !errors.Is(err, errUsage) { + t.Errorf("expected %v, got nil", errUsage) + } + }) + + t.Run("unknown command", func(t *testing.T) { + err := run(io.Discard, false, []string{"testdata/coreboot.rom", "unknown"}) + if !errors.Is(err, errUsage) { + t.Errorf("expected %v, got nil", errUsage) + } + }) + + t.Run("list command", func(t *testing.T) { + stdout := &bytes.Buffer{} + err := run(stdout, false, []string{"testdata/coreboot.rom", "list"}) + if err != nil { + t.Fatalf("expected nil got %v", err) + } + + if !strings.Contains(stdout.String(), "fallback/ramstage") { + t.Errorf("output doesn't contain `fallback/ramstage`, %s", stdout.String()) + } + }) + + t.Run("list json", func(t *testing.T) { + stdout := &bytes.Buffer{} + err := run(stdout, false, []string{"testdata/coreboot.rom", "json"}) + if err != nil { + t.Fatalf("expected nil got %v", err) + } + + if !strings.Contains(stdout.String(), "fallback/ramstage") { + t.Errorf("output doesn't contain `fallback/ramstage`, %s", stdout.String()) + } + + j := make(map[string]any) + err = json.Unmarshal(stdout.Bytes(), &j) + if err != nil { + t.Errorf("expected json output, got unmarshal error: %v", err) + } + }) + + t.Run("extract missing dir", func(t *testing.T) { + err := run(io.Discard, false, []string{"testdata/coreboot.rom", "extract"}) + if !errors.Is(err, errMissingDirectory) { + t.Errorf("expected %v, got nil", errMissingDirectory) + } + }) + + t.Run("extract", func(t *testing.T) { + dir := t.TempDir() + // save local path + romPath, err := filepath.Abs("testdata/coreboot.rom") + if err != nil { + t.Fatal(err) + } + + err = os.Chdir(dir) + if err != nil { + t.Fatal(err) + } + + err = run(io.Discard, false, []string{romPath, "extract", "firmware"}) + if err != nil { + t.Fatalf("expected nil, got %v", err) + } + }) +} diff --git a/cmds/cbfs/testdata/coreboot.rom b/cmds/cbfs/testdata/coreboot.rom new file mode 100644 index 00000000..0bd1b6f6 Binary files /dev/null and b/cmds/cbfs/testdata/coreboot.rom differ