github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/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/bananabytelabs/wazero/experimental/sys"
    10  	"github.com/bananabytelabs/wazero/internal/fsapi"
    11  	"github.com/bananabytelabs/wazero/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  }