diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 70d031289..e1c745104 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -353,13 +353,21 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) { return s, nil } - cache, err := cache.New(s.Config().ID, opts.CacheDir) + c, err := cache.New(s.Config().ID, opts.CacheDir) if err != nil { Warnf("unable to open cache: %v\n", err) - } else { - s.UseCache(cache) + return s, nil } + oldCacheDirs, err := cache.Old(c.Base) + if err != nil { + Warnf("unable to find old cache directories: %v", err) + } else { + Verbosef("found %d old cache directories in %v remove them with 'restic cache --cleanup'\n", + len(oldCacheDirs), c.Base) + } + + s.UseCache(c) return s, nil } diff --git a/internal/cache/cache.go b/internal/cache/cache.go index f83142e19..f4dd59bfd 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strconv" + "time" "github.com/pkg/errors" "github.com/restic/restic/internal/debug" @@ -107,6 +108,11 @@ func New(id string, basedir string) (c *Cache, err error) { return nil, errors.New("cache version is newer") } + err = updateTimestamp(cachedir) + if err != nil { + return nil, err + } + // create the repo cache dir if it does not exist yet if err = fs.MkdirAll(cachedir, dirMode); err != nil { return nil, err @@ -137,6 +143,53 @@ func New(id string, basedir string) (c *Cache, err error) { return c, nil } +// updateTimestamp sets the modification timestamp (mtime and atime) for the +// directory d to the current time. +func updateTimestamp(d string) error { + t := time.Now() + return fs.Chtimes(d, t, t) +} + +const maxCacheAge = 30 * 24 * time.Hour + +// Old returns a list of cache directories with a modification time of more +// than 30 days ago. +func Old(basedir string) ([]string, error) { + var oldCacheDirs []string + + f, err := fs.Open(basedir) + if err != nil { + return nil, err + } + + entries, err := f.Readdir(-1) + if err != nil { + return nil, err + } + + oldest := time.Now().Add(-maxCacheAge) + for _, fi := range entries { + if !fi.IsDir() { + continue + } + + if !fi.ModTime().Before(oldest) { + continue + } + + oldCacheDirs = append(oldCacheDirs, fi.Name()) + } + + err = f.Close() + if err != nil { + return nil, err + } + + debug.Log("%d old cache dirs found", len(oldCacheDirs)) + + return oldCacheDirs, nil +} + // errNoSuchFile is returned when a file is not cached. type errNoSuchFile struct { Type string diff --git a/internal/fs/file.go b/internal/fs/file.go index a90d1b2e7..d055107b4 100644 --- a/internal/fs/file.go +++ b/internal/fs/file.go @@ -4,6 +4,7 @@ import ( "io" "os" "path/filepath" + "time" ) // File is an open file on a file system. @@ -120,3 +121,12 @@ func RemoveIfExists(filename string) error { } return err } + +// Chtimes changes the access and modification times of the named file, +// similar to the Unix utime() or utimes() functions. +// +// The underlying filesystem may truncate or round the values to a less +// precise time unit. If there is an error, it will be of type *PathError. +func Chtimes(name string, atime time.Time, mtime time.Time) error { + return os.Chtimes(fixpath(name), atime, mtime) +}