Skip to content

Commit c6c498c

Browse files
committed
On Windows, do not degenerate working directory through restarts causing command execution failure when APPDATA is a drive name-based path which maps to a UNC share
1 parent 57f9b20 commit c6c498c

File tree

6 files changed

+185
-14
lines changed

6 files changed

+185
-14
lines changed

cmd/launcher/gui/gui_windows.go

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

33
import (
4-
"unicode/utf16"
54
"unsafe"
65

76
"github.com/setlog/trivrost/pkg/system"
@@ -145,7 +144,7 @@ func applyWindowStyle(handle uintptr) {
145144
}
146145

147146
func loadIcons() {
148-
binaryPath := goStringToConstantUTF16WinApiString(system.GetBinaryPath())
147+
binaryPath := C.LPCWSTR(system.GoStringToConstantUTF16WinApiString(system.GetBinaryPath()))
149148
extractedIconCount := C.loadIcons(binaryPath)
150149
didLoadIcons = true
151150
C.free(unsafe.Pointer(binaryPath))
@@ -160,17 +159,6 @@ func loadIcons() {
160159
}
161160
}
162161

163-
func goStringToConstantUTF16WinApiString(s string) C.LPCWSTR {
164-
utf16String := utf16.Encode([]rune(s))
165-
utf16StringPointer := (*uint16)(C.calloc(C.size_t(len(utf16String)+1), C.size_t(unsafe.Sizeof(uint16(0)))))
166-
currentCharPointer := utf16StringPointer
167-
for _, c := range utf16String {
168-
*currentCharPointer = c
169-
currentCharPointer = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(currentCharPointer)) + unsafe.Sizeof(uint16(0))))
170-
}
171-
return (C.LPCWSTR)(unsafe.Pointer(utf16StringPointer))
172-
}
173-
174162
func setProgressState(s progressState) {
175163
C.setProgressBarState(C.ULONG_PTR(panelDownloadStatus.barTotalProgress.Handle()), C.int(s))
176164
}

cmd/launcher/launcher/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func IsInstanceInstalledInSystemMode() bool {
5050

5151
// IsInstanceInstalledForCurrentUser returns true iff the launcher's desired path under user files is occupied by the program running this code.
5252
func IsInstanceInstalledForCurrentUser() bool {
53-
return system.GetProgramPath() == getTargetProgramPath()
53+
return system.FilepathsEquivalent(system.GetProgramPath(), getTargetProgramPath())
5454
}
5555

5656
// IsInstallationOutdated returns true if the time the installed launcher binary was built

pkg/system/api_unix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@ func showLocalFileInFileManager(path string) error {
4747
func isProcessRunning(p *os.Process) bool {
4848
return p.Signal(unix.Signal(0)) == nil
4949
}
50+
51+
func universalPathName(p string) (string, error) {
52+
return p, nil
53+
}

pkg/system/api_windows.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import (
66
"os/exec"
77
"runtime"
88
"strings"
9+
"unicode/utf16"
10+
"unsafe"
911

1012
"golang.org/x/sys/windows"
1113
)
1214

15+
// #cgo LDFLAGS: -lMpr
1316
//#include <windows.h>
17+
//#include <winnetwk.h>
1418
import "C"
1519

1620
func mustDetectArchitecture() {
@@ -59,3 +63,126 @@ func isProcessRunning(p *os.Process) bool {
5963
result := C.GetExitCodeProcess(handle, &lpExitCode)
6064
return (result != 0) && (lpExitCode == C.STILL_ACTIVE)
6165
}
66+
67+
func universalPathName(p string) (string, error) {
68+
s, lpBufferSize, err := universalPathNameWithBufferSize(p, 1000)
69+
if err != nil && err.(*universalNameRetrievalError).ErrorType() == errorMoreData {
70+
s, _, err = universalPathNameWithBufferSize(s, lpBufferSize)
71+
}
72+
if err != nil {
73+
return p, err
74+
}
75+
return s, err
76+
}
77+
78+
func universalPathNameWithBufferSize(p string, lpBufferSizeUse C.DWORD) (universalPath string, lpBufferSize C.DWORD, err error) {
79+
cp := C.LPCWSTR(GoStringToConstantUTF16WinApiString(p))
80+
defer C.free(unsafe.Pointer(cp))
81+
82+
// The possible data written to infoStruct (we request a UNIVERSAL_NAME_INFO below) not only consists of the struct, but also of the data (strings)
83+
// pointed to by pointer-members within the struct. That's why this allocation needs to be much larger than just large enough to hold the struct itself.
84+
infoStruct := C.calloc(C.size_t(lpBufferSizeUse), 1)
85+
defer C.free(unsafe.Pointer(infoStruct))
86+
87+
lpBufferSize = lpBufferSizeUse
88+
errorCode := C.WNetGetUniversalNameW(cp, C.UNIVERSAL_NAME_INFO_LEVEL, C.LPVOID(infoStruct), C.LPDWORD(unsafe.Pointer(&lpBufferSize)))
89+
err = getErrorOfWNetGetUniversalNameW(errorCode)
90+
if err == nil {
91+
universalPath = UTF16WinApiStringToGoString(infoStruct)
92+
}
93+
return universalPath, lpBufferSize, err
94+
}
95+
96+
func getErrorOfWNetGetUniversalNameW(returnCode C.DWORD) error {
97+
if returnCode == C.NO_ERROR {
98+
return nil
99+
}
100+
if returnCode == C.ERROR_BAD_DEVICE {
101+
return &universalNameRetrievalError{errorType: errorBadDevice,
102+
message: `the string pointed to by the lpLocalPath parameter is invalid`}
103+
}
104+
if returnCode == C.ERROR_CONNECTION_UNAVAIL {
105+
return &universalNameRetrievalError{errorType: errorConnectionUnavailable,
106+
message: `there is no current connection to the remote device, but there is a remembered (persistent) connection to it`}
107+
}
108+
if returnCode == C.ERROR_EXTENDED_ERROR {
109+
errorMessage, providerName, err := getLastWNetError()
110+
if err != nil {
111+
return &universalNameRetrievalError{errorType: errorExtendedError,
112+
message: `a network-specific error occurred; getting extended error information failed: ` + err.Error()}
113+
} else {
114+
return &universalNameRetrievalError{errorType: errorExtendedError,
115+
message: `a network-specific error occurred; Network provider "` + providerName + `" reports: ` + errorMessage}
116+
}
117+
}
118+
if returnCode == C.ERROR_MORE_DATA {
119+
return &universalNameRetrievalError{errorType: errorMoreData,
120+
message: `despite trying to query with the requested buffer size, the buffer pointed to by the lpBuffer parameter was too small`}
121+
}
122+
if returnCode == C.ERROR_NOT_SUPPORTED {
123+
return &universalNameRetrievalError{errorType: errorNotSupported,
124+
message: `the dwInfoLevel parameter is set to UNIVERSAL_NAME_INFO_LEVEL, but the network provider does not support UNC names. (None of the network providers support this function)`}
125+
}
126+
if returnCode == C.ERROR_NO_NET_OR_BAD_PATH {
127+
return &universalNameRetrievalError{errorType: errorNoNetOrBadPath,
128+
message: `none of the network providers recognize the local name as having a connection. However, the network is not available for at least one provider to whom the connection may belong`}
129+
}
130+
if returnCode == C.ERROR_NO_NETWORK {
131+
return &universalNameRetrievalError{errorType: errorNoNetwork,
132+
message: `the network is unavailable`}
133+
}
134+
if returnCode == C.ERROR_NOT_CONNECTED {
135+
return &universalNameRetrievalError{errorType: errorNotConnected,
136+
message: `the device specified by the path is not redirected`}
137+
}
138+
return &universalNameRetrievalError{errorType: errorUndocumented,
139+
message: fmt.Sprintf(`undocumented error code %d`, returnCode)}
140+
}
141+
142+
func getLastWNetError() (errorMessage, providerName string, err error) {
143+
var lpError C.DWORD
144+
145+
const errorBufferSize = 5000
146+
const nErrorBufSize C.DWORD = errorBufferSize
147+
lpErrorBuf := (C.LPWSTR)(C.calloc(C.size_t(errorBufferSize+1), C.size_t(unsafe.Sizeof(uint16(0)))))
148+
defer C.free(unsafe.Pointer(lpErrorBuf))
149+
150+
const nameBufferSize = 1000
151+
const nNameBufSize C.DWORD = nameBufferSize
152+
lpNameBuf := (C.LPWSTR)(C.calloc(C.size_t(nameBufferSize+1), C.size_t(unsafe.Sizeof(uint16(0)))))
153+
defer C.free(unsafe.Pointer(lpNameBuf))
154+
155+
returnCode := C.WNetGetLastErrorW(&lpError, lpErrorBuf, nErrorBufSize, lpNameBuf, nNameBufSize)
156+
if returnCode == C.NO_ERROR {
157+
return UTF16WinApiStringToGoString(unsafe.Pointer(lpErrorBuf)), UTF16WinApiStringToGoString(unsafe.Pointer(lpNameBuf)), nil
158+
}
159+
if returnCode == C.ERROR_INVALID_ADDRESS {
160+
return "", "", fmt.Errorf("could not get last WNet error: ERROR_INVALID_ADDRESS")
161+
}
162+
return "", "", fmt.Errorf("could not get last WNet error: undocumented extended error code %d", returnCode)
163+
}
164+
165+
func GoStringToConstantUTF16WinApiString(s string) unsafe.Pointer {
166+
utf16String := utf16.Encode([]rune(s))
167+
utf16StringPointer := (*uint16)(C.calloc(C.size_t(len(utf16String)+1), C.size_t(unsafe.Sizeof(uint16(0)))))
168+
currentCharPointer := utf16StringPointer
169+
for _, c := range utf16String {
170+
*currentCharPointer = c
171+
currentCharPointer = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(currentCharPointer)) + unsafe.Sizeof(uint16(0))))
172+
}
173+
return unsafe.Pointer(utf16StringPointer)
174+
}
175+
176+
func UTF16WinApiStringToGoString(lpwString unsafe.Pointer) string {
177+
ptr := (*uint16)(lpwString)
178+
data := make([]uint16, 0, 0)
179+
for {
180+
if *ptr == 0 {
181+
break
182+
}
183+
data = append(data, *ptr)
184+
ptr = (*uint16)(unsafe.Pointer(((uintptr)(unsafe.Pointer(ptr))) + unsafe.Sizeof(uint16(0))))
185+
}
186+
s := utf16.Decode(data)
187+
return string(s)
188+
}

pkg/system/file_system_funcs.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,25 @@ func CleanUpFileOperation(file *os.File, returnError *error) {
303303
}
304304
}
305305
}
306+
307+
// FilepathsEquivalent returns true if the filepaths a and b are semantically equivalent (exceptions may exist).
308+
func FilepathsEquivalent(a, b string) bool {
309+
a = filepath.Clean(a)
310+
b = filepath.Clean(b)
311+
if a == b {
312+
return true
313+
}
314+
aResolved, aErr := universalPathName(a)
315+
if aErr != nil && aErr.(*universalNameRetrievalError).ErrorType() != errorNotConnected {
316+
log.Warnf(`could not determine UNC path for filepath "%s": %v\n`, a, aErr)
317+
}
318+
bResolved, bErr := universalPathName(b)
319+
if bErr != nil && bErr.(*universalNameRetrievalError).ErrorType() != errorNotConnected {
320+
log.Warnf(`could not determine UNC path for filepath "%s": %v\n`, b, bErr)
321+
}
322+
aResolved = filepath.Clean(aResolved)
323+
bResolved = filepath.Clean(bResolved)
324+
return (a == bResolved && bErr == nil) ||
325+
(b == aResolved && aErr == nil) ||
326+
(aResolved == bResolved && aErr == nil && bErr == nil)
327+
}

pkg/system/universal_path_error.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package system
2+
3+
type universalNameRetrievalErrorType int
4+
5+
const errorBadDevice universalNameRetrievalErrorType = 1
6+
const errorConnectionUnavailable universalNameRetrievalErrorType = 2
7+
const errorExtendedError universalNameRetrievalErrorType = 3
8+
const errorMoreData universalNameRetrievalErrorType = 4
9+
const errorNotSupported universalNameRetrievalErrorType = 5
10+
const errorNoNetOrBadPath universalNameRetrievalErrorType = 6
11+
const errorNoNetwork universalNameRetrievalErrorType = 7
12+
const errorNotConnected universalNameRetrievalErrorType = 8
13+
const errorUndocumented universalNameRetrievalErrorType = 9
14+
15+
type universalNameRetrievalError struct {
16+
message string
17+
errorType universalNameRetrievalErrorType
18+
}
19+
20+
func (err *universalNameRetrievalError) Error() string {
21+
if err == nil {
22+
return "<nil>"
23+
}
24+
return err.message
25+
}
26+
27+
// ErrorType returns the corresponsing WINAPI error type of the WNetGetUniversalNameW function call which generated the error.
28+
func (err *universalNameRetrievalError) ErrorType() universalNameRetrievalErrorType {
29+
return err.errorType
30+
}

0 commit comments

Comments
 (0)