github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/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 // Note: Some changes have been made to this file 6 // to better support Pachyderm. 7 8 package tar 9 10 import ( 11 "io" 12 "path" 13 "sort" 14 "strings" 15 "time" 16 17 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 18 ) 19 20 // Writer provides sequential writing of a tar archive. 21 // Write.WriteHeader begins a new file with the provided Header, 22 // and then Writer can be treated as an io.Writer to supply that file's data. 23 type Writer struct { 24 w io.Writer 25 pad int64 // Amount of padding to write after current file entry 26 curr fileWriter // Writer for current file entry 27 hdr Header // Shallow copy of Header that is safe for mutations 28 blk block // Buffer to use as temporary local storage 29 30 // err is a persistent error. 31 // It is only the responsibility of every exported method of Writer to 32 // ensure that this error is sticky. 33 err error 34 } 35 36 // NewWriter creates a new Writer writing to w. 37 func NewWriter(w io.Writer) *Writer { 38 return &Writer{w: w, curr: ®FileWriter{w, 0}} 39 } 40 41 type fileWriter interface { 42 io.Writer 43 fileState 44 45 Skip(int64) error 46 ReadFrom(io.Reader) (int64, error) 47 } 48 49 // Flush finishes writing the current file's block padding. 50 // The current file must be fully written before Flush can be called. 51 // 52 // This is unnecessary as the next call to WriteHeader or Close 53 // will implicitly flush out the file's padding. 54 func (tw *Writer) Flush() error { 55 if tw.err != nil { 56 return tw.err 57 } 58 if nb := tw.curr.LogicalRemaining(); nb > 0 { 59 return errors.Errorf("archive/tar: missed writing %d bytes", nb) 60 } 61 if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil { 62 return tw.err 63 } 64 tw.pad = 0 65 return nil 66 } 67 68 // WriteHeader writes hdr and prepares to accept the file's contents. 69 // The Header.Size determines how many bytes can be written for the next file. 70 // If the current file is not fully written, then this returns an error. 71 // This implicitly flushes any padding necessary before writing the header. 72 func (tw *Writer) WriteHeader(hdr *Header) error { 73 if err := tw.Flush(); err != nil { 74 return err 75 } 76 tw.hdr = *hdr // Shallow copy of Header 77 78 // Avoid usage of the legacy TypeRegA flag, and automatically promote 79 // it to use TypeReg or TypeDir. 80 if tw.hdr.Typeflag == TypeRegA { 81 if strings.HasSuffix(tw.hdr.Name, "/") { 82 tw.hdr.Typeflag = TypeDir 83 } else { 84 tw.hdr.Typeflag = TypeReg 85 } 86 } 87 88 // Round ModTime and ignore AccessTime and ChangeTime unless 89 // the format is explicitly chosen. 90 // This ensures nominal usage of WriteHeader (without specifying the format) 91 // does not always result in the PAX format being chosen, which 92 // causes a 1KiB increase to every header. 93 if tw.hdr.Format == FormatUnknown { 94 tw.hdr.ModTime = tw.hdr.ModTime.Round(time.Second) 95 tw.hdr.AccessTime = time.Time{} 96 tw.hdr.ChangeTime = time.Time{} 97 } 98 99 allowedFormats, paxHdrs, err := tw.hdr.allowedFormats() 100 switch { 101 case allowedFormats.has(FormatUSTAR): 102 tw.err = tw.writeUSTARHeader(&tw.hdr) 103 return tw.err 104 case allowedFormats.has(FormatPAX): 105 tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs) 106 return tw.err 107 case allowedFormats.has(FormatGNU): 108 tw.err = tw.writeGNUHeader(&tw.hdr) 109 return tw.err 110 default: 111 return err // Non-fatal error 112 } 113 } 114 115 func (tw *Writer) writeUSTARHeader(hdr *Header) error { 116 // Check if we can use USTAR prefix/suffix splitting. 117 var namePrefix string 118 if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok { 119 namePrefix, hdr.Name = prefix, suffix 120 } 121 122 // Pack the main header. 123 var f formatter 124 blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal) 125 f.formatString(blk.USTAR().Prefix(), namePrefix) 126 blk.SetFormat(FormatUSTAR) 127 if f.err != nil { 128 return f.err // Should never happen since header is validated 129 } 130 return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag) 131 } 132 133 func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error { 134 realName, realSize := hdr.Name, hdr.Size 135 136 // TODO(dsnet): Re-enable this when adding sparse support. 137 // See https://golang.org/issue/22735 138 /* 139 // Handle sparse files. 140 var spd sparseDatas 141 var spb []byte 142 if len(hdr.SparseHoles) > 0 { 143 sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map 144 sph = alignSparseEntries(sph, hdr.Size) 145 spd = invertSparseEntries(sph, hdr.Size) 146 147 // Format the sparse map. 148 hdr.Size = 0 // Replace with encoded size 149 spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n') 150 for _, s := range spd { 151 hdr.Size += s.Length 152 spb = append(strconv.AppendInt(spb, s.Offset, 10), '\n') 153 spb = append(strconv.AppendInt(spb, s.Length, 10), '\n') 154 } 155 pad := blockPadding(int64(len(spb))) 156 spb = append(spb, zeroBlock[:pad]...) 157 hdr.Size += int64(len(spb)) // Accounts for encoded sparse map 158 159 // Add and modify appropriate PAX records. 160 dir, file := path.Split(realName) 161 hdr.Name = path.Join(dir, "GNUSparseFile.0", file) 162 paxHdrs[paxGNUSparseMajor] = "1" 163 paxHdrs[paxGNUSparseMinor] = "0" 164 paxHdrs[paxGNUSparseName] = realName 165 paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10) 166 paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10) 167 delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName 168 } 169 */ 170 _ = realSize 171 172 // Write PAX records to the output. 173 isGlobal := hdr.Typeflag == TypeXGlobalHeader 174 if len(paxHdrs) > 0 || isGlobal { 175 // Sort keys for deterministic ordering. 176 var keys []string 177 for k := range paxHdrs { 178 keys = append(keys, k) 179 } 180 sort.Strings(keys) 181 182 // Write each record to a buffer. 183 var buf strings.Builder 184 for _, k := range keys { 185 rec, err := formatPAXRecord(k, paxHdrs[k]) 186 if err != nil { 187 return err 188 } 189 buf.WriteString(rec) 190 } 191 192 // Write the extended header file. 193 var name string 194 var flag byte 195 if isGlobal { 196 name = realName 197 if name == "" { 198 name = "GlobalHead.0.0" 199 } 200 flag = TypeXGlobalHeader 201 } else { 202 dir, file := path.Split(realName) 203 name = path.Join(dir, "PaxHeaders.0", file) 204 flag = TypeXHeader 205 } 206 data := buf.String() 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.GNU().AccessTime(), hdr.AccessTime.Unix()) 259 } 260 if !hdr.ChangeTime.IsZero() { 261 f.formatNumeric(blk.GNU().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.V7() 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.USTAR() 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.V7() 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 // splitUSTARPath splits a path according to USTAR prefix and suffix rules. 409 // If the path is not splittable, then it will return ("", "", false). 410 func splitUSTARPath(name string) (prefix, suffix string, ok bool) { 411 length := len(name) 412 if length <= nameSize || !isASCII(name) { 413 return "", "", false 414 } else if length > prefixSize+1 { 415 length = prefixSize + 1 416 } else if name[length-1] == '/' { 417 length-- 418 } 419 420 i := strings.LastIndex(name[:length], "/") 421 nlen := len(name) - i - 1 // nlen is length of suffix 422 plen := i // plen is length of prefix 423 if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize { 424 return "", "", false 425 } 426 return name[:i], name[i+1:], true 427 } 428 429 // Write writes to the current file in the tar archive. 430 // Write returns the error ErrWriteTooLong if more than 431 // Header.Size bytes are written after WriteHeader. 432 // 433 // Calling Write on special types like TypeLink, TypeSymlink, TypeChar, 434 // TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless 435 // of what the Header.Size claims. 436 func (tw *Writer) Write(b []byte) (int, error) { 437 if tw.err != nil { 438 return 0, tw.err 439 } 440 n, err := tw.curr.Write(b) 441 if err != nil && !errors.Is(err, ErrWriteTooLong) { 442 tw.err = err 443 } 444 return n, err 445 } 446 447 // Skip skips a certain number of bytes to write. 448 // This is necessary because tar expects the number of bytes written to equal 449 // the size field in the header that was written. 450 func (tw *Writer) Skip(n int64) error { 451 if tw.err != nil { 452 return tw.err 453 } 454 return tw.curr.Skip(n) 455 } 456 457 // readFrom populates the content of the current file by reading from r. 458 // The bytes read must match the number of remaining bytes in the current file. 459 // 460 // If the current file is sparse and r is an io.ReadSeeker, 461 // then readFrom uses Seek to skip past holes defined in Header.SparseHoles, 462 // assuming that skipped regions are all NULs. 463 // This always reads the last byte to ensure r is the right size. 464 // 465 // TODO(dsnet): Re-export this when adding sparse file support. 466 // See https://golang.org/issue/22735 467 func (tw *Writer) readFrom(r io.Reader) (int64, error) { 468 if tw.err != nil { 469 return 0, tw.err 470 } 471 n, err := tw.curr.ReadFrom(r) 472 if err != nil && !errors.Is(err, ErrWriteTooLong) { 473 tw.err = err 474 } 475 return n, err 476 } 477 478 // Close closes the tar archive by flushing the padding, and writing the footer. 479 // If the current file (from a prior call to WriteHeader) is not fully written, 480 // then this returns an error. 481 func (tw *Writer) Close() error { 482 if errors.Is(tw.err, ErrWriteAfterClose) { 483 return nil 484 } 485 if tw.err != nil { 486 return tw.err 487 } 488 489 // Trailer: two zero blocks. 490 err := tw.Flush() 491 for i := 0; i < 2 && err == nil; i++ { 492 _, err = tw.w.Write(zeroBlock[:]) 493 } 494 495 // Ensure all future actions are invalid. 496 tw.err = ErrWriteAfterClose 497 return err // Report IO errors 498 } 499 500 // regFileWriter is a fileWriter for writing data to a regular file entry. 501 type regFileWriter struct { 502 w io.Writer // Underlying Writer 503 nb int64 // Number of remaining bytes to write 504 } 505 506 func (fw *regFileWriter) Write(b []byte) (n int, err error) { 507 overwrite := int64(len(b)) > fw.nb 508 if overwrite { 509 b = b[:fw.nb] 510 } 511 if len(b) > 0 { 512 n, err = fw.w.Write(b) 513 fw.nb -= int64(n) 514 } 515 switch { 516 case err != nil: 517 return n, err 518 case overwrite: 519 return n, ErrWriteTooLong 520 default: 521 return n, nil 522 } 523 } 524 525 // Skip skips a certain number of bytes to write. 526 func (fw *regFileWriter) Skip(n int64) error { 527 if n > fw.nb { 528 return ErrWriteTooLong 529 } 530 fw.nb -= n 531 return nil 532 } 533 534 func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) { 535 return io.Copy(struct{ io.Writer }{fw}, r) 536 } 537 538 func (fw regFileWriter) LogicalRemaining() int64 { 539 return fw.nb 540 } 541 func (fw regFileWriter) PhysicalRemaining() int64 { 542 return fw.nb 543 } 544 545 // sparseFileWriter is a fileWriter for writing data to a sparse file entry. 546 type sparseFileWriter struct { 547 fw fileWriter // Underlying fileWriter 548 sp sparseDatas // Normalized list of data fragments 549 pos int64 // Current position in sparse file 550 } 551 552 func (sw *sparseFileWriter) Write(b []byte) (n int, err error) { 553 overwrite := int64(len(b)) > sw.LogicalRemaining() 554 if overwrite { 555 b = b[:sw.LogicalRemaining()] 556 } 557 558 b0 := b 559 endPos := sw.pos + int64(len(b)) 560 for endPos > sw.pos && err == nil { 561 var nf int // Bytes written in fragment 562 dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset() 563 if sw.pos < dataStart { // In a hole fragment 564 bf := b[:min(int64(len(b)), dataStart-sw.pos)] 565 nf, err = zeroWriter{}.Write(bf) 566 } else { // In a data fragment 567 bf := b[:min(int64(len(b)), dataEnd-sw.pos)] 568 nf, err = sw.fw.Write(bf) 569 } 570 b = b[nf:] 571 sw.pos += int64(nf) 572 if sw.pos >= dataEnd && len(sw.sp) > 1 { 573 sw.sp = sw.sp[1:] // Ensure last fragment always remains 574 } 575 } 576 577 n = len(b0) - len(b) 578 switch { 579 case errors.Is(err, ErrWriteTooLong): 580 return n, errMissData // Not possible; implies bug in validation logic 581 case err != nil: 582 return n, err 583 case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0: 584 return n, errUnrefData // Not possible; implies bug in validation logic 585 case overwrite: 586 return n, ErrWriteTooLong 587 default: 588 return n, nil 589 } 590 } 591 592 // Skip skips a certain number of bytes to write. 593 func (sw *sparseFileWriter) Skip(n int64) error { 594 // This code path has no use for Pachyderm. We 595 // should never be here. This is implemented 596 // just to satisfy the fileWriter interface. 597 return errors.Errorf("in Skip function of sparseFileWriter - this is probably a bug") 598 } 599 600 func (sw *sparseFileWriter) ReadFrom(r io.Reader) (n int64, err error) { 601 rs, ok := r.(io.ReadSeeker) 602 if ok { 603 if _, err := rs.Seek(0, io.SeekCurrent); err != nil { 604 ok = false // Not all io.Seeker can really seek 605 } 606 } 607 if !ok { 608 return io.Copy(struct{ io.Writer }{sw}, r) 609 } 610 611 var readLastByte bool 612 pos0 := sw.pos 613 for sw.LogicalRemaining() > 0 && !readLastByte && err == nil { 614 var nf int64 // Size of fragment 615 dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset() 616 if sw.pos < dataStart { // In a hole fragment 617 nf = dataStart - sw.pos 618 if sw.PhysicalRemaining() == 0 { 619 readLastByte = true 620 nf-- 621 } 622 _, err = rs.Seek(nf, io.SeekCurrent) 623 } else { // In a data fragment 624 nf = dataEnd - sw.pos 625 nf, err = io.CopyN(sw.fw, rs, nf) 626 } 627 sw.pos += nf 628 if sw.pos >= dataEnd && len(sw.sp) > 1 { 629 sw.sp = sw.sp[1:] // Ensure last fragment always remains 630 } 631 } 632 633 // If the last fragment is a hole, then seek to 1-byte before EOF, and 634 // read a single byte to ensure the file is the right size. 635 if readLastByte && err == nil { 636 _, err = mustReadFull(rs, []byte{0}) 637 sw.pos++ 638 } 639 640 n = sw.pos - pos0 641 switch { 642 case errors.Is(err, io.EOF): 643 return n, io.ErrUnexpectedEOF 644 case errors.Is(err, ErrWriteTooLong): 645 return n, errMissData // Not possible; implies bug in validation logic 646 case err != nil: 647 return n, err 648 case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0: 649 return n, errUnrefData // Not possible; implies bug in validation logic 650 default: 651 return n, ensureEOF(rs) 652 } 653 } 654 655 func (sw sparseFileWriter) LogicalRemaining() int64 { 656 return sw.sp[len(sw.sp)-1].endOffset() - sw.pos 657 } 658 func (sw sparseFileWriter) PhysicalRemaining() int64 { 659 return sw.fw.PhysicalRemaining() 660 } 661 662 // zeroWriter may only be written with NULs, otherwise it returns errWriteHole. 663 type zeroWriter struct{} 664 665 func (zeroWriter) Write(b []byte) (int, error) { 666 for i, c := range b { 667 if c != 0 { 668 return i, errWriteHole 669 } 670 } 671 return len(b), nil 672 } 673 674 // ensureEOF checks whether r is at EOF, reporting ErrWriteTooLong if not so. 675 func ensureEOF(r io.Reader) error { 676 n, err := tryReadFull(r, []byte{0}) 677 switch { 678 case n > 0: 679 return ErrWriteTooLong 680 case errors.Is(err, io.EOF): 681 return nil 682 default: 683 return err 684 } 685 }