github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/pkg/cpio/cpio.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 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/u-root/u-root/pkg/ls" 17 "github.com/u-root/u-root/pkg/uio" 18 "golang.org/x/sys/unix" 19 ) 20 21 var ( 22 formatMap = make(map[string]RecordFormat) 23 Debug = func(string, ...interface{}) {} 24 ) 25 26 // A RecordReader reads one record from an archive. 27 type RecordReader interface { 28 ReadRecord() (Record, error) 29 } 30 31 // A RecordWriter writes one record to an archive. 32 type RecordWriter interface { 33 WriteRecord(Record) error 34 } 35 36 // A RecordFormat gives readers and writers for dealing with archives from io 37 // objects. 38 type RecordFormat interface { 39 Reader(r io.ReaderAt) RecordReader 40 Writer(w io.Writer) RecordWriter 41 } 42 43 // Format returns the RecordFormat with that name, if it exists. 44 func Format(name string) (RecordFormat, error) { 45 op, ok := formatMap[name] 46 if !ok { 47 return nil, fmt.Errorf("%q is not in cpio format map %v", name, formatMap) 48 } 49 return op, nil 50 } 51 52 // EOFReader is a RecordReader that converts the Trailer record to io.EOF. 53 type EOFReader struct { 54 RecordReader 55 } 56 57 // ReadRecord implements RecordReader. 58 // 59 // ReadRecord returns io.EOF when the record name is TRAILER!!!. 60 func (r EOFReader) ReadRecord() (Record, error) { 61 rec, err := r.RecordReader.ReadRecord() 62 if err != nil { 63 return Record{}, err 64 } 65 // The end of a CPIO archive is marked by a record whose name is 66 // "TRAILER!!!". 67 if rec.Name == Trailer { 68 return Record{}, io.EOF 69 } 70 return rec, nil 71 } 72 73 // DedupWriter is a RecordWriter that does not write more than one record with 74 // the same path. 75 // 76 // There seems to be no harm done in stripping duplicate names when the record 77 // is written, and lots of harm done if we don't do it. 78 type DedupWriter struct { 79 rw RecordWriter 80 81 // alreadyWritten keeps track of paths already written to rw. 82 alreadyWritten map[string]struct{} 83 } 84 85 // NewDedupWriter returns a new deduplicating rw. 86 func NewDedupWriter(rw RecordWriter) RecordWriter { 87 return &DedupWriter{ 88 rw: rw, 89 alreadyWritten: make(map[string]struct{}), 90 } 91 } 92 93 // Passthrough copies from a RecordReader to a RecordWriter 94 // It processes one record at a time to minimize the memory footprint. 95 func Passthrough(r RecordReader, w RecordWriter) error { 96 if err := Concat(w, r, nil); err != nil { 97 return err 98 } 99 if err := WriteTrailer(w); err != nil { 100 return err 101 } 102 return nil 103 } 104 105 // WriteRecord implements RecordWriter. 106 // 107 // If rec.Name was already seen once before, it will not be written again and 108 // WriteRecord returns nil. 109 func (dw *DedupWriter) WriteRecord(rec Record) error { 110 rec.Name = Normalize(rec.Name) 111 112 if _, ok := dw.alreadyWritten[rec.Name]; ok { 113 return nil 114 } 115 dw.alreadyWritten[rec.Name] = struct{}{} 116 return dw.rw.WriteRecord(rec) 117 } 118 119 // WriteRecords writes multiple records. 120 func WriteRecords(w RecordWriter, files []Record) error { 121 for _, f := range files { 122 if err := w.WriteRecord(f); err != nil { 123 return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) 124 } 125 } 126 return nil 127 } 128 129 // WriteTrailer writes the trailer record. 130 func WriteTrailer(w RecordWriter) error { 131 return w.WriteRecord(TrailerRecord) 132 } 133 134 // Concat reads files from r one at a time, and writes them to w. 135 func Concat(w RecordWriter, r RecordReader, transform func(Record) Record) error { 136 // Read and write one file at a time. We don't want all that in memory. 137 for { 138 f, err := r.ReadRecord() 139 if err == io.EOF { 140 return nil 141 } 142 if err != nil { 143 return err 144 } 145 if transform != nil { 146 f = transform(f) 147 } 148 if err := w.WriteRecord(f); err != nil { 149 return err 150 } 151 } 152 } 153 154 // Archive implements RecordWriter and is an in-memory list of files. 155 // 156 // Archive.Reader returns a RecordReader for this archive. 157 type Archive struct { 158 // Files is a map of relative archive path -> record. 159 Files map[string]Record 160 161 // Order is a list of relative archive paths and represents the order 162 // in which Files were added. 163 Order []string 164 } 165 166 // InMemArchive returns an in-memory file archive. 167 func InMemArchive() *Archive { 168 return &Archive{ 169 Files: make(map[string]Record), 170 } 171 } 172 173 func ArchiveFromRecords(rs []Record) *Archive { 174 a := InMemArchive() 175 for _, r := range rs { 176 a.WriteRecord(r) 177 } 178 return a 179 } 180 181 // WriteRecord implements RecordWriter and adds a record to the archive. 182 func (a *Archive) WriteRecord(r Record) error { 183 r.Name = Normalize(r.Name) 184 a.Files[r.Name] = r 185 a.Order = append(a.Order, r.Name) 186 return nil 187 } 188 189 // Empty returns whether the archive has any files in it. 190 func (a *Archive) Empty() bool { 191 return len(a.Files) == 0 192 } 193 194 type archiveReader struct { 195 a *Archive 196 pos int 197 } 198 199 // Reader returns a RecordReader for the archive that starts at the first 200 // record. 201 func (a *Archive) Reader() RecordReader { 202 return &EOFReader{&archiveReader{a: a}} 203 } 204 205 func modeFromLinux(mode uint64) os.FileMode { 206 m := os.FileMode(mode & 0777) 207 switch mode & syscall.S_IFMT { 208 case syscall.S_IFBLK: 209 m |= os.ModeDevice 210 case syscall.S_IFCHR: 211 m |= os.ModeDevice | os.ModeCharDevice 212 case syscall.S_IFDIR: 213 m |= os.ModeDir 214 case syscall.S_IFIFO: 215 m |= os.ModeNamedPipe 216 case syscall.S_IFLNK: 217 m |= os.ModeSymlink 218 case syscall.S_IFREG: 219 // nothing to do 220 case syscall.S_IFSOCK: 221 m |= os.ModeSocket 222 } 223 if mode&syscall.S_ISGID != 0 { 224 m |= os.ModeSetgid 225 } 226 if mode&syscall.S_ISUID != 0 { 227 m |= os.ModeSetuid 228 } 229 if mode&syscall.S_ISVTX != 0 { 230 m |= os.ModeSticky 231 } 232 return m 233 } 234 235 // LSInfoFromRecord converts a Record to be usable with the ls package for 236 // listing files. 237 func LSInfoFromRecord(rec Record) ls.FileInfo { 238 var target string 239 240 mode := modeFromLinux(rec.Mode) 241 if mode&os.ModeType == os.ModeSymlink { 242 if l, err := uio.ReadAll(rec); err != nil { 243 target = err.Error() 244 } else { 245 target = string(l) 246 } 247 } 248 249 return ls.FileInfo{ 250 Name: rec.Name, 251 Mode: mode, 252 Rdev: unix.Mkdev(uint32(rec.Rmajor), uint32(rec.Rminor)), 253 UID: uint32(rec.UID), 254 GID: uint32(rec.GID), 255 Size: int64(rec.FileSize), 256 MTime: time.Unix(int64(rec.MTime), 0).UTC(), 257 SymlinkTarget: target, 258 } 259 } 260 261 // String implements fmt.Stringer. 262 // 263 // String lists files like ls would. 264 func (a *Archive) String() string { 265 var b strings.Builder 266 r := a.Reader() 267 for { 268 record, err := r.ReadRecord() 269 if err != nil { 270 return b.String() 271 } 272 b.WriteString(record.String()) 273 b.WriteString("\n") 274 } 275 } 276 277 // ReadRecord implements RecordReader. 278 func (ar *archiveReader) ReadRecord() (Record, error) { 279 if ar.pos >= len(ar.a.Order) { 280 return Record{}, io.EOF 281 } 282 283 path := ar.a.Order[ar.pos] 284 ar.pos++ 285 return ar.a.Files[path], nil 286 } 287 288 // Contains returns true if a record matching r is in the archive. 289 func (a *Archive) Contains(r Record) bool { 290 r.Name = Normalize(r.Name) 291 if s, ok := a.Files[r.Name]; ok { 292 return Equal(r, s) 293 } 294 return false 295 } 296 297 func (a *Archive) Get(path string) (Record, bool) { 298 r, ok := a.Files[Normalize(path)] 299 return r, ok 300 } 301 302 // ReadArchive reads the entire archive in-memory and makes it accessible by 303 // paths. 304 func ReadArchive(rr RecordReader) (*Archive, error) { 305 a := &Archive{ 306 Files: make(map[string]Record), 307 } 308 err := ForEachRecord(rr, func(r Record) error { 309 return a.WriteRecord(r) 310 }) 311 return a, err 312 } 313 314 // ReadAllRecords returns all records in `r` in the order in which they were 315 // read. 316 func ReadAllRecords(rr RecordReader) ([]Record, error) { 317 var files []Record 318 err := ForEachRecord(rr, func(r Record) error { 319 files = append(files, r) 320 return nil 321 }) 322 return files, err 323 } 324 325 // ForEachRecord reads every record from r and applies f. 326 func ForEachRecord(rr RecordReader, fun func(Record) error) error { 327 for { 328 rec, err := rr.ReadRecord() 329 switch err { 330 case io.EOF: 331 return nil 332 333 case nil: 334 if err := fun(rec); err != nil { 335 return err 336 } 337 338 default: 339 return err 340 } 341 } 342 } 343 344 // Normalize normalizes path to be relative to /. 345 func Normalize(path string) string { 346 if filepath.IsAbs(path) { 347 rel, err := filepath.Rel("/", path) 348 if err != nil { 349 panic("absolute filepath must be relative to /") 350 } 351 return rel 352 } 353 return path 354 } 355 356 // MakeReproducible changes any fields in a Record such that if we run cpio 357 // again, with the same files presented to it in the same order, and those 358 // files have unchanged contents, the cpio file it produces will be bit-for-bit 359 // identical. This is an essential property for firmware-embedded payloads. 360 func MakeReproducible(r Record) Record { 361 r.Ino = 0 362 r.Name = Normalize(r.Name) 363 r.MTime = 0 364 r.UID = 0 365 r.GID = 0 366 r.Dev = 0 367 r.Major = 0 368 r.Minor = 0 369 r.NLink = 0 370 return r 371 } 372 373 // MakeAllReproducible makes all given records reproducible as in 374 // MakeReproducible. 375 func MakeAllReproducible(files []Record) { 376 for i := range files { 377 files[i] = MakeReproducible(files[i]) 378 } 379 }