| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package tar |
| |
| import ( |
| "fmt" |
| "io" |
| "path" |
| "sort" |
| "strings" |
| "time" |
| ) |
| |
| // Writer provides sequential writing of a tar archive. |
| // Write.WriteHeader begins a new file with the provided Header, |
| // and then Writer can be treated as an io.Writer to supply that file's data. |
| type Writer struct { |
| w io.Writer |
| pad int64 // Amount of padding to write after current file entry |
| curr fileWriter // Writer for current file entry |
| hdr Header // Shallow copy of Header that is safe for mutations |
| blk block // Buffer to use as temporary local storage |
| |
| // err is a persistent error. |
| // It is only the responsibility of every exported method of Writer to |
| // ensure that this error is sticky. |
| err error |
| } |
| |
| // NewWriter creates a new Writer writing to w. |
| func NewWriter(w io.Writer) *Writer { |
| return &Writer{w: w, curr: ®FileWriter{w, 0}} |
| } |
| |
| type fileWriter interface { |
| io.Writer |
| fileState |
| |
| ReadFrom(io.Reader) (int64, error) |
| } |
| |
| // Flush finishes writing the current file's block padding. |
| // The current file must be fully written before Flush can be called. |
| // |
| // This is unnecessary as the next call to WriteHeader or Close |
| // will implicitly flush out the file's padding. |
| func (tw *Writer) Flush() error { |
| if tw.err != nil { |
| return tw.err |
| } |
| if nb := tw.curr.LogicalRemaining(); nb > 0 { |
| return fmt.Errorf("archive/tar: missed writing %d bytes", nb) |
| } |
| if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil { |
| return tw.err |
| } |
| tw.pad = 0 |
| return nil |
| } |
| |
| // WriteHeader writes hdr and prepares to accept the file's contents. |
| // The Header.Size determines how many bytes can be written for the next file. |
| // If the current file is not fully written, then this returns an error. |
| // This implicitly flushes any padding necessary before writing the header. |
| func (tw *Writer) WriteHeader(hdr *Header) error { |
| if err := tw.Flush(); err != nil { |
| return err |
| } |
| tw.hdr = *hdr // Shallow copy of Header |
| |
| // Avoid usage of the legacy TypeRegA flag, and automatically promote |
| // it to use TypeReg or TypeDir. |
| if tw.hdr.Typeflag == TypeRegA { |
| if strings.HasSuffix(tw.hdr.Name, "/") { |
| tw.hdr.Typeflag = TypeDir |
| } else { |
| tw.hdr.Typeflag = TypeReg |
| } |
| } |
| |
| // Round ModTime and ignore AccessTime and ChangeTime unless |
| // the format is explicitly chosen. |
| // This ensures nominal usage of WriteHeader (without specifying the format) |
| // does not always result in the PAX format being chosen, which |
| // causes a 1KiB increase to every header. |
| if tw.hdr.Format == FormatUnknown { |
| tw.hdr.ModTime = tw.hdr.ModTime.Round(time.Second) |
| tw.hdr.AccessTime = time.Time{} |
| tw.hdr.ChangeTime = time.Time{} |
| } |
| |
| allowedFormats, paxHdrs, err := tw.hdr.allowedFormats() |
| switch { |
| case allowedFormats.has(FormatUSTAR): |
| tw.err = tw.writeUSTARHeader(&tw.hdr) |
| return tw.err |
| case allowedFormats.has(FormatPAX): |
| tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs) |
| return tw.err |
| case allowedFormats.has(FormatGNU): |
| tw.err = tw.writeGNUHeader(&tw.hdr) |
| return tw.err |
| default: |
| return err // Non-fatal error |
| } |
| } |
| |
| func (tw *Writer) writeUSTARHeader(hdr *Header) error { |
| // Check if we can use USTAR prefix/suffix splitting. |
| var namePrefix string |
| if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok { |
| namePrefix, hdr.Name = prefix, suffix |
| } |
| |
| // Pack the main header. |
| var f formatter |
| blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal) |
| f.formatString(blk.USTAR().Prefix(), namePrefix) |
| blk.SetFormat(FormatUSTAR) |
| if f.err != nil { |
| return f.err // Should never happen since header is validated |
| } |
| return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag) |
| } |
| |
| func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error { |
| realName, realSize := hdr.Name, hdr.Size |
| |
| // TODO(dsnet): Re-enable this when adding sparse support. |
| // See https://golang.org/issue/22735 |
| /* |
| // Handle sparse files. |
| var spd sparseDatas |
| var spb []byte |
| if len(hdr.SparseHoles) > 0 { |
| sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map |
| sph = alignSparseEntries(sph, hdr.Size) |
| spd = invertSparseEntries(sph, hdr.Size) |
| |
| // Format the sparse map. |
| hdr.Size = 0 // Replace with encoded size |
| spb = append(strconv.AppendInt(spb, int64(len(spd)), 10), '\n') |
| for _, s := range spd { |
| hdr.Size += s.Length |
| spb = append(strconv.AppendInt(spb, s.Offset, 10), '\n') |
| spb = append(strconv.AppendInt(spb, s.Length, 10), '\n') |
| } |
| pad := blockPadding(int64(len(spb))) |
| spb = append(spb, zeroBlock[:pad]...) |
| hdr.Size += int64(len(spb)) // Accounts for encoded sparse map |
| |
| // Add and modify appropriate PAX records. |
| dir, file := path.Split(realName) |
| hdr.Name = path.Join(dir, "GNUSparseFile.0", file) |
| paxHdrs[paxGNUSparseMajor] = "1" |
| paxHdrs[paxGNUSparseMinor] = "0" |
| paxHdrs[paxGNUSparseName] = realName |
| paxHdrs[paxGNUSparseRealSize] = strconv.FormatInt(realSize, 10) |
| paxHdrs[paxSize] = strconv.FormatInt(hdr.Size, 10) |
| delete(paxHdrs, paxPath) // Recorded by paxGNUSparseName |
| } |
| */ |
| _ = realSize |
| |
| // Write PAX records to the output. |
| isGlobal := hdr.Typeflag == TypeXGlobalHeader |
| if len(paxHdrs) > 0 || isGlobal { |
| // Sort keys for deterministic ordering. |
| var keys []string |
| for k := range paxHdrs { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| |
| // Write each record to a buffer. |
| var buf strings.Builder |
| for _, k := range keys { |
| rec, err := formatPAXRecord(k, paxHdrs[k]) |
| if err != nil { |
| return err |
| } |
| buf.WriteString(rec) |
| } |
| |
| // Write the extended header file. |
| var name string |
| var flag byte |
| if isGlobal { |
| name = realName |
| if name == "" { |
| name = "GlobalHead.0.0" |
| } |
| flag = TypeXGlobalHeader |
| } else { |
| dir, file := path.Split(realName) |
| name = path.Join(dir, "PaxHeaders.0", file) |
| flag = TypeXHeader |
| } |
| data := buf.String() |
| if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal { |
| return err // Global headers return here |
| } |
| } |
| |
| // Pack the main header. |
| var f formatter // Ignore errors since they are expected |
| fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) } |
| blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal) |
| blk.SetFormat(FormatPAX) |
| if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil { |
| return err |
| } |
| |
| // TODO(dsnet): Re-enable this when adding sparse support. |
| // See https://golang.org/issue/22735 |
| /* |
| // Write the sparse map and setup the sparse writer if necessary. |
| if len(spd) > 0 { |
| // Use tw.curr since the sparse map is accounted for in hdr.Size. |
| if _, err := tw.curr.Write(spb); err != nil { |
| return err |
| } |
| tw.curr = &sparseFileWriter{tw.curr, spd, 0} |
| } |
| */ |
| return nil |
| } |
| |
| func (tw *Writer) writeGNUHeader(hdr *Header) error { |
| // Use long-link files if Name or Linkname exceeds the field size. |
| const longName = "././@LongLink" |
| if len(hdr.Name) > nameSize { |
| data := hdr.Name + "\x00" |
| if err := tw.writeRawFile(longName, data, TypeGNULongName, FormatGNU); err != nil { |
| return err |
| } |
| } |
| if len(hdr.Linkname) > nameSize { |
| data := hdr.Linkname + "\x00" |
| if err := tw.writeRawFile(longName, data, TypeGNULongLink, FormatGNU); err != nil { |
| return err |
| } |
| } |
| |
| // Pack the main header. |
| var f formatter // Ignore errors since they are expected |
| var spd sparseDatas |
| var spb []byte |
| blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric) |
| if !hdr.AccessTime.IsZero() { |
| f.formatNumeric(blk.GNU().AccessTime(), hdr.AccessTime.Unix()) |
| } |
| if !hdr.ChangeTime.IsZero() { |
| f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix()) |
| } |
| // TODO(dsnet): Re-enable this when adding sparse support. |
| // See https://golang.org/issue/22735 |
| /* |
| if hdr.Typeflag == TypeGNUSparse { |
| sph := append([]sparseEntry{}, hdr.SparseHoles...) // Copy sparse map |
| sph = alignSparseEntries(sph, hdr.Size) |
| spd = invertSparseEntries(sph, hdr.Size) |
| |
| // Format the sparse map. |
| formatSPD := func(sp sparseDatas, sa sparseArray) sparseDatas { |
| for i := 0; len(sp) > 0 && i < sa.MaxEntries(); i++ { |
| f.formatNumeric(sa.Entry(i).Offset(), sp[0].Offset) |
| f.formatNumeric(sa.Entry(i).Length(), sp[0].Length) |
| sp = sp[1:] |
| } |
| if len(sp) > 0 { |
| sa.IsExtended()[0] = 1 |
| } |
| return sp |
| } |
| sp2 := formatSPD(spd, blk.GNU().Sparse()) |
| for len(sp2) > 0 { |
| var spHdr block |
| sp2 = formatSPD(sp2, spHdr.Sparse()) |
| spb = append(spb, spHdr[:]...) |
| } |
| |
| // Update size fields in the header block. |
| realSize := hdr.Size |
| hdr.Size = 0 // Encoded size; does not account for encoded sparse map |
| for _, s := range spd { |
| hdr.Size += s.Length |
| } |
| copy(blk.V7().Size(), zeroBlock[:]) // Reset field |
| f.formatNumeric(blk.V7().Size(), hdr.Size) |
| f.formatNumeric(blk.GNU().RealSize(), realSize) |
| } |
| */ |
| blk.SetFormat(FormatGNU) |
| if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil { |
| return err |
| } |
| |
| // Write the extended sparse map and setup the sparse writer if necessary. |
| if len(spd) > 0 { |
| // Use tw.w since the sparse map is not accounted for in hdr.Size. |
| if _, err := tw.w.Write(spb); err != nil { |
| return err |
| } |
| tw.curr = &sparseFileWriter{tw.curr, spd, 0} |
| } |
| return nil |
| } |
| |
| type ( |
| stringFormatter func([]byte, string) |
| numberFormatter func([]byte, int64) |
| ) |
| |
| // templateV7Plus fills out the V7 fields of a block using values from hdr. |
| // It also fills out fields (uname, gname, devmajor, devminor) that are |
| // shared in the USTAR, PAX, and GNU formats using the provided formatters. |
| // |
| // The block returned is only valid until the next call to |
| // templateV7Plus or writeRawFile. |
| func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block { |
| tw.blk.Reset() |
| |
| modTime := hdr.ModTime |
| if modTime.IsZero() { |
| modTime = time.Unix(0, 0) |
| } |
| |
| v7 := tw.blk.V7() |
| v7.TypeFlag()[0] = hdr.Typeflag |
| fmtStr(v7.Name(), hdr.Name) |
| fmtStr(v7.LinkName(), hdr.Linkname) |
| fmtNum(v7.Mode(), hdr.Mode) |
| fmtNum(v7.UID(), int64(hdr.Uid)) |
| fmtNum(v7.GID(), int64(hdr.Gid)) |
| fmtNum(v7.Size(), hdr.Size) |
| fmtNum(v7.ModTime(), modTime.Unix()) |
| |
| ustar := tw.blk.USTAR() |
| fmtStr(ustar.UserName(), hdr.Uname) |
| fmtStr(ustar.GroupName(), hdr.Gname) |
| fmtNum(ustar.DevMajor(), hdr.Devmajor) |
| fmtNum(ustar.DevMinor(), hdr.Devminor) |
| |
| return &tw.blk |
| } |
| |
| // writeRawFile writes a minimal file with the given name and flag type. |
| // It uses format to encode the header format and will write data as the body. |
| // It uses default values for all of the other fields (as BSD and GNU tar does). |
| func (tw *Writer) writeRawFile(name, data string, flag byte, format Format) error { |
| tw.blk.Reset() |
| |
| // Best effort for the filename. |
| name = toASCII(name) |
| if len(name) > nameSize { |
| name = name[:nameSize] |
| } |
| name = strings.TrimRight(name, "/") |
| |
| var f formatter |
| v7 := tw.blk.V7() |
| v7.TypeFlag()[0] = flag |
| f.formatString(v7.Name(), name) |
| f.formatOctal(v7.Mode(), 0) |
| f.formatOctal(v7.UID(), 0) |
| f.formatOctal(v7.GID(), 0) |
| f.formatOctal(v7.Size(), int64(len(data))) // Must be < 8GiB |
| f.formatOctal(v7.ModTime(), 0) |
| tw.blk.SetFormat(format) |
| if f.err != nil { |
| return f.err // Only occurs if size condition is violated |
| } |
| |
| // Write the header and data. |
| if err := tw.writeRawHeader(&tw.blk, int64(len(data)), flag); err != nil { |
| return err |
| } |
| _, err := io.WriteString(tw, data) |
| return err |
| } |
| |
| // writeRawHeader writes the value of blk, regardless of its value. |
| // It sets up the Writer such that it can accept a file of the given size. |
| // If the flag is a special header-only flag, then the size is treated as zero. |
| func (tw *Writer) writeRawHeader(blk *block, size int64, flag byte) error { |
| if err := tw.Flush(); err != nil { |
| return err |
| } |
| if _, err := tw.w.Write(blk[:]); err != nil { |
| return err |
| } |
| if isHeaderOnlyType(flag) { |
| size = 0 |
| } |
| tw.curr = ®FileWriter{tw.w, size} |
| tw.pad = blockPadding(size) |
| return nil |
| } |
| |
| // splitUSTARPath splits a path according to USTAR prefix and suffix rules. |
| // If the path is not splittable, then it will return ("", "", false). |
| func splitUSTARPath(name string) (prefix, suffix string, ok bool) { |
| length := len(name) |
| if length <= nameSize || !isASCII(name) { |
| return "", "", false |
| } else if length > prefixSize+1 { |
| length = prefixSize + 1 |
| } else if name[length-1] == '/' { |
| length-- |
| } |
| |
| i := strings.LastIndex(name[:length], "/") |
| nlen := len(name) - i - 1 // nlen is length of suffix |
| plen := i // plen is length of prefix |
| if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize { |
| return "", "", false |
| } |
| return name[:i], name[i+1:], true |
| } |
| |
| // Write writes to the current file in the tar archive. |
| // Write returns the error ErrWriteTooLong if more than |
| // Header.Size bytes are written after WriteHeader. |
| // |
| // Calling Write on special types like TypeLink, TypeSymlink, TypeChar, |
| // TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless |
| // of what the Header.Size claims. |
| func (tw *Writer) Write(b []byte) (int, error) { |
| if tw.err != nil { |
| return 0, tw.err |
| } |
| n, err := tw.curr.Write(b) |
| if err != nil && err != ErrWriteTooLong { |
| tw.err = err |
| } |
| return n, err |
| } |
| |
| // readFrom populates the content of the current file by reading from r. |
| // The bytes read must match the number of remaining bytes in the current file. |
| // |
| // If the current file is sparse and r is an io.ReadSeeker, |
| // then readFrom uses Seek to skip past holes defined in Header.SparseHoles, |
| // assuming that skipped regions are all NULs. |
| // This always reads the last byte to ensure r is the right size. |
| // |
| // TODO(dsnet): Re-export this when adding sparse file support. |
| // See https://golang.org/issue/22735 |
| func (tw *Writer) readFrom(r io.Reader) (int64, error) { |
| if tw.err != nil { |
| return 0, tw.err |
| } |
| n, err := tw.curr.ReadFrom(r) |
| if err != nil && err != ErrWriteTooLong { |
| tw.err = err |
| } |
| return n, err |
| } |
| |
| // Close closes the tar archive by flushing the padding, and writing the footer. |
| // If the current file (from a prior call to WriteHeader) is not fully written, |
| // then this returns an error. |
| func (tw *Writer) Close() error { |
| if tw.err == ErrWriteAfterClose { |
| return nil |
| } |
| if tw.err != nil { |
| return tw.err |
| } |
| |
| // Trailer: two zero blocks. |
| err := tw.Flush() |
| for i := 0; i < 2 && err == nil; i++ { |
| _, err = tw.w.Write(zeroBlock[:]) |
| } |
| |
| // Ensure all future actions are invalid. |
| tw.err = ErrWriteAfterClose |
| return err // Report IO errors |
| } |
| |
| // regFileWriter is a fileWriter for writing data to a regular file entry. |
| type regFileWriter struct { |
| w io.Writer // Underlying Writer |
| nb int64 // Number of remaining bytes to write |
| } |
| |
| func (fw *regFileWriter) Write(b []byte) (n int, err error) { |
| overwrite := int64(len(b)) > fw.nb |
| if overwrite { |
| b = b[:fw.nb] |
| } |
| if len(b) > 0 { |
| n, err = fw.w.Write(b) |
| fw.nb -= int64(n) |
| } |
| switch { |
| case err != nil: |
| return n, err |
| case overwrite: |
| return n, ErrWriteTooLong |
| default: |
| return n, nil |
| } |
| } |
| |
| func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) { |
| return io.Copy(struct{ io.Writer }{fw}, r) |
| } |
| |
| func (fw regFileWriter) LogicalRemaining() int64 { |
| return fw.nb |
| } |
| func (fw regFileWriter) PhysicalRemaining() int64 { |
| return fw.nb |
| } |
| |
| // sparseFileWriter is a fileWriter for writing data to a sparse file entry. |
| type sparseFileWriter struct { |
| fw fileWriter // Underlying fileWriter |
| sp sparseDatas // Normalized list of data fragments |
| pos int64 // Current position in sparse file |
| } |
| |
| func (sw *sparseFileWriter) Write(b []byte) (n int, err error) { |
| overwrite := int64(len(b)) > sw.LogicalRemaining() |
| if overwrite { |
| b = b[:sw.LogicalRemaining()] |
| } |
| |
| b0 := b |
| endPos := sw.pos + int64(len(b)) |
| for endPos > sw.pos && err == nil { |
| var nf int // Bytes written in fragment |
| dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset() |
| if sw.pos < dataStart { // In a hole fragment |
| bf := b[:min(int64(len(b)), dataStart-sw.pos)] |
| nf, err = zeroWriter{}.Write(bf) |
| } else { // In a data fragment |
| bf := b[:min(int64(len(b)), dataEnd-sw.pos)] |
| nf, err = sw.fw.Write(bf) |
| } |
| b = b[nf:] |
| sw.pos += int64(nf) |
| if sw.pos >= dataEnd && len(sw.sp) > 1 { |
| sw.sp = sw.sp[1:] // Ensure last fragment always remains |
| } |
| } |
| |
| n = len(b0) - len(b) |
| switch { |
| case err == ErrWriteTooLong: |
| return n, errMissData // Not possible; implies bug in validation logic |
| case err != nil: |
| return n, err |
| case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0: |
| return n, errUnrefData // Not possible; implies bug in validation logic |
| case overwrite: |
| return n, ErrWriteTooLong |
| default: |
| return n, nil |
| } |
| } |
| |
| func (sw *sparseFileWriter) ReadFrom(r io.Reader) (n int64, err error) { |
| rs, ok := r.(io.ReadSeeker) |
| if ok { |
| if _, err := rs.Seek(0, io.SeekCurrent); err != nil { |
| ok = false // Not all io.Seeker can really seek |
| } |
| } |
| if !ok { |
| return io.Copy(struct{ io.Writer }{sw}, r) |
| } |
| |
| var readLastByte bool |
| pos0 := sw.pos |
| for sw.LogicalRemaining() > 0 && !readLastByte && err == nil { |
| var nf int64 // Size of fragment |
| dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset() |
| if sw.pos < dataStart { // In a hole fragment |
| nf = dataStart - sw.pos |
| if sw.PhysicalRemaining() == 0 { |
| readLastByte = true |
| nf-- |
| } |
| _, err = rs.Seek(nf, io.SeekCurrent) |
| } else { // In a data fragment |
| nf = dataEnd - sw.pos |
| nf, err = io.CopyN(sw.fw, rs, nf) |
| } |
| sw.pos += nf |
| if sw.pos >= dataEnd && len(sw.sp) > 1 { |
| sw.sp = sw.sp[1:] // Ensure last fragment always remains |
| } |
| } |
| |
| // If the last fragment is a hole, then seek to 1-byte before EOF, and |
| // read a single byte to ensure the file is the right size. |
| if readLastByte && err == nil { |
| _, err = mustReadFull(rs, []byte{0}) |
| sw.pos++ |
| } |
| |
| n = sw.pos - pos0 |
| switch { |
| case err == io.EOF: |
| return n, io.ErrUnexpectedEOF |
| case err == ErrWriteTooLong: |
| return n, errMissData // Not possible; implies bug in validation logic |
| case err != nil: |
| return n, err |
| case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0: |
| return n, errUnrefData // Not possible; implies bug in validation logic |
| default: |
| return n, ensureEOF(rs) |
| } |
| } |
| |
| func (sw sparseFileWriter) LogicalRemaining() int64 { |
| return sw.sp[len(sw.sp)-1].endOffset() - sw.pos |
| } |
| func (sw sparseFileWriter) PhysicalRemaining() int64 { |
| return sw.fw.PhysicalRemaining() |
| } |
| |
| // zeroWriter may only be written with NULs, otherwise it returns errWriteHole. |
| type zeroWriter struct{} |
| |
| func (zeroWriter) Write(b []byte) (int, error) { |
| for i, c := range b { |
| if c != 0 { |
| return i, errWriteHole |
| } |
| } |
| return len(b), nil |
| } |
| |
| // ensureEOF checks whether r is at EOF, reporting ErrWriteTooLong if not so. |
| func ensureEOF(r io.Reader) error { |
| n, err := tryReadFull(r, []byte{0}) |
| switch { |
| case n > 0: |
| return ErrWriteTooLong |
| case err == io.EOF: |
| return nil |
| default: |
| return err |
| } |
| } |