mirror of
https://github.com/restic/restic.git
synced 2025-01-21 06:48:35 +00:00
2a5bbf170d
The implementations are 90% copy&paste from the go standard library as the existing code does not offer any way to read the symlink target based on a filehandle. Fall back to a standard readlink on platforms other than Linux and Windows as those either don't even provide the necessary syscall or in case of macOS are not yet available in Go.
138 lines
4.6 KiB
Go
138 lines
4.6 KiB
Go
package fs
|
|
|
|
import (
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func Freadlink(fd uintptr, name string) (string, error) {
|
|
link, err := readReparseLink(windows.Handle(fd))
|
|
if err != nil {
|
|
return "", &os.PathError{Op: "readlink", Path: name, Err: err}
|
|
}
|
|
return link, nil
|
|
}
|
|
|
|
// based on src/os/file_windows.go from Go 1.23.2
|
|
// internally readReparseLink from the std library uses a filehandle, however,
|
|
// the external interface is based on a path. Thus, copy everything and minimally
|
|
// tweak it to allow passing in a file handle.
|
|
|
|
// normaliseLinkPath converts absolute paths returned by
|
|
// DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, ...)
|
|
// into paths acceptable by all Windows APIs.
|
|
// For example, it converts
|
|
//
|
|
// \??\C:\foo\bar into C:\foo\bar
|
|
// \??\UNC\foo\bar into \\foo\bar
|
|
// \??\Volume{abc}\ into \\?\Volume{abc}\
|
|
func normaliseLinkPath(path string) (string, error) {
|
|
if len(path) < 4 || path[:4] != `\??\` {
|
|
// unexpected path, return it as is
|
|
return path, nil
|
|
}
|
|
// we have path that start with \??\
|
|
s := path[4:]
|
|
switch {
|
|
case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar
|
|
return s, nil
|
|
case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar
|
|
return `\\` + s[4:], nil
|
|
}
|
|
|
|
// \??\Volume{abc}\
|
|
return `\\?\` + path[4:], nil
|
|
// modified to remove the legacy codepath for winreadlinkvolume == 0
|
|
}
|
|
|
|
func readReparseLink(h windows.Handle) (string, error) {
|
|
rdbbuf := make([]byte, windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
|
|
var bytesReturned uint32
|
|
err := windows.DeviceIoControl(h, windows.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
|
|
switch rdb.ReparseTag {
|
|
case syscall.IO_REPARSE_TAG_SYMLINK:
|
|
rb := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
|
|
s := rb.Path()
|
|
if rb.Flags&symlinkFlagRelative != 0 {
|
|
return s, nil
|
|
}
|
|
return normaliseLinkPath(s)
|
|
case windows.IO_REPARSE_TAG_MOUNT_POINT:
|
|
return normaliseLinkPath((*mountPointReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)).Path())
|
|
default:
|
|
// the path is not a symlink or junction but another type of reparse
|
|
// point
|
|
return "", syscall.ENOENT
|
|
}
|
|
}
|
|
|
|
// copied from src/internal/syscall/windows/reparse_windows.go from Go 1.23.0
|
|
// renamed to not export symbols
|
|
|
|
const symlinkFlagRelative = 1
|
|
|
|
type reparseDataBuffer struct {
|
|
ReparseTag uint32
|
|
ReparseDataLength uint16
|
|
Reserved uint16
|
|
DUMMYUNIONNAME byte
|
|
}
|
|
|
|
type symbolicLinkReparseBuffer struct {
|
|
// The integer that contains the offset, in bytes,
|
|
// of the substitute name string in the PathBuffer array,
|
|
// computed as an offset from byte 0 of PathBuffer. Note that
|
|
// this offset must be divided by 2 to get the array index.
|
|
SubstituteNameOffset uint16
|
|
// The integer that contains the length, in bytes, of the
|
|
// substitute name string. If this string is null-terminated,
|
|
// SubstituteNameLength does not include the Unicode null character.
|
|
SubstituteNameLength uint16
|
|
// PrintNameOffset is similar to SubstituteNameOffset.
|
|
PrintNameOffset uint16
|
|
// PrintNameLength is similar to SubstituteNameLength.
|
|
PrintNameLength uint16
|
|
// Flags specifies whether the substitute name is a full path name or
|
|
// a path name relative to the directory containing the symbolic link.
|
|
Flags uint32
|
|
PathBuffer [1]uint16
|
|
}
|
|
|
|
// Path returns path stored in rb.
|
|
func (rb *symbolicLinkReparseBuffer) Path() string {
|
|
n1 := rb.SubstituteNameOffset / 2
|
|
n2 := (rb.SubstituteNameOffset + rb.SubstituteNameLength) / 2
|
|
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[n1:n2:n2])
|
|
}
|
|
|
|
type mountPointReparseBuffer struct {
|
|
// The integer that contains the offset, in bytes,
|
|
// of the substitute name string in the PathBuffer array,
|
|
// computed as an offset from byte 0 of PathBuffer. Note that
|
|
// this offset must be divided by 2 to get the array index.
|
|
SubstituteNameOffset uint16
|
|
// The integer that contains the length, in bytes, of the
|
|
// substitute name string. If this string is null-terminated,
|
|
// SubstituteNameLength does not include the Unicode null character.
|
|
SubstituteNameLength uint16
|
|
// PrintNameOffset is similar to SubstituteNameOffset.
|
|
PrintNameOffset uint16
|
|
// PrintNameLength is similar to SubstituteNameLength.
|
|
PrintNameLength uint16
|
|
PathBuffer [1]uint16
|
|
}
|
|
|
|
// Path returns path stored in rb.
|
|
func (rb *mountPointReparseBuffer) Path() string {
|
|
n1 := rb.SubstituteNameOffset / 2
|
|
n2 := (rb.SubstituteNameOffset + rb.SubstituteNameLength) / 2
|
|
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[n1:n2:n2])
|
|
}
|