git-lfs/tools/filetools_windows.go

55 lines
1021 B
Go
Raw Normal View History

//go:build windows
tools: add a function to properly canonicalize paths Git consistently uses canonicalized paths internally. This is for many reasons, but mostly to verify that a single path is within a repository. In order to interoperate properly with Git, we need to canonicalize paths and do it in the same way as Git. On Unix systems, to canonicalize a path, it is sufficient to make the path absolute and then resolve any symlinks. Go provides two functions to do these two steps, filepath.Abs and filepath.EvalSymlinks, and they work as advertised. Windows, however, has much more complex path handling and these functions do not handle all cases. The typical way to canonicalize paths on Windows is using GetFinalPathNameByHandle, and this is the technique Git uses. Go, however, does not provide a general API to canonicalize paths, unlike Rust's std::fs::canonicalize and similar functionality in myriad other languages. Therefore, in order to get this working on Windows, let's add a function to canonicalize paths in the appropriate system way, one for Unix systems and one for Windows. The code comes from Go's standard library, so update the copyright and license accordingly. Update the CanonicalizePath function to use the new function. We duplicate the Abs call because we an absolute path in CanonicalizePath in some cases even if the path is missing, whereas the new function needs to do it in all cases because we will use it other situations in the future. This should be a simple string check, so it should not involve an extra system call or other overhead.
2021-03-01 16:15:16 +00:00
// +build windows
package tools
import (
"golang.org/x/sys/windows"
)
func openSymlink(path string) (windows.Handle, error) {
p, err := windows.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
attrs := uint32(windows.FILE_FLAG_BACKUP_SEMANTICS)
h, err := windows.CreateFile(p, 0, 0, nil, windows.OPEN_EXISTING, attrs, 0)
if err != nil {
return 0, err
}
return h, nil
}
func CanonicalizeSystemPath(path string) (string, error) {
h, err := openSymlink(path)
if err != nil {
return "", err
}
defer windows.CloseHandle(h)
buf := make([]uint16, 100)
for {
n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), 0)
if err != nil {
return "", err
}
if n < uint32(len(buf)) {
break
}
buf = make([]uint16, n)
}
s := windows.UTF16ToString(buf)
if len(s) > 4 && s[:4] == `\\?\` {
s = s[4:]
if len(s) > 3 && s[:3] == `UNC` {
// return path like \\server\share\...
return `\` + s[3:], nil
}
return s, nil
}
return s, nil
}