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