Commit 2bcc24e9 authored by Joe Tsai's avatar Joe Tsai Committed by Joe Tsai

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: 's avatarIan Lance Taylor <iant@golang.org>
parent fcf445dc
...@@ -124,8 +124,7 @@ func (h *Header) allowedFormats() (format int, paxHdrs map[string]string) { ...@@ -124,8 +124,7 @@ func (h *Header) allowedFormats() (format int, paxHdrs map[string]string) {
if paxKey == paxNone { if paxKey == paxNone {
format &^= formatPAX // No PAX format &^= formatPAX // No PAX
} else { } else {
// TODO(dsnet): Support PAX time here. paxHdrs[paxKey] = formatPAXTime(ts)
// paxHdrs[paxKey] = formatPAXTime(ts)
} }
} }
} }
......
...@@ -6,6 +6,7 @@ package tar ...@@ -6,6 +6,7 @@ package tar
import ( import (
"bytes" "bytes"
"fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
...@@ -218,7 +219,23 @@ func parsePAXTime(s string) (time.Time, error) { ...@@ -218,7 +219,23 @@ func parsePAXTime(s string) (time.Time, error) {
return time.Unix(secs, int64(nsecs)), nil return time.Unix(secs, int64(nsecs)), nil
} }
// TODO(dsnet): Implement formatPAXTime. // formatPAXTime converts ts into a time of the form %d.%d as described in the
// PAX specification. This function is capable of negative timestamps.
func formatPAXTime(ts time.Time) (s string) {
secs, nsecs := ts.Unix(), ts.Nanosecond()
if nsecs == 0 {
return strconv.FormatInt(secs, 10)
}
// If seconds is negative, then perform correction.
sign := ""
if secs < 0 {
sign = "-" // Remember sign
secs = -(secs + 1) // Add a second to secs
nsecs = -(nsecs - 1E9) // Take that second away from nsecs
}
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
}
// parsePAXRecord parses the input PAX record string into a key-value pair. // parsePAXRecord parses the input PAX record string into a key-value pair.
// If parsing is successful, it will slice off the currently read record and // If parsing is successful, it will slice off the currently read record and
......
...@@ -294,6 +294,51 @@ func TestParsePAXTime(t *testing.T) { ...@@ -294,6 +294,51 @@ func TestParsePAXTime(t *testing.T) {
} }
} }
func TestFormatPAXTime(t *testing.T) {
vectors := []struct {
sec, nsec int64
want string
}{
{1350244992, 0, "1350244992"},
{1350244992, 300000000, "1350244992.3"},
{1350244992, 23960100, "1350244992.0239601"},
{1350244992, 23960108, "1350244992.023960108"},
{+1, +1E9 - 1E0, "1.999999999"},
{+1, +1E9 - 1E3, "1.999999"},
{+1, +1E9 - 1E6, "1.999"},
{+1, +0E0 - 0E0, "1"},
{+1, +1E6 - 0E0, "1.001"},
{+1, +1E3 - 0E0, "1.000001"},
{+1, +1E0 - 0E0, "1.000000001"},
{0, 1E9 - 1E0, "0.999999999"},
{0, 1E9 - 1E3, "0.999999"},
{0, 1E9 - 1E6, "0.999"},
{0, 0E0, "0"},
{0, 1E6 + 0E0, "0.001"},
{0, 1E3 + 0E0, "0.000001"},
{0, 1E0 + 0E0, "0.000000001"},
{-1, -1E9 + 1E0, "-1.999999999"},
{-1, -1E9 + 1E3, "-1.999999"},
{-1, -1E9 + 1E6, "-1.999"},
{-1, -0E0 + 0E0, "-1"},
{-1, -1E6 + 0E0, "-1.001"},
{-1, -1E3 + 0E0, "-1.000001"},
{-1, -1E0 + 0E0, "-1.000000001"},
{-1350244992, 0, "-1350244992"},
{-1350244992, -300000000, "-1350244992.3"},
{-1350244992, -23960100, "-1350244992.0239601"},
{-1350244992, -23960108, "-1350244992.023960108"},
}
for _, v := range vectors {
got := formatPAXTime(time.Unix(v.sec, v.nsec))
if got != v.want {
t.Errorf("formatPAXTime(%ds, %dns): got %q, want %q",
v.sec, v.nsec, got, v.want)
}
}
}
func TestParsePAXRecord(t *testing.T) { func TestParsePAXRecord(t *testing.T) {
medName := strings.Repeat("CD", 50) medName := strings.Repeat("CD", 50)
longName := strings.Repeat("AB", 100) longName := strings.Repeat("AB", 100)
......
...@@ -67,17 +67,13 @@ func (tw *Writer) WriteHeader(hdr *Header) error { ...@@ -67,17 +67,13 @@ func (tw *Writer) WriteHeader(hdr *Header) error {
return err return err
} }
// TODO(dsnet): Add PAX timestamps with nanosecond support. switch allowedFormats, paxHdrs := hdr.allowedFormats(); {
hdrCpy := *hdr
hdrCpy.ModTime = hdrCpy.ModTime.Truncate(time.Second)
switch allowedFormats, paxHdrs := hdrCpy.allowedFormats(); {
case allowedFormats&formatUSTAR != 0: case allowedFormats&formatUSTAR != 0:
return tw.writeUSTARHeader(&hdrCpy) return tw.writeUSTARHeader(hdr)
case allowedFormats&formatPAX != 0: case allowedFormats&formatPAX != 0:
return tw.writePAXHeader(&hdrCpy, paxHdrs) return tw.writePAXHeader(hdr, paxHdrs)
case allowedFormats&formatGNU != 0: case allowedFormats&formatGNU != 0:
return tw.writeGNUHeader(&hdrCpy) return tw.writeGNUHeader(hdr)
default: default:
return ErrHeader return ErrHeader
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment