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.
14 // RefCountPtr is a pointer to the reference count of Sysfd.
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.
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.
26 // Cache for the file type, lazily initialized when Seek is called.
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).
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.
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.
44 func (s *SysFile) init() {
45 if s.RefCountPtr == nil {
47 s.RefCountPtr = &s.RefCount
51 func (s *SysFile) ref() SysFile {
52 atomic.AddInt32(s.RefCountPtr, +1)
53 return SysFile{RefCountPtr: s.RefCountPtr}
56 func (s *SysFile) destroy(fd int) error {
57 if s.RefCountPtr != nil && atomic.AddInt32(s.RefCountPtr, -1) > 0 {
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.)
69 // Copy creates a copy of the FD.
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
74 func (fd *FD) Copy() FD {
77 SysFile: fd.SysFile.ref(),
78 IsStream: fd.IsStream,
79 ZeroReadIsEOF: fd.ZeroReadIsEOF,
80 isBlocking: fd.isBlocking,
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
91 // Fchdir wraps syscall.Fchdir.
92 func (fd *FD) Fchdir() error {
93 if err := fd.incref(); err != nil {
97 return syscall.Chdir(fd.Path)
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 {
109 n, err := syscall.ReadDir(fd.Sysfd, buf, cookie)
112 if err == syscall.EAGAIN && fd.pd.pollable() {
113 if err = fd.pd.waitRead(fd.isFile); err == nil {
118 // Do not call eofError; caller does not expect to see io.EOF.
123 func (fd *FD) ReadDirent(buf []byte) (int, error) {
124 n, err := fd.ReadDir(buf, fd.Dircookie)
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.
138 next, ok := direntNext(b)
142 size, ok := direntReclen(b)
146 if size > uint64(len(b)) {
149 fd.Dircookie = syscall.Dircookie(next)
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
163 // Seek wraps syscall.Seek.
164 func (fd *FD) Seek(offset int64, whence int) (int64, error) {
165 if err := fd.incref(); err != nil {
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))
174 if fileType == syscall.FILETYPE_UNKNOWN {
175 var stat syscall.Stat_t
176 if err := fd.Fstat(&stat); err != nil {
179 fileType = stat.Filetype
180 atomic.StoreUint32(&fd.Filetype, uint32(fileType))
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 {
191 return 0, syscall.EINVAL
195 return syscall.Seek(fd.Sysfd, offset, whence)
198 // https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-dirent-record
199 const sizeOfDirent = 24
201 func direntReclen(buf []byte) (uint64, bool) {
202 namelen, ok := direntNamlen(buf)
203 return sizeOfDirent + namelen, ok
206 func direntNamlen(buf []byte) (uint64, bool) {
207 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
210 func direntNext(buf []byte) (uint64, bool) {
211 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Next), unsafe.Sizeof(syscall.Dirent{}.Next))
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) {
219 return readIntLE(b[off:], size), true
222 func readIntLE(b []byte, size uintptr) uint64 {
227 _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
228 return uint64(b[0]) | uint64(b[1])<<8
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
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
237 panic("internal/poll: readInt with unsupported size")