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