Files
wazero/internal/gojs/fs.go
Crypt Keeper 83e4b66659 Consolidates internal code to syscallfs (#1003)
This consolidates internal code to syscallfs, which removes the fs.FS
specific path rules, except when adapting one to syscallfs. For example,
this allows the underlying filesystem to decide if relative paths are
supported or not, as well any EINVAL related concerns.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-01-04 13:53:53 +08:00

743 lines
20 KiB
Go

package gojs
import (
"context"
"fmt"
"io"
"io/fs"
"os"
"syscall"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/custom"
"github.com/tetratelabs/wazero/internal/gojs/goos"
"github.com/tetratelabs/wazero/internal/platform"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/syscallfs"
"github.com/tetratelabs/wazero/internal/wasm"
)
var (
// jsfs = js.Global().Get("fs") // fs_js.go init
//
// js.fsCall conventions:
// * funcWrapper callback is the last parameter
// * arg0 is error and up to one result in arg1
jsfs = newJsVal(goos.RefJsfs, custom.NameFs).
addProperties(map[string]interface{}{
"constants": jsfsConstants, // = jsfs.Get("constants") // init
}).
addFunction(custom.NameFsOpen, jsfsOpen{}).
addFunction(custom.NameFsStat, jsfsStat{}).
addFunction(custom.NameFsFstat, jsfsFstat{}).
addFunction(custom.NameFsLstat, jsfsLstat{}).
addFunction(custom.NameFsClose, jsfsClose{}).
addFunction(custom.NameFsRead, jsfsRead{}).
addFunction(custom.NameFsWrite, jsfsWrite{}).
addFunction(custom.NameFsReaddir, jsfsReaddir{}).
addFunction(custom.NameFsMkdir, jsfsMkdir{}).
addFunction(custom.NameFsRmdir, jsfsRmdir{}).
addFunction(custom.NameFsRename, jsfsRename{}).
addFunction(custom.NameFsUnlink, jsfsUnlink{}).
addFunction(custom.NameFsUtimes, jsfsUtimes{}).
addFunction(custom.NameFsChmod, jsfsChmod{}).
addFunction(custom.NameFsFchmod, jsfsFchmod{}).
addFunction(custom.NameFsChown, jsfsChown{}).
addFunction(custom.NameFsFchown, jsfsFchown{}).
addFunction(custom.NameFsLchown, jsfsLchown{}).
addFunction(custom.NameFsTruncate, jsfsTruncate{}).
addFunction(custom.NameFsFtruncate, jsfsFtruncate{}).
addFunction(custom.NameFsReadlink, jsfsReadlink{}).
addFunction(custom.NameFsLink, jsfsLink{}).
addFunction(custom.NameFsSymlink, jsfsSymlink{}).
addFunction(custom.NameFsFsync, jsfsFsync{})
// jsfsConstants = jsfs Get("constants") // fs_js.go init
jsfsConstants = newJsVal(goos.RefJsfsConstants, "constants").
addProperties(map[string]interface{}{
"O_WRONLY": oWRONLY,
"O_RDWR": oRDWR,
"O_CREAT": oCREAT,
"O_TRUNC": oTRUNC,
"O_APPEND": oAPPEND,
"O_EXCL": oEXCL,
})
// oWRONLY = jsfsConstants Get("O_WRONLY").Int() // fs_js.go init
oWRONLY = float64(os.O_WRONLY)
// oRDWR = jsfsConstants Get("O_RDWR").Int() // fs_js.go init
oRDWR = float64(os.O_RDWR)
// o CREAT = jsfsConstants Get("O_CREAT").Int() // fs_js.go init
oCREAT = float64(os.O_CREATE)
// oTRUNC = jsfsConstants Get("O_TRUNC").Int() // fs_js.go init
oTRUNC = float64(os.O_TRUNC)
// oAPPEND = jsfsConstants Get("O_APPEND").Int() // fs_js.go init
oAPPEND = float64(os.O_APPEND)
// oEXCL = jsfsConstants Get("O_EXCL").Int() // fs_js.go init
oEXCL = float64(os.O_EXCL)
)
// jsfsOpen implements implements jsFn for syscall.Open
//
// jsFD /* Int */, err := fsCall("open", path, flags, perm)
type jsfsOpen struct{}
func (jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
flags := toUint64(args[1]) // flags are derived from constants like oWRONLY
perm := toUint32(args[2])
callback := args[3].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()
fd, err := fsc.OpenFile(path, int(flags), fs.FileMode(perm))
return callback.invoke(ctx, mod, goos.RefJsfs, err, fd) // note: error first
}
// jsfsStat implements jsFn for syscall.Stat
//
// jsSt, err := fsCall("stat", path)
type jsfsStat struct{}
func (jsfsStat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
callback := args[1].(funcWrapper)
stat, err := syscallStat(mod, path)
return callback.invoke(ctx, mod, goos.RefJsfs, err, stat) // note: error first
}
// syscallStat is like syscall.Stat
func syscallStat(mod api.Module, path string) (*jsSt, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
if stat, err := syscallfs.StatPath(fsc.FS(), path); err != nil {
return nil, err
} else {
return newJsSt(stat), nil
}
}
// jsfsLstat implements jsFn for syscall.Lstat
//
// jsSt, err := fsCall("lstat", path)
type jsfsLstat struct{}
func (jsfsLstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
callback := args[1].(funcWrapper)
lstat, err := syscallStat(mod, path) // TODO switch to lstat syscall
return callback.invoke(ctx, mod, goos.RefJsfs, err, lstat) // note: error first
}
// jsfsFstat implements jsFn for syscall.Open
//
// stat, err := fsCall("fstat", fd); err == nil && stat.Call("isDirectory").Bool()
type jsfsFstat struct{}
func (jsfsFstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := toUint32(args[0])
callback := args[1].(funcWrapper)
fstat, err := syscallFstat(fsc, fd)
return callback.invoke(ctx, mod, goos.RefJsfs, err, fstat) // note: error first
}
// mode constants from syscall_js.go
const (
S_IFSOCK = uint32(0o000140000)
S_IFLNK = uint32(0o000120000)
S_IFREG = uint32(0o000100000)
S_IFBLK = uint32(0o000060000)
S_IFDIR = uint32(0o000040000)
S_IFCHR = uint32(0o000020000)
S_IFIFO = uint32(0o000010000)
S_ISUID = uint32(0o004000)
S_ISGID = uint32(0o002000)
S_ISVTX = uint32(0o001000)
)
// syscallFstat is like syscall.Fstat
func syscallFstat(fsc *internalsys.FSContext, fd uint32) (*jsSt, error) {
stat, err := internalsys.StatFile(fsc, fd)
if err != nil {
return nil, err
}
return newJsSt(stat), nil
}
func newJsSt(stat fs.FileInfo) *jsSt {
ret := &jsSt{}
ret.isDir = stat.IsDir()
ret.mode = getJsMode(stat.Mode())
ret.size = stat.Size()
atimeNsec, mtimeNsec, ctimeNsec := platform.StatTimes(stat)
ret.atimeMs = atimeNsec / 1e6
ret.mtimeMs = mtimeNsec / 1e6
ret.ctimeMs = ctimeNsec / 1e6
return ret
}
// getJsMode is required because the mode property read in `GOOS=js` is
// incompatible with normal go. Particularly the directory flag isn't the same.
func getJsMode(mode fs.FileMode) (jsMode uint32) {
jsMode = uint32(mode & fs.ModePerm)
switch mode & fs.ModeType {
case fs.ModeDir:
jsMode |= S_IFDIR
case fs.ModeSymlink:
jsMode |= S_IFLNK
case fs.ModeNamedPipe:
jsMode |= S_IFIFO
case fs.ModeSocket:
jsMode |= S_IFSOCK
case fs.ModeDevice:
jsMode |= S_IFBLK
case fs.ModeCharDevice:
jsMode |= S_IFCHR
case fs.ModeIrregular:
// unmapped to js
}
if mode&fs.ModeType == 0 {
jsMode |= S_IFREG
}
if mode&fs.ModeSetgid != 0 {
jsMode |= S_ISGID
}
if mode&fs.ModeSetuid != 0 {
jsMode |= S_ISUID
}
if mode&fs.ModeSticky != 0 {
jsMode |= S_ISVTX
}
return
}
// jsfsClose implements jsFn for syscall.Close
type jsfsClose struct{}
func (jsfsClose) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
fd := toUint32(args[0])
callback := args[1].(funcWrapper)
err := fsc.CloseFile(fd)
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsRead implements jsFn for syscall.Read and syscall.Pread, called by
// src/internal/poll/fd_unix.go poll.Read.
//
// n, err := fsCall("read", fd, buf, 0, len(b), nil)
type jsfsRead struct{}
func (jsfsRead) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fd := toUint32(args[0])
buf, ok := args[1].(*byteArray)
if !ok {
return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1])
}
offset := toUint32(args[2])
byteCount := toUint32(args[3])
fOffset := args[4] // nil unless Pread
callback := args[5].(funcWrapper)
n, err := syscallRead(mod, fd, fOffset, buf.slice[offset:offset+byteCount])
return callback.invoke(ctx, mod, goos.RefJsfs, err, n) // note: error first
}
// syscallRead is like syscall.Read
func syscallRead(mod api.Module, fd uint32, offset interface{}, p []byte) (n uint32, err error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
f, ok := fsc.LookupFile(fd)
if !ok {
err = syscall.EBADF
}
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
}
}
if nRead, e := f.File.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)
} else {
err = e
}
return
}
// jsfsWrite implements jsFn for syscall.Write and syscall.Pwrite.
//
// Notably, offset is non-nil in Pwrite.
//
// n, err := fsCall("write", fd, buf, 0, len(b), nil)
type jsfsWrite struct{}
func (jsfsWrite) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fd := toUint32(args[0])
buf, ok := args[1].(*byteArray)
if !ok {
return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1])
}
offset := toUint32(args[2])
byteCount := toUint32(args[3])
fOffset := args[4] // nil unless Pread
callback := args[5].(funcWrapper)
if byteCount > 0 { // empty is possible on EOF
n, err := syscallWrite(mod, fd, fOffset, buf.slice[offset:offset+byteCount])
return callback.invoke(ctx, mod, goos.RefJsfs, err, n) // note: error first
}
return callback.invoke(ctx, mod, goos.RefJsfs, nil, goos.RefValueZero)
}
// syscallWrite is like syscall.Write
func syscallWrite(mod api.Module, fd uint32, offset interface{}, p []byte) (n uint32, err error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
if writer := internalsys.WriterForFile(fsc, fd); writer == nil {
err = syscall.EBADF
} else if nWritten, e := writer.Write(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(nWritten)
} else {
err = e
}
return
}
// jsfsReaddir implements jsFn for syscall.Open
//
// dir, err := fsCall("readdir", path)
// dir.Length(), dir.Index(i).String()
type jsfsReaddir struct{}
func (jsfsReaddir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
callback := args[1].(funcWrapper)
stat, err := syscallReaddir(ctx, mod, path)
return callback.invoke(ctx, mod, goos.RefJsfs, err, stat) // note: error first
}
func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArray, error) {
fsc := mod.(*wasm.CallContext).Sys.FS()
// don't allocate a file descriptor
f, err := fsc.FS().OpenFile(name, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
defer f.Close() //nolint
if d, ok := f.(fs.ReadDirFile); !ok {
return nil, syscall.ENOTDIR
} else if l, err := d.ReadDir(-1); err != nil {
return nil, err
} else {
entries := make([]interface{}, 0, len(l))
for _, e := range l {
entries = append(entries, e.Name())
}
return &objectArray{entries}, nil
}
}
// returnZero implements jsFn
type returnZero struct{}
func (returnZero) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) {
return goos.RefValueZero, nil
}
// returnSliceOfZero implements jsFn
type returnSliceOfZero struct{}
func (returnSliceOfZero) invoke(context.Context, api.Module, ...interface{}) (interface{}, error) {
return &objectArray{slice: []interface{}{goos.RefValueZero}}, nil
}
// returnArg0 implements jsFn
type returnArg0 struct{}
func (returnArg0) invoke(_ context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
return args[0], nil
}
// processCwd implements jsFn for fs.Open syscall.Getcwd in fs_js.go
type processCwd struct{}
func (processCwd) invoke(ctx context.Context, _ api.Module, _ ...interface{}) (interface{}, error) {
return getState(ctx).cwd, nil
}
// processChdir implements jsFn for fs.Open syscall.Chdir in fs_js.go
type processChdir struct{}
func (processChdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
if s, err := syscallStat(mod, path); err != nil {
return nil, mapJSError(err)
} else if !s.isDir {
return nil, syscall.ENOTDIR
} else {
getState(ctx).cwd = path
return nil, nil
}
}
// jsfsMkdir implements implements jsFn for fs.Mkdir
//
// jsFD /* Int */, err := fsCall("mkdir", path, perm)
type jsfsMkdir struct{}
func (jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
perm := toUint32(args[1])
callback := args[2].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()
var fd uint32
var err error
if err = fsc.FS().Mkdir(path, fs.FileMode(perm)); err == nil {
fd, err = fsc.OpenFile(path, os.O_RDONLY, 0)
}
return callback.invoke(ctx, mod, goos.RefJsfs, err, fd) // note: error first
}
// jsfsRmdir implements jsFn for the following
//
// _, err := fsCall("rmdir", path) // syscall.Rmdir
type jsfsRmdir struct{}
func (jsfsRmdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
callback := args[1].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()
err := fsc.FS().Rmdir(path)
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsRename implements jsFn for the following
//
// _, err := fsCall("rename", from, to) // syscall.Rename
type jsfsRename struct{}
func (jsfsRename) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
from := args[0].(string)
to := args[1].(string)
callback := args[2].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()
err := fsc.FS().Rename(from, to)
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsUnlink implements jsFn for the following
//
// _, err := fsCall("unlink", path) // syscall.Unlink
type jsfsUnlink struct{}
func (jsfsUnlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
callback := args[1].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()
err := fsc.FS().Unlink(path)
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsUtimes implements jsFn for the following
//
// _, err := fsCall("utimes", path, atime, mtime) // syscall.UtimesNano
type jsfsUtimes struct{}
func (jsfsUtimes) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
atimeSec := toInt64(args[1])
mtimeSec := toInt64(args[2])
callback := args[3].(funcWrapper)
fsc := mod.(*wasm.CallContext).Sys.FS()
err := fsc.FS().Utimes(path, atimeSec*1e9, mtimeSec*1e9)
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsChmod implements jsFn for the following
//
// _, err := fsCall("chmod", path, mode) // syscall.Chmod
type jsfsChmod struct{}
func (jsfsChmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
mode := toUint32(args[1])
callback := args[2].(funcWrapper)
_, _ = path, mode // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsFchmod implements jsFn for the following
//
// _, err := fsCall("fchmod", fd, mode) // syscall.Fchmod
type jsfsFchmod struct{}
func (jsfsFchmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fd := toUint32(args[0])
mode := toUint32(args[1])
callback := args[2].(funcWrapper)
_, _ = fd, mode // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsChown implements jsFn for the following
//
// _, err := fsCall("chown", path, uint32(uid), uint32(gid)) // syscall.Chown
type jsfsChown struct{}
func (jsfsChown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
uid := toUint32(args[1])
gid := toUint32(args[2])
callback := args[3].(funcWrapper)
_, _, _ = path, uid, gid // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsFchown implements jsFn for the following
//
// _, err := fsCall("fchown", fd, uint32(uid), uint32(gid)) // syscall.Fchown
type jsfsFchown struct{}
func (jsfsFchown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fd := toUint32(args[0])
uid := toUint32(args[1])
gid := toUint32(args[2])
callback := args[3].(funcWrapper)
_, _, _ = fd, uid, gid // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsLchown implements jsFn for the following
//
// _, err := fsCall("lchown", path, uint32(uid), uint32(gid)) // syscall.Lchown
type jsfsLchown struct{}
func (jsfsLchown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
uid := toUint32(args[1])
gid := toUint32(args[2])
callback := args[3].(funcWrapper)
_, _, _ = path, uid, gid // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsTruncate implements jsFn for the following
//
// _, err := fsCall("truncate", path, length) // syscall.Truncate
type jsfsTruncate struct{}
func (jsfsTruncate) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
length := toInt64(args[1])
callback := args[2].(funcWrapper)
_, _ = path, length // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsFtruncate implements jsFn for the following
//
// _, err := fsCall("ftruncate", fd, length) // syscall.Ftruncate
type jsfsFtruncate struct{}
func (jsfsFtruncate) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fd := toUint32(args[0])
length := toInt64(args[1])
callback := args[2].(funcWrapper)
_, _ = fd, length // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsReadlink implements jsFn for syscall.Readlink
//
// dst, err := fsCall("readlink", path) // syscall.Readlink
type jsfsReadlink struct{}
func (jsfsReadlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
callback := args[1].(funcWrapper)
_ = path // TODO
var dst string
var err error = syscall.ENOSYS
return callback.invoke(ctx, mod, goos.RefJsfs, err, dst) // note: error first
}
// jsfsLink implements jsFn for the following
//
// _, err := fsCall("link", path, link) // syscall.Link
type jsfsLink struct{}
func (jsfsLink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
link := args[1].(string)
callback := args[2].(funcWrapper)
_, _ = path, link // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsSymlink implements jsFn for the following
//
// _, err := fsCall("symlink", path, link) // syscall.Symlink
type jsfsSymlink struct{}
func (jsfsSymlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
path := args[0].(string)
link := args[1].(string)
callback := args[2].(funcWrapper)
_, _ = path, link // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsfsFsync implements jsFn for the following
//
// _, err := fsCall("fsync", fd) // syscall.Fsync
type jsfsFsync struct{}
func (jsfsFsync) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
fd := toUint32(args[0])
callback := args[1].(funcWrapper)
_ = fd // TODO
var err error = syscall.ENOSYS
return jsfsInvoke(ctx, mod, callback, err)
}
// jsSt is pre-parsed from fs_js.go setStat to avoid thrashing
type jsSt struct {
isDir bool
dev int64
ino uint64
mode uint32
nlink uint32
uid uint32
gid uint32
rdev int64
size int64
blksize int32
blocks int32
atimeMs int64
mtimeMs int64
ctimeMs int64
}
// String implements fmt.Stringer
func (s *jsSt) String() string {
return fmt.Sprintf("{isDir=%v,mode=%s,size=%d,mtimeMs=%d}", s.isDir, fs.FileMode(s.mode), s.size, s.mtimeMs)
}
// get implements jsGet.get
func (s *jsSt) get(_ context.Context, propertyKey string) interface{} {
switch propertyKey {
case "dev":
return s.dev
case "ino":
return s.ino
case "mode":
return s.mode
case "nlink":
return s.nlink
case "uid":
return s.uid
case "gid":
return s.gid
case "rdev":
return s.rdev
case "size":
return s.size
case "blksize":
return s.blksize
case "blocks":
return s.blocks
case "atimeMs":
return s.atimeMs
case "mtimeMs":
return s.mtimeMs
case "ctimeMs":
return s.ctimeMs
}
panic(fmt.Sprintf("TODO: stat.%s", propertyKey))
}
// call implements jsCall.call
func (s *jsSt) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) {
if method == "isDirectory" {
return s.isDir, nil
}
panic(fmt.Sprintf("TODO: stat.%s", method))
}
func jsfsInvoke(ctx context.Context, mod api.Module, callback funcWrapper, err error) (interface{}, error) {
return callback.invoke(ctx, mod, goos.RefJsfs, err, err == nil) // note: error first
}