github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/cpio/utils.go (about) 1 // Copyright 2013-2017 the u-root 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 cpio 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "path/filepath" 12 "strings" 13 14 "github.com/u-root/u-root/pkg/uio" 15 ) 16 17 // Trailer is the name of the trailer record. 18 const Trailer = "TRAILER!!!" 19 20 // TrailerRecord is the last record in any CPIO archive. 21 var TrailerRecord = StaticRecord(nil, Info{Name: Trailer}) 22 23 // StaticRecord returns a record with the given contents and metadata. 24 func StaticRecord(contents []byte, info Info) Record { 25 info.FileSize = uint64(len(contents)) 26 return Record{ 27 ReaderAt: bytes.NewReader(contents), 28 Info: info, 29 } 30 } 31 32 // StaticFile returns a normal file record. 33 func StaticFile(name string, content string, perm uint64) Record { 34 return StaticRecord([]byte(content), Info{ 35 Name: name, 36 Mode: S_IFREG | perm, 37 }) 38 } 39 40 // Symlink returns a symlink record at name pointing to target. 41 func Symlink(name string, target string) Record { 42 return Record{ 43 ReaderAt: strings.NewReader(target), 44 Info: Info{ 45 FileSize: uint64(len(target)), 46 Mode: S_IFLNK | 0777, 47 Name: name, 48 }, 49 } 50 } 51 52 // Directory returns a directory record at name. 53 func Directory(name string, mode uint64) Record { 54 return Record{ 55 Info: Info{ 56 Name: name, 57 Mode: S_IFDIR | mode&^S_IFMT, 58 }, 59 } 60 } 61 62 // CharDev returns a character device record at name. 63 func CharDev(name string, perm uint64, rmajor, rminor uint64) Record { 64 return Record{ 65 Info: Info{ 66 Name: name, 67 Mode: S_IFCHR | perm, 68 Rmajor: rmajor, 69 Rminor: rminor, 70 }, 71 } 72 } 73 74 // EOFReader is a RecordReader that converts the Trailer record to io.EOF. 75 type EOFReader struct { 76 RecordReader 77 } 78 79 // ReadRecord implements RecordReader. 80 // 81 // ReadRecord returns io.EOF when the record name is TRAILER!!!. 82 func (r EOFReader) ReadRecord() (Record, error) { 83 rec, err := r.RecordReader.ReadRecord() 84 if err != nil { 85 return Record{}, err 86 } 87 // The end of a CPIO archive is marked by a record whose name is 88 // "TRAILER!!!". 89 if rec.Name == Trailer { 90 return Record{}, io.EOF 91 } 92 return rec, nil 93 } 94 95 // DedupWriter is a RecordWriter that does not write more than one record with 96 // the same path. 97 // 98 // There seems to be no harm done in stripping duplicate names when the record 99 // is written, and lots of harm done if we don't do it. 100 type DedupWriter struct { 101 rw RecordWriter 102 103 // alreadyWritten keeps track of paths already written to rw. 104 alreadyWritten map[string]struct{} 105 } 106 107 // NewDedupWriter returns a new deduplicating rw. 108 func NewDedupWriter(rw RecordWriter) RecordWriter { 109 return &DedupWriter{ 110 rw: rw, 111 alreadyWritten: make(map[string]struct{}), 112 } 113 } 114 115 // WriteRecord implements RecordWriter. 116 // 117 // If rec.Name was already seen once before, it will not be written again and 118 // WriteRecord returns nil. 119 func (dw *DedupWriter) WriteRecord(rec Record) error { 120 rec.Name = Normalize(rec.Name) 121 122 if _, ok := dw.alreadyWritten[rec.Name]; ok { 123 return nil 124 } 125 dw.alreadyWritten[rec.Name] = struct{}{} 126 return dw.rw.WriteRecord(rec) 127 } 128 129 // WriteRecords writes multiple records to w. 130 func WriteRecords(w RecordWriter, files []Record) error { 131 for _, f := range files { 132 if err := w.WriteRecord(f); err != nil { 133 return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) 134 } 135 } 136 return nil 137 } 138 139 // Passthrough copies from a RecordReader to a RecordWriter. 140 // 141 // Passthrough writes a trailer record. 142 // 143 // It processes one record at a time to minimize the memory footprint. 144 func Passthrough(r RecordReader, w RecordWriter) error { 145 if err := Concat(w, r, nil); err != nil { 146 return err 147 } 148 if err := WriteTrailer(w); err != nil { 149 return err 150 } 151 return nil 152 } 153 154 // WriteTrailer writes the trailer record. 155 func WriteTrailer(w RecordWriter) error { 156 return w.WriteRecord(TrailerRecord) 157 } 158 159 // Concat reads files from r one at a time, and writes them to w. 160 // 161 // Concat does not write a trailer record and applies transform to every record 162 // before writing it. transform may be nil. 163 func Concat(w RecordWriter, r RecordReader, transform func(Record) Record) error { 164 return ForEachRecord(r, func(f Record) error { 165 if transform != nil { 166 f = transform(f) 167 } 168 return w.WriteRecord(f) 169 }) 170 } 171 172 // ReadAllRecords returns all records in r in the order in which they were 173 // read. 174 func ReadAllRecords(rr RecordReader) ([]Record, error) { 175 var files []Record 176 err := ForEachRecord(rr, func(r Record) error { 177 files = append(files, r) 178 return nil 179 }) 180 return files, err 181 } 182 183 // ForEachRecord reads every record from r and applies f. 184 func ForEachRecord(rr RecordReader, fun func(Record) error) error { 185 for { 186 rec, err := rr.ReadRecord() 187 switch err { 188 case io.EOF: 189 return nil 190 191 case nil: 192 if err := fun(rec); err != nil { 193 return err 194 } 195 196 default: 197 return err 198 } 199 } 200 } 201 202 // Normalize normalizes path to be relative to /. 203 func Normalize(path string) string { 204 if filepath.IsAbs(path) { 205 rel, err := filepath.Rel("/", path) 206 if err != nil { 207 panic("absolute filepath must be relative to /") 208 } 209 return rel 210 } 211 return filepath.Clean(path) 212 } 213 214 // MakeReproducible changes any fields in a Record such that if we run cpio 215 // again, with the same files presented to it in the same order, and those 216 // files have unchanged contents, the cpio file it produces will be bit-for-bit 217 // identical. This is an essential property for firmware-embedded payloads. 218 func MakeReproducible(r Record) Record { 219 r.Ino = 0 220 r.Name = Normalize(r.Name) 221 r.MTime = 0 222 r.UID = 0 223 r.GID = 0 224 r.Dev = 0 225 r.Major = 0 226 r.Minor = 0 227 r.NLink = 0 228 return r 229 } 230 231 // MakeAllReproducible makes all given records reproducible as in 232 // MakeReproducible. 233 func MakeAllReproducible(files []Record) { 234 for i := range files { 235 files[i] = MakeReproducible(files[i]) 236 } 237 } 238 239 // AllEqual compares all metadata and contents of r and s. 240 func AllEqual(r []Record, s []Record) bool { 241 if len(r) != len(s) { 242 return false 243 } 244 for i := range r { 245 if !Equal(r[i], s[i]) { 246 return false 247 } 248 } 249 return true 250 } 251 252 // Equal compares the metadata and contents of r and s. 253 func Equal(r Record, s Record) bool { 254 if r.Info != s.Info { 255 return false 256 } 257 return uio.ReaderAtEqual(r.ReaderAt, s.ReaderAt) 258 }