Skip to content

Commit 51f7830

Browse files
committed
memfs: better symlink support
1 parent e2b1f0d commit 51f7830

File tree

1 file changed

+69
-75
lines changed

1 file changed

+69
-75
lines changed

memfs/memory.go

Lines changed: 69 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,13 @@ type Memory struct {
2121
s *storage
2222

2323
tempCount int
24-
links map[string]internalLink
25-
}
26-
27-
type internalLink struct {
28-
Name, Target string
2924
}
3025

3126
//New returns a new Memory filesystem
3227
func New() *Memory {
3328
return &Memory{
34-
base: string(separator),
35-
s: newStorage(),
36-
links: make(map[string]internalLink),
29+
base: string(separator),
30+
s: newStorage(),
3731
}
3832
}
3933

@@ -49,44 +43,51 @@ func (fs *Memory) Open(filename string) (billy.File, error) {
4943

5044
// OpenFile returns the file from a given name with given flag and permits.
5145
func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
52-
path := fs.resolvePath(filename)
53-
f, has := fs.s.Get(path)
46+
fullpath := fs.fullpath(filename)
47+
f, has := fs.s.Get(fullpath)
5448
if !has {
5549
if !isCreate(flag) {
5650
return nil, os.ErrNotExist
5751
}
5852

5953
var err error
60-
f, err = fs.s.New(path, perm, flag)
54+
f, err = fs.s.New(fullpath, perm, flag)
6155
if err != nil {
6256
return nil, err
6357
}
58+
} else {
59+
if target, isLink := fs.resolveIfLink(fullpath, f); isLink {
60+
return fs.OpenFile(target, flag, perm)
61+
}
6462
}
6563

6664
if f.mode.IsDir() {
6765
return nil, fmt.Errorf("cannot open directory: %s", filename)
6866
}
6967

70-
filename, err := filepath.Rel(fs.base, path)
68+
filename, err := filepath.Rel(fs.base, fullpath)
7169
if err != nil {
7270
return nil, err
7371
}
7472

7573
return f.Duplicate(filename, perm, flag), nil
7674
}
7775

78-
func (fs *Memory) resolvePath(path string) string {
79-
fullpath := clean(fs.Join(fs.base, path))
80-
l, ok := fs.links[fullpath]
81-
if !ok {
82-
return fullpath
76+
func (fs *Memory) fullpath(path string) string {
77+
return clean(fs.Join(fs.base, path))
78+
}
79+
80+
func (fs *Memory) resolveIfLink(fullpath string, f *file) (target string, isLink bool) {
81+
if !isSymlink(f.mode) {
82+
return fullpath, false
8383
}
8484

85-
if isAbs(l.Target) {
86-
return l.Target
85+
target = string(f.content.bytes)
86+
if !isAbs(target) {
87+
target = fs.Join(filepath.Dir(fullpath), target)
8788
}
8889

89-
return fs.resolvePath(fs.Join(filepath.Dir(fullpath), l.Target))
90+
return clean(target), true
9091
}
9192

9293
// On Windows OS, IsAbs validates if a path is valid based on if stars with a
@@ -98,41 +99,50 @@ func isAbs(path string) bool {
9899

99100
// Stat returns a billy.FileInfo with the information of the requested file.
100101
func (fs *Memory) Stat(filename string) (billy.FileInfo, error) {
101-
fullpath := fs.resolvePath(filename)
102+
fullpath := fs.fullpath(filename)
102103
f, has := fs.s.Get(fullpath)
103104
if !has {
104105
return nil, os.ErrNotExist
105106
}
106107

107-
fi := f.Stat().(*fileInfo)
108+
fi := f.Stat()
109+
110+
var err error
111+
if target, isLink := fs.resolveIfLink(fullpath, f); isLink {
112+
fi, err = fs.Stat(target)
113+
if err != nil {
114+
return nil, err
115+
}
116+
}
108117

109118
// the name of the file should always the name of the stated file, so we
110119
// overwrite the Stat returned from the storage with it, since the
111120
// filename may belong to a link.
112-
fi.name = filepath.Base(filename)
113-
121+
fi.(*fileInfo).name = filepath.Base(filename)
114122
return fi, nil
115123
}
116124

117125
func (fs *Memory) Lstat(filename string) (billy.FileInfo, error) {
118-
fullpath := clean(fs.Join(fs.base, filename))
119-
l, ok := fs.links[fullpath]
120-
if !ok {
121-
return fs.Stat(filename)
126+
fullpath := fs.fullpath(filename)
127+
f, has := fs.s.Get(fullpath)
128+
if !has {
129+
return nil, os.ErrNotExist
122130
}
123131

124-
return &fileInfo{
125-
name: filepath.Base(l.Name),
126-
mode: 0777 | os.ModeSymlink,
127-
}, nil
132+
return f.Stat(), nil
128133
}
129134

130135
// ReadDir returns a list of billy.FileInfo in the given directory.
131136
func (fs *Memory) ReadDir(path string) ([]billy.FileInfo, error) {
132-
path = fs.resolvePath(path)
137+
fullpath := fs.fullpath(path)
138+
if f, has := fs.s.Get(fullpath); has {
139+
if target, isLink := fs.resolveIfLink(fullpath, f); isLink {
140+
return fs.ReadDir(target)
141+
}
142+
}
133143

134144
var entries []billy.FileInfo
135-
for _, f := range fs.s.Children(path) {
145+
for _, f := range fs.s.Children(fullpath) {
136146
entries = append(entries, f.Stat())
137147
}
138148

@@ -174,49 +184,18 @@ func (fs *Memory) getTempFilename(dir, prefix string) string {
174184

175185
// Rename moves a the `from` file to the `to` file.
176186
func (fs *Memory) Rename(from, to string) error {
177-
if fs.renameIfLink(from, to) {
178-
return nil
179-
}
180-
181187
from = fs.Join(fs.base, from)
182188
to = fs.Join(fs.base, to)
183189

184190
return fs.s.Rename(from, to)
185191
}
186192

187-
func (fs *Memory) renameIfLink(from, to string) bool {
188-
from = clean(fs.Join(fs.base, from))
189-
to = clean(fs.Join(fs.base, to))
190-
191-
if _, ok := fs.links[from]; !ok {
192-
return false
193-
}
194-
195-
fs.links[to] = fs.links[from]
196-
delete(fs.links, from)
197-
return true
198-
}
199-
200193
// Remove deletes a given file from storage.
201194
func (fs *Memory) Remove(filename string) error {
202-
if fs.removeIfLink(filename) {
203-
return nil
204-
}
205-
206195
fullpath := fs.Join(fs.base, filename)
207196
return fs.s.Remove(fullpath)
208197
}
209198

210-
func (fs *Memory) removeIfLink(filename string) bool {
211-
fullpath := clean(fs.Join(fs.base, filename))
212-
if _, ok := fs.links[fullpath]; !ok {
213-
return false
214-
}
215-
216-
delete(fs.links, fullpath)
217-
return true
218-
}
219-
220199
// Join joins any number of path elements into a single path, adding a Separator if necessary.
221200
func (fs *Memory) Join(elem ...string) string {
222201
return filepath.Join(elem...)
@@ -232,26 +211,37 @@ func (fs *Memory) Dir(path string) billy.Filesystem {
232211
}
233212

234213
func (fs *Memory) Symlink(target, link string) error {
235-
if _, err := fs.Stat(link); err == nil {
214+
fullpath := clean(fs.Join(fs.base, link))
215+
216+
_, err := fs.Stat(fullpath)
217+
if err == nil {
236218
return os.ErrExist
237219
}
238220

239-
fullpath := clean(fs.Join(fs.base, link))
240-
fs.links[fullpath] = internalLink{
241-
Name: link,
242-
Target: clean(target),
221+
if !os.IsNotExist(err) {
222+
return err
243223
}
244224

245-
return nil
225+
target = clean(target)
226+
return billy.WriteFile(fs, fullpath, []byte(target), 0777|os.ModeSymlink)
246227
}
247228

248229
func (fs *Memory) Readlink(link string) (string, error) {
249-
fullpath := clean(fs.Join(fs.base, link))
250-
if l, ok := fs.links[fullpath]; ok {
251-
return l.Target, nil
230+
fullpath := fs.fullpath(link)
231+
f, has := fs.s.Get(fullpath)
232+
if !has {
233+
return "", os.ErrNotExist
252234
}
253235

254-
return "", os.ErrNotExist
236+
if !isSymlink(f.mode) {
237+
return "", &os.PathError{
238+
Op: "readlink",
239+
Path: fullpath,
240+
Err: fmt.Errorf("not a symlink"),
241+
}
242+
}
243+
244+
return string(f.content.bytes), nil
255245
}
256246

257247
// Base returns the base path for the filesystem.
@@ -422,3 +412,7 @@ func isReadOnly(flag int) bool {
422412
func isWriteOnly(flag int) bool {
423413
return flag&os.O_WRONLY != 0
424414
}
415+
416+
func isSymlink(m os.FileMode) bool {
417+
return m&os.ModeSymlink != 0
418+
}

0 commit comments

Comments
 (0)