mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-04-04 19:36:54 +00:00 
			
		
		
		
	
		
			
	
	
		
			833 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			833 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
 | ||
|  | // of this source code is governed by a BSD-style license that can be found in
 | ||
|  | // the LICENSE file.
 | ||
|  | 
 | ||
|  | package vfs // import "github.com/cockroachdb/pebble/vfs"
 | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"bytes" | ||
|  | 	"fmt" | ||
|  | 	"io" | ||
|  | 	"os" | ||
|  | 	"path" | ||
|  | 	"sort" | ||
|  | 	"strings" | ||
|  | 	"sync" | ||
|  | 	"sync/atomic" | ||
|  | 	"syscall" | ||
|  | 	"time" | ||
|  | 
 | ||
|  | 	"github.com/cockroachdb/errors" | ||
|  | 	"github.com/cockroachdb/errors/oserror" | ||
|  | 	"github.com/cockroachdb/pebble/internal/invariants" | ||
|  | 	"github.com/cockroachdb/pebble/shims/slices" | ||
|  | ) | ||
|  | 
 | ||
|  | const sep = "/" | ||
|  | 
 | ||
|  | // NewMem returns a new memory-backed FS implementation.
 | ||
|  | func NewMem() *MemFS { | ||
|  | 	return &MemFS{ | ||
|  | 		root: newRootMemNode(), | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // NewStrictMem returns a "strict" memory-backed FS implementation. The behaviour is strict wrt
 | ||
|  | // needing a Sync() call on files or directories for the state changes to be finalized. Any
 | ||
|  | // changes that are not finalized are visible to reads until MemFS.ResetToSyncedState() is called,
 | ||
|  | // at which point they are discarded and no longer visible.
 | ||
|  | //
 | ||
|  | // Expected usage:
 | ||
|  | //
 | ||
|  | //	strictFS := NewStrictMem()
 | ||
|  | //	db := Open(..., &Options{FS: strictFS})
 | ||
|  | //	// Do and commit various operations.
 | ||
|  | //	...
 | ||
|  | //	// Prevent any more changes to finalized state.
 | ||
|  | //	strictFS.SetIgnoreSyncs(true)
 | ||
|  | //	// This will finish any ongoing background flushes, compactions but none of these writes will
 | ||
|  | //	// be finalized since syncs are being ignored.
 | ||
|  | //	db.Close()
 | ||
|  | //	// Discard unsynced state.
 | ||
|  | //	strictFS.ResetToSyncedState()
 | ||
|  | //	// Allow changes to finalized state.
 | ||
|  | //	strictFS.SetIgnoreSyncs(false)
 | ||
|  | //	// Open the DB. This DB should have the same state as if the earlier strictFS operations and
 | ||
|  | //	// db.Close() were not called.
 | ||
|  | //	db := Open(..., &Options{FS: strictFS})
 | ||
|  | func NewStrictMem() *MemFS { | ||
|  | 	return &MemFS{ | ||
|  | 		root:   newRootMemNode(), | ||
|  | 		strict: true, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // NewMemFile returns a memory-backed File implementation. The memory-backed
 | ||
|  | // file takes ownership of data.
 | ||
|  | func NewMemFile(data []byte) File { | ||
|  | 	n := &memNode{} | ||
|  | 	n.refs.Store(1) | ||
|  | 	n.mu.data = data | ||
|  | 	n.mu.modTime = time.Now() | ||
|  | 	return &memFile{ | ||
|  | 		n:    n, | ||
|  | 		read: true, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // MemFS implements FS.
 | ||
|  | type MemFS struct { | ||
|  | 	mu   sync.Mutex | ||
|  | 	root *memNode | ||
|  | 
 | ||
|  | 	// lockFiles holds a map of open file locks. Presence in this map indicates
 | ||
|  | 	// a file lock is currently held. Keys are strings holding the path of the
 | ||
|  | 	// locked file. The stored value is untyped and  unused; only presence of
 | ||
|  | 	// the key within the map is significant.
 | ||
|  | 	lockedFiles sync.Map | ||
|  | 	strict      bool | ||
|  | 	ignoreSyncs bool | ||
|  | 	// Windows has peculiar semantics with respect to hard links and deleting
 | ||
|  | 	// open files. In tests meant to exercise this behavior, this flag can be
 | ||
|  | 	// set to error if removing an open file.
 | ||
|  | 	windowsSemantics bool | ||
|  | } | ||
|  | 
 | ||
|  | var _ FS = &MemFS{} | ||
|  | 
 | ||
|  | // UseWindowsSemantics configures whether the MemFS implements Windows-style
 | ||
|  | // semantics, in particular with respect to whether any of an open file's links
 | ||
|  | // may be removed. Windows semantics default to off.
 | ||
|  | func (y *MemFS) UseWindowsSemantics(windowsSemantics bool) { | ||
|  | 	y.mu.Lock() | ||
|  | 	defer y.mu.Unlock() | ||
|  | 	y.windowsSemantics = windowsSemantics | ||
|  | } | ||
|  | 
 | ||
|  | // String dumps the contents of the MemFS.
 | ||
|  | func (y *MemFS) String() string { | ||
|  | 	y.mu.Lock() | ||
|  | 	defer y.mu.Unlock() | ||
|  | 
 | ||
|  | 	s := new(bytes.Buffer) | ||
|  | 	y.root.dump(s, 0) | ||
|  | 	return s.String() | ||
|  | } | ||
|  | 
 | ||
|  | // SetIgnoreSyncs sets the MemFS.ignoreSyncs field. See the usage comment with NewStrictMem() for
 | ||
|  | // details.
 | ||
|  | func (y *MemFS) SetIgnoreSyncs(ignoreSyncs bool) { | ||
|  | 	if !y.strict { | ||
|  | 		panic("SetIgnoreSyncs can only be used on a strict MemFS") | ||
|  | 	} | ||
|  | 	y.mu.Lock() | ||
|  | 	y.ignoreSyncs = ignoreSyncs | ||
|  | 	y.mu.Unlock() | ||
|  | } | ||
|  | 
 | ||
|  | // ResetToSyncedState discards state in the FS that is not synced. See the usage comment with
 | ||
|  | // NewStrictMem() for details.
 | ||
|  | func (y *MemFS) ResetToSyncedState() { | ||
|  | 	if !y.strict { | ||
|  | 		panic("ResetToSyncedState can only be used on a strict MemFS") | ||
|  | 	} | ||
|  | 	y.mu.Lock() | ||
|  | 	y.root.resetToSyncedState() | ||
|  | 	y.mu.Unlock() | ||
|  | } | ||
|  | 
 | ||
|  | // walk walks the directory tree for the fullname, calling f at each step. If
 | ||
|  | // f returns an error, the walk will be aborted and return that same error.
 | ||
|  | //
 | ||
|  | // Each walk is atomic: y's mutex is held for the entire operation, including
 | ||
|  | // all calls to f.
 | ||
|  | //
 | ||
|  | // dir is the directory at that step, frag is the name fragment, and final is
 | ||
|  | // whether it is the final step. For example, walking "/foo/bar/x" will result
 | ||
|  | // in 3 calls to f:
 | ||
|  | //   - "/", "foo", false
 | ||
|  | //   - "/foo/", "bar", false
 | ||
|  | //   - "/foo/bar/", "x", true
 | ||
|  | //
 | ||
|  | // Similarly, walking "/y/z/", with a trailing slash, will result in 3 calls to f:
 | ||
|  | //   - "/", "y", false
 | ||
|  | //   - "/y/", "z", false
 | ||
|  | //   - "/y/z/", "", true
 | ||
|  | func (y *MemFS) walk(fullname string, f func(dir *memNode, frag string, final bool) error) error { | ||
|  | 	y.mu.Lock() | ||
|  | 	defer y.mu.Unlock() | ||
|  | 
 | ||
|  | 	// For memfs, the current working directory is the same as the root directory,
 | ||
|  | 	// so we strip off any leading "/"s to make fullname a relative path, and
 | ||
|  | 	// the walk starts at y.root.
 | ||
|  | 	for len(fullname) > 0 && fullname[0] == sep[0] { | ||
|  | 		fullname = fullname[1:] | ||
|  | 	} | ||
|  | 	dir := y.root | ||
|  | 
 | ||
|  | 	for { | ||
|  | 		frag, remaining := fullname, "" | ||
|  | 		i := strings.IndexRune(fullname, rune(sep[0])) | ||
|  | 		final := i < 0 | ||
|  | 		if !final { | ||
|  | 			frag, remaining = fullname[:i], fullname[i+1:] | ||
|  | 			for len(remaining) > 0 && remaining[0] == sep[0] { | ||
|  | 				remaining = remaining[1:] | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if err := f(dir, frag, final); err != nil { | ||
|  | 			return err | ||
|  | 		} | ||
|  | 		if final { | ||
|  | 			break | ||
|  | 		} | ||
|  | 		child := dir.children[frag] | ||
|  | 		if child == nil { | ||
|  | 			return &os.PathError{ | ||
|  | 				Op:   "open", | ||
|  | 				Path: fullname, | ||
|  | 				Err:  oserror.ErrNotExist, | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if !child.isDir { | ||
|  | 			return &os.PathError{ | ||
|  | 				Op:   "open", | ||
|  | 				Path: fullname, | ||
|  | 				Err:  errors.New("not a directory"), | ||
|  | 			} | ||
|  | 		} | ||
|  | 		dir, fullname = child, remaining | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | // Create implements FS.Create.
 | ||
|  | func (y *MemFS) Create(fullname string) (File, error) { | ||
|  | 	var ret *memFile | ||
|  | 	err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			n := &memNode{name: frag} | ||
|  | 			dir.children[frag] = n | ||
|  | 			ret = &memFile{ | ||
|  | 				n:     n, | ||
|  | 				fs:    y, | ||
|  | 				read:  true, | ||
|  | 				write: true, | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	ret.n.refs.Add(1) | ||
|  | 	return ret, nil | ||
|  | } | ||
|  | 
 | ||
|  | // Link implements FS.Link.
 | ||
|  | func (y *MemFS) Link(oldname, newname string) error { | ||
|  | 	var n *memNode | ||
|  | 	err := y.walk(oldname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			n = dir.children[frag] | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | 	if err != nil { | ||
|  | 		return err | ||
|  | 	} | ||
|  | 	if n == nil { | ||
|  | 		return &os.LinkError{ | ||
|  | 			Op:  "link", | ||
|  | 			Old: oldname, | ||
|  | 			New: newname, | ||
|  | 			Err: oserror.ErrNotExist, | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return y.walk(newname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			if _, ok := dir.children[frag]; ok { | ||
|  | 				return &os.LinkError{ | ||
|  | 					Op:  "link", | ||
|  | 					Old: oldname, | ||
|  | 					New: newname, | ||
|  | 					Err: oserror.ErrExist, | ||
|  | 				} | ||
|  | 			} | ||
|  | 			dir.children[frag] = n | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | } | ||
|  | 
 | ||
|  | func (y *MemFS) open(fullname string, openForWrite bool) (File, error) { | ||
|  | 	var ret *memFile | ||
|  | 	err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				ret = &memFile{ | ||
|  | 					n:  dir, | ||
|  | 					fs: y, | ||
|  | 				} | ||
|  | 				return nil | ||
|  | 			} | ||
|  | 			if n := dir.children[frag]; n != nil { | ||
|  | 				ret = &memFile{ | ||
|  | 					n:     n, | ||
|  | 					fs:    y, | ||
|  | 					read:  true, | ||
|  | 					write: openForWrite, | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	if ret == nil { | ||
|  | 		return nil, &os.PathError{ | ||
|  | 			Op:   "open", | ||
|  | 			Path: fullname, | ||
|  | 			Err:  oserror.ErrNotExist, | ||
|  | 		} | ||
|  | 	} | ||
|  | 	ret.n.refs.Add(1) | ||
|  | 	return ret, nil | ||
|  | } | ||
|  | 
 | ||
|  | // Open implements FS.Open.
 | ||
|  | func (y *MemFS) Open(fullname string, opts ...OpenOption) (File, error) { | ||
|  | 	return y.open(fullname, false /* openForWrite */) | ||
|  | } | ||
|  | 
 | ||
|  | // OpenReadWrite implements FS.OpenReadWrite.
 | ||
|  | func (y *MemFS) OpenReadWrite(fullname string, opts ...OpenOption) (File, error) { | ||
|  | 	f, err := y.open(fullname, true /* openForWrite */) | ||
|  | 	pathErr, ok := err.(*os.PathError) | ||
|  | 	if ok && pathErr.Err == oserror.ErrNotExist { | ||
|  | 		return y.Create(fullname) | ||
|  | 	} | ||
|  | 	return f, err | ||
|  | } | ||
|  | 
 | ||
|  | // OpenDir implements FS.OpenDir.
 | ||
|  | func (y *MemFS) OpenDir(fullname string) (File, error) { | ||
|  | 	return y.open(fullname, false /* openForWrite */) | ||
|  | } | ||
|  | 
 | ||
|  | // Remove implements FS.Remove.
 | ||
|  | func (y *MemFS) Remove(fullname string) error { | ||
|  | 	return y.walk(fullname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			child, ok := dir.children[frag] | ||
|  | 			if !ok { | ||
|  | 				return oserror.ErrNotExist | ||
|  | 			} | ||
|  | 			if y.windowsSemantics { | ||
|  | 				// Disallow removal of open files/directories which implements
 | ||
|  | 				// Windows semantics. This ensures that we don't regress in the
 | ||
|  | 				// ordering of operations and try to remove a file while it is
 | ||
|  | 				// still open.
 | ||
|  | 				if n := child.refs.Load(); n > 0 { | ||
|  | 					return oserror.ErrInvalid | ||
|  | 				} | ||
|  | 			} | ||
|  | 			if len(child.children) > 0 { | ||
|  | 				return errNotEmpty | ||
|  | 			} | ||
|  | 			delete(dir.children, frag) | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | } | ||
|  | 
 | ||
|  | // RemoveAll implements FS.RemoveAll.
 | ||
|  | func (y *MemFS) RemoveAll(fullname string) error { | ||
|  | 	err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			_, ok := dir.children[frag] | ||
|  | 			if !ok { | ||
|  | 				return nil | ||
|  | 			} | ||
|  | 			delete(dir.children, frag) | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | 	// Match os.RemoveAll which returns a nil error even if the parent
 | ||
|  | 	// directories don't exist.
 | ||
|  | 	if oserror.IsNotExist(err) { | ||
|  | 		err = nil | ||
|  | 	} | ||
|  | 	return err | ||
|  | } | ||
|  | 
 | ||
|  | // Rename implements FS.Rename.
 | ||
|  | func (y *MemFS) Rename(oldname, newname string) error { | ||
|  | 	var n *memNode | ||
|  | 	err := y.walk(oldname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			n = dir.children[frag] | ||
|  | 			delete(dir.children, frag) | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | 	if err != nil { | ||
|  | 		return err | ||
|  | 	} | ||
|  | 	if n == nil { | ||
|  | 		return &os.PathError{ | ||
|  | 			Op:   "open", | ||
|  | 			Path: oldname, | ||
|  | 			Err:  oserror.ErrNotExist, | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return y.walk(newname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag == "" { | ||
|  | 				return errors.New("pebble/vfs: empty file name") | ||
|  | 			} | ||
|  | 			dir.children[frag] = n | ||
|  | 			n.name = frag | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | } | ||
|  | 
 | ||
|  | // ReuseForWrite implements FS.ReuseForWrite.
 | ||
|  | func (y *MemFS) ReuseForWrite(oldname, newname string) (File, error) { | ||
|  | 	if err := y.Rename(oldname, newname); err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	f, err := y.Open(newname) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	y.mu.Lock() | ||
|  | 	defer y.mu.Unlock() | ||
|  | 
 | ||
|  | 	mf := f.(*memFile) | ||
|  | 	mf.read = false | ||
|  | 	mf.write = true | ||
|  | 	return f, nil | ||
|  | } | ||
|  | 
 | ||
|  | // MkdirAll implements FS.MkdirAll.
 | ||
|  | func (y *MemFS) MkdirAll(dirname string, perm os.FileMode) error { | ||
|  | 	return y.walk(dirname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if frag == "" { | ||
|  | 			if final { | ||
|  | 				return nil | ||
|  | 			} | ||
|  | 			return errors.New("pebble/vfs: empty file name") | ||
|  | 		} | ||
|  | 		child := dir.children[frag] | ||
|  | 		if child == nil { | ||
|  | 			dir.children[frag] = &memNode{ | ||
|  | 				name:     frag, | ||
|  | 				children: make(map[string]*memNode), | ||
|  | 				isDir:    true, | ||
|  | 			} | ||
|  | 			return nil | ||
|  | 		} | ||
|  | 		if !child.isDir { | ||
|  | 			return &os.PathError{ | ||
|  | 				Op:   "open", | ||
|  | 				Path: dirname, | ||
|  | 				Err:  errors.New("not a directory"), | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | } | ||
|  | 
 | ||
|  | // Lock implements FS.Lock.
 | ||
|  | func (y *MemFS) Lock(fullname string) (io.Closer, error) { | ||
|  | 	// FS.Lock excludes other processes, but other processes cannot see this
 | ||
|  | 	// process' memory. However some uses (eg, Cockroach tests) may open and
 | ||
|  | 	// close the same MemFS-backed database multiple times. We want mutual
 | ||
|  | 	// exclusion in this case too. See cockroachdb/cockroach#110645.
 | ||
|  | 	_, loaded := y.lockedFiles.Swap(fullname, nil /* the value itself is insignificant */) | ||
|  | 	if loaded { | ||
|  | 		// This file lock has already been acquired. On unix, this results in
 | ||
|  | 		// either EACCES or EAGAIN so we mimic.
 | ||
|  | 		return nil, syscall.EAGAIN | ||
|  | 	} | ||
|  | 	// Otherwise, we successfully acquired the lock. Locks are visible in the
 | ||
|  | 	// parent directory listing, and they also must be created under an existent
 | ||
|  | 	// directory. Create the path so that we have the normal detection of
 | ||
|  | 	// non-existent directory paths, and make the lock visible when listing
 | ||
|  | 	// directory entries.
 | ||
|  | 	f, err := y.Create(fullname) | ||
|  | 	if err != nil { | ||
|  | 		// "Release" the lock since we failed.
 | ||
|  | 		y.lockedFiles.Delete(fullname) | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	return &memFileLock{ | ||
|  | 		y:        y, | ||
|  | 		f:        f, | ||
|  | 		fullname: fullname, | ||
|  | 	}, nil | ||
|  | } | ||
|  | 
 | ||
|  | // List implements FS.List.
 | ||
|  | func (y *MemFS) List(dirname string) ([]string, error) { | ||
|  | 	if !strings.HasSuffix(dirname, sep) { | ||
|  | 		dirname += sep | ||
|  | 	} | ||
|  | 	var ret []string | ||
|  | 	err := y.walk(dirname, func(dir *memNode, frag string, final bool) error { | ||
|  | 		if final { | ||
|  | 			if frag != "" { | ||
|  | 				panic("unreachable") | ||
|  | 			} | ||
|  | 			ret = make([]string, 0, len(dir.children)) | ||
|  | 			for s := range dir.children { | ||
|  | 				ret = append(ret, s) | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return nil | ||
|  | 	}) | ||
|  | 	return ret, err | ||
|  | } | ||
|  | 
 | ||
|  | // Stat implements FS.Stat.
 | ||
|  | func (y *MemFS) Stat(name string) (os.FileInfo, error) { | ||
|  | 	f, err := y.Open(name) | ||
|  | 	if err != nil { | ||
|  | 		if pe, ok := err.(*os.PathError); ok { | ||
|  | 			pe.Op = "stat" | ||
|  | 		} | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	defer f.Close() | ||
|  | 	return f.Stat() | ||
|  | } | ||
|  | 
 | ||
|  | // PathBase implements FS.PathBase.
 | ||
|  | func (*MemFS) PathBase(p string) string { | ||
|  | 	// Note that MemFS uses forward slashes for its separator, hence the use of
 | ||
|  | 	// path.Base, not filepath.Base.
 | ||
|  | 	return path.Base(p) | ||
|  | } | ||
|  | 
 | ||
|  | // PathJoin implements FS.PathJoin.
 | ||
|  | func (*MemFS) PathJoin(elem ...string) string { | ||
|  | 	// Note that MemFS uses forward slashes for its separator, hence the use of
 | ||
|  | 	// path.Join, not filepath.Join.
 | ||
|  | 	return path.Join(elem...) | ||
|  | } | ||
|  | 
 | ||
|  | // PathDir implements FS.PathDir.
 | ||
|  | func (*MemFS) PathDir(p string) string { | ||
|  | 	// Note that MemFS uses forward slashes for its separator, hence the use of
 | ||
|  | 	// path.Dir, not filepath.Dir.
 | ||
|  | 	return path.Dir(p) | ||
|  | } | ||
|  | 
 | ||
|  | // GetDiskUsage implements FS.GetDiskUsage.
 | ||
|  | func (*MemFS) GetDiskUsage(string) (DiskUsage, error) { | ||
|  | 	return DiskUsage{}, ErrUnsupported | ||
|  | } | ||
|  | 
 | ||
|  | // memNode holds a file's data or a directory's children, and implements os.FileInfo.
 | ||
|  | type memNode struct { | ||
|  | 	name  string | ||
|  | 	isDir bool | ||
|  | 	refs  atomic.Int32 | ||
|  | 
 | ||
|  | 	// Mutable state.
 | ||
|  | 	// - For a file: data, syncedDate, modTime: A file is only being mutated by a single goroutine,
 | ||
|  | 	//   but there can be concurrent readers e.g. DB.Checkpoint() which can read WAL or MANIFEST
 | ||
|  | 	//   files that are being written to. Additionally Sync() calls can be concurrent with writing.
 | ||
|  | 	// - For a directory: children and syncedChildren. Concurrent writes are possible, and
 | ||
|  | 	//   these are protected using MemFS.mu.
 | ||
|  | 	mu struct { | ||
|  | 		sync.Mutex | ||
|  | 		data       []byte | ||
|  | 		syncedData []byte | ||
|  | 		modTime    time.Time | ||
|  | 	} | ||
|  | 
 | ||
|  | 	children       map[string]*memNode | ||
|  | 	syncedChildren map[string]*memNode | ||
|  | } | ||
|  | 
 | ||
|  | func newRootMemNode() *memNode { | ||
|  | 	return &memNode{ | ||
|  | 		name:     "/", // set the name to match what file systems do
 | ||
|  | 		children: make(map[string]*memNode), | ||
|  | 		isDir:    true, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) IsDir() bool { | ||
|  | 	return f.isDir | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) ModTime() time.Time { | ||
|  | 	f.mu.Lock() | ||
|  | 	defer f.mu.Unlock() | ||
|  | 	return f.mu.modTime | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) Mode() os.FileMode { | ||
|  | 	if f.isDir { | ||
|  | 		return os.ModeDir | 0755 | ||
|  | 	} | ||
|  | 	return 0755 | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) Name() string { | ||
|  | 	return f.name | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) Size() int64 { | ||
|  | 	f.mu.Lock() | ||
|  | 	defer f.mu.Unlock() | ||
|  | 	return int64(len(f.mu.data)) | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) Sys() interface{} { | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) dump(w *bytes.Buffer, level int) { | ||
|  | 	if f.isDir { | ||
|  | 		w.WriteString("          ") | ||
|  | 	} else { | ||
|  | 		f.mu.Lock() | ||
|  | 		fmt.Fprintf(w, "%8d  ", len(f.mu.data)) | ||
|  | 		f.mu.Unlock() | ||
|  | 	} | ||
|  | 	for i := 0; i < level; i++ { | ||
|  | 		w.WriteString("  ") | ||
|  | 	} | ||
|  | 	w.WriteString(f.name) | ||
|  | 	if !f.isDir { | ||
|  | 		w.WriteByte('\n') | ||
|  | 		return | ||
|  | 	} | ||
|  | 	if level > 0 { // deal with the fact that the root's name is already "/"
 | ||
|  | 		w.WriteByte(sep[0]) | ||
|  | 	} | ||
|  | 	w.WriteByte('\n') | ||
|  | 	names := make([]string, 0, len(f.children)) | ||
|  | 	for name := range f.children { | ||
|  | 		names = append(names, name) | ||
|  | 	} | ||
|  | 	sort.Strings(names) | ||
|  | 	for _, name := range names { | ||
|  | 		f.children[name].dump(w, level+1) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memNode) resetToSyncedState() { | ||
|  | 	if f.isDir { | ||
|  | 		f.children = make(map[string]*memNode) | ||
|  | 		for k, v := range f.syncedChildren { | ||
|  | 			f.children[k] = v | ||
|  | 		} | ||
|  | 		for _, v := range f.children { | ||
|  | 			v.resetToSyncedState() | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		f.mu.Lock() | ||
|  | 		f.mu.data = slices.Clone(f.mu.syncedData) | ||
|  | 		f.mu.Unlock() | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // memFile is a reader or writer of a node's data, and implements File.
 | ||
|  | type memFile struct { | ||
|  | 	n           *memNode | ||
|  | 	fs          *MemFS // nil for a standalone memFile
 | ||
|  | 	rpos        int | ||
|  | 	wpos        int | ||
|  | 	read, write bool | ||
|  | } | ||
|  | 
 | ||
|  | var _ File = (*memFile)(nil) | ||
|  | 
 | ||
|  | func (f *memFile) Close() error { | ||
|  | 	if n := f.n.refs.Add(-1); n < 0 { | ||
|  | 		panic(fmt.Sprintf("pebble: close of unopened file: %d", n)) | ||
|  | 	} | ||
|  | 	f.n = nil | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) Read(p []byte) (int, error) { | ||
|  | 	if !f.read { | ||
|  | 		return 0, errors.New("pebble/vfs: file was not opened for reading") | ||
|  | 	} | ||
|  | 	if f.n.isDir { | ||
|  | 		return 0, errors.New("pebble/vfs: cannot read a directory") | ||
|  | 	} | ||
|  | 	f.n.mu.Lock() | ||
|  | 	defer f.n.mu.Unlock() | ||
|  | 	if f.rpos >= len(f.n.mu.data) { | ||
|  | 		return 0, io.EOF | ||
|  | 	} | ||
|  | 	n := copy(p, f.n.mu.data[f.rpos:]) | ||
|  | 	f.rpos += n | ||
|  | 	return n, nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) ReadAt(p []byte, off int64) (int, error) { | ||
|  | 	if !f.read { | ||
|  | 		return 0, errors.New("pebble/vfs: file was not opened for reading") | ||
|  | 	} | ||
|  | 	if f.n.isDir { | ||
|  | 		return 0, errors.New("pebble/vfs: cannot read a directory") | ||
|  | 	} | ||
|  | 	f.n.mu.Lock() | ||
|  | 	defer f.n.mu.Unlock() | ||
|  | 	if off >= int64(len(f.n.mu.data)) { | ||
|  | 		return 0, io.EOF | ||
|  | 	} | ||
|  | 	n := copy(p, f.n.mu.data[off:]) | ||
|  | 	if n < len(p) { | ||
|  | 		return n, io.EOF | ||
|  | 	} | ||
|  | 	return n, nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) Write(p []byte) (int, error) { | ||
|  | 	if !f.write { | ||
|  | 		return 0, errors.New("pebble/vfs: file was not created for writing") | ||
|  | 	} | ||
|  | 	if f.n.isDir { | ||
|  | 		return 0, errors.New("pebble/vfs: cannot write a directory") | ||
|  | 	} | ||
|  | 	f.n.mu.Lock() | ||
|  | 	defer f.n.mu.Unlock() | ||
|  | 	f.n.mu.modTime = time.Now() | ||
|  | 	if f.wpos+len(p) <= len(f.n.mu.data) { | ||
|  | 		n := copy(f.n.mu.data[f.wpos:f.wpos+len(p)], p) | ||
|  | 		if n != len(p) { | ||
|  | 			panic("stuff") | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		f.n.mu.data = append(f.n.mu.data[:f.wpos], p...) | ||
|  | 	} | ||
|  | 	f.wpos += len(p) | ||
|  | 
 | ||
|  | 	if invariants.Enabled { | ||
|  | 		// Mutate the input buffer to flush out bugs in Pebble which expect the
 | ||
|  | 		// input buffer to be unmodified.
 | ||
|  | 		for i := range p { | ||
|  | 			p[i] ^= 0xff | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return len(p), nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) WriteAt(p []byte, ofs int64) (int, error) { | ||
|  | 	if !f.write { | ||
|  | 		return 0, errors.New("pebble/vfs: file was not created for writing") | ||
|  | 	} | ||
|  | 	if f.n.isDir { | ||
|  | 		return 0, errors.New("pebble/vfs: cannot write a directory") | ||
|  | 	} | ||
|  | 	f.n.mu.Lock() | ||
|  | 	defer f.n.mu.Unlock() | ||
|  | 	f.n.mu.modTime = time.Now() | ||
|  | 
 | ||
|  | 	for len(f.n.mu.data) < int(ofs)+len(p) { | ||
|  | 		f.n.mu.data = append(f.n.mu.data, 0) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	n := copy(f.n.mu.data[int(ofs):int(ofs)+len(p)], p) | ||
|  | 	if n != len(p) { | ||
|  | 		panic("stuff") | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return len(p), nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) Prefetch(offset int64, length int64) error { return nil } | ||
|  | func (f *memFile) Preallocate(offset, length int64) error    { return nil } | ||
|  | 
 | ||
|  | func (f *memFile) Stat() (os.FileInfo, error) { | ||
|  | 	return f.n, nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) Sync() error { | ||
|  | 	if f.fs == nil || !f.fs.strict { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	f.fs.mu.Lock() | ||
|  | 	defer f.fs.mu.Unlock() | ||
|  | 	if f.fs.ignoreSyncs { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	if f.n.isDir { | ||
|  | 		f.n.syncedChildren = make(map[string]*memNode) | ||
|  | 		for k, v := range f.n.children { | ||
|  | 			f.n.syncedChildren[k] = v | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		f.n.mu.Lock() | ||
|  | 		f.n.mu.syncedData = slices.Clone(f.n.mu.data) | ||
|  | 		f.n.mu.Unlock() | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) SyncData() error { | ||
|  | 	return f.Sync() | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) SyncTo(length int64) (fullSync bool, err error) { | ||
|  | 	// NB: This SyncTo implementation lies, with its return values claiming it
 | ||
|  | 	// synced the data up to `length`. When fullSync=false, SyncTo provides no
 | ||
|  | 	// durability guarantees, so this can help surface bugs where we improperly
 | ||
|  | 	// rely on SyncTo providing durability.
 | ||
|  | 	return false, nil | ||
|  | } | ||
|  | 
 | ||
|  | func (f *memFile) Fd() uintptr { | ||
|  | 	return InvalidFd | ||
|  | } | ||
|  | 
 | ||
|  | // Flush is a no-op and present only to prevent buffering at higher levels
 | ||
|  | // (e.g. it prevents sstable.Writer from using a bufio.Writer).
 | ||
|  | func (f *memFile) Flush() error { | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | type memFileLock struct { | ||
|  | 	y        *MemFS | ||
|  | 	f        File | ||
|  | 	fullname string | ||
|  | } | ||
|  | 
 | ||
|  | func (l *memFileLock) Close() error { | ||
|  | 	if l.y == nil { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	l.y.lockedFiles.Delete(l.fullname) | ||
|  | 	l.y = nil | ||
|  | 	return l.f.Close() | ||
|  | } |