]> Cypherpunks repositories - gostls13.git/commitdiff
archive/tar: support PAX subsecond resolution times
authorJoe Tsai <joetsai@digital-static.net>
Mon, 14 Aug 2017 22:24:31 +0000 (15:24 -0700)
committerJoe Tsai <thebrokentoaster@gmail.com>
Tue, 15 Aug 2017 02:20:22 +0000 (02:20 +0000)
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>
src/archive/tar/common.go
src/archive/tar/strconv.go
src/archive/tar/strconv_test.go
src/archive/tar/writer.go

index 5b7bbe5a1e26b7abcb33ce551341e843d045db0a..5b921486f1f9f9cd237e21b09e3d71f7be342919 100644 (file)
@@ -124,8 +124,7 @@ func (h *Header) allowedFormats() (format int, paxHdrs map[string]string) {
                        if paxKey == paxNone {
                                format &^= formatPAX // No PAX
                        } else {
-                               // TODO(dsnet): Support PAX time here.
-                               // paxHdrs[paxKey] = formatPAXTime(ts)
+                               paxHdrs[paxKey] = formatPAXTime(ts)
                        }
                }
        }
index 6aad6805aa141f0c5d9b7206676a91a2213859f9..a93fc4ac7a115bf698ea09ab61d900be4a539e78 100644 (file)
@@ -6,6 +6,7 @@ package tar
 
 import (
        "bytes"
+       "fmt"
        "strconv"
        "strings"
        "time"
@@ -218,7 +219,23 @@ func parsePAXTime(s string) (time.Time, error) {
        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.
 // If parsing is successful, it will slice off the currently read record and
index e2527dc61f7589d9914e04d9b6abd7f7802f36df..7156368ede5884b7b9f5e9b51acf7e8a8b00b23d 100644 (file)
@@ -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) {
        medName := strings.Repeat("CD", 50)
        longName := strings.Repeat("AB", 100)
index d88e5ef340ce6c0749ba4f278798114c094fbf7b..7d393b46dfcf20af01a7c0779cac8b21bf96affa 100644 (file)
@@ -67,17 +67,13 @@ func (tw *Writer) WriteHeader(hdr *Header) error {
                return err
        }
 
-       // TODO(dsnet): Add PAX timestamps with nanosecond support.
-       hdrCpy := *hdr
-       hdrCpy.ModTime = hdrCpy.ModTime.Truncate(time.Second)
-
-       switch allowedFormats, paxHdrs := hdrCpy.allowedFormats(); {
+       switch allowedFormats, paxHdrs := hdr.allowedFormats(); {
        case allowedFormats&formatUSTAR != 0:
-               return tw.writeUSTARHeader(&hdrCpy)
+               return tw.writeUSTARHeader(hdr)
        case allowedFormats&formatPAX != 0:
-               return tw.writePAXHeader(&hdrCpy, paxHdrs)
+               return tw.writePAXHeader(hdr, paxHdrs)
        case allowedFormats&formatGNU != 0:
-               return tw.writeGNUHeader(&hdrCpy)
+               return tw.writeGNUHeader(hdr)
        default:
                return ErrHeader
        }