fs: extracts syscallfs.ReaderAtOffset for WASI and gojs (#1037)

This extracts a utility `syscallfs.ReaderAtOffset()` to allow WASI and
gojs to re-use the same logic to implement `syscall.Pread`.

What's different than before is that if WASI passes multiple iovecs an
emulated `ReaderAt` will seek to the read position on each call to
`Read` vs once per loop. This was a design decision to keep the call
sites compatible between files that implement ReaderAt and those that
emulate them with Seeker (e.g. avoid the need for a read-scoped closer/
defer function). The main use case for emulation is `embed.file`, whose
seek function is cheap, so there's little performance impact to this.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-01-15 17:30:45 -08:00
committed by GitHub
parent 2ce8988ab7
commit 713e187796
11 changed files with 400 additions and 74 deletions

View File

@@ -274,17 +274,13 @@ func syscallRead(mod api.Module, fd uint32, offset interface{}, p []byte) (n uin
err = syscall.EBADF
}
var reader io.Reader = f.File
if offset != nil {
if s, ok := f.File.(io.Seeker); ok {
if _, err := s.Seek(toInt64(offset), io.SeekStart); err != nil {
return 0, err
}
} else {
return 0, syscall.ENOTSUP
}
reader = syscallfs.ReaderAtOffset(f.File, toInt64(offset))
}
if nRead, e := f.File.Read(p); e == nil || e == io.EOF {
if nRead, e := reader.Read(p); e == nil || e == io.EOF {
// fs_js.go cannot parse io.EOF so coerce it to nil.
// See https://github.com/golang/go/issues/43913
n = uint32(nRead)
@@ -309,7 +305,7 @@ func (jsfsWrite) invoke(ctx context.Context, mod api.Module, args ...interface{}
}
offset := goos.ValueToUint32(args[2])
byteCount := goos.ValueToUint32(args[3])
fOffset := args[4] // nil unless Pread
fOffset := args[4] // nil unless Pwrite
callback := args[5].(funcWrapper)
if byteCount > 0 { // empty is possible on EOF

View File

@@ -1,7 +1,9 @@
package fs
import (
"bytes"
"fmt"
"io"
"log"
"os"
"syscall"
@@ -34,12 +36,35 @@ func testAdHoc() {
}
}
// Read the full contents of the file using io.Reader
b, err := os.ReadFile("/animals.txt")
if err != nil {
log.Panicln(err)
}
fmt.Println("contents:", string(b))
// Re-open the same file to test io.ReaderAt
f, err := os.Open("/animals.txt")
if err != nil {
log.Panicln(err)
}
defer f.Close()
// Seek to an arbitrary position.
if _, err = f.Seek(4, io.SeekStart); err != nil {
log.Panicln(err)
}
b1 := make([]byte, len(b))
// We expect to revert to the original position on ReadAt zero.
if _, err = f.ReadAt(b1, 0); err != nil {
log.Panicln(err)
}
if !bytes.Equal(b, b1) {
log.Panicln("unexpected ReadAt contents: ", string(b1))
}
b, err = os.ReadFile("/empty.txt")
if err != nil {
log.Panicln(err)