1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
// Package wfs implements a "writable file system" in the spirit of [fs.FS].
package wfs
import (
"errors"
"io"
"io/fs"
"path"
)
// ReadLinkFS is backported from the latest go master.
type ReadLinkFS interface {
fs.FS
ReadLink(string) (string, error)
Lstat(string) (fs.FileInfo, error)
}
// ReadLink returns the destination of the named symbolic link.
//
// If fsys does not implement [ReadLinkFS], then ReadLink returns an error.
func ReadLink(fsys fs.FS, name string) (string, error) {
sym, ok := fsys.(ReadLinkFS)
if !ok {
return "", &fs.PathError{Op: "readlink", Path: name, Err: fs.ErrInvalid}
}
return sym.ReadLink(name)
}
// Lstat returns an [fs.FileInfo] describing the named file.
// If the file is a symbolic link, the returned [fs.FileInfo] describes the
// symbolic link. Lstat makes no attempt to follow the link.
//
// If fsys does not implement [ReadLinkFS], then Lstat is identical
// to [fs.Stat].
func Lstat(fsys fs.FS, name string) (fs.FileInfo, error) {
sym, ok := fsys.(ReadLinkFS)
if !ok {
return fs.Stat(fsys, name)
}
return sym.Lstat(name)
}
// An FS provides access to a writable hierarchical file system.
type FS interface {
fs.FS
Create(string, fs.FileMode) (io.WriteCloser, error)
Remove(string) error
Mkdir(string) error
Symlink(string, string) error
Chmod(string, fs.FileMode) error
}
// A MkdirModeFS is a file system with a mkdir method that accepts a file mode.
type MkdirModeFS interface {
FS
MkdirMode(string, fs.FileMode) error
}
// MkdirMode creates a directory with the given file permission. If fsys
// implements [MkdirModeFS], MkdirMode calls fsys.MkdirMode. Otherwise,
// MkdirMode calls Mkdir and then Chmod to set the mode.
func MkdirMode(fsys FS, name string, mode fs.FileMode) error {
if fsys, ok := fsys.(MkdirModeFS); ok {
return fsys.MkdirMode(name, mode)
}
if err := fsys.Mkdir(name); err != nil {
return err
}
return fsys.Chmod(name, mode)
}
func removeDir(fsys FS, dir string) error {
entries, readErr := fs.ReadDir(fsys, dir)
var err error
for _, d := range entries {
if err1 := removeAll(fsys, path.Join(dir, d.Name()), d); err == nil {
err = err1
}
}
if err == nil {
err = readErr
}
err1 := fsys.Remove(dir)
if err1 == nil || errors.Is(err, fs.ErrNotExist) {
return nil
}
if err == nil {
err = err1
}
return err
}
func removeAll(fsys FS, path string, d fs.DirEntry) error {
err := fsys.Remove(path)
if err == nil || errors.Is(err, fs.ErrNotExist) {
return nil
}
if !d.IsDir() {
return err
}
return removeDir(fsys, path)
}
// RemoveAll removes path and any children it contains. It removes everything it
// can but returns the first error it encounters. If the path does not exist,
// RemoveAll returns nil (no error).
func RemoveAll(fsys FS, path string) error {
// Simple case: if Remove works, we're done.
err := fsys.Remove(path)
if err == nil || errors.Is(err, fs.ErrNotExist) {
return nil
}
// Otherwise, is this a directory we need to recurse into?
dir, serr := Lstat(fsys, path)
if serr != nil {
if errors.Is(serr, fs.ErrNotExist) {
return nil
}
return serr
}
if !dir.IsDir() {
// Not a directory; return the error from Remove.
return err
}
return removeDir(fsys, path)
}
|