memfs - memfs.go
1 // Package memfs contains both ReadOnly and ReadWrite implementations of an in
2 // memory FileSystem, supporting all of the FS interfaces and more.
3 package memfs // import "vimagination.zapto.org/memfs"
4
5 import (
6 "errors"
7 "io/fs"
8 "path/filepath"
9 "strings"
10 )
11
12 type fsRO struct {
13 de directoryEntry
14 }
15
16 func (f *fsRO) joinRoot(path string) string {
17 return filepath.Join(slash, path)
18 }
19
20 func (f *fsRO) Open(path string) (fs.File, error) {
21 de, err := f.getEntry(path)
22 if err != nil {
23 return nil, &fs.PathError{
24 Op: "open",
25 Path: path,
26 Err: err,
27 }
28 }
29
30 _, fileName := filepath.Split(path)
31
32 of, err := de.open(fileName, opRead|opSeek)
33 if err != nil {
34 return nil, &fs.PathError{
35 Op: "open",
36 Path: path,
37 Err: err,
38 }
39 }
40
41 return of, nil
42 }
43
44 func (f *fsRO) getDirEnt(path string) (dNode, error) {
45 de, err := f.getEntry(path)
46 if err != nil {
47 return nil, err
48 } else if d, ok := de.(dNode); !ok {
49 return nil, fs.ErrInvalid
50 } else {
51 return d, nil
52 }
53 }
54
55 const (
56 maxRedirects uint8 = 255
57 slash = string(filepath.Separator)
58 )
59
60 func (f *fsRO) getEntry(path string) (directoryEntry, error) {
61 if !fs.ValidPath(path) {
62 return nil, fs.ErrInvalid
63 }
64
65 return f.getEntryWithoutCheck(path)
66 }
67
68 func (f *fsRO) getEntryWithoutCheck(path string) (directoryEntry, error) {
69 if f.de.Mode()&0o444 == 0 {
70 return nil, fs.ErrPermission
71 }
72
73 path = f.joinRoot(path)
74 remainingRedirects := maxRedirects
75
76 curr := f.de
77 currPath := slash
78 path = path[1:]
79
80 for path != "" {
81 slashPos := strings.Index(path, slash)
82
83 var name string
84
85 if slashPos == -1 {
86 name = path
87 path = ""
88 } else {
89 name = path[:slashPos]
90 path = path[slashPos+1:]
91 }
92
93 if name == "" {
94 continue
95 }
96
97 if next, err := curr.getEntry(name); err != nil {
98 return nil, err
99 } else if next.Mode()&fs.ModeSymlink == 0 {
100 curr = next.directoryEntry
101 currPath = filepath.Join(currPath, name)
102 } else if remainingRedirects == 0 {
103 return nil, fs.ErrInvalid
104 } else {
105 remainingRedirects--
106
107 b, err := next.bytes()
108 if err != nil {
109 return nil, err
110 }
111
112 link := filepath.Clean(string(b))
113
114 if !strings.HasPrefix(link, slash) {
115 link = filepath.Join(currPath, link)
116 }
117
118 currPath = slash
119 path = filepath.Join(link, path)
120 curr = f.de
121 }
122 }
123
124 return curr, nil
125 }
126
127 func (f *fsRO) getLEntry(path string) (*dirEnt, error) {
128 if !fs.ValidPath(path) {
129 return nil, fs.ErrInvalid
130 }
131
132 jpath := f.joinRoot(path)
133 dirName, fileName := filepath.Split(jpath)
134
135 de, err := f.getEntryWithoutCheck(dirName)
136 if err != nil {
137 return nil, err
138 }
139
140 if jpath == slash {
141 return &dirEnt{
142 directoryEntry: f.de,
143 name: slash,
144 }, nil
145 }
146
147 return de.getEntry(fileName)
148 }
149
150 type exists byte
151
152 const (
153 mustNotExist exists = iota
154 mustExist
155 doesntMatter
156 )
157
158 func (f *fsRO) getEntryWithParent(path string, exists exists) (dNode, *dirEnt, error) {
159 parent, child := splitPath(path)
160 if child == "" {
161 return nil, nil, fs.ErrInvalid
162 }
163
164 d, err := f.getDirEnt(parent)
165 if err != nil {
166 return nil, nil, err
167 }
168
169 c, err := d.getEntry(child)
170 if !errors.Is(err, fs.ErrNotExist) || exists == mustExist {
171 if err != nil {
172 return nil, nil, err
173 } else if exists == mustNotExist {
174 return nil, nil, fs.ErrExist
175 }
176 }
177
178 return d, c, nil
179 }
180
181 func (f *fsRO) ReadDir(path string) ([]fs.DirEntry, error) {
182 d, err := f.getDirEnt(path)
183 if err != nil {
184 return nil, &fs.PathError{
185 Op: "readdir",
186 Path: path,
187 Err: err,
188 }
189 }
190
191 es, err := d.getEntries()
192 if err != nil {
193 return nil, &fs.PathError{
194 Op: "readdir",
195 Path: path,
196 Err: err,
197 }
198 }
199
200 return es, nil
201 }
202
203 func (f *fsRO) ReadFile(path string) ([]byte, error) {
204 de, err := f.getEntry(path)
205 if err != nil {
206 return nil, &fs.PathError{
207 Op: "readfile",
208 Path: path,
209 Err: err,
210 }
211 }
212
213 b, err := de.bytes()
214 if err != nil {
215 return nil, &fs.PathError{
216 Op: "readfile",
217 Path: path,
218 Err: err,
219 }
220 }
221
222 data := make([]byte, len(b))
223
224 copy(data, b)
225
226 return data, nil
227 }
228
229 func (f *fsRO) Stat(path string) (fs.FileInfo, error) {
230 de, err := f.getEntry(path)
231 if err != nil {
232 return nil, &fs.PathError{
233 Op: "stat",
234 Path: path,
235 Err: err,
236 }
237 }
238
239 base := filepath.Base(path)
240
241 if base == "." {
242 base = slash
243 }
244
245 return &dirEnt{
246 name: base,
247 directoryEntry: de,
248 }, nil
249 }
250
251 func (f *fsRO) LStat(path string) (fs.FileInfo, error) {
252 de, err := f.getLEntry(path)
253 if err != nil {
254 return nil, &fs.PathError{
255 Op: "lstat",
256 Path: path,
257 Err: err,
258 }
259 }
260
261 return de, nil
262 }
263
264 func (f *fsRO) Readlink(path string) (string, error) {
265 de, err := f.getLEntry(path)
266 if err != nil {
267 return "", &fs.PathError{
268 Op: "readlink",
269 Path: path,
270 Err: err,
271 }
272 }
273
274 if de.Mode()&fs.ModeSymlink == 0 {
275 return "", &fs.PathError{
276 Op: "readlink",
277 Path: path,
278 Err: fs.ErrInvalid,
279 }
280 }
281
282 b, err := de.bytes()
283 if err != nil {
284 return "", &fs.PathError{
285 Op: "readlink",
286 Path: path,
287 Err: err,
288 }
289 }
290
291 return string(b), nil
292 }
293
294 func (f *fsRO) sub(path string) (directoryEntry, error) {
295 de, err := f.getEntry(path)
296 if err != nil {
297 return nil, &fs.PathError{
298 Op: "sub",
299 Path: path,
300 Err: err,
301 }
302 } else if !de.IsDir() {
303 return nil, &fs.PathError{
304 Op: "sub",
305 Path: path,
306 Err: fs.ErrInvalid,
307 }
308 }
309
310 return de, nil
311 }
312
313 func (f *fsRO) Sub(path string) (fs.FS, error) {
314 de, err := f.sub(path)
315 if err != nil {
316 return nil, err
317 }
318
319 return &fsRO{
320 de: de,
321 }, nil
322 }
323