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 }