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

View as plain text