github.com/linuxboot/fiano@v1.2.0/pkg/cbfs/file.go (about) 1 // Copyright 2018-2021 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 package cbfs 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 15 "github.com/linuxboot/fiano/pkg/compression" 16 ) 17 18 var CbfsHeaderMagicNotFound = errors.New("CBFS header magic doesn't match") 19 20 func (f *File) MarshalJSON() ([]byte, error) { 21 return json.Marshal(mFile{ 22 Name: f.Name, 23 Start: f.RecordStart, 24 Size: f.FileHeader.Size, 25 Type: f.FileHeader.Type.String(), 26 }) 27 } 28 29 // NewFile reads in the CBFS file at current offset 30 // On success it seeks to the end of the file. 31 // On error the current offset withing the ReadSeeker is undefined. 32 func NewFile(r io.ReadSeeker) (*File, error) { 33 var f File 34 off, err := r.Seek(0, io.SeekCurrent) 35 if err != nil { 36 return nil, err 37 } 38 f.RecordStart = uint32(off) 39 40 err = Read(r, &f.FileHeader) 41 if err != nil { 42 return nil, err 43 } 44 if string(f.Magic[:]) != FileMagic { 45 return nil, CbfsHeaderMagicNotFound 46 } 47 Debug("Found CBFS file at %#02x is %v type %v", f.RecordStart, f, f.Type) 48 49 var nameSize uint32 50 if f.AttrOffset == 0 { 51 nameSize = f.SubHeaderOffset - uint32(binary.Size(FileHeader{})) 52 } else { 53 nameSize = f.AttrOffset - uint32(binary.Size(FileHeader{})) 54 } 55 if err := ReadName(r, &f, nameSize); err != nil { 56 return nil, err 57 } 58 if err := ReadAttributes(r, &f); err != nil { 59 return nil, err 60 } 61 if err := ReadData(r, &f); err != nil { 62 return nil, err 63 } 64 65 return &f, nil 66 } 67 68 // ReadName reads the variable length CBFS name. 69 func ReadName(r io.Reader, f *File, size uint32) error { 70 b := make([]byte, size) 71 n, err := r.Read(b) 72 if err != nil { 73 Debug("ReadName failed:%v", err) 74 return err 75 } 76 fname := cleanString(string(b)) 77 Debug("ReadName gets '%s' (%#02x)", fname, b) 78 if n != len(b) { 79 err = fmt.Errorf("ReadName: got %d, want %d for name", n, len(b)) 80 Debug("ReadName short: %v", err) 81 return err 82 } 83 // discard trailing NULLs 84 z := bytes.Split(b, []byte{0}) 85 Debug("ReadName stripped: '%s'", z) 86 f.Name = string(z[0]) 87 return nil 88 } 89 90 // ReadAttributes reads the variable length CBFS attribute list. 91 func ReadAttributes(r io.Reader, f *File) error { 92 if f.AttrOffset == 0 { 93 return nil 94 } 95 96 b := make([]byte, f.SubHeaderOffset-f.AttrOffset) 97 n, err := r.Read(b) 98 if err != nil { 99 Debug("ReadAttributes failed:%v", err) 100 return err 101 } 102 Debug("ReadAttributes gets %#02x", b) 103 if n != len(b) { 104 err = fmt.Errorf("ReadAttributes: got %d, want %d for name", n, len(b)) 105 Debug("ReadAttributes short: %v", err) 106 return err 107 } 108 f.Attr = b 109 return nil 110 } 111 112 // ReadData reads the variable length CBFS file data. 113 func ReadData(r io.ReadSeeker, f *File) error { 114 Debug("ReadData: Seek to %#x", int64(f.RecordStart+f.SubHeaderOffset)) 115 if _, err := r.Seek(int64(f.RecordStart+f.SubHeaderOffset), io.SeekStart); err != nil { 116 return err 117 } 118 Debug("ReadData: read %#x", f.Size) 119 b := make([]byte, f.Size) 120 n, err := r.Read(b) 121 if err != nil { 122 Debug("ReadData failed:%v", err) 123 return err 124 } 125 f.FData = b 126 Debug("ReadData gets %#02x", n) 127 return nil 128 } 129 130 // FindAttribute returns the attribute with given tag as 131 // []byte. It has the size as specified by the tag. 132 // Returns an error if not found or could not read in total. 133 func (f *File) FindAttribute(t Tag) ([]byte, error) { 134 buf := bytes.NewReader(f.Attr) 135 generic := FileAttr{} 136 137 for { 138 if err := binary.Read(buf, Endian, &generic); err != nil { 139 return nil, err 140 } 141 if generic.Tag == uint32(Unused) || generic.Tag == uint32(Unused2) { 142 return nil, fmt.Errorf("end tag found") 143 } 144 // Validate input 145 if generic.Size < uint32(binary.Size(generic)) || generic.Size == 0xffffffff { 146 return nil, fmt.Errorf("tag is malformed, aborting") 147 } 148 Debug("FindAttribute: Found attribute with tag %x", generic.Tag) 149 150 if Tag(generic.Tag) == t { 151 _, _ = buf.Seek(-int64(binary.Size(generic)), io.SeekCurrent) 152 153 ret := make([]byte, generic.Size) 154 err := binary.Read(buf, Endian, &ret) 155 return ret, err 156 } else { 157 _, err := buf.Seek(int64(generic.Size)-int64(binary.Size(generic)), io.SeekCurrent) 158 if err != nil { 159 return nil, err 160 } 161 } 162 } 163 } 164 165 // Compression returns the algorithm used to compress FData. 166 // If no compression attribute is found or on error it returns 'None' 167 func (f *File) Compression() Compression { 168 cattr, err := f.FindAttribute(Compressed) 169 if err != nil { 170 Debug("Compression: No compression tag found: %v", err) 171 return None 172 } 173 174 comp := FileAttrCompression{} 175 if err := binary.Read(bytes.NewBuffer(cattr), Endian, &comp); err != nil { 176 Debug("Compression: failed to read compression tag: %v", err) 177 return None 178 } 179 return comp.Compression 180 } 181 182 // Decompress returns the decompressed FData 183 // If FData is not compressed it returns FData 184 func (f *File) Decompress() ([]byte, error) { 185 c := f.Compression() 186 if c == None { 187 return f.FData, nil 188 } else if c == LZMA { 189 compressor := compression.LZMA{} 190 return compressor.Decode(f.FData) 191 } else if c == LZ4 { 192 compressor := compression.LZ4{} 193 return compressor.Decode(f.FData) 194 } 195 return nil, fmt.Errorf("Unknown compression") 196 }