]> Cypherpunks.ru repositories - gostls13.git/blob - src/internal/poll/fd_wasip1.go
net: implement wasip1 FileListener and FileConn
[gostls13.git] / src / internal / poll / fd_wasip1.go
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 }