Skip to content

Commit d1b4a91

Browse files
authored
Use os.Open for Windows realpath to handle long paths (#862)
1 parent 866466e commit d1b4a91

File tree

2 files changed

+69
-25
lines changed

2 files changed

+69
-25
lines changed

internal/vfs/osvfs/realpath_test.go

+51-21
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,11 @@ import (
1616
func TestSymlinkRealpath(t *testing.T) {
1717
t.Parallel()
1818

19-
tmp := t.TempDir()
20-
21-
target := filepath.Join(tmp, "target")
22-
targetFile := filepath.Join(target, "file")
23-
24-
link := filepath.Join(tmp, "link")
25-
linkFile := filepath.Join(link, "file")
26-
27-
const expectedContents = "hello"
28-
29-
assert.NilError(t, os.MkdirAll(target, 0o777))
30-
assert.NilError(t, os.WriteFile(targetFile, []byte(expectedContents), 0o666))
31-
32-
mklink(t, target, link, true)
19+
targetFile, linkFile := setupSymlinks(t)
3320

3421
gotContents, err := os.ReadFile(linkFile)
3522
assert.NilError(t, err)
36-
assert.Equal(t, string(gotContents), expectedContents)
23+
assert.Equal(t, string(gotContents), "hello")
3724

3825
fs := FS()
3926

@@ -48,22 +35,65 @@ func TestSymlinkRealpath(t *testing.T) {
4835
}
4936
}
5037

51-
func mklink(t *testing.T, target, link string, isDir bool) {
52-
t.Helper()
38+
func setupSymlinks(tb testing.TB) (targetFile, linkFile string) {
39+
tb.Helper()
40+
41+
tmp := tb.TempDir()
42+
43+
target := filepath.Join(tmp, "target")
44+
targetFile = filepath.Join(target, "file")
45+
46+
link := filepath.Join(tmp, "link")
47+
linkFile = filepath.Join(link, "file")
48+
49+
assert.NilError(tb, os.MkdirAll(target, 0o777))
50+
assert.NilError(tb, os.WriteFile(targetFile, []byte("hello"), 0o666))
51+
52+
mklink(tb, target, link, true)
53+
54+
return targetFile, linkFile
55+
}
56+
57+
func mklink(tb testing.TB, target, link string, isDir bool) {
58+
tb.Helper()
5359

5460
if runtime.GOOS == "windows" && isDir {
5561
// Don't use os.Symlink on Windows, as it creates a "real" symlink, not a junction.
56-
assert.NilError(t, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
62+
assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
5763
} else {
5864
err := os.Symlink(target, link)
5965
if err != nil && !isDir && runtime.GOOS == "windows" && strings.Contains(err.Error(), "A required privilege is not held by the client") {
60-
t.Log(err)
61-
t.Skip("file symlink support is not enabled without elevation or developer mode")
66+
tb.Log(err)
67+
tb.Skip("file symlink support is not enabled without elevation or developer mode")
6268
}
63-
assert.NilError(t, err)
69+
assert.NilError(tb, err)
6470
}
6571
}
6672

73+
func BenchmarkRealpath(b *testing.B) {
74+
targetFile, linkFile := setupSymlinks(b)
75+
76+
fs := FS()
77+
normalizedTargetFile := tspath.NormalizePath(targetFile)
78+
normalizedLinkFile := tspath.NormalizePath(linkFile)
79+
80+
b.Run("target", func(b *testing.B) {
81+
b.ReportAllocs()
82+
83+
for b.Loop() {
84+
fs.Realpath(normalizedTargetFile)
85+
}
86+
})
87+
88+
b.Run("link", func(b *testing.B) {
89+
b.ReportAllocs()
90+
91+
for b.Loop() {
92+
fs.Realpath(normalizedLinkFile)
93+
}
94+
})
95+
}
96+
6797
func TestGetAccessibleEntries(t *testing.T) {
6898
t.Parallel()
6999

internal/vfs/osvfs/realpath_windows.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,25 @@ import (
1111
// This implementation is based on what Node's fs.realpath.native does, via libuv: https://github.com/libuv/libuv/blob/ec5a4b54f7da7eeb01679005c615fee9633cdb3b/src/win/fs.c#L2937
1212

1313
func realpath(path string) (string, error) {
14-
h, err := openMetadata(path)
15-
if err != nil {
16-
return "", err
14+
var h windows.Handle
15+
if len(path) < 248 {
16+
var err error
17+
h, err = openMetadata(path)
18+
if err != nil {
19+
return "", err
20+
}
21+
defer windows.CloseHandle(h) //nolint:errcheck
22+
} else {
23+
// For long paths, defer to os.Open to run the path through fixLongPath.
24+
f, err := os.Open(path)
25+
if err != nil {
26+
return "", err
27+
}
28+
defer f.Close()
29+
30+
// Works on directories too since https://go.dev/cl/405275.
31+
h = windows.Handle(f.Fd())
1732
}
18-
defer windows.CloseHandle(h) //nolint:errcheck
1933

2034
// based on https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/os/file_windows.go#L389
2135

0 commit comments

Comments
 (0)