// 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
+ buf *[]byte // buffer for directory I/O
+ bufp int // location of next record in buf
+ vol uint32
+ class uint32 // type of entries in buf
+ path string // absolute directory path, empty if the file system supports FILE_ID_BOTH_DIR_INFO
}
const (
}
}
+// allowReadDirFileID indicates whether File.readdir should try to use FILE_ID_BOTH_DIR_INFO
+// if the underlying file system supports it.
+// Useful for testing purposes.
+var allowReadDirFileID = true
+
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
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)
+ var vol, flags uint32
+ err = windows.GetVolumeInformationByHandle(file.pfd.Sysfd, nil, 0, &vol, nil, &flags, nil, 0)
runtime.KeepAlive(file)
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
+ if allowReadDirFileID && flags&windows.FILE_SUPPORTS_OPEN_BY_FILE_ID != 0 {
+ file.dirinfo.class = windows.FileIdBothDirectoryRestartInfo
+ } else {
+ file.dirinfo.class = windows.FileFullDirectoryRestartInfo
+ // Set the directory path for use by os.SameFile, as it is possible that
+ // the file system supports retrieving the file ID using GetFileInformationByHandle.
+ file.dirinfo.path = file.name
+ if !isAbs(file.dirinfo.path) {
+ // If the path is relative, we need to convert it to an absolute path
+ // in case the current directory changes between this call and a
+ // call to os.SameFile.
+ file.dirinfo.path, err = syscall.FullPath(file.dirinfo.path)
+ if err != nil {
+ err = &PathError{Op: "readdir", Path: file.name, Err: err}
+ return
+ }
+ }
+ }
}
d := file.dirinfo
wantAll := n <= 0
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)))
+ err = windows.GetFileInformationByHandleEx(file.pfd.Sysfd, d.class, (*byte)(unsafe.Pointer(&(*d.buf)[0])), uint32(len(*d.buf)))
runtime.KeepAlive(file)
if err != nil {
if err == syscall.ERROR_NO_MORE_FILES {
break
}
- if infoClass == windows.FileIdBothDirectoryRestartInfo && err == syscall.ERROR_FILE_NOT_FOUND {
+ if err == syscall.ERROR_FILE_NOT_FOUND &&
+ (d.class == windows.FileIdBothDirectoryRestartInfo || d.class == windows.FileFullDirectoryRestartInfo) {
// 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.
}
return
}
- infoClass = windows.FileIdBothDirectoryInfo
+ if d.class == windows.FileIdBothDirectoryRestartInfo {
+ d.class = windows.FileIdBothDirectoryInfo
+ } else if d.class == windows.FileFullDirectoryRestartInfo {
+ d.class = windows.FileFullDirectoryInfo
+ }
}
// 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
+ var nextEntryOffset uint32
+ var nameslice []uint16
+ entry := unsafe.Pointer(&(*d.buf)[d.bufp])
+ if d.class == windows.FileIdBothDirectoryInfo {
+ info := (*windows.FILE_ID_BOTH_DIR_INFO)(entry)
+ nextEntryOffset = info.NextEntryOffset
+ nameslice = unsafe.Slice(&info.FileName[0], info.FileNameLength/2)
+ } else {
+ info := (*windows.FILE_FULL_DIR_INFO)(entry)
+ nextEntryOffset = info.NextEntryOffset
+ nameslice = unsafe.Slice(&info.FileName[0], info.FileNameLength/2)
+ }
+ d.bufp += int(nextEntryOffset)
+ islast = 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)
} else {
- f := newFileStatFromFileIDBothDirInfo(info)
+ var f *fileStat
+ if d.class == windows.FileIdBothDirectoryInfo {
+ f = newFileStatFromFileIDBothDirInfo((*windows.FILE_ID_BOTH_DIR_INFO)(entry))
+ } else {
+ f = newFileStatFromFileFullDirInfo((*windows.FILE_FULL_DIR_INFO)(entry))
+ // Defer appending the entry name to the parent directory path until
+ // it is really needed, to avoid allocating a string that may not be used.
+ // It is currently only used in os.SameFile.
+ f.appendNameToPath = true
+ f.path = d.path
+ }
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 {
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)
+ }
+}
+
+func TestReadDirNoFileID(t *testing.T) {
+ *os.AllowReadDirFileID = false
+ defer func() { *os.AllowReadDirFileID = true }()
+
+ dir := t.TempDir()
+ pathA := filepath.Join(dir, "a")
+ pathB := filepath.Join(dir, "b")
+ if err := os.WriteFile(pathA, nil, 0666); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(pathB, nil, 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ files, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(files) != 2 {
+ t.Fatalf("ReadDir(%q) = %v; want 2 files", dir, files)
+ }
+
+ // Check that os.SameFile works with files returned by os.ReadDir.
+ f1, err := files[0].Info()
+ if err != nil {
+ t.Fatal(err)
+ }
+ f2, err := files[1].Info()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(f1, f1) {
+ t.Errorf("SameFile(%v, %v) = false; want true", f1, f1)
+ }
+ if !os.SameFile(f2, f2) {
+ t.Errorf("SameFile(%v, %v) = false; want true", f2, f2)
+ }
+ if os.SameFile(f1, f2) {
+ t.Errorf("SameFile(%v, %v) = true; want false", f1, f2)
+ }
+
+ // Check that os.SameFile works with a mix of os.ReadDir and os.Stat files.
+ f1s, err := os.Stat(pathA)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f2s, err := os.Stat(pathB)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(f1, f1s) {
+ t.Errorf("SameFile(%v, %v) = false; want true", f1, f1s)
+ }
+ if !os.SameFile(f2, f2s) {
+ t.Errorf("SameFile(%v, %v) = false; want true", f2, f2s)
+ }
+}
// 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
}
}
+// newFileStatFromFileFullDirInfo copies all required information
+// from windows.FILE_FULL_DIR_INFO d into the newly created fileStat.
+func newFileStatFromFileFullDirInfo(d *windows.FILE_FULL_DIR_INFO) *fileStat {
+ 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,
+ }
+}
+
// newFileStatFromWin32finddata copies all required information
// from syscall.Win32finddata d into the newly created fileStat.
func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
// already done
return nil
}
- pathp, err := syscall.UTF16PtrFromString(fs.path)
+ var path string
+ if fs.appendNameToPath {
+ path = fixLongPath(fs.path + `\` + fs.name)
+ } else {
+ path = fs.path
+ }
+ pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}