fd_wasip1.mx raw

   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 []byte
  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, []byte, 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