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