Skip to content

Commit 510953b

Browse files
authored
Workaround to Windows filepath.EvalSymlinks() known issues (#46)
* use a workaround to Windows filepath.EvalSymlinks() known issues * Add a probe call to determine the needed buffer size on Windows
1 parent e20b78b commit 510953b

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/xanzy/go-gitlab v0.115.0
1212
golang.org/x/crypto v0.32.0
1313
golang.org/x/oauth2 v0.25.0
14+
golang.org/x/sys v0.29.0
1415
gopkg.in/yaml.v3 v3.0.1
1516
)
1617

@@ -24,6 +25,5 @@ require (
2425
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
2526
github.com/hashicorp/go-version v1.7.0 // indirect
2627
github.com/pmezard/go-difflib v1.0.0 // indirect
27-
golang.org/x/sys v0.29.0 // indirect
2828
golang.org/x/time v0.9.0 // indirect
2929
)

internal/path.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package internal
22

33
import (
44
"os"
5-
"path/filepath"
65
)
76

87
// GetExecutablePath returns the path of the executable file with all symlinks resolved.
@@ -12,7 +11,7 @@ func GetExecutablePath() (string, error) {
1211
return "", err
1312
}
1413

15-
exe, err = filepath.EvalSymlinks(exe)
14+
exe, err = ResolvePath(exe)
1615
if err != nil {
1716
return "", err
1817
}

internal/resolve_path_unix.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build !windows
2+
3+
package internal
4+
5+
import (
6+
"path/filepath"
7+
)
8+
9+
// ResolvePath returns the path of a given filename with all symlinks resolved.
10+
func ResolvePath(filename string) (string, error) {
11+
finalPath, err := filepath.EvalSymlinks(filename)
12+
if err != nil {
13+
return "", err
14+
}
15+
16+
return finalPath, nil
17+
}

internal/resolve_path_windows.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//go:build windows
2+
3+
package internal
4+
5+
import (
6+
"golang.org/x/sys/windows"
7+
"os"
8+
"strings"
9+
"syscall"
10+
)
11+
12+
// ResolvePath returns the path of a given filename with all symlinks resolved.
13+
func ResolvePath(filename string) (string, error) {
14+
f, err := os.Open(filename)
15+
if err != nil {
16+
return "", err
17+
}
18+
defer f.Close()
19+
20+
// Get the Windows handle
21+
handle := windows.Handle(f.Fd())
22+
23+
// Probe call to determine the needed buffer size
24+
bufSize, err := windows.GetFinalPathNameByHandle(handle, nil, 0, 0)
25+
if err != nil {
26+
return "", err
27+
}
28+
29+
buf := make([]uint16, bufSize)
30+
n, err := windows.GetFinalPathNameByHandle(handle, &buf[0], uint32(len(buf)), 0)
31+
if err != nil {
32+
return "", err
33+
}
34+
35+
// Convert the buffer to a string
36+
final := syscall.UTF16ToString(buf[:n])
37+
38+
// Strip possible "\\?\" prefix
39+
final = strings.TrimPrefix(final, `\\?\`)
40+
41+
return final, nil
42+
}

update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (up *Updater) UpdateCommand(ctx context.Context, cmdPath string, current st
5454
return nil, fmt.Errorf("failed to stat '%s'. file may not exist: %s", cmdPath, err)
5555
}
5656
if stat.Mode()&os.ModeSymlink != 0 {
57-
p, err := filepath.EvalSymlinks(cmdPath)
57+
p, err := internal.ResolvePath(cmdPath)
5858
if err != nil {
5959
return nil, fmt.Errorf("failed to resolve symlink '%s' for executable: %s", cmdPath, err)
6060
}

0 commit comments

Comments
 (0)