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  }