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 }