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

     1  package sysfs
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"strings"
     7  	"syscall"
     8  	"unsafe"
     9  
    10  	"github.com/tetratelabs/wazero/internal/fsapi"
    11  	"github.com/tetratelabs/wazero/internal/platform"
    12  )
    13  
    14  func newOsFile(openPath string, openFlag int, openPerm fs.FileMode, f *os.File) fsapi.File {
    15  	return &windowsOsFile{
    16  		osFile: osFile{path: openPath, flag: openFlag, perm: openPerm, file: f, fd: f.Fd()},
    17  	}
    18  }
    19  
    20  func openFile(path string, flag int, perm fs.FileMode) (*os.File, syscall.Errno) {
    21  	isDir := flag&fsapi.O_DIRECTORY > 0
    22  	flag &= ^(fsapi.O_DIRECTORY | fsapi.O_NOFOLLOW) // erase placeholders
    23  
    24  	// TODO: document why we are opening twice
    25  	fd, err := open(path, flag|syscall.O_CLOEXEC, uint32(perm))
    26  	if err == nil {
    27  		return os.NewFile(uintptr(fd), path), 0
    28  	}
    29  
    30  	// TODO: Set FILE_SHARE_DELETE for directory as well.
    31  	f, err := os.OpenFile(path, flag, perm)
    32  	errno := platform.UnwrapOSError(err)
    33  	if errno == 0 {
    34  		return f, 0
    35  	}
    36  
    37  	switch errno {
    38  	case syscall.EINVAL:
    39  		// WASI expects ENOTDIR for a file path with a trailing slash.
    40  		if strings.HasSuffix(path, "/") {
    41  			errno = syscall.ENOTDIR
    42  		}
    43  	// To match expectations of WASI, e.g. TinyGo TestStatBadDir, return
    44  	// ENOENT, not ENOTDIR.
    45  	case syscall.ENOTDIR:
    46  		errno = syscall.ENOENT
    47  	case syscall.ENOENT:
    48  		if isSymlink(path) {
    49  			// Either symlink or hard link not found. We change the returned
    50  			// errno depending on if it is symlink or not to have consistent
    51  			// behavior across OSes.
    52  			if isDir {
    53  				// Dangling symlink dir must raise ENOTDIR.
    54  				errno = syscall.ENOTDIR
    55  			} else {
    56  				errno = syscall.ELOOP
    57  			}
    58  		}
    59  	}
    60  	return f, errno
    61  }
    62  
    63  func isSymlink(path string) bool {
    64  	if st, e := os.Lstat(path); e == nil && st.Mode()&os.ModeSymlink != 0 {
    65  		return true
    66  	}
    67  	return false
    68  }
    69  
    70  // # Differences from syscall.Open
    71  //
    72  // This code is based on syscall.Open from the below link with some differences
    73  // https://github.com/golang/go/blame/go1.20/src/syscall/syscall_windows.go#L308-L379
    74  //
    75  //   - syscall.O_CREAT doesn't imply syscall.GENERIC_WRITE as that breaks
    76  //     flag expectations in wasi.
    77  //   - add support for setting FILE_SHARE_DELETE.
    78  func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
    79  	if len(path) == 0 {
    80  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    81  	}
    82  	pathp, err := syscall.UTF16PtrFromString(path)
    83  	if err != nil {
    84  		return syscall.InvalidHandle, err
    85  	}
    86  	var access uint32
    87  	switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    88  	case syscall.O_RDONLY:
    89  		access = syscall.GENERIC_READ
    90  	case syscall.O_WRONLY:
    91  		access = syscall.GENERIC_WRITE
    92  	case syscall.O_RDWR:
    93  		access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
    94  	}
    95  	if mode&syscall.O_APPEND != 0 {
    96  		access &^= syscall.GENERIC_WRITE
    97  		access |= syscall.FILE_APPEND_DATA
    98  	}
    99  	sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
   100  	var sa *syscall.SecurityAttributes
   101  	if mode&syscall.O_CLOEXEC == 0 {
   102  		var _sa syscall.SecurityAttributes
   103  		_sa.Length = uint32(unsafe.Sizeof(sa))
   104  		_sa.InheritHandle = 1
   105  		sa = &_sa
   106  	}
   107  	var createmode uint32
   108  	switch {
   109  	case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
   110  		createmode = syscall.CREATE_NEW
   111  	case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
   112  		createmode = syscall.CREATE_ALWAYS
   113  	case mode&syscall.O_CREAT == syscall.O_CREAT:
   114  		createmode = syscall.OPEN_ALWAYS
   115  	case mode&syscall.O_TRUNC == syscall.O_TRUNC:
   116  		createmode = syscall.TRUNCATE_EXISTING
   117  	default:
   118  		createmode = syscall.OPEN_EXISTING
   119  	}
   120  	var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL
   121  	if perm&syscall.S_IWRITE == 0 {
   122  		attrs = syscall.FILE_ATTRIBUTE_READONLY
   123  		if createmode == syscall.CREATE_ALWAYS {
   124  			// We have been asked to create a read-only file.
   125  			// If the file already exists, the semantics of
   126  			// the Unix open system call is to preserve the
   127  			// existing permissions. If we pass CREATE_ALWAYS
   128  			// and FILE_ATTRIBUTE_READONLY to CreateFile,
   129  			// and the file already exists, CreateFile will
   130  			// change the file permissions.
   131  			// Avoid that to preserve the Unix semantics.
   132  			h, e := syscall.CreateFile(pathp, access, sharemode, sa, syscall.TRUNCATE_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
   133  			switch e {
   134  			case syscall.ERROR_FILE_NOT_FOUND, syscall.ERROR_PATH_NOT_FOUND:
   135  				// File does not exist. These are the same
   136  				// errors as Errno.Is checks for ErrNotExist.
   137  				// Carry on to create the file.
   138  			default:
   139  				// Success or some different error.
   140  				return h, e
   141  			}
   142  		}
   143  	}
   144  
   145  	if platform.IsGo120 {
   146  		// This shouldn't be included before 1.20 to have consistent behavior.
   147  		// https://github.com/golang/go/commit/0f0aa5d8a6a0253627d58b3aa083b24a1091933f
   148  		if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ {
   149  			// Necessary for opening directory handles.
   150  			attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS
   151  		}
   152  	}
   153  
   154  	h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
   155  	return h, e
   156  }
   157  
   158  // windowsOsFile overrides osFile to special case directory handling in Windows.
   159  type windowsOsFile struct {
   160  	osFile
   161  
   162  	dirInitialized bool
   163  }
   164  
   165  // Readdir implements File.Readdir
   166  func (f *windowsOsFile) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
   167  	if errno = f.maybeInitDir(); errno != 0 {
   168  		return
   169  	}
   170  
   171  	return f.osFile.Readdir(n)
   172  }
   173  
   174  func (f *windowsOsFile) maybeInitDir() syscall.Errno {
   175  	if f.dirInitialized {
   176  		return 0
   177  	}
   178  
   179  	if isDir, errno := f.IsDir(); errno != 0 {
   180  		return errno
   181  	} else if !isDir {
   182  		return syscall.ENOTDIR
   183  	}
   184  
   185  	// On Windows, once the directory is opened, changes to the directory are
   186  	// not visible on ReadDir on that already-opened file handle.
   187  	//
   188  	// To provide consistent behavior with other platforms, we re-open it.
   189  	if errno := f.osFile.Close(); errno != 0 {
   190  		return errno
   191  	}
   192  	newW, errno := openFile(f.path, f.flag, f.perm)
   193  	if errno != 0 {
   194  		return errno
   195  	}
   196  	f.osFile.file = newW
   197  	f.dirInitialized = true
   198  	return 0
   199  }