restic/cmd/restic/cmd_repair_snapshots.go

229 lines
6.3 KiB
Go
Raw Normal View History

2020-08-05 21:32:15 +02:00
package main
import (
2022-12-10 17:18:04 +01:00
"context"
"github.com/restic/restic/internal/backend"
2020-08-05 21:32:15 +02:00
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/walker"
2022-12-10 17:18:04 +01:00
"golang.org/x/sync/errgroup"
2020-08-05 21:32:15 +02:00
"github.com/spf13/cobra"
)
2022-12-10 17:25:38 +01:00
var cmdRepairSnapshots = &cobra.Command{
Use: "snapshots [flags] [snapshot ID] [...]",
2020-08-05 21:32:15 +02:00
Short: "Repair snapshots",
Long: `
2022-12-10 17:25:38 +01:00
The "repair snapshots" command allows to repair broken snapshots.
2020-08-05 21:32:15 +02:00
It scans the given snapshots and generates new ones where
damaged tress and file contents are removed.
If the broken snapshots are deleted, a prune run will
be able to refit the repository.
The command depends on a good state of the index, so if
there are inaccurancies in the index, make sure to run
"repair index" before!
2020-08-05 21:32:15 +02:00
WARNING:
========
Repairing and deleting broken snapshots causes data loss!
It will remove broken dirs and modify broken files in
the modified snapshots.
If the contents of directories and files are still available,
the better option is to redo a backup which in that case is
able to "heal" already present snapshots.
Only use this command if you need to recover an old and
broken snapshot!
EXIT STATUS
===========
Exit status is 0 if the command was successful, and non-zero if there was any error.
`,
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
2022-12-10 17:25:38 +01:00
return runRepairSnapshots(cmd.Context(), globalOptions, repairSnapshotOptions, args)
2020-08-05 21:32:15 +02:00
},
}
2021-02-20 20:56:03 +01:00
// RepairOptions collects all options for the repair command.
2020-08-05 21:32:15 +02:00
type RepairOptions struct {
DryRun bool
Forget bool
2022-12-10 17:18:04 +01:00
restic.SnapshotFilter
2020-08-05 21:32:15 +02:00
}
2022-12-10 17:25:38 +01:00
var repairSnapshotOptions RepairOptions
2020-08-05 21:32:15 +02:00
func init() {
2022-12-10 17:25:38 +01:00
cmdRepair.AddCommand(cmdRepairSnapshots)
flags := cmdRepairSnapshots.Flags()
2022-12-10 17:18:04 +01:00
flags.BoolVarP(&repairSnapshotOptions.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done")
flags.BoolVarP(&repairSnapshotOptions.Forget, "forget", "", false, "remove original snapshots after creating new ones")
2022-12-10 17:25:38 +01:00
initMultiSnapshotFilter(flags, &repairSnapshotOptions.SnapshotFilter, true)
2020-08-05 21:32:15 +02:00
}
2022-12-10 17:25:38 +01:00
func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string) error {
2022-12-10 17:18:04 +01:00
repo, err := OpenRepository(ctx, globalOptions)
2020-08-05 21:32:15 +02:00
if err != nil {
return err
}
if !opts.DryRun {
var lock *restic.Lock
var err error
lock, ctx, err = lockRepoExclusive(ctx, repo, gopts.RetryLock, gopts.JSON)
defer unlockRepo(lock)
if err != nil {
return err
}
} else {
repo.SetDryRun()
}
snapshotLister, err := backend.MemorizeList(ctx, repo.Backend(), restic.SnapshotFile)
2020-08-05 21:32:15 +02:00
if err != nil {
return err
}
2022-12-10 17:18:04 +01:00
if err := repo.LoadIndex(ctx); err != nil {
2020-08-05 21:32:15 +02:00
return err
}
// get snapshots to check & repair
var snapshots []*restic.Snapshot
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, args) {
2020-08-05 21:32:15 +02:00
snapshots = append(snapshots, sn)
}
2022-12-10 17:18:04 +01:00
return repairSnapshots(ctx, opts, repo, snapshots)
2020-08-05 21:32:15 +02:00
}
2022-12-10 17:18:04 +01:00
func repairSnapshots(ctx context.Context, opts RepairOptions, repo restic.Repository, snapshots []*restic.Snapshot) error {
// Three error cases are checked:
// - tree is a nil tree (-> will be replaced by an empty tree)
// - trees which cannot be loaded (-> the tree contents will be removed)
// - files whose contents are not fully available (-> file will be modified)
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
RewriteNode: func(node *restic.Node, path string) *restic.Node {
if node.Type != "file" {
return node
}
ok := true
var newContent restic.IDs
var newSize uint64
// check all contents and remove if not available
for _, id := range node.Content {
if size, found := repo.LookupBlobSize(id, restic.DataBlob); !found {
ok = false
} else {
newContent = append(newContent, id)
newSize += uint64(size)
}
}
if !ok {
if newSize == 0 {
Printf("removed defective file '%v'\n", path+node.Name)
node = nil
} else {
Printf("repaired defective file '%v'\n", path+node.Name)
node.Content = newContent
node.Size = newSize
}
}
return node
},
RewriteFailedTree: func(nodeID restic.ID, path string, _ error) (restic.ID, error) {
if path == "/" {
// remove snapshots with invalid root node
return restic.ID{}, nil
}
// If a subtree fails to load, remove it
Printf("removed defective dir '%v'", path)
emptyID, err := restic.SaveTree(ctx, repo, &restic.Tree{})
if err != nil {
return restic.ID{}, err
}
return emptyID, nil
},
AllowUnstableSerialization: true,
})
2020-08-05 21:32:15 +02:00
deleteSn := restic.NewIDSet()
Verbosef("check and repair %d snapshots\n", len(snapshots))
bar := newProgressMax(!globalOptions.Quiet, uint64(len(snapshots)), "snapshots")
2022-12-10 17:18:04 +01:00
wg, ctx := errgroup.WithContext(ctx)
repo.StartPackUploader(ctx, wg)
wg.Go(func() error {
for _, sn := range snapshots {
debug.Log("process snapshot %v", sn.ID())
Printf("%v:\n", sn)
newID, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
2022-12-10 17:18:04 +01:00
switch {
case err != nil:
2020-08-05 21:32:15 +02:00
return err
case newID.IsNull():
2022-12-10 17:18:04 +01:00
Printf("the root tree is damaged -> delete snapshot.\n")
deleteSn.Insert(*sn.ID())
case !newID.Equal(*sn.Tree):
err = changeSnapshot(ctx, opts.DryRun, repo, sn, &newID)
2022-12-10 17:18:04 +01:00
if err != nil {
return err
}
deleteSn.Insert(*sn.ID())
default:
Printf("is ok.\n")
2020-08-05 21:32:15 +02:00
}
2022-12-10 17:18:04 +01:00
debug.Log("processed snapshot %v", sn.ID())
bar.Add(1)
2020-08-05 21:32:15 +02:00
}
2022-12-10 17:18:04 +01:00
bar.Done()
return repo.Flush(ctx)
})
2020-08-05 21:32:15 +02:00
2022-12-10 17:18:04 +01:00
err := wg.Wait()
2020-08-05 21:32:15 +02:00
if err != nil {
return err
}
if len(deleteSn) > 0 && opts.Forget {
2020-08-05 21:32:15 +02:00
Verbosef("delete %d snapshots...\n", len(deleteSn))
if !opts.DryRun {
2022-12-10 17:18:04 +01:00
DeleteFiles(ctx, globalOptions, repo, deleteSn, restic.SnapshotFile)
2020-08-05 21:32:15 +02:00
}
}
return nil
}
// changeSnapshot creates a modified snapshot:
// - set the tree to newID
// - add the rag opts.AddTag
// - preserve original ID
// if opts.DryRun is set, it doesn't change anything but only
func changeSnapshot(ctx context.Context, dryRun bool, repo restic.Repository, sn *restic.Snapshot, newID *restic.ID) error {
sn.AddTags([]string{"repaired"})
// Always set the original snapshot id as this essentially a new snapshot.
sn.Original = sn.ID()
sn.Tree = newID
if !dryRun {
2022-12-10 17:18:04 +01:00
newID, err := restic.SaveSnapshot(ctx, repo, sn)
2020-08-05 21:32:15 +02:00
if err != nil {
return err
}
Printf("snapshot repaired -> %v created.\n", newID.Str())
} else {
2021-02-20 20:56:03 +01:00
Printf("would have repaired snapshot %v.\n", sn.ID().Str())
2020-08-05 21:32:15 +02:00
}
return nil
}