Ver Fonte

lib/fs: Fallback EvalSymlinks method on windows (fixes #5609) (#5611)

Mingxuan Lin há 6 anos atrás
pai
commit
eb4fe808c5
2 ficheiros alterados com 109 adições e 1 exclusões
  1. 70 1
      lib/fs/basicfs_windows.go
  2. 39 0
      lib/fs/basicfs_windows_test.go

+ 70 - 1
lib/fs/basicfs_windows.go

@@ -211,6 +211,63 @@ func isMaybeWin83(absPath string) bool {
 	return strings.Contains(strings.TrimPrefix(filepath.Base(absPath), WindowsTempPrefix), "~")
 }
 
+func getFinalPathName(in string) (string, error) {
+	// Return the normalized path
+	// Wrap the call to GetFinalPathNameByHandleW
+	// The string returned by this function uses the \?\ syntax
+	// Implies GetFullPathName + GetLongPathName
+	kernel32, err := syscall.LoadDLL("kernel32.dll")
+	if err != nil {
+		return "", err
+	}
+	GetFinalPathNameByHandleW, err := kernel32.FindProc("GetFinalPathNameByHandleW")
+	// https://github.com/golang/go/blob/ff048033e4304898245d843e79ed1a0897006c6d/src/internal/syscall/windows/syscall_windows.go#L303
+	if err != nil {
+		return "", err
+	}
+	inPath, err := syscall.UTF16PtrFromString(in)
+	if err != nil {
+		return "", err
+	}
+	// Get a file handler
+	h, err := syscall.CreateFile(inPath,
+		syscall.GENERIC_READ,
+		syscall.FILE_SHARE_READ,
+		nil,
+		syscall.OPEN_EXISTING,
+		uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS),
+		0)
+	if err != nil {
+		return "", err
+	}
+	defer syscall.CloseHandle(h)
+	// Call GetFinalPathNameByHandleW
+	var VOLUME_NAME_DOS uint32 = 0x0      // not yet defined in syscall
+	var bufSize uint32 = syscall.MAX_PATH // 260
+	for i := 0; i < 2; i++ {
+		buf := make([]uint16, bufSize)
+		var ret uintptr
+		ret, _, err = GetFinalPathNameByHandleW.Call(
+			uintptr(h),                       // HANDLE hFile
+			uintptr(unsafe.Pointer(&buf[0])), // LPWSTR lpszFilePath
+			uintptr(bufSize),                 // DWORD  cchFilePath
+			uintptr(VOLUME_NAME_DOS),         // DWORD  dwFlags
+		)
+		// The returned value is the actual length of the norm path
+		// After Win 10 build 1607, MAX_PATH limitations have been removed
+		// so it is necessary to check newBufSize
+		newBufSize := uint32(ret) + 1
+		if ret == 0 || newBufSize > bufSize*100 {
+			break
+		}
+		if newBufSize <= bufSize {
+			return syscall.UTF16ToString(buf), nil
+		}
+		bufSize = newBufSize
+	}
+	return "", err
+}
+
 func evalSymlinks(in string) (string, error) {
 	out, err := filepath.EvalSymlinks(in)
 	if err != nil && strings.HasPrefix(in, `\\?\`) {
@@ -218,7 +275,19 @@ func evalSymlinks(in string) (string, error) {
 		out, err = filepath.EvalSymlinks(in[4:])
 	}
 	if err != nil {
-		return "", err
+		// Try to get a normalized path from Win-API
+		var err1 error
+		out, err1 = getFinalPathName(in)
+		if err1 != nil {
+			return "", err // return the prior error
+		}
+		// Trim UNC prefix, equivalent to
+		// https://github.com/golang/go/blob/2396101e0590cb7d77556924249c26af0ccd9eff/src/os/file_windows.go#L470
+		if strings.HasPrefix(out, `\\?\UNC\`) {
+			out = `\` + out[7:] // path like \\server\share\...
+		} else {
+			out = strings.TrimPrefix(out, `\\?\`)
+		}
 	}
 	return longFilenameSupport(out), nil
 }

+ 39 - 0
lib/fs/basicfs_windows_test.go

@@ -139,3 +139,42 @@ func TestRelUnrootedCheckedWindows(t *testing.T) {
 		}
 	}
 }
+
+func TestGetFinalPath(t *testing.T) {
+	testCases := []struct {
+		input         string
+		expectedPath  string
+		eqToEvalSyml  bool
+		ignoreMissing bool
+	}{
+		{`c:\`, `C:\`, true, false},
+		{`\\?\c:\`, `C:\`, false, false},
+		{`c:\wInDows\sYstEm32`, `C:\Windows\System32`, true, false},
+		{`c:\parent\child`, `C:\parent\child`, false, true},
+	}
+
+	for _, testCase := range testCases {
+		out, err := getFinalPathName(testCase.input)
+		if err != nil {
+			if testCase.ignoreMissing && os.IsNotExist(err) {
+				continue
+			}
+			t.Errorf("getFinalPathName failed at %q with error %s", testCase.input, err)
+		}
+		// Trim UNC prefix
+		if strings.HasPrefix(out, `\\?\UNC\`) {
+			out = `\` + out[7:]
+		} else {
+			out = strings.TrimPrefix(out, `\\?\`)
+		}
+		if out != testCase.expectedPath {
+			t.Errorf("getFinalPathName got wrong path: %q (expected %q)", out, testCase.expectedPath)
+		}
+		if testCase.eqToEvalSyml {
+			evlPath, err1 := filepath.EvalSymlinks(testCase.input)
+			if err1 != nil || out != evlPath {
+				t.Errorf("EvalSymlinks got different results %q %s", evlPath, err1)
+			}
+		}
+	}
+}