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.
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."
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 {
25 cacheTime time.Duration
28 func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs {
29 return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime}
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:
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
43 // happens if someone writes directly to the overlay without
44 // going through this union
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)
53 return cacheHit, lfi, nil
55 if lfi.ModTime().Add(u.cacheTime).Before(time.Now()) {
56 bfi, err = u.base.Stat(name)
58 return cacheLocal, lfi, nil
60 if bfi.ModTime().After(lfi.ModTime()) {
61 return cacheStale, bfi, nil
64 return cacheHit, lfi, nil
67 if err == syscall.ENOENT || os.IsNotExist(err) {
68 return cacheMiss, nil, nil
71 return cacheMiss, nil, err
74 func (u *CacheOnReadFs) copyToLayer(name string) error {
75 return copyToLayer(u.base, u.layer, name)
78 func (u *CacheOnReadFs) Chtimes(name string, atime, mtime time.Time) error {
79 st, _, err := u.cacheStatus(name)
86 err = u.base.Chtimes(name, atime, mtime)
87 case cacheStale, cacheMiss:
88 if err := u.copyToLayer(name); err != nil {
91 err = u.base.Chtimes(name, atime, mtime)
96 return u.layer.Chtimes(name, atime, mtime)
99 func (u *CacheOnReadFs) Chmod(name string, mode os.FileMode) error {
100 st, _, err := u.cacheStatus(name)
107 err = u.base.Chmod(name, mode)
108 case cacheStale, cacheMiss:
109 if err := u.copyToLayer(name); err != nil {
112 err = u.base.Chmod(name, mode)
117 return u.layer.Chmod(name, mode)
120 func (u *CacheOnReadFs) Stat(name string) (os.FileInfo, error) {
121 st, fi, err := u.cacheStatus(name)
127 return u.base.Stat(name)
128 default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo
133 func (u *CacheOnReadFs) Rename(oldname, newname string) error {
134 st, _, err := u.cacheStatus(oldname)
141 err = u.base.Rename(oldname, newname)
142 case cacheStale, cacheMiss:
143 if err := u.copyToLayer(oldname); err != nil {
146 err = u.base.Rename(oldname, newname)
151 return u.layer.Rename(oldname, newname)
154 func (u *CacheOnReadFs) Remove(name string) error {
155 st, _, err := u.cacheStatus(name)
161 case cacheHit, cacheStale, cacheMiss:
162 err = u.base.Remove(name)
167 return u.layer.Remove(name)
170 func (u *CacheOnReadFs) RemoveAll(name string) error {
171 st, _, err := u.cacheStatus(name)
177 case cacheHit, cacheStale, cacheMiss:
178 err = u.base.RemoveAll(name)
183 return u.layer.RemoveAll(name)
186 func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
187 st, _, err := u.cacheStatus(name)
192 case cacheLocal, cacheHit:
194 if err := u.copyToLayer(name); err != nil {
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)
203 lfi, err := u.layer.OpenFile(name, flag, perm)
205 bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...?
208 return &UnionFile{Base: bfi, Layer: lfi}, nil
210 return u.layer.OpenFile(name, flag, perm)
213 func (u *CacheOnReadFs) Open(name string) (File, error) {
214 st, fi, err := u.cacheStatus(name)
221 return u.layer.Open(name)
224 bfi, err := u.base.Stat(name)
229 return u.base.Open(name)
231 if err := u.copyToLayer(name); err != nil {
234 return u.layer.Open(name)
238 if err := u.copyToLayer(name); err != nil {
241 return u.layer.Open(name)
245 return u.layer.Open(name)
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 {
254 return &UnionFile{Base: bfile, Layer: lfile}, nil
257 func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error {
258 err := u.base.Mkdir(name, perm)
262 return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache
265 func (u *CacheOnReadFs) Name() string {
266 return "CacheOnReadFs"
269 func (u *CacheOnReadFs) MkdirAll(name string, perm os.FileMode) error {
270 err := u.base.MkdirAll(name, perm)
274 return u.layer.MkdirAll(name, perm)
277 func (u *CacheOnReadFs) Create(name string) (File, error) {
278 bfh, err := u.base.Create(name)
282 lfh, err := u.layer.Create(name)
284 // oops, see comment about OS_TRUNC above, should we remove? then we have to
285 // remember if the file did not exist before
289 return &UnionFile{Base: bfh, Layer: lfh}, nil