github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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 "os" 12 "path/filepath" 13 "strings" 14 15 "github.com/mvdan/u-root-coreutils/pkg/uio" 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: 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: S_IFLNK | 0o777, 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: S_IFDIR | mode&^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: 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 // WriteRecordsAndDirs writes records to w, with a slight difference from WriteRecords: 141 // the record path is split and all the 142 // directories are written first, in order, mimic'ing what happens with 143 // find . -print 144 // 145 // When is this function needed? 146 // Most cpio programs will create directories as needed for paths such as a/b/c/d 147 // The cpio creation process for Linux uses find, and will create a 148 // record for each directory in a/b/c/d 149 // 150 // But when code programatically generates a cpio for the Linux kernel, 151 // the cpio is not generated via find, and Linux will not create 152 // intermediate directories. The result, seen in practice, is that a path, 153 // such as a/b/c/d, when unpacked by the linux kernel, will be ignored if 154 // a/b/c does not exist! 155 // 156 // Again, this function is very rarely needed, save when we programatically generate 157 // an initramfs for Linux. 158 // This code only works with a deduplicating writer. Further, it will not accept a 159 // Record if the full pathname of that Record already exists. This is arguably 160 // overly restrictive but, at the same, avoids some very unpleasant programmer 161 // errors. 162 // There is overlap here with DedupWriter but given that this is a Special Snowflake 163 // function, it seems best to leave the DedupWriter code alone. 164 func WriteRecordsAndDirs(rw RecordWriter, files []Record) error { 165 w, ok := rw.(*DedupWriter) 166 if !ok { 167 return fmt.Errorf("WriteRecordsAndDirs(%T,...): only DedupWriter allowed:%w", rw, os.ErrInvalid) 168 } 169 for _, f := range files { 170 // This redundant Normalize does no harm, but, yes, it is redundant. 171 // Signed 172 // The Department of Redundancy Department. 173 f.Name = Normalize(f.Name) 174 if r, ok := w.alreadyWritten[f.Name]; ok { 175 return fmt.Errorf("WriteRecordsAndDirs: %q already in the archive: %v:%w", f.Name, r, os.ErrExist) 176 } 177 178 var recs []Record 179 // Paths must be written to the archive in the order in which they 180 // need to be created, i.e., a/b/c/d must be written as 181 // a, a/b/, a/b/c, a/b/c/d 182 // Note: do not use os.Separator here: cpio is a Unix standard, and hence 183 // / is used. 184 els := strings.Split(filepath.Dir(f.Name), "/") 185 for i := range els { 186 d := filepath.Join(els[:i+1]...) 187 recs = append(recs, Directory(d, 0777)) 188 } 189 recs = append(recs, f) 190 if err := WriteRecords(rw, recs); err != nil { 191 return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) 192 } 193 } 194 return nil 195 } 196 197 // Passthrough copies from a RecordReader to a RecordWriter. 198 // 199 // Passthrough writes a trailer record. 200 // 201 // It processes one record at a time to minimize the memory footprint. 202 func Passthrough(r RecordReader, w RecordWriter) error { 203 if err := Concat(w, r, nil); err != nil { 204 return err 205 } 206 if err := WriteTrailer(w); err != nil { 207 return err 208 } 209 return nil 210 } 211 212 // WriteTrailer writes the trailer record. 213 func WriteTrailer(w RecordWriter) error { 214 return w.WriteRecord(TrailerRecord) 215 } 216 217 // Concat reads files from r one at a time, and writes them to w. 218 // 219 // Concat does not write a trailer record and applies transform to every record 220 // before writing it. transform may be nil. 221 func Concat(w RecordWriter, r RecordReader, transform func(Record) Record) error { 222 return ForEachRecord(r, func(f Record) error { 223 if transform != nil { 224 f = transform(f) 225 } 226 return w.WriteRecord(f) 227 }) 228 } 229 230 // ReadAllRecords returns all records in r in the order in which they were 231 // read. 232 func ReadAllRecords(rr RecordReader) ([]Record, error) { 233 var files []Record 234 err := ForEachRecord(rr, func(r Record) error { 235 files = append(files, r) 236 return nil 237 }) 238 return files, err 239 } 240 241 // ForEachRecord reads every record from r and applies f. 242 func ForEachRecord(rr RecordReader, fun func(Record) error) error { 243 for { 244 rec, err := rr.ReadRecord() 245 switch err { 246 case io.EOF: 247 return nil 248 249 case nil: 250 if err := fun(rec); err != nil { 251 return err 252 } 253 254 default: 255 return err 256 } 257 } 258 } 259 260 // Normalize normalizes path to be relative to /. 261 func Normalize(path string) string { 262 if filepath.IsAbs(path) { 263 rel, err := filepath.Rel("/", path) 264 if err != nil { 265 // TODO: libraries should not panic. 266 panic("absolute filepath must be relative to /") 267 } 268 return rel 269 } 270 return filepath.Clean(path) 271 } 272 273 // MakeReproducible changes any fields in a Record such that if we run cpio 274 // again, with the same files presented to it in the same order, and those 275 // files have unchanged contents, the cpio file it produces will be bit-for-bit 276 // identical. This is an essential property for firmware-embedded payloads. 277 func MakeReproducible(r Record) Record { 278 r.Ino = 0 279 r.Name = Normalize(r.Name) 280 r.MTime = 0 281 r.UID = 0 282 r.GID = 0 283 r.Dev = 0 284 r.Major = 0 285 r.Minor = 0 286 r.NLink = 0 287 return r 288 } 289 290 // MakeAllReproducible makes all given records reproducible as in 291 // MakeReproducible. 292 func MakeAllReproducible(files []Record) { 293 for i := range files { 294 files[i] = MakeReproducible(files[i]) 295 } 296 } 297 298 // AllEqual compares all metadata and contents of r and s. 299 func AllEqual(r []Record, s []Record) bool { 300 if len(r) != len(s) { 301 return false 302 } 303 for i := range r { 304 if !Equal(r[i], s[i]) { 305 return false 306 } 307 } 308 return true 309 } 310 311 // Equal compares the metadata and contents of r and s. 312 func Equal(r Record, s Record) bool { 313 if r.Info != s.Info { 314 return false 315 } 316 return uio.ReaderAtEqual(r.ReaderAt, s.ReaderAt) 317 }