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