mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-27 17:15:18 +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()
|
|
}
|