github.com/dorkamotorka/go/src@v0.0.0-20230614113921-187095f0e316/os/dir_windows.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os
     6  
     7  import (
     8  	"internal/syscall/windows"
     9  	"io"
    10  	"io/fs"
    11  	"runtime"
    12  	"sync"
    13  	"syscall"
    14  	"unsafe"
    15  )
    16  
    17  // Auxiliary information if the File describes a directory
    18  type dirInfo struct {
    19  	// buf is a slice pointer so the slice header
    20  	// does not escape to the heap when returning
    21  	// buf to dirBufPool.
    22  	buf  *[]byte // buffer for directory I/O
    23  	bufp int     // location of next record in buf
    24  	vol  uint32
    25  }
    26  
    27  const (
    28  	// dirBufSize is the size of the dirInfo buffer.
    29  	// The buffer must be big enough to hold at least a single entry.
    30  	// The filename alone can be 512 bytes (MAX_PATH*2), and the fixed part of
    31  	// the FILE_ID_BOTH_DIR_INFO structure is 105 bytes, so dirBufSize
    32  	// should not be set below 1024 bytes (512+105+safety buffer).
    33  	// Windows 8.1 and earlier only works with buffer sizes up to 64 kB.
    34  	dirBufSize = 64 * 1024 // 64kB
    35  )
    36  
    37  var dirBufPool = sync.Pool{
    38  	New: func() any {
    39  		// The buffer must be at least a block long.
    40  		buf := make([]byte, dirBufSize)
    41  		return &buf
    42  	},
    43  }
    44  
    45  func (d *dirInfo) close() {
    46  	if d.buf != nil {
    47  		dirBufPool.Put(d.buf)
    48  		d.buf = nil
    49  	}
    50  }
    51  
    52  func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
    53  	// If this file has no dirinfo, create one.
    54  	var infoClass uint32 = windows.FileIdBothDirectoryInfo
    55  	if file.dirinfo == nil {
    56  		// vol is used by os.SameFile.
    57  		// It is safe to query it once and reuse the value.
    58  		// Hard links are not allowed to reference files in other volumes.
    59  		// Junctions and symbolic links can reference files and directories in other volumes,
    60  		// but the reparse point should still live in the parent volume.
    61  		var vol uint32
    62  		err = windows.GetVolumeInformationByHandle(file.pfd.Sysfd, nil, 0, &vol, nil, nil, nil, 0)
    63  		runtime.KeepAlive(file)
    64  		if err != nil {
    65  			err = &PathError{Op: "readdir", Path: file.name, Err: err}
    66  			return
    67  		}
    68  		infoClass = windows.FileIdBothDirectoryRestartInfo
    69  		file.dirinfo = new(dirInfo)
    70  		file.dirinfo.buf = dirBufPool.Get().(*[]byte)
    71  		file.dirinfo.vol = vol
    72  	}
    73  	d := file.dirinfo
    74  	wantAll := n <= 0
    75  	if wantAll {
    76  		n = -1
    77  	}
    78  	for n != 0 {
    79  		// Refill the buffer if necessary
    80  		if d.bufp == 0 {
    81  			err = windows.GetFileInformationByHandleEx(file.pfd.Sysfd, infoClass, (*byte)(unsafe.Pointer(&(*d.buf)[0])), uint32(len(*d.buf)))
    82  			runtime.KeepAlive(file)
    83  			if err != nil {
    84  				if err == syscall.ERROR_NO_MORE_FILES {
    85  					break
    86  				}
    87  				if s, _ := file.Stat(); s != nil && !s.IsDir() {
    88  					err = &PathError{Op: "readdir", Path: file.name, Err: syscall.ENOTDIR}
    89  				} else {
    90  					err = &PathError{Op: "GetFileInformationByHandleEx", Path: file.name, Err: err}
    91  				}
    92  				return
    93  			}
    94  			infoClass = windows.FileIdBothDirectoryInfo
    95  		}
    96  		// Drain the buffer
    97  		var islast bool
    98  		for n != 0 && !islast {
    99  			info := (*windows.FILE_ID_BOTH_DIR_INFO)(unsafe.Pointer(&(*d.buf)[d.bufp]))
   100  			d.bufp += int(info.NextEntryOffset)
   101  			islast = info.NextEntryOffset == 0
   102  			if islast {
   103  				d.bufp = 0
   104  			}
   105  			nameslice := unsafe.Slice(&info.FileName[0], info.FileNameLength/2)
   106  			name := syscall.UTF16ToString(nameslice)
   107  			if name == "." || name == ".." { // Useless names
   108  				continue
   109  			}
   110  			if mode == readdirName {
   111  				names = append(names, name)
   112  			} else {
   113  				f := newFileStatFromFileIDBothDirInfo(info)
   114  				f.name = name
   115  				f.vol = d.vol
   116  				// f.path is used by os.SameFile to decide if it needs
   117  				// to fetch vol, idxhi and idxlo. But these are already set,
   118  				// so set f.path to "" to prevent os.SameFile doing it again.
   119  				f.path = ""
   120  				if mode == readdirDirEntry {
   121  					dirents = append(dirents, dirEntry{f})
   122  				} else {
   123  					infos = append(infos, f)
   124  				}
   125  			}
   126  			n--
   127  		}
   128  	}
   129  	if !wantAll && len(names)+len(dirents)+len(infos) == 0 {
   130  		return nil, nil, nil, io.EOF
   131  	}
   132  	return names, dirents, infos, nil
   133  }
   134  
   135  type dirEntry struct {
   136  	fs *fileStat
   137  }
   138  
   139  func (de dirEntry) Name() string            { return de.fs.Name() }
   140  func (de dirEntry) IsDir() bool             { return de.fs.IsDir() }
   141  func (de dirEntry) Type() FileMode          { return de.fs.Mode().Type() }
   142  func (de dirEntry) Info() (FileInfo, error) { return de.fs, nil }
   143  
   144  func (de dirEntry) String() string {
   145  	return fs.FormatDirEntry(de)
   146  }