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