github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/samples/cachingfs/caching_fs.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cachingfs 16 17 import ( 18 "context" 19 "crypto/rand" 20 "fmt" 21 "io" 22 "os" 23 "time" 24 25 "github.com/scaleoutsean/fusego" 26 "github.com/scaleoutsean/fusego/fuseops" 27 "github.com/scaleoutsean/fusego/fuseutil" 28 "github.com/jacobsa/syncutil" 29 ) 30 31 const ( 32 // Sizes of the files according to the file system. 33 FooSize = 123 34 BarSize = 456 35 ) 36 37 // A file system with a fixed structure that looks like this: 38 // 39 // foo 40 // dir/ 41 // bar 42 // 43 // The file system is configured with durations that specify how long to allow 44 // inode entries and attributes to be cached, used when responding to fuse 45 // requests. It also exposes methods for renumbering inodes and updating mtimes 46 // that are useful in testing that these durations are honored. 47 // 48 // Each file responds to reads with random contents. SetKeepCache can be used 49 // to control whether the response to OpenFileOp tells the kernel to keep the 50 // file's data in the page cache or not. 51 type CachingFS interface { 52 fuseutil.FileSystem 53 54 // Return the current inode ID of the file/directory with the given name. 55 FooID() fuseops.InodeID 56 DirID() fuseops.InodeID 57 BarID() fuseops.InodeID 58 59 // Cause the inode IDs to change to values that have never before been used. 60 RenumberInodes() 61 62 // Cause further queries for the attributes of inodes to use the supplied 63 // time as the inode's mtime. 64 SetMtime(mtime time.Time) 65 66 // Instruct the file system whether or not to reply to OpenFileOp with 67 // FOPEN_KEEP_CACHE set. 68 SetKeepCache(keep bool) 69 } 70 71 // Create a file system that issues cacheable responses according to the 72 // following rules: 73 // 74 // * LookUpInodeResponse.Entry.EntryExpiration is set according to 75 // lookupEntryTimeout. 76 // 77 // * GetInodeAttributesResponse.AttributesExpiration is set according to 78 // getattrTimeout. 79 // 80 // * Nothing else is marked cacheable. (In particular, the attributes 81 // returned by LookUpInode are not cacheable.) 82 // 83 func NewCachingFS( 84 lookupEntryTimeout time.Duration, 85 getattrTimeout time.Duration) (CachingFS, error) { 86 roundUp := func(n fuseops.InodeID) fuseops.InodeID { 87 return numInodes * ((n + numInodes - 1) / numInodes) 88 } 89 90 cfs := &cachingFS{ 91 lookupEntryTimeout: lookupEntryTimeout, 92 getattrTimeout: getattrTimeout, 93 baseID: roundUp(fuseops.RootInodeID + 1), 94 mtime: time.Now(), 95 } 96 97 cfs.mu = syncutil.NewInvariantMutex(cfs.checkInvariants) 98 99 return cfs, nil 100 } 101 102 const ( 103 // Inode IDs are issued such that "foo" always receives an ID that is 104 // congruent to fooOffset modulo numInodes, etc. 105 fooOffset = iota 106 dirOffset 107 barOffset 108 numInodes 109 ) 110 111 type cachingFS struct { 112 fuseutil.NotImplementedFileSystem 113 114 ///////////////////////// 115 // Constant data 116 ///////////////////////// 117 118 lookupEntryTimeout time.Duration 119 getattrTimeout time.Duration 120 121 ///////////////////////// 122 // Mutable state 123 ///////////////////////// 124 125 mu syncutil.InvariantMutex 126 127 // GUARDED_BY(mu) 128 keepPageCache bool 129 130 // The current ID of the lowest numbered non-root inode. 131 // 132 // INVARIANT: baseID > fuseops.RootInodeID 133 // INVARIANT: baseID % numInodes == 0 134 // 135 // GUARDED_BY(mu) 136 baseID fuseops.InodeID 137 138 // GUARDED_BY(mu) 139 mtime time.Time 140 } 141 142 //////////////////////////////////////////////////////////////////////// 143 // Helpers 144 //////////////////////////////////////////////////////////////////////// 145 146 func (fs *cachingFS) checkInvariants() { 147 // INVARIANT: baseID > fuseops.RootInodeID 148 // INVARIANT: baseID % numInodes == 0 149 if fs.baseID <= fuseops.RootInodeID || fs.baseID%numInodes != 0 { 150 panic(fmt.Sprintf("Bad baseID: %v", fs.baseID)) 151 } 152 } 153 154 // LOCKS_REQUIRED(fs.mu) 155 func (fs *cachingFS) fooID() fuseops.InodeID { 156 return fs.baseID + fooOffset 157 } 158 159 // LOCKS_REQUIRED(fs.mu) 160 func (fs *cachingFS) dirID() fuseops.InodeID { 161 return fs.baseID + dirOffset 162 } 163 164 // LOCKS_REQUIRED(fs.mu) 165 func (fs *cachingFS) barID() fuseops.InodeID { 166 return fs.baseID + barOffset 167 } 168 169 // LOCKS_REQUIRED(fs.mu) 170 func (fs *cachingFS) rootAttrs() fuseops.InodeAttributes { 171 return fuseops.InodeAttributes{ 172 Mode: os.ModeDir | 0777, 173 Mtime: fs.mtime, 174 } 175 } 176 177 // LOCKS_REQUIRED(fs.mu) 178 func (fs *cachingFS) fooAttrs() fuseops.InodeAttributes { 179 return fuseops.InodeAttributes{ 180 Nlink: 1, 181 Size: FooSize, 182 Mode: 0777, 183 Mtime: fs.mtime, 184 } 185 } 186 187 // LOCKS_REQUIRED(fs.mu) 188 func (fs *cachingFS) dirAttrs() fuseops.InodeAttributes { 189 return fuseops.InodeAttributes{ 190 Nlink: 1, 191 Mode: os.ModeDir | 0777, 192 Mtime: fs.mtime, 193 } 194 } 195 196 // LOCKS_REQUIRED(fs.mu) 197 func (fs *cachingFS) barAttrs() fuseops.InodeAttributes { 198 return fuseops.InodeAttributes{ 199 Nlink: 1, 200 Size: BarSize, 201 Mode: 0777, 202 Mtime: fs.mtime, 203 } 204 } 205 206 //////////////////////////////////////////////////////////////////////// 207 // Public interface 208 //////////////////////////////////////////////////////////////////////// 209 210 // LOCKS_EXCLUDED(fs.mu) 211 func (fs *cachingFS) FooID() fuseops.InodeID { 212 fs.mu.Lock() 213 defer fs.mu.Unlock() 214 215 return fs.fooID() 216 } 217 218 // LOCKS_EXCLUDED(fs.mu) 219 func (fs *cachingFS) DirID() fuseops.InodeID { 220 fs.mu.Lock() 221 defer fs.mu.Unlock() 222 223 return fs.dirID() 224 } 225 226 // LOCKS_EXCLUDED(fs.mu) 227 func (fs *cachingFS) BarID() fuseops.InodeID { 228 fs.mu.Lock() 229 defer fs.mu.Unlock() 230 231 return fs.barID() 232 } 233 234 // LOCKS_EXCLUDED(fs.mu) 235 func (fs *cachingFS) RenumberInodes() { 236 fs.mu.Lock() 237 defer fs.mu.Unlock() 238 239 fs.baseID += numInodes 240 } 241 242 // LOCKS_EXCLUDED(fs.mu) 243 func (fs *cachingFS) SetMtime(mtime time.Time) { 244 fs.mu.Lock() 245 defer fs.mu.Unlock() 246 247 fs.mtime = mtime 248 } 249 250 // LOCKS_EXCLUDED(fs.mu) 251 func (fs *cachingFS) SetKeepCache(keep bool) { 252 fs.mu.Lock() 253 defer fs.mu.Unlock() 254 255 fs.keepPageCache = keep 256 } 257 258 //////////////////////////////////////////////////////////////////////// 259 // FileSystem methods 260 //////////////////////////////////////////////////////////////////////// 261 262 func (fs *cachingFS) StatFS( 263 ctx context.Context, 264 op *fuseops.StatFSOp) error { 265 return nil 266 } 267 268 // LOCKS_EXCLUDED(fs.mu) 269 func (fs *cachingFS) LookUpInode( 270 ctx context.Context, 271 op *fuseops.LookUpInodeOp) error { 272 fs.mu.Lock() 273 defer fs.mu.Unlock() 274 275 // Find the ID and attributes. 276 var id fuseops.InodeID 277 var attrs fuseops.InodeAttributes 278 279 switch op.Name { 280 case "foo": 281 // Parent must be the root. 282 if op.Parent != fuseops.RootInodeID { 283 return fuse.ENOENT 284 } 285 286 id = fs.fooID() 287 attrs = fs.fooAttrs() 288 289 case "dir": 290 // Parent must be the root. 291 if op.Parent != fuseops.RootInodeID { 292 return fuse.ENOENT 293 } 294 295 id = fs.dirID() 296 attrs = fs.dirAttrs() 297 298 case "bar": 299 // Parent must be dir. 300 if op.Parent == fuseops.RootInodeID || op.Parent%numInodes != dirOffset { 301 return fuse.ENOENT 302 } 303 304 id = fs.barID() 305 attrs = fs.barAttrs() 306 307 default: 308 return fuse.ENOENT 309 } 310 311 // Fill in the response. 312 op.Entry.Child = id 313 op.Entry.Attributes = attrs 314 op.Entry.EntryExpiration = time.Now().Add(fs.lookupEntryTimeout) 315 316 return nil 317 } 318 319 // LOCKS_EXCLUDED(fs.mu) 320 func (fs *cachingFS) GetInodeAttributes( 321 ctx context.Context, 322 op *fuseops.GetInodeAttributesOp) error { 323 fs.mu.Lock() 324 defer fs.mu.Unlock() 325 326 // Figure out which inode the request is for. 327 var attrs fuseops.InodeAttributes 328 329 switch { 330 case op.Inode == fuseops.RootInodeID: 331 attrs = fs.rootAttrs() 332 333 case op.Inode%numInodes == fooOffset: 334 attrs = fs.fooAttrs() 335 336 case op.Inode%numInodes == dirOffset: 337 attrs = fs.dirAttrs() 338 339 case op.Inode%numInodes == barOffset: 340 attrs = fs.barAttrs() 341 } 342 343 // Fill in the response. 344 op.Attributes = attrs 345 op.AttributesExpiration = time.Now().Add(fs.getattrTimeout) 346 347 return nil 348 } 349 350 func (fs *cachingFS) OpenDir( 351 ctx context.Context, 352 op *fuseops.OpenDirOp) error { 353 return nil 354 } 355 356 func (fs *cachingFS) OpenFile( 357 ctx context.Context, 358 op *fuseops.OpenFileOp) error { 359 fs.mu.Lock() 360 defer fs.mu.Unlock() 361 362 op.KeepPageCache = fs.keepPageCache 363 364 return nil 365 } 366 367 func (fs *cachingFS) ReadFile( 368 ctx context.Context, 369 op *fuseops.ReadFileOp) error { 370 var err error 371 op.BytesRead, err = io.ReadFull(rand.Reader, op.Dst) 372 return err 373 }