返回 DeepSeek-Reasonix
executable_path_windows.go
根目录 / internal / desktoplauncher / executable_path_windows.go
1 //go:build windows
2
3 package desktoplauncher
4
5 import (
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "golang.org/x/sys/windows"
12 )
13
14 const maxFinalPathUTF16 = 1 << 16
15
16 type launchLocation string
17
18 const (
19 launchLocationLocal launchLocation = "local"
20 launchLocationUNC launchLocation = "unc"
21 launchLocationRemoteDrive launchLocation = "remote_drive"
22 )
23
24 // resolveExecutablePath opens the launcher and asks Windows for the final DOS
25 // path represented by that handle. Unlike filepath.EvalSymlinks, this resolves
26 // directory junctions such as Scoop's stable current entry.
27 func resolveExecutablePath(path string) (string, error) {
28 file, err := os.Open(path)
29 if err != nil {
30 return "", fmt.Errorf("open executable: %w", err)
31 }
32 defer file.Close()
33
34 handle := windows.Handle(file.Fd())
35 size := uint32(256)
36 for {
37 buf := make([]uint16, size)
38 n, err := windows.GetFinalPathNameByHandle(handle, &buf[0], size, 0)
39 if err != nil {
40 return "", fmt.Errorf("get final executable path: %w", err)
41 }
42 if n < size {
43 return normalizeFinalWindowsPath(windows.UTF16ToString(buf[:n])), nil
44 }
45 if n >= maxFinalPathUTF16 {
46 return "", fmt.Errorf("get final executable path: required buffer is too large: %d", n)
47 }
48 size = n + 1
49 }
50 }
51
52 func normalizeFinalWindowsPath(path string) string {
53 const (
54 extendedPrefix = `\\?\`
55 extendedUNC = `\\?\UNC\`
56 )
57 if len(path) >= len(extendedUNC) && strings.EqualFold(path[:len(extendedUNC)], extendedUNC) {
58 return `\\` + path[len(extendedUNC):]
59 }
60 if len(path) >= 7 && strings.EqualFold(path[:len(extendedPrefix)], extendedPrefix) &&
61 isASCIILetter(path[4]) && path[5] == ':' && path[6] == '\\' {
62 return path[len(extendedPrefix):]
63 }
64 return path
65 }
66
67 func isASCIILetter(ch byte) bool {
68 return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z'
69 }
70
71 func classifyLaunchLocation(path string) (launchLocation, error) {
72 return classifyLaunchLocationWith(path, windows.GetDriveType)
73 }
74
75 func classifyLaunchLocationWith(path string, getDriveType func(*uint16) uint32) (launchLocation, error) {
76 volume := filepath.VolumeName(filepath.Clean(path))
77 if volume == "" {
78 return "", fmt.Errorf("determine executable volume: path has no volume")
79 }
80 if strings.HasPrefix(volume, `\\`) {
81 return launchLocationUNC, nil
82 }
83 root := volume
84 if !strings.HasSuffix(root, `\`) {
85 root += `\`
86 }
87 rootPtr, err := windows.UTF16PtrFromString(root)
88 if err != nil {
89 return "", fmt.Errorf("determine executable volume: %w", err)
90 }
91 switch driveType := getDriveType(rootPtr); driveType {
92 case windows.DRIVE_REMOTE:
93 return launchLocationRemoteDrive, nil
94 case windows.DRIVE_UNKNOWN, windows.DRIVE_NO_ROOT_DIR:
95 return "", fmt.Errorf("determine executable volume: Windows returned drive type %d", driveType)
96 default:
97 return launchLocationLocal, nil
98 }
99 }
100
100 lines GO