Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Examine file ctime when checking if files have changed. #2212

Merged
merged 4 commits into from Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions changelog/unreleased/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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the wording!

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
7 changes: 6 additions & 1 deletion internal/archiver/archiver.go
Expand Up @@ -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
}
Expand Down
14 changes: 14 additions & 0 deletions internal/archiver/archiver_test.go
@@ -1,6 +1,7 @@
package archiver

import (
"bytes"
"context"
"io/ioutil"
"os"
Expand Down Expand Up @@ -576,6 +577,19 @@ func TestFileChanged(t *testing.T) {
save(t, filename, defaultContent)
},
},
{
Name: "new-content-same-timestamp",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Table-driven tests are awesome, easily extendable for contributors! :)

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) {
Expand Down
1 change: 1 addition & 0 deletions internal/fs/stat.go
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions internal/fs/stat_bsd.go
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/fs/stat_unix.go
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions internal/fs/stat_windows.go
Expand Up @@ -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
}