Skip to content

Commit 3d5163c

Browse files
committed
path/filepath: fix EvalSymlinks(".") on windows
Also new tests added. So, perhaps, this CL corrects even more broken EvalSymlinks behaviour. Fixes #12451 Change-Id: I81b9d92bab74bcb8eca6db6633546982fe5cec87 Reviewed-on: https://go-review.googlesource.com/16192 Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Alex Brainman <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 914db9f commit 3d5163c

File tree

3 files changed

+166
-50
lines changed

3 files changed

+166
-50
lines changed

src/path/filepath/path_test.go

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,18 @@ var EvalSymlinksTests = []EvalSymlinksTest{
752752
{"test/linkabs", "/"},
753753
}
754754

755+
// findEvalSymlinksTestDirsDest searches testDirs
756+
// for matching path and returns correspondent dest.
757+
func findEvalSymlinksTestDirsDest(t *testing.T, testDirs []EvalSymlinksTest, path string) string {
758+
for _, d := range testDirs {
759+
if d.path == path {
760+
return d.dest
761+
}
762+
}
763+
t.Fatalf("did not find %q in testDirs slice", path)
764+
return ""
765+
}
766+
755767
// simpleJoin builds a file name from the directory and path.
756768
// It does not use Join because we don't want ".." to be evaluated.
757769
func simpleJoin(dir, path string) string {
@@ -780,8 +792,22 @@ func TestEvalSymlinks(t *testing.T) {
780792
t.Fatal("eval symlink for tmp dir:", err)
781793
}
782794

795+
tests := EvalSymlinksTests
796+
testdirs := EvalSymlinksTestDirs
797+
if runtime.GOOS == "windows" {
798+
if len(tmpDir) < 3 {
799+
t.Fatalf("tmpDir path %q is too short", tmpDir)
800+
}
801+
if tmpDir[1] != ':' {
802+
t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
803+
}
804+
newtest := EvalSymlinksTest{"test/linkabswin", tmpDir[:3]}
805+
tests = append(tests, newtest)
806+
testdirs = append(testdirs, newtest)
807+
}
808+
783809
// Create the symlink farm using relative paths.
784-
for _, d := range EvalSymlinksTestDirs {
810+
for _, d := range testdirs {
785811
var err error
786812
path := simpleJoin(tmpDir, d.path)
787813
if d.dest == "" {
@@ -794,8 +820,13 @@ func TestEvalSymlinks(t *testing.T) {
794820
}
795821
}
796822

823+
wd, err := os.Getwd()
824+
if err != nil {
825+
t.Fatal(err)
826+
}
827+
797828
// Evaluate the symlink farm.
798-
for _, d := range EvalSymlinksTests {
829+
for _, d := range tests {
799830
path := simpleJoin(tmpDir, d.path)
800831
dest := simpleJoin(tmpDir, d.dest)
801832
if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) {
@@ -806,6 +837,56 @@ func TestEvalSymlinks(t *testing.T) {
806837
} else if filepath.Clean(p) != filepath.Clean(dest) {
807838
t.Errorf("Clean(%q)=%q, want %q", path, p, dest)
808839
}
840+
841+
// test EvalSymlinks(".")
842+
func() {
843+
defer func() {
844+
err := os.Chdir(wd)
845+
if err != nil {
846+
t.Fatal(err)
847+
}
848+
}()
849+
850+
err := os.Chdir(path)
851+
if err != nil {
852+
t.Error(err)
853+
return
854+
}
855+
p, err := filepath.EvalSymlinks(".")
856+
if err != nil {
857+
t.Errorf(`EvalSymlinks(".") in %q directory error: %v`, d.path, err)
858+
return
859+
}
860+
if p == "." {
861+
return
862+
}
863+
want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path))
864+
if p == want {
865+
return
866+
}
867+
t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want "." or %q`, d.path, p, want)
868+
}()
869+
870+
// test EvalSymlinks where parameter is relative path
871+
func() {
872+
defer func() {
873+
err := os.Chdir(wd)
874+
if err != nil {
875+
t.Fatal(err)
876+
}
877+
}()
878+
879+
err := os.Chdir(tmpDir)
880+
if err != nil {
881+
t.Error(err)
882+
return
883+
}
884+
if p, err := filepath.EvalSymlinks(d.path); err != nil {
885+
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
886+
} else if filepath.Clean(p) != filepath.Clean(d.dest) {
887+
t.Errorf("Clean(%q)=%q, want %q", d.path, p, d.dest)
888+
}
889+
}()
809890
}
810891
}
811892

src/path/filepath/symlink.go

Lines changed: 78 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,68 +5,99 @@
55
package filepath
66

77
import (
8-
"bytes"
98
"errors"
109
"os"
10+
"runtime"
1111
)
1212

13-
const utf8RuneSelf = 0x80
13+
// isRoot returns true if path is root of file system
14+
// (`/` on unix and `/`, `\`, `c:\` or `c:/` on windows).
15+
func isRoot(path string) bool {
16+
if runtime.GOOS != "windows" {
17+
return path == "/"
18+
}
19+
switch len(path) {
20+
case 1:
21+
return os.IsPathSeparator(path[0])
22+
case 3:
23+
return path[1] == ':' && os.IsPathSeparator(path[2])
24+
}
25+
return false
26+
}
1427

15-
func walkSymlinks(path string) (string, error) {
16-
const maxIter = 255
17-
originalPath := path
18-
// consume path by taking each frontmost path element,
19-
// expanding it if it's a symlink, and appending it to b
20-
var b bytes.Buffer
21-
for n := 0; path != ""; n++ {
22-
if n > maxIter {
23-
return "", errors.New("EvalSymlinks: too many links in " + originalPath)
24-
}
28+
// isDriveLetter returns true if path is Windows drive letter (like "c:").
29+
func isDriveLetter(path string) bool {
30+
if runtime.GOOS != "windows" {
31+
return false
32+
}
33+
return len(path) == 2 && path[1] == ':'
34+
}
2535

26-
// find next path component, p
27-
var i = -1
28-
for j, c := range path {
29-
if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) {
30-
i = j
31-
break
32-
}
33-
}
34-
var p string
35-
if i == -1 {
36-
p, path = path, ""
37-
} else {
38-
p, path = path[:i], path[i+1:]
39-
}
36+
func walkLink(path string, linksWalked *int) (newpath string, islink bool, err error) {
37+
if *linksWalked > 255 {
38+
return "", false, errors.New("EvalSymlinks: too many links")
39+
}
40+
fi, err := os.Lstat(path)
41+
if err != nil {
42+
return "", false, err
43+
}
44+
if fi.Mode()&os.ModeSymlink == 0 {
45+
return path, false, nil
46+
}
47+
newpath, err = os.Readlink(path)
48+
if err != nil {
49+
return "", false, err
50+
}
51+
*linksWalked++
52+
return newpath, true, nil
53+
}
4054

41-
if p == "" {
42-
if b.Len() == 0 {
43-
// must be absolute path
44-
b.WriteRune(Separator)
55+
func walkLinks(path string, linksWalked *int) (string, error) {
56+
switch dir, file := Split(path); {
57+
case dir == "":
58+
newpath, _, err := walkLink(file, linksWalked)
59+
return newpath, err
60+
case file == "":
61+
if isDriveLetter(dir) {
62+
// appending "." to avoid bug in Join (see issue 11551)
63+
return dir + ".", nil
64+
}
65+
if os.IsPathSeparator(dir[len(dir)-1]) {
66+
if isRoot(dir) {
67+
return dir, nil
4568
}
46-
continue
69+
return walkLinks(dir[:len(dir)-1], linksWalked)
4770
}
48-
49-
fi, err := os.Lstat(b.String() + p)
71+
newpath, _, err := walkLink(dir, linksWalked)
72+
return newpath, err
73+
default:
74+
newdir, err := walkLinks(dir, linksWalked)
5075
if err != nil {
5176
return "", err
5277
}
53-
if fi.Mode()&os.ModeSymlink == 0 {
54-
b.WriteString(p)
55-
if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') {
56-
b.WriteRune(Separator)
57-
}
58-
continue
59-
}
60-
61-
// it's a symlink, put it at the front of path
62-
dest, err := os.Readlink(b.String() + p)
78+
newpath, islink, err := walkLink(Join(newdir, file), linksWalked)
6379
if err != nil {
6480
return "", err
6581
}
66-
if IsAbs(dest) || os.IsPathSeparator(dest[0]) {
67-
b.Reset()
82+
if !islink {
83+
return newpath, nil
84+
}
85+
if IsAbs(newpath) || os.IsPathSeparator(newpath[0]) {
86+
return newpath, nil
6887
}
69-
path = dest + string(Separator) + path
88+
return Join(newdir, newpath), nil
89+
90+
}
91+
}
92+
93+
func walkSymlinks(path string) (string, error) {
94+
if path == "" {
95+
return path, nil
96+
}
97+
var linksWalked int // to protect against cycles
98+
newpath, err := walkLinks(path, &linksWalked)
99+
if err != nil {
100+
return "", err
70101
}
71-
return Clean(b.String()), nil
102+
return Clean(newpath), nil
72103
}

src/path/filepath/symlink_windows.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ func toLong(path string) (string, error) {
4747
}
4848

4949
func evalSymlinks(path string) (string, error) {
50-
path, err := walkSymlinks(path)
50+
newpath, err := walkSymlinks(path)
5151
if err != nil {
5252
return "", err
5353
}
54+
// discard the walk if path is "." and link destination is relative path (just like unix does)
55+
if path != "." || IsAbs(newpath) {
56+
path = newpath
57+
}
5458

5559
p, err := toShort(path)
5660
if err != nil {

0 commit comments

Comments
 (0)