vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,14 @@
//go:build linux || darwin
package nativepath
import "syscall"
func ignoringEINTR[T any](fn func() (T, error)) (T, error) {
for {
v, err := fn()
if err != syscall.EINTR { //nolint:errorlint // syscall functions return raw syscall.Errno, never wrapped
return v, err
}
}
}

View File

@@ -0,0 +1,66 @@
package nativepath
import (
"path/filepath"
"sync"
"unsafe"
"golang.org/x/sys/unix"
)
// On macOS, we use open + fcntl(F_GETPATH) to resolve the canonical path in
// O(1) syscalls instead of Go's filepath.EvalSymlinks which does an lstat per
// path component — O(depth).
//
// How it works:
// - open(path, O_EVTONLY|O_NONBLOCK|O_CLOEXEC) follows all symlinks and gives
// us a lightweight fd. O_EVTONLY is macOS's event-only descriptor — it
// doesn't require read permission (similar to Linux's O_PATH) but still
// references the vnode. O_NONBLOCK prevents blocking on FIFOs.
// - fcntl(fd, F_GETPATH, buf) asks the kernel for the canonical path of the
// open file descriptor, written into a MAXPATHLEN buffer.
//
// unix.FcntlInt takes an int arg, so call it through a uintptr-escaping wrapper
// to keep the buffer pointer valid until fcntl returns.
var hasFGetPath = sync.OnceValue(func() bool {
// Verify that F_GETPATH is supported by this kernel version.
var buf [unix.PathMax]byte
fd, err := unix.Open(".", unix.O_EVTONLY|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
if err != nil {
return false
}
defer unix.Close(fd)
_, err = fcntlGetPath(fd, &buf)
return err == nil
})
func fcntlGetPath(fd int, buf *[unix.PathMax]byte) (int, error) {
return ignoringEINTR(func() (int, error) {
return fcntlGetPathPtr(uintptr(fd), uintptr(unsafe.Pointer(&buf[0])))
})
}
//go:uintptrescapes
func fcntlGetPathPtr(fd uintptr, buf uintptr) (int, error) {
return unix.FcntlInt(fd, unix.F_GETPATH, int(buf))
}
func Realpath(path string) (string, error) {
if !hasFGetPath() {
return filepath.EvalSymlinks(path)
}
fd, err := unix.Open(path, unix.O_EVTONLY|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
if err != nil {
return "", err
}
defer unix.Close(fd)
var buf [unix.PathMax]byte
if _, err := fcntlGetPath(fd, &buf); err != nil {
return "", err
}
return unix.ByteSliceToString(buf[:]), nil
}

View File

@@ -0,0 +1,70 @@
package nativepath
import (
"os"
"path/filepath"
"strconv"
"sync"
"golang.org/x/sys/unix"
)
// On Linux, we use the O_PATH + /proc/self/fd trick to resolve the canonical
// path in O(1) syscalls (open + readlink + close) instead of Go's
// filepath.EvalSymlinks which does an lstat per path component — O(depth).
//
// This is the approach libuv/Node.js could use, though libuv currently just
// calls C realpath(3) which itself does a readlink per component. On the Go
// side, the per-component approach is even more expensive because each
// os.Lstat call involves goroutine scheduling overhead (entersyscall /
// exitsyscall).
//
// How it works:
// - open(path, O_PATH|O_CLOEXEC) gives us a lightweight fd that follows all
// symlinks to the final target. O_PATH requires only search permission on
// directories (same as lstat), and works for both files and directories.
// - readlink("/proc/self/fd/<fd>") returns the fully resolved canonical path
// that the kernel computed during the open.
//
// Falls back to filepath.EvalSymlinks if /proc is not available (e.g. containers
// or chroots without procfs mounted).
const _procSelfFD = "/proc/self/fd/"
var hasProcSelfFD = sync.OnceValue(func() bool {
var stat unix.Stat_t
return unix.Stat(_procSelfFD, &stat) == nil
})
func Realpath(path string) (string, error) {
if !hasProcSelfFD() {
return filepath.EvalSymlinks(path)
}
fd, err := ignoringEINTR(func() (int, error) {
return unix.Open(path, unix.O_CLOEXEC|unix.O_PATH, 0)
})
if err != nil {
return "", &os.PathError{Op: "open", Path: path, Err: err}
}
defer unix.Close(fd)
var procBuf [len(_procSelfFD) + 20]byte // 20 digits is enough for any int64 fd
n := copy(procBuf[:], _procSelfFD)
n += copy(procBuf[n:], strconv.Itoa(fd))
procPath := string(procBuf[:n])
buf := make([]byte, 256)
for {
nn, err := ignoringEINTR(func() (int, error) {
return unix.Readlink(procPath, buf)
})
if err != nil {
return "", &os.PathError{Op: "readlink", Path: path, Err: err}
}
if nn < len(buf) {
return string(buf[:nn]), nil
}
buf = make([]byte, len(buf)*2)
}
}

View File

@@ -0,0 +1,9 @@
//go:build !windows && !linux && !darwin
package nativepath
import "path/filepath"
func Realpath(path string) (string, error) {
return filepath.EvalSymlinks(path)
}

View File

@@ -0,0 +1,100 @@
package nativepath
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
}

View File

@@ -0,0 +1,10 @@
//go:build !windows
package nativepath
import "os"
func IsSymlinkOrReparsePoint(path string) bool {
info, err := os.Lstat(path)
return err == nil && info.Mode()&os.ModeSymlink != 0
}

View File

@@ -0,0 +1,29 @@
package nativepath
import (
"syscall"
"unsafe"
)
func IsSymlinkOrReparsePoint(path string) bool {
if len(path) >= 248 {
path = `\\?\` + path
}
pathUTF16, err := syscall.UTF16PtrFromString(path)
if err != nil {
return false
}
var data syscall.Win32FileAttributeData
err = syscall.GetFileAttributesEx(
pathUTF16,
syscall.GetFileExInfoStandard,
(*byte)(unsafe.Pointer(&data)),
)
if err != nil {
return false
}
return data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
}

View File

@@ -0,0 +1,185 @@
package nativepath
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"gotest.tools/v3/assert"
)
func TestIsSymlinkOrReparsePoint(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
t.Run("regular file", func(t *testing.T) {
t.Parallel()
file := filepath.Join(tmp, "regular.txt")
assert.NilError(t, os.WriteFile(file, []byte("hello"), 0o666))
assert.Equal(t, IsSymlinkOrReparsePoint(file), false)
})
t.Run("regular directory", func(t *testing.T) {
t.Parallel()
dir := filepath.Join(tmp, "regular-dir")
assert.NilError(t, os.MkdirAll(dir, 0o777))
assert.Equal(t, IsSymlinkOrReparsePoint(dir), false)
})
t.Run("junction point", func(t *testing.T) {
t.Parallel()
target := filepath.Join(tmp, "junction-target")
link := filepath.Join(tmp, "junction-link")
assert.NilError(t, os.MkdirAll(target, 0o777))
mklink(t, target, link, true)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
})
t.Run("file symlink", func(t *testing.T) {
t.Parallel()
target := filepath.Join(tmp, "symlink-target.txt")
link := filepath.Join(tmp, "symlink-link.txt")
assert.NilError(t, os.WriteFile(target, []byte("hello"), 0o666))
mklink(t, target, link, false)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
})
t.Run("directory symlink", func(t *testing.T) {
t.Parallel()
target := filepath.Join(tmp, "dir-symlink-target")
link := filepath.Join(tmp, "dir-symlink-link")
assert.NilError(t, os.MkdirAll(target, 0o777))
mklink(t, target, link, false)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
})
t.Run("nonexistent path", func(t *testing.T) {
t.Parallel()
nonexistent := filepath.Join(tmp, "does-not-exist")
assert.Equal(t, IsSymlinkOrReparsePoint(nonexistent), false)
})
t.Run("empty path", func(t *testing.T) {
t.Parallel()
assert.Equal(t, IsSymlinkOrReparsePoint(""), false)
})
t.Run("invalid path with null byte", func(t *testing.T) {
t.Parallel()
assert.Equal(t, IsSymlinkOrReparsePoint("invalid\x00path"), false)
})
}
func TestIsSymlinkOrReparsePointLongPath(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
// Create a deeply nested path that exceeds 248 characters
longPathBase := tmp
pathComponent := "very_long_directory_name_to_exceed_max_path_limit_abcdefghijklmnopqrstuvwxyz"
for len(longPathBase) < 250 {
longPathBase = filepath.Join(longPathBase, pathComponent)
}
target := filepath.Join(longPathBase, "target")
link := filepath.Join(longPathBase, "link")
// Use \\?\ prefix to enable long path support for mklink
longTarget := `\\?\` + target
longLink := `\\?\` + link
assert.NilError(t, os.MkdirAll(longTarget, 0o777))
assert.NilError(t, exec.Command("cmd", "/c", "mklink", "/J", longLink, longTarget).Run())
// With long path support enabled, this should work even for paths >= 248 chars
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
}
func TestIsSymlinkOrReparsePointNestedInSymlink(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
// Create a structure: target/inner-target, link -> target, then check link/inner-link
target := filepath.Join(tmp, "target")
innerTarget := filepath.Join(target, "inner-target")
assert.NilError(t, os.MkdirAll(innerTarget, 0o777))
link := filepath.Join(tmp, "link")
mklink(t, target, link, true)
// Create a junction inside the target
innerLink := filepath.Join(target, "inner-link")
mklink(t, innerTarget, innerLink, true)
// Check the junction through the symlink path
nestedPath := filepath.Join(link, "inner-link")
assert.Equal(t, IsSymlinkOrReparsePoint(nestedPath), true)
}
func TestIsSymlinkOrReparsePointRelativePath(t *testing.T) { //nolint:paralleltest // Cannot use t.Parallel() with t.Chdir()
tmp := t.TempDir()
t.Chdir(tmp)
target := "target-rel"
link := "link-rel"
assert.NilError(t, os.MkdirAll(target, 0o777))
mklink(t, target, link, true)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
assert.Equal(t, IsSymlinkOrReparsePoint(target), false)
}
func BenchmarkIsSymlinkOrJunction(b *testing.B) {
tmp := b.TempDir()
regularFile := filepath.Join(tmp, "regular.txt")
assert.NilError(b, os.WriteFile(regularFile, []byte("hello"), 0o666))
target := filepath.Join(tmp, "target")
link := filepath.Join(tmp, "link")
assert.NilError(b, os.MkdirAll(target, 0o777))
assert.NilError(b, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
b.Run("regular file", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
IsSymlinkOrReparsePoint(regularFile)
}
})
b.Run("junction", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
IsSymlinkOrReparsePoint(link)
}
})
b.Run("nonexistent", func(b *testing.B) {
b.ReportAllocs()
nonexistent := filepath.Join(tmp, "does-not-exist")
for b.Loop() {
IsSymlinkOrReparsePoint(nonexistent)
}
})
}
func mklink(tb testing.TB, target, link string, isDir bool) {
tb.Helper()
if isDir {
assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
} else {
err := os.Symlink(target, link)
if err != nil && strings.Contains(err.Error(), "A required privilege is not held by the client") {
tb.Log(err)
tb.Skip("file symlink support is not enabled without elevation or developer mode")
}
assert.NilError(tb, err)
}
}