github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/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/experimental/sys"
    11  )
    12  
    13  func openFile(path string, oflag sys.Oflag, perm fs.FileMode) (*os.File, sys.Errno) {
    14  	isDir := oflag&sys.O_DIRECTORY > 0
    15  	flag := toOsOpenFlag(oflag)
    16  
    17  	// TODO: document why we are opening twice
    18  	fd, err := open(path, flag|syscall.O_CLOEXEC, uint32(perm))
    19  	if err == nil {
    20  		return os.NewFile(uintptr(fd), path), 0
    21  	}
    22  
    23  	// TODO: Set FILE_SHARE_DELETE for directory as well.
    24  	f, err := os.OpenFile(path, flag, perm)
    25  	errno := sys.UnwrapOSError(err)
    26  	if errno == 0 {
    27  		return f, 0
    28  	}
    29  
    30  	switch errno {
    31  	case sys.EINVAL:
    32  		// WASI expects ENOTDIR for a file path with a trailing slash.
    33  		if strings.HasSuffix(path, "/") {
    34  			errno = sys.ENOTDIR
    35  		}
    36  	// To match expectations of WASI, e.g. TinyGo TestStatBadDir, return
    37  	// ENOENT, not ENOTDIR.
    38  	case sys.ENOTDIR:
    39  		errno = sys.ENOENT
    40  	case sys.ENOENT:
    41  		if isSymlink(path) {
    42  			// Either symlink or hard link not found. We change the returned
    43  			// errno depending on if it is symlink or not to have consistent
    44  			// behavior across OSes.
    45  			if isDir {
    46  				// Dangling symlink dir must raise ENOTDIR.
    47  				errno = sys.ENOTDIR
    48  			} else {
    49  				errno = sys.ELOOP
    50  			}
    51  		}
    52  	}
    53  	return f, errno
    54  }
    55  
    56  const supportedSyscallOflag = sys.O_NONBLOCK
    57  
    58  // Map to synthetic values here https://github.com/golang/go/blob/go1.20/src/syscall/types_windows.go#L34-L48
    59  func withSyscallOflag(oflag sys.Oflag, flag int) int {
    60  	// O_DIRECTORY not defined in windows
    61  	// O_DSYNC not defined in windows
    62  	// O_NOFOLLOW not defined in windows
    63  	if oflag&sys.O_NONBLOCK != 0 {
    64  		flag |= syscall.O_NONBLOCK
    65  	}
    66  	// O_RSYNC not defined in windows
    67  	return flag
    68  }
    69  
    70  func isSymlink(path string) bool {
    71  	if st, e := os.Lstat(path); e == nil && st.Mode()&os.ModeSymlink != 0 {
    72  		return true
    73  	}
    74  	return false
    75  }
    76  
    77  // # Differences from syscall.Open
    78  //
    79  // This code is based on syscall.Open from the below link with some differences
    80  // https://github.com/golang/go/blame/go1.20/src/syscall/syscall_windows.go#L308-L379
    81  //
    82  //   - syscall.O_CREAT doesn't imply syscall.GENERIC_WRITE as that breaks
    83  //     flag expectations in wasi.
    84  //   - add support for setting FILE_SHARE_DELETE.
    85  func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
    86  	if len(path) == 0 {
    87  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    88  	}
    89  	pathp, err := syscall.UTF16PtrFromString(path)
    90  	if err != nil {
    91  		return syscall.InvalidHandle, err
    92  	}
    93  	var access uint32
    94  	switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    95  	case syscall.O_RDONLY:
    96  		access = syscall.GENERIC_READ
    97  	case syscall.O_WRONLY:
    98  		access = syscall.GENERIC_WRITE
    99  	case syscall.O_RDWR:
   100  		access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
   101  	}
   102  	if mode&syscall.O_APPEND != 0 {
   103  		access &^= syscall.GENERIC_WRITE
   104  		access |= syscall.FILE_APPEND_DATA
   105  	}
   106  	sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
   107  	var sa *syscall.SecurityAttributes
   108  	if mode&syscall.O_CLOEXEC == 0 {
   109  		var _sa syscall.SecurityAttributes
   110  		_sa.Length = uint32(unsafe.Sizeof(sa))
   111  		_sa.InheritHandle = 1
   112  		sa = &_sa
   113  	}
   114  	var createmode uint32
   115  	switch {
   116  	case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
   117  		createmode = syscall.CREATE_NEW
   118  	case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
   119  		createmode = syscall.CREATE_ALWAYS
   120  	case mode&syscall.O_CREAT == syscall.O_CREAT:
   121  		createmode = syscall.OPEN_ALWAYS
   122  	case mode&syscall.O_TRUNC == syscall.O_TRUNC:
   123  		createmode = syscall.TRUNCATE_EXISTING
   124  	default:
   125  		createmode = syscall.OPEN_EXISTING
   126  	}
   127  	var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL
   128  	if perm&syscall.S_IWRITE == 0 {
   129  		attrs = syscall.FILE_ATTRIBUTE_READONLY
   130  		if createmode == syscall.CREATE_ALWAYS {
   131  			// We have been asked to create a read-only file.
   132  			// If the file already exists, the semantics of
   133  			// the Unix open system call is to preserve the
   134  			// existing permissions. If we pass CREATE_ALWAYS
   135  			// and FILE_ATTRIBUTE_READONLY to CreateFile,
   136  			// and the file already exists, CreateFile will
   137  			// change the file permissions.
   138  			// Avoid that to preserve the Unix semantics.
   139  			h, e := syscall.CreateFile(pathp, access, sharemode, sa, syscall.TRUNCATE_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
   140  			switch e {
   141  			case syscall.ERROR_FILE_NOT_FOUND, syscall.ERROR_PATH_NOT_FOUND:
   142  				// File does not exist. These are the same
   143  				// errors as Errno.Is checks for ErrNotExist.
   144  				// Carry on to create the file.
   145  			default:
   146  				// Success or some different error.
   147  				return h, e
   148  			}
   149  		}
   150  	}
   151  
   152  	// This shouldn't be included before 1.20 to have consistent behavior.
   153  	// https://github.com/golang/go/commit/0f0aa5d8a6a0253627d58b3aa083b24a1091933f
   154  	if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ {
   155  		// Necessary for opening directory handles.
   156  		attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS
   157  	}
   158  
   159  	h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
   160  	return h, e
   161  }