diff --git a/changelog/0.9.5_2019-04-23/issue-2179 b/changelog/0.9.5_2019-04-23/issue-2179 new file mode 100644 index 000000000..e87778d17 --- /dev/null +++ b/changelog/0.9.5_2019-04-23/issue-2179 @@ -0,0 +1,15 @@ +Enhancement: Use ctime when checking for file changes + +Previously, restic only checked a file's mtime (along with other non-timestamp +metadata) to decide if a file has changed. This could cause restic to not notice +that a file has changed (and therefore continue to store the old version, as +opposed to the modified version) if something edits the file and then resets the +timestamp. Restic now also checks the ctime of files, so any modifications to a +file should be noticed, and the modified file will be backed up. The ctime check +will be disabled if the --ignore-inode flag was given. + +If this change causes problems for you, please open an issue, and we can look in +to adding a seperate flag to disable just the ctime check. + +https://github.com/restic/restic/issues/2179 +https://github.com/restic/restic/pull/2212 diff --git a/internal/archiver/archiver.go b/internal/archiver/archiver.go index b21f79e87..34e6df6c2 100644 --- a/internal/archiver/archiver.go +++ b/internal/archiver/archiver.go @@ -453,8 +453,13 @@ func fileChanged(fi os.FileInfo, node *restic.Node, ignoreInode bool) bool { return true } - // check size + // check status change timestamp extFI := fs.ExtendedStat(fi) + if !ignoreInode && !extFI.ChangeTime.Equal(node.ChangeTime) { + return true + } + + // check size if uint64(fi.Size()) != node.Size || uint64(extFI.Size) != node.Size { return true } diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index 419b3a91c..f15de610b 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -1,6 +1,7 @@ package archiver import ( + "bytes" "context" "io/ioutil" "os" @@ -576,6 +577,19 @@ func TestFileChanged(t *testing.T) { save(t, filename, defaultContent) }, }, + { + Name: "new-content-same-timestamp", + Modify: func(t testing.TB, filename string) { + fi, err := os.Stat(filename) + if err != nil { + t.Fatal(err) + } + extFI := fs.ExtendedStat(fi) + save(t, filename, bytes.ToUpper(defaultContent)) + sleep() + setTimestamp(t, filename, extFI.AccessTime, extFI.ModTime) + }, + }, { Name: "other-content", Modify: func(t testing.TB, filename string) { diff --git a/internal/fs/stat.go b/internal/fs/stat.go index d37d12942..e1006fd61 100644 --- a/internal/fs/stat.go +++ b/internal/fs/stat.go @@ -22,6 +22,7 @@ type ExtendedFileInfo struct { AccessTime time.Time // last access time stamp ModTime time.Time // last (content) modification time stamp + ChangeTime time.Time // last status change time stamp } // ExtendedStat returns an ExtendedFileInfo constructed from the os.FileInfo. diff --git a/internal/fs/stat_bsd.go b/internal/fs/stat_bsd.go index 62a258e64..d5e8ce550 100644 --- a/internal/fs/stat_bsd.go +++ b/internal/fs/stat_bsd.go @@ -30,6 +30,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo { AccessTime: time.Unix(s.Atimespec.Unix()), ModTime: time.Unix(s.Mtimespec.Unix()), + ChangeTime: time.Unix(s.Ctimespec.Unix()), } return extFI diff --git a/internal/fs/stat_unix.go b/internal/fs/stat_unix.go index 56c22f8bc..34b98a31e 100644 --- a/internal/fs/stat_unix.go +++ b/internal/fs/stat_unix.go @@ -30,6 +30,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo { AccessTime: time.Unix(s.Atim.Unix()), ModTime: time.Unix(s.Mtim.Unix()), + ChangeTime: time.Unix(s.Ctim.Unix()), } return extFI diff --git a/internal/fs/stat_windows.go b/internal/fs/stat_windows.go index 16f9fe0eb..a8f13ccea 100644 --- a/internal/fs/stat_windows.go +++ b/internal/fs/stat_windows.go @@ -27,5 +27,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo { mtime := syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds()) extFI.ModTime = time.Unix(mtime.Unix()) + extFI.ChangeTime = extFI.ModTime + return extFI }