@@ -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>
1418import "C"
1519
1620func mustDetectArchitecture () {
@@ -59,3 +63,130 @@ 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 (StringToUTF16UnmanagedString (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 .LPVOID (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 , infoStruct , & lpBufferSize )
89+ err = getErrorOfWNetGetUniversalNameW (errorCode )
90+ if err == nil {
91+ lpUniversalName := unsafe .Pointer (* (* C .LPWSTR )(infoStruct ))
92+ universalPath = UTF16StringToString (lpUniversalName )
93+ }
94+ return universalPath , lpBufferSize , err
95+ }
96+
97+ func getErrorOfWNetGetUniversalNameW (returnCode C.DWORD ) error {
98+ if returnCode == C .NO_ERROR {
99+ return nil
100+ }
101+ if returnCode == C .ERROR_BAD_DEVICE {
102+ return & universalNameRetrievalError {errorType : errorBadDevice ,
103+ message : `the string pointed to by the lpLocalPath parameter is invalid` }
104+ }
105+ if returnCode == C .ERROR_CONNECTION_UNAVAIL {
106+ return & universalNameRetrievalError {errorType : errorConnectionUnavailable ,
107+ message : `there is no current connection to the remote device, but there is a remembered (persistent) connection to it` }
108+ }
109+ if returnCode == C .ERROR_EXTENDED_ERROR {
110+ errorMessage , providerName , err := getLastWNetError ()
111+ if err != nil {
112+ return & universalNameRetrievalError {errorType : errorExtendedError ,
113+ message : `a network-specific error occurred; getting extended error information failed: ` + err .Error ()}
114+ }
115+ return & universalNameRetrievalError {errorType : errorExtendedError ,
116+ message : `a network-specific error occurred; Network provider "` + providerName + `" reports: ` + errorMessage }
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 UTF16StringToString (unsafe .Pointer (lpErrorBuf )), UTF16StringToString (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+ // StringToUTF16UnmanagedString returns an unmanaged, null-terminated UTF16 string for given string.
166+ // The caller is responsible for freeing the returned pointer.
167+ func StringToUTF16UnmanagedString (s string ) unsafe.Pointer {
168+ utf16String := utf16 .Encode ([]rune (s ))
169+ utf16StringPointer := (* uint16 )(C .calloc (C .size_t (len (utf16String )+ 1 ), C .size_t (unsafe .Sizeof (uint16 (0 )))))
170+ currentCharPointer := utf16StringPointer
171+ for _ , c := range utf16String {
172+ * currentCharPointer = c
173+ currentCharPointer = (* uint16 )(unsafe .Pointer (uintptr (unsafe .Pointer (currentCharPointer )) + unsafe .Sizeof (uint16 (0 ))))
174+ }
175+ return unsafe .Pointer (utf16StringPointer )
176+ }
177+
178+ // UTF16StringToString returns a string for a given null-terminated UTF16 string.
179+ // This function does not call free on the parameter.
180+ func UTF16StringToString (lpwString unsafe.Pointer ) string {
181+ ptr := (* uint16 )(lpwString )
182+ data := make ([]uint16 , 0 , 0 )
183+ for {
184+ if * ptr == 0 {
185+ break
186+ }
187+ data = append (data , * ptr )
188+ ptr = (* uint16 )(unsafe .Pointer (((uintptr )(unsafe .Pointer (ptr ))) + unsafe .Sizeof (uint16 (0 ))))
189+ }
190+ s := utf16 .Decode (data )
191+ return string (s )
192+ }
0 commit comments