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 }