github.com/maxnasonov/afero@v1.8.4/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) 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  }