afero/cacheOnReadFs.go

291 lines
6.7 KiB
Go
Raw Normal View History

2016-01-02 09:52:40 +03:00
package afero
import (
"os"
"syscall"
"time"
)
// If the cache duration is 0, cache time will be unlimited, i.e. once
// a file is in the layer, the base will never be read again for this file.
//
// For cache times greater than 0, the modification time of a file is
// checked. Note that a lot of file system implementations only allow a
// resolution of a second for timestamps... or as the godoc for os.Chtimes()
// states: "The underlying filesystem may truncate or round the values to a
// less precise time unit."
//
// This caching union will forward all write calls also to the base file
// system first. To prevent writing to the base Fs, wrap it in a read-only
// filter - Note: this will also make the overlay read-only, for writing files
// in the overlay, use the overlay Fs directly, not via the union Fs.
2016-01-12 04:25:54 +03:00
type CacheOnReadFs struct {
2016-01-02 09:52:40 +03:00
base Fs
layer Fs
cacheTime time.Duration
}
func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs {
return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime}
}
2016-01-02 09:52:40 +03:00
type cacheState int
const (
// not present in the overlay, unknown if it exists in the base:
2016-10-04 06:03:51 +03:00
cacheMiss cacheState = iota
2016-01-02 09:52:40 +03:00
// present in the overlay and in base, base file is newer:
cacheStale
// present in the overlay - with cache time == 0 it may exist in the base,
// with cacheTime > 0 it exists in the base and is same age or newer in the
// overlay
cacheHit
// happens if someone writes directly to the overlay without
// going through this union
cacheLocal
)
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) cacheStatus(name string) (state cacheState, fi os.FileInfo, err error) {
2016-01-02 09:52:40 +03:00
var lfi, bfi os.FileInfo
lfi, err = u.layer.Stat(name)
if err == nil {
if u.cacheTime == 0 {
return cacheHit, lfi, nil
}
if lfi.ModTime().Add(u.cacheTime).Before(time.Now()) {
bfi, err = u.base.Stat(name)
if err != nil {
return cacheLocal, lfi, nil
}
if bfi.ModTime().After(lfi.ModTime()) {
return cacheStale, bfi, nil
}
}
return cacheHit, lfi, nil
}
if err == syscall.ENOENT || os.IsNotExist(err) {
2016-01-02 09:52:40 +03:00
return cacheMiss, nil, nil
}
2016-01-02 09:52:40 +03:00
return cacheMiss, nil, err
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) copyToLayer(name string) error {
2016-01-02 09:52:40 +03:00
return copyToLayer(u.base, u.layer, name)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Chtimes(name string, atime, mtime time.Time) error {
2016-01-02 09:52:40 +03:00
st, _, err := u.cacheStatus(name)
if err != nil {
return err
}
switch st {
case cacheLocal:
case cacheHit:
err = u.base.Chtimes(name, atime, mtime)
case cacheStale, cacheMiss:
if err := u.copyToLayer(name); err != nil {
return err
}
err = u.base.Chtimes(name, atime, mtime)
}
if err != nil {
return err
}
return u.layer.Chtimes(name, atime, mtime)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Chmod(name string, mode os.FileMode) error {
2016-01-02 09:52:40 +03:00
st, _, err := u.cacheStatus(name)
if err != nil {
return err
}
switch st {
case cacheLocal:
case cacheHit:
err = u.base.Chmod(name, mode)
case cacheStale, cacheMiss:
if err := u.copyToLayer(name); err != nil {
return err
}
err = u.base.Chmod(name, mode)
}
if err != nil {
return err
}
return u.layer.Chmod(name, mode)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Stat(name string) (os.FileInfo, error) {
2016-01-02 09:52:40 +03:00
st, fi, err := u.cacheStatus(name)
if err != nil {
return nil, err
}
switch st {
case cacheMiss:
return u.base.Stat(name)
default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo
return fi, nil
}
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Rename(oldname, newname string) error {
2016-01-02 09:52:40 +03:00
st, _, err := u.cacheStatus(oldname)
if err != nil {
return err
}
switch st {
case cacheLocal:
case cacheHit:
err = u.base.Rename(oldname, newname)
case cacheStale, cacheMiss:
if err := u.copyToLayer(oldname); err != nil {
return err
}
err = u.base.Rename(oldname, newname)
}
if err != nil {
return err
}
return u.layer.Rename(oldname, newname)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Remove(name string) error {
2016-01-02 09:52:40 +03:00
st, _, err := u.cacheStatus(name)
if err != nil {
return err
}
switch st {
case cacheLocal:
case cacheHit, cacheStale, cacheMiss:
err = u.base.Remove(name)
}
if err != nil {
return err
}
return u.layer.Remove(name)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) RemoveAll(name string) error {
2016-01-02 09:52:40 +03:00
st, _, err := u.cacheStatus(name)
if err != nil {
return err
}
switch st {
case cacheLocal:
case cacheHit, cacheStale, cacheMiss:
err = u.base.RemoveAll(name)
}
if err != nil {
return err
}
return u.layer.RemoveAll(name)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
2016-01-02 09:52:40 +03:00
st, _, err := u.cacheStatus(name)
if err != nil {
return nil, err
}
switch st {
case cacheLocal, cacheHit:
default:
if err := u.copyToLayer(name); err != nil {
return nil, err
}
}
if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {
bfi, err := u.base.OpenFile(name, flag, perm)
if err != nil {
return nil, err
}
lfi, err := u.layer.OpenFile(name, flag, perm)
if err != nil {
bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...?
return nil, err
}
return &UnionFile{base: bfi, layer: lfi}, nil
}
return u.layer.OpenFile(name, flag, perm)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Open(name string) (File, error) {
2016-01-02 09:52:40 +03:00
st, fi, err := u.cacheStatus(name)
if err != nil {
return nil, err
}
switch st {
case cacheLocal:
return u.layer.Open(name)
case cacheMiss:
bfi, err := u.base.Stat(name)
if err != nil {
return nil, err
}
if bfi.IsDir() {
return u.base.Open(name)
}
if err := u.copyToLayer(name); err != nil {
return nil, err
}
return u.layer.Open(name)
case cacheStale:
if !fi.IsDir() {
if err := u.copyToLayer(name); err != nil {
return nil, err
}
return u.layer.Open(name)
}
case cacheHit:
if !fi.IsDir() {
return u.layer.Open(name)
}
}
// the dirs from cacheHit, cacheStale fall down here:
bfile, _ := u.base.Open(name)
lfile, err := u.layer.Open(name)
if err != nil && bfile == nil {
return nil, err
}
return &UnionFile{base: bfile, layer: lfile}, nil
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error {
2016-01-02 09:52:40 +03:00
err := u.base.Mkdir(name, perm)
if err != nil {
return err
}
return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Name() string {
return "CacheOnReadFs"
2016-01-02 09:52:40 +03:00
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) MkdirAll(name string, perm os.FileMode) error {
2016-01-02 09:52:40 +03:00
err := u.base.MkdirAll(name, perm)
if err != nil {
return err
}
return u.layer.MkdirAll(name, perm)
}
2016-01-12 04:25:54 +03:00
func (u *CacheOnReadFs) Create(name string) (File, error) {
2016-01-02 09:52:40 +03:00
bfh, err := u.base.Create(name)
if err != nil {
return nil, err
}
lfh, err := u.layer.Create(name)
if err != nil {
// oops, see comment about OS_TRUNC above, should we remove? then we have to
// remember if the file did not exist before
bfh.Close()
return nil, err
}
return &UnionFile{base: bfh, layer: lfh}, nil
}