archiver: Shortcut for handling empty files

Regular files that are empty according to stat are now not opened for
reading their contents. Such files are quite common (in my homedir, at
least) and we can save multiple system calls this way. On a network
filesystem, that can mean round trips. Also, we can back up empty files
that we cannot open for reading. Finally, fixes #4257.

Existing tests cover this case. fs.Reader now no longer has a meaningful
Size. Nothing depended on that.
This commit is contained in:
greatroar 2023-03-23 08:13:48 +01:00
parent f646406822
commit 50d8377e31
3 changed files with 22 additions and 13 deletions

View File

@ -363,6 +363,26 @@ func (arch *Archiver) Save(ctx context.Context, snPath, target string, previous
switch {
case fs.IsRegularFile(fi):
if fi.Size() == 0 {
// Shortcut for empty files. Git uses lots of these, and
// some virtual filesystems (notably juicefs; #4257) present
// infinitely-sized special files as empty regular files.
// We can also save empty files without being able to open them.
debug.Log(" %v empty", target)
node, err := arch.nodeFromFileInfo(snPath, target, fi)
if err != nil {
return FutureNode{}, false, err
}
node.Content = restic.IDs{}
fn = newFutureNodeWithResult(futureNodeResult{
snPath: snPath,
target: target,
node: node,
})
return fn, false, nil
}
debug.Log(" %v regular file", target)
// check if the file has not changed before performing a fopen operation (more expensive, specially

View File

@ -23,7 +23,6 @@ type Reader struct {
// for FileInfo
Mode os.FileMode
ModTime time.Time
Size int64
AllowEmptyFile bool
@ -65,7 +64,6 @@ func (fs *Reader) Open(name string) (f File, err error) {
func (fs *Reader) fi() os.FileInfo {
return fakeFileInfo{
name: fs.Name,
size: fs.Size,
mode: fs.Mode,
modtime: fs.ModTime,
}
@ -107,7 +105,6 @@ func (fs *Reader) Lstat(name string) (os.FileInfo, error) {
getDirInfo := func(name string) os.FileInfo {
fi := fakeFileInfo{
name: fs.Base(name),
size: 0,
mode: os.ModeDir | 0755,
modtime: time.Now(),
}
@ -292,7 +289,6 @@ func (d fakeDir) Readdir(n int) ([]os.FileInfo, error) {
// fakeFileInfo implements the bare minimum of os.FileInfo.
type fakeFileInfo struct {
name string
size int64
mode os.FileMode
modtime time.Time
}
@ -302,7 +298,8 @@ func (fi fakeFileInfo) Name() string {
}
func (fi fakeFileInfo) Size() int64 {
return fi.size
// Fake size to fool the archiver's empty file check.
return -1
}
func (fi fakeFileInfo) Mode() os.FileMode {

View File

@ -142,10 +142,6 @@ func verifyDirectoryContentsFI(t testing.TB, fs FS, dir string, want []os.FileIn
t.Errorf("entry %d: wrong value for ModTime: want %v, got %v", i, fi1.ModTime(), fi2.ModTime())
}
if fi1.Size() != fi2.Size() {
t.Errorf("entry %d: wrong value for Size: want %v, got %v", i, fi1.Size(), fi2.Size())
}
if fi1.Sys() != fi2.Sys() {
t.Errorf("entry %d: wrong value for Sys: want %v, got %v", i, fi1.Sys(), fi2.Sys())
}
@ -202,7 +198,6 @@ func TestFSReader(t *testing.T) {
mode: 0644,
modtime: now,
name: filename,
size: int64(len(data)),
}
verifyDirectoryContentsFI(t, fs, "/", []os.FileInfo{fi})
},
@ -214,7 +209,6 @@ func TestFSReader(t *testing.T) {
mode: 0644,
modtime: now,
name: filename,
size: int64(len(data)),
}
verifyDirectoryContentsFI(t, fs, ".", []os.FileInfo{fi})
},
@ -324,7 +318,6 @@ func TestFSReader(t *testing.T) {
ReadCloser: io.NopCloser(bytes.NewReader(data)),
Mode: 0644,
Size: int64(len(data)),
ModTime: now,
}
@ -359,7 +352,6 @@ func TestFSReaderDir(t *testing.T) {
ReadCloser: io.NopCloser(bytes.NewReader(data)),
Mode: 0644,
Size: int64(len(data)),
ModTime: now,
}