aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/archive/tar/writer.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/archive/tar/writer.go')
-rw-r--r--libgo/go/archive/tar/writer.go118
1 files changed, 102 insertions, 16 deletions
diff --git a/libgo/go/archive/tar/writer.go b/libgo/go/archive/tar/writer.go
index 5af504b..d92dd06 100644
--- a/libgo/go/archive/tar/writer.go
+++ b/libgo/go/archive/tar/writer.go
@@ -8,10 +8,14 @@ package tar
// - catch more errors (no first header, etc.)
import (
+ "bytes"
"errors"
"fmt"
"io"
+ "os"
+ "path"
"strconv"
+ "strings"
"time"
)
@@ -19,23 +23,13 @@ var (
ErrWriteTooLong = errors.New("archive/tar: write too long")
ErrFieldTooLong = errors.New("archive/tar: header field too long")
ErrWriteAfterClose = errors.New("archive/tar: write after close")
+ errNameTooLong = errors.New("archive/tar: name too long")
)
// A Writer provides sequential writing of a tar archive in POSIX.1 format.
// A tar archive consists of a sequence of files.
// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
// writing at most hdr.Size bytes in total.
-//
-// Example:
-// tw := tar.NewWriter(w)
-// hdr := new(tar.Header)
-// hdr.Size = length of data in bytes
-// // populate other hdr fields as desired
-// if err := tw.WriteHeader(hdr); err != nil {
-// // handle error
-// }
-// io.Copy(tw, data)
-// tw.Close()
type Writer struct {
w io.Writer
err error
@@ -130,15 +124,31 @@ func (tw *Writer) WriteHeader(hdr *Header) error {
if tw.err != nil {
return tw.err
}
-
+ // Decide whether or not to use PAX extensions
+ // TODO(shanemhansen): we might want to use PAX headers for
+ // subsecond time resolution, but for now let's just capture
+ // the long name/long symlink use case.
+ suffix := hdr.Name
+ prefix := ""
+ if len(hdr.Name) > fileNameSize || len(hdr.Linkname) > fileNameSize {
+ var err error
+ prefix, suffix, err = tw.splitUSTARLongName(hdr.Name)
+ // Either we were unable to pack the long name into ustar format
+ // or the link name is too long; use PAX headers.
+ if err == errNameTooLong || len(hdr.Linkname) > fileNameSize {
+ if err := tw.writePAXHeader(hdr); err != nil {
+ return err
+ }
+ } else if err != nil {
+ return err
+ }
+ }
tw.nb = int64(hdr.Size)
tw.pad = -tw.nb & (blockSize - 1) // blockSize is a power of two
header := make([]byte, blockSize)
s := slicer(header)
-
- // TODO(dsymonds): handle names longer than 100 chars
- copy(s.next(100), []byte(hdr.Name))
+ tw.cString(s.next(fileNameSize), suffix)
// Handle out of range ModTime carefully.
var modTime int64
@@ -159,11 +169,15 @@ func (tw *Writer) WriteHeader(hdr *Header) error {
tw.cString(s.next(32), hdr.Gname) // 297:329
tw.numeric(s.next(8), hdr.Devmajor) // 329:337
tw.numeric(s.next(8), hdr.Devminor) // 337:345
-
+ tw.cString(s.next(155), prefix) // 345:500
// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
if tw.usedBinary {
copy(header[257:265], []byte("ustar \x00"))
}
+ // Use the ustar magic if we used ustar long names.
+ if len(prefix) > 0 {
+ copy(header[257:265], []byte("ustar\000"))
+ }
// The chksum field is terminated by a NUL and a space.
// This is different from the other octal fields.
@@ -181,6 +195,78 @@ func (tw *Writer) WriteHeader(hdr *Header) error {
return tw.err
}
+// writeUSTARLongName splits a USTAR long name hdr.Name.
+// name must be < 256 characters. errNameTooLong is returned
+// if hdr.Name can't be split. The splitting heuristic
+// is compatible with gnu tar.
+func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err error) {
+ length := len(name)
+ if length > fileNamePrefixSize+1 {
+ length = fileNamePrefixSize + 1
+ } else if name[length-1] == '/' {
+ length--
+ }
+ i := strings.LastIndex(name[:length], "/")
+ nlen := length - i - 1
+ if i <= 0 || nlen > fileNameSize || nlen == 0 {
+ err = errNameTooLong
+ return
+ }
+ prefix, suffix = name[:i], name[i+1:]
+ return
+}
+
+// writePaxHeader writes an extended pax header to the
+// archive.
+func (tw *Writer) writePAXHeader(hdr *Header) error {
+ // Prepare extended header
+ ext := new(Header)
+ ext.Typeflag = TypeXHeader
+ // Setting ModTime is required for reader parsing to
+ // succeed, and seems harmless enough.
+ ext.ModTime = hdr.ModTime
+ // The spec asks that we namespace our pseudo files
+ // with the current pid.
+ pid := os.Getpid()
+ dir, file := path.Split(hdr.Name)
+ ext.Name = path.Join(dir,
+ fmt.Sprintf("PaxHeaders.%d", pid), file)[0:100]
+ // Construct the body
+ var buf bytes.Buffer
+ if len(hdr.Name) > fileNameSize {
+ fmt.Fprint(&buf, paxHeader("path="+hdr.Name))
+ }
+ if len(hdr.Linkname) > fileNameSize {
+ fmt.Fprint(&buf, paxHeader("linkpath="+hdr.Linkname))
+ }
+ ext.Size = int64(len(buf.Bytes()))
+ if err := tw.WriteHeader(ext); err != nil {
+ return err
+ }
+ if _, err := tw.Write(buf.Bytes()); err != nil {
+ return err
+ }
+ if err := tw.Flush(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// paxHeader formats a single pax record, prefixing it with the appropriate length
+func paxHeader(msg string) string {
+ const padding = 2 // Extra padding for space and newline
+ size := len(msg) + padding
+ size += len(strconv.Itoa(size))
+ record := fmt.Sprintf("%d %s\n", size, msg)
+ if len(record) != size {
+ // Final adjustment if adding size increased
+ // the number of digits in size
+ size = len(record)
+ record = fmt.Sprintf("%d %s\n", size, msg)
+ }
+ return record
+}
+
// Write writes to the current entry in the tar archive.
// Write returns the error ErrWriteTooLong if more than
// hdr.Size bytes are written after WriteHeader.