forked from TrueCloudLab/restic
Merge pull request #4925 from MichaelEischer/fix-restore-truncation
restore: Fix truncation of uptodate files
This commit is contained in:
commit
fa5ff0873a
2 changed files with 55 additions and 51 deletions
|
@ -93,17 +93,20 @@ func (r *fileRestorer) targetPath(location string) string {
|
||||||
return filepath.Join(r.dst, location)
|
return filepath.Join(r.dst, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *fileRestorer) forEachBlob(blobIDs []restic.ID, fn func(packID restic.ID, packBlob restic.Blob, idx int)) error {
|
func (r *fileRestorer) forEachBlob(blobIDs []restic.ID, fn func(packID restic.ID, packBlob restic.Blob, idx int, fileOffset int64)) error {
|
||||||
if len(blobIDs) == 0 {
|
if len(blobIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileOffset := int64(0)
|
||||||
for i, blobID := range blobIDs {
|
for i, blobID := range blobIDs {
|
||||||
packs := r.idx(restic.DataBlob, blobID)
|
packs := r.idx(restic.DataBlob, blobID)
|
||||||
if len(packs) == 0 {
|
if len(packs) == 0 {
|
||||||
return errors.Errorf("Unknown blob %s", blobID.String())
|
return errors.Errorf("Unknown blob %s", blobID.String())
|
||||||
}
|
}
|
||||||
fn(packs[0].PackID, packs[0].Blob, i)
|
pb := packs[0]
|
||||||
|
fn(pb.PackID, pb.Blob, i, fileOffset)
|
||||||
|
fileOffset += int64(pb.DataLength())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -120,28 +123,24 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error {
|
||||||
// create packInfo from fileInfo
|
// create packInfo from fileInfo
|
||||||
for _, file := range r.files {
|
for _, file := range r.files {
|
||||||
fileBlobs := file.blobs.(restic.IDs)
|
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
|
largeFile := len(fileBlobs) > largeFileBlobCount
|
||||||
var packsMap map[restic.ID][]fileBlobInfo
|
var packsMap map[restic.ID][]fileBlobInfo
|
||||||
if largeFile {
|
if largeFile {
|
||||||
packsMap = make(map[restic.ID][]fileBlobInfo)
|
packsMap = make(map[restic.ID][]fileBlobInfo)
|
||||||
|
file.blobs = packsMap
|
||||||
}
|
}
|
||||||
fileOffset := int64(0)
|
restoredBlobs := false
|
||||||
err := r.forEachBlob(fileBlobs, func(packID restic.ID, blob restic.Blob, idx int) {
|
err := r.forEachBlob(fileBlobs, func(packID restic.ID, blob restic.Blob, idx int, fileOffset int64) {
|
||||||
if largeFile {
|
|
||||||
if !file.state.HasMatchingBlob(idx) {
|
if !file.state.HasMatchingBlob(idx) {
|
||||||
|
if largeFile {
|
||||||
packsMap[packID] = append(packsMap[packID], fileBlobInfo{id: blob.ID, offset: fileOffset})
|
packsMap[packID] = append(packsMap[packID], fileBlobInfo{id: blob.ID, offset: fileOffset})
|
||||||
|
}
|
||||||
|
restoredBlobs = true
|
||||||
} else {
|
} else {
|
||||||
r.reportBlobProgress(file, uint64(blob.DataLength()))
|
r.reportBlobProgress(file, uint64(blob.DataLength()))
|
||||||
|
// completely ignore blob
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
fileOffset += int64(blob.DataLength())
|
|
||||||
pack, ok := packs[packID]
|
pack, ok := packs[packID]
|
||||||
if !ok {
|
if !ok {
|
||||||
pack = &packInfo{
|
pack = &packInfo{
|
||||||
|
@ -156,6 +155,11 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error {
|
||||||
file.sparse = r.sparse
|
file.sparse = r.sparse
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
// repository index is messed up, can't do anything
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(fileBlobs) == 1 {
|
if len(fileBlobs) == 1 {
|
||||||
// no need to preallocate files with a single block, thus we can always consider them to be sparse
|
// no need to preallocate files with a single block, thus we can always consider them to be sparse
|
||||||
// in addition, a short chunk will never match r.zeroChunk which would prevent sparseness for short files
|
// in addition, a short chunk will never match r.zeroChunk which would prevent sparseness for short files
|
||||||
|
@ -168,12 +172,17 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error {
|
||||||
file.sparse = false
|
file.sparse = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
// empty file or one with already uptodate content. Make sure that the file size is correct
|
||||||
// repository index is messed up, can't do anything
|
if !restoredBlobs {
|
||||||
return err
|
err := r.truncateFileToSize(file.location, file.size)
|
||||||
|
if errFile := r.sanitizeError(file, err); errFile != nil {
|
||||||
|
return errFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// the progress events were already sent for non-zero size files
|
||||||
|
if file.size == 0 {
|
||||||
|
r.reportBlobProgress(file, 0)
|
||||||
}
|
}
|
||||||
if largeFile {
|
|
||||||
file.blobs = packsMap
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// drop no longer necessary file list
|
// drop no longer necessary file list
|
||||||
|
@ -214,17 +223,12 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error {
|
||||||
return wg.Wait()
|
return wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *fileRestorer) restoreEmptyFileAt(location string) error {
|
func (r *fileRestorer) truncateFileToSize(location string, size int64) error {
|
||||||
f, err := createFile(r.targetPath(location), 0, false, r.allowRecursiveDelete)
|
f, err := createFile(r.targetPath(location), size, false, r.allowRecursiveDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = f.Close(); err != nil {
|
return f.Close()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.progress.AddProgress(location, restore.ActionFileRestored, 0, 0)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type blobToFileOffsetsMapping map[restic.ID]struct {
|
type blobToFileOffsetsMapping map[restic.ID]struct {
|
||||||
|
@ -246,16 +250,10 @@ func (r *fileRestorer) downloadPack(ctx context.Context, pack *packInfo) error {
|
||||||
blobInfo.files[file] = append(blobInfo.files[file], fileOffset)
|
blobInfo.files[file] = append(blobInfo.files[file], fileOffset)
|
||||||
}
|
}
|
||||||
if fileBlobs, ok := file.blobs.(restic.IDs); ok {
|
if fileBlobs, ok := file.blobs.(restic.IDs); ok {
|
||||||
fileOffset := int64(0)
|
err := r.forEachBlob(fileBlobs, func(packID restic.ID, blob restic.Blob, idx int, fileOffset int64) {
|
||||||
err := r.forEachBlob(fileBlobs, func(packID restic.ID, blob restic.Blob, idx int) {
|
if packID.Equal(pack.id) && !file.state.HasMatchingBlob(idx) {
|
||||||
if packID.Equal(pack.id) {
|
|
||||||
if !file.state.HasMatchingBlob(idx) {
|
|
||||||
addBlob(blob, fileOffset)
|
addBlob(blob, fileOffset)
|
||||||
} else {
|
|
||||||
r.reportBlobProgress(file, uint64(blob.DataLength()))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
fileOffset += int64(blob.DataLength())
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// restoreFiles should have caught this error before
|
// restoreFiles should have caught this error before
|
||||||
|
|
|
@ -1003,6 +1003,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||||
"dirtest": Dir{
|
"dirtest": Dir{
|
||||||
Nodes: map[string]Node{
|
Nodes: map[string]Node{
|
||||||
"file": File{Data: "content: file\n", ModTime: baseTime},
|
"file": File{Data: "content: file\n", ModTime: baseTime},
|
||||||
|
"foo": File{Data: "content: foobar", ModTime: baseTime},
|
||||||
},
|
},
|
||||||
ModTime: baseTime,
|
ModTime: baseTime,
|
||||||
},
|
},
|
||||||
|
@ -1014,6 +1015,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||||
"dirtest": Dir{
|
"dirtest": Dir{
|
||||||
Nodes: map[string]Node{
|
Nodes: map[string]Node{
|
||||||
"file": File{Data: "content: file2\n", ModTime: baseTime.Add(-time.Second)},
|
"file": File{Data: "content: file2\n", ModTime: baseTime.Add(-time.Second)},
|
||||||
|
"foo": File{Data: "content: foo", ModTime: baseTime},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1029,13 +1031,14 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"foo": "content: new\n",
|
"foo": "content: new\n",
|
||||||
"dirtest/file": "content: file2\n",
|
"dirtest/file": "content: file2\n",
|
||||||
|
"dirtest/foo": "content: foo",
|
||||||
},
|
},
|
||||||
Progress: restoreui.State{
|
Progress: restoreui.State{
|
||||||
FilesFinished: 3,
|
FilesFinished: 4,
|
||||||
FilesTotal: 3,
|
FilesTotal: 4,
|
||||||
FilesSkipped: 0,
|
FilesSkipped: 0,
|
||||||
AllBytesWritten: 28,
|
AllBytesWritten: 40,
|
||||||
AllBytesTotal: 28,
|
AllBytesTotal: 40,
|
||||||
AllBytesSkipped: 0,
|
AllBytesSkipped: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1044,13 +1047,14 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"foo": "content: new\n",
|
"foo": "content: new\n",
|
||||||
"dirtest/file": "content: file2\n",
|
"dirtest/file": "content: file2\n",
|
||||||
|
"dirtest/foo": "content: foo",
|
||||||
},
|
},
|
||||||
Progress: restoreui.State{
|
Progress: restoreui.State{
|
||||||
FilesFinished: 3,
|
FilesFinished: 4,
|
||||||
FilesTotal: 3,
|
FilesTotal: 4,
|
||||||
FilesSkipped: 0,
|
FilesSkipped: 0,
|
||||||
AllBytesWritten: 28,
|
AllBytesWritten: 40,
|
||||||
AllBytesTotal: 28,
|
AllBytesTotal: 40,
|
||||||
AllBytesSkipped: 0,
|
AllBytesSkipped: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1059,14 +1063,15 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"foo": "content: new\n",
|
"foo": "content: new\n",
|
||||||
"dirtest/file": "content: file\n",
|
"dirtest/file": "content: file\n",
|
||||||
|
"dirtest/foo": "content: foobar",
|
||||||
},
|
},
|
||||||
Progress: restoreui.State{
|
Progress: restoreui.State{
|
||||||
FilesFinished: 2,
|
FilesFinished: 2,
|
||||||
FilesTotal: 2,
|
FilesTotal: 2,
|
||||||
FilesSkipped: 1,
|
FilesSkipped: 2,
|
||||||
AllBytesWritten: 13,
|
AllBytesWritten: 13,
|
||||||
AllBytesTotal: 13,
|
AllBytesTotal: 13,
|
||||||
AllBytesSkipped: 15,
|
AllBytesSkipped: 27,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1074,14 +1079,15 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"foo": "content: foo\n",
|
"foo": "content: foo\n",
|
||||||
"dirtest/file": "content: file\n",
|
"dirtest/file": "content: file\n",
|
||||||
|
"dirtest/foo": "content: foobar",
|
||||||
},
|
},
|
||||||
Progress: restoreui.State{
|
Progress: restoreui.State{
|
||||||
FilesFinished: 1,
|
FilesFinished: 1,
|
||||||
FilesTotal: 1,
|
FilesTotal: 1,
|
||||||
FilesSkipped: 2,
|
FilesSkipped: 3,
|
||||||
AllBytesWritten: 0,
|
AllBytesWritten: 0,
|
||||||
AllBytesTotal: 0,
|
AllBytesTotal: 0,
|
||||||
AllBytesSkipped: 28,
|
AllBytesSkipped: 40,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue