Skip to content

Instantly share code, notes, and snippets.

@hirochachacha
Last active December 28, 2015 01:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hirochachacha/bba426b5defff3d8f5fe to your computer and use it in GitHub Desktop.
Save hirochachacha/bba426b5defff3d8f5fe to your computer and use it in GitHub Desktop.
fchdir, fchmod implementation on windows
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