github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 "errors" 9 "fmt" 10 "io" 11 "io/fs" 12 "path" 13 "sort" 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. 21 type 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. 35 func NewWriter(w io.Writer) *Writer { 36 return &Writer{w: w, curr: ®FileWriter{w, 0}} 37 } 38 39 type 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. 51 func (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. 69 func (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 112 func (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 130 func (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 sort.Strings(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 236 func (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 317 type ( 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. 328 func (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). 358 func (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. 393 func (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 = ®FileWriter{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. 411 func (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). 447 func 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. 473 func (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 494 func (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. 508 func (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. 528 type regFileWriter struct { 529 w io.Writer // Underlying Writer 530 nb int64 // Number of remaining bytes to write 531 } 532 533 func (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 552 func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) { 553 return io.Copy(struct{ io.Writer }{fw}, r) 554 } 555 556 // logicalRemaining implements fileState.logicalRemaining. 557 func (fw regFileWriter) logicalRemaining() int64 { 558 return fw.nb 559 } 560 561 // physicalRemaining implements fileState.physicalRemaining. 562 func (fw regFileWriter) physicalRemaining() int64 { 563 return fw.nb 564 } 565 566 // sparseFileWriter is a fileWriter for writing data to a sparse file entry. 567 type 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 573 func (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 613 func (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 668 func (sw sparseFileWriter) logicalRemaining() int64 { 669 return sw.sp[len(sw.sp)-1].endOffset() - sw.pos 670 } 671 func (sw sparseFileWriter) physicalRemaining() int64 { 672 return sw.fw.physicalRemaining() 673 } 674 675 // zeroWriter may only be written with NULs, otherwise it returns errWriteHole. 676 type zeroWriter struct{} 677 678 func (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. 688 func 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 }