github.com/gaukas/wazerofs@v0.1.0/sysfs/stat_windows.go (about)

     1  //go:build (amd64 || arm64) && windows
     2  
     3  package sysfs
     4  
     5  import (
     6  	"io/fs"
     7  	"syscall"
     8  
     9  	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
    10  	"github.com/tetratelabs/wazero/sys"
    11  )
    12  
    13  // dirNlinkIncludesDot is false because Windows does not return dot entries.
    14  //
    15  // Note: this is only used in tests
    16  const dirNlinkIncludesDot = false
    17  
    18  func lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
    19  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
    20  	// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
    21  	// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
    22  	attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
    23  	return statPath(attrs, path)
    24  }
    25  
    26  func stat(path string) (sys.Stat_t, experimentalsys.Errno) {
    27  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
    28  	return statPath(attrs, path)
    29  }
    30  
    31  func statPath(createFileAttrs uint32, path string) (sys.Stat_t, experimentalsys.Errno) {
    32  	if len(path) == 0 {
    33  		return sys.Stat_t{}, experimentalsys.ENOENT
    34  	}
    35  	pathp, err := syscall.UTF16PtrFromString(path)
    36  	if err != nil {
    37  		return sys.Stat_t{}, experimentalsys.EINVAL
    38  	}
    39  
    40  	// open the file handle
    41  	h, err := syscall.CreateFile(pathp, 0, 0, nil,
    42  		syscall.OPEN_EXISTING, createFileAttrs, 0)
    43  	if err != nil {
    44  		errno := experimentalsys.UnwrapOSError(err)
    45  		// To match expectations of WASI, e.g. TinyGo TestStatBadDir, return
    46  		// ENOENT, not ENOTDIR.
    47  		if errno == experimentalsys.ENOTDIR {
    48  			errno = experimentalsys.ENOENT
    49  		}
    50  		return sys.Stat_t{}, errno
    51  	}
    52  	defer syscall.CloseHandle(h)
    53  
    54  	return statHandle(h)
    55  }
    56  
    57  // fdFile allows masking the `Fd` function on os.File.
    58  type fdFile interface {
    59  	Fd() uintptr
    60  }
    61  
    62  func statFile(f fs.File) (sys.Stat_t, experimentalsys.Errno) {
    63  	if osF, ok := f.(fdFile); ok {
    64  		// Attempt to get the stat by handle, which works for normal files
    65  		st, err := statHandle(syscall.Handle(osF.Fd()))
    66  
    67  		// ERROR_INVALID_HANDLE happens before Go 1.20. Don't fail as we only
    68  		// use that approach to fill in inode data, which is not critical.
    69  		//
    70  		// Note: statHandle uses UnwrapOSError which coerces
    71  		// ERROR_INVALID_HANDLE to EBADF.
    72  		if err != experimentalsys.EBADF {
    73  			return st, err
    74  		}
    75  	}
    76  	return defaultStatFile(f)
    77  }
    78  
    79  func statHandle(h syscall.Handle) (sys.Stat_t, experimentalsys.Errno) {
    80  	winFt, err := syscall.GetFileType(h)
    81  	if err != nil {
    82  		return sys.Stat_t{}, experimentalsys.UnwrapOSError(err)
    83  	}
    84  
    85  	var fi syscall.ByHandleFileInformation
    86  	if err = syscall.GetFileInformationByHandle(h, &fi); err != nil {
    87  		return sys.Stat_t{}, experimentalsys.UnwrapOSError(err)
    88  	}
    89  
    90  	var m fs.FileMode
    91  	if fi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
    92  		m |= 0o444
    93  	} else {
    94  		m |= 0o666
    95  	}
    96  
    97  	switch { // check whether this is a symlink first
    98  	case fi.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
    99  		m |= fs.ModeSymlink
   100  	case winFt == syscall.FILE_TYPE_PIPE:
   101  		m |= fs.ModeNamedPipe
   102  	case winFt == syscall.FILE_TYPE_CHAR:
   103  		m |= fs.ModeDevice | fs.ModeCharDevice
   104  	case fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
   105  		m |= fs.ModeDir | 0o111 // e.g. 0o444 -> 0o555
   106  	}
   107  
   108  	st := sys.Stat_t{}
   109  	// FileIndex{High,Low} can be combined and used as a unique identifier like inode.
   110  	// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
   111  	st.Dev = uint64(fi.VolumeSerialNumber)
   112  	st.Ino = (uint64(fi.FileIndexHigh) << 32) | uint64(fi.FileIndexLow)
   113  	st.Mode = m
   114  	st.Nlink = uint64(fi.NumberOfLinks)
   115  	st.Size = int64(fi.FileSizeHigh)<<32 + int64(fi.FileSizeLow)
   116  	st.Atim = fi.LastAccessTime.Nanoseconds()
   117  	st.Mtim = fi.LastWriteTime.Nanoseconds()
   118  	st.Ctim = fi.CreationTime.Nanoseconds()
   119  	return st, 0
   120  }