github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/samples/memfs/inode.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 memfs 16 17 import ( 18 "fmt" 19 "io" 20 "os" 21 "time" 22 23 "github.com/scaleoutsean/fusego" 24 "github.com/scaleoutsean/fusego/fuseops" 25 "github.com/scaleoutsean/fusego/fuseutil" 26 ) 27 28 // Common attributes for files and directories. 29 // 30 // External synchronization is required. 31 type inode struct { 32 ///////////////////////// 33 // Mutable state 34 ///////////////////////// 35 36 // The current attributes of this inode. 37 // 38 // INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0 39 // INVARIANT: !(isDir() && isSymlink()) 40 // INVARIANT: attrs.Size == len(contents) 41 attrs fuseops.InodeAttributes 42 43 // For directories, entries describing the children of the directory. Unused 44 // entries are of type DT_Unknown. 45 // 46 // This array can never be shortened, nor can its elements be moved, because 47 // we use its indices for Dirent.Offset, which is exposed to the user who 48 // might be calling readdir in a loop while concurrently modifying the 49 // directory. Unused entries can, however, be reused. 50 // 51 // INVARIANT: If !isDir(), len(entries) == 0 52 // INVARIANT: For each i, entries[i].Offset == i+1 53 // INVARIANT: Contains no duplicate names in used entries. 54 entries []fuseutil.Dirent 55 56 // For files, the current contents of the file. 57 // 58 // INVARIANT: If !isFile(), len(contents) == 0 59 contents []byte 60 61 // For symlinks, the target of the symlink. 62 // 63 // INVARIANT: If !isSymlink(), len(target) == 0 64 target string 65 66 // extended attributes and values 67 xattrs map[string][]byte 68 } 69 70 //////////////////////////////////////////////////////////////////////// 71 // Helpers 72 //////////////////////////////////////////////////////////////////////// 73 74 // Create a new inode with the supplied attributes, which need not contain 75 // time-related information (the inode object will take care of that). 76 func newInode(attrs fuseops.InodeAttributes) *inode { 77 // Update time info. 78 now := time.Now() 79 attrs.Mtime = now 80 attrs.Crtime = now 81 82 // Create the object. 83 return &inode{ 84 attrs: attrs, 85 xattrs: make(map[string][]byte), 86 } 87 } 88 89 func (in *inode) CheckInvariants() { 90 // INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0 91 if !(in.attrs.Mode&^(os.ModePerm|os.ModeDir|os.ModeSymlink) == 0) { 92 panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode)) 93 } 94 95 // INVARIANT: !(isDir() && isSymlink()) 96 if in.isDir() && in.isSymlink() { 97 panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode)) 98 } 99 100 // INVARIANT: attrs.Size == len(contents) 101 if in.attrs.Size != uint64(len(in.contents)) { 102 panic(fmt.Sprintf( 103 "Size mismatch: %d vs. %d", 104 in.attrs.Size, 105 len(in.contents))) 106 } 107 108 // INVARIANT: If !isDir(), len(entries) == 0 109 if !in.isDir() && len(in.entries) != 0 { 110 panic(fmt.Sprintf("Unexpected entries length: %d", len(in.entries))) 111 } 112 113 // INVARIANT: For each i, entries[i].Offset == i+1 114 for i, e := range in.entries { 115 if !(e.Offset == fuseops.DirOffset(i+1)) { 116 panic(fmt.Sprintf("Unexpected offset for index %d: %d", i, e.Offset)) 117 } 118 } 119 120 // INVARIANT: Contains no duplicate names in used entries. 121 childNames := make(map[string]struct{}) 122 for _, e := range in.entries { 123 if e.Type != fuseutil.DT_Unknown { 124 if _, ok := childNames[e.Name]; ok { 125 panic(fmt.Sprintf("Duplicate name: %s", e.Name)) 126 } 127 128 childNames[e.Name] = struct{}{} 129 } 130 } 131 132 // INVARIANT: If !isFile(), len(contents) == 0 133 if !in.isFile() && len(in.contents) != 0 { 134 panic(fmt.Sprintf("Unexpected length: %d", len(in.contents))) 135 } 136 137 // INVARIANT: If !isSymlink(), len(target) == 0 138 if !in.isSymlink() && len(in.target) != 0 { 139 panic(fmt.Sprintf("Unexpected target length: %d", len(in.target))) 140 } 141 142 return 143 } 144 145 func (in *inode) isDir() bool { 146 return in.attrs.Mode&os.ModeDir != 0 147 } 148 149 func (in *inode) isSymlink() bool { 150 return in.attrs.Mode&os.ModeSymlink != 0 151 } 152 153 func (in *inode) isFile() bool { 154 return !(in.isDir() || in.isSymlink()) 155 } 156 157 // Return the index of the child within in.entries, if it exists. 158 // 159 // REQUIRES: in.isDir() 160 func (in *inode) findChild(name string) (i int, ok bool) { 161 if !in.isDir() { 162 panic("findChild called on non-directory.") 163 } 164 165 var e fuseutil.Dirent 166 for i, e = range in.entries { 167 if e.Name == name { 168 return i, true 169 } 170 } 171 172 return 0, false 173 } 174 175 //////////////////////////////////////////////////////////////////////// 176 // Public methods 177 //////////////////////////////////////////////////////////////////////// 178 179 // Return the number of children of the directory. 180 // 181 // REQUIRES: in.isDir() 182 func (in *inode) Len() int { 183 var n int 184 for _, e := range in.entries { 185 if e.Type != fuseutil.DT_Unknown { 186 n++ 187 } 188 } 189 190 return n 191 } 192 193 // Find an entry for the given child name and return its inode ID. 194 // 195 // REQUIRES: in.isDir() 196 func (in *inode) LookUpChild(name string) ( 197 id fuseops.InodeID, 198 typ fuseutil.DirentType, 199 ok bool) { 200 index, ok := in.findChild(name) 201 if ok { 202 id = in.entries[index].Inode 203 typ = in.entries[index].Type 204 } 205 206 return id, typ, ok 207 } 208 209 // Add an entry for a child. 210 // 211 // REQUIRES: in.isDir() 212 // REQUIRES: dt != fuseutil.DT_Unknown 213 func (in *inode) AddChild( 214 id fuseops.InodeID, 215 name string, 216 dt fuseutil.DirentType) { 217 var index int 218 219 // Update the modification time. 220 in.attrs.Mtime = time.Now() 221 222 // No matter where we place the entry, make sure it has the correct Offset 223 // field. 224 defer func() { 225 in.entries[index].Offset = fuseops.DirOffset(index + 1) 226 }() 227 228 // Set up the entry. 229 e := fuseutil.Dirent{ 230 Inode: id, 231 Name: name, 232 Type: dt, 233 } 234 235 // Look for a gap in which we can insert it. 236 for index = range in.entries { 237 if in.entries[index].Type == fuseutil.DT_Unknown { 238 in.entries[index] = e 239 return 240 } 241 } 242 243 // Append it to the end. 244 index = len(in.entries) 245 in.entries = append(in.entries, e) 246 } 247 248 // Remove an entry for a child. 249 // 250 // REQUIRES: in.isDir() 251 // REQUIRES: An entry for the given name exists. 252 func (in *inode) RemoveChild(name string) { 253 // Update the modification time. 254 in.attrs.Mtime = time.Now() 255 256 // Find the entry. 257 i, ok := in.findChild(name) 258 if !ok { 259 panic(fmt.Sprintf("Unknown child: %s", name)) 260 } 261 262 // Mark it as unused. 263 in.entries[i] = fuseutil.Dirent{ 264 Type: fuseutil.DT_Unknown, 265 Offset: fuseops.DirOffset(i + 1), 266 } 267 } 268 269 // Serve a ReadDir request. 270 // 271 // REQUIRES: in.isDir() 272 func (in *inode) ReadDir(p []byte, offset int) int { 273 if !in.isDir() { 274 panic("ReadDir called on non-directory.") 275 } 276 277 var n int 278 for i := offset; i < len(in.entries); i++ { 279 e := in.entries[i] 280 281 // Skip unused entries. 282 if e.Type == fuseutil.DT_Unknown { 283 continue 284 } 285 286 tmp := fuseutil.WriteDirent(p[n:], in.entries[i]) 287 if tmp == 0 { 288 break 289 } 290 291 n += tmp 292 } 293 294 return n 295 } 296 297 // Read from the file's contents. See documentation for ioutil.ReaderAt. 298 // 299 // REQUIRES: in.isFile() 300 func (in *inode) ReadAt(p []byte, off int64) (int, error) { 301 if !in.isFile() { 302 panic("ReadAt called on non-file.") 303 } 304 305 // Ensure the offset is in range. 306 if off > int64(len(in.contents)) { 307 return 0, io.EOF 308 } 309 310 // Read what we can. 311 n := copy(p, in.contents[off:]) 312 if n < len(p) { 313 return n, io.EOF 314 } 315 316 return n, nil 317 } 318 319 // Write to the file's contents. See documentation for ioutil.WriterAt. 320 // 321 // REQUIRES: in.isFile() 322 func (in *inode) WriteAt(p []byte, off int64) (int, error) { 323 if !in.isFile() { 324 panic("WriteAt called on non-file.") 325 } 326 327 // Update the modification time. 328 in.attrs.Mtime = time.Now() 329 330 // Ensure that the contents slice is long enough. 331 newLen := int(off) + len(p) 332 if len(in.contents) < newLen { 333 padding := make([]byte, newLen-len(in.contents)) 334 in.contents = append(in.contents, padding...) 335 in.attrs.Size = uint64(newLen) 336 } 337 338 // Copy in the data. 339 n := copy(in.contents[off:], p) 340 341 // Sanity check. 342 if n != len(p) { 343 panic(fmt.Sprintf("Unexpected short copy: %v", n)) 344 } 345 346 return n, nil 347 } 348 349 // Update attributes from non-nil parameters. 350 func (in *inode) SetAttributes( 351 size *uint64, 352 mode *os.FileMode, 353 mtime *time.Time) { 354 // Update the modification time. 355 in.attrs.Mtime = time.Now() 356 357 // Truncate? 358 if size != nil { 359 intSize := int(*size) 360 361 // Update contents. 362 if intSize <= len(in.contents) { 363 in.contents = in.contents[:intSize] 364 } else { 365 padding := make([]byte, intSize-len(in.contents)) 366 in.contents = append(in.contents, padding...) 367 } 368 369 // Update attributes. 370 in.attrs.Size = *size 371 } 372 373 // Change mode? 374 if mode != nil { 375 in.attrs.Mode = *mode 376 } 377 378 // Change mtime? 379 if mtime != nil { 380 in.attrs.Mtime = *mtime 381 } 382 } 383 384 func (in *inode) Fallocate(mode uint32, offset uint64, length uint64) error { 385 if mode != 0 { 386 return fuse.ENOSYS 387 } 388 newSize := int(offset + length) 389 if newSize > len(in.contents) { 390 padding := make([]byte, newSize-len(in.contents)) 391 in.contents = append(in.contents, padding...) 392 in.attrs.Size = offset + length 393 } 394 return nil 395 }