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