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  }