gitlab.com/apertussolutions/u-root@v7.0.0+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  }