cacheOnReadFs.go raw

   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  }
 316