github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/cpio/newc.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  	"encoding/binary"
    10  	"encoding/hex"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  
    15  	"github.com/mvdan/u-root-coreutils/pkg/uio"
    16  )
    17  
    18  const (
    19  	newcMagic = "070701"
    20  	magicLen  = 6
    21  )
    22  
    23  // Newc is the newc CPIO record format.
    24  var Newc RecordFormat = newc{magic: newcMagic}
    25  
    26  type header struct {
    27  	Ino        uint32
    28  	Mode       uint32
    29  	UID        uint32
    30  	GID        uint32
    31  	NLink      uint32
    32  	MTime      uint32
    33  	FileSize   uint32
    34  	Major      uint32
    35  	Minor      uint32
    36  	Rmajor     uint32
    37  	Rminor     uint32
    38  	NameLength uint32
    39  	CRC        uint32
    40  }
    41  
    42  func headerFromInfo(i Info) header {
    43  	var h header
    44  	h.Ino = uint32(i.Ino)
    45  	h.Mode = uint32(i.Mode)
    46  	h.UID = uint32(i.UID)
    47  	h.GID = uint32(i.GID)
    48  	h.NLink = uint32(i.NLink)
    49  	h.MTime = uint32(i.MTime)
    50  	h.FileSize = uint32(i.FileSize)
    51  	h.Major = uint32(i.Major)
    52  	h.Minor = uint32(i.Minor)
    53  	h.Rmajor = uint32(i.Rmajor)
    54  	h.Rminor = uint32(i.Rminor)
    55  	h.NameLength = uint32(len(i.Name)) + 1
    56  	return h
    57  }
    58  
    59  func (h header) Info() Info {
    60  	var i Info
    61  	i.Ino = uint64(h.Ino)
    62  	i.Mode = uint64(h.Mode)
    63  	i.UID = uint64(h.UID)
    64  	i.GID = uint64(h.GID)
    65  	i.NLink = uint64(h.NLink)
    66  	i.MTime = uint64(h.MTime)
    67  	i.FileSize = uint64(h.FileSize)
    68  	i.Major = uint64(h.Major)
    69  	i.Minor = uint64(h.Minor)
    70  	i.Rmajor = uint64(h.Rmajor)
    71  	i.Rminor = uint64(h.Rminor)
    72  	return i
    73  }
    74  
    75  // newc implements RecordFormat for the newc format.
    76  type newc struct {
    77  	magic string
    78  }
    79  
    80  // round4 returns the next multiple of 4 close to n.
    81  func round4(n int64) int64 {
    82  	return (n + 3) &^ 0x3
    83  }
    84  
    85  type writer struct {
    86  	n   newc
    87  	w   io.Writer
    88  	pos int64
    89  }
    90  
    91  // Writer implements RecordFormat.Writer.
    92  func (n newc) Writer(w io.Writer) RecordWriter {
    93  	return NewDedupWriter(&writer{n: n, w: w})
    94  }
    95  
    96  func (w *writer) Write(b []byte) (int, error) {
    97  	n, err := w.w.Write(b)
    98  	if err != nil {
    99  		return 0, err
   100  	}
   101  	w.pos += int64(n)
   102  	return n, nil
   103  }
   104  
   105  func (w *writer) pad() error {
   106  	if o := round4(w.pos); o != w.pos {
   107  		var pad [3]byte
   108  		if _, err := w.Write(pad[:o-w.pos]); err != nil {
   109  			return err
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  // WriteRecord writes newc cpio records. It pads the header+name write to 4
   116  // byte alignment and pads the data write as well.
   117  func (w *writer) WriteRecord(f Record) error {
   118  	// Write magic.
   119  	if _, err := w.Write([]byte(w.n.magic)); err != nil {
   120  		return err
   121  	}
   122  
   123  	buf := &bytes.Buffer{}
   124  	hdr := headerFromInfo(f.Info)
   125  	if f.ReaderAt == nil {
   126  		hdr.FileSize = 0
   127  	}
   128  	hdr.CRC = 0
   129  	if err := binary.Write(buf, binary.BigEndian, hdr); err != nil {
   130  		return err
   131  	}
   132  
   133  	hexBuf := make([]byte, hex.EncodedLen(buf.Len()))
   134  	n := hex.Encode(hexBuf, buf.Bytes())
   135  	// It's much easier to debug if we match GNU output format.
   136  	hexBuf = bytes.ToUpper(hexBuf)
   137  
   138  	// Write header.
   139  	if _, err := w.Write(hexBuf[:n]); err != nil {
   140  		return err
   141  	}
   142  
   143  	// Append NULL char.
   144  	cstr := append([]byte(f.Info.Name), 0)
   145  	// Write name.
   146  	if _, err := w.Write(cstr); err != nil {
   147  		return err
   148  	}
   149  
   150  	// Pad to a multiple of 4.
   151  	if err := w.pad(); err != nil {
   152  		return err
   153  	}
   154  
   155  	// Some files do not have any content.
   156  	if f.ReaderAt == nil {
   157  		return nil
   158  	}
   159  
   160  	// Write file contents.
   161  	m, err := io.Copy(w, uio.Reader(f))
   162  	if err != nil {
   163  		return err
   164  	}
   165  	if m != int64(f.Info.FileSize) {
   166  		return fmt.Errorf("WriteRecord: %s: wrote %d bytes of file instead of %d bytes; archive is now corrupt", f.Info.Name, m, f.Info.FileSize)
   167  	}
   168  	if c, ok := f.ReaderAt.(io.Closer); ok {
   169  		if err := c.Close(); err != nil {
   170  			return err
   171  		}
   172  	}
   173  	if m > 0 {
   174  		return w.pad()
   175  	}
   176  	return nil
   177  }
   178  
   179  type reader struct {
   180  	n   newc
   181  	r   io.ReaderAt
   182  	pos int64
   183  }
   184  
   185  // discarder is used to implement ReadAt from a Reader
   186  // by reading, and discarding, data until the offset
   187  // is reached. It can only go forward. It is designed
   188  // for pipe-like files.
   189  type discarder struct {
   190  	r   io.Reader
   191  	pos int64
   192  }
   193  
   194  // ReadAt implements ReadAt for a discarder.
   195  // It is an error for the offset to be negative.
   196  func (r *discarder) ReadAt(p []byte, off int64) (int, error) {
   197  	if off-r.pos < 0 {
   198  		return 0, fmt.Errorf("negative seek on discarder not allowed")
   199  	}
   200  	if off != r.pos {
   201  		i, err := io.Copy(io.Discard, io.LimitReader(r.r, off-r.pos))
   202  		if err != nil || i != off-r.pos {
   203  			return 0, err
   204  		}
   205  		r.pos += i
   206  	}
   207  	n, err := io.ReadFull(r.r, p)
   208  	if err != nil {
   209  		return n, err
   210  	}
   211  	r.pos += int64(n)
   212  	return n, err
   213  }
   214  
   215  var _ io.ReaderAt = &discarder{}
   216  
   217  // Reader implements RecordFormat.Reader.
   218  func (n newc) Reader(r io.ReaderAt) RecordReader {
   219  	return EOFReader{&reader{n: n, r: r}}
   220  }
   221  
   222  // NewFileReader implements RecordFormat.Reader. If the file
   223  // implements ReadAt, then it is used for greater efficiency.
   224  // If it only implements Read, then a discarder will be used
   225  // instead.
   226  // Note a complication:
   227  //
   228  //	r, _, _ := os.Pipe()
   229  //	var b [2]byte
   230  //	_, err := r.ReadAt(b[:], 0)
   231  //	fmt.Printf("%v", err)
   232  //
   233  // Pipes claim to implement ReadAt; most Unix kernels
   234  // do not agree. Even a seek to the current position fails.
   235  // This means that
   236  // if rat, ok := r.(io.ReaderAt); ok {
   237  // would seem to work, but would fail when the
   238  // actual ReadAt on the pipe occurs, even for offset 0,
   239  // which does not require a seek! The kernel checks for
   240  // whether the fd is seekable and returns an error,
   241  // even for values of offset which won't require a seek.
   242  // So, the code makes a simple test: can we seek to
   243  // current offset? If not, then the file is wrapped with a
   244  // discardreader. The discard reader is far less efficient
   245  // but allows cpio to read from a pipe.
   246  func (n newc) NewFileReader(f *os.File) (RecordReader, error) {
   247  	_, err := f.Seek(0, 0)
   248  	if err == nil {
   249  		return EOFReader{&reader{n: n, r: f}}, nil
   250  	}
   251  	return EOFReader{&reader{n: n, r: &discarder{r: f}}}, nil
   252  }
   253  
   254  func (r *reader) read(p []byte) error {
   255  	n, err := r.r.ReadAt(p, r.pos)
   256  
   257  	if err == io.EOF {
   258  		return io.EOF
   259  	}
   260  
   261  	if err != nil || n != len(p) {
   262  		return fmt.Errorf("ReadAt(pos = %d): got %d, want %d bytes; error %v", r.pos, n, len(p), err)
   263  	}
   264  
   265  	r.pos += int64(n)
   266  	return nil
   267  }
   268  
   269  func (r *reader) readAligned(p []byte) error {
   270  	err := r.read(p)
   271  	r.pos = round4(r.pos)
   272  	return err
   273  }
   274  
   275  // ReadRecord implements RecordReader for the newc cpio format.
   276  func (r *reader) ReadRecord() (Record, error) {
   277  	hdr := header{}
   278  	recPos := r.pos
   279  
   280  	buf := make([]byte, hex.EncodedLen(binary.Size(hdr))+magicLen)
   281  	if err := r.read(buf); err != nil {
   282  		return Record{}, err
   283  	}
   284  
   285  	// Check the magic.
   286  	if magic := string(buf[:magicLen]); magic != r.n.magic {
   287  		return Record{}, fmt.Errorf("reader: magic got %q, want %q", magic, r.n.magic)
   288  	}
   289  
   290  	// Decode hex header fields.
   291  	dst := make([]byte, binary.Size(hdr))
   292  	if _, err := hex.Decode(dst, buf[magicLen:]); err != nil {
   293  		return Record{}, fmt.Errorf("reader: error decoding hex: %v", err)
   294  	}
   295  	if err := binary.Read(bytes.NewReader(dst), binary.BigEndian, &hdr); err != nil {
   296  		return Record{}, err
   297  	}
   298  	Debug("Decoded header is %v\n", hdr)
   299  
   300  	// Get the name.
   301  	if hdr.NameLength == 0 {
   302  		return Record{}, fmt.Errorf("name field of length zero")
   303  	}
   304  	nameBuf := make([]byte, hdr.NameLength)
   305  	if err := r.readAligned(nameBuf); err != nil {
   306  		Debug("name read failed")
   307  		return Record{}, err
   308  	}
   309  
   310  	info := hdr.Info()
   311  	info.Name = Normalize(string(nameBuf[:hdr.NameLength-1]))
   312  
   313  	recLen := uint64(r.pos - recPos)
   314  	filePos := r.pos
   315  
   316  	//TODO: check if hdr.FileSize is equal to the actual fileSize of the record
   317  	content := io.NewSectionReader(r.r, r.pos, int64(hdr.FileSize))
   318  	r.pos = round4(r.pos + int64(hdr.FileSize))
   319  	return Record{
   320  		Info:     info,
   321  		ReaderAt: content,
   322  		RecLen:   recLen,
   323  		RecPos:   recPos,
   324  		FilePos:  filePos,
   325  	}, nil
   326  }
   327  
   328  func init() {
   329  	formatMap["newc"] = Newc
   330  }