memfs - memfs_rw.go
1 package memfs
2
3 import (
4 "errors"
5 "io/fs"
6 "path/filepath"
7 "strings"
8 "sync"
9 "time"
10 )
11
12 // FS represents an in-memory fs.FS implementation, with additional methods for
13 // a more 'OS' like experience.
14 type FS struct {
15 mu sync.RWMutex
16 fsRO
17 }
18
19 func New() *FS {
20 return &FS{
21 fsRO: fsRO{
22 de: &dnodeRW{
23 dnode: dnode{
24 mode: fs.ModeDir | fs.ModePerm,
25 modtime: time.Now(),
26 },
27 },
28 },
29 }
30 }
31
32 // FSRO represents all of the methods on a read-only FS implementation.
33 type FSRO interface {
34 fs.FS
35 fs.ReadDirFS
36 fs.ReadFileFS
37 fs.StatFS
38 fs.SubFS
39 LStat(path string) (fs.FileInfo, error)
40 Readlink(path string) (string, error)
41 }
42
43 // Seal converts the Read-Write FS into a Read-only one.
44 //
45 // The resulting FSRO cannot be changed, and has no locking. As the current
46 // implementation doesn't copy any data, it destroys the current FS in order to
47 // remove the need for locks on the resulting FSRO.
48 func (f *FS) Seal() FSRO {
49 f.mu.Lock()
50 defer f.mu.Unlock()
51
52 return &fsRO{
53 de: f.de.seal(),
54 }
55 }
56
57 func (f *FS) ReadDir(path string) ([]fs.DirEntry, error) {
58 f.mu.RLock()
59 defer f.mu.RUnlock()
60
61 d, err := f.getDirEnt(path)
62 if err != nil {
63 return nil, &fs.PathError{
64 Op: "readdir",
65 Path: path,
66 Err: err,
67 }
68 }
69
70 des, err := d.getEntries()
71 if err != nil {
72 return nil, &fs.PathError{
73 Op: "readdir",
74 Path: path,
75 Err: err,
76 }
77 }
78
79 return des, nil
80 }
81
82 func (f *FS) ReadFile(path string) ([]byte, error) {
83 f.mu.RLock()
84 defer f.mu.RUnlock()
85
86 return f.fsRO.ReadFile(path)
87 }
88
89 func (f *FS) Stat(path string) (fs.FileInfo, error) {
90 f.mu.RLock()
91 defer f.mu.RUnlock()
92
93 return f.fsRO.Stat(path)
94 }
95
96 func (f *FS) Mkdir(path string, perm fs.FileMode) error {
97 f.mu.Lock()
98 defer f.mu.Unlock()
99
100 return f.mkdir("mkdir", path, path, perm)
101 }
102
103 func (f *FS) mkdir(op, opath, path string, perm fs.FileMode) error {
104 d, _, err := f.getEntryWithParent(path, mustNotExist)
105 if err != nil {
106 return &fs.PathError{
107 Op: op,
108 Path: opath,
109 Err: err,
110 }
111 }
112
113 if err := d.setEntry(&dirEnt{
114 directoryEntry: &dnodeRW{
115 dnode: dnode{
116 modtime: time.Now(),
117 mode: fs.ModeDir | perm,
118 },
119 },
120 name: filepath.Base(path),
121 }); err != nil {
122 return &fs.PathError{
123 Op: op,
124 Path: opath,
125 Err: err,
126 }
127 }
128
129 return nil
130 }
131
132 func (f *FS) MkdirAll(path string, perm fs.FileMode) error {
133 f.mu.Lock()
134 defer f.mu.Unlock()
135
136 cpath := filepath.Join(slash, path)
137 last := 0
138
139 for {
140 pos := strings.IndexRune(cpath[last:], filepath.Separator)
141 if pos < 0 {
142 break
143 } else if pos == 0 {
144 last++
145
146 continue
147 }
148
149 last += pos
150
151 if err := f.mkdir("mkdirall", path, cpath[:last], perm); err != nil && !errors.Is(err, fs.ErrExist) {
152 return err
153 }
154 }
155
156 return f.mkdir("mkdirall", path, cpath, perm)
157 }
158
159 const defaultPerms = 0o666
160
161 func (f *FS) Create(path string) (*File, error) {
162 return f.openFile("create", path, ReadWrite|Create|Truncate, defaultPerms)
163 }
164
165 // Mode is used to determine how a file is opened.
166 //
167 // Each value of Mode matches the intention of its similarly named OS
168 // counterpart.
169 type Mode uint8
170
171 const (
172 ReadOnly Mode = 1 << iota
173 WriteOnly
174 Append
175 Create
176 Excl
177 Truncate
178
179 ReadWrite = ReadOnly | WriteOnly
180 )
181
182 func existCheck(mode Mode) exists {
183 if mode&Excl != 0 {
184 return mustNotExist
185 } else if mode&Create == 0 {
186 return mustExist
187 }
188
189 return doesntMatter
190 }
191
192 func openMode(mode Mode) opMode {
193 openMode := opSeek
194 if mode&ReadOnly != 0 {
195 openMode |= opRead
196 }
197
198 if mode&WriteOnly != 0 {
199 openMode |= opWrite
200 }
201
202 return openMode
203 }
204
205 func (f *FS) openOrCreateFile(path string, mode Mode, perm fs.FileMode) (fs.File, error) {
206 d, existingFile, err := f.getEntryWithParent(path, existCheck(mode))
207 if err != nil {
208 return nil, err
209 }
210
211 fileName := filepath.Base(path)
212
213 if existingFile == nil {
214 existingFile = &dirEnt{
215 directoryEntry: &inodeRW{
216 inode: inode{
217 modtime: time.Now(),
218 mode: perm,
219 },
220 },
221 name: fileName,
222 }
223
224 if err = d.setEntry(existingFile); err != nil {
225 return nil, err
226 }
227 }
228
229 return existingFile.open(fileName, openMode(mode))
230 }
231
232 func (f *FS) openFile(op, path string, mode Mode, perm fs.FileMode) (*File, error) {
233 f.mu.Lock()
234 defer f.mu.Unlock()
235
236 of, err := f.openOrCreateFile(path, mode, perm)
237 if err != nil {
238 return nil, &fs.PathError{Op: op, Path: path, Err: err}
239 }
240
241 ef, ok := of.(*File)
242 if !ok {
243 return nil, &fs.PathError{Op: op, Path: path, Err: fs.ErrInvalid}
244 }
245
246 ef.handleOpenMode(mode)
247
248 return ef, nil
249 }
250
251 func (f *FS) OpenFile(path string, mode Mode, perm fs.FileMode) (*File, error) {
252 return f.openFile("openfile", path, mode, perm)
253 }
254
255 func (f *FS) Link(oldPath, newPath string) error {
256 f.mu.Lock()
257 defer f.mu.Unlock()
258
259 if oe, err := f.getLEntry(oldPath); err != nil {
260 return &fs.PathError{Op: "link", Path: oldPath, Err: err}
261 } else if oe.IsDir() {
262 return &fs.PathError{Op: "link", Path: oldPath, Err: fs.ErrInvalid}
263 } else if d, _, err := f.getEntryWithParent(newPath, mustNotExist); err != nil {
264 return &fs.PathError{Op: "link", Path: newPath, Err: err}
265 } else if err := d.setEntry(&dirEnt{directoryEntry: oe.directoryEntry, name: filepath.Base(newPath)}); err != nil {
266 return &fs.PathError{Op: "link", Path: newPath, Err: err}
267 }
268
269 return nil
270 }
271
272 func (f *FS) Symlink(oldPath, newPath string) error {
273 f.mu.Lock()
274 defer f.mu.Unlock()
275
276 d, _, err := f.getEntryWithParent(newPath, mustNotExist)
277 if err != nil {
278 return &fs.PathError{
279 Op: "symlink",
280 Path: newPath,
281 Err: err,
282 }
283 }
284
285 if err = d.setEntry(&dirEnt{
286 directoryEntry: &inodeRW{
287 inode: inode{
288 data: []byte(filepath.Clean(oldPath)),
289 modtime: time.Now(),
290 mode: fs.ModeSymlink | fs.ModePerm,
291 },
292 },
293 name: filepath.Base(newPath),
294 }); err != nil {
295 return &fs.PathError{
296 Op: "symlink",
297 Path: newPath,
298 Err: err,
299 }
300 }
301
302 return nil
303 }
304
305 const canWrite = 0o222
306
307 func (f *FS) Rename(oldPath, newPath string) error {
308 f.mu.Lock()
309 defer f.mu.Unlock()
310
311 if od, oldFile, err := f.getEntryWithParent(oldPath, mustExist); err != nil {
312 return &fs.PathError{Op: "rename", Path: oldPath, Err: err}
313 } else if nd, _, err := f.getEntryWithParent(newPath, mustNotExist); err != nil {
314 return &fs.PathError{Op: "rename", Path: newPath, Err: err}
315 } else if nd.Mode()&canWrite == 0 {
316 return &fs.PathError{Op: "rename", Path: newPath, Err: fs.ErrPermission}
317 } else if err = od.removeEntry(oldFile.name); err != nil {
318 return &fs.PathError{Op: "rename", Path: newPath, Err: err}
319 } else if err = nd.setEntry(&dirEnt{directoryEntry: oldFile.directoryEntry, name: filepath.Base(newPath)}); err != nil {
320 return &fs.PathError{Op: "rename", Path: newPath, Err: err}
321 }
322
323 return nil
324 }
325
326 func (f *FS) Remove(path string) error {
327 f.mu.Lock()
328 defer f.mu.Unlock()
329
330 d, de, err := f.getEntryWithParent(path, mustExist)
331 if err != nil {
332 return &fs.PathError{Op: "remove", Path: path, Err: err}
333 }
334
335 if dir, ok := de.directoryEntry.(dNode); ok && dir.hasEntries() {
336 return &fs.PathError{Op: "remove", Path: path, Err: fs.ErrInvalid}
337 }
338
339 if err = d.removeEntry(de.name); err != nil {
340 return &fs.PathError{Op: "remove", Path: path, Err: err}
341 }
342
343 return nil
344 }
345
346 func splitPath(path string) (string, string) {
347 dirName, fileName := filepath.Split(filepath.Join("/", path))
348
349 if dirName == "/" {
350 dirName = "."
351 } else {
352 dirName = strings.TrimPrefix(dirName, "/")
353 }
354
355 return strings.TrimSuffix(dirName, "/"), fileName
356 }
357
358 func (f *FS) RemoveAll(path string) error {
359 f.mu.Lock()
360 defer f.mu.Unlock()
361
362 dirName, fileName := splitPath(path)
363
364 d, err := f.getDirEnt(dirName)
365 if err != nil {
366 return &fs.PathError{
367 Op: "removeall",
368 Path: path,
369 Err: err,
370 }
371 }
372
373 if err := d.removeEntry(fileName); err != nil {
374 return &fs.PathError{
375 Op: "removeall",
376 Path: path,
377 Err: err,
378 }
379 }
380
381 return nil
382 }
383
384 func (f *FS) LStat(path string) (fs.FileInfo, error) {
385 f.mu.RLock()
386 defer f.mu.RUnlock()
387
388 return f.fsRO.LStat(path)
389 }
390
391 func (f *FS) Readlink(path string) (string, error) {
392 f.mu.RLock()
393 defer f.mu.RUnlock()
394
395 return f.fsRO.Readlink(path)
396 }
397
398 func (f *FS) Chown(path string, _, _ int) error {
399 f.mu.RLock()
400 defer f.mu.RUnlock()
401
402 if _, err := f.getEntry(path); err != nil {
403 return &fs.PathError{
404 Op: "chown",
405 Path: path,
406 Err: err,
407 }
408 }
409
410 return nil
411 }
412
413 func (f *FS) Chmod(path string, mode fs.FileMode) error {
414 f.mu.RLock()
415 defer f.mu.RUnlock()
416
417 de, err := f.getEntry(path)
418 if err != nil {
419 return &fs.PathError{
420 Op: "chmod",
421 Path: path,
422 Err: err,
423 }
424 }
425
426 de.setMode(mode & fs.ModePerm)
427
428 return nil
429 }
430
431 func (f *FS) Lchown(path string, _, _ int) error {
432 f.mu.RLock()
433 defer f.mu.RUnlock()
434
435 if _, err := f.getLEntry(path); err != nil {
436 return &fs.PathError{
437 Op: "lchown",
438 Path: path,
439 Err: err,
440 }
441 }
442
443 return nil
444 }
445
446 func (f *FS) Chtimes(path string, atime time.Time, mtime time.Time) error {
447 f.mu.RLock()
448 defer f.mu.RUnlock()
449
450 de, err := f.getEntry(path)
451 if err != nil {
452 return &fs.PathError{
453 Op: "chtimes",
454 Path: path,
455 Err: err,
456 }
457 }
458
459 de.setTimes(atime, mtime)
460
461 return nil
462 }
463
464 func (f *FS) Lchtimes(path string, atime time.Time, mtime time.Time) error {
465 f.mu.RLock()
466 defer f.mu.RUnlock()
467
468 de, err := f.getLEntry(path)
469 if err != nil {
470 return &fs.PathError{
471 Op: "lchtimes",
472 Path: path,
473 Err: err,
474 }
475 }
476
477 de.setTimes(atime, mtime)
478
479 return nil
480 }
481
482 func (f *FS) Sub(path string) (fs.FS, error) {
483 f.mu.RLock()
484 defer f.mu.RUnlock()
485
486 de, err := f.fsRO.sub(path)
487 if err != nil {
488 return nil, err
489 }
490
491 return &FS{
492 fsRO: fsRO{
493 de: de,
494 },
495 }, nil
496 }
497