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