In order to support more configuration, we should stop using context as it is getting gnarly. Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io> Signed-off-by: Adrian Cole <adrian@tetrate.io>
791 lines
23 KiB
Go
791 lines
23 KiB
Go
package gojs
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"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/sysfs"
|
|
"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)
|
|
)
|
|
|
|
// The following interfaces are used until we finalize our own FD-scoped file.
|
|
type (
|
|
// chmodFile is implemented by os.File in file_posix.go
|
|
chmodFile interface{ Chmod(fs.FileMode) error }
|
|
// syncFile is implemented by os.File in file_posix.go
|
|
syncFile interface{ Sync() error }
|
|
// truncateFile is implemented by os.File in file_posix.go
|
|
truncateFile interface{ Truncate(size int64) error }
|
|
)
|
|
|
|
// 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 := resolvePath(ctx, args[0].(string))
|
|
flags := toUint64(args[1]) // flags are derived from constants like oWRONLY
|
|
perm := getPerm(ctx, goos.ValueToUint32(args[2]))
|
|
callback := args[3].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
|
|
fd, err := fsc.OpenFile(fsc.RootFS(), path, int(flags), 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 := resolvePath(ctx, 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()
|
|
|
|
var stat platform.Stat_t
|
|
if err := fsc.RootFS().Stat(path, &stat); err != nil {
|
|
return nil, err
|
|
}
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
callback := args[1].(funcWrapper)
|
|
|
|
lstat, err := syscallLstat(mod, path)
|
|
|
|
return callback.invoke(ctx, mod, goos.RefJsfs, err, lstat) // note: error first
|
|
}
|
|
|
|
// syscallLstat is like syscall.Lstat
|
|
func syscallLstat(mod api.Module, path string) (*jsSt, error) {
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
|
|
var stat platform.Stat_t
|
|
if err := fsc.RootFS().Lstat(path, &stat); err != nil {
|
|
return nil, err
|
|
}
|
|
return newJsSt(&stat), nil
|
|
}
|
|
|
|
// 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 := goos.ValueToUint32(args[0])
|
|
callback := args[1].(funcWrapper)
|
|
|
|
fstat, err := syscallFstat(fsc, fd)
|
|
return callback.invoke(ctx, mod, goos.RefJsfs, err, fstat) // note: error first
|
|
}
|
|
|
|
// syscallFstat is like syscall.Fstat
|
|
func syscallFstat(fsc *internalsys.FSContext, fd uint32) (*jsSt, error) {
|
|
f, ok := fsc.LookupFile(fd)
|
|
if !ok {
|
|
return nil, syscall.EBADF
|
|
}
|
|
|
|
var st platform.Stat_t
|
|
if err := f.Stat(&st); err != nil {
|
|
return nil, err
|
|
}
|
|
return newJsSt(&st), nil
|
|
}
|
|
|
|
func newJsSt(st *platform.Stat_t) *jsSt {
|
|
ret := &jsSt{}
|
|
ret.isDir = st.Mode.IsDir()
|
|
ret.dev = st.Dev
|
|
ret.ino = st.Ino
|
|
ret.mode = custom.ToJsMode(st.Mode)
|
|
ret.nlink = uint32(st.Nlink)
|
|
ret.size = st.Size
|
|
ret.atimeMs = st.Atim / 1e6
|
|
ret.mtimeMs = st.Mtim / 1e6
|
|
ret.ctimeMs = st.Ctim / 1e6
|
|
return ret
|
|
}
|
|
|
|
// 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 := goos.ValueToUint32(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 := goos.ValueToUint32(args[0])
|
|
buf, ok := args[1].(*goos.ByteArray)
|
|
if !ok {
|
|
return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1])
|
|
}
|
|
offset := goos.ValueToUint32(args[2])
|
|
byteCount := goos.ValueToUint32(args[3])
|
|
fOffset := args[4] // nil unless Pread
|
|
callback := args[5].(funcWrapper)
|
|
|
|
n, err := syscallRead(mod, fd, fOffset, buf.Unwrap()[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
|
|
}
|
|
|
|
var reader io.Reader = f.File
|
|
|
|
if offset != nil {
|
|
reader = sysfs.ReaderAtOffset(f.File, toInt64(offset))
|
|
}
|
|
|
|
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)
|
|
} 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 := goos.ValueToUint32(args[0])
|
|
buf, ok := args[1].(*goos.ByteArray)
|
|
if !ok {
|
|
return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1])
|
|
}
|
|
offset := goos.ValueToUint32(args[2])
|
|
byteCount := goos.ValueToUint32(args[3])
|
|
fOffset := args[4] // nil unless Pwrite
|
|
callback := args[5].(funcWrapper)
|
|
|
|
if byteCount > 0 { // empty is possible on EOF
|
|
n, err := syscallWrite(mod, fd, fOffset, buf.Unwrap()[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()
|
|
|
|
var writer io.Writer
|
|
if f, ok := fsc.LookupFile(fd); !ok {
|
|
err = syscall.EBADF
|
|
} else if offset != nil {
|
|
writer = sysfs.WriterAtOffset(f.File, toInt64(offset))
|
|
} else if writer, ok = f.File.(io.Writer); !ok {
|
|
err = syscall.EBADF
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
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 := resolvePath(ctx, 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.RootFS().OpenFile(name, os.O_RDONLY, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close() //nolint
|
|
|
|
if names, err := platform.Readdirnames(f, -1); err != nil {
|
|
return nil, err
|
|
} else {
|
|
entries := make([]interface{}, 0, len(names))
|
|
for _, e := range names {
|
|
entries = append(entries, e)
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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 := path.Clean(args[0].(string))
|
|
|
|
if s, err := syscallStat(mod, path); err != nil {
|
|
return nil, err
|
|
} else if !s.isDir {
|
|
return nil, syscall.ENOTDIR
|
|
} else {
|
|
getState(ctx).cwd = path
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// processUmask implements jsFn for fs.Open syscall.Umask in fs_js.go
|
|
type processUmask struct{}
|
|
|
|
func (processUmask) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
|
|
mask := goos.ValueToUint32(args[0])
|
|
|
|
s := getState(ctx)
|
|
oldmask := s.umask
|
|
s.umask = mask
|
|
|
|
return oldmask, 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 := resolvePath(ctx, args[0].(string))
|
|
perm := getPerm(ctx, goos.ValueToUint32(args[1]))
|
|
callback := args[2].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
root := fsc.RootFS()
|
|
|
|
var fd uint32
|
|
var err error
|
|
// We need at least read access to open the file descriptor
|
|
if perm == 0 {
|
|
perm = 0o0500
|
|
}
|
|
if err = root.Mkdir(path, perm); err == nil {
|
|
fd, err = fsc.OpenFile(root, 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 := resolvePath(ctx, args[0].(string))
|
|
callback := args[1].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().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 := resolvePath(ctx, args[0].(string))
|
|
to := resolvePath(ctx, args[1].(string))
|
|
callback := args[2].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().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 := resolvePath(ctx, args[0].(string))
|
|
callback := args[1].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Unlink(path)
|
|
|
|
return jsfsInvoke(ctx, mod, callback, err)
|
|
}
|
|
|
|
// jsfsUtimes implements jsFn for the following
|
|
//
|
|
// _, err := fsCall("utimes", path, atime, mtime) // syscall.Utimens
|
|
type jsfsUtimes struct{}
|
|
|
|
func (jsfsUtimes) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
|
path := resolvePath(ctx, args[0].(string))
|
|
atimeSec := toInt64(args[1])
|
|
mtimeSec := toInt64(args[2])
|
|
callback := args[3].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
times := [2]syscall.Timespec{
|
|
syscall.NsecToTimespec(atimeSec * 1e9), syscall.NsecToTimespec(mtimeSec * 1e9),
|
|
}
|
|
err := fsc.RootFS().Utimens(path, ×, true)
|
|
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
mode := custom.FromJsMode(goos.ValueToUint32(args[1]), 0)
|
|
callback := args[2].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Chmod(path, mode)
|
|
|
|
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 := goos.ValueToUint32(args[0])
|
|
mode := custom.FromJsMode(goos.ValueToUint32(args[1]), 0)
|
|
callback := args[2].(funcWrapper)
|
|
|
|
// Check to see if the file descriptor is available
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
var err error
|
|
if f, ok := fsc.LookupFile(fd); !ok {
|
|
err = syscall.EBADF
|
|
} else if chmodFile, ok := f.File.(chmodFile); !ok {
|
|
err = syscall.EBADF // possibly a fake file
|
|
} else {
|
|
err = chmodFile.Chmod(mode)
|
|
}
|
|
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
uid := goos.ValueToInt32(args[1])
|
|
gid := goos.ValueToInt32(args[2])
|
|
callback := args[3].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Chown(path, int(uid), int(gid))
|
|
|
|
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 := goos.ValueToUint32(args[0])
|
|
uid := goos.ValueToUint32(args[1])
|
|
gid := goos.ValueToUint32(args[2])
|
|
callback := args[3].(funcWrapper)
|
|
|
|
// Check to see if the file descriptor is available
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
var err error
|
|
if f, ok := fsc.LookupFile(fd); !ok {
|
|
err = syscall.EBADF
|
|
} else {
|
|
err = platform.ChownFile(f.File, int(uid), int(gid))
|
|
}
|
|
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
uid := goos.ValueToUint32(args[1])
|
|
gid := goos.ValueToUint32(args[2])
|
|
callback := args[3].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Lchown(path, int(uid), int(gid))
|
|
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
length := toInt64(args[1])
|
|
callback := args[2].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Truncate(path, length)
|
|
|
|
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 := goos.ValueToUint32(args[0])
|
|
length := toInt64(args[1])
|
|
callback := args[2].(funcWrapper)
|
|
|
|
// Check to see if the file descriptor is available
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
var err error
|
|
if f, ok := fsc.LookupFile(fd); !ok {
|
|
err = syscall.EBADF
|
|
} else if truncateFile, ok := f.File.(truncateFile); !ok {
|
|
err = syscall.EBADF // possibly a fake file
|
|
} else {
|
|
err = truncateFile.Truncate(length)
|
|
}
|
|
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
callback := args[1].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
dst, err := fsc.RootFS().Readlink(path)
|
|
|
|
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 := resolvePath(ctx, args[0].(string))
|
|
link := resolvePath(ctx, args[1].(string))
|
|
callback := args[2].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Link(path, link)
|
|
|
|
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) {
|
|
dst := args[0].(string) // The dst of a symlink must not be resolved, as it should be resolved during readLink.
|
|
link := resolvePath(ctx, args[1].(string))
|
|
callback := args[2].(funcWrapper)
|
|
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
err := fsc.RootFS().Symlink(dst, link)
|
|
|
|
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 := goos.ValueToUint32(args[0])
|
|
callback := args[1].(funcWrapper)
|
|
|
|
// Check to see if the file descriptor is available
|
|
fsc := mod.(*wasm.CallContext).Sys.FS()
|
|
var err error
|
|
if f, ok := fsc.LookupFile(fd); !ok {
|
|
err = syscall.EBADF
|
|
} else if syncFile, ok := f.File.(syncFile); !ok {
|
|
err = syscall.EBADF // possibly a fake file
|
|
} else {
|
|
err = syncFile.Sync()
|
|
}
|
|
|
|
return jsfsInvoke(ctx, mod, callback, err)
|
|
}
|
|
|
|
// jsSt is pre-parsed from fs_js.go setStat to avoid thrashing
|
|
type jsSt struct {
|
|
isDir bool
|
|
dev uint64
|
|
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, custom.FromJsMode(s.mode, 0), s.size, s.mtimeMs)
|
|
}
|
|
|
|
// Get implements the same method as documented on goos.GetFunction
|
|
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
|
|
}
|
|
|
|
// resolvePath is needed when a non-absolute path is given to a function.
|
|
// Unlike other host ABI, GOOS=js maintains the CWD host side.
|
|
func resolvePath(ctx context.Context, path string) string {
|
|
if len(path) == 0 || path[0] == '/' {
|
|
return path // leave alone .. or absolute paths.
|
|
}
|
|
return joinPath(getState(ctx).cwd, path)
|
|
}
|
|
|
|
// joinPath avoids us having to rename fields just to avoid conflict with the
|
|
// path package.
|
|
func joinPath(dirName, baseName string) string {
|
|
return path.Join(dirName, baseName)
|
|
}
|
|
|
|
// getPerm converts the input js permissions to a go-compatible one, after
|
|
// subtracting the current umask.
|
|
func getPerm(ctx context.Context, perm uint32) fs.FileMode {
|
|
umask := getState(ctx).umask
|
|
return custom.FromJsMode(perm, umask)
|
|
}
|