writer.mx raw
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 "bytes"
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: ®FileWriter{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 bytes.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 []byte
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][]byte) 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 bytes.Buffer
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 []byte
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 []byte) { 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, []byte)
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 []byte, 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 = bytes.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 = ®FileWriter{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 []byte, 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 []byte) (prefix, suffix []byte, 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 := bytes.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