github.com/linuxboot/fiano@v1.2.0/cmds/fmap/fmap.go (about)

     1  // Copyright 2017-2018 the LinuxBoot 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  // Fmap parses flash maps.
     6  //
     7  // Synopsis:
     8  //     fmap checksum [md5|sha1|sha256] FILE
     9  //     fmap extract [index|name] FILE
    10  //     fmap jget JSONFILE FILE
    11  //     fmap jput JSONFILE FILE
    12  //     fmap summary FILE
    13  //     fmap usage FILE
    14  //     fmap verify FILE
    15  //
    16  // Description:
    17  //     checksum: Print a checksum using the given hash function.
    18  //     extract:  Print the i-th area or area name from the flash.
    19  //     jget:     Write json representation of the fmap to JSONFILE.
    20  //     jput:     Replace current fmap with json representation in JSONFILE.
    21  //     summary:  Print a human readable summary.
    22  //     usage:    Print human readable usage stats.
    23  //     verify:   Return 1 if the flash map is invalid.
    24  //
    25  //     This implementation is based off of https://github.com/dhendrix/flashmap.
    26  package main
    27  
    28  import (
    29  	"bytes"
    30  	"crypto/md5"
    31  	"crypto/sha1"
    32  	"crypto/sha256"
    33  	"encoding/json"
    34  	"errors"
    35  	"fmt"
    36  	"hash"
    37  	"io"
    38  	"os"
    39  	"strconv"
    40  	"text/template"
    41  
    42  	"github.com/linuxboot/fiano/pkg/fmap"
    43  	"github.com/linuxboot/fiano/pkg/log"
    44  )
    45  
    46  var cmds = map[string]struct {
    47  	nArgs               int
    48  	openFile, parseFMap bool
    49  	f                   func(a cmdArgs) error
    50  }{
    51  	"checksum": {1, true, true, checksum},
    52  	"extract":  {1, true, true, extract},
    53  	"jget":     {1, true, true, jsonGet},
    54  	"jput":     {1, false, false, jsonPut},
    55  	"summary":  {0, true, true, summary},
    56  	"usage":    {0, true, false, usage},
    57  	"jusage":   {0, true, false, jusage},
    58  	"verify":   {0, true, true, verify},
    59  }
    60  
    61  type cmdArgs struct {
    62  	args []string
    63  	f    *fmap.FMap     // optional
    64  	m    *fmap.Metadata // optional
    65  	r    *os.File
    66  }
    67  
    68  var hashFuncs = map[string](func() hash.Hash){
    69  	"md5":    md5.New,
    70  	"sha1":   sha1.New,
    71  	"sha256": sha256.New,
    72  }
    73  
    74  type jsonSchema struct {
    75  	FMap     *fmap.FMap
    76  	Metadata *fmap.Metadata
    77  }
    78  
    79  // Print a checksum using the given hash function.
    80  func checksum(a cmdArgs) error {
    81  	if _, ok := hashFuncs[a.args[0]]; !ok {
    82  		msg := "Not a valid hash function. Must be one of:"
    83  		for k := range hashFuncs {
    84  			msg += " " + k
    85  		}
    86  		return errors.New(msg)
    87  	}
    88  
    89  	checksum, err := a.f.Checksum(a.r, hashFuncs[a.args[0]]())
    90  	if err != nil {
    91  		return err
    92  	}
    93  	fmt.Printf("%x\n", checksum)
    94  	return nil
    95  }
    96  
    97  // Print the i-th area of the flash.
    98  func extract(a cmdArgs) error {
    99  	i, err := strconv.Atoi(a.args[0])
   100  	if err != nil {
   101  		i = a.f.IndexOfArea(a.args[0])
   102  		if i == -1 {
   103  			return fmt.Errorf("area %q not found", a.args[0])
   104  		}
   105  	}
   106  	area, err := a.f.ReadArea(a.r, i)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	_, err = os.Stdout.Write(area)
   111  	return err
   112  }
   113  
   114  // Write json representation of the fmap to JSONFILE.
   115  func jsonGet(a cmdArgs) error {
   116  	data, err := json.MarshalIndent(jsonSchema{a.f, a.m}, "", "\t")
   117  	if err != nil {
   118  		return err
   119  	}
   120  	data = append(data, byte('\n'))
   121  	return os.WriteFile(a.args[0], data, 0666)
   122  }
   123  
   124  // Replace current fmap with json representation in JSONFILE.
   125  func jsonPut(a cmdArgs) error {
   126  	r, err := os.OpenFile(os.Args[len(os.Args)-1], os.O_WRONLY, 0666)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer r.Close()
   131  
   132  	data, err := os.ReadFile(a.args[0])
   133  	if err != nil {
   134  		return err
   135  	}
   136  	j := jsonSchema{}
   137  	if err := json.Unmarshal(data, &j); err != nil {
   138  		return err
   139  	}
   140  	return fmap.Write(r, j.FMap, j.Metadata)
   141  }
   142  
   143  // Print a human readable summary.
   144  func summary(a cmdArgs) error {
   145  	const desc = `Fmap found at {{printf "%#x" .Metadata.Start}}:
   146  	Signature:  {{printf "%s" .Signature}}
   147  	VerMajor:   {{.VerMajor}}
   148  	VerMinor:   {{.VerMinor}}
   149  	Base:       {{printf "%#x" .Base}}
   150  	Size:       {{printf "%#x" .Size}}
   151  	Name:       {{.Name}}
   152  	NAreas:     {{.NAreas}}
   153  {{- range $i, $v := .Areas}}
   154  	Areas[{{$i}}]:
   155  		Offset:  {{printf "%#x" $v.Offset}}
   156  		Size:    {{printf "%#x" $v.Size}}
   157  		Name:    {{$v.Name}}
   158  		Flags:   {{printf "%#x" $v.Flags}} ({{FlagNames $v.Flags}})
   159  {{- end}}
   160  `
   161  	t := template.Must(template.New("desc").
   162  		Funcs(template.FuncMap{"FlagNames": fmap.FlagNames}).
   163  		Parse(desc))
   164  	// Combine the two structs to pass into template.
   165  	combined := struct {
   166  		*fmap.FMap
   167  		Metadata *fmap.Metadata
   168  	}{a.f, a.m}
   169  	return t.Execute(os.Stdout, combined)
   170  }
   171  
   172  // Print human readable usage stats.
   173  func usage(a cmdArgs) error {
   174  	blockSize := 4 * 1024
   175  	rowLength := 32
   176  
   177  	buffer := make([]byte, blockSize)
   178  	fullBlock := bytes.Repeat([]byte{0xff}, blockSize)
   179  	zeroBlock := bytes.Repeat([]byte{0x00}, blockSize)
   180  
   181  	fmt.Println("Legend: '.' - full (0xff), '0' - zero (0x00), '#' - mixed")
   182  
   183  	if _, err := a.r.Seek(0, io.SeekStart); err != nil {
   184  		return err
   185  	}
   186  
   187  	var numBlocks, numFull, numZero int
   188  loop:
   189  	for {
   190  		fmt.Printf("%#08x: ", numBlocks*blockSize)
   191  		for col := 0; col < rowLength; col++ {
   192  			// Read next block.
   193  			_, err := io.ReadFull(a.r, buffer)
   194  			if err == io.EOF {
   195  				fmt.Print("\n")
   196  				break loop
   197  			} else if err == io.ErrUnexpectedEOF {
   198  				fmt.Printf("\nWarning: flash is not a multiple of %d", len(buffer))
   199  				break loop
   200  			} else if err != nil {
   201  				return err
   202  			}
   203  			numBlocks++
   204  
   205  			// Analyze block.
   206  			if bytes.Equal(buffer, fullBlock) {
   207  				numFull++
   208  				fmt.Print(".")
   209  			} else if bytes.Equal(buffer, zeroBlock) {
   210  				numZero++
   211  				fmt.Print("0")
   212  			} else {
   213  				fmt.Print("#")
   214  			}
   215  		}
   216  		fmt.Print("\n")
   217  	}
   218  
   219  	// Print usage statistics.
   220  	print := func(name string, n int) {
   221  		fmt.Printf("%s %d (%.1f%%)\n", name, n,
   222  			float32(n)/float32(numBlocks)*100)
   223  	}
   224  	print("Blocks:      ", numBlocks)
   225  	print("Full (0xff): ", numFull)
   226  	print("Empty (0x00):", numZero)
   227  	print("Mixed:       ", numBlocks-numFull-numZero)
   228  	return nil
   229  }
   230  
   231  type rowEntry struct {
   232  	Entries []string `json:"entries"`
   233  	Address string   `json:"address"`
   234  }
   235  type flashLaout struct {
   236  	Data   []rowEntry `json:"layout"`
   237  	Blocks int        `json:"blocks"`
   238  	Full   int        `json:"full"`
   239  	Zero   int        `json:"zero"`
   240  	Used   int        `json:"used"`
   241  }
   242  
   243  // Print machine readable usage stats.
   244  func jusage(a cmdArgs) error {
   245  	blockSize := 4 * 1024
   246  	rowLength := 32
   247  
   248  	buffer := make([]byte, blockSize)
   249  	fullBlock := bytes.Repeat([]byte{0xff}, blockSize)
   250  	zeroBlock := bytes.Repeat([]byte{0x00}, blockSize)
   251  
   252  	if _, err := a.r.Seek(0, io.SeekStart); err != nil {
   253  		return err
   254  	}
   255  
   256  	var numBlocks, numFull, numZero int
   257  
   258  	var layout flashLaout
   259  
   260  loop:
   261  	for {
   262  		var row rowEntry
   263  		row.Address = fmt.Sprintf("%#08x", numBlocks*blockSize)
   264  		for col := 0; col < rowLength; col++ {
   265  			// Read next block.
   266  			_, err := io.ReadFull(a.r, buffer)
   267  			if err == io.EOF {
   268  				break loop
   269  			} else if err == io.ErrUnexpectedEOF {
   270  				fmt.Printf("\nWarning: flash is not a multiple of %d", len(buffer))
   271  				break loop
   272  			} else if err != nil {
   273  				return err
   274  			}
   275  			numBlocks++
   276  
   277  			// Analyze block.
   278  			if bytes.Equal(buffer, fullBlock) {
   279  				numFull++
   280  				row.Entries = append(row.Entries, "full")
   281  			} else if bytes.Equal(buffer, zeroBlock) {
   282  				numZero++
   283  				row.Entries = append(row.Entries, "zero")
   284  			} else {
   285  				row.Entries = append(row.Entries, "used")
   286  			}
   287  		}
   288  		layout.Data = append(layout.Data, row)
   289  	}
   290  	layout.Blocks = numBlocks
   291  	layout.Full = numFull
   292  	layout.Zero = numZero
   293  	layout.Used = numBlocks - numFull - numZero
   294  
   295  	// fmt.Printf("%s\n%s\n", layout.data[0].address, layout.data[0].entries[0])
   296  	data, err := json.MarshalIndent(layout, "", "  ")
   297  	if err != nil {
   298  		return err
   299  	}
   300  	fmt.Println(string(data))
   301  	return nil
   302  }
   303  
   304  // Return 1 if the flash map is invalid.
   305  func verify(a cmdArgs) error {
   306  	var err error
   307  	for i, area := range a.f.Areas {
   308  		if area.Offset+area.Size > a.f.Size {
   309  			err = errors.New("invalid flash map")
   310  			log.Errorf("Area %d is out of range", i)
   311  		}
   312  	}
   313  	return err
   314  }
   315  
   316  func printUsage() {
   317  	fmt.Printf("Usage: %s CMD [ARGS...] FILE\n", os.Args[0])
   318  	fmt.Printf("CMD can be one of:\n")
   319  	for k := range cmds {
   320  		fmt.Printf("\t%s\n", k)
   321  	}
   322  	os.Exit(2)
   323  }
   324  
   325  func main() {
   326  	// Validate args.
   327  	if len(os.Args) <= 2 {
   328  		printUsage()
   329  	}
   330  	cmd, ok := cmds[os.Args[1]]
   331  	if !ok {
   332  		log.Errorf("Invalid command %#v\n", os.Args[1])
   333  		printUsage()
   334  	}
   335  	if len(os.Args) != cmd.nArgs+3 {
   336  		log.Errorf("Expected %d arguments, got %d\n", cmd.nArgs+3, len(os.Args))
   337  		printUsage()
   338  	}
   339  
   340  	// Args passed to the command.
   341  	a := cmdArgs{
   342  		args: os.Args[2 : len(os.Args)-1],
   343  	}
   344  
   345  	// Open file, but only for specific commands.
   346  	if cmd.openFile {
   347  		// Open file.
   348  		r, err := os.Open(os.Args[len(os.Args)-1])
   349  		if err != nil {
   350  			log.Fatalf("%v", err)
   351  		}
   352  		a.r = r
   353  		defer r.Close()
   354  	}
   355  
   356  	// Parse fmap, but only for specific commands.
   357  	if cmd.parseFMap {
   358  		// Parse fmap.
   359  		f, m, err := fmap.Read(a.r)
   360  		if err != nil {
   361  			log.Fatalf("%v", err)
   362  		}
   363  		a.f, a.m = f, m
   364  	}
   365  
   366  	// Execute command.
   367  	if err := cmd.f(a); err != nil {
   368  		log.Fatalf("%v", err)
   369  	}
   370  }