2
2
mirror of https://github.com/octoleo/restic.git synced 2024-12-23 11:28:54 +00:00

checker: add option to remove orphaned packs

This commit is contained in:
Alexander Neumann 2015-07-12 17:09:48 +02:00
parent 5108d91bc7
commit d36f07c6eb
2 changed files with 49 additions and 17 deletions

View File

@ -48,7 +48,8 @@ type Checker struct {
sync.Mutex sync.Mutex
M map[mapID]uint M map[mapID]uint
} }
indexes map[mapID]*repository.Index indexes map[mapID]*repository.Index
orphanedPacks backend.IDs
masterIndex *repository.Index masterIndex *repository.Index
@ -139,12 +140,13 @@ func (c *Checker) LoadIndex() error {
// PackError describes an error with a specific pack. // PackError describes an error with a specific pack.
type PackError struct { type PackError struct {
ID backend.ID ID backend.ID
error Orphaned bool
Err error
} }
func (e PackError) Error() string { func (e PackError) Error() string {
return "pack " + e.ID.String() + ": " + e.error.Error() return "pack " + e.ID.String() + ": " + e.Err.Error()
} }
func packIDTester(repo *repository.Repository, inChan <-chan mapID, errChan chan<- error, wg *sync.WaitGroup, done <-chan struct{}) { func packIDTester(repo *repository.Repository, inChan <-chan mapID, errChan chan<- error, wg *sync.WaitGroup, done <-chan struct{}) {
@ -156,10 +158,10 @@ func packIDTester(repo *repository.Repository, inChan <-chan mapID, errChan chan
for id := range inChan { for id := range inChan {
ok, err := repo.Backend().Test(backend.Data, map2str(id)) ok, err := repo.Backend().Test(backend.Data, map2str(id))
if err != nil { if err != nil {
err = PackError{map2id(id), err} err = PackError{ID: map2id(id), Err: err}
} else { } else {
if !ok { if !ok {
err = PackError{map2id(id), errors.New("does not exist")} err = PackError{ID: map2id(id), Err: errors.New("does not exist")}
} }
} }
@ -227,10 +229,11 @@ func (c *Checker) Packs(errChan chan<- error, done <-chan struct{}) {
for id := range c.repo.List(backend.Data, done) { for id := range c.repo.List(backend.Data, done) {
debug.Log("Checker.Packs", "check data blob %v", id.Str()) debug.Log("Checker.Packs", "check data blob %v", id.Str())
if _, ok := seenPacks[id2map(id)]; !ok { if _, ok := seenPacks[id2map(id)]; !ok {
c.orphanedPacks = append(c.orphanedPacks, id)
select { select {
case <-done: case <-done:
return return
case errChan <- PackError{id, errors.New("not referenced in any index")}: case errChan <- PackError{ID: id, Orphaned: true, Err: errors.New("not referenced in any index")}:
} }
} }
} }
@ -591,3 +594,8 @@ func (c *Checker) UnusedBlobs() (blobs backend.IDs) {
return blobs return blobs
} }
// OrphanedPacks returns a slice of unused packs (only available after Packs() was run).
func (c *Checker) OrphanedPacks() backend.IDs {
return c.orphanedPacks
}

View File

@ -5,11 +5,13 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/restic/restic/backend"
"github.com/restic/restic/checker" "github.com/restic/restic/checker"
) )
type CmdCheck struct { type CmdCheck struct {
ReadData bool ` long:"read-data" description:"Read data blobs" default:"false"` ReadData bool `long:"read-data" description:"Read data blobs" default:"false"`
RemoveOrphaned bool `long:"remove" description:"Remove data that isn't used" default:"false"`
global *GlobalOptions global *GlobalOptions
} }
@ -45,10 +47,10 @@ func (cmd CmdCheck) Execute(args []string) error {
return err return err
} }
checker := checker.New(repo) chkr := checker.New(repo)
cmd.global.Verbosef("Load indexes\n") cmd.global.Verbosef("Load indexes\n")
if err = checker.LoadIndex(); err != nil { if err = chkr.LoadIndex(); err != nil {
return err return err
} }
@ -59,20 +61,42 @@ func (cmd CmdCheck) Execute(args []string) error {
errChan := make(chan error) errChan := make(chan error)
cmd.global.Verbosef("Check all packs\n") cmd.global.Verbosef("Check all packs\n")
go checker.Packs(errChan, done) go chkr.Packs(errChan, done)
foundOrphanedPacks := false
for err := range errChan {
errorsFound = true
fmt.Fprintf(os.Stderr, "%v\n", err)
if e, ok := err.(checker.PackError); ok && e.Orphaned {
foundOrphanedPacks = true
}
}
cmd.global.Verbosef("Check snapshots, trees and blobs\n")
errChan = make(chan error)
go chkr.Structure(errChan, done)
for err := range errChan { for err := range errChan {
errorsFound = true errorsFound = true
fmt.Fprintf(os.Stderr, "error: %v\n", err) fmt.Fprintf(os.Stderr, "error: %v\n", err)
} }
cmd.global.Verbosef("Check snapshots, trees and blobs\n") for _, id := range chkr.UnusedBlobs() {
errChan = make(chan error) cmd.global.Verbosef("unused blob %v\n", id.Str())
go checker.Structure(errChan, done) }
for err := range errChan { if foundOrphanedPacks && cmd.RemoveOrphaned {
errorsFound = true IDs := chkr.OrphanedPacks()
fmt.Fprintf(os.Stderr, "error: %v\n", err) cmd.global.Verbosef("Remove %d orphaned packs... ", len(IDs))
for _, id := range IDs {
if err := repo.Backend().Remove(backend.Data, id.String()); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
}
cmd.global.Verbosef("done\n")
} }
if errorsFound { if errorsFound {