Last active
January 21, 2016 14:56
-
-
Save hirochachacha/0227f21b6d6aef47f8c2 to your computer and use it in GitHub Desktop.
filepath.EvalSymlinks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"os" | |
"path/filepath" | |
"syscall" | |
"unsafe" | |
) | |
const ( | |
FILE_NAME_NORMALIZED = 0x0 | |
FILE_NAME_OPEND = 0x8 | |
VOLUME_NAME_DOS = 0x0 | |
VOLUME_NAME_GUID = 0x1 | |
VOLUME_NAME_NONE = 0x4 | |
VOLUME_NAME_NT = 0x2 | |
) | |
const ( | |
ObjectBasicInformation = iota | |
ObjectNameInformation | |
ObjectTypeInformation | |
ObjectAllInformation | |
ObjectDataInformation | |
) | |
const ( | |
ERROR_NOT_ENOUGH_MEMORY syscall.Errno = 0x8 | |
) | |
const ( | |
STATUS_BUFFER_OVERFLOW syscall.Errno = 0x80000005 | |
) | |
var ( | |
modntdll = syscall.NewLazyDLL("ntdll.dll") | |
modkernel32 = syscall.NewLazyDLL("kernel32.dll") | |
procNtQueryObject = modntdll.NewProc("NtQueryObject") | |
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW") | |
procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW") | |
procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW") | |
) | |
func wIndex(ws []uint16, w uint16) int { | |
for i, c := range ws { | |
if c == w { | |
return i | |
} | |
} | |
return -1 | |
} | |
func wEqual(a, b []uint16) bool { | |
if len(a) != len(b) { | |
return false | |
} | |
for i, w := range a { | |
if b[i] != w { | |
return false | |
} | |
} | |
return true | |
} | |
func wHasPrefix(ws, prefix []uint16) bool { | |
return len(ws) >= len(prefix) && wEqual(ws[0:len(prefix)], prefix) | |
} | |
func OpenDir(path string) (fd syscall.Handle, err error) { | |
if len(path) == 0 { | |
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND | |
} | |
pathp, err := syscall.UTF16PtrFromString(path) | |
if err != nil { | |
return syscall.InvalidHandle, err | |
} | |
return syscall.CreateFile(pathp, syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) | |
} | |
func NtQueryObject(handle syscall.Handle, infoClass uint32, info *byte, infoLen uint32, retLen *uint32) (err error) { | |
r0, _, _ := syscall.Syscall6(procNtQueryObject.Addr(), 5, uintptr(handle), uintptr(infoClass), uintptr(unsafe.Pointer(info)), uintptr(infoLen), uintptr(unsafe.Pointer(retLen)), 0) | |
if r0 != 0 { | |
err = syscall.Errno(r0) | |
} | |
return | |
} | |
func GetFinalPathNameByHandle(handle syscall.Handle, path *uint16, pathLen uint32, flag uint32) (n uint32, err error) { | |
r0, _, e1 := syscall.Syscall6(procGetFinalPathNameByHandleW.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(path)), uintptr(pathLen), uintptr(flag), 0, 0) | |
n = uint32(r0) | |
if r0 == 0 { | |
if e1 != 0 { | |
err = error(e1) | |
} else { | |
err = syscall.EINVAL | |
} | |
} | |
return | |
} | |
func GetLogicalDriveStrings(bufLen uint32, buffer *uint16) (n uint32, err error) { | |
r0, _, e1 := syscall.Syscall(procGetLogicalDriveStringsW.Addr(), 2, uintptr(bufLen), uintptr(unsafe.Pointer(buffer)), 0) | |
n = uint32(r0) | |
if r0 == 0 { | |
if e1 != 0 { | |
err = error(e1) | |
} else { | |
err = syscall.EINVAL | |
} | |
} | |
return | |
} | |
func QueryDosDevice(drive *uint16, volume *uint16, volumeLen uint32) (n uint32, err error) { | |
r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(drive)), uintptr(unsafe.Pointer(volume)), uintptr(volumeLen)) | |
n = uint32(r0) | |
if r0 == 0 { | |
if e1 != 0 { | |
err = error(e1) | |
} else { | |
err = syscall.EINVAL | |
} | |
} | |
return | |
} | |
func FinalPathByHandle(fd syscall.Handle) (path string, err error) { | |
if fd == syscall.InvalidHandle { | |
return "", syscall.EINVAL | |
} | |
// GetFinalPathNameByHandle is not supported before Windows Vista | |
if procGetFinalPathNameByHandleW.Find() == nil { | |
pathLen := uint32(syscall.MAX_PATH) | |
for { | |
path := make([]uint16, pathLen) | |
n, err := GetFinalPathNameByHandle(fd, &path[0], pathLen, FILE_NAME_NORMALIZED|VOLUME_NAME_DOS) | |
if err == ERROR_NOT_ENOUGH_MEMORY { | |
pathLen *= 2 | |
continue | |
} | |
if err != nil { | |
break | |
// return "", err | |
} | |
if n <= pathLen { | |
return syscall.UTF16ToString(path[4:n]), nil | |
} | |
} | |
} | |
// fallback if failed | |
type unicode struct { | |
Length uint16 | |
MaximumLength uint16 | |
Buffer *uint16 | |
} | |
type objNameInfo struct { | |
Name unicode | |
NameBuffer uint32 | |
} | |
var n uint32 | |
bufLen := uint32(syscall.MAX_PATH*2 + 8) | |
for { | |
buf := make([]byte, bufLen) | |
err := NtQueryObject(fd, ObjectNameInformation, &buf[0], bufLen, &n) | |
if err == STATUS_BUFFER_OVERFLOW { | |
bufLen = n | |
continue | |
} | |
if err != nil { | |
return "", err | |
} | |
if n <= bufLen { | |
info := (*objNameInfo)(unsafe.Pointer(&buf[0])) | |
name := (*[0xffff]uint16)(unsafe.Pointer(info.Name.Buffer))[:info.Name.Length/2] | |
driveLen := uint32(100) | |
for { | |
drives := make([]uint16, driveLen) | |
n, err = GetLogicalDriveStrings(driveLen, &drives[0]) | |
if err != nil { | |
return "", err | |
} | |
if n < driveLen { | |
drives = drives[:n] | |
volumeLen := uint32(100) | |
volumeBuf := make([]uint16, volumeLen) | |
for { | |
i := wIndex(drives, 0) | |
if i == -1 { | |
return "", nil | |
} | |
drives[i-1] = 0 | |
drive := drives[:i-1] | |
var volume []uint16 | |
for { | |
n, err = QueryDosDevice(&drive[0], &volumeBuf[0], volumeLen) | |
if err == syscall.ERROR_INSUFFICIENT_BUFFER { | |
volumeLen *= 2 | |
volumeBuf = make([]uint16, volumeLen) | |
continue | |
} | |
if err != nil { | |
return "", err | |
} | |
volume = volumeBuf[:n-2] | |
break | |
} | |
if wHasPrefix(name, volume) { | |
name = name[len(volume)-len(drive):] | |
copy(name, drive) | |
return syscall.UTF16ToString(name), nil | |
} | |
drives = drives[i+1:] | |
} | |
} | |
driveLen *= 2 | |
} | |
} | |
} | |
} | |
func EvalSymlinks(path string) (string, error) { | |
fd, err := OpenDir(path) | |
if err != nil { | |
return "", err | |
} | |
defer syscall.CloseHandle(fd) | |
abs, err := FinalPathByHandle(fd) | |
if err != nil { | |
return "", err | |
} | |
if filepath.IsAbs(path) { | |
return abs, nil | |
} | |
wd, err := os.Getwd() | |
if err != nil { | |
return "", err | |
} | |
return filepath.Rel(wd, abs) | |
} | |
func main() { | |
path, err := EvalSymlinks(os.Args[1]) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println(path) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment