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  }