Skip to content

Commit d634056

Browse files
committed
Add threadsafe variant of memfs
1 parent bffa736 commit d634056

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

internal/safememfs/safememfs.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2025 Google LLC
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package safememfs
5+
6+
import (
7+
"os"
8+
"sync"
9+
10+
"github.com/go-git/go-billy/v5"
11+
"github.com/go-git/go-billy/v5/memfs"
12+
)
13+
14+
// SafeMemory is a thread-safe wrapper for any billy.Filesystem.
15+
type SafeMemory struct {
16+
fs billy.Filesystem // memfs.Memory
17+
// mu protects the access to the map operations in the memfs.Memory storage.
18+
// Notably, we're fine with the reads and write colliding if there are
19+
// simultaneous Opens to the same file. The underlying file storage is
20+
// responsible for resolving those races.
21+
mu *sync.Mutex
22+
}
23+
24+
// New creates a new thread-safe in-memory filesystem.
25+
func New() *SafeMemory {
26+
return &SafeMemory{
27+
fs: memfs.New(),
28+
mu: &sync.Mutex{},
29+
}
30+
}
31+
32+
func (s *SafeMemory) Chroot(path string) (billy.Filesystem, error) {
33+
s.mu.Lock()
34+
defer s.mu.Unlock()
35+
newFs, err := s.fs.Chroot(path)
36+
if err != nil {
37+
return nil, err
38+
}
39+
return &SafeMemory{
40+
fs: newFs,
41+
mu: s.mu, // NOTE: same mutex
42+
}, nil
43+
}
44+
45+
func (s *SafeMemory) Root() string {
46+
return "/"
47+
}
48+
49+
// --- Write Operations ---
50+
51+
func (s *SafeMemory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
52+
s.mu.Lock()
53+
defer s.mu.Unlock()
54+
return s.fs.OpenFile(filename, flag, perm)
55+
}
56+
57+
func (s *SafeMemory) Create(filename string) (billy.File, error) {
58+
return s.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
59+
}
60+
61+
func (s *SafeMemory) MkdirAll(path string, perm os.FileMode) error {
62+
s.mu.Lock()
63+
defer s.mu.Unlock()
64+
return s.fs.MkdirAll(path, perm)
65+
}
66+
67+
func (s *SafeMemory) Rename(from, to string) error {
68+
s.mu.Lock()
69+
defer s.mu.Unlock()
70+
return s.fs.Rename(from, to)
71+
}
72+
73+
func (s *SafeMemory) Remove(filename string) error {
74+
s.mu.Lock()
75+
defer s.mu.Unlock()
76+
return s.fs.Remove(filename)
77+
}
78+
79+
func (s *SafeMemory) Symlink(target, link string) error {
80+
s.mu.Lock()
81+
defer s.mu.Unlock()
82+
return s.fs.Symlink(target, link)
83+
}
84+
85+
func (s *SafeMemory) TempFile(dir, prefix string) (billy.File, error) {
86+
s.mu.Lock()
87+
defer s.mu.Unlock()
88+
return s.fs.TempFile(dir, prefix)
89+
}
90+
91+
// --- Read Operations ---
92+
93+
func (s *SafeMemory) Open(filename string) (billy.File, error) {
94+
return s.OpenFile(filename, os.O_RDONLY, 0666)
95+
}
96+
97+
func (s *SafeMemory) Stat(filename string) (os.FileInfo, error) {
98+
s.mu.Lock()
99+
defer s.mu.Unlock()
100+
return s.fs.Stat(filename)
101+
}
102+
103+
func (s *SafeMemory) Lstat(filename string) (os.FileInfo, error) {
104+
s.mu.Lock()
105+
defer s.mu.Unlock()
106+
return s.fs.Lstat(filename)
107+
}
108+
109+
func (s *SafeMemory) ReadDir(path string) ([]os.FileInfo, error) {
110+
s.mu.Lock()
111+
defer s.mu.Unlock()
112+
return s.fs.ReadDir(path)
113+
}
114+
115+
func (s *SafeMemory) Readlink(link string) (string, error) {
116+
s.mu.Lock()
117+
defer s.mu.Unlock()
118+
return s.fs.Readlink(link)
119+
}
120+
121+
func (s *SafeMemory) Join(elem ...string) string {
122+
// No lock needed as this is a pure function
123+
return s.fs.Join(elem...)
124+
}
125+
126+
// Capabilities forwards the call to the underlying filesystem.
127+
// No lock needed as it doesn't modify state.
128+
func (s *SafeMemory) Capabilities() billy.Capability {
129+
if capable, ok := s.fs.(billy.Capable); ok {
130+
return capable.Capabilities()
131+
}
132+
// Default capabilities if the underlying fs doesn't implement billy.Capable
133+
return 0
134+
}
135+
136+
// Ensure the wrapper implements the full interface.
137+
var _ billy.Filesystem = (*SafeMemory)(nil)
138+
var _ billy.Capable = (*SafeMemory)(nil)

0 commit comments

Comments
 (0)