forked from TrueCloudLab/restic
node: Make sure year of all timestamps is valid
Sometimes restic gets bogus timestamps which cannot be converted to JSON, because the stdlib JSON encoder returns an error if the year is not within [0, 9999]. We now make sure that we at least record _some_ timestamp and cap the year either to 0000 or 9999. Before, restic would refuse to save the file at all, so this improves the status quo. This fixes #2174 and #1173
This commit is contained in:
parent
c7762453cf
commit
939f3e972c
3 changed files with 81 additions and 17 deletions
10
changelog/unreleased/issue-2174
Normal file
10
changelog/unreleased/issue-2174
Normal file
|
@ -0,0 +1,10 @@
|
|||
Bugfix: Save files with invalid timestamps
|
||||
|
||||
When restic reads invalid timestamps (year is before 0000 or after 9999) it
|
||||
refused to read and archive the file. We've changed the behavior and will now
|
||||
save modified timestamps with the year set to either 0000 or 9999, the rest of
|
||||
the timestamp stays the same, so the file will be saved (albeit with a bogus
|
||||
timestamp).
|
||||
|
||||
https://github.com/restic/restic/issues/2174
|
||||
https://github.com/restic/restic/issues/1173
|
|
@ -332,24 +332,27 @@ func (node *Node) createFifoAt(path string) error {
|
|||
return mkfifo(path, 0600)
|
||||
}
|
||||
|
||||
// FixTime returns a time.Time which can safely be used to marshal as JSON. If
|
||||
// the timestamp is ealier that year zero, the year is set to zero. In the same
|
||||
// way, if the year is larger than 9999, the year is set to 9999. Other than
|
||||
// the year nothing is changed.
|
||||
func FixTime(t time.Time) time.Time {
|
||||
switch {
|
||||
case t.Year() < 0000:
|
||||
return t.AddDate(-t.Year(), 0, 0)
|
||||
case t.Year() > 9999:
|
||||
return t.AddDate(-(t.Year() - 9999), 0, 0)
|
||||
default:
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func (node Node) MarshalJSON() ([]byte, error) {
|
||||
if node.ModTime.Year() < 0 || node.ModTime.Year() > 9999 {
|
||||
err := errors.Errorf("node %v has invalid ModTime year %d: %v",
|
||||
node.Path, node.ModTime.Year(), node.ModTime)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if node.ChangeTime.Year() < 0 || node.ChangeTime.Year() > 9999 {
|
||||
err := errors.Errorf("node %v has invalid ChangeTime year %d: %v",
|
||||
node.Path, node.ChangeTime.Year(), node.ChangeTime)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if node.AccessTime.Year() < 0 || node.AccessTime.Year() > 9999 {
|
||||
err := errors.Errorf("node %v has invalid AccessTime year %d: %v",
|
||||
node.Path, node.AccessTime.Year(), node.AccessTime)
|
||||
return nil, err
|
||||
}
|
||||
// make sure invalid timestamps for mtime and atime are converted to
|
||||
// something we can actually save.
|
||||
node.ModTime = FixTime(node.ModTime)
|
||||
node.AccessTime = FixTime(node.AccessTime)
|
||||
node.ChangeTime = FixTime(node.ChangeTime)
|
||||
|
||||
type nodeJSON Node
|
||||
nj := nodeJSON(node)
|
||||
|
|
|
@ -244,3 +244,54 @@ func AssertFsTimeEqual(t *testing.T, label string, nodeType string, t1 time.Time
|
|||
|
||||
rtest.Assert(t, equal, "%s: %s doesn't match (%v != %v)", label, nodeType, t1, t2)
|
||||
}
|
||||
|
||||
func parseTimeNano(t testing.TB, s string) time.Time {
|
||||
// 2006-01-02T15:04:05.999999999Z07:00
|
||||
ts, err := time.Parse(time.RFC3339Nano, s)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing %q: %v", s, err)
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
func TestFixTime(t *testing.T) {
|
||||
// load UTC location
|
||||
utc, err := time.LoadLocation("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
src, want time.Time
|
||||
}{
|
||||
{
|
||||
src: parseTimeNano(t, "2006-01-02T15:04:05.999999999+07:00"),
|
||||
want: parseTimeNano(t, "2006-01-02T15:04:05.999999999+07:00"),
|
||||
},
|
||||
{
|
||||
src: time.Date(0, 1, 2, 3, 4, 5, 6, utc),
|
||||
want: parseTimeNano(t, "0000-01-02T03:04:05.000000006+00:00"),
|
||||
},
|
||||
{
|
||||
src: time.Date(-2, 1, 2, 3, 4, 5, 6, utc),
|
||||
want: parseTimeNano(t, "0000-01-02T03:04:05.000000006+00:00"),
|
||||
},
|
||||
{
|
||||
src: time.Date(12345, 1, 2, 3, 4, 5, 6, utc),
|
||||
want: parseTimeNano(t, "9999-01-02T03:04:05.000000006+00:00"),
|
||||
},
|
||||
{
|
||||
src: time.Date(9999, 1, 2, 3, 4, 5, 6, utc),
|
||||
want: parseTimeNano(t, "9999-01-02T03:04:05.000000006+00:00"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
res := restic.FixTime(test.src)
|
||||
if !res.Equal(test.want) {
|
||||
t.Fatalf("wrong result for %v, want:\n %v\ngot:\n %v", test.src, test.want, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue