github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/osfile.go (about) 1 package sysfs 2 3 import ( 4 "io" 5 "io/fs" 6 "os" 7 "syscall" 8 "time" 9 10 "github.com/tetratelabs/wazero/internal/fsapi" 11 "github.com/tetratelabs/wazero/internal/platform" 12 ) 13 14 func newDefaultOsFile(openPath string, openFlag int, openPerm fs.FileMode, f *os.File) fsapi.File { 15 return &osFile{path: openPath, flag: openFlag, perm: openPerm, file: f, fd: f.Fd()} 16 } 17 18 // osFile is a file opened with this package, and uses os.File or syscalls to 19 // implement api.File. 20 type osFile struct { 21 path string 22 flag int 23 perm fs.FileMode 24 file *os.File 25 fd uintptr 26 27 // closed is true when closed was called. This ensures proper syscall.EBADF 28 closed bool 29 30 // cachedStat includes fields that won't change while a file is open. 31 cachedSt *cachedStat 32 } 33 34 // cachedStat returns the cacheable parts of platform.sys.Stat_t or an error if 35 // they couldn't be retrieved. 36 func (f *osFile) cachedStat() (fileType fs.FileMode, ino uint64, errno syscall.Errno) { 37 if f.cachedSt == nil { 38 if _, errno = f.Stat(); errno != 0 { 39 return 40 } 41 } 42 return f.cachedSt.fileType, f.cachedSt.ino, 0 43 } 44 45 // Ino implements the same method as documented on fsapi.File 46 func (f *osFile) Ino() (uint64, syscall.Errno) { 47 if _, ino, errno := f.cachedStat(); errno != 0 { 48 return 0, errno 49 } else { 50 return ino, 0 51 } 52 } 53 54 // IsAppend implements File.IsAppend 55 func (f *osFile) IsAppend() bool { 56 return f.flag&syscall.O_APPEND == syscall.O_APPEND 57 } 58 59 // SetAppend implements the same method as documented on fsapi.File 60 func (f *osFile) SetAppend(enable bool) (errno syscall.Errno) { 61 if enable { 62 f.flag |= syscall.O_APPEND 63 } else { 64 f.flag &= ^syscall.O_APPEND 65 } 66 67 // Clear any create flag, as we are re-opening, not re-creating. 68 f.flag &= ^syscall.O_CREAT 69 70 // appendMode (bool) cannot be changed later, so we have to re-open the 71 // file. https://github.com/golang/go/blob/go1.20/src/os/file_unix.go#L60 72 return fileError(f, f.closed, f.reopen()) 73 } 74 75 func (f *osFile) reopen() (errno syscall.Errno) { 76 // Clear any create flag, as we are re-opening, not re-creating. 77 f.flag &= ^syscall.O_CREAT 78 79 _ = f.close() 80 f.file, errno = OpenFile(f.path, f.flag, f.perm) 81 return 82 } 83 84 // IsNonblock implements the same method as documented on fsapi.File 85 func (f *osFile) IsNonblock() bool { 86 return f.flag&fsapi.O_NONBLOCK == fsapi.O_NONBLOCK 87 } 88 89 // SetNonblock implements the same method as documented on fsapi.File 90 func (f *osFile) SetNonblock(enable bool) (errno syscall.Errno) { 91 if enable { 92 f.flag |= fsapi.O_NONBLOCK 93 } else { 94 f.flag &= ^fsapi.O_NONBLOCK 95 } 96 if err := setNonblock(f.fd, enable); err != nil { 97 return fileError(f, f.closed, platform.UnwrapOSError(err)) 98 } 99 return 0 100 } 101 102 // IsDir implements the same method as documented on fsapi.File 103 func (f *osFile) IsDir() (bool, syscall.Errno) { 104 if ft, _, errno := f.cachedStat(); errno != 0 { 105 return false, errno 106 } else if ft.Type() == fs.ModeDir { 107 return true, 0 108 } 109 return false, 0 110 } 111 112 // Stat implements the same method as documented on fsapi.File 113 func (f *osFile) Stat() (fsapi.Stat_t, syscall.Errno) { 114 if f.closed { 115 return fsapi.Stat_t{}, syscall.EBADF 116 } 117 118 st, errno := statFile(f.file) 119 switch errno { 120 case 0: 121 f.cachedSt = &cachedStat{fileType: st.Mode & fs.ModeType, ino: st.Ino} 122 case syscall.EIO: 123 errno = syscall.EBADF 124 } 125 return st, errno 126 } 127 128 // Read implements the same method as documented on fsapi.File 129 func (f *osFile) Read(buf []byte) (n int, errno syscall.Errno) { 130 if len(buf) == 0 { 131 return 0, 0 // Short-circuit 0-len reads. 132 } 133 if NonBlockingFileIoSupported && f.IsNonblock() { 134 n, errno = readFd(f.fd, buf) 135 } else { 136 n, errno = read(f.file, buf) 137 } 138 if errno != 0 { 139 // Defer validation overhead until we've already had an error. 140 errno = fileError(f, f.closed, errno) 141 } 142 return 143 } 144 145 // Pread implements the same method as documented on fsapi.File 146 func (f *osFile) Pread(buf []byte, off int64) (n int, errno syscall.Errno) { 147 if n, errno = pread(f.file, buf, off); errno != 0 { 148 // Defer validation overhead until we've already had an error. 149 errno = fileError(f, f.closed, errno) 150 } 151 return 152 } 153 154 // Seek implements the same method as documented on fsapi.File 155 func (f *osFile) Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno) { 156 if newOffset, errno = seek(f.file, offset, whence); errno != 0 { 157 // Defer validation overhead until we've already had an error. 158 errno = fileError(f, f.closed, errno) 159 160 // If the error was trying to rewind a directory, re-open it. Notably, 161 // seeking to zero on a directory doesn't work on Windows with Go 1.18. 162 if errno == syscall.EISDIR && offset == 0 && whence == io.SeekStart { 163 return 0, f.reopen() 164 } 165 } 166 return 167 } 168 169 // PollRead implements the same method as documented on fsapi.File 170 func (f *osFile) PollRead(timeout *time.Duration) (ready bool, errno syscall.Errno) { 171 fdSet := platform.FdSet{} 172 fd := int(f.fd) 173 fdSet.Set(fd) 174 nfds := fd + 1 // See https://man7.org/linux/man-pages/man2/select.2.html#:~:text=condition%20has%20occurred.-,nfds,-This%20argument%20should 175 count, err := _select(nfds, &fdSet, nil, nil, timeout) 176 if errno = platform.UnwrapOSError(err); errno != 0 { 177 // Defer validation overhead until we've already had an error. 178 errno = fileError(f, f.closed, errno) 179 } 180 return count > 0, errno 181 } 182 183 // Readdir implements File.Readdir. Notably, this uses "Readdir", not 184 // "ReadDir", from os.File. 185 func (f *osFile) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) { 186 if dirents, errno = readdir(f.file, f.path, n); errno != 0 { 187 errno = adjustReaddirErr(f, f.closed, errno) 188 } 189 return 190 } 191 192 // Write implements the same method as documented on fsapi.File 193 func (f *osFile) Write(buf []byte) (n int, errno syscall.Errno) { 194 if n, errno = write(f.file, buf); errno != 0 { 195 // Defer validation overhead until we've already had an error. 196 errno = fileError(f, f.closed, errno) 197 } 198 return 199 } 200 201 // Pwrite implements the same method as documented on fsapi.File 202 func (f *osFile) Pwrite(buf []byte, off int64) (n int, errno syscall.Errno) { 203 if n, errno = pwrite(f.file, buf, off); errno != 0 { 204 // Defer validation overhead until we've already had an error. 205 errno = fileError(f, f.closed, errno) 206 } 207 return 208 } 209 210 // Truncate implements the same method as documented on fsapi.File 211 func (f *osFile) Truncate(size int64) (errno syscall.Errno) { 212 if errno = platform.UnwrapOSError(f.file.Truncate(size)); errno != 0 { 213 // Defer validation overhead until we've already had an error. 214 errno = fileError(f, f.closed, errno) 215 } 216 return 217 } 218 219 // Sync implements the same method as documented on fsapi.File 220 func (f *osFile) Sync() syscall.Errno { 221 return fsync(f.file) 222 } 223 224 // Datasync implements the same method as documented on fsapi.File 225 func (f *osFile) Datasync() syscall.Errno { 226 return datasync(f.file) 227 } 228 229 // Chmod implements the same method as documented on fsapi.File 230 func (f *osFile) Chmod(mode fs.FileMode) syscall.Errno { 231 if f.closed { 232 return syscall.EBADF 233 } 234 235 return platform.UnwrapOSError(f.file.Chmod(mode)) 236 } 237 238 // Chown implements the same method as documented on fsapi.File 239 func (f *osFile) Chown(uid, gid int) syscall.Errno { 240 if f.closed { 241 return syscall.EBADF 242 } 243 244 return fchown(f.fd, uid, gid) 245 } 246 247 // Utimens implements the same method as documented on fsapi.File 248 func (f *osFile) Utimens(times *[2]syscall.Timespec) syscall.Errno { 249 if f.closed { 250 return syscall.EBADF 251 } 252 253 err := futimens(f.fd, times) 254 return platform.UnwrapOSError(err) 255 } 256 257 // Close implements the same method as documented on fsapi.File 258 func (f *osFile) Close() syscall.Errno { 259 if f.closed { 260 return 0 261 } 262 f.closed = true 263 return f.close() 264 } 265 266 func (f *osFile) close() syscall.Errno { 267 return platform.UnwrapOSError(f.file.Close()) 268 }