github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/internal/poll/fd_wasip1.go (about)

     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  	// Cache for the file type, lazily initialized when Seek is called.
    15  	Filetype uint32
    16  
    17  	// If the file represents a directory, this field contains the current
    18  	// readdir position. It is reset to zero if the program calls Seek(0, 0).
    19  	Dircookie uint64
    20  
    21  	// Absolute path of the file, as returned by syscall.PathOpen;
    22  	// this is used by Fchdir to emulate setting the current directory
    23  	// to an open file descriptor.
    24  	Path string
    25  
    26  	// TODO(achille): it could be meaningful to move isFile from FD to a method
    27  	// on this struct type, and expose it as `IsFile() bool` which derives the
    28  	// result from the Filetype field. We would need to ensure that Filetype is
    29  	// always set instead of being lazily initialized.
    30  }
    31  
    32  // dupCloseOnExecOld always errors on wasip1 because there is no mechanism to
    33  // duplicate file descriptors.
    34  func dupCloseOnExecOld(fd int) (int, string, error) {
    35  	return -1, "dup", syscall.ENOSYS
    36  }
    37  
    38  // Fchdir wraps syscall.Fchdir.
    39  func (fd *FD) Fchdir() error {
    40  	if err := fd.incref(); err != nil {
    41  		return err
    42  	}
    43  	defer fd.decref()
    44  	return syscall.Chdir(fd.Path)
    45  }
    46  
    47  // ReadDir wraps syscall.ReadDir.
    48  // We treat this like an ordinary system call rather than a call
    49  // that tries to fill the buffer.
    50  func (fd *FD) ReadDir(buf []byte, cookie syscall.Dircookie) (int, error) {
    51  	if err := fd.incref(); err != nil {
    52  		return 0, err
    53  	}
    54  	defer fd.decref()
    55  	for {
    56  		n, err := syscall.ReadDir(fd.Sysfd, buf, cookie)
    57  		if err != nil {
    58  			n = 0
    59  			if err == syscall.EAGAIN && fd.pd.pollable() {
    60  				if err = fd.pd.waitRead(fd.isFile); err == nil {
    61  					continue
    62  				}
    63  			}
    64  		}
    65  		// Do not call eofError; caller does not expect to see io.EOF.
    66  		return n, err
    67  	}
    68  }
    69  
    70  func (fd *FD) ReadDirent(buf []byte) (int, error) {
    71  	n, err := fd.ReadDir(buf, fd.Dircookie)
    72  	if err != nil {
    73  		return 0, err
    74  	}
    75  	if n <= 0 {
    76  		return n, nil // EOF
    77  	}
    78  
    79  	// We assume that the caller of ReadDirent will consume the entire buffer
    80  	// up to the last full entry, so we scan through the buffer looking for the
    81  	// value of the last next cookie.
    82  	b := buf[:n]
    83  
    84  	for len(b) > 0 {
    85  		next, ok := direntNext(b)
    86  		if !ok {
    87  			break
    88  		}
    89  		size, ok := direntReclen(b)
    90  		if !ok {
    91  			break
    92  		}
    93  		if size > uint64(len(b)) {
    94  			break
    95  		}
    96  		fd.Dircookie = syscall.Dircookie(next)
    97  		b = b[size:]
    98  	}
    99  
   100  	// Trim a potentially incomplete trailing entry; this is necessary because
   101  	// the code in src/os/dir_unix.go does not deal well with partial values in
   102  	// calls to direntReclen, etc... and ends up causing an early EOF before all
   103  	// directory entries were consumed. ReadDirent is called with a large enough
   104  	// buffer (8 KiB) that at least one entry should always fit, tho this seems
   105  	// a bit brittle but cannot be addressed without a large change of the
   106  	// algorithm in the os.(*File).readdir method.
   107  	return n - len(b), nil
   108  }
   109  
   110  // Seek wraps syscall.Seek.
   111  func (fd *FD) Seek(offset int64, whence int) (int64, error) {
   112  	if err := fd.incref(); err != nil {
   113  		return 0, err
   114  	}
   115  	defer fd.decref()
   116  	// syscall.Filetype is a uint8 but we store it as a uint32 in SysFile in
   117  	// order to use atomic load/store on the field, which is why we have to
   118  	// perform this type conversion.
   119  	fileType := syscall.Filetype(atomic.LoadUint32(&fd.Filetype))
   120  
   121  	if fileType == syscall.FILETYPE_UNKNOWN {
   122  		var stat syscall.Stat_t
   123  		if err := fd.Fstat(&stat); err != nil {
   124  			return 0, err
   125  		}
   126  		fileType = stat.Filetype
   127  		atomic.StoreUint32(&fd.Filetype, uint32(fileType))
   128  	}
   129  
   130  	if fileType == syscall.FILETYPE_DIRECTORY {
   131  		// If the file descriptor is opened on a directory, we reset the readdir
   132  		// cookie when seeking back to the beginning to allow reusing the file
   133  		// descriptor to scan the directory again.
   134  		if offset == 0 && whence == 0 {
   135  			fd.Dircookie = 0
   136  			return 0, nil
   137  		} else {
   138  			return 0, syscall.EINVAL
   139  		}
   140  	}
   141  
   142  	return syscall.Seek(fd.Sysfd, offset, whence)
   143  }
   144  
   145  // https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-dirent-record
   146  const sizeOfDirent = 24
   147  
   148  func direntReclen(buf []byte) (uint64, bool) {
   149  	namelen, ok := direntNamlen(buf)
   150  	return sizeOfDirent + namelen, ok
   151  }
   152  
   153  func direntNamlen(buf []byte) (uint64, bool) {
   154  	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
   155  }
   156  
   157  func direntNext(buf []byte) (uint64, bool) {
   158  	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Next), unsafe.Sizeof(syscall.Dirent{}.Next))
   159  }
   160  
   161  // readInt returns the size-bytes unsigned integer in native byte order at offset off.
   162  func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
   163  	if len(b) < int(off+size) {
   164  		return 0, false
   165  	}
   166  	return readIntLE(b[off:], size), true
   167  }
   168  
   169  func readIntLE(b []byte, size uintptr) uint64 {
   170  	switch size {
   171  	case 1:
   172  		return uint64(b[0])
   173  	case 2:
   174  		_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
   175  		return uint64(b[0]) | uint64(b[1])<<8
   176  	case 4:
   177  		_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
   178  		return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24
   179  	case 8:
   180  		_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
   181  		return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
   182  			uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
   183  	default:
   184  		panic("internal/poll: readInt with unsupported size")
   185  	}
   186  }