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 }