Source file src/archive/tar/writer.go

     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  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/fs"
    12  	"maps"
    13  	"path"
    14  	"slices"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  // Writer provides sequential writing of a tar archive.
    20  // [Writer.WriteHeader] begins a new file with the provided [Header],
    21  // and then Writer can be treated as an io.Writer to supply that file's data.
    22  type Writer struct {
    23  	w    io.Writer
    24  	pad  int64      // Amount of padding to write after current file entry
    25  	curr fileWriter // Writer for current file entry
    26  	hdr  Header     // Shallow copy of Header that is safe for mutations
    27  	blk  block      // Buffer to use as temporary local storage
    28  
    29  	// err is a persistent error.
    30  	// It is only the responsibility of every exported method of Writer to
    31  	// ensure that this error is sticky.
    32  	err error
    33  }
    34  
    35  // NewWriter creates a new Writer writing to w.
    36  func NewWriter(w io.Writer) *Writer {
    37  	return &Writer{w: w, curr: &regFileWriter{w, 0}}
    38  }
    39  
    40  type fileWriter interface {
    41  	io.Writer
    42  	fileState
    43  
    44  	ReadFrom(io.Reader) (int64, error)
    45  }
    46  
    47  // Flush finishes writing the current file's block padding.
    48  // The current file must be fully written before Flush can be called.
    49  //
    50  // This is unnecessary as the next call to [Writer.WriteHeader] or [Writer.Close]
    51  // will implicitly flush out the file's padding.
    52  func (tw *Writer) Flush() error {
    53  	if tw.err != nil {
    54  		return tw.err
    55  	}
    56  	if nb := tw.curr.logicalRemaining(); nb > 0 {
    57  		return fmt.Errorf("archive/tar: missed writing %d bytes", nb)
    58  	}
    59  	if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil {
    60  		return tw.err
    61  	}
    62  	tw.pad = 0
    63  	return nil
    64  }
    65  
    66  // WriteHeader writes hdr and prepares to accept the file's contents.
    67  // The Header.Size determines how many bytes can be written for the next file.
    68  // If the current file is not fully written, then this returns an error.
    69  // This implicitly flushes any padding necessary before writing the header.
    70  func (tw *Writer) WriteHeader(hdr *Header) error {
    71  	if err := tw.Flush(); err != nil {
    72  		return err
    73  	}
    74  	tw.hdr = *hdr // Shallow copy of Header
    75  
    76  	// Avoid usage of the legacy TypeRegA flag, and automatically promote
    77  	// it to use TypeReg or TypeDir.
    78  	if tw.hdr.Typeflag == TypeRegA {
    79  		if strings.HasSuffix(tw.hdr.Name, "/") {
    80  			tw.hdr.Typeflag = TypeDir
    81  		} else {
    82  			tw.hdr.Typeflag = TypeReg
    83  		}
    84  	}
    85  
    86  	// Round ModTime and ignore AccessTime and ChangeTime unless
    87  	// the format is explicitly chosen.
    88  	// This ensures nominal usage of WriteHeader (without specifying the format)
    89  	// does not always result in the PAX format being chosen, which
    90  	// causes a 1KiB increase to every header.
    91  	if tw.hdr.Format == FormatUnknown {
    92  		tw.hdr.ModTime = tw.hdr.ModTime.Round(time.Second)
    93  		tw.hdr.AccessTime = time.Time{}
    94  		tw.hdr.ChangeTime = time.Time{}
    95  	}
    96  
    97  	allowedFormats, paxHdrs, err := tw.hdr.allowedFormats()
    98  	switch {
    99  	case allowedFormats.has(FormatUSTAR):
   100  		tw.err = tw.writeUSTARHeader(&tw.hdr)
   101  		return tw.err
   102  	case allowedFormats.has(FormatPAX):
   103  		tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs)
   104  		return tw.err
   105  	case allowedFormats.has(FormatGNU):
   106  		tw.err = tw.writeGNUHeader(&tw.hdr)
   107  		return tw.err
   108  	default:
   109  		return err // Non-fatal error
   110  	}
   111  }
   112  
   113  func (tw *Writer) writeUSTARHeader(hdr *Header) error {
   114  	// Check if we can use USTAR prefix/suffix splitting.
   115  	var namePrefix string
   116  	if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok {
   117  		namePrefix, hdr.Name = prefix, suffix
   118  	}
   119  
   120  	// Pack the main header.
   121  	var f formatter
   122  	blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal)
   123  	f.formatString(blk.toUSTAR().prefix(), namePrefix)
   124  	blk.setFormat(FormatUSTAR)
   125  	if f.err != nil {
   126  		return f.err // Should never happen since header is validated
   127  	}
   128  	return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)
   129  }
   130  
   131  func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
   132  	realName, realSize := hdr.Name, hdr.Size
   133  
   134  	// TODO(dsnet): Re-enable this when adding sparse support.
   135  	// See https://golang.org/issue/22735
   136  	/*
   137  		// Handle sparse files.
   138  		var spd sparseDatas
   139  		var spb []byte
   140  		if len(hdr.SparseHoles) > 0 {
   141  			sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map
   142  			sph = alignSparseEntries(sph, hdr.Size)
   143  			spd = invertSparseEntries(sph, hdr.Size)
   144  
   145  			// Format the sparse map.
   146  			hdr.Size = 0 // Replace with encoded size
   147  			spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n')
   148  			for _, s := range spd {
   149  				hdr.Size += s.Length
   150  				spb = append(strconv.AppendInt(spb, s.Offset, 10), '\n')
   151  				spb = append(strconv.AppendInt(spb, s.Length, 10), '\n')
   152  			}
   153  			pad := blockPadding(int64(len(spb)))
   154  			spb = append(spb, zeroBlock[:pad]...)
   155  			hdr.Size += int64(len(spb)) // Accounts for encoded sparse map
   156  
   157  			// Add and modify appropriate PAX records.
   158  			dir, file := path.Split(realName)
   159  			hdr.Name = path.Join(dir, "GNUSparseFile.0", file)
   160  			paxHdrs[paxGNUSparseMajor] = "1"
   161  			paxHdrs[paxGNUSparseMinor] = "0"
   162  			paxHdrs[paxGNUSparseName] = realName
   163  			paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10)
   164  			paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10)
   165  			delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName
   166  		}
   167  	*/
   168  	_ = realSize
   169  
   170  	// Write PAX records to the output.
   171  	isGlobal := hdr.Typeflag == TypeXGlobalHeader
   172  	if len(paxHdrs) > 0 || isGlobal {
   173  		// Write each record to a buffer.
   174  		var buf strings.Builder
   175  		// Sort keys for deterministic ordering.
   176  		for _, k := range slices.Sorted(maps.Keys(paxHdrs)) {
   177  			rec, err := formatPAXRecord(k, paxHdrs[k])
   178  			if err != nil {
   179  				return err
   180  			}
   181  			buf.WriteString(rec)
   182  		}
   183  
   184  		// Write the extended header file.
   185  		var name string
   186  		var flag byte
   187  		if isGlobal {
   188  			name = realName
   189  			if name == "" {
   190  				name = "GlobalHead.0.0"
   191  			}
   192  			flag = TypeXGlobalHeader
   193  		} else {
   194  			dir, file := path.Split(realName)
   195  			name = path.Join(dir, "PaxHeaders.0", file)
   196  			flag = TypeXHeader
   197  		}
   198  		data := buf.String()
   199  		if len(data) > maxSpecialFileSize {
   200  			return ErrFieldTooLong
   201  		}
   202  		if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal {
   203  			return err // Global headers return here
   204  		}
   205  	}
   206  
   207  	// Pack the main header.
   208  	var f formatter // Ignore errors since they are expected
   209  	fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) }
   210  	blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal)
   211  	blk.setFormat(FormatPAX)
   212  	if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
   213  		return err
   214  	}
   215  
   216  	// TODO(dsnet): Re-enable this when adding sparse support.
   217  	// See https://golang.org/issue/22735
   218  	/*
   219  		// Write the sparse map and setup the sparse writer if necessary.
   220  		if len(spd) > 0 {
   221  			// Use tw.curr since the sparse map is accounted for in hdr.Size.
   222  			if _, err := tw.curr.Write(spb); err != nil {
   223  				return err
   224  			}
   225  			tw.curr = &sparseFileWriter{tw.curr, spd, 0}
   226  		}
   227  	*/
   228  	return nil
   229  }
   230  
   231  func (tw *Writer) writeGNUHeader(hdr *Header) error {
   232  	// Use long-link files if Name or Linkname exceeds the field size.
   233  	const longName = "././@LongLink"
   234  	if len(hdr.Name) > nameSize {
   235  		data := hdr.Name + "\x00"
   236  		if err := tw.writeRawFile(longName, data, TypeGNULongName, FormatGNU); err != nil {
   237  			return err
   238  		}
   239  	}
   240  	if len(hdr.Linkname) > nameSize {
   241  		data := hdr.Linkname + "\x00"
   242  		if err := tw.writeRawFile(longName, data, TypeGNULongLink, FormatGNU); err != nil {
   243  			return err
   244  		}
   245  	}
   246  
   247  	// Pack the main header.
   248  	var f formatter // Ignore errors since they are expected
   249  	var spd sparseDatas
   250  	var spb []byte
   251  	blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric)
   252  	if !hdr.AccessTime.IsZero() {
   253  		f.formatNumeric(blk.toGNU().accessTime(), hdr.AccessTime.Unix())
   254  	}
   255  	if !hdr.ChangeTime.IsZero() {
   256  		f.formatNumeric(blk.toGNU().changeTime(), hdr.ChangeTime.Unix())
   257  	}
   258  	// TODO(dsnet): Re-enable this when adding sparse support.
   259  	// See https://golang.org/issue/22735
   260  	/*
   261  		if hdr.Typeflag == TypeGNUSparse {
   262  			sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map
   263  			sph = alignSparseEntries(sph, hdr.Size)
   264  			spd = invertSparseEntries(sph, hdr.Size)
   265  
   266  			// Format the sparse map.
   267  			formatSPD := func(sp sparseDatas, sa sparseArray) sparseDatas {
   268  				for i := 0; len(sp) > 0 && i < sa.MaxEntries(); i++ {
   269  					f.formatNumeric(sa.Entry(i).Offset(), sp[0].Offset)
   270  					f.formatNumeric(sa.Entry(i).Length(), sp[0].Length)
   271  					sp = sp[1:]
   272  				}
   273  				if len(sp) > 0 {
   274  					sa.IsExtended()[0] = 1
   275  				}
   276  				return sp
   277  			}
   278  			sp2 := formatSPD(spd, blk.GNU().Sparse())
   279  			for len(sp2) > 0 {
   280  				var spHdr block
   281  				sp2 = formatSPD(sp2, spHdr.Sparse())
   282  				spb = append(spb, spHdr[:]...)
   283  			}
   284  
   285  			// Update size fields in the header block.
   286  			realSize := hdr.Size
   287  			hdr.Size = 0 // Encoded size; does not account for encoded sparse map
   288  			for _, s := range spd {
   289  				hdr.Size += s.Length
   290  			}
   291  			copy(blk.V7().Size(), zeroBlock[:]) // Reset field
   292  			f.formatNumeric(blk.V7().Size(), hdr.Size)
   293  			f.formatNumeric(blk.GNU().RealSize(), realSize)
   294  		}
   295  	*/
   296  	blk.setFormat(FormatGNU)
   297  	if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
   298  		return err
   299  	}
   300  
   301  	// Write the extended sparse map and setup the sparse writer if necessary.
   302  	if len(spd) > 0 {
   303  		// Use tw.w since the sparse map is not accounted for in hdr.Size.
   304  		if _, err := tw.w.Write(spb); err != nil {
   305  			return err
   306  		}
   307  		tw.curr = &sparseFileWriter{tw.curr, spd, 0}
   308  	}
   309  	return nil
   310  }
   311  
   312  type (
   313  	stringFormatter func([]byte, string)
   314  	numberFormatter func([]byte, int64)
   315  )
   316  
   317  // templateV7Plus fills out the V7 fields of a block using values from hdr.
   318  // It also fills out fields (uname, gname, devmajor, devminor) that are
   319  // shared in the USTAR, PAX, and GNU formats using the provided formatters.
   320  //
   321  // The block returned is only valid until the next call to
   322  // templateV7Plus or writeRawFile.
   323  func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block {
   324  	tw.blk.reset()
   325  
   326  	modTime := hdr.ModTime
   327  	if modTime.IsZero() {
   328  		modTime = time.Unix(0, 0)
   329  	}
   330  
   331  	v7 := tw.blk.toV7()
   332  	v7.typeFlag()[0] = hdr.Typeflag
   333  	fmtStr(v7.name(), hdr.Name)
   334  	fmtStr(v7.linkName(), hdr.Linkname)
   335  	fmtNum(v7.mode(), hdr.Mode)
   336  	fmtNum(v7.uid(), int64(hdr.Uid))
   337  	fmtNum(v7.gid(), int64(hdr.Gid))
   338  	fmtNum(v7.size(), hdr.Size)
   339  	fmtNum(v7.modTime(), modTime.Unix())
   340  
   341  	ustar := tw.blk.toUSTAR()
   342  	fmtStr(ustar.userName(), hdr.Uname)
   343  	fmtStr(ustar.groupName(), hdr.Gname)
   344  	fmtNum(ustar.devMajor(), hdr.Devmajor)
   345  	fmtNum(ustar.devMinor(), hdr.Devminor)
   346  
   347  	return &tw.blk
   348  }
   349  
   350  // writeRawFile writes a minimal file with the given name and flag type.
   351  // It uses format to encode the header format and will write data as the body.
   352  // It uses default values for all of the other fields (as BSD and GNU tar does).
   353  func (tw *Writer) writeRawFile(name, data string, flag byte, format Format) error {
   354  	tw.blk.reset()
   355  
   356  	// Best effort for the filename.
   357  	name = toASCII(name)
   358  	if len(name) > nameSize {
   359  		name = name[:nameSize]
   360  	}
   361  	name = strings.TrimRight(name, "/")
   362  
   363  	var f formatter
   364  	v7 := tw.blk.toV7()
   365  	v7.typeFlag()[0] = flag
   366  	f.formatString(v7.name(), name)
   367  	f.formatOctal(v7.mode(), 0)
   368  	f.formatOctal(v7.uid(), 0)
   369  	f.formatOctal(v7.gid(), 0)
   370  	f.formatOctal(v7.size(), int64(len(data))) // Must be < 8GiB
   371  	f.formatOctal(v7.modTime(), 0)
   372  	tw.blk.setFormat(format)
   373  	if f.err != nil {
   374  		return f.err // Only occurs if size condition is violated
   375  	}
   376  
   377  	// Write the header and data.
   378  	if err := tw.writeRawHeader(&tw.blk, int64(len(data)), flag); err != nil {
   379  		return err
   380  	}
   381  	_, err := io.WriteString(tw, data)
   382  	return err
   383  }
   384  
   385  // writeRawHeader writes the value of blk, regardless of its value.
   386  // It sets up the Writer such that it can accept a file of the given size.
   387  // If the flag is a special header-only flag, then the size is treated as zero.
   388  func (tw *Writer) writeRawHeader(blk *block, size int64, flag byte) error {
   389  	if err := tw.Flush(); err != nil {
   390  		return err
   391  	}
   392  	if _, err := tw.w.Write(blk[:]); err != nil {
   393  		return err
   394  	}
   395  	if isHeaderOnlyType(flag) {
   396  		size = 0
   397  	}
   398  	tw.curr = &regFileWriter{tw.w, size}
   399  	tw.pad = blockPadding(size)
   400  	return nil
   401  }
   402  
   403  // AddFS adds the files from fs.FS to the archive.
   404  // It walks the directory tree starting at the root of the filesystem
   405  // adding each file to the tar archive while maintaining the directory structure.
   406  func (tw *Writer) AddFS(fsys fs.FS) error {
   407  	return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
   408  		if err != nil {
   409  			return err
   410  		}
   411  		if name == "." {
   412  			return nil
   413  		}
   414  		info, err := d.Info()
   415  		if err != nil {
   416  			return err
   417  		}
   418  		linkTarget := ""
   419  		if typ := d.Type(); typ == fs.ModeSymlink {
   420  			var err error
   421  			linkTarget, err = fs.ReadLink(fsys, name)
   422  			if err != nil {
   423  				return err
   424  			}
   425  		} else if !typ.IsRegular() && typ != fs.ModeDir {
   426  			return errors.New("tar: cannot add non-regular file")
   427  		}
   428  		h, err := FileInfoHeader(info, linkTarget)
   429  		if err != nil {
   430  			return err
   431  		}
   432  		h.Name = name
   433  		if d.IsDir() {
   434  			h.Name += "/"
   435  		}
   436  		if err := tw.WriteHeader(h); err != nil {
   437  			return err
   438  		}
   439  		if !d.Type().IsRegular() {
   440  			return nil
   441  		}
   442  		f, err := fsys.Open(name)
   443  		if err != nil {
   444  			return err
   445  		}
   446  		defer f.Close()
   447  		_, err = io.Copy(tw, f)
   448  		return err
   449  	})
   450  }
   451  
   452  // splitUSTARPath splits a path according to USTAR prefix and suffix rules.
   453  // If the path is not splittable, then it will return ("", "", false).
   454  func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
   455  	length := len(name)
   456  	if length <= nameSize || !isASCII(name) {
   457  		return "", "", false
   458  	} else if length > prefixSize+1 {
   459  		length = prefixSize + 1
   460  	} else if name[length-1] == '/' {
   461  		length--
   462  	}
   463  
   464  	i := strings.LastIndex(name[:length], "/")
   465  	nlen := len(name) - i - 1 // nlen is length of suffix
   466  	plen := i                 // plen is length of prefix
   467  	if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize {
   468  		return "", "", false
   469  	}
   470  	return name[:i], name[i+1:], true
   471  }
   472  
   473  // Write writes to the current file in the tar archive.
   474  // Write returns the error [ErrWriteTooLong] if more than
   475  // Header.Size bytes are written after [Writer.WriteHeader].
   476  //
   477  // Calling Write on special types like [TypeLink], [TypeSymlink], [TypeChar],
   478  // [TypeBlock], [TypeDir], and [TypeFifo] returns (0, [ErrWriteTooLong]) regardless
   479  // of what the [Header.Size] claims.
   480  func (tw *Writer) Write(b []byte) (int, error) {
   481  	if tw.err != nil {
   482  		return 0, tw.err
   483  	}
   484  	n, err := tw.curr.Write(b)
   485  	if err != nil && err != ErrWriteTooLong {
   486  		tw.err = err
   487  	}
   488  	return n, err
   489  }
   490  
   491  // readFrom populates the content of the current file by reading from r.
   492  // The bytes read must match the number of remaining bytes in the current file.
   493  //
   494  // If the current file is sparse and r is an io.ReadSeeker,
   495  // then readFrom uses Seek to skip past holes defined in Header.SparseHoles,
   496  // assuming that skipped regions are all NULs.
   497  // This always reads the last byte to ensure r is the right size.
   498  //
   499  // TODO(dsnet): Re-export this when adding sparse file support.
   500  // See https://golang.org/issue/22735
   501  func (tw *Writer) readFrom(r io.Reader) (int64, error) {
   502  	if tw.err != nil {
   503  		return 0, tw.err
   504  	}
   505  	n, err := tw.curr.ReadFrom(r)
   506  	if err != nil && err != ErrWriteTooLong {
   507  		tw.err = err
   508  	}
   509  	return n, err
   510  }
   511  
   512  // Close closes the tar archive by flushing the padding, and writing the footer.
   513  // If the current file (from a prior call to [Writer.WriteHeader]) is not fully written,
   514  // then this returns an error.
   515  func (tw *Writer) Close() error {
   516  	if tw.err == ErrWriteAfterClose {
   517  		return nil
   518  	}
   519  	if tw.err != nil {
   520  		return tw.err
   521  	}
   522  
   523  	// Trailer: two zero blocks.
   524  	err := tw.Flush()
   525  	for i := 0; i < 2 && err == nil; i++ {
   526  		_, err = tw.w.Write(zeroBlock[:])
   527  	}
   528  
   529  	// Ensure all future actions are invalid.
   530  	tw.err = ErrWriteAfterClose
   531  	return err // Report IO errors
   532  }
   533  
   534  // regFileWriter is a fileWriter for writing data to a regular file entry.
   535  type regFileWriter struct {
   536  	w  io.Writer // Underlying Writer
   537  	nb int64     // Number of remaining bytes to write
   538  }
   539  
   540  func (fw *regFileWriter) Write(b []byte) (n int, err error) {
   541  	overwrite := int64(len(b)) > fw.nb
   542  	if overwrite {
   543  		b = b[:fw.nb]
   544  	}
   545  	if len(b) > 0 {
   546  		n, err = fw.w.Write(b)
   547  		fw.nb -= int64(n)
   548  	}
   549  	switch {
   550  	case err != nil:
   551  		return n, err
   552  	case overwrite:
   553  		return n, ErrWriteTooLong
   554  	default:
   555  		return n, nil
   556  	}
   557  }
   558  
   559  func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) {
   560  	return io.Copy(struct{ io.Writer }{fw}, r)
   561  }
   562  
   563  // logicalRemaining implements fileState.logicalRemaining.
   564  func (fw regFileWriter) logicalRemaining() int64 {
   565  	return fw.nb
   566  }
   567  
   568  // physicalRemaining implements fileState.physicalRemaining.
   569  func (fw regFileWriter) physicalRemaining() int64 {
   570  	return fw.nb
   571  }
   572  
   573  // sparseFileWriter is a fileWriter for writing data to a sparse file entry.
   574  type sparseFileWriter struct {
   575  	fw  fileWriter  // Underlying fileWriter
   576  	sp  sparseDatas // Normalized list of data fragments
   577  	pos int64       // Current position in sparse file
   578  }
   579  
   580  func (sw *sparseFileWriter) Write(b []byte) (n int, err error) {
   581  	overwrite := int64(len(b)) > sw.logicalRemaining()
   582  	if overwrite {
   583  		b = b[:sw.logicalRemaining()]
   584  	}
   585  
   586  	b0 := b
   587  	endPos := sw.pos + int64(len(b))
   588  	for endPos > sw.pos && err == nil {
   589  		var nf int // Bytes written in fragment
   590  		dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset()
   591  		if sw.pos < dataStart { // In a hole fragment
   592  			bf := b[:min(int64(len(b)), dataStart-sw.pos)]
   593  			nf, err = zeroWriter{}.Write(bf)
   594  		} else { // In a data fragment
   595  			bf := b[:min(int64(len(b)), dataEnd-sw.pos)]
   596  			nf, err = sw.fw.Write(bf)
   597  		}
   598  		b = b[nf:]
   599  		sw.pos += int64(nf)
   600  		if sw.pos >= dataEnd && len(sw.sp) > 1 {
   601  			sw.sp = sw.sp[1:] // Ensure last fragment always remains
   602  		}
   603  	}
   604  
   605  	n = len(b0) - len(b)
   606  	switch {
   607  	case err == ErrWriteTooLong:
   608  		return n, errMissData // Not possible; implies bug in validation logic
   609  	case err != nil:
   610  		return n, err
   611  	case sw.logicalRemaining() == 0 && sw.physicalRemaining() > 0:
   612  		return n, errUnrefData // Not possible; implies bug in validation logic
   613  	case overwrite:
   614  		return n, ErrWriteTooLong
   615  	default:
   616  		return n, nil
   617  	}
   618  }
   619  
   620  func (sw *sparseFileWriter) ReadFrom(r io.Reader) (n int64, err error) {
   621  	rs, ok := r.(io.ReadSeeker)
   622  	if ok {
   623  		if _, err := rs.Seek(0, io.SeekCurrent); err != nil {
   624  			ok = false // Not all io.Seeker can really seek
   625  		}
   626  	}
   627  	if !ok {
   628  		return io.Copy(struct{ io.Writer }{sw}, r)
   629  	}
   630  
   631  	var readLastByte bool
   632  	pos0 := sw.pos
   633  	for sw.logicalRemaining() > 0 && !readLastByte && err == nil {
   634  		var nf int64 // Size of fragment
   635  		dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset()
   636  		if sw.pos < dataStart { // In a hole fragment
   637  			nf = dataStart - sw.pos
   638  			if sw.physicalRemaining() == 0 {
   639  				readLastByte = true
   640  				nf--
   641  			}
   642  			_, err = rs.Seek(nf, io.SeekCurrent)
   643  		} else { // In a data fragment
   644  			nf = dataEnd - sw.pos
   645  			nf, err = io.CopyN(sw.fw, rs, nf)
   646  		}
   647  		sw.pos += nf
   648  		if sw.pos >= dataEnd && len(sw.sp) > 1 {
   649  			sw.sp = sw.sp[1:] // Ensure last fragment always remains
   650  		}
   651  	}
   652  
   653  	// If the last fragment is a hole, then seek to 1-byte before EOF, and
   654  	// read a single byte to ensure the file is the right size.
   655  	if readLastByte && err == nil {
   656  		_, err = mustReadFull(rs, []byte{0})
   657  		sw.pos++
   658  	}
   659  
   660  	n = sw.pos - pos0
   661  	switch {
   662  	case err == io.EOF:
   663  		return n, io.ErrUnexpectedEOF
   664  	case err == ErrWriteTooLong:
   665  		return n, errMissData // Not possible; implies bug in validation logic
   666  	case err != nil:
   667  		return n, err
   668  	case sw.logicalRemaining() == 0 && sw.physicalRemaining() > 0:
   669  		return n, errUnrefData // Not possible; implies bug in validation logic
   670  	default:
   671  		return n, ensureEOF(rs)
   672  	}
   673  }
   674  
   675  func (sw sparseFileWriter) logicalRemaining() int64 {
   676  	return sw.sp[len(sw.sp)-1].endOffset() - sw.pos
   677  }
   678  
   679  func (sw sparseFileWriter) physicalRemaining() int64 {
   680  	return sw.fw.physicalRemaining()
   681  }
   682  
   683  // zeroWriter may only be written with NULs, otherwise it returns errWriteHole.
   684  type zeroWriter struct{}
   685  
   686  func (zeroWriter) Write(b []byte) (int, error) {
   687  	for i, c := range b {
   688  		if c != 0 {
   689  			return i, errWriteHole
   690  		}
   691  	}
   692  	return len(b), nil
   693  }
   694  
   695  // ensureEOF checks whether r is at EOF, reporting ErrWriteTooLong if not so.
   696  func ensureEOF(r io.Reader) error {
   697  	n, err := tryReadFull(r, []byte{0})
   698  	switch {
   699  	case n > 0:
   700  		return ErrWriteTooLong
   701  	case err == io.EOF:
   702  		return nil
   703  	default:
   704  		return err
   705  	}
   706  }
   707  

View as plain text