github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/vfs/mem_fs.go (about) 1 // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package vfs // import "github.com/cockroachdb/pebble/vfs" 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "os" 12 "path" 13 "slices" 14 "sort" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "syscall" 19 "time" 20 21 "github.com/cockroachdb/errors" 22 "github.com/cockroachdb/errors/oserror" 23 "github.com/cockroachdb/pebble/internal/invariants" 24 ) 25 26 const sep = "/" 27 28 // NewMem returns a new memory-backed FS implementation. 29 func NewMem() *MemFS { 30 return &MemFS{ 31 root: newRootMemNode(), 32 } 33 } 34 35 // NewStrictMem returns a "strict" memory-backed FS implementation. The behaviour is strict wrt 36 // needing a Sync() call on files or directories for the state changes to be finalized. Any 37 // changes that are not finalized are visible to reads until MemFS.ResetToSyncedState() is called, 38 // at which point they are discarded and no longer visible. 39 // 40 // Expected usage: 41 // 42 // strictFS := NewStrictMem() 43 // db := Open(..., &Options{FS: strictFS}) 44 // // Do and commit various operations. 45 // ... 46 // // Prevent any more changes to finalized state. 47 // strictFS.SetIgnoreSyncs(true) 48 // // This will finish any ongoing background flushes, compactions but none of these writes will 49 // // be finalized since syncs are being ignored. 50 // db.Close() 51 // // Discard unsynced state. 52 // strictFS.ResetToSyncedState() 53 // // Allow changes to finalized state. 54 // strictFS.SetIgnoreSyncs(false) 55 // // Open the DB. This DB should have the same state as if the earlier strictFS operations and 56 // // db.Close() were not called. 57 // db := Open(..., &Options{FS: strictFS}) 58 func NewStrictMem() *MemFS { 59 return &MemFS{ 60 root: newRootMemNode(), 61 strict: true, 62 } 63 } 64 65 // NewMemFile returns a memory-backed File implementation. The memory-backed 66 // file takes ownership of data. 67 func NewMemFile(data []byte) File { 68 n := &memNode{} 69 n.refs.Store(1) 70 n.mu.data = data 71 n.mu.modTime = time.Now() 72 return &memFile{ 73 n: n, 74 read: true, 75 } 76 } 77 78 // MemFS implements FS. 79 type MemFS struct { 80 mu sync.Mutex 81 root *memNode 82 83 // lockFiles holds a map of open file locks. Presence in this map indicates 84 // a file lock is currently held. Keys are strings holding the path of the 85 // locked file. The stored value is untyped and unused; only presence of 86 // the key within the map is significant. 87 lockedFiles sync.Map 88 strict bool 89 ignoreSyncs bool 90 // Windows has peculiar semantics with respect to hard links and deleting 91 // open files. In tests meant to exercise this behavior, this flag can be 92 // set to error if removing an open file. 93 windowsSemantics bool 94 } 95 96 var _ FS = &MemFS{} 97 98 // UseWindowsSemantics configures whether the MemFS implements Windows-style 99 // semantics, in particular with respect to whether any of an open file's links 100 // may be removed. Windows semantics default to off. 101 func (y *MemFS) UseWindowsSemantics(windowsSemantics bool) { 102 y.mu.Lock() 103 defer y.mu.Unlock() 104 y.windowsSemantics = windowsSemantics 105 } 106 107 // String dumps the contents of the MemFS. 108 func (y *MemFS) String() string { 109 y.mu.Lock() 110 defer y.mu.Unlock() 111 112 s := new(bytes.Buffer) 113 y.root.dump(s, 0) 114 return s.String() 115 } 116 117 // SetIgnoreSyncs sets the MemFS.ignoreSyncs field. See the usage comment with NewStrictMem() for 118 // details. 119 func (y *MemFS) SetIgnoreSyncs(ignoreSyncs bool) { 120 if !y.strict { 121 panic("SetIgnoreSyncs can only be used on a strict MemFS") 122 } 123 y.mu.Lock() 124 y.ignoreSyncs = ignoreSyncs 125 y.mu.Unlock() 126 } 127 128 // ResetToSyncedState discards state in the FS that is not synced. See the usage comment with 129 // NewStrictMem() for details. 130 func (y *MemFS) ResetToSyncedState() { 131 if !y.strict { 132 panic("ResetToSyncedState can only be used on a strict MemFS") 133 } 134 y.mu.Lock() 135 y.root.resetToSyncedState() 136 y.mu.Unlock() 137 } 138 139 // walk walks the directory tree for the fullname, calling f at each step. If 140 // f returns an error, the walk will be aborted and return that same error. 141 // 142 // Each walk is atomic: y's mutex is held for the entire operation, including 143 // all calls to f. 144 // 145 // dir is the directory at that step, frag is the name fragment, and final is 146 // whether it is the final step. For example, walking "/foo/bar/x" will result 147 // in 3 calls to f: 148 // - "/", "foo", false 149 // - "/foo/", "bar", false 150 // - "/foo/bar/", "x", true 151 // 152 // Similarly, walking "/y/z/", with a trailing slash, will result in 3 calls to f: 153 // - "/", "y", false 154 // - "/y/", "z", false 155 // - "/y/z/", "", true 156 func (y *MemFS) walk(fullname string, f func(dir *memNode, frag string, final bool) error) error { 157 y.mu.Lock() 158 defer y.mu.Unlock() 159 160 // For memfs, the current working directory is the same as the root directory, 161 // so we strip off any leading "/"s to make fullname a relative path, and 162 // the walk starts at y.root. 163 for len(fullname) > 0 && fullname[0] == sep[0] { 164 fullname = fullname[1:] 165 } 166 dir := y.root 167 168 for { 169 frag, remaining := fullname, "" 170 i := strings.IndexRune(fullname, rune(sep[0])) 171 final := i < 0 172 if !final { 173 frag, remaining = fullname[:i], fullname[i+1:] 174 for len(remaining) > 0 && remaining[0] == sep[0] { 175 remaining = remaining[1:] 176 } 177 } 178 if err := f(dir, frag, final); err != nil { 179 return err 180 } 181 if final { 182 break 183 } 184 child := dir.children[frag] 185 if child == nil { 186 return &os.PathError{ 187 Op: "open", 188 Path: fullname, 189 Err: oserror.ErrNotExist, 190 } 191 } 192 if !child.isDir { 193 return &os.PathError{ 194 Op: "open", 195 Path: fullname, 196 Err: errors.New("not a directory"), 197 } 198 } 199 dir, fullname = child, remaining 200 } 201 return nil 202 } 203 204 // Create implements FS.Create. 205 func (y *MemFS) Create(fullname string) (File, error) { 206 var ret *memFile 207 err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { 208 if final { 209 if frag == "" { 210 return errors.New("pebble/vfs: empty file name") 211 } 212 n := &memNode{name: frag} 213 dir.children[frag] = n 214 ret = &memFile{ 215 n: n, 216 fs: y, 217 read: true, 218 write: true, 219 } 220 } 221 return nil 222 }) 223 if err != nil { 224 return nil, err 225 } 226 ret.n.refs.Add(1) 227 return ret, nil 228 } 229 230 // Link implements FS.Link. 231 func (y *MemFS) Link(oldname, newname string) error { 232 var n *memNode 233 err := y.walk(oldname, func(dir *memNode, frag string, final bool) error { 234 if final { 235 if frag == "" { 236 return errors.New("pebble/vfs: empty file name") 237 } 238 n = dir.children[frag] 239 } 240 return nil 241 }) 242 if err != nil { 243 return err 244 } 245 if n == nil { 246 return &os.LinkError{ 247 Op: "link", 248 Old: oldname, 249 New: newname, 250 Err: oserror.ErrNotExist, 251 } 252 } 253 return y.walk(newname, func(dir *memNode, frag string, final bool) error { 254 if final { 255 if frag == "" { 256 return errors.New("pebble/vfs: empty file name") 257 } 258 if _, ok := dir.children[frag]; ok { 259 return &os.LinkError{ 260 Op: "link", 261 Old: oldname, 262 New: newname, 263 Err: oserror.ErrExist, 264 } 265 } 266 dir.children[frag] = n 267 } 268 return nil 269 }) 270 } 271 272 func (y *MemFS) open(fullname string, openForWrite bool) (File, error) { 273 var ret *memFile 274 err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { 275 if final { 276 if frag == "" { 277 ret = &memFile{ 278 n: dir, 279 fs: y, 280 } 281 return nil 282 } 283 if n := dir.children[frag]; n != nil { 284 ret = &memFile{ 285 n: n, 286 fs: y, 287 read: true, 288 write: openForWrite, 289 } 290 } 291 } 292 return nil 293 }) 294 if err != nil { 295 return nil, err 296 } 297 if ret == nil { 298 return nil, &os.PathError{ 299 Op: "open", 300 Path: fullname, 301 Err: oserror.ErrNotExist, 302 } 303 } 304 ret.n.refs.Add(1) 305 return ret, nil 306 } 307 308 // Open implements FS.Open. 309 func (y *MemFS) Open(fullname string, opts ...OpenOption) (File, error) { 310 return y.open(fullname, false /* openForWrite */) 311 } 312 313 // OpenReadWrite implements FS.OpenReadWrite. 314 func (y *MemFS) OpenReadWrite(fullname string, opts ...OpenOption) (File, error) { 315 f, err := y.open(fullname, true /* openForWrite */) 316 pathErr, ok := err.(*os.PathError) 317 if ok && pathErr.Err == oserror.ErrNotExist { 318 return y.Create(fullname) 319 } 320 return f, err 321 } 322 323 // OpenDir implements FS.OpenDir. 324 func (y *MemFS) OpenDir(fullname string) (File, error) { 325 return y.open(fullname, false /* openForWrite */) 326 } 327 328 // Remove implements FS.Remove. 329 func (y *MemFS) Remove(fullname string) error { 330 return y.walk(fullname, func(dir *memNode, frag string, final bool) error { 331 if final { 332 if frag == "" { 333 return errors.New("pebble/vfs: empty file name") 334 } 335 child, ok := dir.children[frag] 336 if !ok { 337 return oserror.ErrNotExist 338 } 339 if y.windowsSemantics { 340 // Disallow removal of open files/directories which implements 341 // Windows semantics. This ensures that we don't regress in the 342 // ordering of operations and try to remove a file while it is 343 // still open. 344 if n := child.refs.Load(); n > 0 { 345 return oserror.ErrInvalid 346 } 347 } 348 if len(child.children) > 0 { 349 return errNotEmpty 350 } 351 delete(dir.children, frag) 352 } 353 return nil 354 }) 355 } 356 357 // RemoveAll implements FS.RemoveAll. 358 func (y *MemFS) RemoveAll(fullname string) error { 359 err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { 360 if final { 361 if frag == "" { 362 return errors.New("pebble/vfs: empty file name") 363 } 364 _, ok := dir.children[frag] 365 if !ok { 366 return nil 367 } 368 delete(dir.children, frag) 369 } 370 return nil 371 }) 372 // Match os.RemoveAll which returns a nil error even if the parent 373 // directories don't exist. 374 if oserror.IsNotExist(err) { 375 err = nil 376 } 377 return err 378 } 379 380 // Rename implements FS.Rename. 381 func (y *MemFS) Rename(oldname, newname string) error { 382 var n *memNode 383 err := y.walk(oldname, func(dir *memNode, frag string, final bool) error { 384 if final { 385 if frag == "" { 386 return errors.New("pebble/vfs: empty file name") 387 } 388 n = dir.children[frag] 389 delete(dir.children, frag) 390 } 391 return nil 392 }) 393 if err != nil { 394 return err 395 } 396 if n == nil { 397 return &os.PathError{ 398 Op: "open", 399 Path: oldname, 400 Err: oserror.ErrNotExist, 401 } 402 } 403 return y.walk(newname, func(dir *memNode, frag string, final bool) error { 404 if final { 405 if frag == "" { 406 return errors.New("pebble/vfs: empty file name") 407 } 408 dir.children[frag] = n 409 n.name = frag 410 } 411 return nil 412 }) 413 } 414 415 // ReuseForWrite implements FS.ReuseForWrite. 416 func (y *MemFS) ReuseForWrite(oldname, newname string) (File, error) { 417 if err := y.Rename(oldname, newname); err != nil { 418 return nil, err 419 } 420 f, err := y.Open(newname) 421 if err != nil { 422 return nil, err 423 } 424 y.mu.Lock() 425 defer y.mu.Unlock() 426 427 mf := f.(*memFile) 428 mf.read = false 429 mf.write = true 430 return f, nil 431 } 432 433 // MkdirAll implements FS.MkdirAll. 434 func (y *MemFS) MkdirAll(dirname string, perm os.FileMode) error { 435 return y.walk(dirname, func(dir *memNode, frag string, final bool) error { 436 if frag == "" { 437 if final { 438 return nil 439 } 440 return errors.New("pebble/vfs: empty file name") 441 } 442 child := dir.children[frag] 443 if child == nil { 444 dir.children[frag] = &memNode{ 445 name: frag, 446 children: make(map[string]*memNode), 447 isDir: true, 448 } 449 return nil 450 } 451 if !child.isDir { 452 return &os.PathError{ 453 Op: "open", 454 Path: dirname, 455 Err: errors.New("not a directory"), 456 } 457 } 458 return nil 459 }) 460 } 461 462 // Lock implements FS.Lock. 463 func (y *MemFS) Lock(fullname string) (io.Closer, error) { 464 // FS.Lock excludes other processes, but other processes cannot see this 465 // process' memory. However some uses (eg, Cockroach tests) may open and 466 // close the same MemFS-backed database multiple times. We want mutual 467 // exclusion in this case too. See cockroachdb/cockroach#110645. 468 _, loaded := y.lockedFiles.Swap(fullname, nil /* the value itself is insignificant */) 469 if loaded { 470 // This file lock has already been acquired. On unix, this results in 471 // either EACCES or EAGAIN so we mimic. 472 return nil, syscall.EAGAIN 473 } 474 // Otherwise, we successfully acquired the lock. Locks are visible in the 475 // parent directory listing, and they also must be created under an existent 476 // directory. Create the path so that we have the normal detection of 477 // non-existent directory paths, and make the lock visible when listing 478 // directory entries. 479 f, err := y.Create(fullname) 480 if err != nil { 481 // "Release" the lock since we failed. 482 y.lockedFiles.Delete(fullname) 483 return nil, err 484 } 485 return &memFileLock{ 486 y: y, 487 f: f, 488 fullname: fullname, 489 }, nil 490 } 491 492 // List implements FS.List. 493 func (y *MemFS) List(dirname string) ([]string, error) { 494 if !strings.HasSuffix(dirname, sep) { 495 dirname += sep 496 } 497 var ret []string 498 err := y.walk(dirname, func(dir *memNode, frag string, final bool) error { 499 if final { 500 if frag != "" { 501 panic("unreachable") 502 } 503 ret = make([]string, 0, len(dir.children)) 504 for s := range dir.children { 505 ret = append(ret, s) 506 } 507 } 508 return nil 509 }) 510 return ret, err 511 } 512 513 // Stat implements FS.Stat. 514 func (y *MemFS) Stat(name string) (os.FileInfo, error) { 515 f, err := y.Open(name) 516 if err != nil { 517 if pe, ok := err.(*os.PathError); ok { 518 pe.Op = "stat" 519 } 520 return nil, err 521 } 522 defer f.Close() 523 return f.Stat() 524 } 525 526 // PathBase implements FS.PathBase. 527 func (*MemFS) PathBase(p string) string { 528 // Note that MemFS uses forward slashes for its separator, hence the use of 529 // path.Base, not filepath.Base. 530 return path.Base(p) 531 } 532 533 // PathJoin implements FS.PathJoin. 534 func (*MemFS) PathJoin(elem ...string) string { 535 // Note that MemFS uses forward slashes for its separator, hence the use of 536 // path.Join, not filepath.Join. 537 return path.Join(elem...) 538 } 539 540 // PathDir implements FS.PathDir. 541 func (*MemFS) PathDir(p string) string { 542 // Note that MemFS uses forward slashes for its separator, hence the use of 543 // path.Dir, not filepath.Dir. 544 return path.Dir(p) 545 } 546 547 // GetDiskUsage implements FS.GetDiskUsage. 548 func (*MemFS) GetDiskUsage(string) (DiskUsage, error) { 549 return DiskUsage{}, ErrUnsupported 550 } 551 552 // memNode holds a file's data or a directory's children, and implements os.FileInfo. 553 type memNode struct { 554 name string 555 isDir bool 556 refs atomic.Int32 557 558 // Mutable state. 559 // - For a file: data, syncedDate, modTime: A file is only being mutated by a single goroutine, 560 // but there can be concurrent readers e.g. DB.Checkpoint() which can read WAL or MANIFEST 561 // files that are being written to. Additionally Sync() calls can be concurrent with writing. 562 // - For a directory: children and syncedChildren. Concurrent writes are possible, and 563 // these are protected using MemFS.mu. 564 mu struct { 565 sync.Mutex 566 data []byte 567 syncedData []byte 568 modTime time.Time 569 } 570 571 children map[string]*memNode 572 syncedChildren map[string]*memNode 573 } 574 575 func newRootMemNode() *memNode { 576 return &memNode{ 577 name: "/", // set the name to match what file systems do 578 children: make(map[string]*memNode), 579 isDir: true, 580 } 581 } 582 583 func (f *memNode) IsDir() bool { 584 return f.isDir 585 } 586 587 func (f *memNode) ModTime() time.Time { 588 f.mu.Lock() 589 defer f.mu.Unlock() 590 return f.mu.modTime 591 } 592 593 func (f *memNode) Mode() os.FileMode { 594 if f.isDir { 595 return os.ModeDir | 0755 596 } 597 return 0755 598 } 599 600 func (f *memNode) Name() string { 601 return f.name 602 } 603 604 func (f *memNode) Size() int64 { 605 f.mu.Lock() 606 defer f.mu.Unlock() 607 return int64(len(f.mu.data)) 608 } 609 610 func (f *memNode) Sys() interface{} { 611 return nil 612 } 613 614 func (f *memNode) dump(w *bytes.Buffer, level int) { 615 if f.isDir { 616 w.WriteString(" ") 617 } else { 618 f.mu.Lock() 619 fmt.Fprintf(w, "%8d ", len(f.mu.data)) 620 f.mu.Unlock() 621 } 622 for i := 0; i < level; i++ { 623 w.WriteString(" ") 624 } 625 w.WriteString(f.name) 626 if !f.isDir { 627 w.WriteByte('\n') 628 return 629 } 630 if level > 0 { // deal with the fact that the root's name is already "/" 631 w.WriteByte(sep[0]) 632 } 633 w.WriteByte('\n') 634 names := make([]string, 0, len(f.children)) 635 for name := range f.children { 636 names = append(names, name) 637 } 638 sort.Strings(names) 639 for _, name := range names { 640 f.children[name].dump(w, level+1) 641 } 642 } 643 644 func (f *memNode) resetToSyncedState() { 645 if f.isDir { 646 f.children = make(map[string]*memNode) 647 for k, v := range f.syncedChildren { 648 f.children[k] = v 649 } 650 for _, v := range f.children { 651 v.resetToSyncedState() 652 } 653 } else { 654 f.mu.Lock() 655 f.mu.data = slices.Clone(f.mu.syncedData) 656 f.mu.Unlock() 657 } 658 } 659 660 // memFile is a reader or writer of a node's data, and implements File. 661 type memFile struct { 662 n *memNode 663 fs *MemFS // nil for a standalone memFile 664 rpos int 665 wpos int 666 read, write bool 667 } 668 669 var _ File = (*memFile)(nil) 670 671 func (f *memFile) Close() error { 672 if n := f.n.refs.Add(-1); n < 0 { 673 panic(fmt.Sprintf("pebble: close of unopened file: %d", n)) 674 } 675 f.n = nil 676 return nil 677 } 678 679 func (f *memFile) Read(p []byte) (int, error) { 680 if !f.read { 681 return 0, errors.New("pebble/vfs: file was not opened for reading") 682 } 683 if f.n.isDir { 684 return 0, errors.New("pebble/vfs: cannot read a directory") 685 } 686 f.n.mu.Lock() 687 defer f.n.mu.Unlock() 688 if f.rpos >= len(f.n.mu.data) { 689 return 0, io.EOF 690 } 691 n := copy(p, f.n.mu.data[f.rpos:]) 692 f.rpos += n 693 return n, nil 694 } 695 696 func (f *memFile) ReadAt(p []byte, off int64) (int, error) { 697 if !f.read { 698 return 0, errors.New("pebble/vfs: file was not opened for reading") 699 } 700 if f.n.isDir { 701 return 0, errors.New("pebble/vfs: cannot read a directory") 702 } 703 f.n.mu.Lock() 704 defer f.n.mu.Unlock() 705 if off >= int64(len(f.n.mu.data)) { 706 return 0, io.EOF 707 } 708 n := copy(p, f.n.mu.data[off:]) 709 if n < len(p) { 710 return n, io.EOF 711 } 712 return n, nil 713 } 714 715 func (f *memFile) Write(p []byte) (int, error) { 716 if !f.write { 717 return 0, errors.New("pebble/vfs: file was not created for writing") 718 } 719 if f.n.isDir { 720 return 0, errors.New("pebble/vfs: cannot write a directory") 721 } 722 f.n.mu.Lock() 723 defer f.n.mu.Unlock() 724 f.n.mu.modTime = time.Now() 725 if f.wpos+len(p) <= len(f.n.mu.data) { 726 n := copy(f.n.mu.data[f.wpos:f.wpos+len(p)], p) 727 if n != len(p) { 728 panic("stuff") 729 } 730 } else { 731 f.n.mu.data = append(f.n.mu.data[:f.wpos], p...) 732 } 733 f.wpos += len(p) 734 735 if invariants.Enabled { 736 // Mutate the input buffer to flush out bugs in Pebble which expect the 737 // input buffer to be unmodified. 738 for i := range p { 739 p[i] ^= 0xff 740 } 741 } 742 return len(p), nil 743 } 744 745 func (f *memFile) WriteAt(p []byte, ofs int64) (int, error) { 746 if !f.write { 747 return 0, errors.New("pebble/vfs: file was not created for writing") 748 } 749 if f.n.isDir { 750 return 0, errors.New("pebble/vfs: cannot write a directory") 751 } 752 f.n.mu.Lock() 753 defer f.n.mu.Unlock() 754 f.n.mu.modTime = time.Now() 755 756 for len(f.n.mu.data) < int(ofs)+len(p) { 757 f.n.mu.data = append(f.n.mu.data, 0) 758 } 759 760 n := copy(f.n.mu.data[int(ofs):int(ofs)+len(p)], p) 761 if n != len(p) { 762 panic("stuff") 763 } 764 765 return len(p), nil 766 } 767 768 func (f *memFile) Prefetch(offset int64, length int64) error { return nil } 769 func (f *memFile) Preallocate(offset, length int64) error { return nil } 770 771 func (f *memFile) Stat() (os.FileInfo, error) { 772 return f.n, nil 773 } 774 775 func (f *memFile) Sync() error { 776 if f.fs == nil || !f.fs.strict { 777 return nil 778 } 779 f.fs.mu.Lock() 780 defer f.fs.mu.Unlock() 781 if f.fs.ignoreSyncs { 782 return nil 783 } 784 if f.n.isDir { 785 f.n.syncedChildren = make(map[string]*memNode) 786 for k, v := range f.n.children { 787 f.n.syncedChildren[k] = v 788 } 789 } else { 790 f.n.mu.Lock() 791 f.n.mu.syncedData = slices.Clone(f.n.mu.data) 792 f.n.mu.Unlock() 793 } 794 return nil 795 } 796 797 func (f *memFile) SyncData() error { 798 return f.Sync() 799 } 800 801 func (f *memFile) SyncTo(length int64) (fullSync bool, err error) { 802 // NB: This SyncTo implementation lies, with its return values claiming it 803 // synced the data up to `length`. When fullSync=false, SyncTo provides no 804 // durability guarantees, so this can help surface bugs where we improperly 805 // rely on SyncTo providing durability. 806 return false, nil 807 } 808 809 func (f *memFile) Fd() uintptr { 810 return InvalidFd 811 } 812 813 // Flush is a no-op and present only to prevent buffering at higher levels 814 // (e.g. it prevents sstable.Writer from using a bufio.Writer). 815 func (f *memFile) Flush() error { 816 return nil 817 } 818 819 type memFileLock struct { 820 y *MemFS 821 f File 822 fullname string 823 } 824 825 func (l *memFileLock) Close() error { 826 if l.y == nil { 827 return nil 828 } 829 l.y.lockedFiles.Delete(l.fullname) 830 l.y = nil 831 return l.f.Close() 832 }