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 }