github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/stat_windows.go (about)

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