Skip to content

Commit e55a933

Browse files
committed
Copy Go implementation of EvalSymlinks
1 parent 82d3e13 commit e55a933

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

internal/vfs/os.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (vfs *osFS) Realpath(path string) string {
8282

8383
orig := path
8484
path = filepath.FromSlash(path)
85-
path, err := filepath.EvalSymlinks(path)
85+
path, err := walkSymlinks(path)
8686
if err != nil {
8787
return orig
8888
}

internal/vfs/symlink.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2012 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package vfs
6+
7+
import (
8+
"errors"
9+
"io/fs"
10+
"os"
11+
"path/filepath"
12+
"runtime"
13+
"syscall"
14+
)
15+
16+
func volumeNameLen(path string) int {
17+
return len(filepath.VolumeName(path))
18+
}
19+
20+
func walkSymlinks(path string) (string, error) {
21+
volLen := volumeNameLen(path)
22+
pathSeparator := string(os.PathSeparator)
23+
24+
if volLen < len(path) && os.IsPathSeparator(path[volLen]) {
25+
volLen++
26+
}
27+
vol := path[:volLen]
28+
dest := vol
29+
linksWalked := 0
30+
for start, end := volLen, volLen; start < len(path); start = end { //nolint:ineffassign
31+
for start < len(path) && os.IsPathSeparator(path[start]) {
32+
start++
33+
}
34+
end = start
35+
for end < len(path) && !os.IsPathSeparator(path[end]) {
36+
end++
37+
}
38+
39+
// On Windows, "." can be a symlink.
40+
// We look it up, and use the value if it is absolute.
41+
// If not, we just return ".".
42+
isWindowsDot := runtime.GOOS == "windows" && path[volumeNameLen(path):] == "."
43+
44+
// The next path component is in path[start:end].
45+
if end == start {
46+
// No more path components.
47+
break
48+
} else if path[start:end] == "." && !isWindowsDot {
49+
// Ignore path component ".".
50+
continue
51+
} else if path[start:end] == ".." {
52+
// Back up to previous component if possible.
53+
// Note that volLen includes any leading slash.
54+
55+
// Set r to the index of the last slash in dest,
56+
// after the volume.
57+
var r int
58+
for r = len(dest) - 1; r >= volLen; r-- {
59+
if os.IsPathSeparator(dest[r]) {
60+
break
61+
}
62+
}
63+
if r < volLen || dest[r+1:] == ".." {
64+
// Either path has no slashes
65+
// (it's empty or just "C:")
66+
// or it ends in a ".." we had to keep.
67+
// Either way, keep this "..".
68+
if len(dest) > volLen {
69+
dest += pathSeparator
70+
}
71+
dest += ".."
72+
} else {
73+
// Discard everything since the last slash.
74+
dest = dest[:r]
75+
}
76+
continue
77+
}
78+
79+
// Ordinary path component. Add it to result.
80+
81+
if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
82+
dest += pathSeparator
83+
}
84+
85+
dest += path[start:end]
86+
87+
// Resolve symlink.
88+
89+
fi, err := os.Lstat(dest)
90+
if err != nil {
91+
return "", err
92+
}
93+
94+
if fi.Mode()&fs.ModeSymlink == 0 {
95+
if !fi.Mode().IsDir() && end < len(path) {
96+
return "", syscall.ENOTDIR
97+
}
98+
continue
99+
}
100+
101+
// Found symlink.
102+
103+
linksWalked++
104+
if linksWalked > 255 {
105+
return "", errors.New("EvalSymlinks: too many links")
106+
}
107+
108+
link, err := os.Readlink(dest)
109+
if err != nil {
110+
return "", err
111+
}
112+
113+
if isWindowsDot && !filepath.IsAbs(link) {
114+
// On Windows, if "." is a relative symlink,
115+
// just return ".".
116+
break
117+
}
118+
119+
path = link + path[end:]
120+
121+
v := volumeNameLen(link)
122+
if v > 0 {
123+
// Symlink to drive name is an absolute path.
124+
if v < len(link) && os.IsPathSeparator(link[v]) {
125+
v++
126+
}
127+
vol = link[:v]
128+
dest = vol
129+
end = len(vol)
130+
} else if len(link) > 0 && os.IsPathSeparator(link[0]) {
131+
// Symlink to absolute path.
132+
dest = link[:1]
133+
end = 1
134+
vol = link[:1]
135+
volLen = 1
136+
} else {
137+
// Symlink to relative path; replace last
138+
// path component in dest.
139+
var r int
140+
for r = len(dest) - 1; r >= volLen; r-- {
141+
if os.IsPathSeparator(dest[r]) {
142+
break
143+
}
144+
}
145+
if r < volLen {
146+
dest = vol
147+
} else {
148+
dest = dest[:r]
149+
}
150+
end = 0
151+
}
152+
}
153+
return filepath.Clean(dest), nil
154+
}

0 commit comments

Comments
 (0)