cacheOnReadFs.go (7495B)
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) copyFileToLayer(name string, flag int, perm os.FileMode) error { 79 return copyFileToLayer(u.base, u.layer, name, flag, perm) 80 } 81 82 func (u *CacheOnReadFs) Chtimes(name string, atime, mtime time.Time) error { 83 st, _, err := u.cacheStatus(name) 84 if err != nil { 85 return err 86 } 87 switch st { 88 case cacheLocal: 89 case cacheHit: 90 err = u.base.Chtimes(name, atime, mtime) 91 case cacheStale, cacheMiss: 92 if err := u.copyToLayer(name); err != nil { 93 return err 94 } 95 err = u.base.Chtimes(name, atime, mtime) 96 } 97 if err != nil { 98 return err 99 } 100 return u.layer.Chtimes(name, atime, mtime) 101 } 102 103 func (u *CacheOnReadFs) Chmod(name string, mode os.FileMode) error { 104 st, _, err := u.cacheStatus(name) 105 if err != nil { 106 return err 107 } 108 switch st { 109 case cacheLocal: 110 case cacheHit: 111 err = u.base.Chmod(name, mode) 112 case cacheStale, cacheMiss: 113 if err := u.copyToLayer(name); err != nil { 114 return err 115 } 116 err = u.base.Chmod(name, mode) 117 } 118 if err != nil { 119 return err 120 } 121 return u.layer.Chmod(name, mode) 122 } 123 124 func (u *CacheOnReadFs) Chown(name string, uid, gid int) error { 125 st, _, err := u.cacheStatus(name) 126 if err != nil { 127 return err 128 } 129 switch st { 130 case cacheLocal: 131 case cacheHit: 132 err = u.base.Chown(name, uid, gid) 133 case cacheStale, cacheMiss: 134 if err := u.copyToLayer(name); err != nil { 135 return err 136 } 137 err = u.base.Chown(name, uid, gid) 138 } 139 if err != nil { 140 return err 141 } 142 return u.layer.Chown(name, uid, gid) 143 } 144 145 func (u *CacheOnReadFs) Stat(name string) (os.FileInfo, error) { 146 st, fi, err := u.cacheStatus(name) 147 if err != nil { 148 return nil, err 149 } 150 switch st { 151 case cacheMiss: 152 return u.base.Stat(name) 153 default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo 154 return fi, nil 155 } 156 } 157 158 func (u *CacheOnReadFs) Rename(oldname, newname string) error { 159 st, _, err := u.cacheStatus(oldname) 160 if err != nil { 161 return err 162 } 163 switch st { 164 case cacheLocal: 165 case cacheHit: 166 err = u.base.Rename(oldname, newname) 167 case cacheStale, cacheMiss: 168 if err := u.copyToLayer(oldname); err != nil { 169 return err 170 } 171 err = u.base.Rename(oldname, newname) 172 } 173 if err != nil { 174 return err 175 } 176 return u.layer.Rename(oldname, newname) 177 } 178 179 func (u *CacheOnReadFs) Remove(name string) error { 180 st, _, err := u.cacheStatus(name) 181 if err != nil { 182 return err 183 } 184 switch st { 185 case cacheLocal: 186 case cacheHit, cacheStale, cacheMiss: 187 err = u.base.Remove(name) 188 } 189 if err != nil { 190 return err 191 } 192 return u.layer.Remove(name) 193 } 194 195 func (u *CacheOnReadFs) RemoveAll(name string) error { 196 st, _, err := u.cacheStatus(name) 197 if err != nil { 198 return err 199 } 200 switch st { 201 case cacheLocal: 202 case cacheHit, cacheStale, cacheMiss: 203 err = u.base.RemoveAll(name) 204 } 205 if err != nil { 206 return err 207 } 208 return u.layer.RemoveAll(name) 209 } 210 211 func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 212 st, _, err := u.cacheStatus(name) 213 if err != nil { 214 return nil, err 215 } 216 switch st { 217 case cacheLocal, cacheHit: 218 default: 219 if err := u.copyFileToLayer(name, flag, perm); err != nil { 220 return nil, err 221 } 222 } 223 if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { 224 bfi, err := u.base.OpenFile(name, flag, perm) 225 if err != nil { 226 return nil, err 227 } 228 lfi, err := u.layer.OpenFile(name, flag, perm) 229 if err != nil { 230 bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...? 231 return nil, err 232 } 233 return &UnionFile{Base: bfi, Layer: lfi}, nil 234 } 235 return u.layer.OpenFile(name, flag, perm) 236 } 237 238 func (u *CacheOnReadFs) Open(name string) (File, error) { 239 st, fi, err := u.cacheStatus(name) 240 if err != nil { 241 return nil, err 242 } 243 244 switch st { 245 case cacheLocal: 246 return u.layer.Open(name) 247 248 case cacheMiss: 249 bfi, err := u.base.Stat(name) 250 if err != nil { 251 return nil, err 252 } 253 if bfi.IsDir() { 254 return u.base.Open(name) 255 } 256 if err := u.copyToLayer(name); err != nil { 257 return nil, err 258 } 259 return u.layer.Open(name) 260 261 case cacheStale: 262 if !fi.IsDir() { 263 if err := u.copyToLayer(name); err != nil { 264 return nil, err 265 } 266 return u.layer.Open(name) 267 } 268 case cacheHit: 269 if !fi.IsDir() { 270 return u.layer.Open(name) 271 } 272 } 273 // the dirs from cacheHit, cacheStale fall down here: 274 bfile, _ := u.base.Open(name) 275 lfile, err := u.layer.Open(name) 276 if err != nil && bfile == nil { 277 return nil, err 278 } 279 return &UnionFile{Base: bfile, Layer: lfile}, nil 280 } 281 282 func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error { 283 err := u.base.Mkdir(name, perm) 284 if err != nil { 285 return err 286 } 287 return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache 288 } 289 290 func (u *CacheOnReadFs) Name() string { 291 return "CacheOnReadFs" 292 } 293 294 func (u *CacheOnReadFs) MkdirAll(name string, perm os.FileMode) error { 295 err := u.base.MkdirAll(name, perm) 296 if err != nil { 297 return err 298 } 299 return u.layer.MkdirAll(name, perm) 300 } 301 302 func (u *CacheOnReadFs) Create(name string) (File, error) { 303 bfh, err := u.base.Create(name) 304 if err != nil { 305 return nil, err 306 } 307 lfh, err := u.layer.Create(name) 308 if err != nil { 309 // oops, see comment about OS_TRUNC above, should we remove? then we have to 310 // remember if the file did not exist before 311 bfh.Close() 312 return nil, err 313 } 314 return &UnionFile{Base: bfh, Layer: lfh}, nil 315 }