1 package sys
2
3 import (
4 "io"
5 "io/fs"
6 "net"
7
8 "github.com/tetratelabs/wazero/experimental/sys"
9 "github.com/tetratelabs/wazero/internal/descriptor"
10 "github.com/tetratelabs/wazero/internal/fsapi"
11 socketapi "github.com/tetratelabs/wazero/internal/sock"
12 "github.com/tetratelabs/wazero/internal/sysfs"
13 )
14
15 const (
16 FdStdin int32 = iota
17 FdStdout
18 FdStderr
19
20
21
22
23
24
25
26
27
28
29
30 FdPreopen
31 )
32
33 const modeDevice = fs.ModeDevice | 0o640
34
35
36 type FileEntry struct {
37
38
39
40
41
42
43
44
45 Name string
46
47
48 IsPreopen bool
49
50
51 FS sys.FS
52
53
54 File fsapi.File
55
56
57 direntCache *DirentCache
58 }
59
60
61
62
63
64
65
66
67
68
69
70
71
72 func (f *FileEntry) DirentCache() (*DirentCache, sys.Errno) {
73 if dir := f.direntCache; dir != nil {
74 return dir, 0
75 }
76
77
78 if isDir, errno := f.File.IsDir(); errno != 0 {
79 return nil, errno
80 } else if !isDir {
81 return nil, sys.ENOTDIR
82 }
83
84
85 if dotEntries, errno := synthesizeDotEntries(f); errno != 0 {
86 return nil, errno
87 } else {
88 f.direntCache = &DirentCache{f: f.File, dotEntries: dotEntries}
89 }
90
91 return f.direntCache, 0
92 }
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 type DirentCache struct {
112
113 f sys.File
114
115
116
117 dotEntries []sys.Dirent
118
119
120
121
122
123 dirents []sys.Dirent
124
125
126 countRead uint64
127
128
129
130
131 eof bool
132 }
133
134
135 func synthesizeDotEntries(f *FileEntry) ([]sys.Dirent, sys.Errno) {
136 dotIno, errno := f.File.Ino()
137 if errno != 0 {
138 return nil, errno
139 }
140 result := [2]sys.Dirent{}
141 result[0] = sys.Dirent{Name: ".", Ino: dotIno, Type: fs.ModeDir}
142
143
144 result[1] = sys.Dirent{Name: "..", Ino: 0, Type: fs.ModeDir}
145 return result[:], 0
146 }
147
148
149 var exhaustedDirents = [0]sys.Dirent{}
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 func (d *DirentCache) Read(pos uint64, n uint32) (dirents []sys.Dirent, errno sys.Errno) {
168 switch {
169 case pos > d.countRead:
170 return nil, sys.ENOENT
171 case pos == 0 && d.dirents != nil:
172
173
174 if _, errno = d.f.Seek(0, io.SeekStart); errno != 0 {
175 return
176 }
177 d.dirents = nil
178 d.countRead = 0
179 }
180
181 if n == 0 {
182 return
183 }
184
185 if d.dirents == nil {
186
187 d.dirents = d.dotEntries
188 d.countRead = 2
189 d.eof = false
190
191 if countToRead := int(n - 2); countToRead <= 0 {
192 return
193 } else if dirents, errno = d.f.Readdir(countToRead); errno != 0 {
194 return
195 } else if countRead := len(dirents); countRead > 0 {
196 d.eof = countRead < countToRead
197 d.dirents = append(d.dotEntries, dirents...)
198 d.countRead += uint64(countRead)
199 }
200
201 return d.cachedDirents(n), 0
202 }
203
204
205 cacheStart := d.countRead - uint64(len(d.dirents))
206 if pos < cacheStart {
207
208
209
210
211
212 errno = sys.ENOENT
213 return
214 } else if posInCache := pos - cacheStart; posInCache != 0 {
215 if uint64(len(d.dirents)) == posInCache {
216
217 d.dirents = exhaustedDirents[:]
218 } else {
219 d.dirents = d.dirents[posInCache:]
220 }
221 }
222
223
224 if countToRead := int(n) - len(d.dirents); countToRead > 0 && !d.eof {
225
226 if dirents, errno = d.f.Readdir(countToRead); errno != 0 {
227 return
228 }
229
230
231 if countRead := len(dirents); countRead > 0 {
232 d.eof = countRead < countToRead
233 d.dirents = append(d.dirents, dirents...)
234 d.countRead += uint64(countRead)
235 }
236 }
237
238 return d.cachedDirents(n), 0
239 }
240
241
242 func (d *DirentCache) cachedDirents(n uint32) []sys.Dirent {
243 direntCount := uint32(len(d.dirents))
244 switch {
245 case direntCount == 0:
246 return nil
247 case direntCount > n:
248 return d.dirents[:n]
249 }
250 return d.dirents
251 }
252
253 type FSContext struct {
254
255 rootFS sys.FS
256
257
258
259
260 openedFiles FileTable
261 }
262
263
264
265 type FileTable = descriptor.Table[int32, *FileEntry]
266
267
268
269
270
271
272 func (c *FSContext) RootFS() sys.FS {
273 if rootFS := c.rootFS; rootFS == nil {
274 return sys.UnimplementedFS{}
275 } else {
276 return rootFS
277 }
278 }
279
280
281 func (c *FSContext) LookupFile(fd int32) (*FileEntry, bool) {
282 return c.openedFiles.Lookup(fd)
283 }
284
285
286
287 func (c *FSContext) OpenFile(fs sys.FS, path string, flag sys.Oflag, perm fs.FileMode) (int32, sys.Errno) {
288 if f, errno := fs.OpenFile(path, flag, perm); errno != 0 {
289 return 0, errno
290 } else {
291 fe := &FileEntry{FS: fs, File: fsapi.Adapt(f)}
292 if path == "/" || path == "." {
293 fe.Name = ""
294 } else {
295 fe.Name = path
296 }
297 if newFD, ok := c.openedFiles.Insert(fe); !ok {
298 return 0, sys.EBADF
299 } else {
300 return newFD, 0
301 }
302 }
303 }
304
305
306 func (c *FSContext) Renumber(from, to int32) sys.Errno {
307 fromFile, ok := c.openedFiles.Lookup(from)
308 if !ok || to < 0 {
309 return sys.EBADF
310 } else if fromFile.IsPreopen {
311 return sys.ENOTSUP
312 }
313
314
315
316
317
318
319 if toFile, ok := c.openedFiles.Lookup(to); ok {
320 if toFile.IsPreopen {
321 return sys.ENOTSUP
322 }
323 _ = toFile.File.Close()
324 }
325
326 c.openedFiles.Delete(from)
327 if !c.openedFiles.InsertAt(fromFile, to) {
328 return sys.EBADF
329 }
330 return 0
331 }
332
333
334
335 func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, sys.Errno) {
336 var sock socketapi.TCPSock
337 if e, ok := c.LookupFile(sockFD); !ok || !e.IsPreopen {
338 return 0, sys.EBADF
339 } else if sock, ok = e.File.(socketapi.TCPSock); !ok {
340 return 0, sys.EBADF
341 }
342
343 conn, errno := sock.Accept()
344 if errno != 0 {
345 return 0, errno
346 }
347
348 fe := &FileEntry{File: fsapi.Adapt(conn)}
349
350 if nonblock {
351 if errno = fe.File.SetNonblock(true); errno != 0 {
352 _ = conn.Close()
353 return 0, errno
354 }
355 }
356
357 if newFD, ok := c.openedFiles.Insert(fe); !ok {
358 return 0, sys.EBADF
359 } else {
360 return newFD, 0
361 }
362 }
363
364
365 func (c *FSContext) CloseFile(fd int32) (errno sys.Errno) {
366 f, ok := c.openedFiles.Lookup(fd)
367 if !ok {
368 return sys.EBADF
369 }
370 if errno = f.File.Close(); errno != 0 {
371 return errno
372 }
373 c.openedFiles.Delete(fd)
374 return errno
375 }
376
377
378 func (c *FSContext) Close() (err error) {
379
380 c.openedFiles.Range(func(fd int32, entry *FileEntry) bool {
381 if errno := entry.File.Close(); errno != 0 {
382 err = errno
383 }
384 return true
385 })
386
387 c.openedFiles = FileTable{}
388 return
389 }
390
391
392
393 func (c *Context) InitFSContext(
394 stdin io.Reader,
395 stdout, stderr io.Writer,
396 fs []sys.FS, guestPaths []string,
397 tcpListeners []*net.TCPListener,
398 ) (err error) {
399 inFile, err := stdinFileEntry(stdin)
400 if err != nil {
401 return err
402 }
403 c.fsc.openedFiles.Insert(inFile)
404 outWriter, err := stdioWriterFileEntry("stdout", stdout)
405 if err != nil {
406 return err
407 }
408 c.fsc.openedFiles.Insert(outWriter)
409 errWriter, err := stdioWriterFileEntry("stderr", stderr)
410 if err != nil {
411 return err
412 }
413 c.fsc.openedFiles.Insert(errWriter)
414
415 for i, fs := range fs {
416 guestPath := guestPaths[i]
417
418 if StripPrefixesAndTrailingSlash(guestPath) == "" {
419
420 guestPath = "/"
421 c.fsc.rootFS = fs
422 }
423 c.fsc.openedFiles.Insert(&FileEntry{
424 FS: fs,
425 Name: guestPath,
426 IsPreopen: true,
427 File: &lazyDir{fs: fs},
428 })
429 }
430
431 for _, tl := range tcpListeners {
432 c.fsc.openedFiles.Insert(&FileEntry{IsPreopen: true, File: fsapi.Adapt(sysfs.NewTCPListenerFile(tl))})
433 }
434 return nil
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448 func StripPrefixesAndTrailingSlash(path string) string {
449
450 pathLen := len(path)
451 for ; pathLen > 0 && path[pathLen-1] == '/'; pathLen-- {
452 }
453
454 pathI := 0
455 loop:
456 for pathI < pathLen {
457 switch path[pathI] {
458 case '/':
459 pathI++
460 case '.':
461 nextI := pathI + 1
462 if nextI < pathLen && path[nextI] == '/' {
463 pathI = nextI + 1
464 } else if nextI == pathLen {
465 pathI = nextI
466 } else {
467 break loop
468 }
469 default:
470 break loop
471 }
472 }
473 return path[pathI:pathLen]
474 }
475
View as plain text