Last active
December 28, 2015 01:52
-
-
Save hirochachacha/bba426b5defff3d8f5fe to your computer and use it in GitHub Desktop.
fchdir, fchmod implementation on windows
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" | |
"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 makeInheritSa() *syscall.SecurityAttributes { | |
var sa syscall.SecurityAttributes | |
sa.Length = uint32(unsafe.Sizeof(sa)) | |
sa.InheritHandle = 1 | |
return &sa | |
} | |
func OpenDir(path string, mode int, perm uint32) (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 | |
} | |
var access uint32 | |
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { | |
case syscall.O_RDONLY: | |
access = syscall.GENERIC_READ | |
case syscall.O_WRONLY: | |
access = syscall.GENERIC_WRITE | |
case syscall.O_RDWR: | |
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE | |
} | |
if mode&syscall.O_CREAT != 0 { | |
access |= syscall.GENERIC_WRITE | |
} | |
if mode&syscall.O_APPEND != 0 { | |
access &^= syscall.GENERIC_WRITE | |
access |= syscall.FILE_APPEND_DATA | |
} | |
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE) | |
var sa *syscall.SecurityAttributes | |
if mode&syscall.O_CLOEXEC == 0 { | |
sa = makeInheritSa() | |
} | |
var createmode uint32 | |
switch { | |
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): | |
createmode = syscall.CREATE_NEW | |
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC): | |
createmode = syscall.CREATE_ALWAYS | |
case mode&syscall.O_CREAT == syscall.O_CREAT: | |
createmode = syscall.OPEN_ALWAYS | |
case mode&syscall.O_TRUNC == syscall.O_TRUNC: | |
createmode = syscall.TRUNCATE_EXISTING | |
default: | |
createmode = syscall.OPEN_EXISTING | |
} | |
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) | |
return h, e | |
} | |
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 { | |
// GetFinalPathNameByHandle return path with long path prefix `\\?\` | |
pathLen := uint32(syscall.MAX_PATH+4) | |
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 Fchdir(fd syscall.Handle) error { | |
path, err := FinalPathByHandle(fd) | |
if err != nil { | |
return err | |
} | |
return os.Chdir(path) | |
} | |
func Fchmod(fd syscall.Handle, mode os.FileMode) error { | |
path, err := FinalPathByHandle(fd) | |
if err != nil { | |
return err | |
} | |
return os.Chmod(path, mode) | |
} | |
func main() { | |
var dfd syscall.Handle | |
fmt.Println("invalid handle: ", syscall.InvalidHandle) | |
f, err := os.OpenFile(".", os.O_RDONLY, 0) | |
if err != nil { | |
panic(err) | |
} | |
dfd = syscall.Handle(f.Fd()) | |
fmt.Println("search handle: ", dfd) | |
dfd, err = OpenDir(".", syscall.O_RDONLY, 0) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println("directory handle: ", dfd) | |
err = Fchdir(dfd) | |
if err != nil { | |
panic(err) | |
} | |
wd, err := os.Getwd() | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println(wd) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment