github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/file.go (about) 1 package sysfs 2 3 import ( 4 "io" 5 "io/fs" 6 "os" 7 "syscall" 8 9 "github.com/tetratelabs/wazero/internal/fsapi" 10 "github.com/tetratelabs/wazero/internal/platform" 11 ) 12 13 func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) { 14 // Return constant stat, which has fake times, but keep the underlying 15 // file mode. Fake times are needed to pass wasi-testsuite. 16 // https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19 17 var mode fs.FileMode 18 if st, err := f.Stat(); err != nil { 19 return nil, err 20 } else { 21 mode = st.Mode() 22 } 23 var flag int 24 if stdin { 25 flag = syscall.O_RDONLY 26 } else { 27 flag = syscall.O_WRONLY 28 } 29 var file fsapi.File 30 if of, ok := f.(*os.File); ok { 31 // This is ok because functions that need path aren't used by stdioFile 32 file = newOsFile("", flag, 0, of) 33 } else { 34 file = &fsFile{file: f} 35 } 36 return &stdioFile{File: file, st: fsapi.Stat_t{Mode: mode, Nlink: 1}}, nil 37 } 38 39 func OpenFile(path string, flag int, perm fs.FileMode) (*os.File, syscall.Errno) { 40 if flag&fsapi.O_DIRECTORY != 0 && flag&(syscall.O_WRONLY|syscall.O_RDWR) != 0 { 41 return nil, syscall.EISDIR // invalid to open a directory writeable 42 } 43 return openFile(path, flag, perm) 44 } 45 46 func OpenOSFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) { 47 f, errno := OpenFile(path, flag, perm) 48 if errno != 0 { 49 return nil, errno 50 } 51 return newOsFile(path, flag, perm, f), 0 52 } 53 54 func OpenFSFile(fs fs.FS, path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) { 55 if flag&fsapi.O_DIRECTORY != 0 && flag&(syscall.O_WRONLY|syscall.O_RDWR) != 0 { 56 return nil, syscall.EISDIR // invalid to open a directory writeable 57 } 58 f, err := fs.Open(path) 59 if errno := platform.UnwrapOSError(err); errno != 0 { 60 return nil, errno 61 } 62 // Don't return an os.File because the path is not absolute. osFile needs 63 // the path to be real and certain fs.File impls are subrooted. 64 return &fsFile{fs: fs, name: path, file: f}, 0 65 } 66 67 type stdioFile struct { 68 fsapi.File 69 st fsapi.Stat_t 70 } 71 72 // IsDir implements File.IsDir 73 func (f *stdioFile) IsDir() (bool, syscall.Errno) { 74 return false, 0 75 } 76 77 // Stat implements File.Stat 78 func (f *stdioFile) Stat() (fsapi.Stat_t, syscall.Errno) { 79 return f.st, 0 80 } 81 82 // Close implements File.Close 83 func (f *stdioFile) Close() syscall.Errno { 84 return 0 85 } 86 87 // fsFile is used for wrapped os.File, like os.Stdin or any fs.File 88 // implementation. Notably, this does not have access to the full file path. 89 // so certain operations can't be supported, such as inode lookups on Windows. 90 type fsFile struct { 91 fsapi.UnimplementedFile 92 93 // fs is the file-system that opened the file, or nil when wrapped for 94 // pre-opens like stdio. 95 fs fs.FS 96 97 // name is what was used in fs for Open, so it may not be the actual path. 98 name string 99 100 // file is always set, possibly an os.File like os.Stdin. 101 file fs.File 102 103 // closed is true when closed was called. This ensures proper syscall.EBADF 104 closed bool 105 106 // cachedStat includes fields that won't change while a file is open. 107 cachedSt *cachedStat 108 } 109 110 type cachedStat struct { 111 // fileType is the same as what's documented on Dirent. 112 fileType fs.FileMode 113 114 // ino is the same as what's documented on Dirent. 115 ino uint64 116 } 117 118 // cachedStat returns the cacheable parts of platform.sys.Stat_t or an error if 119 // they couldn't be retrieved. 120 func (f *fsFile) cachedStat() (fileType fs.FileMode, ino uint64, errno syscall.Errno) { 121 if f.cachedSt == nil { 122 if _, errno = f.Stat(); errno != 0 { 123 return 124 } 125 } 126 return f.cachedSt.fileType, f.cachedSt.ino, 0 127 } 128 129 // Ino implements File.Ino 130 func (f *fsFile) Ino() (uint64, syscall.Errno) { 131 if _, ino, errno := f.cachedStat(); errno != 0 { 132 return 0, errno 133 } else { 134 return ino, 0 135 } 136 } 137 138 // IsAppend implements File.IsAppend 139 func (f *fsFile) IsAppend() bool { 140 return false 141 } 142 143 // SetAppend implements File.SetAppend 144 func (f *fsFile) SetAppend(bool) (errno syscall.Errno) { 145 return fileError(f, f.closed, syscall.ENOSYS) 146 } 147 148 // IsDir implements File.IsDir 149 func (f *fsFile) IsDir() (bool, syscall.Errno) { 150 if ft, _, errno := f.cachedStat(); errno != 0 { 151 return false, errno 152 } else if ft.Type() == fs.ModeDir { 153 return true, 0 154 } 155 return false, 0 156 } 157 158 // Stat implements File.Stat 159 func (f *fsFile) Stat() (st fsapi.Stat_t, errno syscall.Errno) { 160 if f.closed { 161 errno = syscall.EBADF 162 return 163 } 164 165 // While some functions in fsapi.File need the full path, especially in 166 // Windows, stat does not. Casting here allows os.DirFS to return inode 167 // information. 168 if of, ok := f.file.(*os.File); ok { 169 if st, errno = statFile(of); errno != 0 { 170 return 171 } 172 return f.cacheStat(st) 173 } else if t, err := f.file.Stat(); err != nil { 174 errno = platform.UnwrapOSError(err) 175 return 176 } else { 177 st = StatFromDefaultFileInfo(t) 178 return f.cacheStat(st) 179 } 180 } 181 182 func (f *fsFile) cacheStat(st fsapi.Stat_t) (fsapi.Stat_t, syscall.Errno) { 183 f.cachedSt = &cachedStat{fileType: st.Mode & fs.ModeType, ino: st.Ino} 184 return st, 0 185 } 186 187 // Read implements File.Read 188 func (f *fsFile) Read(buf []byte) (n int, errno syscall.Errno) { 189 if n, errno = read(f.file, buf); errno != 0 { 190 // Defer validation overhead until we've already had an error. 191 errno = fileError(f, f.closed, errno) 192 } 193 return 194 } 195 196 // Pread implements File.Pread 197 func (f *fsFile) Pread(buf []byte, off int64) (n int, errno syscall.Errno) { 198 if ra, ok := f.file.(io.ReaderAt); ok { 199 if n, errno = pread(ra, buf, off); errno != 0 { 200 // Defer validation overhead until we've already had an error. 201 errno = fileError(f, f.closed, errno) 202 } 203 return 204 } 205 206 // See /RATIONALE.md "fd_pread: io.Seeker fallback when io.ReaderAt is not supported" 207 if rs, ok := f.file.(io.ReadSeeker); ok { 208 // Determine the current position in the file, as we need to revert it. 209 currentOffset, err := rs.Seek(0, io.SeekCurrent) 210 if err != nil { 211 return 0, fileError(f, f.closed, platform.UnwrapOSError(err)) 212 } 213 214 // Put the read position back when complete. 215 defer func() { _, _ = rs.Seek(currentOffset, io.SeekStart) }() 216 217 // If the current offset isn't in sync with this reader, move it. 218 if off != currentOffset { 219 if _, err = rs.Seek(off, io.SeekStart); err != nil { 220 return 0, fileError(f, f.closed, platform.UnwrapOSError(err)) 221 } 222 } 223 224 n, err = rs.Read(buf) 225 if errno = platform.UnwrapOSError(err); errno != 0 { 226 // Defer validation overhead until we've already had an error. 227 errno = fileError(f, f.closed, errno) 228 } 229 } else { 230 errno = syscall.ENOSYS // unsupported 231 } 232 return 233 } 234 235 // Seek implements File.Seek. 236 func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno) { 237 // If this is a directory, and we're attempting to seek to position zero, 238 // we have to re-open the file to ensure the directory state is reset. 239 var isDir bool 240 if offset == 0 && whence == io.SeekStart { 241 if isDir, errno = f.IsDir(); errno != 0 { 242 return 243 } else if isDir { 244 return 0, f.reopen() 245 } 246 } 247 248 if s, ok := f.file.(io.Seeker); ok { 249 if newOffset, errno = seek(s, offset, whence); errno != 0 { 250 // Defer validation overhead until we've already had an error. 251 errno = fileError(f, f.closed, errno) 252 } 253 } else { 254 errno = syscall.ENOSYS // unsupported 255 } 256 return 257 } 258 259 func (f *fsFile) reopen() syscall.Errno { 260 _ = f.close() 261 var err error 262 f.file, err = f.fs.Open(f.name) 263 return platform.UnwrapOSError(err) 264 } 265 266 // Readdir implements File.Readdir. Notably, this uses fs.ReadDirFile if 267 // available. 268 func (f *fsFile) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) { 269 if of, ok := f.file.(*os.File); ok { 270 // We can't use f.name here because it is the path up to the fsapi.FS, 271 // not necessarily the real path. For this reason, Windows may not be 272 // able to populate inodes. However, Darwin and Linux will. 273 if dirents, errno = readdir(of, "", n); errno != 0 { 274 errno = adjustReaddirErr(f, f.closed, errno) 275 } 276 return 277 } 278 279 // Try with fs.ReadDirFile which is available on api.FS implementations 280 // like embed:fs. 281 if rdf, ok := f.file.(fs.ReadDirFile); ok { 282 entries, e := rdf.ReadDir(n) 283 if errno = adjustReaddirErr(f, f.closed, e); errno != 0 { 284 return 285 } 286 dirents = make([]fsapi.Dirent, 0, len(entries)) 287 for _, e := range entries { 288 // By default, we don't attempt to read inode data 289 dirents = append(dirents, fsapi.Dirent{Name: e.Name(), Type: e.Type()}) 290 } 291 } else { 292 errno = syscall.ENOTDIR 293 } 294 return 295 } 296 297 // Write implements File.Write 298 func (f *fsFile) Write(buf []byte) (n int, errno syscall.Errno) { 299 if w, ok := f.file.(io.Writer); ok { 300 if n, errno = write(w, buf); errno != 0 { 301 // Defer validation overhead until we've already had an error. 302 errno = fileError(f, f.closed, errno) 303 } 304 } else { 305 errno = syscall.ENOSYS // unsupported 306 } 307 return 308 } 309 310 // Pwrite implements File.Pwrite 311 func (f *fsFile) Pwrite(buf []byte, off int64) (n int, errno syscall.Errno) { 312 if wa, ok := f.file.(io.WriterAt); ok { 313 if n, errno = pwrite(wa, buf, off); errno != 0 { 314 // Defer validation overhead until we've already had an error. 315 errno = fileError(f, f.closed, errno) 316 } 317 } else { 318 errno = syscall.ENOSYS // unsupported 319 } 320 return 321 } 322 323 // Close implements File.Close 324 func (f *fsFile) Close() syscall.Errno { 325 if f.closed { 326 return 0 327 } 328 f.closed = true 329 return f.close() 330 } 331 332 func (f *fsFile) close() syscall.Errno { 333 return platform.UnwrapOSError(f.file.Close()) 334 } 335 336 // dirError is used for commands that work against a directory, but not a file. 337 func dirError(f fsapi.File, isClosed bool, errno syscall.Errno) syscall.Errno { 338 if vErrno := validate(f, isClosed, false, true); vErrno != 0 { 339 return vErrno 340 } 341 return errno 342 } 343 344 // fileError is used for commands that work against a file, but not a directory. 345 func fileError(f fsapi.File, isClosed bool, errno syscall.Errno) syscall.Errno { 346 if vErrno := validate(f, isClosed, true, false); vErrno != 0 { 347 return vErrno 348 } 349 return errno 350 } 351 352 // validate is used to making syscalls which will fail. 353 func validate(f fsapi.File, isClosed, wantFile, wantDir bool) syscall.Errno { 354 if isClosed { 355 return syscall.EBADF 356 } 357 358 isDir, errno := f.IsDir() 359 if errno != 0 { 360 return errno 361 } 362 363 if wantFile && isDir { 364 return syscall.EISDIR 365 } else if wantDir && !isDir { 366 return syscall.ENOTDIR 367 } 368 return 0 369 } 370 371 func read(r io.Reader, buf []byte) (n int, errno syscall.Errno) { 372 if len(buf) == 0 { 373 return 0, 0 // less overhead on zero-length reads. 374 } 375 376 n, err := r.Read(buf) 377 return n, platform.UnwrapOSError(err) 378 } 379 380 func pread(ra io.ReaderAt, buf []byte, off int64) (n int, errno syscall.Errno) { 381 if len(buf) == 0 { 382 return 0, 0 // less overhead on zero-length reads. 383 } 384 385 n, err := ra.ReadAt(buf, off) 386 return n, platform.UnwrapOSError(err) 387 } 388 389 func seek(s io.Seeker, offset int64, whence int) (int64, syscall.Errno) { 390 if uint(whence) > io.SeekEnd { 391 return 0, syscall.EINVAL // negative or exceeds the largest valid whence 392 } 393 394 newOffset, err := s.Seek(offset, whence) 395 return newOffset, platform.UnwrapOSError(err) 396 } 397 398 func readdir(f *os.File, path string, n int) (dirents []fsapi.Dirent, errno syscall.Errno) { 399 fis, e := f.Readdir(n) 400 if errno = platform.UnwrapOSError(e); errno != 0 { 401 return 402 } 403 404 dirents = make([]fsapi.Dirent, 0, len(fis)) 405 406 // linux/darwin won't have to fan out to lstat, but windows will. 407 var ino uint64 408 for fi := range fis { 409 t := fis[fi] 410 if ino, errno = inoFromFileInfo(path, t); errno != 0 { 411 return 412 } 413 dirents = append(dirents, fsapi.Dirent{Name: t.Name(), Ino: ino, Type: t.Mode().Type()}) 414 } 415 return 416 } 417 418 func write(w io.Writer, buf []byte) (n int, errno syscall.Errno) { 419 if len(buf) == 0 { 420 return 0, 0 // less overhead on zero-length writes. 421 } 422 423 n, err := w.Write(buf) 424 return n, platform.UnwrapOSError(err) 425 } 426 427 func pwrite(w io.WriterAt, buf []byte, off int64) (n int, errno syscall.Errno) { 428 if len(buf) == 0 { 429 return 0, 0 // less overhead on zero-length writes. 430 } 431 432 n, err := w.WriteAt(buf, off) 433 return n, platform.UnwrapOSError(err) 434 }