github.com/millerlogic/afero@v1.1.1/cacheOnReadFs.go (about) 1 package afero 2 3 import ( 4 "os" 5 "syscall" 6 "time" 7 ) 8 9 // If the cache duration is 0, cache time will be unlimited, i.e. once 10 // a file is in the layer, the base will never be read again for this file. 11 // 12 // For cache times greater than 0, the modification time of a file is 13 // checked. Note that a lot of file system implementations only allow a 14 // resolution of a second for timestamps... or as the godoc for os.Chtimes() 15 // states: "The underlying filesystem may truncate or round the values to a 16 // less precise time unit." 17 // 18 // This caching union will forward all write calls also to the base file 19 // system first. To prevent writing to the base Fs, wrap it in a read-only 20 // filter - Note: this will also make the overlay read-only, for writing files 21 // in the overlay, use the overlay Fs directly, not via the union Fs. 22 type CacheOnReadFs struct { 23 base Fs 24 layer Fs 25 cacheTime time.Duration 26 } 27 28 func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs { 29 return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime} 30 } 31 32 type cacheState int 33 34 const ( 35 // not present in the overlay, unknown if it exists in the base: 36 cacheMiss cacheState = iota 37 // present in the overlay and in base, base file is newer: 38 cacheStale 39 // present in the overlay - with cache time == 0 it may exist in the base, 40 // with cacheTime > 0 it exists in the base and is same age or newer in the 41 // overlay 42 cacheHit 43 // happens if someone writes directly to the overlay without 44 // going through this union 45 cacheLocal 46 ) 47 48 func (u *CacheOnReadFs) cacheStatus(name string) (state cacheState, fi os.FileInfo, err error) { 49 var lfi, bfi os.FileInfo 50 lfi, err = u.layer.Stat(name) 51 if err == nil { 52 if u.cacheTime == 0 { 53 return cacheHit, lfi, nil 54 } 55 if lfi.ModTime().Add(u.cacheTime).Before(time.Now()) { 56 bfi, err = u.base.Stat(name) 57 if err != nil { 58 return cacheLocal, lfi, nil 59 } 60 if bfi.ModTime().After(lfi.ModTime()) { 61 return cacheStale, bfi, nil 62 } 63 } 64 return cacheHit, lfi, nil 65 } 66 67 if err == syscall.ENOENT || os.IsNotExist(err) { 68 return cacheMiss, nil, nil 69 } 70 71 return cacheMiss, nil, err 72 } 73 74 func (u *CacheOnReadFs) copyToLayer(name string) error { 75 return copyToLayer(u.base, u.layer, name) 76 } 77 78 func (u *CacheOnReadFs) Chtimes(name string, atime, mtime time.Time) error { 79 st, _, err := u.cacheStatus(name) 80 if err != nil { 81 return err 82 } 83 switch st { 84 case cacheLocal: 85 case cacheHit: 86 err = u.base.Chtimes(name, atime, mtime) 87 case cacheStale, cacheMiss: 88 if err := u.copyToLayer(name); err != nil { 89 return err 90 } 91 err = u.base.Chtimes(name, atime, mtime) 92 } 93 if err != nil { 94 return err 95 } 96 return u.layer.Chtimes(name, atime, mtime) 97 } 98 99 func (u *CacheOnReadFs) Chmod(name string, mode os.FileMode) error { 100 st, _, err := u.cacheStatus(name) 101 if err != nil { 102 return err 103 } 104 switch st { 105 case cacheLocal: 106 case cacheHit: 107 err = u.base.Chmod(name, mode) 108 case cacheStale, cacheMiss: 109 if err := u.copyToLayer(name); err != nil { 110 return err 111 } 112 err = u.base.Chmod(name, mode) 113 } 114 if err != nil { 115 return err 116 } 117 return u.layer.Chmod(name, mode) 118 } 119 120 func (u *CacheOnReadFs) Stat(name string) (os.FileInfo, error) { 121 st, fi, err := u.cacheStatus(name) 122 if err != nil { 123 return nil, err 124 } 125 switch st { 126 case cacheMiss: 127 return u.base.Stat(name) 128 default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo 129 return fi, nil 130 } 131 } 132 133 func (u *CacheOnReadFs) Rename(oldname, newname string) error { 134 st, _, err := u.cacheStatus(oldname) 135 if err != nil { 136 return err 137 } 138 switch st { 139 case cacheLocal: 140 case cacheHit: 141 err = u.base.Rename(oldname, newname) 142 case cacheStale, cacheMiss: 143 if err := u.copyToLayer(oldname); err != nil { 144 return err 145 } 146 err = u.base.Rename(oldname, newname) 147 } 148 if err != nil { 149 return err 150 } 151 return u.layer.Rename(oldname, newname) 152 } 153 154 func (u *CacheOnReadFs) Remove(name string) error { 155 st, _, err := u.cacheStatus(name) 156 if err != nil { 157 return err 158 } 159 switch st { 160 case cacheLocal: 161 case cacheHit, cacheStale, cacheMiss: 162 err = u.base.Remove(name) 163 } 164 if err != nil { 165 return err 166 } 167 return u.layer.Remove(name) 168 } 169 170 func (u *CacheOnReadFs) RemoveAll(name string) error { 171 st, _, err := u.cacheStatus(name) 172 if err != nil { 173 return err 174 } 175 switch st { 176 case cacheLocal: 177 case cacheHit, cacheStale, cacheMiss: 178 err = u.base.RemoveAll(name) 179 } 180 if err != nil { 181 return err 182 } 183 return u.layer.RemoveAll(name) 184 } 185 186 func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 187 st, _, err := u.cacheStatus(name) 188 if err != nil { 189 return nil, err 190 } 191 switch st { 192 case cacheLocal, cacheHit: 193 default: 194 if err := u.copyToLayer(name); err != nil { 195 return nil, err 196 } 197 } 198 if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { 199 bfi, err := u.base.OpenFile(name, flag, perm) 200 if err != nil { 201 return nil, err 202 } 203 lfi, err := u.layer.OpenFile(name, flag, perm) 204 if err != nil { 205 bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...? 206 return nil, err 207 } 208 return &UnionFile{Base: bfi, Layer: lfi}, nil 209 } 210 return u.layer.OpenFile(name, flag, perm) 211 } 212 213 func (u *CacheOnReadFs) Open(name string) (File, error) { 214 st, fi, err := u.cacheStatus(name) 215 if err != nil { 216 return nil, err 217 } 218 219 switch st { 220 case cacheLocal: 221 return u.layer.Open(name) 222 223 case cacheMiss: 224 bfi, err := u.base.Stat(name) 225 if err != nil { 226 return nil, err 227 } 228 if bfi.IsDir() { 229 return u.base.Open(name) 230 } 231 if err := u.copyToLayer(name); err != nil { 232 return nil, err 233 } 234 return u.layer.Open(name) 235 236 case cacheStale: 237 if !fi.IsDir() { 238 if err := u.copyToLayer(name); err != nil { 239 return nil, err 240 } 241 return u.layer.Open(name) 242 } 243 case cacheHit: 244 if !fi.IsDir() { 245 return u.layer.Open(name) 246 } 247 } 248 // the dirs from cacheHit, cacheStale fall down here: 249 bfile, _ := u.base.Open(name) 250 lfile, err := u.layer.Open(name) 251 if err != nil && bfile == nil { 252 return nil, err 253 } 254 return &UnionFile{Base: bfile, Layer: lfile}, nil 255 } 256 257 func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error { 258 err := u.base.Mkdir(name, perm) 259 if err != nil { 260 return err 261 } 262 return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache 263 } 264 265 func (u *CacheOnReadFs) Name() string { 266 return "CacheOnReadFs" 267 } 268 269 func (u *CacheOnReadFs) MkdirAll(name string, perm os.FileMode) error { 270 err := u.base.MkdirAll(name, perm) 271 if err != nil { 272 return err 273 } 274 return u.layer.MkdirAll(name, perm) 275 } 276 277 func (u *CacheOnReadFs) Create(name string) (File, error) { 278 bfh, err := u.base.Create(name) 279 if err != nil { 280 return nil, err 281 } 282 lfh, err := u.layer.Create(name) 283 if err != nil { 284 // oops, see comment about OS_TRUNC above, should we remove? then we have to 285 // remember if the file did not exist before 286 bfh.Close() 287 return nil, err 288 } 289 return &UnionFile{Base: bfh, Layer: lfh}, nil 290 }