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, }