Source file src/internal/poll/fd_wasip1.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package poll
     6  
     7  import (
     8  	"internal/byteorder"
     9  	"sync/atomic"
    10  	"syscall"
    11  	"unsafe"
    12  )
    13  
    14  type SysFile struct {
    15  	// RefCountPtr is a pointer to the reference count of Sysfd.
    16  	//
    17  	// WASI preview 1 lacks a dup(2) system call. When the os and net packages
    18  	// need to share a file/socket, instead of duplicating the underlying file
    19  	// descriptor, we instead provide a way to copy FD instances and manage the
    20  	// underlying file descriptor with reference counting.
    21  	RefCountPtr *int32
    22  
    23  	// RefCount is the reference count of Sysfd. When a copy of an FD is made,
    24  	// it points to the reference count of the original FD instance.
    25  	RefCount int32
    26  
    27  	// Cache for the file type, lazily initialized when Seek is called.
    28  	Filetype uint32
    29  
    30  	// If the file represents a directory, this field contains the current
    31  	// readdir position. It is reset to zero if the program calls Seek(0, 0).
    32  	Dircookie uint64
    33  
    34  	// Absolute path of the file, as returned by syscall.PathOpen;
    35  	// this is used by Fchdir to emulate setting the current directory
    36  	// to an open file descriptor.
    37  	Path string
    38  
    39  	// TODO(achille): it could be meaningful to move isFile from FD to a method
    40  	// on this struct type, and expose it as `IsFile() bool` which derives the
    41  	// result from the Filetype field. We would need to ensure that Filetype is
    42  	// always set instead of being lazily initialized.
    43  }
    44  
    45  func (s *SysFile) init() {
    46  	if s.RefCountPtr == nil {
    47  		s.RefCount = 1
    48  		s.RefCountPtr = &s.RefCount
    49  	}
    50  }
    51  
    52  func (s *SysFile) ref() SysFile {
    53  	atomic.AddInt32(s.RefCountPtr, +1)
    54  	return SysFile{RefCountPtr: s.RefCountPtr}
    55  }
    56  
    57  func (s *SysFile) destroy(fd int) error {
    58  	if s.RefCountPtr != nil && atomic.AddInt32(s.RefCountPtr, -1) > 0 {
    59  		return nil
    60  	}
    61  
    62  	// We don't use ignoringEINTR here because POSIX does not define
    63  	// whether the descriptor is closed if close returns EINTR.
    64  	// If the descriptor is indeed closed, using a loop would race
    65  	// with some other goroutine opening a new descriptor.
    66  	// (The Linux kernel guarantees that it is closed on an EINTR error.)
    67  	return CloseFunc(fd)
    68  }
    69  
    70  // Copy creates a copy of the FD.
    71  //
    72  // The FD instance points to the same underlying file descriptor. The file
    73  // descriptor isn't closed until all FD instances that refer to it have been
    74  // closed/destroyed.
    75  func (fd *FD) Copy() FD {
    76  	return FD{
    77  		Sysfd:         fd.Sysfd,
    78  		SysFile:       fd.SysFile.ref(),
    79  		IsStream:      fd.IsStream,
    80  		ZeroReadIsEOF: fd.ZeroReadIsEOF,
    81  		isBlocking:    fd.isBlocking,
    82  		isFile:        fd.isFile,
    83  	}
    84  }
    85  
    86  // dupCloseOnExecOld always errors on wasip1 because there is no mechanism to
    87  // duplicate file descriptors.
    88  func dupCloseOnExecOld(fd int) (int, string, error) {
    89  	return -1, "dup", syscall.ENOSYS
    90  }
    91  
    92  // Fchdir wraps syscall.Fchdir.
    93  func (fd *FD) Fchdir() error {
    94  	if err := fd.incref(); err != nil {
    95  		return err
    96  	}
    97  	defer fd.decref()
    98  	return syscall.Chdir(fd.Path)
    99  }
   100  
   101  // ReadDir wraps syscall.ReadDir.
   102  // We treat this like an ordinary system call rather than a call
   103  // that tries to fill the buffer.
   104  func (fd *FD) ReadDir(buf []byte, cookie syscall.Dircookie) (int, error) {
   105  	if err := fd.incref(); err != nil {
   106  		return 0, err
   107  	}
   108  	defer fd.decref()
   109  	for {
   110  		n, err := syscall.ReadDir(fd.Sysfd, buf, cookie)
   111  		if err != nil {
   112  			n = 0
   113  			if err == syscall.EAGAIN && fd.pd.pollable() {
   114  				if err = fd.pd.waitRead(fd.isFile); err == nil {
   115  					continue
   116  				}
   117  			}
   118  		}
   119  		// Do not call eofError; caller does not expect to see io.EOF.
   120  		return n, err
   121  	}
   122  }
   123  
   124  func (fd *FD) ReadDirent(buf []byte) (int, error) {
   125  	n, err := fd.ReadDir(buf, fd.Dircookie)
   126  	if err != nil {
   127  		return 0, err
   128  	}
   129  	if n <= 0 {
   130  		return n, nil // EOF
   131  	}
   132  
   133  	// We assume that the caller of ReadDirent will consume the entire buffer
   134  	// up to the last full entry, so we scan through the buffer looking for the
   135  	// value of the last next cookie.
   136  	b := buf[:n]
   137  
   138  	for len(b) > 0 {
   139  		next, ok := direntNext(b)
   140  		if !ok {
   141  			break
   142  		}
   143  		size, ok := direntReclen(b)
   144  		if !ok {
   145  			break
   146  		}
   147  		if size > uint64(len(b)) {
   148  			break
   149  		}
   150  		fd.Dircookie = syscall.Dircookie(next)
   151  		b = b[size:]
   152  	}
   153  
   154  	// Trim a potentially incomplete trailing entry; this is necessary because
   155  	// the code in src/os/dir_unix.go does not deal well with partial values in
   156  	// calls to direntReclen, etc... and ends up causing an early EOF before all
   157  	// directory entries were consumed. ReadDirent is called with a large enough
   158  	// buffer (8 KiB) that at least one entry should always fit, tho this seems
   159  	// a bit brittle but cannot be addressed without a large change of the
   160  	// algorithm in the os.(*File).readdir method.
   161  	return n - len(b), nil
   162  }
   163  
   164  // Seek wraps syscall.Seek.
   165  func (fd *FD) Seek(offset int64, whence int) (int64, error) {
   166  	if err := fd.incref(); err != nil {
   167  		return 0, err
   168  	}
   169  	defer fd.decref()
   170  	// syscall.Filetype is a uint8 but we store it as a uint32 in SysFile in
   171  	// order to use atomic load/store on the field, which is why we have to
   172  	// perform this type conversion.
   173  	fileType := syscall.Filetype(atomic.LoadUint32(&fd.Filetype))
   174  
   175  	if fileType == syscall.FILETYPE_UNKNOWN {
   176  		var stat syscall.Stat_t
   177  		if err := fd.Fstat(&stat); err != nil {
   178  			return 0, err
   179  		}
   180  		fileType = stat.Filetype
   181  		atomic.StoreUint32(&fd.Filetype, uint32(fileType))
   182  	}
   183  
   184  	if fileType == syscall.FILETYPE_DIRECTORY {
   185  		// If the file descriptor is opened on a directory, we reset the readdir
   186  		// cookie when seeking back to the beginning to allow reusing the file
   187  		// descriptor to scan the directory again.
   188  		if offset == 0 && whence == 0 {
   189  			fd.Dircookie = 0
   190  			return 0, nil
   191  		} else {
   192  			return 0, syscall.EINVAL
   193  		}
   194  	}
   195  
   196  	return syscall.Seek(fd.Sysfd, offset, whence)
   197  }
   198  
   199  // https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-dirent-record
   200  const sizeOfDirent = 24
   201  
   202  func direntReclen(buf []byte) (uint64, bool) {
   203  	namelen, ok := direntNamlen(buf)
   204  	return sizeOfDirent + namelen, ok
   205  }
   206  
   207  func direntNamlen(buf []byte) (uint64, bool) {
   208  	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
   209  }
   210  
   211  func direntNext(buf []byte) (uint64, bool) {
   212  	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Next), unsafe.Sizeof(syscall.Dirent{}.Next))
   213  }
   214  
   215  // readInt returns the size-bytes unsigned integer in native byte order at offset off.
   216  func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
   217  	if len(b) < int(off+size) {
   218  		return 0, false
   219  	}
   220  	return readIntLE(b[off:], size), true
   221  }
   222  
   223  func readIntLE(b []byte, size uintptr) uint64 {
   224  	switch size {
   225  	case 1:
   226  		return uint64(b[0])
   227  	case 2:
   228  		return uint64(byteorder.LeUint16(b))
   229  	case 4:
   230  		return uint64(byteorder.LeUint32(b))
   231  	case 8:
   232  		return uint64(byteorder.LeUint64(b))
   233  	default:
   234  		panic("internal/poll: readInt with unsupported size")
   235  	}
   236  }
   237  

View as plain text