github.com/lixiangzhong/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  }