github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/cache/file.go (about) 1 package cache 2 3 import ( 4 "io" 5 "os" 6 "path/filepath" 7 8 "github.com/pkg/errors" 9 "github.com/restic/restic/internal/crypto" 10 "github.com/restic/restic/internal/debug" 11 "github.com/restic/restic/internal/fs" 12 "github.com/restic/restic/internal/restic" 13 ) 14 15 func (c *Cache) filename(h restic.Handle) string { 16 if len(h.Name) < 2 { 17 panic("Name is empty or too short") 18 } 19 subdir := h.Name[:2] 20 return filepath.Join(c.Path, cacheLayoutPaths[h.Type], subdir, h.Name) 21 } 22 23 func (c *Cache) canBeCached(t restic.FileType) bool { 24 if c == nil { 25 return false 26 } 27 28 if _, ok := cacheLayoutPaths[t]; !ok { 29 return false 30 } 31 32 return true 33 } 34 35 type readCloser struct { 36 io.Reader 37 io.Closer 38 } 39 40 // Load returns a reader that yields the contents of the file with the 41 // given handle. rd must be closed after use. If an error is returned, the 42 // ReadCloser is nil. 43 func (c *Cache) Load(h restic.Handle, length int, offset int64) (io.ReadCloser, error) { 44 debug.Log("Load from cache: %v", h) 45 if !c.canBeCached(h.Type) { 46 return nil, errors.New("cannot be cached") 47 } 48 49 f, err := fs.Open(c.filename(h)) 50 if err != nil { 51 return nil, errors.Wrap(err, "Open") 52 } 53 54 fi, err := f.Stat() 55 if err != nil { 56 _ = f.Close() 57 return nil, errors.Wrap(err, "Stat") 58 } 59 60 if fi.Size() <= crypto.Extension { 61 _ = f.Close() 62 _ = c.Remove(h) 63 return nil, errors.Errorf("cached file %v is truncated, removing", h) 64 } 65 66 if offset > 0 { 67 if _, err = f.Seek(offset, io.SeekStart); err != nil { 68 _ = f.Close() 69 return nil, err 70 } 71 } 72 73 rd := readCloser{Reader: f, Closer: f} 74 if length > 0 { 75 rd.Reader = io.LimitReader(f, int64(length)) 76 } 77 78 return rd, nil 79 } 80 81 // SaveWriter returns a writer for the cache object h. It must be closed after writing is finished. 82 func (c *Cache) SaveWriter(h restic.Handle) (io.WriteCloser, error) { 83 debug.Log("Save to cache: %v", h) 84 if !c.canBeCached(h.Type) { 85 return nil, errors.New("cannot be cached") 86 } 87 88 p := c.filename(h) 89 err := fs.MkdirAll(filepath.Dir(p), 0700) 90 if err != nil { 91 return nil, errors.Wrap(err, "MkdirAll") 92 } 93 94 f, err := fs.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0400) 95 if err != nil { 96 return nil, errors.Wrap(err, "Create") 97 } 98 99 return f, err 100 } 101 102 // Save saves a file in the cache. 103 func (c *Cache) Save(h restic.Handle, rd io.Reader) error { 104 debug.Log("Save to cache: %v", h) 105 if rd == nil { 106 return errors.New("Save() called with nil reader") 107 } 108 109 f, err := c.SaveWriter(h) 110 if err != nil { 111 return err 112 } 113 114 n, err := io.Copy(f, rd) 115 if err != nil { 116 _ = f.Close() 117 _ = c.Remove(h) 118 return errors.Wrap(err, "Copy") 119 } 120 121 if n <= crypto.Extension { 122 _ = f.Close() 123 _ = c.Remove(h) 124 return errors.Errorf("trying to cache truncated file %v", h) 125 } 126 127 if err = f.Close(); err != nil { 128 _ = c.Remove(h) 129 return errors.Wrap(err, "Close") 130 } 131 132 return nil 133 } 134 135 // Remove deletes a file. When the file is not cache, no error is returned. 136 func (c *Cache) Remove(h restic.Handle) error { 137 if !c.Has(h) { 138 return nil 139 } 140 141 return fs.Remove(c.filename(h)) 142 } 143 144 // Clear removes all files of type t from the cache that are not contained in 145 // the set valid. 146 func (c *Cache) Clear(t restic.FileType, valid restic.IDSet) error { 147 debug.Log("Clearing cache for %v: %v valid files", t, len(valid)) 148 if !c.canBeCached(t) { 149 return nil 150 } 151 152 list, err := c.list(t) 153 if err != nil { 154 return err 155 } 156 157 for id := range list { 158 if valid.Has(id) { 159 continue 160 } 161 162 if err = fs.Remove(c.filename(restic.Handle{Type: t, Name: id.String()})); err != nil { 163 return err 164 } 165 } 166 167 return nil 168 } 169 170 func isFile(fi os.FileInfo) bool { 171 return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0 172 } 173 174 // list returns a list of all files of type T in the cache. 175 func (c *Cache) list(t restic.FileType) (restic.IDSet, error) { 176 if !c.canBeCached(t) { 177 return nil, errors.New("cannot be cached") 178 } 179 180 list := restic.NewIDSet() 181 dir := filepath.Join(c.Path, cacheLayoutPaths[t]) 182 err := filepath.Walk(dir, func(name string, fi os.FileInfo, err error) error { 183 if err != nil { 184 return errors.Wrap(err, "Walk") 185 } 186 187 if !isFile(fi) { 188 return nil 189 } 190 191 id, err := restic.ParseID(filepath.Base(name)) 192 if err != nil { 193 return nil 194 } 195 196 list.Insert(id) 197 return nil 198 }) 199 200 return list, err 201 } 202 203 // Has returns true if the file is cached. 204 func (c *Cache) Has(h restic.Handle) bool { 205 if !c.canBeCached(h.Type) { 206 return false 207 } 208 209 _, err := fs.Stat(c.filename(h)) 210 if err == nil { 211 return true 212 } 213 214 return false 215 }