github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/internal/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/restic/restic/internal/debug"
    13  	"github.com/restic/restic/internal/fs"
    14  	"github.com/restic/restic/internal/restic"
    15  )
    16  
    17  // Cache manages a local cache.
    18  type Cache struct {
    19  	Path             string
    20  	Base             string
    21  	PerformReadahead func(restic.Handle) bool
    22  }
    23  
    24  const dirMode = 0700
    25  const fileMode = 0600
    26  
    27  func readVersion(dir string) (v uint, err error) {
    28  	buf, err := ioutil.ReadFile(filepath.Join(dir, "version"))
    29  	if os.IsNotExist(err) {
    30  		return 0, nil
    31  	}
    32  
    33  	if err != nil {
    34  		return 0, errors.Wrap(err, "ReadFile")
    35  	}
    36  
    37  	ver, err := strconv.ParseUint(string(buf), 10, 32)
    38  	if err != nil {
    39  		return 0, errors.Wrap(err, "ParseUint")
    40  	}
    41  
    42  	return uint(ver), nil
    43  }
    44  
    45  const cacheVersion = 1
    46  
    47  // ensure Cache implements restic.Cache
    48  var _ restic.Cache = &Cache{}
    49  
    50  var cacheLayoutPaths = map[restic.FileType]string{
    51  	restic.DataFile:     "data",
    52  	restic.SnapshotFile: "snapshots",
    53  	restic.IndexFile:    "index",
    54  }
    55  
    56  const cachedirTagSignature = "Signature: 8a477f597d28d172789f06886806bc55\n"
    57  
    58  func writeCachedirTag(dir string) error {
    59  	if err := fs.MkdirAll(dir, dirMode); err != nil {
    60  		return err
    61  	}
    62  
    63  	f, err := fs.OpenFile(filepath.Join(dir, "CACHEDIR.TAG"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
    64  	if err != nil {
    65  		if os.IsExist(errors.Cause(err)) {
    66  			return nil
    67  		}
    68  
    69  		return errors.Wrap(err, "OpenFile")
    70  	}
    71  
    72  	debug.Log("Create CACHEDIR.TAG at %v", dir)
    73  	if _, err := f.Write([]byte(cachedirTagSignature)); err != nil {
    74  		_ = f.Close()
    75  		return errors.Wrap(err, "Write")
    76  	}
    77  
    78  	return f.Close()
    79  }
    80  
    81  // New returns a new cache for the repo ID at basedir. If basedir is the empty
    82  // string, the default cache location (according to the XDG standard) is used.
    83  //
    84  // For partial files, the complete file is loaded and stored in the cache when
    85  // performReadahead returns true.
    86  func New(id string, basedir string) (c *Cache, err error) {
    87  	if basedir == "" {
    88  		basedir, err = DefaultDir()
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  	}
    93  
    94  	err = mkdirCacheDir(basedir)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	// create base dir and tag it as a cache directory
   100  	if err = writeCachedirTag(basedir); err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	cachedir := filepath.Join(basedir, id)
   105  	debug.Log("using cache dir %v", cachedir)
   106  
   107  	v, err := readVersion(cachedir)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if v > cacheVersion {
   113  		return nil, errors.New("cache version is newer")
   114  	}
   115  
   116  	// create the repo cache dir if it does not exist yet
   117  	if err = fs.MkdirAll(cachedir, dirMode); err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	// update the timestamp so that we can detect old cache dirs
   122  	err = updateTimestamp(cachedir)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	if v < cacheVersion {
   128  		err = ioutil.WriteFile(filepath.Join(cachedir, "version"), []byte(fmt.Sprintf("%d", cacheVersion)), 0644)
   129  		if err != nil {
   130  			return nil, errors.Wrap(err, "WriteFile")
   131  		}
   132  	}
   133  
   134  	for _, p := range cacheLayoutPaths {
   135  		if err = fs.MkdirAll(filepath.Join(cachedir, p), dirMode); err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  
   140  	c = &Cache{
   141  		Path: cachedir,
   142  		Base: basedir,
   143  		PerformReadahead: func(restic.Handle) bool {
   144  			// do not perform readahead by default
   145  			return false
   146  		},
   147  	}
   148  
   149  	return c, nil
   150  }
   151  
   152  // updateTimestamp sets the modification timestamp (mtime and atime) for the
   153  // directory d to the current time.
   154  func updateTimestamp(d string) error {
   155  	t := time.Now()
   156  	return fs.Chtimes(d, t, t)
   157  }
   158  
   159  const maxCacheAge = 30 * 24 * time.Hour
   160  
   161  // Old returns a list of cache directories with a modification time of more
   162  // than 30 days ago.
   163  func Old(basedir string) ([]string, error) {
   164  	var oldCacheDirs []string
   165  
   166  	f, err := fs.Open(basedir)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	entries, err := f.Readdir(-1)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	oldest := time.Now().Add(-maxCacheAge)
   177  	for _, fi := range entries {
   178  		if !fi.IsDir() {
   179  			continue
   180  		}
   181  
   182  		if !fi.ModTime().Before(oldest) {
   183  			continue
   184  		}
   185  
   186  		oldCacheDirs = append(oldCacheDirs, fi.Name())
   187  	}
   188  
   189  	err = f.Close()
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	debug.Log("%d old cache dirs found", len(oldCacheDirs))
   195  
   196  	return oldCacheDirs, nil
   197  }
   198  
   199  // errNoSuchFile is returned when a file is not cached.
   200  type errNoSuchFile struct {
   201  	Type string
   202  	Name string
   203  }
   204  
   205  func (e errNoSuchFile) Error() string {
   206  	return fmt.Sprintf("file %v (%v) is not cached", e.Name, e.Type)
   207  }
   208  
   209  // IsNotExist returns true if the error was caused by a non-existing file.
   210  func (c *Cache) IsNotExist(err error) bool {
   211  	_, ok := errors.Cause(err).(errNoSuchFile)
   212  	return ok
   213  }
   214  
   215  // Wrap returns a backend with a cache.
   216  func (c *Cache) Wrap(be restic.Backend) restic.Backend {
   217  	return newBackend(be, c)
   218  }
   219  
   220  // BaseDir returns the base directory.
   221  func (c *Cache) BaseDir() string {
   222  	return c.Base
   223  }