diff --git a/internal/restorer/filerestorer.go b/internal/restorer/filerestorer.go index 3551857dd..f3a68c58a 100644 --- a/internal/restorer/filerestorer.go +++ b/internal/restorer/filerestorer.go @@ -120,6 +120,13 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error { // create packInfo from fileInfo for _, file := range r.files { fileBlobs := file.blobs.(restic.IDs) + if len(fileBlobs) == 0 { + err := r.restoreEmptyFileAt(file.location) + if errFile := r.sanitizeError(file, err); errFile != nil { + return errFile + } + } + largeFile := len(fileBlobs) > largeFileBlobCount var packsMap map[restic.ID][]fileBlobInfo if largeFile { @@ -195,6 +202,21 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error { return wg.Wait() } +func (r *fileRestorer) restoreEmptyFileAt(location string) error { + f, err := createFile(r.targetPath(location), 0, false) + if err != nil { + return err + } + if err = f.Close(); err != nil { + return err + } + + if r.progress != nil { + r.progress.AddProgress(location, 0, 0) + } + return nil +} + type blobToFileOffsetsMapping map[restic.ID]struct { files map[*fileInfo][]int64 // file -> offsets (plural!) of the blob in the file blob restic.Blob diff --git a/internal/restorer/filerestorer_test.go b/internal/restorer/filerestorer_test.go index 03797e0c8..d29c0dcea 100644 --- a/internal/restorer/filerestorer_test.go +++ b/internal/restorer/filerestorer_test.go @@ -206,6 +206,10 @@ func TestFileRestorerBasic(t *testing.T) { {"data3-1", "pack3-1"}, }, }, + { + name: "empty", + blobs: []TestBlob{}, + }, }, nil, sparse) } } diff --git a/internal/restorer/restorer.go b/internal/restorer/restorer.go index c471800df..f691c4cae 100644 --- a/internal/restorer/restorer.go +++ b/internal/restorer/restorer.go @@ -203,31 +203,6 @@ func (res *Restorer) restoreHardlinkAt(node *restic.Node, target, path, location return res.restoreNodeMetadataTo(node, path, location) } -func (res *Restorer) restoreEmptyFileAt(node *restic.Node, target, location string) error { - wr, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) - if fs.IsAccessDenied(err) { - // If file is readonly, clear the readonly flag by resetting the - // permissions of the file and try again - // as the metadata will be set again in the second pass and the - // readonly flag will be applied again if needed. - if err = fs.ResetPermissions(target); err != nil { - return err - } - if wr, err = os.OpenFile(target, os.O_TRUNC|os.O_WRONLY, 0600); err != nil { - return err - } - } - if err = wr.Close(); err != nil { - return err - } - - if res.progress != nil { - res.progress.AddProgress(location, 0, 0) - } - - return res.restoreNodeMetadataTo(node, target, location) -} - // RestoreTo creates the directories and files in the snapshot below dst. // Before an item is created, res.Filter is called. func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { @@ -274,13 +249,6 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { return nil } - if node.Size == 0 { - if res.progress != nil { - res.progress.AddFile(node.Size) - } - return nil // deal with empty files later - } - if node.Links > 1 { if idx.Has(node.Inode, node.DeviceID) { if res.progress != nil { @@ -320,14 +288,6 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { return res.restoreNodeTo(ctx, node, target, location) } - // create empty files, but not hardlinks to empty files - if node.Size == 0 && (node.Links < 2 || !idx.Has(node.Inode, node.DeviceID)) { - if node.Links > 1 { - idx.Add(node.Inode, node.DeviceID, location) - } - return res.restoreEmptyFileAt(node, target, location) - } - if idx.Has(node.Inode, node.DeviceID) && idx.Value(node.Inode, node.DeviceID) != location { return res.restoreHardlinkAt(node, filerestorer.targetPath(idx.Value(node.Inode, node.DeviceID)), target, location) }