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  }