Blank identifier discarding results; verify intentional ignoring of return values
_ = realSize
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package tar67import (8 "errors"9 "fmt"10 "io"11 "io/fs"12 "maps"13 "path"14 "slices"15 "strings"16 "time"17)1819// 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.22type Writer struct {23 w io.Writer24 pad int64 // Amount of padding to write after current file entry25 curr fileWriter // Writer for current file entry26 hdr Header // Shallow copy of Header that is safe for mutations27 blk block // Buffer to use as temporary local storage2829 // err is a persistent error.30 // It is only the responsibility of every exported method of Writer to31 // ensure that this error is sticky.32 err error33}3435// NewWriter creates a new Writer writing to w.36func NewWriter(w io.Writer) *Writer {37 return &Writer{w: w, curr: ®FileWriter{w, 0}}38}3940type fileWriter interface {41 io.Writer42 fileState4344 ReadFrom(io.Reader) (int64, error)45}4647// 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.52func (tw *Writer) Flush() error {53 if tw.err != nil {54 return tw.err55 }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.err61 }62 tw.pad = 063 return nil64}6566// 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.70func (tw *Writer) WriteHeader(hdr *Header) error {71 if err := tw.Flush(); err != nil {72 return err73 }74 tw.hdr = *hdr // Shallow copy of Header7576 // Avoid usage of the legacy TypeRegA flag, and automatically promote77 // it to use TypeReg or TypeDir.78 if tw.hdr.Typeflag == TypeRegA {79 if strings.HasSuffix(tw.hdr.Name, "/") {80 tw.hdr.Typeflag = TypeDir81 } else {82 tw.hdr.Typeflag = TypeReg83 }84 }8586 // Round ModTime and ignore AccessTime and ChangeTime unless87 // 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, which90 // 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 }9697 allowedFormats, paxHdrs, err := tw.hdr.allowedFormats()98 switch {99 case allowedFormats.has(FormatUSTAR):100 tw.err = tw.writeUSTARHeader(&tw.hdr)101 return tw.err102 case allowedFormats.has(FormatPAX):103 tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs)104 return tw.err105 case allowedFormats.has(FormatGNU):106 tw.err = tw.writeGNUHeader(&tw.hdr)107 return tw.err108 default:109 return err // Non-fatal error110 }111}112113func (tw *Writer) writeUSTARHeader(hdr *Header) error {114 // Check if we can use USTAR prefix/suffix splitting.115 var namePrefix string116 if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok {117 namePrefix, hdr.Name = prefix, suffix118 }119120 // Pack the main header.121 var f formatter122 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 validated127 }128 return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag)129}130131func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {132 realName, realSize := hdr.Name, hdr.Size133134 // TODO(dsnet): Re-enable this when adding sparse support.135 // See https://golang.org/issue/22735136 /*137 // Handle sparse files.138 var spd sparseDatas139 var spb []byte140 if len(hdr.SparseHoles) > 0 {141 sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map142 sph = alignSparseEntries(sph, hdr.Size)143 spd = invertSparseEntries(sph, hdr.Size)144145 // Format the sparse map.146 hdr.Size = 0 // Replace with encoded size147 spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n')148 for _, s := range spd {149 hdr.Size += s.Length150 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 map156157 // 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] = realName163 paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10)164 paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10)165 delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName166 }167 */168 _ = realSize169170 // Write PAX records to the output.171 isGlobal := hdr.Typeflag == TypeXGlobalHeader172 if len(paxHdrs) > 0 || isGlobal {173 // Write each record to a buffer.174 var buf strings.Builder175 // 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 err180 }181 buf.WriteString(rec)182 }183184 // Write the extended header file.185 var name string186 var flag byte187 if isGlobal {188 name = realName189 if name == "" {190 name = "GlobalHead.0.0"191 }192 flag = TypeXGlobalHeader193 } else {194 dir, file := path.Split(realName)195 name = path.Join(dir, "PaxHeaders.0", file)196 flag = TypeXHeader197 }198 data := buf.String()199 if len(data) > maxSpecialFileSize {200 return ErrFieldTooLong201 }202 if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal {203 return err // Global headers return here204 }205 }206207 // Pack the main header.208 var f formatter // Ignore errors since they are expected209 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 err214 }215216 // TODO(dsnet): Re-enable this when adding sparse support.217 // See https://golang.org/issue/22735218 /*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 err224 }225 tw.curr = &sparseFileWriter{tw.curr, spd, 0}226 }227 */228 return nil229}230231func (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 err238 }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 err244 }245 }246247 // Pack the main header.248 var f formatter // Ignore errors since they are expected249 var spd sparseDatas250 var spb []byte251 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/22735260 /*261 if hdr.Typeflag == TypeGNUSparse {262 sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map263 sph = alignSparseEntries(sph, hdr.Size)264 spd = invertSparseEntries(sph, hdr.Size)265266 // 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] = 1275 }276 return sp277 }278 sp2 := formatSPD(spd, blk.GNU().Sparse())279 for len(sp2) > 0 {280 var spHdr block281 sp2 = formatSPD(sp2, spHdr.Sparse())282 spb = append(spb, spHdr[:]...)283 }284285 // Update size fields in the header block.286 realSize := hdr.Size287 hdr.Size = 0 // Encoded size; does not account for encoded sparse map288 for _, s := range spd {289 hdr.Size += s.Length290 }291 copy(blk.V7().Size(), zeroBlock[:]) // Reset field292 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 err299 }300301 // 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 err306 }307 tw.curr = &sparseFileWriter{tw.curr, spd, 0}308 }309 return nil310}311312type (313 stringFormatter func([]byte, string)314 numberFormatter func([]byte, int64)315)316317// templateV7Plus fills out the V7 fields of a block using values from hdr.318// It also fills out fields (uname, gname, devmajor, devminor) that are319// shared in the USTAR, PAX, and GNU formats using the provided formatters.320//321// The block returned is only valid until the next call to322// templateV7Plus or writeRawFile.323func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block {324 tw.blk.reset()325326 modTime := hdr.ModTime327 if modTime.IsZero() {328 modTime = time.Unix(0, 0)329 }330331 v7 := tw.blk.toV7()332 v7.typeFlag()[0] = hdr.Typeflag333 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())340341 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)346347 return &tw.blk348}349350// 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).353func (tw *Writer) writeRawFile(name, data string, flag byte, format Format) error {354 tw.blk.reset()355356 // Best effort for the filename.357 name = toASCII(name)358 if len(name) > nameSize {359 name = name[:nameSize]360 }361 name = strings.TrimRight(name, "/")362363 var f formatter364 v7 := tw.blk.toV7()365 v7.typeFlag()[0] = flag366 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 < 8GiB371 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 violated375 }376377 // Write the header and data.378 if err := tw.writeRawHeader(&tw.blk, int64(len(data)), flag); err != nil {379 return err380 }381 _, err := io.WriteString(tw, data)382 return err383}384385// 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.388func (tw *Writer) writeRawHeader(blk *block, size int64, flag byte) error {389 if err := tw.Flush(); err != nil {390 return err391 }392 if _, err := tw.w.Write(blk[:]); err != nil {393 return err394 }395 if isHeaderOnlyType(flag) {396 size = 0397 }398 tw.curr = ®FileWriter{tw.w, size}399 tw.pad = blockPadding(size)400 return nil401}402403// AddFS adds the files from fs.FS to the archive.404// It walks the directory tree starting at the root of the filesystem405// adding each file to the tar archive while maintaining the directory structure.406func (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 err410 }411 if name == "." {412 return nil413 }414 info, err := d.Info()415 if err != nil {416 return err417 }418 linkTarget := ""419 if typ := d.Type(); typ == fs.ModeSymlink {420 var err error421 linkTarget, err = fs.ReadLink(fsys, name)422 if err != nil {423 return err424 }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 err431 }432 h.Name = name433 if d.IsDir() {434 h.Name += "/"435 }436 if err := tw.WriteHeader(h); err != nil {437 return err438 }439 if !d.Type().IsRegular() {440 return nil441 }442 f, err := fsys.Open(name)443 if err != nil {444 return err445 }446 defer f.Close()447 _, err = io.Copy(tw, f)448 return err449 })450}451452// splitUSTARPath splits a path according to USTAR prefix and suffix rules.453// If the path is not splittable, then it will return ("", "", false).454func splitUSTARPath(name string) (prefix, suffix string, ok bool) {455 length := len(name)456 if length <= nameSize || !isASCII(name) {457 return "", "", false458 } else if length > prefixSize+1 {459 length = prefixSize + 1460 } else if name[length-1] == '/' {461 length--462 }463464 i := strings.LastIndex(name[:length], "/")465 nlen := len(name) - i - 1 // nlen is length of suffix466 plen := i // plen is length of prefix467 if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize {468 return "", "", false469 }470 return name[:i], name[i+1:], true471}472473// Write writes to the current file in the tar archive.474// Write returns the error [ErrWriteTooLong] if more than475// 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]) regardless479// of what the [Header.Size] claims.480func (tw *Writer) Write(b []byte) (int, error) {481 if tw.err != nil {482 return 0, tw.err483 }484 n, err := tw.curr.Write(b)485 if err != nil && err != ErrWriteTooLong {486 tw.err = err487 }488 return n, err489}490491// 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/22735501func (tw *Writer) readFrom(r io.Reader) (int64, error) {502 if tw.err != nil {503 return 0, tw.err504 }505 n, err := tw.curr.ReadFrom(r)506 if err != nil && err != ErrWriteTooLong {507 tw.err = err508 }509 return n, err510}511512// 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.515func (tw *Writer) Close() error {516 if tw.err == ErrWriteAfterClose {517 return nil518 }519 if tw.err != nil {520 return tw.err521 }522523 // Trailer: two zero blocks.524 err := tw.Flush()525 for i := 0; i < 2 && err == nil; i++ {526 _, err = tw.w.Write(zeroBlock[:])527 }528529 // Ensure all future actions are invalid.530 tw.err = ErrWriteAfterClose531 return err // Report IO errors532}533534// regFileWriter is a fileWriter for writing data to a regular file entry.535type regFileWriter struct {536 w io.Writer // Underlying Writer537 nb int64 // Number of remaining bytes to write538}539540func (fw *regFileWriter) Write(b []byte) (n int, err error) {541 overwrite := int64(len(b)) > fw.nb542 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, err552 case overwrite:553 return n, ErrWriteTooLong554 default:555 return n, nil556 }557}558559func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) {560 return io.Copy(struct{ io.Writer }{fw}, r)561}562563// logicalRemaining implements fileState.logicalRemaining.564func (fw regFileWriter) logicalRemaining() int64 {565 return fw.nb566}567568// physicalRemaining implements fileState.physicalRemaining.569func (fw regFileWriter) physicalRemaining() int64 {570 return fw.nb571}572573// sparseFileWriter is a fileWriter for writing data to a sparse file entry.574type sparseFileWriter struct {575 fw fileWriter // Underlying fileWriter576 sp sparseDatas // Normalized list of data fragments577 pos int64 // Current position in sparse file578}579580func (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 }585586 b0 := b587 endPos := sw.pos + int64(len(b))588 for endPos > sw.pos && err == nil {589 var nf int // Bytes written in fragment590 dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset()591 if sw.pos < dataStart { // In a hole fragment592 bf := b[:min(int64(len(b)), dataStart-sw.pos)]593 nf, err = zeroWriter{}.Write(bf)594 } else { // In a data fragment595 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 remains602 }603 }604605 n = len(b0) - len(b)606 switch {607 case err == ErrWriteTooLong:608 return n, errMissData // Not possible; implies bug in validation logic609 case err != nil:610 return n, err611 case sw.logicalRemaining() == 0 && sw.physicalRemaining() > 0:612 return n, errUnrefData // Not possible; implies bug in validation logic613 case overwrite:614 return n, ErrWriteTooLong615 default:616 return n, nil617 }618}619620func (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 seek625 }626 }627 if !ok {628 return io.Copy(struct{ io.Writer }{sw}, r)629 }630631 var readLastByte bool632 pos0 := sw.pos633 for sw.logicalRemaining() > 0 && !readLastByte && err == nil {634 var nf int64 // Size of fragment635 dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset()636 if sw.pos < dataStart { // In a hole fragment637 nf = dataStart - sw.pos638 if sw.physicalRemaining() == 0 {639 readLastByte = true640 nf--641 }642 _, err = rs.Seek(nf, io.SeekCurrent)643 } else { // In a data fragment644 nf = dataEnd - sw.pos645 nf, err = io.CopyN(sw.fw, rs, nf)646 }647 sw.pos += nf648 if sw.pos >= dataEnd && len(sw.sp) > 1 {649 sw.sp = sw.sp[1:] // Ensure last fragment always remains650 }651 }652653 // If the last fragment is a hole, then seek to 1-byte before EOF, and654 // 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 }659660 n = sw.pos - pos0661 switch {662 case err == io.EOF:663 return n, io.ErrUnexpectedEOF664 case err == ErrWriteTooLong:665 return n, errMissData // Not possible; implies bug in validation logic666 case err != nil:667 return n, err668 case sw.logicalRemaining() == 0 && sw.physicalRemaining() > 0:669 return n, errUnrefData // Not possible; implies bug in validation logic670 default:671 return n, ensureEOF(rs)672 }673}674675func (sw sparseFileWriter) logicalRemaining() int64 {676 return sw.sp[len(sw.sp)-1].endOffset() - sw.pos677}678679func (sw sparseFileWriter) physicalRemaining() int64 {680 return sw.fw.physicalRemaining()681}682683// zeroWriter may only be written with NULs, otherwise it returns errWriteHole.684type zeroWriter struct{}685686func (zeroWriter) Write(b []byte) (int, error) {687 for i, c := range b {688 if c != 0 {689 return i, errWriteHole690 }691 }692 return len(b), nil693}694695// ensureEOF checks whether r is at EOF, reporting ErrWriteTooLong if not so.696func ensureEOF(r io.Reader) error {697 n, err := tryReadFull(r, []byte{0})698 switch {699 case n > 0:700 return ErrWriteTooLong701 case err == io.EOF:702 return nil703 default:704 return err705 }706}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.