Skip to content

Commit

Permalink
Add command for reading a flash map
Browse files Browse the repository at this point in the history
Currently, there is one flag `-s` for summary. It may seem
counter-intuitive for a command to only have one flag, but it will make
more sense with these commands that I have planned:

- `-V`: perform additional verification checks on the fmap (signature is
  defined once, areas are mutually exclusive, areas cover the entire
  flash, ...)
- `-c i`: create a new area at index `i` (shifting other areas). The
  contents for the new area are read from stdin. If the area is
  smaller, move other areas to fill the hole. If the area is larger, move
  areas and increase the flash size to accommodate.
- `-r i`: read the contents at index `i` and pipe to stdout
- `-u i`: update area at index `i` with new data passed into stdin. Same
  rules from `-c i` apply.
- `-d i`: delete area at index `i` from the flash map. Other areas a
  rearranged to fill this hole and the flash is shrunk.

This uses a slightly modified code structure from the other commands. I
am hoping it will lead to better testability and modularity. The command
is split into two directories:

- `fmap/lib/fmap.go`: The logic behind the command is stored here.
  Useful functions are Exported. There is no main function, so other go
  programs can use it as a library. It is *unit tested* by
  `fmap/lib/fmap.go` which calls individual functions.
- `fmap/fmap.go`: This is a simple wrapper whose sole purpose is to
  enable using the library via the command line. It has its own
  *integration test* in `fmap/lib/fmap.go` which tests the output is
  properly written to stdout.
  • Loading branch information
rjoleary committed Jan 23, 2017
1 parent a5da42d commit c2df5d2
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dev
etc/
lib64/
usr/
lib/
/lib/
tinycorelinux*
tmp
*.swp
Expand All @@ -43,4 +43,4 @@ vmlinux

# Python Virtual Env
*.env
*.venv
*.venv
Binary file added cmds/fmap/fake_test.flash
Binary file not shown.
78 changes: 78 additions & 0 deletions cmds/fmap/fmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Fmap parses flash maps.
//
// Synopsis:
// fmap [-p] [FILE]
//
// Description:
// Return 0 if the flash map is valid and 1 otherwise. Detailed information
// is printed to stderr. If FILE is not specified, read from stdin.
//
// This implementation is based off of https://github.com/dhendrix/flashmap.
//
// Options:
// -s: print human readable summary
package main

import (
"flag"
fmap "github.com/u-root/u-root/cmds/fmap/lib"
"log"
"os"
"text/template"
)

var (
summary = flag.Bool("s", false, "print human readable summary")
)

// Print human readable summary of the fmap.
func printFMap(f *fmap.FMap) {
const desc = `Fmap found at {{printf "%#x" .Start}}:
Signature: {{printf "%s" .Signature}}
VerMajor: {{.VerMajor}}
VerMinor: {{.VerMinor}}
Base: {{printf "%#x" .Base}}
Size: {{printf "%#x" .Size}}
Name: {{printf "%s" .Name}}
NAreas: {{len .Areas}}
{{- range $i, $v := .Areas}}
Areas[{{$i}}]:
Offset: {{printf "%#x" $v.Offset}}
Size: {{printf "%#x" $v.Size}}
Name: {{printf "%s" $v.Name}}
Flags: {{printf "%#x" $v.Flags}} ({{FlagNames $v.Flags}})
{{- end}}
`
t := template.Must(template.New("desc").
Funcs(template.FuncMap{"FlagNames": fmap.FlagNames}).
Parse(desc))
if err := t.Execute(os.Stdout, f); err != nil {
log.Fatal(err)
}
}

func main() {
flag.Parse()

// Choose a reader
r := os.Stdin
if flag.NArg() == 1 {
var err error
r, err = os.Open(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
} else if flag.NArg() > 1 {
log.Fatal("Too many arguments")
}

// Read fmap and optionally print summary.
f := fmap.ReadFMap(r)
if *summary {
printFMap(f)
}
}
42 changes: 42 additions & 0 deletions cmds/fmap/fmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2016 the u-root 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"
"os/exec"
"testing"
)

func TestFlashSummary(t *testing.T) {
testFlash := "fake_test.flash"
expected := `Fmap found at 0x9f4:
Signature: __FMAP__
VerMajor: 1
VerMinor: 0
Base: 0xcafebabedeadbeef
Size: 0x44332211
Name: Fake flash
NAreas: 2
Areas[0]:
Offset: 0xdeadbeef
Size: 0x11111111
Name: Area Number 1Hello
Flags: 0x1013 (STATIC|COMPRESSED|0x1010)
Areas[1]:
Offset: 0xcafebabe
Size: 0x22222222
Name: Area Number 2xxxxxxxxxxxxxxxxxxx
Flags: 0x0 (0x0)
`
out, err := exec.Command("go", "run", "fmap.go", "-s", testFlash).CombinedOutput()
if err != nil {
t.Fatal(err)
}
out = bytes.Replace(out, []byte{0}, []byte{}, -1)
if string(out) != expected {
t.Errorf("expected:\n%s\ngot:\n%s", expected, string(out))
}
}
113 changes: 113 additions & 0 deletions cmds/fmap/lib/fmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2017 the u-root 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 fmap

import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"log"
"strings"
)

var signature = []byte("__FMAP__")

const (
FmapAreaStatic = 1 << iota
FmapAreaCompressed
FmapAreaReadOnly
)

type FMap struct {
Start uint64 // not part of the written struct, but useful nevertheless
Signature [8]uint8
VerMajor uint8
VerMinor uint8
Base uint64
Size uint32
Name [32]uint8
Areas []FMapArea
}

type FMapArea struct {
Offset uint32
Size uint32
Name [32]uint8
Flags uint16
}

// FlagNames returns human readable representation of the flags.
func FlagNames(flags uint16) string {
names := []string{}
m := []struct {
val uint16
name string
}{
{FmapAreaStatic, "STATIC"},
{FmapAreaCompressed, "COMPRESSED"},
{FmapAreaReadOnly, "READ_ONLY"},
}
for _, v := range m {
if v.val&flags != 0 {
names = append(names, v.name)
flags -= v.val
}
}
// Write a hex value for unknown flags.
if flags != 0 || len(names) == 0 {
names = append(names, fmt.Sprintf("%#x", flags))
}
return strings.Join(names, "|")
}

func readField(r io.Reader, data interface{}) {
// The endianness might depend on your machine or it might not.
if err := binary.Read(r, binary.LittleEndian, data); err != nil {
log.Fatal("Unexpected EOF while parsing fmap")
}
}

// Read an FMap into the data structure.
func ReadFMap(f io.Reader) *FMap {
// Read flash into memory.
// TODO: it is possible to parse fmap without reading entire file into memory
data, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}

// Check for too many fmaps.
if bytes.Count(data, signature) >= 2 {
log.Print("Warning: Found multiple signatures, using first")
}

// Check for too few fmaps.
start := bytes.Index(data, signature)
if start == -1 {
log.Fatal("Cannot find fmap signature")
}

// Reader anchored to the start of the fmap
r := bytes.NewReader(data[start:])

// Read fields.
fmap := FMap{Start: uint64(start)}
readField(r, &fmap.Signature)
readField(r, &fmap.VerMajor)
readField(r, &fmap.VerMinor)
readField(r, &fmap.Base)
readField(r, &fmap.Size)
readField(r, &fmap.Name)
var nAreas uint16
readField(r, &nAreas)
fmap.Areas = make([]FMapArea, nAreas)
for i := 0; i < len(fmap.Areas); i++ {
readField(r, &fmap.Areas[i])
}

return &fmap
}
93 changes: 93 additions & 0 deletions cmds/fmap/lib/fmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2017 the u-root 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 fmap

import (
"bytes"
"reflect"
"strings"
"testing"
)

// Flash map is stored in little-endian.
var fmapName = []byte("Fake flash" + strings.Repeat("\x00", 32-10))
var area0Name = []byte("Area Number 1\x00\x00\x00Hello" + strings.Repeat("\x00", 32-21))
var area1Name = []byte("Area Number 2xxxxxxxxxxxxxxxxxxx")
var fakeFlash = bytes.Join([][]byte{
// Arbitrary data
bytes.Repeat([]byte{0x53, 0x11, 0x34, 0x22}, 94387),

// Signature
[]byte("__FMAP__"),
// VerMajor, VerMinor
{1, 0},
// Base
{0xef, 0xbe, 0xad, 0xde, 0xbe, 0xba, 0xfe, 0xca},
// Size
{0x11, 0x22, 0x33, 0x44},
// Name (32 bytes)
fmapName,
// NAreas
{0x02, 0x00},

// Areas[0].Offset
{0xef, 0xbe, 0xad, 0xde},
// Areas[0].Size
{0x11, 0x11, 0x11, 0x11},
// Areas[0].Name (32 bytes)
area0Name,
// Areas[0].Flags
{0x13, 0x10},

// Areas[1].Offset
{0xbe, 0xba, 0xfe, 0xca},
// Areas[1].Size
{0x22, 0x22, 0x22, 0x22},
// Areas[1].Name (32 bytes)
area1Name,
// Areas[1].Flags
{0x00, 0x00},
}, []byte{})

func TestReadFMap(t *testing.T) {
r := bytes.NewReader(fakeFlash)
fmap := ReadFMap(r)
expected := FMap{
Start: 4 * 94387,
VerMajor: 1,
VerMinor: 0,
Base: 0xcafebabedeadbeef,
Size: 0x44332211,
Areas: []FMapArea{
{
Offset: 0xdeadbeef,
Size: 0x11111111,
Flags: 0x1013,
}, {
Offset: 0xcafebabe,
Size: 0x22222222,
Flags: 0x0000,
},
},
}
copy(expected.Signature[:], []byte("__FMAP__"))
copy(expected.Name[:], fmapName)
copy(expected.Areas[0].Name[:], area0Name)
copy(expected.Areas[1].Name[:], area1Name)
if !reflect.DeepEqual(*fmap, expected) {
t.Errorf("expected: %+v\ngot: %+v", expected, *fmap)
}
}

func TestFieldNames(t *testing.T) {
r := bytes.NewReader(fakeFlash)
fmap := ReadFMap(r)
for i, expected := range []string{"STATIC|COMPRESSED|0x1010", "0x0"} {
got := FlagNames(fmap.Areas[i].Flags)
if got != expected {
t.Errorf("expected: %s\ngot: %s", expected, got)
}
}
}
1 change: 1 addition & 0 deletions roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
| dmesg | -c | -Clr | |
| echo | -n | -e | |
| ectool | | | u-root specific |
| fmap | -s | -crudV | u-root specific |
| :x: free | | -bkmght | Not implemented yet! |
| freq | -cdorx | | From plan 9 |
| :x: gitclone | | | Not implemented yet! |
Expand Down

0 comments on commit c2df5d2

Please sign in to comment.