mirror of
https://github.com/vbatts/tar-split.git
synced 2025-07-06 00:08:30 +00:00
Update archive/tar with go1.19
This commit is contained in:
parent
80a436fd61
commit
d2a420ca6b
13 changed files with 420 additions and 328 deletions
|
@ -13,8 +13,8 @@ package tar
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -221,9 +221,11 @@ func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }
|
||||||
// that the file has no data in it, which is rather odd.
|
// that the file has no data in it, which is rather odd.
|
||||||
//
|
//
|
||||||
// As an example, if the underlying raw file contains the 10-byte data:
|
// As an example, if the underlying raw file contains the 10-byte data:
|
||||||
|
//
|
||||||
// var compactFile = "abcdefgh"
|
// var compactFile = "abcdefgh"
|
||||||
//
|
//
|
||||||
// And the sparse map has the following entries:
|
// And the sparse map has the following entries:
|
||||||
|
//
|
||||||
// var spd sparseDatas = []sparseEntry{
|
// var spd sparseDatas = []sparseEntry{
|
||||||
// {Offset: 2, Length: 5}, // Data fragment for 2..6
|
// {Offset: 2, Length: 5}, // Data fragment for 2..6
|
||||||
// {Offset: 18, Length: 3}, // Data fragment for 18..20
|
// {Offset: 18, Length: 3}, // Data fragment for 18..20
|
||||||
|
@ -235,6 +237,7 @@ func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Then the content of the resulting sparse file with a Header.Size of 25 is:
|
// Then the content of the resulting sparse file with a Header.Size of 25 is:
|
||||||
|
//
|
||||||
// var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4
|
// var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4
|
||||||
type (
|
type (
|
||||||
sparseDatas []sparseEntry
|
sparseDatas []sparseEntry
|
||||||
|
@ -293,9 +296,9 @@ func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry {
|
||||||
// The input must have been already validated.
|
// The input must have been already validated.
|
||||||
//
|
//
|
||||||
// This function mutates src and returns a normalized map where:
|
// This function mutates src and returns a normalized map where:
|
||||||
// * adjacent fragments are coalesced together
|
// - adjacent fragments are coalesced together
|
||||||
// * only the last fragment may be empty
|
// - only the last fragment may be empty
|
||||||
// * the endOffset of the last fragment is the total size
|
// - the endOffset of the last fragment is the total size
|
||||||
func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
|
func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
|
||||||
dst := src[:0]
|
dst := src[:0]
|
||||||
var pre sparseEntry
|
var pre sparseEntry
|
||||||
|
@ -316,10 +319,10 @@ func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry {
|
||||||
// fileState tracks the number of logical (includes sparse holes) and physical
|
// fileState tracks the number of logical (includes sparse holes) and physical
|
||||||
// (actual in tar archive) bytes remaining for the current file.
|
// (actual in tar archive) bytes remaining for the current file.
|
||||||
//
|
//
|
||||||
// Invariant: LogicalRemaining >= PhysicalRemaining
|
// Invariant: logicalRemaining >= physicalRemaining
|
||||||
type fileState interface {
|
type fileState interface {
|
||||||
LogicalRemaining() int64
|
logicalRemaining() int64
|
||||||
PhysicalRemaining() int64
|
physicalRemaining() int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// allowedFormats determines which formats can be used.
|
// allowedFormats determines which formats can be used.
|
||||||
|
@ -413,22 +416,22 @@ func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err
|
||||||
|
|
||||||
// Check basic fields.
|
// Check basic fields.
|
||||||
var blk block
|
var blk block
|
||||||
v7 := blk.V7()
|
v7 := blk.toV7()
|
||||||
ustar := blk.USTAR()
|
ustar := blk.toUSTAR()
|
||||||
gnu := blk.GNU()
|
gnu := blk.toGNU()
|
||||||
verifyString(h.Name, len(v7.Name()), "Name", paxPath)
|
verifyString(h.Name, len(v7.name()), "Name", paxPath)
|
||||||
verifyString(h.Linkname, len(v7.LinkName()), "Linkname", paxLinkpath)
|
verifyString(h.Linkname, len(v7.linkName()), "Linkname", paxLinkpath)
|
||||||
verifyString(h.Uname, len(ustar.UserName()), "Uname", paxUname)
|
verifyString(h.Uname, len(ustar.userName()), "Uname", paxUname)
|
||||||
verifyString(h.Gname, len(ustar.GroupName()), "Gname", paxGname)
|
verifyString(h.Gname, len(ustar.groupName()), "Gname", paxGname)
|
||||||
verifyNumeric(h.Mode, len(v7.Mode()), "Mode", paxNone)
|
verifyNumeric(h.Mode, len(v7.mode()), "Mode", paxNone)
|
||||||
verifyNumeric(int64(h.Uid), len(v7.UID()), "Uid", paxUid)
|
verifyNumeric(int64(h.Uid), len(v7.uid()), "Uid", paxUid)
|
||||||
verifyNumeric(int64(h.Gid), len(v7.GID()), "Gid", paxGid)
|
verifyNumeric(int64(h.Gid), len(v7.gid()), "Gid", paxGid)
|
||||||
verifyNumeric(h.Size, len(v7.Size()), "Size", paxSize)
|
verifyNumeric(h.Size, len(v7.size()), "Size", paxSize)
|
||||||
verifyNumeric(h.Devmajor, len(ustar.DevMajor()), "Devmajor", paxNone)
|
verifyNumeric(h.Devmajor, len(ustar.devMajor()), "Devmajor", paxNone)
|
||||||
verifyNumeric(h.Devminor, len(ustar.DevMinor()), "Devminor", paxNone)
|
verifyNumeric(h.Devminor, len(ustar.devMinor()), "Devminor", paxNone)
|
||||||
verifyTime(h.ModTime, len(v7.ModTime()), "ModTime", paxMtime)
|
verifyTime(h.ModTime, len(v7.modTime()), "ModTime", paxMtime)
|
||||||
verifyTime(h.AccessTime, len(gnu.AccessTime()), "AccessTime", paxAtime)
|
verifyTime(h.AccessTime, len(gnu.accessTime()), "AccessTime", paxAtime)
|
||||||
verifyTime(h.ChangeTime, len(gnu.ChangeTime()), "ChangeTime", paxCtime)
|
verifyTime(h.ChangeTime, len(gnu.changeTime()), "ChangeTime", paxCtime)
|
||||||
|
|
||||||
// Check for header-only types.
|
// Check for header-only types.
|
||||||
var whyOnlyPAX, whyOnlyGNU string
|
var whyOnlyPAX, whyOnlyGNU string
|
||||||
|
@ -525,12 +528,12 @@ func (h Header) allowedFormats() (format Format, paxHdrs map[string]string, err
|
||||||
return format, paxHdrs, err
|
return format, paxHdrs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInfo returns an os.FileInfo for the Header.
|
// FileInfo returns an fs.FileInfo for the Header.
|
||||||
func (h *Header) FileInfo() os.FileInfo {
|
func (h *Header) FileInfo() fs.FileInfo {
|
||||||
return headerFileInfo{h}
|
return headerFileInfo{h}
|
||||||
}
|
}
|
||||||
|
|
||||||
// headerFileInfo implements os.FileInfo.
|
// headerFileInfo implements fs.FileInfo.
|
||||||
type headerFileInfo struct {
|
type headerFileInfo struct {
|
||||||
h *Header
|
h *Header
|
||||||
}
|
}
|
||||||
|
@ -538,7 +541,7 @@ type headerFileInfo struct {
|
||||||
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
||||||
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||||
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
||||||
func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
func (fi headerFileInfo) Sys() any { return fi.h }
|
||||||
|
|
||||||
// Name returns the base name of the file.
|
// Name returns the base name of the file.
|
||||||
func (fi headerFileInfo) Name() string {
|
func (fi headerFileInfo) Name() string {
|
||||||
|
@ -549,57 +552,57 @@ func (fi headerFileInfo) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode returns the permission and mode bits for the headerFileInfo.
|
// Mode returns the permission and mode bits for the headerFileInfo.
|
||||||
func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
func (fi headerFileInfo) Mode() (mode fs.FileMode) {
|
||||||
// Set file permission bits.
|
// Set file permission bits.
|
||||||
mode = os.FileMode(fi.h.Mode).Perm()
|
mode = fs.FileMode(fi.h.Mode).Perm()
|
||||||
|
|
||||||
// Set setuid, setgid and sticky bits.
|
// Set setuid, setgid and sticky bits.
|
||||||
if fi.h.Mode&c_ISUID != 0 {
|
if fi.h.Mode&c_ISUID != 0 {
|
||||||
mode |= os.ModeSetuid
|
mode |= fs.ModeSetuid
|
||||||
}
|
}
|
||||||
if fi.h.Mode&c_ISGID != 0 {
|
if fi.h.Mode&c_ISGID != 0 {
|
||||||
mode |= os.ModeSetgid
|
mode |= fs.ModeSetgid
|
||||||
}
|
}
|
||||||
if fi.h.Mode&c_ISVTX != 0 {
|
if fi.h.Mode&c_ISVTX != 0 {
|
||||||
mode |= os.ModeSticky
|
mode |= fs.ModeSticky
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set file mode bits; clear perm, setuid, setgid, and sticky bits.
|
// Set file mode bits; clear perm, setuid, setgid, and sticky bits.
|
||||||
switch m := os.FileMode(fi.h.Mode) &^ 07777; m {
|
switch m := fs.FileMode(fi.h.Mode) &^ 07777; m {
|
||||||
case c_ISDIR:
|
case c_ISDIR:
|
||||||
mode |= os.ModeDir
|
mode |= fs.ModeDir
|
||||||
case c_ISFIFO:
|
case c_ISFIFO:
|
||||||
mode |= os.ModeNamedPipe
|
mode |= fs.ModeNamedPipe
|
||||||
case c_ISLNK:
|
case c_ISLNK:
|
||||||
mode |= os.ModeSymlink
|
mode |= fs.ModeSymlink
|
||||||
case c_ISBLK:
|
case c_ISBLK:
|
||||||
mode |= os.ModeDevice
|
mode |= fs.ModeDevice
|
||||||
case c_ISCHR:
|
case c_ISCHR:
|
||||||
mode |= os.ModeDevice
|
mode |= fs.ModeDevice
|
||||||
mode |= os.ModeCharDevice
|
mode |= fs.ModeCharDevice
|
||||||
case c_ISSOCK:
|
case c_ISSOCK:
|
||||||
mode |= os.ModeSocket
|
mode |= fs.ModeSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
switch fi.h.Typeflag {
|
switch fi.h.Typeflag {
|
||||||
case TypeSymlink:
|
case TypeSymlink:
|
||||||
mode |= os.ModeSymlink
|
mode |= fs.ModeSymlink
|
||||||
case TypeChar:
|
case TypeChar:
|
||||||
mode |= os.ModeDevice
|
mode |= fs.ModeDevice
|
||||||
mode |= os.ModeCharDevice
|
mode |= fs.ModeCharDevice
|
||||||
case TypeBlock:
|
case TypeBlock:
|
||||||
mode |= os.ModeDevice
|
mode |= fs.ModeDevice
|
||||||
case TypeDir:
|
case TypeDir:
|
||||||
mode |= os.ModeDir
|
mode |= fs.ModeDir
|
||||||
case TypeFifo:
|
case TypeFifo:
|
||||||
mode |= os.ModeNamedPipe
|
mode |= fs.ModeNamedPipe
|
||||||
}
|
}
|
||||||
|
|
||||||
return mode
|
return mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
||||||
var sysStat func(fi os.FileInfo, h *Header) error
|
var sysStat func(fi fs.FileInfo, h *Header) error
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Mode constants from the USTAR spec:
|
// Mode constants from the USTAR spec:
|
||||||
|
@ -623,10 +626,10 @@ const (
|
||||||
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
||||||
// If fi describes a directory, a slash is appended to the name.
|
// If fi describes a directory, a slash is appended to the name.
|
||||||
//
|
//
|
||||||
// Since os.FileInfo's Name method only returns the base name of
|
// Since fs.FileInfo's Name method only returns the base name of
|
||||||
// the file it describes, it may be necessary to modify Header.Name
|
// the file it describes, it may be necessary to modify Header.Name
|
||||||
// to provide the full path name of the file.
|
// to provide the full path name of the file.
|
||||||
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
func FileInfoHeader(fi fs.FileInfo, link string) (*Header, error) {
|
||||||
if fi == nil {
|
if fi == nil {
|
||||||
return nil, errors.New("archive/tar: FileInfo is nil")
|
return nil, errors.New("archive/tar: FileInfo is nil")
|
||||||
}
|
}
|
||||||
|
@ -643,29 +646,29 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
||||||
case fi.IsDir():
|
case fi.IsDir():
|
||||||
h.Typeflag = TypeDir
|
h.Typeflag = TypeDir
|
||||||
h.Name += "/"
|
h.Name += "/"
|
||||||
case fm&os.ModeSymlink != 0:
|
case fm&fs.ModeSymlink != 0:
|
||||||
h.Typeflag = TypeSymlink
|
h.Typeflag = TypeSymlink
|
||||||
h.Linkname = link
|
h.Linkname = link
|
||||||
case fm&os.ModeDevice != 0:
|
case fm&fs.ModeDevice != 0:
|
||||||
if fm&os.ModeCharDevice != 0 {
|
if fm&fs.ModeCharDevice != 0 {
|
||||||
h.Typeflag = TypeChar
|
h.Typeflag = TypeChar
|
||||||
} else {
|
} else {
|
||||||
h.Typeflag = TypeBlock
|
h.Typeflag = TypeBlock
|
||||||
}
|
}
|
||||||
case fm&os.ModeNamedPipe != 0:
|
case fm&fs.ModeNamedPipe != 0:
|
||||||
h.Typeflag = TypeFifo
|
h.Typeflag = TypeFifo
|
||||||
case fm&os.ModeSocket != 0:
|
case fm&fs.ModeSocket != 0:
|
||||||
return nil, fmt.Errorf("archive/tar: sockets not supported")
|
return nil, fmt.Errorf("archive/tar: sockets not supported")
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
||||||
}
|
}
|
||||||
if fm&os.ModeSetuid != 0 {
|
if fm&fs.ModeSetuid != 0 {
|
||||||
h.Mode |= c_ISUID
|
h.Mode |= c_ISUID
|
||||||
}
|
}
|
||||||
if fm&os.ModeSetgid != 0 {
|
if fm&fs.ModeSetgid != 0 {
|
||||||
h.Mode |= c_ISGID
|
h.Mode |= c_ISGID
|
||||||
}
|
}
|
||||||
if fm&os.ModeSticky != 0 {
|
if fm&fs.ModeSticky != 0 {
|
||||||
h.Mode |= c_ISVTX
|
h.Mode |= c_ISVTX
|
||||||
}
|
}
|
||||||
// If possible, populate additional fields from OS-specific
|
// If possible, populate additional fields from OS-specific
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
package tar
|
package tar
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Format represents the tar archive format.
|
// Format represents the tar archive format.
|
||||||
//
|
//
|
||||||
|
@ -156,28 +158,28 @@ var zeroBlock block
|
||||||
type block [blockSize]byte
|
type block [blockSize]byte
|
||||||
|
|
||||||
// Convert block to any number of formats.
|
// Convert block to any number of formats.
|
||||||
func (b *block) V7() *headerV7 { return (*headerV7)(b) }
|
func (b *block) toV7() *headerV7 { return (*headerV7)(b) }
|
||||||
func (b *block) GNU() *headerGNU { return (*headerGNU)(b) }
|
func (b *block) toGNU() *headerGNU { return (*headerGNU)(b) }
|
||||||
func (b *block) STAR() *headerSTAR { return (*headerSTAR)(b) }
|
func (b *block) toSTAR() *headerSTAR { return (*headerSTAR)(b) }
|
||||||
func (b *block) USTAR() *headerUSTAR { return (*headerUSTAR)(b) }
|
func (b *block) toUSTAR() *headerUSTAR { return (*headerUSTAR)(b) }
|
||||||
func (b *block) Sparse() sparseArray { return (sparseArray)(b[:]) }
|
func (b *block) toSparse() sparseArray { return sparseArray(b[:]) }
|
||||||
|
|
||||||
// GetFormat checks that the block is a valid tar header based on the checksum.
|
// GetFormat checks that the block is a valid tar header based on the checksum.
|
||||||
// It then attempts to guess the specific format based on magic values.
|
// It then attempts to guess the specific format based on magic values.
|
||||||
// If the checksum fails, then FormatUnknown is returned.
|
// If the checksum fails, then FormatUnknown is returned.
|
||||||
func (b *block) GetFormat() Format {
|
func (b *block) getFormat() Format {
|
||||||
// Verify checksum.
|
// Verify checksum.
|
||||||
var p parser
|
var p parser
|
||||||
value := p.parseOctal(b.V7().Chksum())
|
value := p.parseOctal(b.toV7().chksum())
|
||||||
chksum1, chksum2 := b.ComputeChecksum()
|
chksum1, chksum2 := b.computeChecksum()
|
||||||
if p.err != nil || (value != chksum1 && value != chksum2) {
|
if p.err != nil || (value != chksum1 && value != chksum2) {
|
||||||
return FormatUnknown
|
return FormatUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guess the magic values.
|
// Guess the magic values.
|
||||||
magic := string(b.USTAR().Magic())
|
magic := string(b.toUSTAR().magic())
|
||||||
version := string(b.USTAR().Version())
|
version := string(b.toUSTAR().version())
|
||||||
trailer := string(b.STAR().Trailer())
|
trailer := string(b.toSTAR().trailer())
|
||||||
switch {
|
switch {
|
||||||
case magic == magicUSTAR && trailer == trailerSTAR:
|
case magic == magicUSTAR && trailer == trailerSTAR:
|
||||||
return formatSTAR
|
return formatSTAR
|
||||||
|
@ -190,23 +192,23 @@ func (b *block) GetFormat() Format {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFormat writes the magic values necessary for specified format
|
// setFormat writes the magic values necessary for specified format
|
||||||
// and then updates the checksum accordingly.
|
// and then updates the checksum accordingly.
|
||||||
func (b *block) SetFormat(format Format) {
|
func (b *block) setFormat(format Format) {
|
||||||
// Set the magic values.
|
// Set the magic values.
|
||||||
switch {
|
switch {
|
||||||
case format.has(formatV7):
|
case format.has(formatV7):
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
case format.has(FormatGNU):
|
case format.has(FormatGNU):
|
||||||
copy(b.GNU().Magic(), magicGNU)
|
copy(b.toGNU().magic(), magicGNU)
|
||||||
copy(b.GNU().Version(), versionGNU)
|
copy(b.toGNU().version(), versionGNU)
|
||||||
case format.has(formatSTAR):
|
case format.has(formatSTAR):
|
||||||
copy(b.STAR().Magic(), magicUSTAR)
|
copy(b.toSTAR().magic(), magicUSTAR)
|
||||||
copy(b.STAR().Version(), versionUSTAR)
|
copy(b.toSTAR().version(), versionUSTAR)
|
||||||
copy(b.STAR().Trailer(), trailerSTAR)
|
copy(b.toSTAR().trailer(), trailerSTAR)
|
||||||
case format.has(FormatUSTAR | FormatPAX):
|
case format.has(FormatUSTAR | FormatPAX):
|
||||||
copy(b.USTAR().Magic(), magicUSTAR)
|
copy(b.toUSTAR().magic(), magicUSTAR)
|
||||||
copy(b.USTAR().Version(), versionUSTAR)
|
copy(b.toUSTAR().version(), versionUSTAR)
|
||||||
default:
|
default:
|
||||||
panic("invalid format")
|
panic("invalid format")
|
||||||
}
|
}
|
||||||
|
@ -214,17 +216,17 @@ func (b *block) SetFormat(format Format) {
|
||||||
// Update checksum.
|
// Update checksum.
|
||||||
// This field is special in that it is terminated by a NULL then space.
|
// This field is special in that it is terminated by a NULL then space.
|
||||||
var f formatter
|
var f formatter
|
||||||
field := b.V7().Chksum()
|
field := b.toV7().chksum()
|
||||||
chksum, _ := b.ComputeChecksum() // Possible values are 256..128776
|
chksum, _ := b.computeChecksum() // Possible values are 256..128776
|
||||||
f.formatOctal(field[:7], chksum) // Never fails since 128776 < 262143
|
f.formatOctal(field[:7], chksum) // Never fails since 128776 < 262143
|
||||||
field[7] = ' '
|
field[7] = ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComputeChecksum computes the checksum for the header block.
|
// computeChecksum computes the checksum for the header block.
|
||||||
// POSIX specifies a sum of the unsigned byte values, but the Sun tar used
|
// POSIX specifies a sum of the unsigned byte values, but the Sun tar used
|
||||||
// signed byte values.
|
// signed byte values.
|
||||||
// We compute and return both.
|
// We compute and return both.
|
||||||
func (b *block) ComputeChecksum() (unsigned, signed int64) {
|
func (b *block) computeChecksum() (unsigned, signed int64) {
|
||||||
for i, c := range b {
|
for i, c := range b {
|
||||||
if 148 <= i && i < 156 {
|
if 148 <= i && i < 156 {
|
||||||
c = ' ' // Treat the checksum field itself as all spaces.
|
c = ' ' // Treat the checksum field itself as all spaces.
|
||||||
|
@ -236,68 +238,68 @@ func (b *block) ComputeChecksum() (unsigned, signed int64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset clears the block with all zeros.
|
// Reset clears the block with all zeros.
|
||||||
func (b *block) Reset() {
|
func (b *block) reset() {
|
||||||
*b = block{}
|
*b = block{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type headerV7 [blockSize]byte
|
type headerV7 [blockSize]byte
|
||||||
|
|
||||||
func (h *headerV7) Name() []byte { return h[000:][:100] }
|
func (h *headerV7) name() []byte { return h[000:][:100] }
|
||||||
func (h *headerV7) Mode() []byte { return h[100:][:8] }
|
func (h *headerV7) mode() []byte { return h[100:][:8] }
|
||||||
func (h *headerV7) UID() []byte { return h[108:][:8] }
|
func (h *headerV7) uid() []byte { return h[108:][:8] }
|
||||||
func (h *headerV7) GID() []byte { return h[116:][:8] }
|
func (h *headerV7) gid() []byte { return h[116:][:8] }
|
||||||
func (h *headerV7) Size() []byte { return h[124:][:12] }
|
func (h *headerV7) size() []byte { return h[124:][:12] }
|
||||||
func (h *headerV7) ModTime() []byte { return h[136:][:12] }
|
func (h *headerV7) modTime() []byte { return h[136:][:12] }
|
||||||
func (h *headerV7) Chksum() []byte { return h[148:][:8] }
|
func (h *headerV7) chksum() []byte { return h[148:][:8] }
|
||||||
func (h *headerV7) TypeFlag() []byte { return h[156:][:1] }
|
func (h *headerV7) typeFlag() []byte { return h[156:][:1] }
|
||||||
func (h *headerV7) LinkName() []byte { return h[157:][:100] }
|
func (h *headerV7) linkName() []byte { return h[157:][:100] }
|
||||||
|
|
||||||
type headerGNU [blockSize]byte
|
type headerGNU [blockSize]byte
|
||||||
|
|
||||||
func (h *headerGNU) V7() *headerV7 { return (*headerV7)(h) }
|
func (h *headerGNU) v7() *headerV7 { return (*headerV7)(h) }
|
||||||
func (h *headerGNU) Magic() []byte { return h[257:][:6] }
|
func (h *headerGNU) magic() []byte { return h[257:][:6] }
|
||||||
func (h *headerGNU) Version() []byte { return h[263:][:2] }
|
func (h *headerGNU) version() []byte { return h[263:][:2] }
|
||||||
func (h *headerGNU) UserName() []byte { return h[265:][:32] }
|
func (h *headerGNU) userName() []byte { return h[265:][:32] }
|
||||||
func (h *headerGNU) GroupName() []byte { return h[297:][:32] }
|
func (h *headerGNU) groupName() []byte { return h[297:][:32] }
|
||||||
func (h *headerGNU) DevMajor() []byte { return h[329:][:8] }
|
func (h *headerGNU) devMajor() []byte { return h[329:][:8] }
|
||||||
func (h *headerGNU) DevMinor() []byte { return h[337:][:8] }
|
func (h *headerGNU) devMinor() []byte { return h[337:][:8] }
|
||||||
func (h *headerGNU) AccessTime() []byte { return h[345:][:12] }
|
func (h *headerGNU) accessTime() []byte { return h[345:][:12] }
|
||||||
func (h *headerGNU) ChangeTime() []byte { return h[357:][:12] }
|
func (h *headerGNU) changeTime() []byte { return h[357:][:12] }
|
||||||
func (h *headerGNU) Sparse() sparseArray { return (sparseArray)(h[386:][:24*4+1]) }
|
func (h *headerGNU) sparse() sparseArray { return sparseArray(h[386:][:24*4+1]) }
|
||||||
func (h *headerGNU) RealSize() []byte { return h[483:][:12] }
|
func (h *headerGNU) realSize() []byte { return h[483:][:12] }
|
||||||
|
|
||||||
type headerSTAR [blockSize]byte
|
type headerSTAR [blockSize]byte
|
||||||
|
|
||||||
func (h *headerSTAR) V7() *headerV7 { return (*headerV7)(h) }
|
func (h *headerSTAR) v7() *headerV7 { return (*headerV7)(h) }
|
||||||
func (h *headerSTAR) Magic() []byte { return h[257:][:6] }
|
func (h *headerSTAR) magic() []byte { return h[257:][:6] }
|
||||||
func (h *headerSTAR) Version() []byte { return h[263:][:2] }
|
func (h *headerSTAR) version() []byte { return h[263:][:2] }
|
||||||
func (h *headerSTAR) UserName() []byte { return h[265:][:32] }
|
func (h *headerSTAR) userName() []byte { return h[265:][:32] }
|
||||||
func (h *headerSTAR) GroupName() []byte { return h[297:][:32] }
|
func (h *headerSTAR) groupName() []byte { return h[297:][:32] }
|
||||||
func (h *headerSTAR) DevMajor() []byte { return h[329:][:8] }
|
func (h *headerSTAR) devMajor() []byte { return h[329:][:8] }
|
||||||
func (h *headerSTAR) DevMinor() []byte { return h[337:][:8] }
|
func (h *headerSTAR) devMinor() []byte { return h[337:][:8] }
|
||||||
func (h *headerSTAR) Prefix() []byte { return h[345:][:131] }
|
func (h *headerSTAR) prefix() []byte { return h[345:][:131] }
|
||||||
func (h *headerSTAR) AccessTime() []byte { return h[476:][:12] }
|
func (h *headerSTAR) accessTime() []byte { return h[476:][:12] }
|
||||||
func (h *headerSTAR) ChangeTime() []byte { return h[488:][:12] }
|
func (h *headerSTAR) changeTime() []byte { return h[488:][:12] }
|
||||||
func (h *headerSTAR) Trailer() []byte { return h[508:][:4] }
|
func (h *headerSTAR) trailer() []byte { return h[508:][:4] }
|
||||||
|
|
||||||
type headerUSTAR [blockSize]byte
|
type headerUSTAR [blockSize]byte
|
||||||
|
|
||||||
func (h *headerUSTAR) V7() *headerV7 { return (*headerV7)(h) }
|
func (h *headerUSTAR) v7() *headerV7 { return (*headerV7)(h) }
|
||||||
func (h *headerUSTAR) Magic() []byte { return h[257:][:6] }
|
func (h *headerUSTAR) magic() []byte { return h[257:][:6] }
|
||||||
func (h *headerUSTAR) Version() []byte { return h[263:][:2] }
|
func (h *headerUSTAR) version() []byte { return h[263:][:2] }
|
||||||
func (h *headerUSTAR) UserName() []byte { return h[265:][:32] }
|
func (h *headerUSTAR) userName() []byte { return h[265:][:32] }
|
||||||
func (h *headerUSTAR) GroupName() []byte { return h[297:][:32] }
|
func (h *headerUSTAR) groupName() []byte { return h[297:][:32] }
|
||||||
func (h *headerUSTAR) DevMajor() []byte { return h[329:][:8] }
|
func (h *headerUSTAR) devMajor() []byte { return h[329:][:8] }
|
||||||
func (h *headerUSTAR) DevMinor() []byte { return h[337:][:8] }
|
func (h *headerUSTAR) devMinor() []byte { return h[337:][:8] }
|
||||||
func (h *headerUSTAR) Prefix() []byte { return h[345:][:155] }
|
func (h *headerUSTAR) prefix() []byte { return h[345:][:155] }
|
||||||
|
|
||||||
type sparseArray []byte
|
type sparseArray []byte
|
||||||
|
|
||||||
func (s sparseArray) Entry(i int) sparseElem { return (sparseElem)(s[i*24:]) }
|
func (s sparseArray) entry(i int) sparseElem { return sparseElem(s[i*24:]) }
|
||||||
func (s sparseArray) IsExtended() []byte { return s[24*s.MaxEntries():][:1] }
|
func (s sparseArray) isExtended() []byte { return s[24*s.maxEntries():][:1] }
|
||||||
func (s sparseArray) MaxEntries() int { return len(s) / 24 }
|
func (s sparseArray) maxEntries() int { return len(s) / 24 }
|
||||||
|
|
||||||
type sparseElem []byte
|
type sparseElem []byte
|
||||||
|
|
||||||
func (s sparseElem) Offset() []byte { return s[00:][:12] }
|
func (s sparseElem) offset() []byte { return s[00:][:12] }
|
||||||
func (s sparseElem) Length() []byte { return s[12:][:12] }
|
func (s sparseElem) length() []byte { return s[12:][:12] }
|
||||||
|
|
80
archive/tar/fuzz_test.go
Normal file
80
archive/tar/fuzz_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2021 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 (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzReader(f *testing.F) {
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
w := NewWriter(b)
|
||||||
|
inp := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||||
|
err := w.WriteHeader(&Header{
|
||||||
|
Name: "lorem.txt",
|
||||||
|
Mode: 0600,
|
||||||
|
Size: int64(len(inp)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
f.Fatalf("failed to create writer: %s", err)
|
||||||
|
}
|
||||||
|
_, err = w.Write(inp)
|
||||||
|
if err != nil {
|
||||||
|
f.Fatalf("failed to write file to archive: %s", err)
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
f.Fatalf("failed to write archive: %s", err)
|
||||||
|
}
|
||||||
|
f.Add(b.Bytes())
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte) {
|
||||||
|
r := NewReader(bytes.NewReader(b))
|
||||||
|
type file struct {
|
||||||
|
header *Header
|
||||||
|
content []byte
|
||||||
|
}
|
||||||
|
files := []file{}
|
||||||
|
for {
|
||||||
|
hdr, err := r.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
if _, err := io.Copy(buf, r); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, file{header: hdr, content: buf.Bytes()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were unable to read anything out of the archive don't
|
||||||
|
// bother trying to roundtrip it.
|
||||||
|
if len(files) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
w := NewWriter(out)
|
||||||
|
for _, f := range files {
|
||||||
|
if err := w.WriteHeader(f.header); err != nil {
|
||||||
|
t.Fatalf("unable to write previously parsed header: %s", err)
|
||||||
|
}
|
||||||
|
if _, err := w.Write(f.content); err != nil {
|
||||||
|
t.Fatalf("unable to write previously parsed content: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
t.Fatalf("Unable to write archive: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We may want to check if the archive roundtrips. This would require
|
||||||
|
// taking into account addition of the two zero trailer blocks that Writer.Close
|
||||||
|
// appends.
|
||||||
|
})
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ package tar
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -41,7 +40,7 @@ type fileReader interface {
|
||||||
// RawBytes accesses the raw bytes of the archive, apart from the file payload itself.
|
// RawBytes accesses the raw bytes of the archive, apart from the file payload itself.
|
||||||
// This includes the header and padding.
|
// This includes the header and padding.
|
||||||
//
|
//
|
||||||
// This call resets the current rawbytes buffer
|
// # This call resets the current rawbytes buffer
|
||||||
//
|
//
|
||||||
// Only when RawAccounting is enabled, otherwise this returns nil
|
// Only when RawAccounting is enabled, otherwise this returns nil
|
||||||
func (tr *Reader) RawBytes() []byte {
|
func (tr *Reader) RawBytes() []byte {
|
||||||
|
@ -96,7 +95,7 @@ func (tr *Reader) next() (*Header, error) {
|
||||||
format := FormatUSTAR | FormatPAX | FormatGNU
|
format := FormatUSTAR | FormatPAX | FormatGNU
|
||||||
for {
|
for {
|
||||||
// Discard the remainder of the file and any padding.
|
// Discard the remainder of the file and any padding.
|
||||||
if err := discard(tr, tr.curr.PhysicalRemaining()); err != nil {
|
if err := discard(tr, tr.curr.physicalRemaining()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
n, err := tryReadFull(tr.r, tr.blk[:tr.pad])
|
n, err := tryReadFull(tr.r, tr.blk[:tr.pad])
|
||||||
|
@ -138,7 +137,7 @@ func (tr *Reader) next() (*Header, error) {
|
||||||
continue // This is a meta header affecting the next header
|
continue // This is a meta header affecting the next header
|
||||||
case TypeGNULongName, TypeGNULongLink:
|
case TypeGNULongName, TypeGNULongLink:
|
||||||
format.mayOnlyBe(FormatGNU)
|
format.mayOnlyBe(FormatGNU)
|
||||||
realname, err := ioutil.ReadAll(tr)
|
realname, err := io.ReadAll(tr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -332,7 +331,7 @@ func mergePAX(hdr *Header, paxHdrs map[string]string) (err error) {
|
||||||
// parsePAX parses PAX headers.
|
// parsePAX parses PAX headers.
|
||||||
// If an extended header (type 'x') is invalid, ErrHeader is returned
|
// If an extended header (type 'x') is invalid, ErrHeader is returned
|
||||||
func parsePAX(r io.Reader) (map[string]string, error) {
|
func parsePAX(r io.Reader) (map[string]string, error) {
|
||||||
buf, err := ioutil.ReadAll(r)
|
buf, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -381,9 +380,9 @@ func parsePAX(r io.Reader) (map[string]string, error) {
|
||||||
// header in case further processing is required.
|
// header in case further processing is required.
|
||||||
//
|
//
|
||||||
// The err will be set to io.EOF only when one of the following occurs:
|
// The err will be set to io.EOF only when one of the following occurs:
|
||||||
// * Exactly 0 bytes are read and EOF is hit.
|
// - Exactly 0 bytes are read and EOF is hit.
|
||||||
// * Exactly 1 block of zeros is read and EOF is hit.
|
// - Exactly 1 block of zeros is read and EOF is hit.
|
||||||
// * At least 2 blocks of zeros are read.
|
// - At least 2 blocks of zeros are read.
|
||||||
func (tr *Reader) readHeader() (*Header, *block, error) {
|
func (tr *Reader) readHeader() (*Header, *block, error) {
|
||||||
// Two blocks of zero bytes marks the end of the archive.
|
// Two blocks of zero bytes marks the end of the archive.
|
||||||
n, err := io.ReadFull(tr.r, tr.blk[:])
|
n, err := io.ReadFull(tr.r, tr.blk[:])
|
||||||
|
@ -409,7 +408,7 @@ func (tr *Reader) readHeader() (*Header, *block, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the header matches a known format.
|
// Verify the header matches a known format.
|
||||||
format := tr.blk.GetFormat()
|
format := tr.blk.getFormat()
|
||||||
if format == FormatUnknown {
|
if format == FormatUnknown {
|
||||||
return nil, nil, ErrHeader
|
return nil, nil, ErrHeader
|
||||||
}
|
}
|
||||||
|
@ -418,30 +417,30 @@ func (tr *Reader) readHeader() (*Header, *block, error) {
|
||||||
hdr := new(Header)
|
hdr := new(Header)
|
||||||
|
|
||||||
// Unpack the V7 header.
|
// Unpack the V7 header.
|
||||||
v7 := tr.blk.V7()
|
v7 := tr.blk.toV7()
|
||||||
hdr.Typeflag = v7.TypeFlag()[0]
|
hdr.Typeflag = v7.typeFlag()[0]
|
||||||
hdr.Name = p.parseString(v7.Name())
|
hdr.Name = p.parseString(v7.name())
|
||||||
hdr.Linkname = p.parseString(v7.LinkName())
|
hdr.Linkname = p.parseString(v7.linkName())
|
||||||
hdr.Size = p.parseNumeric(v7.Size())
|
hdr.Size = p.parseNumeric(v7.size())
|
||||||
hdr.Mode = p.parseNumeric(v7.Mode())
|
hdr.Mode = p.parseNumeric(v7.mode())
|
||||||
hdr.Uid = int(p.parseNumeric(v7.UID()))
|
hdr.Uid = int(p.parseNumeric(v7.uid()))
|
||||||
hdr.Gid = int(p.parseNumeric(v7.GID()))
|
hdr.Gid = int(p.parseNumeric(v7.gid()))
|
||||||
hdr.ModTime = time.Unix(p.parseNumeric(v7.ModTime()), 0)
|
hdr.ModTime = time.Unix(p.parseNumeric(v7.modTime()), 0)
|
||||||
|
|
||||||
// Unpack format specific fields.
|
// Unpack format specific fields.
|
||||||
if format > formatV7 {
|
if format > formatV7 {
|
||||||
ustar := tr.blk.USTAR()
|
ustar := tr.blk.toUSTAR()
|
||||||
hdr.Uname = p.parseString(ustar.UserName())
|
hdr.Uname = p.parseString(ustar.userName())
|
||||||
hdr.Gname = p.parseString(ustar.GroupName())
|
hdr.Gname = p.parseString(ustar.groupName())
|
||||||
hdr.Devmajor = p.parseNumeric(ustar.DevMajor())
|
hdr.Devmajor = p.parseNumeric(ustar.devMajor())
|
||||||
hdr.Devminor = p.parseNumeric(ustar.DevMinor())
|
hdr.Devminor = p.parseNumeric(ustar.devMinor())
|
||||||
|
|
||||||
var prefix string
|
var prefix string
|
||||||
switch {
|
switch {
|
||||||
case format.has(FormatUSTAR | FormatPAX):
|
case format.has(FormatUSTAR | FormatPAX):
|
||||||
hdr.Format = format
|
hdr.Format = format
|
||||||
ustar := tr.blk.USTAR()
|
ustar := tr.blk.toUSTAR()
|
||||||
prefix = p.parseString(ustar.Prefix())
|
prefix = p.parseString(ustar.prefix())
|
||||||
|
|
||||||
// For Format detection, check if block is properly formatted since
|
// For Format detection, check if block is properly formatted since
|
||||||
// the parser is more liberal than what USTAR actually permits.
|
// the parser is more liberal than what USTAR actually permits.
|
||||||
|
@ -450,23 +449,23 @@ func (tr *Reader) readHeader() (*Header, *block, error) {
|
||||||
hdr.Format = FormatUnknown // Non-ASCII characters in block.
|
hdr.Format = FormatUnknown // Non-ASCII characters in block.
|
||||||
}
|
}
|
||||||
nul := func(b []byte) bool { return int(b[len(b)-1]) == 0 }
|
nul := func(b []byte) bool { return int(b[len(b)-1]) == 0 }
|
||||||
if !(nul(v7.Size()) && nul(v7.Mode()) && nul(v7.UID()) && nul(v7.GID()) &&
|
if !(nul(v7.size()) && nul(v7.mode()) && nul(v7.uid()) && nul(v7.gid()) &&
|
||||||
nul(v7.ModTime()) && nul(ustar.DevMajor()) && nul(ustar.DevMinor())) {
|
nul(v7.modTime()) && nul(ustar.devMajor()) && nul(ustar.devMinor())) {
|
||||||
hdr.Format = FormatUnknown // Numeric fields must end in NUL
|
hdr.Format = FormatUnknown // Numeric fields must end in NUL
|
||||||
}
|
}
|
||||||
case format.has(formatSTAR):
|
case format.has(formatSTAR):
|
||||||
star := tr.blk.STAR()
|
star := tr.blk.toSTAR()
|
||||||
prefix = p.parseString(star.Prefix())
|
prefix = p.parseString(star.prefix())
|
||||||
hdr.AccessTime = time.Unix(p.parseNumeric(star.AccessTime()), 0)
|
hdr.AccessTime = time.Unix(p.parseNumeric(star.accessTime()), 0)
|
||||||
hdr.ChangeTime = time.Unix(p.parseNumeric(star.ChangeTime()), 0)
|
hdr.ChangeTime = time.Unix(p.parseNumeric(star.changeTime()), 0)
|
||||||
case format.has(FormatGNU):
|
case format.has(FormatGNU):
|
||||||
hdr.Format = format
|
hdr.Format = format
|
||||||
var p2 parser
|
var p2 parser
|
||||||
gnu := tr.blk.GNU()
|
gnu := tr.blk.toGNU()
|
||||||
if b := gnu.AccessTime(); b[0] != 0 {
|
if b := gnu.accessTime(); b[0] != 0 {
|
||||||
hdr.AccessTime = time.Unix(p2.parseNumeric(b), 0)
|
hdr.AccessTime = time.Unix(p2.parseNumeric(b), 0)
|
||||||
}
|
}
|
||||||
if b := gnu.ChangeTime(); b[0] != 0 {
|
if b := gnu.changeTime(); b[0] != 0 {
|
||||||
hdr.ChangeTime = time.Unix(p2.parseNumeric(b), 0)
|
hdr.ChangeTime = time.Unix(p2.parseNumeric(b), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,15 +485,15 @@ func (tr *Reader) readHeader() (*Header, *block, error) {
|
||||||
// files generated by a pre-Go1.8 toolchain. If the generated file
|
// files generated by a pre-Go1.8 toolchain. If the generated file
|
||||||
// happened to have a prefix field that parses as valid
|
// happened to have a prefix field that parses as valid
|
||||||
// atime and ctime fields (e.g., when they are valid octal strings),
|
// atime and ctime fields (e.g., when they are valid octal strings),
|
||||||
// then it is impossible to distinguish between an valid GNU file
|
// then it is impossible to distinguish between a valid GNU file
|
||||||
// and an invalid pre-Go1.8 file.
|
// and an invalid pre-Go1.8 file.
|
||||||
//
|
//
|
||||||
// See https://golang.org/issues/12594
|
// See https://golang.org/issues/12594
|
||||||
// See https://golang.org/issues/21005
|
// See https://golang.org/issues/21005
|
||||||
if p2.err != nil {
|
if p2.err != nil {
|
||||||
hdr.AccessTime, hdr.ChangeTime = time.Time{}, time.Time{}
|
hdr.AccessTime, hdr.ChangeTime = time.Time{}, time.Time{}
|
||||||
ustar := tr.blk.USTAR()
|
ustar := tr.blk.toUSTAR()
|
||||||
if s := p.parseString(ustar.Prefix()); isASCII(s) {
|
if s := p.parseString(ustar.prefix()); isASCII(s) {
|
||||||
prefix = s
|
prefix = s
|
||||||
}
|
}
|
||||||
hdr.Format = FormatUnknown // Buggy file is not GNU
|
hdr.Format = FormatUnknown // Buggy file is not GNU
|
||||||
|
@ -519,33 +518,33 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err
|
||||||
// Make sure that the input format is GNU.
|
// Make sure that the input format is GNU.
|
||||||
// Unfortunately, the STAR format also has a sparse header format that uses
|
// Unfortunately, the STAR format also has a sparse header format that uses
|
||||||
// the same type flag but has a completely different layout.
|
// the same type flag but has a completely different layout.
|
||||||
if blk.GetFormat() != FormatGNU {
|
if blk.getFormat() != FormatGNU {
|
||||||
return nil, ErrHeader
|
return nil, ErrHeader
|
||||||
}
|
}
|
||||||
hdr.Format.mayOnlyBe(FormatGNU)
|
hdr.Format.mayOnlyBe(FormatGNU)
|
||||||
|
|
||||||
var p parser
|
var p parser
|
||||||
hdr.Size = p.parseNumeric(blk.GNU().RealSize())
|
hdr.Size = p.parseNumeric(blk.toGNU().realSize())
|
||||||
if p.err != nil {
|
if p.err != nil {
|
||||||
return nil, p.err
|
return nil, p.err
|
||||||
}
|
}
|
||||||
s := blk.GNU().Sparse()
|
s := blk.toGNU().sparse()
|
||||||
spd := make(sparseDatas, 0, s.MaxEntries())
|
spd := make(sparseDatas, 0, s.maxEntries())
|
||||||
for {
|
for {
|
||||||
for i := 0; i < s.MaxEntries(); i++ {
|
for i := 0; i < s.maxEntries(); i++ {
|
||||||
// This termination condition is identical to GNU and BSD tar.
|
// This termination condition is identical to GNU and BSD tar.
|
||||||
if s.Entry(i).Offset()[0] == 0x00 {
|
if s.entry(i).offset()[0] == 0x00 {
|
||||||
break // Don't return, need to process extended headers (even if empty)
|
break // Don't return, need to process extended headers (even if empty)
|
||||||
}
|
}
|
||||||
offset := p.parseNumeric(s.Entry(i).Offset())
|
offset := p.parseNumeric(s.entry(i).offset())
|
||||||
length := p.parseNumeric(s.Entry(i).Length())
|
length := p.parseNumeric(s.entry(i).length())
|
||||||
if p.err != nil {
|
if p.err != nil {
|
||||||
return nil, p.err
|
return nil, p.err
|
||||||
}
|
}
|
||||||
spd = append(spd, sparseEntry{Offset: offset, Length: length})
|
spd = append(spd, sparseEntry{Offset: offset, Length: length})
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.IsExtended()[0] > 0 {
|
if s.isExtended()[0] > 0 {
|
||||||
// There are more entries. Read an extension header and parse its entries.
|
// There are more entries. Read an extension header and parse its entries.
|
||||||
if _, err := mustReadFull(tr.r, blk[:]); err != nil {
|
if _, err := mustReadFull(tr.r, blk[:]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -553,7 +552,7 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err
|
||||||
if tr.RawAccounting {
|
if tr.RawAccounting {
|
||||||
tr.rawBytes.Write(blk[:])
|
tr.rawBytes.Write(blk[:])
|
||||||
}
|
}
|
||||||
s = blk.Sparse()
|
s = blk.toSparse()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return spd, nil // Done
|
return spd, nil // Done
|
||||||
|
@ -735,11 +734,13 @@ func (fr *regFileReader) WriteTo(w io.Writer) (int64, error) {
|
||||||
return io.Copy(w, struct{ io.Reader }{fr})
|
return io.Copy(w, struct{ io.Reader }{fr})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr regFileReader) LogicalRemaining() int64 {
|
// logicalRemaining implements fileState.logicalRemaining.
|
||||||
|
func (fr regFileReader) logicalRemaining() int64 {
|
||||||
return fr.nb
|
return fr.nb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr regFileReader) PhysicalRemaining() int64 {
|
// logicalRemaining implements fileState.physicalRemaining.
|
||||||
|
func (fr regFileReader) physicalRemaining() int64 {
|
||||||
return fr.nb
|
return fr.nb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,9 +752,9 @@ type sparseFileReader struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *sparseFileReader) Read(b []byte) (n int, err error) {
|
func (sr *sparseFileReader) Read(b []byte) (n int, err error) {
|
||||||
finished := int64(len(b)) >= sr.LogicalRemaining()
|
finished := int64(len(b)) >= sr.logicalRemaining()
|
||||||
if finished {
|
if finished {
|
||||||
b = b[:sr.LogicalRemaining()]
|
b = b[:sr.logicalRemaining()]
|
||||||
}
|
}
|
||||||
|
|
||||||
b0 := b
|
b0 := b
|
||||||
|
@ -781,7 +782,7 @@ func (sr *sparseFileReader) Read(b []byte) (n int, err error) {
|
||||||
return n, errMissData // Less data in dense file than sparse file
|
return n, errMissData // Less data in dense file than sparse file
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return n, err
|
return n, err
|
||||||
case sr.LogicalRemaining() == 0 && sr.PhysicalRemaining() > 0:
|
case sr.logicalRemaining() == 0 && sr.physicalRemaining() > 0:
|
||||||
return n, errUnrefData // More data in dense file than sparse file
|
return n, errUnrefData // More data in dense file than sparse file
|
||||||
case finished:
|
case finished:
|
||||||
return n, io.EOF
|
return n, io.EOF
|
||||||
|
@ -803,7 +804,7 @@ func (sr *sparseFileReader) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
|
||||||
var writeLastByte bool
|
var writeLastByte bool
|
||||||
pos0 := sr.pos
|
pos0 := sr.pos
|
||||||
for sr.LogicalRemaining() > 0 && !writeLastByte && err == nil {
|
for sr.logicalRemaining() > 0 && !writeLastByte && err == nil {
|
||||||
var nf int64 // Size of fragment
|
var nf int64 // Size of fragment
|
||||||
holeStart, holeEnd := sr.sp[0].Offset, sr.sp[0].endOffset()
|
holeStart, holeEnd := sr.sp[0].Offset, sr.sp[0].endOffset()
|
||||||
if sr.pos < holeStart { // In a data fragment
|
if sr.pos < holeStart { // In a data fragment
|
||||||
|
@ -811,7 +812,7 @@ func (sr *sparseFileReader) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
nf, err = io.CopyN(ws, sr.fr, nf)
|
nf, err = io.CopyN(ws, sr.fr, nf)
|
||||||
} else { // In a hole fragment
|
} else { // In a hole fragment
|
||||||
nf = holeEnd - sr.pos
|
nf = holeEnd - sr.pos
|
||||||
if sr.PhysicalRemaining() == 0 {
|
if sr.physicalRemaining() == 0 {
|
||||||
writeLastByte = true
|
writeLastByte = true
|
||||||
nf--
|
nf--
|
||||||
}
|
}
|
||||||
|
@ -836,18 +837,18 @@ func (sr *sparseFileReader) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
return n, errMissData // Less data in dense file than sparse file
|
return n, errMissData // Less data in dense file than sparse file
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return n, err
|
return n, err
|
||||||
case sr.LogicalRemaining() == 0 && sr.PhysicalRemaining() > 0:
|
case sr.logicalRemaining() == 0 && sr.physicalRemaining() > 0:
|
||||||
return n, errUnrefData // More data in dense file than sparse file
|
return n, errUnrefData // More data in dense file than sparse file
|
||||||
default:
|
default:
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr sparseFileReader) LogicalRemaining() int64 {
|
func (sr sparseFileReader) logicalRemaining() int64 {
|
||||||
return sr.sp[len(sr.sp)-1].endOffset() - sr.pos
|
return sr.sp[len(sr.sp)-1].endOffset() - sr.pos
|
||||||
}
|
}
|
||||||
func (sr sparseFileReader) PhysicalRemaining() int64 {
|
func (sr sparseFileReader) physicalRemaining() int64 {
|
||||||
return sr.fr.PhysicalRemaining()
|
return sr.fr.physicalRemaining()
|
||||||
}
|
}
|
||||||
|
|
||||||
type zeroReader struct{}
|
type zeroReader struct{}
|
||||||
|
@ -889,7 +890,6 @@ func discard(tr *Reader, n int64) error {
|
||||||
var err error
|
var err error
|
||||||
r := tr.r
|
r := tr.r
|
||||||
if tr.RawAccounting {
|
if tr.RawAccounting {
|
||||||
|
|
||||||
copySkipped, err = io.CopyN(tr.rawBytes, tr.r, n)
|
copySkipped, err = io.CopyN(tr.rawBytes, tr.r, n)
|
||||||
goto out
|
goto out
|
||||||
}
|
}
|
||||||
|
@ -914,7 +914,7 @@ func discard(tr *Reader, n int64) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copySkipped, err = io.CopyN(ioutil.Discard, r, n-seekSkipped)
|
copySkipped, err = io.CopyN(io.Discard, r, n-seekSkipped)
|
||||||
out:
|
out:
|
||||||
if err == io.EOF && seekSkipped+copySkipped < n {
|
if err == io.EOF && seekSkipped+copySkipped < n {
|
||||||
err = io.ErrUnexpectedEOF
|
err = io.ErrUnexpectedEOF
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -773,7 +772,7 @@ func TestReadTruncation(t *testing.T) {
|
||||||
"testdata/pax-path-hdr.tar",
|
"testdata/pax-path-hdr.tar",
|
||||||
"testdata/sparse-formats.tar",
|
"testdata/sparse-formats.tar",
|
||||||
} {
|
} {
|
||||||
buf, err := ioutil.ReadFile(p)
|
buf, err := os.ReadFile(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -865,7 +864,7 @@ func TestReadTruncation(t *testing.T) {
|
||||||
}
|
}
|
||||||
cnt++
|
cnt++
|
||||||
if s2 == "manual" {
|
if s2 == "manual" {
|
||||||
if _, err = tr.writeTo(ioutil.Discard); err != nil {
|
if _, err = tr.writeTo(io.Discard); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1022,12 +1021,12 @@ func TestParsePAX(t *testing.T) {
|
||||||
|
|
||||||
func TestReadOldGNUSparseMap(t *testing.T) {
|
func TestReadOldGNUSparseMap(t *testing.T) {
|
||||||
populateSparseMap := func(sa sparseArray, sps []string) []string {
|
populateSparseMap := func(sa sparseArray, sps []string) []string {
|
||||||
for i := 0; len(sps) > 0 && i < sa.MaxEntries(); i++ {
|
for i := 0; len(sps) > 0 && i < sa.maxEntries(); i++ {
|
||||||
copy(sa.Entry(i), sps[0])
|
copy(sa.entry(i), sps[0])
|
||||||
sps = sps[1:]
|
sps = sps[1:]
|
||||||
}
|
}
|
||||||
if len(sps) > 0 {
|
if len(sps) > 0 {
|
||||||
copy(sa.IsExtended(), "\x80")
|
copy(sa.isExtended(), "\x80")
|
||||||
}
|
}
|
||||||
return sps
|
return sps
|
||||||
}
|
}
|
||||||
|
@ -1035,19 +1034,19 @@ func TestReadOldGNUSparseMap(t *testing.T) {
|
||||||
makeInput := func(format Format, size string, sps ...string) (out []byte) {
|
makeInput := func(format Format, size string, sps ...string) (out []byte) {
|
||||||
// Write the initial GNU header.
|
// Write the initial GNU header.
|
||||||
var blk block
|
var blk block
|
||||||
gnu := blk.GNU()
|
gnu := blk.toGNU()
|
||||||
sparse := gnu.Sparse()
|
sparse := gnu.sparse()
|
||||||
copy(gnu.RealSize(), size)
|
copy(gnu.realSize(), size)
|
||||||
sps = populateSparseMap(sparse, sps)
|
sps = populateSparseMap(sparse, sps)
|
||||||
if format != FormatUnknown {
|
if format != FormatUnknown {
|
||||||
blk.SetFormat(format)
|
blk.setFormat(format)
|
||||||
}
|
}
|
||||||
out = append(out, blk[:]...)
|
out = append(out, blk[:]...)
|
||||||
|
|
||||||
// Write extended sparse blocks.
|
// Write extended sparse blocks.
|
||||||
for len(sps) > 0 {
|
for len(sps) > 0 {
|
||||||
var blk block
|
var blk block
|
||||||
sps = populateSparseMap(blk.Sparse(), sps)
|
sps = populateSparseMap(blk.toSparse(), sps)
|
||||||
out = append(out, blk[:]...)
|
out = append(out, blk[:]...)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
@ -1360,11 +1359,11 @@ func TestFileReader(t *testing.T) {
|
||||||
wantCnt int64
|
wantCnt int64
|
||||||
wantErr error
|
wantErr error
|
||||||
}
|
}
|
||||||
testRemaining struct { // LogicalRemaining() == wantLCnt, PhysicalRemaining() == wantPCnt
|
testRemaining struct { // logicalRemaining() == wantLCnt, physicalRemaining() == wantPCnt
|
||||||
wantLCnt int64
|
wantLCnt int64
|
||||||
wantPCnt int64
|
wantPCnt int64
|
||||||
}
|
}
|
||||||
testFnc interface{} // testRead | testWriteTo | testRemaining
|
testFnc any // testRead | testWriteTo | testRemaining
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -1377,7 +1376,7 @@ func TestFileReader(t *testing.T) {
|
||||||
spd sparseDatas
|
spd sparseDatas
|
||||||
size int64
|
size int64
|
||||||
}
|
}
|
||||||
fileMaker interface{} // makeReg | makeSparse
|
fileMaker any // makeReg | makeSparse
|
||||||
)
|
)
|
||||||
|
|
||||||
vectors := []struct {
|
vectors := []struct {
|
||||||
|
@ -1597,11 +1596,11 @@ func TestFileReader(t *testing.T) {
|
||||||
t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
|
t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
|
||||||
}
|
}
|
||||||
case testRemaining:
|
case testRemaining:
|
||||||
if got := fr.LogicalRemaining(); got != tf.wantLCnt {
|
if got := fr.logicalRemaining(); got != tf.wantLCnt {
|
||||||
t.Errorf("test %d.%d, LogicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
|
t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
|
||||||
}
|
}
|
||||||
if got := fr.PhysicalRemaining(); got != tf.wantPCnt {
|
if got := fr.physicalRemaining(); got != tf.wantPCnt {
|
||||||
t.Errorf("test %d.%d, PhysicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
|
t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
|
t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build linux dragonfly openbsd solaris
|
//go:build aix || linux || dragonfly || openbsd || solaris
|
||||||
|
|
||||||
package tar
|
package tar
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build darwin freebsd netbsd
|
//go:build darwin || freebsd || netbsd
|
||||||
|
|
||||||
package tar
|
package tar
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build linux darwin dragonfly freebsd openbsd netbsd solaris
|
//go:build unix
|
||||||
|
|
||||||
package tar
|
package tar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"io/fs"
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -23,7 +23,7 @@ func init() {
|
||||||
// The downside is that renaming uname or gname by the OS never takes effect.
|
// The downside is that renaming uname or gname by the OS never takes effect.
|
||||||
var userMap, groupMap sync.Map // map[int]string
|
var userMap, groupMap sync.Map // map[int]string
|
||||||
|
|
||||||
func statUnix(fi os.FileInfo, h *Header) error {
|
func statUnix(fi fs.FileInfo, h *Header) error {
|
||||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
@ -54,6 +54,11 @@ func statUnix(fi os.FileInfo, h *Header) error {
|
||||||
if h.Typeflag == TypeChar || h.Typeflag == TypeBlock {
|
if h.Typeflag == TypeChar || h.Typeflag == TypeBlock {
|
||||||
dev := uint64(sys.Rdev) // May be int32 or uint32
|
dev := uint64(sys.Rdev) // May be int32 or uint32
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
|
case "aix":
|
||||||
|
var major, minor uint32
|
||||||
|
major = uint32((dev & 0x3fffffff00000000) >> 32)
|
||||||
|
minor = uint32((dev & 0x00000000ffffffff) >> 0)
|
||||||
|
h.Devmajor, h.Devminor = int64(major), int64(minor)
|
||||||
case "linux":
|
case "linux":
|
||||||
// Copied from golang.org/x/sys/unix/dev_linux.go.
|
// Copied from golang.org/x/sys/unix/dev_linux.go.
|
||||||
major := uint32((dev & 0x00000000000fff00) >> 8)
|
major := uint32((dev & 0x00000000000fff00) >> 8)
|
||||||
|
@ -61,7 +66,7 @@ func statUnix(fi os.FileInfo, h *Header) error {
|
||||||
minor := uint32((dev & 0x00000000000000ff) >> 0)
|
minor := uint32((dev & 0x00000000000000ff) >> 0)
|
||||||
minor |= uint32((dev & 0x00000ffffff00000) >> 12)
|
minor |= uint32((dev & 0x00000ffffff00000) >> 12)
|
||||||
h.Devmajor, h.Devminor = int64(major), int64(minor)
|
h.Devmajor, h.Devminor = int64(major), int64(minor)
|
||||||
case "darwin":
|
case "darwin", "ios":
|
||||||
// Copied from golang.org/x/sys/unix/dev_darwin.go.
|
// Copied from golang.org/x/sys/unix/dev_darwin.go.
|
||||||
major := uint32((dev >> 24) & 0xff)
|
major := uint32((dev >> 24) & 0xff)
|
||||||
minor := uint32(dev & 0xffffff)
|
minor := uint32(dev & 0xffffff)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
// hasNUL reports whether the NUL character exists within s.
|
// hasNUL reports whether the NUL character exists within s.
|
||||||
func hasNUL(s string) bool {
|
func hasNUL(s string) bool {
|
||||||
return strings.IndexByte(s, 0) >= 0
|
return strings.Contains(s, "\x00")
|
||||||
}
|
}
|
||||||
|
|
||||||
// isASCII reports whether the input is an ASCII C-style string.
|
// isASCII reports whether the input is an ASCII C-style string.
|
||||||
|
@ -28,7 +28,7 @@ func isASCII(s string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// toASCII converts the input to an ASCII C-style string.
|
// toASCII converts the input to an ASCII C-style string.
|
||||||
// This a best effort conversion, so invalid characters are dropped.
|
// This is a best effort conversion, so invalid characters are dropped.
|
||||||
func toASCII(s string) string {
|
func toASCII(s string) string {
|
||||||
if isASCII(s) {
|
if isASCII(s) {
|
||||||
return s
|
return s
|
||||||
|
@ -201,10 +201,7 @@ func parsePAXTime(s string) (time.Time, error) {
|
||||||
const maxNanoSecondDigits = 9
|
const maxNanoSecondDigits = 9
|
||||||
|
|
||||||
// Split string into seconds and sub-seconds parts.
|
// Split string into seconds and sub-seconds parts.
|
||||||
ss, sn := s, ""
|
ss, sn, _ := strings.Cut(s, ".")
|
||||||
if pos := strings.IndexByte(s, '.'); pos >= 0 {
|
|
||||||
ss, sn = s[:pos], s[pos+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the seconds.
|
// Parse the seconds.
|
||||||
secs, err := strconv.ParseInt(ss, 10, 64)
|
secs, err := strconv.ParseInt(ss, 10, 64)
|
||||||
|
@ -244,7 +241,7 @@ func formatPAXTime(ts time.Time) (s string) {
|
||||||
if secs < 0 {
|
if secs < 0 {
|
||||||
sign = "-" // Remember sign
|
sign = "-" // Remember sign
|
||||||
secs = -(secs + 1) // Add a second to secs
|
secs = -(secs + 1) // Add a second to secs
|
||||||
nsecs = -(nsecs - 1E9) // Take that second away from nsecs
|
nsecs = -(nsecs - 1e9) // Take that second away from nsecs
|
||||||
}
|
}
|
||||||
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
|
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
|
||||||
}
|
}
|
||||||
|
@ -254,29 +251,32 @@ func formatPAXTime(ts time.Time) (s string) {
|
||||||
// return the remainder as r.
|
// return the remainder as r.
|
||||||
func parsePAXRecord(s string) (k, v, r string, err error) {
|
func parsePAXRecord(s string) (k, v, r string, err error) {
|
||||||
// The size field ends at the first space.
|
// The size field ends at the first space.
|
||||||
sp := strings.IndexByte(s, ' ')
|
nStr, rest, ok := strings.Cut(s, " ")
|
||||||
if sp == -1 {
|
if !ok {
|
||||||
return "", "", s, ErrHeader
|
return "", "", s, ErrHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the first token as a decimal integer.
|
// Parse the first token as a decimal integer.
|
||||||
n, perr := strconv.ParseInt(s[:sp], 10, 0) // Intentionally parse as native int
|
n, perr := strconv.ParseInt(nStr, 10, 0) // Intentionally parse as native int
|
||||||
if perr != nil || n < 5 || int64(len(s)) < n {
|
if perr != nil || n < 5 || n > int64(len(s)) {
|
||||||
|
return "", "", s, ErrHeader
|
||||||
|
}
|
||||||
|
n -= int64(len(nStr) + 1) // convert from index in s to index in rest
|
||||||
|
if n <= 0 {
|
||||||
return "", "", s, ErrHeader
|
return "", "", s, ErrHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract everything between the space and the final newline.
|
// Extract everything between the space and the final newline.
|
||||||
rec, nl, rem := s[sp+1:n-1], s[n-1:n], s[n:]
|
rec, nl, rem := rest[:n-1], rest[n-1:n], rest[n:]
|
||||||
if nl != "\n" {
|
if nl != "\n" {
|
||||||
return "", "", s, ErrHeader
|
return "", "", s, ErrHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first equals separates the key from the value.
|
// The first equals separates the key from the value.
|
||||||
eq := strings.IndexByte(rec, '=')
|
k, v, ok = strings.Cut(rec, "=")
|
||||||
if eq == -1 {
|
if !ok {
|
||||||
return "", "", s, ErrHeader
|
return "", "", s, ErrHeader
|
||||||
}
|
}
|
||||||
k, v = rec[:eq], rec[eq+1:]
|
|
||||||
|
|
||||||
if !validPAXRecord(k, v) {
|
if !validPAXRecord(k, v) {
|
||||||
return "", "", s, ErrHeader
|
return "", "", s, ErrHeader
|
||||||
|
@ -306,6 +306,7 @@ func formatPAXRecord(k, v string) (string, error) {
|
||||||
|
|
||||||
// validPAXRecord reports whether the key-value pair is valid where each
|
// validPAXRecord reports whether the key-value pair is valid where each
|
||||||
// record is formatted as:
|
// record is formatted as:
|
||||||
|
//
|
||||||
// "%d %s=%s\n" % (size, key, value)
|
// "%d %s=%s\n" % (size, key, value)
|
||||||
//
|
//
|
||||||
// Keys and values should be UTF-8, but the number of bad writers out there
|
// Keys and values should be UTF-8, but the number of bad writers out there
|
||||||
|
@ -314,7 +315,7 @@ func formatPAXRecord(k, v string) (string, error) {
|
||||||
// for the PAX version of the USTAR string fields.
|
// for the PAX version of the USTAR string fields.
|
||||||
// The key must not contain an '=' character.
|
// The key must not contain an '=' character.
|
||||||
func validPAXRecord(k, v string) bool {
|
func validPAXRecord(k, v string) bool {
|
||||||
if k == "" || strings.IndexByte(k, '=') >= 0 {
|
if k == "" || strings.Contains(k, "=") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch k {
|
switch k {
|
||||||
|
|
|
@ -303,27 +303,27 @@ func TestFormatPAXTime(t *testing.T) {
|
||||||
{1350244992, 300000000, "1350244992.3"},
|
{1350244992, 300000000, "1350244992.3"},
|
||||||
{1350244992, 23960100, "1350244992.0239601"},
|
{1350244992, 23960100, "1350244992.0239601"},
|
||||||
{1350244992, 23960108, "1350244992.023960108"},
|
{1350244992, 23960108, "1350244992.023960108"},
|
||||||
{+1, +1E9 - 1E0, "1.999999999"},
|
{+1, +1e9 - 1e0, "1.999999999"},
|
||||||
{+1, +1E9 - 1E3, "1.999999"},
|
{+1, +1e9 - 1e3, "1.999999"},
|
||||||
{+1, +1E9 - 1E6, "1.999"},
|
{+1, +1e9 - 1e6, "1.999"},
|
||||||
{+1, +0E0 - 0E0, "1"},
|
{+1, +0e0 - 0e0, "1"},
|
||||||
{+1, +1E6 - 0E0, "1.001"},
|
{+1, +1e6 - 0e0, "1.001"},
|
||||||
{+1, +1E3 - 0E0, "1.000001"},
|
{+1, +1e3 - 0e0, "1.000001"},
|
||||||
{+1, +1E0 - 0E0, "1.000000001"},
|
{+1, +1e0 - 0e0, "1.000000001"},
|
||||||
{0, 1E9 - 1E0, "0.999999999"},
|
{0, 1e9 - 1e0, "0.999999999"},
|
||||||
{0, 1E9 - 1E3, "0.999999"},
|
{0, 1e9 - 1e3, "0.999999"},
|
||||||
{0, 1E9 - 1E6, "0.999"},
|
{0, 1e9 - 1e6, "0.999"},
|
||||||
{0, 0E0, "0"},
|
{0, 0e0, "0"},
|
||||||
{0, 1E6 + 0E0, "0.001"},
|
{0, 1e6 + 0e0, "0.001"},
|
||||||
{0, 1E3 + 0E0, "0.000001"},
|
{0, 1e3 + 0e0, "0.000001"},
|
||||||
{0, 1E0 + 0E0, "0.000000001"},
|
{0, 1e0 + 0e0, "0.000000001"},
|
||||||
{-1, -1E9 + 1E0, "-1.999999999"},
|
{-1, -1e9 + 1e0, "-1.999999999"},
|
||||||
{-1, -1E9 + 1E3, "-1.999999"},
|
{-1, -1e9 + 1e3, "-1.999999"},
|
||||||
{-1, -1E9 + 1E6, "-1.999"},
|
{-1, -1e9 + 1e6, "-1.999"},
|
||||||
{-1, -0E0 + 0E0, "-1"},
|
{-1, -0e0 + 0e0, "-1"},
|
||||||
{-1, -1E6 + 0E0, "-1.001"},
|
{-1, -1e6 + 0e0, "-1.001"},
|
||||||
{-1, -1E3 + 0E0, "-1.000001"},
|
{-1, -1e3 + 0e0, "-1.000001"},
|
||||||
{-1, -1E0 + 0E0, "-1.000000001"},
|
{-1, -1e0 + 0e0, "-1.000000001"},
|
||||||
{-1350244992, 0, "-1350244992"},
|
{-1350244992, 0, "-1350244992"},
|
||||||
{-1350244992, -300000000, "-1350244992.3"},
|
{-1350244992, -300000000, "-1350244992.3"},
|
||||||
{-1350244992, -23960100, "-1350244992.0239601"},
|
{-1350244992, -23960100, "-1350244992.0239601"},
|
||||||
|
@ -368,6 +368,13 @@ func TestParsePAXRecord(t *testing.T) {
|
||||||
{"16 longkeyname=hahaha\n", "16 longkeyname=hahaha\n", "", "", false},
|
{"16 longkeyname=hahaha\n", "16 longkeyname=hahaha\n", "", "", false},
|
||||||
{"3 somelongkey=\n", "3 somelongkey=\n", "", "", false},
|
{"3 somelongkey=\n", "3 somelongkey=\n", "", "", false},
|
||||||
{"50 tooshort=\n", "50 tooshort=\n", "", "", false},
|
{"50 tooshort=\n", "50 tooshort=\n", "", "", false},
|
||||||
|
{"0000000000000000000000000000000030 mtime=1432668921.098285006\n30 ctime=2147483649.15163319", "0000000000000000000000000000000030 mtime=1432668921.098285006\n30 ctime=2147483649.15163319", "mtime", "1432668921.098285006", false},
|
||||||
|
{"06 k=v\n", "06 k=v\n", "", "", false},
|
||||||
|
{"00006 k=v\n", "00006 k=v\n", "", "", false},
|
||||||
|
{"000006 k=v\n", "000006 k=v\n", "", "", false},
|
||||||
|
{"000000 k=v\n", "000000 k=v\n", "", "", false},
|
||||||
|
{"0 k=v\n", "0 k=v\n", "", "", false},
|
||||||
|
{"+0000005 x=\n", "+0000005 x=\n", "", "", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range vectors {
|
for _, v := range vectors {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -23,7 +23,7 @@ import (
|
||||||
|
|
||||||
type testError struct{ error }
|
type testError struct{ error }
|
||||||
|
|
||||||
type fileOps []interface{} // []T where T is (string | int64)
|
type fileOps []any // []T where T is (string | int64)
|
||||||
|
|
||||||
// testFile is an io.ReadWriteSeeker where the IO operations performed
|
// testFile is an io.ReadWriteSeeker where the IO operations performed
|
||||||
// on it must match the list of operations in ops.
|
// on it must match the list of operations in ops.
|
||||||
|
@ -264,16 +264,11 @@ func TestFileInfoHeaderSymlink(t *testing.T) {
|
||||||
case "android", "nacl", "plan9", "windows":
|
case "android", "nacl", "plan9", "windows":
|
||||||
t.Skip("symlinks not supported")
|
t.Skip("symlinks not supported")
|
||||||
}
|
}
|
||||||
tmpdir, err := ioutil.TempDir("", "TestFileInfoHeaderSymlink")
|
tmpdir := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpdir)
|
|
||||||
|
|
||||||
link := filepath.Join(tmpdir, "link")
|
link := filepath.Join(tmpdir, "link")
|
||||||
target := tmpdir
|
target := tmpdir
|
||||||
err = os.Symlink(target, link)
|
if err := os.Symlink(target, link); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
fi, err := os.Lstat(link)
|
fi, err := os.Lstat(link)
|
||||||
|
@ -329,7 +324,7 @@ func TestRoundTrip(t *testing.T) {
|
||||||
if !reflect.DeepEqual(rHdr, hdr) {
|
if !reflect.DeepEqual(rHdr, hdr) {
|
||||||
t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
|
t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
|
||||||
}
|
}
|
||||||
rData, err := ioutil.ReadAll(tr)
|
rData, err := io.ReadAll(tr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Read: %v", err)
|
t.Fatalf("Read: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -340,7 +335,7 @@ func TestRoundTrip(t *testing.T) {
|
||||||
|
|
||||||
type headerRoundTripTest struct {
|
type headerRoundTripTest struct {
|
||||||
h *Header
|
h *Header
|
||||||
fm os.FileMode
|
fm fs.FileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderRoundTrip(t *testing.T) {
|
func TestHeaderRoundTrip(t *testing.T) {
|
||||||
|
@ -363,7 +358,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360600852, 0),
|
ModTime: time.Unix(1360600852, 0),
|
||||||
Typeflag: TypeSymlink,
|
Typeflag: TypeSymlink,
|
||||||
},
|
},
|
||||||
fm: 0777 | os.ModeSymlink,
|
fm: 0777 | fs.ModeSymlink,
|
||||||
}, {
|
}, {
|
||||||
// character device node.
|
// character device node.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -373,7 +368,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360578951, 0),
|
ModTime: time.Unix(1360578951, 0),
|
||||||
Typeflag: TypeChar,
|
Typeflag: TypeChar,
|
||||||
},
|
},
|
||||||
fm: 0666 | os.ModeDevice | os.ModeCharDevice,
|
fm: 0666 | fs.ModeDevice | fs.ModeCharDevice,
|
||||||
}, {
|
}, {
|
||||||
// block device node.
|
// block device node.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -383,7 +378,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360578954, 0),
|
ModTime: time.Unix(1360578954, 0),
|
||||||
Typeflag: TypeBlock,
|
Typeflag: TypeBlock,
|
||||||
},
|
},
|
||||||
fm: 0660 | os.ModeDevice,
|
fm: 0660 | fs.ModeDevice,
|
||||||
}, {
|
}, {
|
||||||
// directory.
|
// directory.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -393,7 +388,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360601116, 0),
|
ModTime: time.Unix(1360601116, 0),
|
||||||
Typeflag: TypeDir,
|
Typeflag: TypeDir,
|
||||||
},
|
},
|
||||||
fm: 0755 | os.ModeDir,
|
fm: 0755 | fs.ModeDir,
|
||||||
}, {
|
}, {
|
||||||
// fifo node.
|
// fifo node.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -403,7 +398,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360578949, 0),
|
ModTime: time.Unix(1360578949, 0),
|
||||||
Typeflag: TypeFifo,
|
Typeflag: TypeFifo,
|
||||||
},
|
},
|
||||||
fm: 0600 | os.ModeNamedPipe,
|
fm: 0600 | fs.ModeNamedPipe,
|
||||||
}, {
|
}, {
|
||||||
// setuid.
|
// setuid.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -413,7 +408,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1355405093, 0),
|
ModTime: time.Unix(1355405093, 0),
|
||||||
Typeflag: TypeReg,
|
Typeflag: TypeReg,
|
||||||
},
|
},
|
||||||
fm: 0755 | os.ModeSetuid,
|
fm: 0755 | fs.ModeSetuid,
|
||||||
}, {
|
}, {
|
||||||
// setguid.
|
// setguid.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -423,7 +418,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360602346, 0),
|
ModTime: time.Unix(1360602346, 0),
|
||||||
Typeflag: TypeReg,
|
Typeflag: TypeReg,
|
||||||
},
|
},
|
||||||
fm: 0750 | os.ModeSetgid,
|
fm: 0750 | fs.ModeSetgid,
|
||||||
}, {
|
}, {
|
||||||
// sticky.
|
// sticky.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -433,7 +428,7 @@ func TestHeaderRoundTrip(t *testing.T) {
|
||||||
ModTime: time.Unix(1360602540, 0),
|
ModTime: time.Unix(1360602540, 0),
|
||||||
Typeflag: TypeReg,
|
Typeflag: TypeReg,
|
||||||
},
|
},
|
||||||
fm: 0600 | os.ModeSticky,
|
fm: 0600 | fs.ModeSticky,
|
||||||
}, {
|
}, {
|
||||||
// hard link.
|
// hard link.
|
||||||
h: &Header{
|
h: &Header{
|
||||||
|
@ -806,9 +801,9 @@ func Benchmark(b *testing.B) {
|
||||||
b.Run(v.label, func(b *testing.B) {
|
b.Run(v.label, func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
// Writing to ioutil.Discard because we want to
|
// Writing to io.Discard because we want to
|
||||||
// test purely the writer code and not bring in disk performance into this.
|
// test purely the writer code and not bring in disk performance into this.
|
||||||
tw := NewWriter(ioutil.Discard)
|
tw := NewWriter(io.Discard)
|
||||||
for _, file := range v.files {
|
for _, file := range v.files {
|
||||||
if err := tw.WriteHeader(file.hdr); err != nil {
|
if err := tw.WriteHeader(file.hdr); err != nil {
|
||||||
b.Errorf("unexpected WriteHeader error: %v", err)
|
b.Errorf("unexpected WriteHeader error: %v", err)
|
||||||
|
@ -846,7 +841,7 @@ func Benchmark(b *testing.B) {
|
||||||
if _, err := tr.Next(); err != nil {
|
if _, err := tr.Next(); err != nil {
|
||||||
b.Errorf("unexpected Next error: %v", err)
|
b.Errorf("unexpected Next error: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(ioutil.Discard, tr); err != nil {
|
if _, err := io.Copy(io.Discard, tr); err != nil {
|
||||||
b.Errorf("unexpected Copy error : %v", err)
|
b.Errorf("unexpected Copy error : %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (tw *Writer) Flush() error {
|
||||||
if tw.err != nil {
|
if tw.err != nil {
|
||||||
return tw.err
|
return tw.err
|
||||||
}
|
}
|
||||||
if nb := tw.curr.LogicalRemaining(); nb > 0 {
|
if nb := tw.curr.logicalRemaining(); nb > 0 {
|
||||||
return fmt.Errorf("archive/tar: missed writing %d bytes", nb)
|
return fmt.Errorf("archive/tar: missed writing %d bytes", nb)
|
||||||
}
|
}
|
||||||
if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil {
|
if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil {
|
||||||
|
@ -117,8 +117,8 @@ func (tw *Writer) writeUSTARHeader(hdr *Header) error {
|
||||||
// Pack the main header.
|
// Pack the main header.
|
||||||
var f formatter
|
var f formatter
|
||||||
blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal)
|
blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal)
|
||||||
f.formatString(blk.USTAR().Prefix(), namePrefix)
|
f.formatString(blk.toUSTAR().prefix(), namePrefix)
|
||||||
blk.SetFormat(FormatUSTAR)
|
blk.setFormat(FormatUSTAR)
|
||||||
if f.err != nil {
|
if f.err != nil {
|
||||||
return f.err // Should never happen since header is validated
|
return f.err // Should never happen since header is validated
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
|
||||||
var f formatter // Ignore errors since they are expected
|
var f formatter // Ignore errors since they are expected
|
||||||
fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) }
|
fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) }
|
||||||
blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal)
|
blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal)
|
||||||
blk.SetFormat(FormatPAX)
|
blk.setFormat(FormatPAX)
|
||||||
if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
|
if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -250,10 +250,10 @@ func (tw *Writer) writeGNUHeader(hdr *Header) error {
|
||||||
var spb []byte
|
var spb []byte
|
||||||
blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric)
|
blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric)
|
||||||
if !hdr.AccessTime.IsZero() {
|
if !hdr.AccessTime.IsZero() {
|
||||||
f.formatNumeric(blk.GNU().AccessTime(), hdr.AccessTime.Unix())
|
f.formatNumeric(blk.toGNU().accessTime(), hdr.AccessTime.Unix())
|
||||||
}
|
}
|
||||||
if !hdr.ChangeTime.IsZero() {
|
if !hdr.ChangeTime.IsZero() {
|
||||||
f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix())
|
f.formatNumeric(blk.toGNU().changeTime(), hdr.ChangeTime.Unix())
|
||||||
}
|
}
|
||||||
// TODO(dsnet): Re-enable this when adding sparse support.
|
// TODO(dsnet): Re-enable this when adding sparse support.
|
||||||
// See https://golang.org/issue/22735
|
// See https://golang.org/issue/22735
|
||||||
|
@ -293,7 +293,7 @@ func (tw *Writer) writeGNUHeader(hdr *Header) error {
|
||||||
f.formatNumeric(blk.GNU().RealSize(), realSize)
|
f.formatNumeric(blk.GNU().RealSize(), realSize)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
blk.SetFormat(FormatGNU)
|
blk.setFormat(FormatGNU)
|
||||||
if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
|
if err := tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -321,28 +321,28 @@ type (
|
||||||
// The block returned is only valid until the next call to
|
// The block returned is only valid until the next call to
|
||||||
// templateV7Plus or writeRawFile.
|
// templateV7Plus or writeRawFile.
|
||||||
func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block {
|
func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block {
|
||||||
tw.blk.Reset()
|
tw.blk.reset()
|
||||||
|
|
||||||
modTime := hdr.ModTime
|
modTime := hdr.ModTime
|
||||||
if modTime.IsZero() {
|
if modTime.IsZero() {
|
||||||
modTime = time.Unix(0, 0)
|
modTime = time.Unix(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
v7 := tw.blk.V7()
|
v7 := tw.blk.toV7()
|
||||||
v7.TypeFlag()[0] = hdr.Typeflag
|
v7.typeFlag()[0] = hdr.Typeflag
|
||||||
fmtStr(v7.Name(), hdr.Name)
|
fmtStr(v7.name(), hdr.Name)
|
||||||
fmtStr(v7.LinkName(), hdr.Linkname)
|
fmtStr(v7.linkName(), hdr.Linkname)
|
||||||
fmtNum(v7.Mode(), hdr.Mode)
|
fmtNum(v7.mode(), hdr.Mode)
|
||||||
fmtNum(v7.UID(), int64(hdr.Uid))
|
fmtNum(v7.uid(), int64(hdr.Uid))
|
||||||
fmtNum(v7.GID(), int64(hdr.Gid))
|
fmtNum(v7.gid(), int64(hdr.Gid))
|
||||||
fmtNum(v7.Size(), hdr.Size)
|
fmtNum(v7.size(), hdr.Size)
|
||||||
fmtNum(v7.ModTime(), modTime.Unix())
|
fmtNum(v7.modTime(), modTime.Unix())
|
||||||
|
|
||||||
ustar := tw.blk.USTAR()
|
ustar := tw.blk.toUSTAR()
|
||||||
fmtStr(ustar.UserName(), hdr.Uname)
|
fmtStr(ustar.userName(), hdr.Uname)
|
||||||
fmtStr(ustar.GroupName(), hdr.Gname)
|
fmtStr(ustar.groupName(), hdr.Gname)
|
||||||
fmtNum(ustar.DevMajor(), hdr.Devmajor)
|
fmtNum(ustar.devMajor(), hdr.Devmajor)
|
||||||
fmtNum(ustar.DevMinor(), hdr.Devminor)
|
fmtNum(ustar.devMinor(), hdr.Devminor)
|
||||||
|
|
||||||
return &tw.blk
|
return &tw.blk
|
||||||
}
|
}
|
||||||
|
@ -351,7 +351,7 @@ func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum num
|
||||||
// It uses format to encode the header format and will write data as the body.
|
// 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).
|
// 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 {
|
func (tw *Writer) writeRawFile(name, data string, flag byte, format Format) error {
|
||||||
tw.blk.Reset()
|
tw.blk.reset()
|
||||||
|
|
||||||
// Best effort for the filename.
|
// Best effort for the filename.
|
||||||
name = toASCII(name)
|
name = toASCII(name)
|
||||||
|
@ -361,15 +361,15 @@ func (tw *Writer) writeRawFile(name, data string, flag byte, format Format) erro
|
||||||
name = strings.TrimRight(name, "/")
|
name = strings.TrimRight(name, "/")
|
||||||
|
|
||||||
var f formatter
|
var f formatter
|
||||||
v7 := tw.blk.V7()
|
v7 := tw.blk.toV7()
|
||||||
v7.TypeFlag()[0] = flag
|
v7.typeFlag()[0] = flag
|
||||||
f.formatString(v7.Name(), name)
|
f.formatString(v7.name(), name)
|
||||||
f.formatOctal(v7.Mode(), 0)
|
f.formatOctal(v7.mode(), 0)
|
||||||
f.formatOctal(v7.UID(), 0)
|
f.formatOctal(v7.uid(), 0)
|
||||||
f.formatOctal(v7.GID(), 0)
|
f.formatOctal(v7.gid(), 0)
|
||||||
f.formatOctal(v7.Size(), int64(len(data))) // Must be < 8GiB
|
f.formatOctal(v7.size(), int64(len(data))) // Must be < 8GiB
|
||||||
f.formatOctal(v7.ModTime(), 0)
|
f.formatOctal(v7.modTime(), 0)
|
||||||
tw.blk.SetFormat(format)
|
tw.blk.setFormat(format)
|
||||||
if f.err != nil {
|
if f.err != nil {
|
||||||
return f.err // Only occurs if size condition is violated
|
return f.err // Only occurs if size condition is violated
|
||||||
}
|
}
|
||||||
|
@ -511,10 +511,13 @@ func (fw *regFileWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||||
return io.Copy(struct{ io.Writer }{fw}, r)
|
return io.Copy(struct{ io.Writer }{fw}, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fw regFileWriter) LogicalRemaining() int64 {
|
// logicalRemaining implements fileState.logicalRemaining.
|
||||||
|
func (fw regFileWriter) logicalRemaining() int64 {
|
||||||
return fw.nb
|
return fw.nb
|
||||||
}
|
}
|
||||||
func (fw regFileWriter) PhysicalRemaining() int64 {
|
|
||||||
|
// logicalRemaining implements fileState.physicalRemaining.
|
||||||
|
func (fw regFileWriter) physicalRemaining() int64 {
|
||||||
return fw.nb
|
return fw.nb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,9 +529,9 @@ type sparseFileWriter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sw *sparseFileWriter) Write(b []byte) (n int, err error) {
|
func (sw *sparseFileWriter) Write(b []byte) (n int, err error) {
|
||||||
overwrite := int64(len(b)) > sw.LogicalRemaining()
|
overwrite := int64(len(b)) > sw.logicalRemaining()
|
||||||
if overwrite {
|
if overwrite {
|
||||||
b = b[:sw.LogicalRemaining()]
|
b = b[:sw.logicalRemaining()]
|
||||||
}
|
}
|
||||||
|
|
||||||
b0 := b
|
b0 := b
|
||||||
|
@ -556,7 +559,7 @@ func (sw *sparseFileWriter) Write(b []byte) (n int, err error) {
|
||||||
return n, errMissData // Not possible; implies bug in validation logic
|
return n, errMissData // Not possible; implies bug in validation logic
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return n, err
|
return n, err
|
||||||
case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0:
|
case sw.logicalRemaining() == 0 && sw.physicalRemaining() > 0:
|
||||||
return n, errUnrefData // Not possible; implies bug in validation logic
|
return n, errUnrefData // Not possible; implies bug in validation logic
|
||||||
case overwrite:
|
case overwrite:
|
||||||
return n, ErrWriteTooLong
|
return n, ErrWriteTooLong
|
||||||
|
@ -578,12 +581,12 @@ func (sw *sparseFileWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
|
||||||
var readLastByte bool
|
var readLastByte bool
|
||||||
pos0 := sw.pos
|
pos0 := sw.pos
|
||||||
for sw.LogicalRemaining() > 0 && !readLastByte && err == nil {
|
for sw.logicalRemaining() > 0 && !readLastByte && err == nil {
|
||||||
var nf int64 // Size of fragment
|
var nf int64 // Size of fragment
|
||||||
dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset()
|
dataStart, dataEnd := sw.sp[0].Offset, sw.sp[0].endOffset()
|
||||||
if sw.pos < dataStart { // In a hole fragment
|
if sw.pos < dataStart { // In a hole fragment
|
||||||
nf = dataStart - sw.pos
|
nf = dataStart - sw.pos
|
||||||
if sw.PhysicalRemaining() == 0 {
|
if sw.physicalRemaining() == 0 {
|
||||||
readLastByte = true
|
readLastByte = true
|
||||||
nf--
|
nf--
|
||||||
}
|
}
|
||||||
|
@ -613,18 +616,18 @@ func (sw *sparseFileWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
return n, errMissData // Not possible; implies bug in validation logic
|
return n, errMissData // Not possible; implies bug in validation logic
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return n, err
|
return n, err
|
||||||
case sw.LogicalRemaining() == 0 && sw.PhysicalRemaining() > 0:
|
case sw.logicalRemaining() == 0 && sw.physicalRemaining() > 0:
|
||||||
return n, errUnrefData // Not possible; implies bug in validation logic
|
return n, errUnrefData // Not possible; implies bug in validation logic
|
||||||
default:
|
default:
|
||||||
return n, ensureEOF(rs)
|
return n, ensureEOF(rs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sw sparseFileWriter) LogicalRemaining() int64 {
|
func (sw sparseFileWriter) logicalRemaining() int64 {
|
||||||
return sw.sp[len(sw.sp)-1].endOffset() - sw.pos
|
return sw.sp[len(sw.sp)-1].endOffset() - sw.pos
|
||||||
}
|
}
|
||||||
func (sw sparseFileWriter) PhysicalRemaining() int64 {
|
func (sw sparseFileWriter) physicalRemaining() int64 {
|
||||||
return sw.fw.PhysicalRemaining()
|
return sw.fw.physicalRemaining()
|
||||||
}
|
}
|
||||||
|
|
||||||
// zeroWriter may only be written with NULs, otherwise it returns errWriteHole.
|
// zeroWriter may only be written with NULs, otherwise it returns errWriteHole.
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -68,7 +67,7 @@ func TestWriter(t *testing.T) {
|
||||||
testClose struct { // Close() == wantErr
|
testClose struct { // Close() == wantErr
|
||||||
wantErr error
|
wantErr error
|
||||||
}
|
}
|
||||||
testFnc interface{} // testHeader | testWrite | testReadFrom | testClose
|
testFnc any // testHeader | testWrite | testReadFrom | testClose
|
||||||
)
|
)
|
||||||
|
|
||||||
vectors := []struct {
|
vectors := []struct {
|
||||||
|
@ -520,7 +519,7 @@ func TestWriter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.file != "" {
|
if v.file != "" {
|
||||||
want, err := ioutil.ReadFile(v.file)
|
want, err := os.ReadFile(v.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ReadFile() = %v, want nil", err)
|
t.Fatalf("ReadFile() = %v, want nil", err)
|
||||||
}
|
}
|
||||||
|
@ -988,11 +987,9 @@ func TestIssue12594(t *testing.T) {
|
||||||
// The prefix field should never appear in the GNU format.
|
// The prefix field should never appear in the GNU format.
|
||||||
var blk block
|
var blk block
|
||||||
copy(blk[:], b.Bytes())
|
copy(blk[:], b.Bytes())
|
||||||
prefix := string(blk.USTAR().Prefix())
|
prefix := string(blk.toUSTAR().prefix())
|
||||||
if i := strings.IndexByte(prefix, 0); i >= 0 {
|
prefix, _, _ = strings.Cut(prefix, "\x00") // Truncate at the NUL terminator
|
||||||
prefix = prefix[:i] // Truncate at the NUL terminator
|
if blk.getFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
|
||||||
}
|
|
||||||
if blk.GetFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
|
|
||||||
t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
|
t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1030,11 +1027,11 @@ func TestFileWriter(t *testing.T) {
|
||||||
wantCnt int64
|
wantCnt int64
|
||||||
wantErr error
|
wantErr error
|
||||||
}
|
}
|
||||||
testRemaining struct { // LogicalRemaining() == wantLCnt, PhysicalRemaining() == wantPCnt
|
testRemaining struct { // logicalRemaining() == wantLCnt, physicalRemaining() == wantPCnt
|
||||||
wantLCnt int64
|
wantLCnt int64
|
||||||
wantPCnt int64
|
wantPCnt int64
|
||||||
}
|
}
|
||||||
testFnc interface{} // testWrite | testReadFrom | testRemaining
|
testFnc any // testWrite | testReadFrom | testRemaining
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -1047,7 +1044,7 @@ func TestFileWriter(t *testing.T) {
|
||||||
sph sparseHoles
|
sph sparseHoles
|
||||||
size int64
|
size int64
|
||||||
}
|
}
|
||||||
fileMaker interface{} // makeReg | makeSparse
|
fileMaker any // makeReg | makeSparse
|
||||||
)
|
)
|
||||||
|
|
||||||
vectors := []struct {
|
vectors := []struct {
|
||||||
|
@ -1293,11 +1290,11 @@ func TestFileWriter(t *testing.T) {
|
||||||
t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
|
t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
|
||||||
}
|
}
|
||||||
case testRemaining:
|
case testRemaining:
|
||||||
if got := fw.LogicalRemaining(); got != tf.wantLCnt {
|
if got := fw.logicalRemaining(); got != tf.wantLCnt {
|
||||||
t.Errorf("test %d.%d, LogicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
|
t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
|
||||||
}
|
}
|
||||||
if got := fw.PhysicalRemaining(); got != tf.wantPCnt {
|
if got := fw.physicalRemaining(); got != tf.wantPCnt {
|
||||||
t.Errorf("test %d.%d, PhysicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
|
t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
|
t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue