2
2
mirror of https://github.com/octoleo/restic.git synced 2024-05-31 08:00:48 +00:00

Merge pull request #1436 from restic/remove-old-cache

Remove old cache directories
This commit is contained in:
Alexander Neumann 2017-11-27 21:37:05 +01:00
commit dc38265b54
7 changed files with 136 additions and 25 deletions

View File

@ -35,12 +35,14 @@ Important Changes in 0.8.0
that can be used to disable the cache. By deafult, the cache a standard that can be used to disable the cache. By deafult, the cache a standard
cache folder for the OS, which can be overridden with `--cache-dir`. The cache folder for the OS, which can be overridden with `--cache-dir`. The
cache will automatically populate, indexes and snapshots are saved as they cache will automatically populate, indexes and snapshots are saved as they
are loaded. are loaded. Cache directories for repos that haven't been used recently can
automatically be removed by restic with the `--cleanup-cache` option.
https://github.com/restic/restic/pull/1040 https://github.com/restic/restic/pull/1040
https://github.com/restic/restic/issues/29 https://github.com/restic/restic/issues/29
https://github.com/restic/restic/issues/738 https://github.com/restic/restic/issues/738
https://github.com/restic/restic/issues/282 https://github.com/restic/restic/issues/282
https://github.com/restic/restic/pull/1287 https://github.com/restic/restic/pull/1287
https://github.com/restic/restic/pull/1436
* A related change was to by default create pack files in the repo that * A related change was to by default create pack files in the repo that
contain either data or metadata, not both mixed together. This allows easy contain either data or metadata, not both mixed together. This allows easy

View File

@ -6,6 +6,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"syscall" "syscall"
@ -23,6 +24,7 @@ import (
"github.com/restic/restic/internal/backend/swift" "github.com/restic/restic/internal/backend/swift"
"github.com/restic/restic/internal/cache" "github.com/restic/restic/internal/cache"
"github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/limiter" "github.com/restic/restic/internal/limiter"
"github.com/restic/restic/internal/options" "github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
@ -45,6 +47,7 @@ type GlobalOptions struct {
CacheDir string CacheDir string
NoCache bool NoCache bool
CACerts []string CACerts []string
CleanupCache bool
LimitUploadKb int LimitUploadKb int
LimitDownloadKb int LimitDownloadKb int
@ -81,6 +84,7 @@ func init() {
f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache directory") f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache directory")
f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache") f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache")
f.StringSliceVar(&globalOptions.CACerts, "cacert", nil, "path to load root certificates from (default: use system certificates)") f.StringSliceVar(&globalOptions.CACerts, "cacert", nil, "path to load root certificates from (default: use system certificates)")
f.BoolVar(&globalOptions.CleanupCache, "cleanup-cache", false, "auto remove old cache directories")
f.IntVar(&globalOptions.LimitUploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)") f.IntVar(&globalOptions.LimitUploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)")
f.IntVar(&globalOptions.LimitDownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)") f.IntVar(&globalOptions.LimitDownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)")
f.StringSliceVarP(&globalOptions.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)") f.StringSliceVarP(&globalOptions.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)")
@ -353,13 +357,39 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
return s, nil return s, nil
} }
cache, err := cache.New(s.Config().ID, opts.CacheDir) c, err := cache.New(s.Config().ID, opts.CacheDir)
if err != nil { if err != nil {
Warnf("unable to open cache: %v\n", err) Warnf("unable to open cache: %v\n", err)
} else { return s, nil
s.UseCache(cache)
} }
oldCacheDirs, err := cache.Old(c.Base)
if err != nil {
Warnf("unable to find old cache directories: %v", err)
}
// nothing more to do if no old cache dirs could be found
if len(oldCacheDirs) == 0 {
return s, nil
}
// cleanup old cache dirs if instructed to do so
if opts.CleanupCache {
Printf("removing %d old cache dirs from %v\n", len(oldCacheDirs), c.Base)
for _, item := range oldCacheDirs {
dir := filepath.Join(c.Base, item)
err = fs.RemoveAll(dir)
if err != nil {
Warnf("unable to remove %v: %v\n", dir, err)
}
}
} else {
Verbosef("found %d old cache directories in %v, pass --cleanup-cache to remove them\n",
len(oldCacheDirs), c.Base)
}
s.UseCache(c)
return s, nil return s, nil
} }

View File

@ -19,8 +19,18 @@ a lower version number is found the cache is recreated with the current
version. If a higher version number is found the cache is ignored and left as version. If a higher version number is found the cache is ignored and left as
is. is.
Snapshots and Indexes Snapshots, Data and Indexes
--------------------- ---------------------------
Snapshot, Data and Index files are cached in the sub-directories ``snapshots``, Snapshot, Data and Index files are cached in the sub-directories ``snapshots``,
``data`` and ``index``, as read from the repository. ``data`` and ``index``, as read from the repository.
Expiry
------
Whenever a cache directory for a repo is used, that directory's modification
timestamp is updated to the current time. By looking at the modification
timestamps of the repo cache directories it is easy to decide which directories
are old and haven't been used in a long time. Those are probably stale and can
be removed.

View File

@ -280,3 +280,10 @@ entirely. In this case, all data is loaded from the repo.
The cache is ephemeral: When a file cannot be read from the cache, it is loaded The cache is ephemeral: When a file cannot be read from the cache, it is loaded
from the repository. from the repository.
Within the cache directory, there's a sub directory for each repository the
cache was used with. Restic updates the timestamps of a repo directory each
time it is used, so by looking at the timestamps of the sub directories of the
cache directory it can decide which sub directories are old and probably not
needed any more. You can either remove these directories manually, or run a
restic command with the ``--cleanup-cache`` flag.

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/debug"
@ -84,10 +85,12 @@ func writeCachedirTag(dir string) error {
// performReadahead returns true. // performReadahead returns true.
func New(id string, basedir string) (c *Cache, err error) { func New(id string, basedir string) (c *Cache, err error) {
if basedir == "" { if basedir == "" {
basedir, err = defaultCacheDir() basedir, err = DefaultDir()
if err != nil { }
return nil, err
} err = mkdirCacheDir(basedir)
if err != nil {
return nil, err
} }
// create base dir and tag it as a cache directory // create base dir and tag it as a cache directory
@ -112,6 +115,12 @@ func New(id string, basedir string) (c *Cache, err error) {
return nil, err return nil, err
} }
// update the timestamp so that we can detect old cache dirs
err = updateTimestamp(cachedir)
if err != nil {
return nil, err
}
if v < cacheVersion { if v < cacheVersion {
err = ioutil.WriteFile(filepath.Join(cachedir, "version"), []byte(fmt.Sprintf("%d", cacheVersion)), 0644) err = ioutil.WriteFile(filepath.Join(cachedir, "version"), []byte(fmt.Sprintf("%d", cacheVersion)), 0644)
if err != nil { if err != nil {
@ -137,6 +146,53 @@ func New(id string, basedir string) (c *Cache, err error) {
return c, nil 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. // errNoSuchFile is returned when a file is not cached.
type errNoSuchFile struct { type errNoSuchFile struct {
Type string Type string

26
internal/cache/dir.go vendored
View File

@ -49,29 +49,25 @@ func darwinCacheDir() (string, error) {
return filepath.Join(home, "Library", "Caches", "restic"), nil return filepath.Join(home, "Library", "Caches", "restic"), nil
} }
// defaultCacheDir determines and creates the default cache directory for this // DefaultDir returns the default cache directory for the current OS.
// system. func DefaultDir() (cachedir string, err error) {
func defaultCacheDir() (string, error) {
var cachedir string
var err error
switch runtime.GOOS { switch runtime.GOOS {
case "darwin": case "darwin":
cachedir, err = darwinCacheDir() cachedir, err = darwinCacheDir()
case "windows": case "windows":
cachedir, err = windowsCacheDir() cachedir, err = windowsCacheDir()
default:
// Default to XDG for Linux and any other OSes.
cachedir, err = xdgCacheDir()
}
if err != nil {
return "", err
} }
// Default to XDG for Linux and any other OSes.
return xdgCacheDir()
}
func mkdirCacheDir(cachedir string) error {
fi, err := fs.Stat(cachedir) fi, err := fs.Stat(cachedir)
if os.IsNotExist(errors.Cause(err)) { if os.IsNotExist(errors.Cause(err)) {
err = fs.MkdirAll(cachedir, 0700) err = fs.MkdirAll(cachedir, 0700)
if err != nil { if err != nil {
return "", errors.Wrap(err, "MkdirAll") return errors.Wrap(err, "MkdirAll")
} }
fi, err = fs.Stat(cachedir) fi, err = fs.Stat(cachedir)
@ -79,12 +75,12 @@ func defaultCacheDir() (string, error) {
} }
if err != nil { if err != nil {
return "", errors.Wrap(err, "Stat") return errors.Wrap(err, "Stat")
} }
if !fi.IsDir() { if !fi.IsDir() {
return "", errors.Errorf("cache dir %v is not a directory", cachedir) return errors.Errorf("cache dir %v is not a directory", cachedir)
} }
return cachedir, nil return nil
} }

View File

@ -4,6 +4,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"time"
) )
// File is an open file on a file system. // File is an open file on a file system.
@ -120,3 +121,12 @@ func RemoveIfExists(filename string) error {
} }
return err 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)
}