github.com/bir3/gocompiler@v0.9.2202/src/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 // 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 }