Uname string // User name of owner
Gname string // Group name of owner
+ // The PAX format encodes the timestamps with sub-second resolution,
+ // while the other formats (USTAR and GNU) truncate to the nearest second.
+ // If the Format is unspecified, then Writer.WriteHeader ignores
+ // AccessTime and ChangeTime when using the USTAR format.
ModTime time.Time // Modification time
AccessTime time.Time // Access time (requires either PAX or GNU support)
ChangeTime time.Time // Change time (requires either PAX or GNU support)
// Since the Reader liberally reads some non-compliant files,
// it is possible for this to be FormatUnknown.
//
- // When Writer.WriteHeader is called, if this is FormatUnknown,
- // then it tries to encode the header in the order of USTAR, PAX, then GNU.
- // Otherwise, it tries to use the specified format.
+ // If the format is unspecified when Writer.WriteHeader is called,
+ // then it uses the first format (in the order of USTAR, PAX, GNU)
+ // capable of encoding this Header (see Format).
Format Format
}
paxHdrs = make(map[string]string)
var whyNoUSTAR, whyNoPAX, whyNoGNU string
+ var preferPAX bool // Prefer PAX over USTAR
verifyString := func(s string, size int, name, paxKey string) {
// NUL-terminator is optional for path and linkpath.
// Technically, it is required for uname and gname,
if ts.IsZero() {
return // Always okay
}
- needsNano := ts.Nanosecond() != 0
- hasFieldUSTAR := paxKey == paxMtime
- if !fitsInBase256(size, ts.Unix()) || needsNano {
+ if !fitsInBase256(size, ts.Unix()) {
whyNoGNU = fmt.Sprintf("GNU cannot encode %s=%v", name, ts)
format.mustNotBe(FormatGNU)
}
- if !fitsInOctal(size, ts.Unix()) || needsNano || !hasFieldUSTAR {
+ isMtime := paxKey == paxMtime
+ fitsOctal := fitsInOctal(size, ts.Unix())
+ noACTime := !isMtime && h.Format != FormatUnknown
+ if (isMtime && !fitsOctal) || noACTime {
whyNoUSTAR = fmt.Sprintf("USTAR cannot encode %s=%v", name, ts)
format.mustNotBe(FormatUSTAR)
+ }
+ needsNano := ts.Nanosecond() != 0
+ if !isMtime || !fitsOctal || needsNano {
+ preferPAX = true // USTAR may truncate sub-second measurements
if paxKey == paxNone {
whyNoPAX = fmt.Sprintf("PAX cannot encode %s=%v", name, ts)
format.mustNotBe(FormatPAX)
// Check desired format.
if wantFormat := h.Format; wantFormat != FormatUnknown {
- if wantFormat.has(FormatPAX) {
+ if wantFormat.has(FormatPAX) && !preferPAX {
wantFormat.mayBe(FormatUSTAR) // PAX implies USTAR allowed too
}
format.mayOnlyBe(wantFormat) // Set union of formats allowed and format wanted
import "strings"
+// Format represents the tar archive format.
+//
+// The original tar format was introduced in Unix V7.
+// Since then, there have been multiple competing formats attempting to
+// standardize or extend the V7 format to overcome its limitations.
+// The most common formats are the USTAR, PAX, and GNU formats,
+// each with their own advantages and limitations.
+//
+// The following table captures the capabilities of each format:
+//
+// | USTAR | PAX | GNU
+// ------------------+--------+-----------+----------
+// Name | 256B | unlimited | unlimited
+// Linkname | 100B | unlimited | unlimited
+// Size | uint33 | unlimited | uint89
+// Mode | uint21 | uint21 | uint57
+// Uid/Gid | uint21 | unlimited | uint57
+// Uname/Gname | 32B | unlimited | 32B
+// ModTime | uint33 | unlimited | int89
+// AccessTime | n/a | unlimited | int89
+// ChangeTime | n/a | unlimited | int89
+// Devmajor/Devminor | uint21 | uint21 | uint57
+// ------------------+--------+-----------+----------
+// string encoding | ASCII | UTF-8 | binary
+// sub-second times | no | yes | no
+// sparse files | no | yes | yes
+//
+// The table's upper portion shows the Header fields, where each format reports
+// the maximum number of bytes allowed for each string field and
+// the integer type used to store each numeric field
+// (where timestamps are stored as the number of seconds since the Unix epoch).
+//
+// The table's lower portion shows specialized features of each format,
+// such as supported string encodings, support for sub-second timestamps,
+// or support for sparse files.
type Format int
// Constants to identify various tar formats.
header: &Header{ModTime: time.Unix(-1, 0)},
paxHdrs: map[string]string{paxMtime: "-1"},
formats: FormatPAX | FormatGNU,
+ }, {
+ header: &Header{ModTime: time.Unix(1, 500)},
+ paxHdrs: map[string]string{paxMtime: "1.0000005"},
+ formats: FormatUSTAR | FormatPAX | FormatGNU,
+ }, {
+ header: &Header{ModTime: time.Unix(1, 0)},
+ formats: FormatUSTAR | FormatPAX | FormatGNU,
+ }, {
+ header: &Header{ModTime: time.Unix(1, 0), Format: FormatPAX},
+ formats: FormatUSTAR | FormatPAX,
+ }, {
+ header: &Header{ModTime: time.Unix(1, 500), Format: FormatUSTAR},
+ paxHdrs: map[string]string{paxMtime: "1.0000005"},
+ formats: FormatUSTAR,
+ }, {
+ header: &Header{ModTime: time.Unix(1, 500), Format: FormatPAX},
+ paxHdrs: map[string]string{paxMtime: "1.0000005"},
+ formats: FormatPAX,
+ }, {
+ header: &Header{ModTime: time.Unix(1, 500), Format: FormatGNU},
+ paxHdrs: map[string]string{paxMtime: "1.0000005"},
+ formats: FormatGNU,
}, {
header: &Header{ModTime: time.Unix(-1, 500)},
paxHdrs: map[string]string{paxMtime: "-0.9999995"},
- formats: FormatPAX,
+ formats: FormatPAX | FormatGNU,
}, {
header: &Header{ModTime: time.Unix(-1, 500), Format: FormatGNU},
paxHdrs: map[string]string{paxMtime: "-0.9999995"},
- formats: FormatUnknown,
+ formats: FormatGNU,
}, {
header: &Header{AccessTime: time.Unix(0, 0)},
paxHdrs: map[string]string{paxAtime: "0"},
- formats: FormatPAX | FormatGNU,
+ formats: FormatUSTAR | FormatPAX | FormatGNU,
+ }, {
+ header: &Header{AccessTime: time.Unix(0, 0), Format: FormatUSTAR},
+ paxHdrs: map[string]string{paxAtime: "0"},
+ formats: FormatUnknown,
+ }, {
+ header: &Header{AccessTime: time.Unix(0, 0), Format: FormatPAX},
+ paxHdrs: map[string]string{paxAtime: "0"},
+ formats: FormatPAX,
+ }, {
+ header: &Header{AccessTime: time.Unix(0, 0), Format: FormatGNU},
+ paxHdrs: map[string]string{paxAtime: "0"},
+ formats: FormatGNU,
}, {
header: &Header{AccessTime: time.Unix(-123, 0)},
paxHdrs: map[string]string{paxAtime: "-123"},
- formats: FormatPAX | FormatGNU,
+ formats: FormatUSTAR | FormatPAX | FormatGNU,
+ }, {
+ header: &Header{AccessTime: time.Unix(-123, 0), Format: FormatPAX},
+ paxHdrs: map[string]string{paxAtime: "-123"},
+ formats: FormatPAX,
}, {
header: &Header{ChangeTime: time.Unix(123, 456)},
paxHdrs: map[string]string{paxCtime: "123.000000456"},
- formats: FormatPAX,
+ formats: FormatUSTAR | FormatPAX | FormatGNU,
}, {
- header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
+ header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatUSTAR},
paxHdrs: map[string]string{paxCtime: "123.000000456"},
formats: FormatUnknown,
+ }, {
+ header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatGNU},
+ paxHdrs: map[string]string{paxCtime: "123.000000456"},
+ formats: FormatGNU,
+ }, {
+ header: &Header{ChangeTime: time.Unix(123, 456), Format: FormatPAX},
+ paxHdrs: map[string]string{paxCtime: "123.000000456"},
+ formats: FormatPAX,
}, {
header: &Header{Name: "sparse.db", Size: 1000, SparseHoles: []SparseEntry{{0, 500}}},
formats: FormatPAX,