From 50d8377e31c38afea987649d7a935b781595b63d Mon Sep 17 00:00:00 2001 From: greatroar <61184462+greatroar@users.noreply.github.com> Date: Thu, 23 Mar 2023 08:13:48 +0100 Subject: [PATCH] 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. --- internal/archiver/archiver.go | 20 ++++++++++++++++++++ internal/fs/fs_reader.go | 7 ++----- internal/fs/fs_reader_test.go | 8 -------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/internal/archiver/archiver.go b/internal/archiver/archiver.go index a56965d63..584f2114a 100644 --- a/internal/archiver/archiver.go +++ b/internal/archiver/archiver.go @@ -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 diff --git a/internal/fs/fs_reader.go b/internal/fs/fs_reader.go index 1551ad919..146b0b4ec 100644 --- a/internal/fs/fs_reader.go +++ b/internal/fs/fs_reader.go @@ -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 { diff --git a/internal/fs/fs_reader_test.go b/internal/fs/fs_reader_test.go index d3ef5608a..054d18320 100644 --- a/internal/fs/fs_reader_test.go +++ b/internal/fs/fs_reader_test.go @@ -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, }