github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/pkg/cpio/cpio.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  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/u-root/u-root/pkg/ls"
    17  	"github.com/u-root/u-root/pkg/uio"
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  var (
    22  	formatMap = make(map[string]RecordFormat)
    23  	Debug     = func(string, ...interface{}) {}
    24  )
    25  
    26  // A RecordReader reads one record from an archive.
    27  type RecordReader interface {
    28  	ReadRecord() (Record, error)
    29  }
    30  
    31  // A RecordWriter writes one record to an archive.
    32  type RecordWriter interface {
    33  	WriteRecord(Record) error
    34  }
    35  
    36  // A RecordFormat gives readers and writers for dealing with archives from io
    37  // objects.
    38  type RecordFormat interface {
    39  	Reader(r io.ReaderAt) RecordReader
    40  	Writer(w io.Writer) RecordWriter
    41  }
    42  
    43  // Format returns the RecordFormat with that name, if it exists.
    44  func Format(name string) (RecordFormat, error) {
    45  	op, ok := formatMap[name]
    46  	if !ok {
    47  		return nil, fmt.Errorf("%q is not in cpio format map %v", name, formatMap)
    48  	}
    49  	return op, nil
    50  }
    51  
    52  // EOFReader is a RecordReader that converts the Trailer record to io.EOF.
    53  type EOFReader struct {
    54  	RecordReader
    55  }
    56  
    57  // ReadRecord implements RecordReader.
    58  //
    59  // ReadRecord returns io.EOF when the record name is TRAILER!!!.
    60  func (r EOFReader) ReadRecord() (Record, error) {
    61  	rec, err := r.RecordReader.ReadRecord()
    62  	if err != nil {
    63  		return Record{}, err
    64  	}
    65  	// The end of a CPIO archive is marked by a record whose name is
    66  	// "TRAILER!!!".
    67  	if rec.Name == Trailer {
    68  		return Record{}, io.EOF
    69  	}
    70  	return rec, nil
    71  }
    72  
    73  // DedupWriter is a RecordWriter that does not write more than one record with
    74  // the same path.
    75  //
    76  // There seems to be no harm done in stripping duplicate names when the record
    77  // is written, and lots of harm done if we don't do it.
    78  type DedupWriter struct {
    79  	rw RecordWriter
    80  
    81  	// alreadyWritten keeps track of paths already written to rw.
    82  	alreadyWritten map[string]struct{}
    83  }
    84  
    85  // NewDedupWriter returns a new deduplicating rw.
    86  func NewDedupWriter(rw RecordWriter) RecordWriter {
    87  	return &DedupWriter{
    88  		rw:             rw,
    89  		alreadyWritten: make(map[string]struct{}),
    90  	}
    91  }
    92  
    93  // Passthrough copies from a RecordReader to a RecordWriter
    94  // It processes one record at a time to minimize the memory footprint.
    95  func Passthrough(r RecordReader, w RecordWriter) error {
    96  	if err := Concat(w, r, nil); err != nil {
    97  		return err
    98  	}
    99  	if err := WriteTrailer(w); err != nil {
   100  		return err
   101  	}
   102  	return nil
   103  }
   104  
   105  // WriteRecord implements RecordWriter.
   106  //
   107  // If rec.Name was already seen once before, it will not be written again and
   108  // WriteRecord returns nil.
   109  func (dw *DedupWriter) WriteRecord(rec Record) error {
   110  	rec.Name = Normalize(rec.Name)
   111  
   112  	if _, ok := dw.alreadyWritten[rec.Name]; ok {
   113  		return nil
   114  	}
   115  	dw.alreadyWritten[rec.Name] = struct{}{}
   116  	return dw.rw.WriteRecord(rec)
   117  }
   118  
   119  // WriteRecords writes multiple records.
   120  func WriteRecords(w RecordWriter, files []Record) error {
   121  	for _, f := range files {
   122  		if err := w.WriteRecord(f); err != nil {
   123  			return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err)
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  // WriteTrailer writes the trailer record.
   130  func WriteTrailer(w RecordWriter) error {
   131  	return w.WriteRecord(TrailerRecord)
   132  }
   133  
   134  // Concat reads files from r one at a time, and writes them to w.
   135  func Concat(w RecordWriter, r RecordReader, transform func(Record) Record) error {
   136  	// Read and write one file at a time. We don't want all that in memory.
   137  	for {
   138  		f, err := r.ReadRecord()
   139  		if err == io.EOF {
   140  			return nil
   141  		}
   142  		if err != nil {
   143  			return err
   144  		}
   145  		if transform != nil {
   146  			f = transform(f)
   147  		}
   148  		if err := w.WriteRecord(f); err != nil {
   149  			return err
   150  		}
   151  	}
   152  }
   153  
   154  // Archive implements RecordWriter and is an in-memory list of files.
   155  //
   156  // Archive.Reader returns a RecordReader for this archive.
   157  type Archive struct {
   158  	// Files is a map of relative archive path -> record.
   159  	Files map[string]Record
   160  
   161  	// Order is a list of relative archive paths and represents the order
   162  	// in which Files were added.
   163  	Order []string
   164  }
   165  
   166  // InMemArchive returns an in-memory file archive.
   167  func InMemArchive() *Archive {
   168  	return &Archive{
   169  		Files: make(map[string]Record),
   170  	}
   171  }
   172  
   173  func ArchiveFromRecords(rs []Record) *Archive {
   174  	a := InMemArchive()
   175  	for _, r := range rs {
   176  		a.WriteRecord(r)
   177  	}
   178  	return a
   179  }
   180  
   181  // WriteRecord implements RecordWriter and adds a record to the archive.
   182  func (a *Archive) WriteRecord(r Record) error {
   183  	r.Name = Normalize(r.Name)
   184  	a.Files[r.Name] = r
   185  	a.Order = append(a.Order, r.Name)
   186  	return nil
   187  }
   188  
   189  // Empty returns whether the archive has any files in it.
   190  func (a *Archive) Empty() bool {
   191  	return len(a.Files) == 0
   192  }
   193  
   194  type archiveReader struct {
   195  	a   *Archive
   196  	pos int
   197  }
   198  
   199  // Reader returns a RecordReader for the archive that starts at the first
   200  // record.
   201  func (a *Archive) Reader() RecordReader {
   202  	return &EOFReader{&archiveReader{a: a}}
   203  }
   204  
   205  func modeFromLinux(mode uint64) os.FileMode {
   206  	m := os.FileMode(mode & 0777)
   207  	switch mode & syscall.S_IFMT {
   208  	case syscall.S_IFBLK:
   209  		m |= os.ModeDevice
   210  	case syscall.S_IFCHR:
   211  		m |= os.ModeDevice | os.ModeCharDevice
   212  	case syscall.S_IFDIR:
   213  		m |= os.ModeDir
   214  	case syscall.S_IFIFO:
   215  		m |= os.ModeNamedPipe
   216  	case syscall.S_IFLNK:
   217  		m |= os.ModeSymlink
   218  	case syscall.S_IFREG:
   219  		// nothing to do
   220  	case syscall.S_IFSOCK:
   221  		m |= os.ModeSocket
   222  	}
   223  	if mode&syscall.S_ISGID != 0 {
   224  		m |= os.ModeSetgid
   225  	}
   226  	if mode&syscall.S_ISUID != 0 {
   227  		m |= os.ModeSetuid
   228  	}
   229  	if mode&syscall.S_ISVTX != 0 {
   230  		m |= os.ModeSticky
   231  	}
   232  	return m
   233  }
   234  
   235  // LSInfoFromRecord converts a Record to be usable with the ls package for
   236  // listing files.
   237  func LSInfoFromRecord(rec Record) ls.FileInfo {
   238  	var target string
   239  
   240  	mode := modeFromLinux(rec.Mode)
   241  	if mode&os.ModeType == os.ModeSymlink {
   242  		if l, err := uio.ReadAll(rec); err != nil {
   243  			target = err.Error()
   244  		} else {
   245  			target = string(l)
   246  		}
   247  	}
   248  
   249  	return ls.FileInfo{
   250  		Name:          rec.Name,
   251  		Mode:          mode,
   252  		Rdev:          unix.Mkdev(uint32(rec.Rmajor), uint32(rec.Rminor)),
   253  		UID:           uint32(rec.UID),
   254  		GID:           uint32(rec.GID),
   255  		Size:          int64(rec.FileSize),
   256  		MTime:         time.Unix(int64(rec.MTime), 0).UTC(),
   257  		SymlinkTarget: target,
   258  	}
   259  }
   260  
   261  // String implements fmt.Stringer.
   262  //
   263  // String lists files like ls would.
   264  func (a *Archive) String() string {
   265  	var b strings.Builder
   266  	r := a.Reader()
   267  	for {
   268  		record, err := r.ReadRecord()
   269  		if err != nil {
   270  			return b.String()
   271  		}
   272  		b.WriteString(record.String())
   273  		b.WriteString("\n")
   274  	}
   275  }
   276  
   277  // ReadRecord implements RecordReader.
   278  func (ar *archiveReader) ReadRecord() (Record, error) {
   279  	if ar.pos >= len(ar.a.Order) {
   280  		return Record{}, io.EOF
   281  	}
   282  
   283  	path := ar.a.Order[ar.pos]
   284  	ar.pos++
   285  	return ar.a.Files[path], nil
   286  }
   287  
   288  // Contains returns true if a record matching r is in the archive.
   289  func (a *Archive) Contains(r Record) bool {
   290  	r.Name = Normalize(r.Name)
   291  	if s, ok := a.Files[r.Name]; ok {
   292  		return Equal(r, s)
   293  	}
   294  	return false
   295  }
   296  
   297  func (a *Archive) Get(path string) (Record, bool) {
   298  	r, ok := a.Files[Normalize(path)]
   299  	return r, ok
   300  }
   301  
   302  // ReadArchive reads the entire archive in-memory and makes it accessible by
   303  // paths.
   304  func ReadArchive(rr RecordReader) (*Archive, error) {
   305  	a := &Archive{
   306  		Files: make(map[string]Record),
   307  	}
   308  	err := ForEachRecord(rr, func(r Record) error {
   309  		return a.WriteRecord(r)
   310  	})
   311  	return a, err
   312  }
   313  
   314  // ReadAllRecords returns all records in `r` in the order in which they were
   315  // read.
   316  func ReadAllRecords(rr RecordReader) ([]Record, error) {
   317  	var files []Record
   318  	err := ForEachRecord(rr, func(r Record) error {
   319  		files = append(files, r)
   320  		return nil
   321  	})
   322  	return files, err
   323  }
   324  
   325  // ForEachRecord reads every record from r and applies f.
   326  func ForEachRecord(rr RecordReader, fun func(Record) error) error {
   327  	for {
   328  		rec, err := rr.ReadRecord()
   329  		switch err {
   330  		case io.EOF:
   331  			return nil
   332  
   333  		case nil:
   334  			if err := fun(rec); err != nil {
   335  				return err
   336  			}
   337  
   338  		default:
   339  			return err
   340  		}
   341  	}
   342  }
   343  
   344  // Normalize normalizes path to be relative to /.
   345  func Normalize(path string) string {
   346  	if filepath.IsAbs(path) {
   347  		rel, err := filepath.Rel("/", path)
   348  		if err != nil {
   349  			panic("absolute filepath must be relative to /")
   350  		}
   351  		return rel
   352  	}
   353  	return path
   354  }
   355  
   356  // MakeReproducible changes any fields in a Record such that if we run cpio
   357  // again, with the same files presented to it in the same order, and those
   358  // files have unchanged contents, the cpio file it produces will be bit-for-bit
   359  // identical. This is an essential property for firmware-embedded payloads.
   360  func MakeReproducible(r Record) Record {
   361  	r.Ino = 0
   362  	r.Name = Normalize(r.Name)
   363  	r.MTime = 0
   364  	r.UID = 0
   365  	r.GID = 0
   366  	r.Dev = 0
   367  	r.Major = 0
   368  	r.Minor = 0
   369  	r.NLink = 0
   370  	return r
   371  }
   372  
   373  // MakeAllReproducible makes all given records reproducible as in
   374  // MakeReproducible.
   375  func MakeAllReproducible(files []Record) {
   376  	for i := range files {
   377  		files[i] = MakeReproducible(files[i])
   378  	}
   379  }