github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sysfs/osfile.go (about) 1 package sysfs 2 3 import ( 4 "io" 5 "io/fs" 6 "os" 7 "runtime" 8 9 experimentalsys "github.com/wasilibs/wazerox/experimental/sys" 10 "github.com/wasilibs/wazerox/internal/fsapi" 11 "github.com/wasilibs/wazerox/sys" 12 ) 13 14 func newOsFile(path string, flag experimentalsys.Oflag, perm fs.FileMode, f *os.File) fsapi.File { 15 // Windows cannot read files written to a directory after it was opened. 16 // This was noticed in #1087 in zig tests. Use a flag instead of a 17 // different type. 18 reopenDir := runtime.GOOS == "windows" 19 return &osFile{path: path, flag: flag, perm: perm, reopenDir: reopenDir, file: f, fd: f.Fd()} 20 } 21 22 // osFile is a file opened with this package, and uses os.File or syscalls to 23 // implement api.File. 24 type osFile struct { 25 path string 26 flag experimentalsys.Oflag 27 perm fs.FileMode 28 file *os.File 29 fd uintptr 30 31 // reopenDir is true if reopen should be called before Readdir. This flag 32 // is deferred until Readdir to prevent redundant rewinds. This could 33 // happen if Seek(0) was called twice, or if in Windows, Seek(0) was called 34 // before Readdir. 35 reopenDir bool 36 37 // closed is true when closed was called. This ensures proper sys.EBADF 38 closed bool 39 40 // cachedStat includes fields that won't change while a file is open. 41 cachedSt *cachedStat 42 } 43 44 // cachedStat returns the cacheable parts of sys.Stat_t or an error if they 45 // couldn't be retrieved. 46 func (f *osFile) cachedStat() (dev uint64, ino sys.Inode, isDir bool, errno experimentalsys.Errno) { 47 if f.cachedSt == nil { 48 if _, errno = f.Stat(); errno != 0 { 49 return 50 } 51 } 52 return f.cachedSt.dev, f.cachedSt.ino, f.cachedSt.isDir, 0 53 } 54 55 // Dev implements the same method as documented on sys.File 56 func (f *osFile) Dev() (uint64, experimentalsys.Errno) { 57 dev, _, _, errno := f.cachedStat() 58 return dev, errno 59 } 60 61 // Ino implements the same method as documented on sys.File 62 func (f *osFile) Ino() (sys.Inode, experimentalsys.Errno) { 63 _, ino, _, errno := f.cachedStat() 64 return ino, errno 65 } 66 67 // IsDir implements the same method as documented on sys.File 68 func (f *osFile) IsDir() (bool, experimentalsys.Errno) { 69 _, _, isDir, errno := f.cachedStat() 70 return isDir, errno 71 } 72 73 // IsAppend implements File.IsAppend 74 func (f *osFile) IsAppend() bool { 75 return f.flag&experimentalsys.O_APPEND == experimentalsys.O_APPEND 76 } 77 78 // SetAppend implements the same method as documented on sys.File 79 func (f *osFile) SetAppend(enable bool) (errno experimentalsys.Errno) { 80 if enable { 81 f.flag |= experimentalsys.O_APPEND 82 } else { 83 f.flag &= ^experimentalsys.O_APPEND 84 } 85 86 // Clear any create or trunc flag, as we are re-opening, not re-creating. 87 f.flag &= ^(experimentalsys.O_CREAT | experimentalsys.O_TRUNC) 88 89 // appendMode (bool) cannot be changed later, so we have to re-open the 90 // file. https://github.com/golang/go/blob/go1.20/src/os/file_unix.go#L60 91 return fileError(f, f.closed, f.reopen()) 92 } 93 94 // compile-time check to ensure osFile.reopen implements reopenFile. 95 var _ reopenFile = (*fsFile)(nil).reopen 96 97 func (f *osFile) reopen() (errno experimentalsys.Errno) { 98 // Clear any create flag, as we are re-opening, not re-creating. 99 f.flag &= ^experimentalsys.O_CREAT 100 101 var ( 102 isDir bool 103 offset int64 104 err error 105 ) 106 107 isDir, errno = f.IsDir() 108 if errno != 0 { 109 return errno 110 } 111 112 if !isDir { 113 offset, err = f.file.Seek(0, io.SeekCurrent) 114 if err != nil { 115 return experimentalsys.UnwrapOSError(err) 116 } 117 } 118 119 _ = f.close() 120 f.file, errno = OpenFile(f.path, f.flag, f.perm) 121 if errno != 0 { 122 return errno 123 } 124 125 if !isDir { 126 _, err = f.file.Seek(offset, io.SeekStart) 127 if err != nil { 128 return experimentalsys.UnwrapOSError(err) 129 } 130 } 131 132 return 0 133 } 134 135 // IsNonblock implements the same method as documented on fsapi.File 136 func (f *osFile) IsNonblock() bool { 137 return isNonblock(f) 138 } 139 140 // SetNonblock implements the same method as documented on fsapi.File 141 func (f *osFile) SetNonblock(enable bool) (errno experimentalsys.Errno) { 142 if enable { 143 f.flag |= experimentalsys.O_NONBLOCK 144 } else { 145 f.flag &= ^experimentalsys.O_NONBLOCK 146 } 147 if errno = setNonblock(f.fd, enable); errno != 0 { 148 return fileError(f, f.closed, errno) 149 } 150 return 0 151 } 152 153 // Stat implements the same method as documented on sys.File 154 func (f *osFile) Stat() (sys.Stat_t, experimentalsys.Errno) { 155 if f.closed { 156 return sys.Stat_t{}, experimentalsys.EBADF 157 } 158 159 st, errno := statFile(f.file) 160 switch errno { 161 case 0: 162 f.cachedSt = &cachedStat{dev: st.Dev, ino: st.Ino, isDir: st.Mode&fs.ModeDir == fs.ModeDir} 163 case experimentalsys.EIO: 164 errno = experimentalsys.EBADF 165 } 166 return st, errno 167 } 168 169 // Read implements the same method as documented on sys.File 170 func (f *osFile) Read(buf []byte) (n int, errno experimentalsys.Errno) { 171 if len(buf) == 0 { 172 return 0, 0 // Short-circuit 0-len reads. 173 } 174 if nonBlockingFileReadSupported && f.IsNonblock() { 175 n, errno = readFd(f.fd, buf) 176 } else { 177 n, errno = read(f.file, buf) 178 } 179 if errno != 0 { 180 // Defer validation overhead until we've already had an error. 181 errno = fileError(f, f.closed, errno) 182 } 183 return 184 } 185 186 // Pread implements the same method as documented on sys.File 187 func (f *osFile) Pread(buf []byte, off int64) (n int, errno experimentalsys.Errno) { 188 if n, errno = pread(f.file, buf, off); errno != 0 { 189 // Defer validation overhead until we've already had an error. 190 errno = fileError(f, f.closed, errno) 191 } 192 return 193 } 194 195 // Seek implements the same method as documented on sys.File 196 func (f *osFile) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) { 197 if newOffset, errno = seek(f.file, offset, whence); errno != 0 { 198 // Defer validation overhead until we've already had an error. 199 errno = fileError(f, f.closed, errno) 200 201 // If the error was trying to rewind a directory, re-open it. Notably, 202 // seeking to zero on a directory doesn't work on Windows with Go 1.19. 203 if errno == experimentalsys.EISDIR && offset == 0 && whence == io.SeekStart { 204 errno = 0 205 f.reopenDir = true 206 } 207 } 208 return 209 } 210 211 // Poll implements the same method as documented on fsapi.File 212 func (f *osFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { 213 return poll(f.fd, flag, timeoutMillis) 214 } 215 216 // Readdir implements File.Readdir. Notably, this uses "Readdir", not 217 // "ReadDir", from os.File. 218 func (f *osFile) Readdir(n int) (dirents []experimentalsys.Dirent, errno experimentalsys.Errno) { 219 if f.reopenDir { // re-open the directory if needed. 220 f.reopenDir = false 221 if errno = adjustReaddirErr(f, f.closed, f.reopen()); errno != 0 { 222 return 223 } 224 } 225 226 if dirents, errno = readdir(f.file, f.path, n); errno != 0 { 227 errno = adjustReaddirErr(f, f.closed, errno) 228 } 229 return 230 } 231 232 // Write implements the same method as documented on sys.File 233 func (f *osFile) Write(buf []byte) (n int, errno experimentalsys.Errno) { 234 if len(buf) == 0 { 235 return 0, 0 // Short-circuit 0-len writes. 236 } 237 if nonBlockingFileWriteSupported && f.IsNonblock() { 238 n, errno = writeFd(f.fd, buf) 239 } else if n, errno = write(f.file, buf); errno != 0 { 240 // Defer validation overhead until we've already had an error. 241 errno = fileError(f, f.closed, errno) 242 } 243 return 244 } 245 246 // Pwrite implements the same method as documented on sys.File 247 func (f *osFile) Pwrite(buf []byte, off int64) (n int, errno experimentalsys.Errno) { 248 if n, errno = pwrite(f.file, buf, off); errno != 0 { 249 // Defer validation overhead until we've already had an error. 250 errno = fileError(f, f.closed, errno) 251 } 252 return 253 } 254 255 // Truncate implements the same method as documented on sys.File 256 func (f *osFile) Truncate(size int64) (errno experimentalsys.Errno) { 257 if errno = experimentalsys.UnwrapOSError(f.file.Truncate(size)); errno != 0 { 258 // Defer validation overhead until we've already had an error. 259 errno = fileError(f, f.closed, errno) 260 } 261 return 262 } 263 264 // Sync implements the same method as documented on sys.File 265 func (f *osFile) Sync() experimentalsys.Errno { 266 return fsync(f.file) 267 } 268 269 // Datasync implements the same method as documented on sys.File 270 func (f *osFile) Datasync() experimentalsys.Errno { 271 return datasync(f.file) 272 } 273 274 // Utimens implements the same method as documented on sys.File 275 func (f *osFile) Utimens(atim, mtim int64) experimentalsys.Errno { 276 if f.closed { 277 return experimentalsys.EBADF 278 } 279 280 err := futimens(f.fd, atim, mtim) 281 return experimentalsys.UnwrapOSError(err) 282 } 283 284 // Close implements the same method as documented on sys.File 285 func (f *osFile) Close() experimentalsys.Errno { 286 if f.closed { 287 return 0 288 } 289 f.closed = true 290 return f.close() 291 } 292 293 func (f *osFile) close() experimentalsys.Errno { 294 return experimentalsys.UnwrapOSError(f.file.Close()) 295 }