From d0f7e8cb8b7904aba3846d7ebee5cf840fcfb599 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Tue, 1 Nov 2016 17:27:39 +1100 Subject: [PATCH] keywords: correctly handle time keywords Previously, the time generation code would inexplicably drop parts of the nanotime -- potentially causing validation to succeed when it should fail. This was probably do to a bug in the remainder logic, but instead we should be using .Nanosecond() anyway. After changing the time of a file with a test case like this: // Change the time to something known with nanosec != 0. chtime := time.Unix(100, 987654321) if err := os.Chtimes("somefile", chtime, chtime); err != nil { // panic } timeKeywordFunc() would return the wrong value (time=100.000000021). This fixes the issue and adds a test case. Signed-off-by: Aleksa Sarai --- keywords.go | 10 ++--- keywords_test.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 keywords_test.go diff --git a/keywords.go b/keywords.go index ffc01ba..3faad1d 100644 --- a/keywords.go +++ b/keywords.go @@ -287,14 +287,12 @@ var ( } } tartimeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) { - return fmt.Sprintf("tar_time=%d.000000000", info.ModTime().Unix()), nil + return fmt.Sprintf("tar_time=%d.%9.9d", info.ModTime().Unix(), 0), nil } timeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) { - t := info.ModTime().UnixNano() - if t == 0 { - return "time=0.000000000", nil - } - return fmt.Sprintf("time=%d.%9.9d", (t / 1e9), (t % (t / 1e9))), nil + tSec := info.ModTime().Unix() + tNano := info.ModTime().Nanosecond() + return fmt.Sprintf("time=%d.%9.9d", tSec, tNano), nil } linkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (string, error) { if sys, ok := info.Sys().(*tar.Header); ok { diff --git a/keywords_test.go b/keywords_test.go new file mode 100644 index 0000000..5860fc0 --- /dev/null +++ b/keywords_test.go @@ -0,0 +1,95 @@ +package mtree + +import ( + "fmt" + "os" + "testing" + "time" +) + +type fakeFileInfo struct { + mtime time.Time +} + +func (ffi fakeFileInfo) Name() string { + // noop + return "" +} + +func (ffi fakeFileInfo) Size() int64 { + // noop + return -1 +} + +func (ffi fakeFileInfo) Mode() os.FileMode { + // noop + return 0 +} + +func (ffi fakeFileInfo) ModTime() time.Time { + return ffi.mtime +} + +func (ffi fakeFileInfo) IsDir() bool { + return ffi.Mode().IsDir() +} + +func (ffi fakeFileInfo) Sys() interface{} { + // noop + return nil +} + +func TestKeywordsTimeNano(t *testing.T) { + // We have to make sure that timeKeywordFunc always returns the correct + // formatting with regards to the nanotime. + + for _, test := range []struct { + sec, nsec int64 + }{ + {1234, 123456789}, + {5555, 987654321}, + {1337, 100000000}, + {8888, 999999999}, + {144123582122, 1}, + {857125628319, 0}, + } { + mtime := time.Unix(test.sec, test.nsec) + expected := fmt.Sprintf("time=%d.%9.9d", test.sec, test.nsec) + got, err := timeKeywordFunc("", fakeFileInfo{ + mtime: mtime, + }, nil) + if err != nil { + t.Errorf("unexpected error while parsing '%q': %q", mtime, err) + } + if expected != got { + t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got) + } + } +} + +func TestKeywordsTimeTar(t *testing.T) { + // tartimeKeywordFunc always has nsec = 0. + + for _, test := range []struct { + sec, nsec int64 + }{ + {1234, 123456789}, + {5555, 987654321}, + {1337, 100000000}, + {8888, 999999999}, + {144123582122, 1}, + {857125628319, 0}, + } { + mtime := time.Unix(test.sec, test.nsec) + expected := fmt.Sprintf("tar_time=%d.%9.9d", test.sec, 0) + got, err := tartimeKeywordFunc("", fakeFileInfo{ + mtime: mtime, + }, nil) + if err != nil { + t.Errorf("unexpected error while parsing '%q': %q", mtime, err) + } + if expected != got { + t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got) + } + } +}