diff options
Diffstat (limited to 'libgo/go/archive/tar/writer.go')
-rw-r--r-- | libgo/go/archive/tar/writer.go | 118 |
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. |