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 }