github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/archive/tar/writer.go (about)

     1  // Copyright 2009 The Go 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 tar
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"path"
    12  	"sort"
    13  	"strings"
    14  	"time"
    15  )
    16  
    17  // A Writer provides sequential writing of a tar archive in POSIX.1 format.
    18  // A tar archive consists of a sequence of files.
    19  // Call WriteHeader to begin a new file, and then call Write to supply that file's data,
    20  // writing at most hdr.Size bytes in total.
    21  type Writer struct {
    22  	w   io.Writer
    23  	nb  int64  // number of unwritten bytes for current file entry
    24  	pad int64  // amount of padding to write after current file entry
    25  	hdr Header // Shallow copy of Header that is safe for mutations
    26  	blk block  // Buffer to use as temporary local storage
    27  
    28  	// err is a persistent error.
    29  	// It is only the responsibility of every exported method of Writer to
    30  	// ensure that this error is sticky.
    31  	err error
    32  }
    33  
    34  // NewWriter creates a new Writer writing to w.
    35  func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
    36  
    37  // Flush finishes writing the current file's block padding.
    38  // The current file must be fully written before Flush can be called.
    39  //
    40  // Deprecated: This is unecessary as the next call to WriteHeader or Close
    41  // will implicitly flush out the file's padding.
    42  func (tw *Writer) Flush() error {
    43  	if tw.err != nil {
    44  		return tw.err
    45  	}
    46  	if tw.nb > 0 {
    47  		return fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
    48  	}
    49  	if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil {
    50  		return tw.err
    51  	}
    52  	tw.pad = 0
    53  	return nil
    54  }
    55  
    56  // WriteHeader writes hdr and prepares to accept the file's contents.
    57  // WriteHeader calls Flush if it is not the first header.
    58  // Calling after a Close will return ErrWriteAfterClose.
    59  func (tw *Writer) WriteHeader(hdr *Header) error {
    60  	if err := tw.Flush(); err != nil {
    61  		return err
    62  	}
    63  
    64  	tw.hdr = *hdr // Shallow copy of Header
    65  	switch allowedFormats, paxHdrs := tw.hdr.allowedFormats(); {
    66  	case allowedFormats&formatUSTAR != 0:
    67  		tw.err = tw.writeUSTARHeader(&tw.hdr)
    68  		return tw.err
    69  	case allowedFormats&formatPAX != 0:
    70  		tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs)
    71  		return tw.err
    72  	case allowedFormats&formatGNU != 0:
    73  		tw.err = tw.writeGNUHeader(&tw.hdr)
    74  		return tw.err
    75  	default:
    76  		return ErrHeader // Non-fatal error
    77  	}
    78  }
    79  
    80  func (tw *Writer) writeUSTARHeader(hdr *Header) error {
    81  	// Check if we can use USTAR prefix/suffix splitting.
    82  	var namePrefix string
    83  	if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok {
    84  		namePrefix, hdr.Name = prefix, suffix
    85  	}
    86  
    87  	// Pack the main header.
    88  	var f formatter
    89  	blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal)
    90  	f.formatString(blk.USTAR().Prefix(), namePrefix)
    91  	blk.SetFormat(formatUSTAR)
    92  	if f.err != nil {
    93  		return f.err // Should never happen since header is validated
    94  	}
    95  	return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)
    96  }
    97  
    98  func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
    99  	// Write PAX records to the output.
   100  	if len(paxHdrs) > 0 {
   101  		// Sort keys for deterministic ordering.
   102  		var keys []string
   103  		for k := range paxHdrs {
   104  			keys = append(keys, k)
   105  		}
   106  		sort.Strings(keys)
   107  
   108  		// Write each record to a buffer.
   109  		var buf bytes.Buffer
   110  		for _, k := range keys {
   111  			rec, err := formatPAXRecord(k, paxHdrs[k])
   112  			if err != nil {
   113  				return err
   114  			}
   115  			buf.WriteString(rec)
   116  		}
   117  
   118  		// Write the extended header file.
   119  		dir, file := path.Split(hdr.Name)
   120  		name := path.Join(dir, "PaxHeaders.0", file)
   121  		data := buf.String()
   122  		if err := tw.writeRawFile(name, data, TypeXHeader, formatPAX); err != nil {
   123  			return err
   124  		}
   125  	}
   126  
   127  	// Pack the main header.
   128  	var f formatter // Ignore errors since they are expected
   129  	fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) }
   130  	blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal)
   131  	blk.SetFormat(formatPAX)
   132  	return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)
   133  }
   134  
   135  func (tw *Writer) writeGNUHeader(hdr *Header) error {
   136  	// TODO(dsnet): Support writing sparse files.
   137  	// See https://golang.org/issue/13548
   138  
   139  	// Use long-link files if Name or Linkname exceeds the field size.
   140  	const longName = "././@LongLink"
   141  	if len(hdr.Name) > nameSize {
   142  		data := hdr.Name + "\x00"
   143  		if err := tw.writeRawFile(longName, data, TypeGNULongName, formatGNU); err != nil {
   144  			return err
   145  		}
   146  	}
   147  	if len(hdr.Linkname) > nameSize {
   148  		data := hdr.Linkname + "\x00"
   149  		if err := tw.writeRawFile(longName, data, TypeGNULongLink, formatGNU); err != nil {
   150  			return err
   151  		}
   152  	}
   153  
   154  	// Pack the main header.
   155  	var f formatter // Ignore errors since they are expected
   156  	blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric)
   157  	if !hdr.AccessTime.IsZero() {
   158  		f.formatNumeric(blk.GNU().AccessTime(), hdr.AccessTime.Unix())
   159  	}
   160  	if !hdr.ChangeTime.IsZero() {
   161  		f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix())
   162  	}
   163  	blk.SetFormat(formatGNU)
   164  	return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)
   165  }
   166  
   167  type (
   168  	stringFormatter func([]byte, string)
   169  	numberFormatter func([]byte, int64)
   170  )
   171  
   172  // templateV7Plus fills out the V7 fields of a block using values from hdr.
   173  // It also fills out fields (uname, gname, devmajor, devminor) that are
   174  // shared in the USTAR, PAX, and GNU formats using the provided formatters.
   175  //
   176  // The block returned is only valid until the next call to
   177  // templateV7Plus or writeRawFile.
   178  func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block {
   179  	tw.blk.Reset()
   180  
   181  	modTime := hdr.ModTime
   182  	if modTime.IsZero() {
   183  		modTime = time.Unix(0, 0)
   184  	}
   185  
   186  	v7 := tw.blk.V7()
   187  	v7.TypeFlag()[0] = hdr.Typeflag
   188  	fmtStr(v7.Name(), hdr.Name)
   189  	fmtStr(v7.LinkName(), hdr.Linkname)
   190  	fmtNum(v7.Mode(), hdr.Mode)
   191  	fmtNum(v7.UID(), int64(hdr.Uid))
   192  	fmtNum(v7.GID(), int64(hdr.Gid))
   193  	fmtNum(v7.Size(), hdr.Size)
   194  	fmtNum(v7.ModTime(), modTime.Unix())
   195  
   196  	ustar := tw.blk.USTAR()
   197  	fmtStr(ustar.UserName(), hdr.Uname)
   198  	fmtStr(ustar.GroupName(), hdr.Gname)
   199  	fmtNum(ustar.DevMajor(), hdr.Devmajor)
   200  	fmtNum(ustar.DevMinor(), hdr.Devminor)
   201  
   202  	return &tw.blk
   203  }
   204  
   205  // writeRawFile writes a minimal file with the given name and flag type.
   206  // It uses format to encode the header format and will write data as the body.
   207  // It uses default values for all of the other fields (as BSD and GNU tar does).
   208  func (tw *Writer) writeRawFile(name, data string, flag byte, format int) error {
   209  	tw.blk.Reset()
   210  
   211  	// Best effort for the filename.
   212  	name = toASCII(name)
   213  	if len(name) > nameSize {
   214  		name = name[:nameSize]
   215  	}
   216  
   217  	var f formatter
   218  	v7 := tw.blk.V7()
   219  	v7.TypeFlag()[0] = flag
   220  	f.formatString(v7.Name(), name)
   221  	f.formatOctal(v7.Mode(), 0)
   222  	f.formatOctal(v7.UID(), 0)
   223  	f.formatOctal(v7.GID(), 0)
   224  	f.formatOctal(v7.Size(), int64(len(data))) // Must be < 8GiB
   225  	f.formatOctal(v7.ModTime(), 0)
   226  	tw.blk.SetFormat(format)
   227  	if f.err != nil {
   228  		return f.err // Only occurs if size condition is violated
   229  	}
   230  
   231  	// Write the header and data.
   232  	if err := tw.writeRawHeader(&tw.blk, int64(len(data)), flag); err != nil {
   233  		return err
   234  	}
   235  	_, err := io.WriteString(tw, data)
   236  	return err
   237  }
   238  
   239  // writeRawHeader writes the value of blk, regardless of its value.
   240  // It sets up the Writer such that it can accept a file of the given size.
   241  // If the flag is a special header-only flag, then the size is treated as zero.
   242  func (tw *Writer) writeRawHeader(blk *block, size int64, flag byte) error {
   243  	if err := tw.Flush(); err != nil {
   244  		return err
   245  	}
   246  	if _, err := tw.w.Write(blk[:]); err != nil {
   247  		return err
   248  	}
   249  	if isHeaderOnlyType(flag) {
   250  		size = 0
   251  	}
   252  	tw.nb = size
   253  	tw.pad = -size & (blockSize - 1) // blockSize is a power of two
   254  	return nil
   255  }
   256  
   257  // splitUSTARPath splits a path according to USTAR prefix and suffix rules.
   258  // If the path is not splittable, then it will return ("", "", false).
   259  func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
   260  	length := len(name)
   261  	if length <= nameSize || !isASCII(name) {
   262  		return "", "", false
   263  	} else if length > prefixSize+1 {
   264  		length = prefixSize + 1
   265  	} else if name[length-1] == '/' {
   266  		length--
   267  	}
   268  
   269  	i := strings.LastIndex(name[:length], "/")
   270  	nlen := len(name) - i - 1 // nlen is length of suffix
   271  	plen := i                 // plen is length of prefix
   272  	if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize {
   273  		return "", "", false
   274  	}
   275  	return name[:i], name[i+1:], true
   276  }
   277  
   278  // Write writes to the current entry in the tar archive.
   279  // Write returns the error ErrWriteTooLong if more than
   280  // Header.Size bytes are written after WriteHeader.
   281  //
   282  // Calling Write on special types like TypeLink, TypeSymLink, TypeChar,
   283  // TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless
   284  // of what the Header.Size claims.
   285  func (tw *Writer) Write(b []byte) (int, error) {
   286  	if tw.err != nil {
   287  		return 0, tw.err
   288  	}
   289  
   290  	overwrite := int64(len(b)) > tw.nb
   291  	if overwrite {
   292  		b = b[:tw.nb]
   293  	}
   294  	n, err := tw.w.Write(b)
   295  	tw.nb -= int64(n)
   296  	if err == nil && overwrite {
   297  		return n, ErrWriteTooLong // Non-fatal error
   298  	}
   299  	tw.err = err
   300  	return n, err
   301  }
   302  
   303  // Close closes the tar archive, flushing any unwritten
   304  // data to the underlying writer.
   305  func (tw *Writer) Close() error {
   306  	if tw.err == ErrWriteAfterClose {
   307  		return nil
   308  	}
   309  	if tw.err != nil {
   310  		return tw.err
   311  	}
   312  
   313  	// Trailer: two zero blocks.
   314  	err := tw.Flush()
   315  	for i := 0; i < 2 && err == nil; i++ {
   316  		_, err = tw.w.Write(zeroBlock[:])
   317  	}
   318  
   319  	// Ensure all future actions are invalid.
   320  	tw.err = ErrWriteAfterClose
   321  	return err // Report IO errors
   322  }