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