]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[release-branch.go1.21] Revert "os: use handle based APIs to read directories on...
authorBryan Mills <bcmills@google.com>
Wed, 16 Aug 2023 17:23:10 +0000 (17:23 +0000)
committerCarlos Amedee <carlos@golang.org>
Wed, 23 Aug 2023 19:57:00 +0000 (19:57 +0000)
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>
src/internal/syscall/windows/syscall_windows.go
src/internal/syscall/windows/zsyscall_windows.go
src/os/dir_windows.go
src/os/file_windows.go
src/os/os_windows_test.go
src/os/types_windows.go

index 53d32a14a01437665300fe61b000274980d26b65..892b878d165dd5dda249c47cf2633a5970b5c0de 100644 (file)
@@ -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
index 32744b00fc7bb1985ed8009d2c201309dbbf09e4..a5c246b773d398b3c8a4ab81d529b4b303b5b9e5 100644 (file)
@@ -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 {
index 1724af58d576ffe8c3388b2702e58340874d3712..9dc2cd76899bfed214e0f4410b04c7ec88d14d1e 100644 (file)
 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
index c77d182faefcbbfd09bf1e8b6c4035e397768ad0..8d77a63d37faa5e04cc88ff9be77fbbb44ada120 100644 (file)
@@ -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 == "" {
index a0bfd991e33c1ea5e8554117c62e0ff1391e0146..d6aab18720fc6171f8b8864258107ab2826a525a 100644 (file)
@@ -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)
+       }
+}
index d1623f7b17edc645e27ff26ad822fc0d23eb869e..9a3d508783940ab64a718abd72b124cee17d8d89 100644 (file)
@@ -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
        }