kittenipc/kitcom/internal/tsgo/vfs/osvfs/realpath_windows.go
2025-10-15 10:12:44 +03:00

101 lines
2.3 KiB
Go

package osvfs
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/windows"
)
// This implementation is based on what Node's fs.realpath.native does, via libuv: https://github.com/libuv/libuv/blob/ec5a4b54f7da7eeb01679005c615fee9633cdb3b/src/win/fs.c#L2937
func realpath(path string) (string, error) {
var h windows.Handle
if len(path) < 248 {
var err error
h, err = openMetadata(path)
if err != nil {
return "", err
}
defer windows.CloseHandle(h) //nolint:errcheck
} else {
// For long paths, defer to os.Open to run the path through fixLongPath.
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
// Works on directories too since https://go.dev/cl/405275.
h = windows.Handle(f.Fd())
}
// based on https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/os/file_windows.go#L389
const _VOLUME_NAME_DOS = 0
buf := make([]uint16, 310) // https://github.com/microsoft/go-winio/blob/3c9576c9346a1892dee136329e7e15309e82fb4f/internal/stringbuffer/wstring.go#L13
for {
n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), _VOLUME_NAME_DOS)
if err != nil {
return "", err
}
if n < uint32(len(buf)) {
break
}
buf = make([]uint16, n)
}
s := syscall.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 "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s)
}
func openMetadata(path string) (windows.Handle, error) {
// based on https://github.com/microsoft/go-winio/blob/3c9576c9346a1892dee136329e7e15309e82fb4f/pkg/fs/resolve.go#L113
pathUTF16, err := windows.UTF16PtrFromString(path)
if err != nil {
return windows.InvalidHandle, err
}
const (
_FILE_ANY_ACCESS = 0
_FILE_SHARE_READ = 0x01
_FILE_SHARE_WRITE = 0x02
_FILE_SHARE_DELETE = 0x04
_OPEN_EXISTING = 0x03
_FILE_FLAG_BACKUP_SEMANTICS = 0x0200_0000
)
h, err := windows.CreateFile(
pathUTF16,
_FILE_ANY_ACCESS,
_FILE_SHARE_READ|_FILE_SHARE_WRITE|_FILE_SHARE_DELETE,
nil,
_OPEN_EXISTING,
_FILE_FLAG_BACKUP_SEMANTICS,
0,
)
if err != nil {
return 0, &os.PathError{
Op: "CreateFile",
Path: path,
Err: err,
}
}
return h, nil
}