Skip to content

Commit 98a5a28

Browse files
authored
*: Readlink and Symlink implementation (#32)
1 parent 81e8eae commit 98a5a28

File tree

11 files changed

+314
-174
lines changed

11 files changed

+314
-174
lines changed

fs.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,14 @@ type Filesystem interface {
3939
MkdirAll(filename string, perm os.FileMode) error
4040
Join(elem ...string) string
4141
Dir(path string) Filesystem
42-
Base() string
43-
}
44-
45-
// Symlinker is a Filesystem with support for creating symlinks.
46-
type Symlinker interface {
47-
Filesystem
48-
4942
// Symlink creates a symbolic-link from link to target. target may be an
5043
// absolute or relative path, and need not refer to an existing node.
5144
// Parent directories of link are created as necessary.
5245
Symlink(target, link string) error
53-
5446
// Readlink returns the target path of link. An error is returned if link is
5547
// not a symbolic-link.
5648
Readlink(link string) (string, error)
49+
Base() string
5750
}
5851

5952
// File implements io.Closer, io.Reader, io.Seeker, and io.Writer>

memfs/memory.go

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"os"
99
"path/filepath"
10+
"strings"
1011
"time"
1112

1213
"gopkg.in/src-d/go-billy.v2"
@@ -20,13 +21,19 @@ type Memory struct {
2021
s *storage
2122

2223
tempCount int
24+
links map[string]internalLink
25+
}
26+
27+
type internalLink struct {
28+
Name, Target string
2329
}
2430

2531
//New returns a new Memory filesystem
2632
func New() *Memory {
2733
return &Memory{
28-
base: string(separator),
29-
s: newStorage(),
34+
base: string(separator),
35+
s: newStorage(),
36+
links: make(map[string]internalLink),
3037
}
3138
}
3239

@@ -42,8 +49,7 @@ func (fs *Memory) Open(filename string) (billy.File, error) {
4249

4350
// OpenFile returns the file from a given name with given flag and permits.
4451
func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
45-
path := fs.Join(fs.base, filename)
46-
52+
path := fs.resolvePath(filename)
4753
f, has := fs.s.Get(path)
4854
if !has {
4955
if !isCreate(flag) {
@@ -69,21 +75,48 @@ func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.F
6975
return f.Duplicate(filename, perm, flag), nil
7076
}
7177

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
83+
}
84+
85+
if isAbs(l.Target) {
86+
return l.Target
87+
}
88+
89+
return fs.resolvePath(fs.Join(filepath.Dir(fullpath), l.Target))
90+
}
91+
92+
// On Windows OS, IsAbs validates if a path is valid based on if stars with a
93+
// unit (eg.: `C:\`) to assert that is absolute, but in this mem implementation
94+
// any path starting by `separator` is also considered absolute.
95+
func isAbs(path string) bool {
96+
return filepath.IsAbs(path) || strings.HasPrefix(path, string(separator))
97+
}
98+
7299
// Stat returns a billy.FileInfo with the information of the requested file.
73100
func (fs *Memory) Stat(filename string) (billy.FileInfo, error) {
74-
fullpath := fs.Join(fs.base, filename)
75-
101+
fullpath := fs.resolvePath(filename)
76102
f, has := fs.s.Get(fullpath)
77103
if !has {
78104
return nil, os.ErrNotExist
79105
}
80106

81-
return f.Stat(), nil
107+
fi := f.Stat().(*fileInfo)
108+
109+
// the name of the file should always the name of the stated file, so we
110+
// overwrite the Stat returned from the storage with it, since the
111+
// filename may belong to a link.
112+
fi.name = filepath.Base(filename)
113+
114+
return fi, nil
82115
}
83116

84117
// ReadDir returns a list of billy.FileInfo in the given directory.
85118
func (fs *Memory) ReadDir(path string) ([]billy.FileInfo, error) {
86-
path = fs.Join(fs.base, path)
119+
path = fs.resolvePath(path)
87120

88121
var entries []billy.FileInfo
89122
for _, f := range fs.s.Children(path) {
@@ -128,18 +161,49 @@ func (fs *Memory) getTempFilename(dir, prefix string) string {
128161

129162
// Rename moves a the `from` file to the `to` file.
130163
func (fs *Memory) Rename(from, to string) error {
164+
if fs.renameIfLink(from, to) {
165+
return nil
166+
}
167+
131168
from = fs.Join(fs.base, from)
132169
to = fs.Join(fs.base, to)
133170

134171
return fs.s.Rename(from, to)
135172
}
136173

174+
func (fs *Memory) renameIfLink(from, to string) bool {
175+
from = clean(fs.Join(fs.base, from))
176+
to = clean(fs.Join(fs.base, to))
177+
178+
if _, ok := fs.links[from]; !ok {
179+
return false
180+
}
181+
182+
fs.links[to] = fs.links[from]
183+
delete(fs.links, from)
184+
return true
185+
}
186+
137187
// Remove deletes a given file from storage.
138188
func (fs *Memory) Remove(filename string) error {
189+
if fs.removeIfLink(filename) {
190+
return nil
191+
}
192+
139193
fullpath := fs.Join(fs.base, filename)
140194
return fs.s.Remove(fullpath)
141195
}
142196

197+
func (fs *Memory) removeIfLink(filename string) bool {
198+
fullpath := clean(fs.Join(fs.base, filename))
199+
if _, ok := fs.links[fullpath]; !ok {
200+
return false
201+
}
202+
203+
delete(fs.links, fullpath)
204+
return true
205+
}
206+
143207
// Join joins any number of path elements into a single path, adding a Separator if necessary.
144208
func (fs *Memory) Join(elem ...string) string {
145209
return filepath.Join(elem...)
@@ -154,6 +218,29 @@ func (fs *Memory) Dir(path string) billy.Filesystem {
154218
}
155219
}
156220

221+
func (fs *Memory) Symlink(target, link string) error {
222+
if _, err := fs.Stat(link); err == nil {
223+
return os.ErrExist
224+
}
225+
226+
fullpath := clean(fs.Join(fs.base, link))
227+
fs.links[fullpath] = internalLink{
228+
Name: link,
229+
Target: clean(target),
230+
}
231+
232+
return nil
233+
}
234+
235+
func (fs *Memory) Readlink(link string) (string, error) {
236+
fullpath := clean(fs.Join(fs.base, link))
237+
if l, ok := fs.links[fullpath]; ok {
238+
return l.Target, nil
239+
}
240+
241+
return "", os.ErrNotExist
242+
}
243+
157244
// Base returns the base path for the filesystem.
158245
func (fs *Memory) Base() string {
159246
return fs.base

memfs/storage.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ func newStorage() *storage {
2222
}
2323

2424
func (s *storage) Has(path string) bool {
25-
path = filepath.Clean(path)
25+
path = clean(path)
2626

2727
_, ok := s.files[path]
2828
return ok
2929
}
3030

3131
func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) {
32-
path = filepath.Clean(path)
32+
path = clean(path)
3333
if s.Has(path) {
3434
if !s.MustGet(path).mode.IsDir() {
3535
return nil, fmt.Errorf("file already exists %q", path)
@@ -54,7 +54,7 @@ func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) {
5454

5555
func (s *storage) createParent(path string, mode os.FileMode, f *file) error {
5656
base := filepath.Dir(path)
57-
base = filepath.Clean(base)
57+
base = clean(base)
5858
if f.Filename() == string(separator) {
5959
return nil
6060
}
@@ -72,7 +72,7 @@ func (s *storage) createParent(path string, mode os.FileMode, f *file) error {
7272
}
7373

7474
func (s *storage) Children(path string) []*file {
75-
path = filepath.Clean(path)
75+
path = clean(path)
7676

7777
l := make([]*file, 0)
7878
for _, f := range s.children[path] {
@@ -92,7 +92,7 @@ func (s *storage) MustGet(path string) *file {
9292
}
9393

9494
func (s *storage) Get(path string) (*file, bool) {
95-
path = filepath.Clean(path)
95+
path = clean(path)
9696
if !s.Has(path) {
9797
return nil, false
9898
}
@@ -102,8 +102,8 @@ func (s *storage) Get(path string) (*file, bool) {
102102
}
103103

104104
func (s *storage) Rename(from, to string) error {
105-
from = filepath.Clean(from)
106-
to = filepath.Clean(to)
105+
from = clean(from)
106+
to = clean(to)
107107

108108
if !s.Has(from) {
109109
return os.ErrNotExist
@@ -149,7 +149,7 @@ func (s *storage) move(from, to string) error {
149149
}
150150

151151
func (s *storage) Remove(path string) error {
152-
path = filepath.Clean(path)
152+
path = clean(path)
153153

154154
f, has := s.Get(path)
155155
if !has {
@@ -168,6 +168,10 @@ func (s *storage) Remove(path string) error {
168168
return nil
169169
}
170170

171+
func clean(path string) string {
172+
return filepath.Clean(filepath.FromSlash(path))
173+
}
174+
171175
type content struct {
172176
bytes []byte
173177
}

osfs/os.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
"path/filepath"
88

9+
"strings"
10+
911
"gopkg.in/src-d/go-billy.v2"
1012
)
1113

@@ -35,7 +37,7 @@ func (fs *OS) Create(filename string) (billy.File, error) {
3537
// OpenFile is equivalent to standard os.OpenFile.
3638
// If flag os.O_CREATE is set, all parent directories will be created.
3739
func (fs *OS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
38-
fullpath := fs.Join(fs.base, filename)
40+
fullpath := fs.absolutize(filename)
3941

4042
if flag&os.O_CREATE != 0 {
4143
if err := fs.createDir(fullpath); err != nil {
@@ -70,7 +72,7 @@ func (fs *OS) createDir(fullpath string) error {
7072
// ReadDir returns the filesystem info for all the archives under the specified
7173
// path.
7274
func (fs *OS) ReadDir(path string) ([]billy.FileInfo, error) {
73-
fullpath := fs.Join(fs.base, path)
75+
fullpath := fs.absolutize(path)
7476

7577
l, err := ioutil.ReadDir(fullpath)
7678
if err != nil {
@@ -87,8 +89,8 @@ func (fs *OS) ReadDir(path string) ([]billy.FileInfo, error) {
8789

8890
// Rename moves a file in disk from _from_ to _to_.
8991
func (fs *OS) Rename(from, to string) error {
90-
from = fs.Join(fs.base, from)
91-
to = fs.Join(fs.base, to)
92+
from = fs.absolutize(from)
93+
to = fs.absolutize(to)
9294

9395
if err := fs.createDir(to); err != nil {
9496
return err
@@ -99,7 +101,7 @@ func (fs *OS) Rename(from, to string) error {
99101

100102
// MkdirAll creates a directory.
101103
func (fs *OS) MkdirAll(path string, perm os.FileMode) error {
102-
fullpath := fs.Join(fs.base, path)
104+
fullpath := fs.absolutize(path)
103105
return os.MkdirAll(fullpath, defaultDirectoryMode)
104106
}
105107

@@ -108,21 +110,15 @@ func (fs *OS) Open(filename string) (billy.File, error) {
108110
return fs.OpenFile(filename, os.O_RDONLY, 0)
109111
}
110112

111-
// Stat returns the FileInfo structure describing file.
112-
func (fs *OS) Stat(filename string) (billy.FileInfo, error) {
113-
fullpath := fs.Join(fs.base, filename)
114-
return os.Stat(fullpath)
115-
}
116-
117113
// Remove deletes a file in disk.
118114
func (fs *OS) Remove(filename string) error {
119-
fullpath := fs.Join(fs.base, filename)
115+
fullpath := fs.absolutize(filename)
120116
return os.Remove(fullpath)
121117
}
122118

123119
// TempFile creates a new temporal file.
124120
func (fs *OS) TempFile(dir, prefix string) (billy.File, error) {
125-
fullpath := fs.Join(fs.base, dir)
121+
fullpath := fs.absolutize(dir)
126122
if err := fs.createDir(fullpath + string(os.PathSeparator)); err != nil {
127123
return nil, err
128124
}
@@ -153,7 +149,7 @@ func (fs *OS) Join(elem ...string) string {
153149
// Dir returns a new Filesystem from the same type of fs using as baseDir the
154150
// given path
155151
func (fs *OS) Dir(path string) billy.Filesystem {
156-
return New(fs.Join(fs.base, path))
152+
return New(fs.absolutize(path))
157153
}
158154

159155
// Base returns the base path of the filesytem
@@ -170,12 +166,14 @@ func (fs *OS) RemoveAll(path string) error {
170166

171167
// Symlink imlements billy.Symlinker.Symlink.
172168
func (fs *OS) Symlink(target, link string) error {
173-
if filepath.IsAbs(target) {
174-
// only rewrite target if it's already absolute
175-
target = fs.Join(fs.base, target)
169+
target = filepath.FromSlash(target)
170+
171+
// only rewrite target if it's already absolute
172+
if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
173+
target = fs.absolutize(target)
176174
}
177-
link = fs.Join(fs.base, link)
178175

176+
link = fs.absolutize(link)
179177
if err := fs.createDir(link); err != nil {
180178
return err
181179
}
@@ -191,7 +189,7 @@ func (fs *OS) Readlink(link string) (string, error) {
191189
return "", err
192190
}
193191

194-
if !filepath.IsAbs(target) {
192+
if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) {
195193
return target, nil
196194
}
197195

@@ -237,3 +235,10 @@ func (f *osFile) Close() error {
237235
func (f *osFile) ReadAt(p []byte, off int64) (int, error) {
238236
return f.file.ReadAt(p, off)
239237
}
238+
239+
func (fs *OS) absolutize(relpath string) string {
240+
fullpath := filepath.FromSlash(filepath.ToSlash(relpath))
241+
242+
fullpath = fs.Join(fs.base, fullpath)
243+
return filepath.Clean(fullpath)
244+
}

0 commit comments

Comments
 (0)