aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Mills <bcmills@google.com>2023-08-16 17:23:10 +0000
committerCarlos Amedee <carlos@golang.org>2023-08-23 19:57:00 +0000
commit7c97cc7d979a918b8d60081794a846b50f6a7bbe (patch)
treeb4b86c0ae3db4cb0d118ee59890f80e1c0e056c5
parentcb6ea9499691af3277de3834dee238ee25c370d4 (diff)
downloadgo-7c97cc7d979a918b8d60081794a846b50f6a7bbe.tar.gz
go-7c97cc7d979a918b8d60081794a846b50f6a7bbe.zip
[release-branch.go1.21] Revert "os: use handle based APIs to read directories on windows"
This reverts CL 452995. Reason for revert: caused os.File.ReadDir to fail on filesystems that do not support FILE_ID_BOTH_DIR_INFO. This is an alternative to a fix-forward change in CL 518196. Since the original change was mostly a performance improvement, reverting to the previous implementation seems less risky than backporting a larger fix. Fixes #61910 Fixes #61964 Change-Id: I60f1602b9eb6ea353e7eb23429f19f1ffa16b394 Reviewed-on: https://go-review.googlesource.com/c/go/+/520156 Run-TryBot: Bryan Mills <bcmills@google.com> Reviewed-by: Austin Clements <austin@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
-rw-r--r--src/internal/syscall/windows/syscall_windows.go20
-rw-r--r--src/internal/syscall/windows/zsyscall_windows.go81
-rw-r--r--src/os/dir_windows.go132
-rw-r--r--src/os/file_windows.go69
-rw-r--r--src/os/os_windows_test.go12
-rw-r--r--src/os/types_windows.go41
6 files changed, 158 insertions, 197 deletions
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
index 53d32a14a0..892b878d16 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -375,25 +375,5 @@ func ErrorLoadingGetTempPath2() error {
//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
-type FILE_ID_BOTH_DIR_INFO struct {
- NextEntryOffset uint32
- FileIndex uint32
- CreationTime syscall.Filetime
- LastAccessTime syscall.Filetime
- LastWriteTime syscall.Filetime
- ChangeTime syscall.Filetime
- EndOfFile uint64
- AllocationSize uint64
- FileAttributes uint32
- FileNameLength uint32
- EaSize uint32
- ShortNameLength uint32
- ShortName [12]uint16
- FileID uint64
- FileName [1]uint16
-}
-
-//sys GetVolumeInformationByHandle(file syscall.Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationByHandleW
-
//sys RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry
//sys RtlVirtualUnwind(handlerType uint32, baseAddress uintptr, pc uintptr, entry uintptr, ctxt uintptr, data *uintptr, frame *uintptr, ctxptrs *byte) (ret uintptr) = kernel32.RtlVirtualUnwind
diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go
index 32744b00fc..a5c246b773 100644
--- a/src/internal/syscall/windows/zsyscall_windows.go
+++ b/src/internal/syscall/windows/zsyscall_windows.go
@@ -45,43 +45,42 @@ var (
moduserenv = syscall.NewLazyDLL(sysdll.Add("userenv.dll"))
modws2_32 = syscall.NewLazyDLL(sysdll.Add("ws2_32.dll"))
- procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
- procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
- procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
- procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
- procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
- procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
- procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
- procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
- procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
- procCreateEventW = modkernel32.NewProc("CreateEventW")
- procGetACP = modkernel32.NewProc("GetACP")
- procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
- procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
- procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
- procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
- procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
- procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
- procGetTempPath2W = modkernel32.NewProc("GetTempPath2W")
- procGetVolumeInformationByHandleW = modkernel32.NewProc("GetVolumeInformationByHandleW")
- procLockFileEx = modkernel32.NewProc("LockFileEx")
- procModule32FirstW = modkernel32.NewProc("Module32FirstW")
- procModule32NextW = modkernel32.NewProc("Module32NextW")
- procMoveFileExW = modkernel32.NewProc("MoveFileExW")
- procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
- procRtlLookupFunctionEntry = modkernel32.NewProc("RtlLookupFunctionEntry")
- procRtlVirtualUnwind = modkernel32.NewProc("RtlVirtualUnwind")
- procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
- procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
- procVirtualQuery = modkernel32.NewProc("VirtualQuery")
- procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
- procNetShareDel = modnetapi32.NewProc("NetShareDel")
- procNetUserGetLocalGroups = modnetapi32.NewProc("NetUserGetLocalGroups")
- procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
- procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
- procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
- procGetProfilesDirectoryW = moduserenv.NewProc("GetProfilesDirectoryW")
- procWSASocketW = modws2_32.NewProc("WSASocketW")
+ procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
+ procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
+ procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
+ procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
+ procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
+ procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
+ procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
+ procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
+ procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
+ procCreateEventW = modkernel32.NewProc("CreateEventW")
+ procGetACP = modkernel32.NewProc("GetACP")
+ procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
+ procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
+ procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
+ procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
+ procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
+ procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
+ procGetTempPath2W = modkernel32.NewProc("GetTempPath2W")
+ procLockFileEx = modkernel32.NewProc("LockFileEx")
+ procModule32FirstW = modkernel32.NewProc("Module32FirstW")
+ procModule32NextW = modkernel32.NewProc("Module32NextW")
+ procMoveFileExW = modkernel32.NewProc("MoveFileExW")
+ procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
+ procRtlLookupFunctionEntry = modkernel32.NewProc("RtlLookupFunctionEntry")
+ procRtlVirtualUnwind = modkernel32.NewProc("RtlVirtualUnwind")
+ procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
+ procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
+ procVirtualQuery = modkernel32.NewProc("VirtualQuery")
+ procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
+ procNetShareDel = modnetapi32.NewProc("NetShareDel")
+ procNetUserGetLocalGroups = modnetapi32.NewProc("NetUserGetLocalGroups")
+ procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
+ procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
+ procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
+ procGetProfilesDirectoryW = moduserenv.NewProc("GetProfilesDirectoryW")
+ procWSASocketW = modws2_32.NewProc("WSASocketW")
)
func adjustTokenPrivileges(token syscall.Token, disableAllPrivileges bool, newstate *TOKEN_PRIVILEGES, buflen uint32, prevstate *TOKEN_PRIVILEGES, returnlen *uint32) (ret uint32, err error) {
@@ -242,14 +241,6 @@ func GetTempPath2(buflen uint32, buf *uint16) (n uint32, err error) {
return
}
-func GetVolumeInformationByHandle(file syscall.Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) {
- r1, _, e1 := syscall.Syscall9(procGetVolumeInformationByHandleW.Addr(), 8, uintptr(file), uintptr(unsafe.Pointer(volumeNameBuffer)), uintptr(volumeNameSize), uintptr(unsafe.Pointer(volumeNameSerialNumber)), uintptr(unsafe.Pointer(maximumComponentLength)), uintptr(unsafe.Pointer(fileSystemFlags)), uintptr(unsafe.Pointer(fileSystemNameBuffer)), uintptr(fileSystemNameSize), 0)
- if r1 == 0 {
- err = errnoErr(e1)
- }
- return
-}
-
func LockFileEx(file syscall.Handle, flags uint32, reserved uint32, bytesLow uint32, bytesHigh uint32, overlapped *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(file), uintptr(flags), uintptr(reserved), uintptr(bytesLow), uintptr(bytesHigh), uintptr(unsafe.Pointer(overlapped)))
if r1 == 0 {
diff --git a/src/os/dir_windows.go b/src/os/dir_windows.go
index 1724af58d5..9dc2cd7689 100644
--- a/src/os/dir_windows.go
+++ b/src/os/dir_windows.go
@@ -5,138 +5,60 @@
package os
import (
- "internal/syscall/windows"
"io"
"io/fs"
"runtime"
- "sync"
"syscall"
- "unsafe"
)
-// Auxiliary information if the File describes a directory
-type dirInfo struct {
- // buf is a slice pointer so the slice header
- // does not escape to the heap when returning
- // buf to dirBufPool.
- buf *[]byte // buffer for directory I/O
- bufp int // location of next record in buf
- vol uint32
-}
-
-const (
- // dirBufSize is the size of the dirInfo buffer.
- // The buffer must be big enough to hold at least a single entry.
- // The filename alone can be 512 bytes (MAX_PATH*2), and the fixed part of
- // the FILE_ID_BOTH_DIR_INFO structure is 105 bytes, so dirBufSize
- // should not be set below 1024 bytes (512+105+safety buffer).
- // Windows 8.1 and earlier only works with buffer sizes up to 64 kB.
- dirBufSize = 64 * 1024 // 64kB
-)
-
-var dirBufPool = sync.Pool{
- New: func() any {
- // The buffer must be at least a block long.
- buf := make([]byte, dirBufSize)
- return &buf
- },
-}
-
-func (d *dirInfo) close() {
- if d.buf != nil {
- dirBufPool.Put(d.buf)
- d.buf = nil
- }
-}
-
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
// If this file has no dirinfo, create one.
- var infoClass uint32 = windows.FileIdBothDirectoryInfo
+ needdata := true
if file.dirinfo == nil {
- // vol is used by os.SameFile.
- // It is safe to query it once and reuse the value.
- // Hard links are not allowed to reference files in other volumes.
- // Junctions and symbolic links can reference files and directories in other volumes,
- // but the reparse point should still live in the parent volume.
- var vol uint32
- err = windows.GetVolumeInformationByHandle(file.pfd.Sysfd, nil, 0, &vol, nil, nil, nil, 0)
- runtime.KeepAlive(file)
+ needdata = false
+ file.dirinfo, err = openDir(file.name)
if err != nil {
err = &PathError{Op: "readdir", Path: file.name, Err: err}
return
}
- infoClass = windows.FileIdBothDirectoryRestartInfo
- file.dirinfo = new(dirInfo)
- file.dirinfo.buf = dirBufPool.Get().(*[]byte)
- file.dirinfo.vol = vol
}
- d := file.dirinfo
wantAll := n <= 0
if wantAll {
n = -1
}
- for n != 0 {
- // Refill the buffer if necessary
- if d.bufp == 0 {
- err = windows.GetFileInformationByHandleEx(file.pfd.Sysfd, infoClass, (*byte)(unsafe.Pointer(&(*d.buf)[0])), uint32(len(*d.buf)))
+ d := &file.dirinfo.data
+ for n != 0 && !file.dirinfo.isempty {
+ if needdata {
+ e := syscall.FindNextFile(file.dirinfo.h, d)
runtime.KeepAlive(file)
- if err != nil {
- if err == syscall.ERROR_NO_MORE_FILES {
+ if e != nil {
+ if e == syscall.ERROR_NO_MORE_FILES {
break
- }
- if infoClass == windows.FileIdBothDirectoryRestartInfo && err == syscall.ERROR_FILE_NOT_FOUND {
- // GetFileInformationByHandleEx doesn't document the return error codes when the info class is FileIdBothDirectoryRestartInfo,
- // but MS-FSA 2.1.5.6.3 [1] specifies that the underlying file system driver should return STATUS_NO_SUCH_FILE when
- // reading an empty root directory, which is mapped to ERROR_FILE_NOT_FOUND by Windows.
- // Note that some file system drivers may never return this error code, as the spec allows to return the "." and ".."
- // entries in such cases, making the directory appear non-empty.
- // The chances of false positive are very low, as we know that the directory exists, else GetVolumeInformationByHandle
- // would have failed, and that the handle is still valid, as we haven't closed it.
- // See go.dev/issue/61159.
- // [1] https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fsa/fa8194e0-53ec-413b-8315-e8fa85396fd8
- break
- }
- if s, _ := file.Stat(); s != nil && !s.IsDir() {
- err = &PathError{Op: "readdir", Path: file.name, Err: syscall.ENOTDIR}
} else {
- err = &PathError{Op: "GetFileInformationByHandleEx", Path: file.name, Err: err}
+ err = &PathError{Op: "FindNextFile", Path: file.name, Err: e}
+ return
}
- return
}
- infoClass = windows.FileIdBothDirectoryInfo
}
- // Drain the buffer
- var islast bool
- for n != 0 && !islast {
- info := (*windows.FILE_ID_BOTH_DIR_INFO)(unsafe.Pointer(&(*d.buf)[d.bufp]))
- d.bufp += int(info.NextEntryOffset)
- islast = info.NextEntryOffset == 0
- if islast {
- d.bufp = 0
- }
- nameslice := unsafe.Slice(&info.FileName[0], info.FileNameLength/2)
- name := syscall.UTF16ToString(nameslice)
- if name == "." || name == ".." { // Useless names
- continue
- }
- if mode == readdirName {
- names = append(names, name)
+ needdata = true
+ name := syscall.UTF16ToString(d.FileName[0:])
+ if name == "." || name == ".." { // Useless names
+ continue
+ }
+ if mode == readdirName {
+ names = append(names, name)
+ } else {
+ f := newFileStatFromWin32finddata(d)
+ f.name = name
+ f.path = file.dirinfo.path
+ f.appendNameToPath = true
+ if mode == readdirDirEntry {
+ dirents = append(dirents, dirEntry{f})
} else {
- f := newFileStatFromFileIDBothDirInfo(info)
- f.name = name
- f.vol = d.vol
- // f.path is used by os.SameFile to decide if it needs
- // to fetch vol, idxhi and idxlo. But these are already set,
- // so set f.path to "" to prevent os.SameFile doing it again.
- f.path = ""
- if mode == readdirDirEntry {
- dirents = append(dirents, dirEntry{f})
- } else {
- infos = append(infos, f)
- }
+ infos = append(infos, f)
}
- n--
}
+ n--
}
if !wantAll && len(names)+len(dirents)+len(infos) == 0 {
return nil, nil, nil, io.EOF
diff --git a/src/os/file_windows.go b/src/os/file_windows.go
index c77d182fae..8d77a63d37 100644
--- a/src/os/file_windows.go
+++ b/src/os/file_windows.go
@@ -87,6 +87,18 @@ func NewFile(fd uintptr, name string) *File {
return newFile(h, name, "file")
}
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ h syscall.Handle // search handle created with FindFirstFile
+ data syscall.Win32finddata
+ path string
+ isempty bool // set if FindFirstFile returns ERROR_FILE_NOT_FOUND
+}
+
+func (d *dirInfo) close() error {
+ return syscall.FindClose(d.h)
+}
+
func epipecheck(file *File, e error) {
}
@@ -94,6 +106,63 @@ func epipecheck(file *File, e error) {
// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
const DevNull = "NUL"
+func openDir(name string) (d *dirInfo, e error) {
+ var mask string
+
+ path := fixLongPath(name)
+
+ if len(path) == 2 && path[1] == ':' { // it is a drive letter, like C:
+ mask = path + `*`
+ } else if len(path) > 0 {
+ lc := path[len(path)-1]
+ if lc == '/' || lc == '\\' {
+ mask = path + `*`
+ } else {
+ mask = path + `\*`
+ }
+ } else {
+ mask = `\*`
+ }
+ maskp, e := syscall.UTF16PtrFromString(mask)
+ if e != nil {
+ return nil, e
+ }
+ d = new(dirInfo)
+ d.h, e = syscall.FindFirstFile(maskp, &d.data)
+ if e != nil {
+ // FindFirstFile returns ERROR_FILE_NOT_FOUND when
+ // no matching files can be found. Then, if directory
+ // exists, we should proceed.
+ // If FindFirstFile failed because name does not point
+ // to a directory, we should return ENOTDIR.
+ var fa syscall.Win32FileAttributeData
+ pathp, e1 := syscall.UTF16PtrFromString(path)
+ if e1 != nil {
+ return nil, e
+ }
+ e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+ if e1 != nil {
+ return nil, e
+ }
+ if fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
+ return nil, syscall.ENOTDIR
+ }
+ if e != syscall.ERROR_FILE_NOT_FOUND {
+ return nil, e
+ }
+ d.isempty = true
+ }
+ d.path = path
+ if !isAbs(d.path) {
+ d.path, e = syscall.FullPath(d.path)
+ if e != nil {
+ d.close()
+ return nil, e
+ }
+ }
+ return d, nil
+}
+
// openFileNolog is the Windows implementation of OpenFile.
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
if name == "" {
diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go
index a0bfd991e3..d6aab18720 100644
--- a/src/os/os_windows_test.go
+++ b/src/os/os_windows_test.go
@@ -1453,3 +1453,15 @@ func TestNewFileInvalid(t *testing.T) {
t.Errorf("NewFile(InvalidHandle) got %v want nil", f)
}
}
+
+func TestReadDirPipe(t *testing.T) {
+ dir := `\\.\pipe\`
+ fi, err := os.Stat(dir)
+ if err != nil || !fi.IsDir() {
+ t.Skipf("%s is not a directory", dir)
+ }
+ _, err = os.ReadDir(dir)
+ if err != nil {
+ t.Errorf("ReadDir(%q) = %v", dir, err)
+ }
+}
diff --git a/src/os/types_windows.go b/src/os/types_windows.go
index d1623f7b17..9a3d508783 100644
--- a/src/os/types_windows.go
+++ b/src/os/types_windows.go
@@ -16,7 +16,7 @@ import (
type fileStat struct {
name string
- // from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx
+ // from ByHandleFileInformation, Win32FileAttributeData and Win32finddata
FileAttributes uint32
CreationTime syscall.Filetime
LastAccessTime syscall.Filetime
@@ -24,7 +24,7 @@ type fileStat struct {
FileSizeHigh uint32
FileSizeLow uint32
- // from Win32finddata and GetFileInformationByHandleEx
+ // from Win32finddata
ReparseTag uint32
// what syscall.GetFileType returns
@@ -32,10 +32,11 @@ type fileStat struct {
// used to implement SameFile
sync.Mutex
- path string
- vol uint32
- idxhi uint32
- idxlo uint32
+ path string
+ vol uint32
+ idxhi uint32
+ idxlo uint32
+ appendNameToPath bool
}
// newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
@@ -79,26 +80,6 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f
}, nil
}
-// newFileStatFromFileIDBothDirInfo copies all required information
-// from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat.
-func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat {
- // The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct.
- // FileAttributes can contain any file attributes that is currently set on the file,
- // not just the ones documented.
- // EaSize contains the reparse tag if the file is a reparse point.
- return &fileStat{
- FileAttributes: d.FileAttributes,
- CreationTime: d.CreationTime,
- LastAccessTime: d.LastAccessTime,
- LastWriteTime: d.LastWriteTime,
- FileSizeHigh: uint32(d.EndOfFile >> 32),
- FileSizeLow: uint32(d.EndOfFile),
- ReparseTag: d.EaSize,
- idxhi: uint32(d.FileID >> 32),
- idxlo: uint32(d.FileID),
- }
-}
-
// newFileStatFromWin32finddata copies all required information
// from syscall.Win32finddata d into the newly created fileStat.
func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
@@ -188,7 +169,13 @@ func (fs *fileStat) loadFileId() error {
// already done
return nil
}
- pathp, err := syscall.UTF16PtrFromString(fs.path)
+ var path string
+ if fs.appendNameToPath {
+ path = fs.path + `\` + fs.name
+ } else {
+ path = fs.path
+ }
+ pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}