Skip to content

Commit 2bcc24e

Browse files
committed
archive/tar: support PAX subsecond resolution times
Add support for PAX subsecond resolution times. Since the parser supports negative timestamps, the formatter also handles negative timestamps. The relevant PAX specification is: <<< Portable file timestamps cannot be negative. If pax encounters a file with a negative timestamp in copy or write mode, it can reject the file, substitute a non-negative timestamp, or generate a non-portable timestamp with a leading '-'. >>> <<< All of these time records shall be formatted as a decimal representation of the time in seconds since the Epoch. If a <period> ( '.' ) decimal point character is present, the digits to the right of the point shall represent the units of a subsecond timing granularity, where the first digit is tenths of a second and each subsequent digit is a tenth of the previous digit. >>> Fixes #11171 Change-Id: Ied108f3d2654390bc1b0ddd66a4081c2b83e490b Reviewed-on: https://go-review.googlesource.com/55552 Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
1 parent fcf445d commit 2bcc24e

File tree

4 files changed

+68
-11
lines changed

4 files changed

+68
-11
lines changed

src/archive/tar/common.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ func (h *Header) allowedFormats() (format int, paxHdrs map[string]string) {
124124
if paxKey == paxNone {
125125
format &^= formatPAX // No PAX
126126
} else {
127-
// TODO(dsnet): Support PAX time here.
128-
// paxHdrs[paxKey] = formatPAXTime(ts)
127+
paxHdrs[paxKey] = formatPAXTime(ts)
129128
}
130129
}
131130
}

src/archive/tar/strconv.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package tar
66

77
import (
88
"bytes"
9+
"fmt"
910
"strconv"
1011
"strings"
1112
"time"
@@ -218,7 +219,23 @@ func parsePAXTime(s string) (time.Time, error) {
218219
return time.Unix(secs, int64(nsecs)), nil
219220
}
220221

221-
// TODO(dsnet): Implement formatPAXTime.
222+
// formatPAXTime converts ts into a time of the form %d.%d as described in the
223+
// PAX specification. This function is capable of negative timestamps.
224+
func formatPAXTime(ts time.Time) (s string) {
225+
secs, nsecs := ts.Unix(), ts.Nanosecond()
226+
if nsecs == 0 {
227+
return strconv.FormatInt(secs, 10)
228+
}
229+
230+
// If seconds is negative, then perform correction.
231+
sign := ""
232+
if secs < 0 {
233+
sign = "-" // Remember sign
234+
secs = -(secs + 1) // Add a second to secs
235+
nsecs = -(nsecs - 1E9) // Take that second away from nsecs
236+
}
237+
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
238+
}
222239

223240
// parsePAXRecord parses the input PAX record string into a key-value pair.
224241
// If parsing is successful, it will slice off the currently read record and

src/archive/tar/strconv_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,51 @@ func TestParsePAXTime(t *testing.T) {
294294
}
295295
}
296296

297+
func TestFormatPAXTime(t *testing.T) {
298+
vectors := []struct {
299+
sec, nsec int64
300+
want string
301+
}{
302+
{1350244992, 0, "1350244992"},
303+
{1350244992, 300000000, "1350244992.3"},
304+
{1350244992, 23960100, "1350244992.0239601"},
305+
{1350244992, 23960108, "1350244992.023960108"},
306+
{+1, +1E9 - 1E0, "1.999999999"},
307+
{+1, +1E9 - 1E3, "1.999999"},
308+
{+1, +1E9 - 1E6, "1.999"},
309+
{+1, +0E0 - 0E0, "1"},
310+
{+1, +1E6 - 0E0, "1.001"},
311+
{+1, +1E3 - 0E0, "1.000001"},
312+
{+1, +1E0 - 0E0, "1.000000001"},
313+
{0, 1E9 - 1E0, "0.999999999"},
314+
{0, 1E9 - 1E3, "0.999999"},
315+
{0, 1E9 - 1E6, "0.999"},
316+
{0, 0E0, "0"},
317+
{0, 1E6 + 0E0, "0.001"},
318+
{0, 1E3 + 0E0, "0.000001"},
319+
{0, 1E0 + 0E0, "0.000000001"},
320+
{-1, -1E9 + 1E0, "-1.999999999"},
321+
{-1, -1E9 + 1E3, "-1.999999"},
322+
{-1, -1E9 + 1E6, "-1.999"},
323+
{-1, -0E0 + 0E0, "-1"},
324+
{-1, -1E6 + 0E0, "-1.001"},
325+
{-1, -1E3 + 0E0, "-1.000001"},
326+
{-1, -1E0 + 0E0, "-1.000000001"},
327+
{-1350244992, 0, "-1350244992"},
328+
{-1350244992, -300000000, "-1350244992.3"},
329+
{-1350244992, -23960100, "-1350244992.0239601"},
330+
{-1350244992, -23960108, "-1350244992.023960108"},
331+
}
332+
333+
for _, v := range vectors {
334+
got := formatPAXTime(time.Unix(v.sec, v.nsec))
335+
if got != v.want {
336+
t.Errorf("formatPAXTime(%ds, %dns): got %q, want %q",
337+
v.sec, v.nsec, got, v.want)
338+
}
339+
}
340+
}
341+
297342
func TestParsePAXRecord(t *testing.T) {
298343
medName := strings.Repeat("CD", 50)
299344
longName := strings.Repeat("AB", 100)

src/archive/tar/writer.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,13 @@ func (tw *Writer) WriteHeader(hdr *Header) error {
6767
return err
6868
}
6969

70-
// TODO(dsnet): Add PAX timestamps with nanosecond support.
71-
hdrCpy := *hdr
72-
hdrCpy.ModTime = hdrCpy.ModTime.Truncate(time.Second)
73-
74-
switch allowedFormats, paxHdrs := hdrCpy.allowedFormats(); {
70+
switch allowedFormats, paxHdrs := hdr.allowedFormats(); {
7571
case allowedFormats&formatUSTAR != 0:
76-
return tw.writeUSTARHeader(&hdrCpy)
72+
return tw.writeUSTARHeader(hdr)
7773
case allowedFormats&formatPAX != 0:
78-
return tw.writePAXHeader(&hdrCpy, paxHdrs)
74+
return tw.writePAXHeader(hdr, paxHdrs)
7975
case allowedFormats&formatGNU != 0:
80-
return tw.writeGNUHeader(&hdrCpy)
76+
return tw.writeGNUHeader(hdr)
8177
default:
8278
return ErrHeader
8379
}

0 commit comments

Comments
 (0)