github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/gojs/fs.go (about)

     1  package gojs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/bananabytelabs/wazero/api"
     8  	experimentalsys "github.com/bananabytelabs/wazero/experimental/sys"
     9  	"github.com/bananabytelabs/wazero/internal/gojs/custom"
    10  	"github.com/bananabytelabs/wazero/internal/gojs/goos"
    11  	"github.com/bananabytelabs/wazero/internal/gojs/util"
    12  	internalsys "github.com/bananabytelabs/wazero/internal/sys"
    13  	"github.com/bananabytelabs/wazero/internal/wasm"
    14  	"github.com/bananabytelabs/wazero/sys"
    15  )
    16  
    17  var (
    18  	// jsfsConstants = jsfs Get("constants") // fs_js.go init
    19  	jsfsConstants = newJsVal(goos.RefJsfsConstants, "constants").
    20  			addProperties(map[string]interface{}{
    21  			"O_WRONLY": oWRONLY,
    22  			"O_RDWR":   oRDWR,
    23  			"O_CREAT":  oCREAT,
    24  			"O_TRUNC":  oTRUNC,
    25  			"O_APPEND": oAPPEND,
    26  			"O_EXCL":   oEXCL,
    27  		})
    28  
    29  	// oWRONLY = jsfsConstants Get("O_WRONLY").Int() // fs_js.go init
    30  	oWRONLY = float64(experimentalsys.O_WRONLY)
    31  
    32  	// oRDWR = jsfsConstants Get("O_RDWR").Int() // fs_js.go init
    33  	oRDWR = float64(experimentalsys.O_RDWR)
    34  
    35  	// o CREAT = jsfsConstants Get("O_CREAT").Int() // fs_js.go init
    36  	oCREAT = float64(experimentalsys.O_CREAT)
    37  
    38  	// oTRUNC = jsfsConstants Get("O_TRUNC").Int() // fs_js.go init
    39  	oTRUNC = float64(experimentalsys.O_TRUNC)
    40  
    41  	// oAPPEND = jsfsConstants Get("O_APPEND").Int() // fs_js.go init
    42  	oAPPEND = float64(experimentalsys.O_APPEND)
    43  
    44  	// oEXCL = jsfsConstants Get("O_EXCL").Int() // fs_js.go init
    45  	oEXCL = float64(experimentalsys.O_EXCL)
    46  )
    47  
    48  // jsfs = js.Global().Get("fs") // fs_js.go init
    49  //
    50  // js.fsCall conventions:
    51  // * funcWrapper callback is the last parameter
    52  //   - arg0 is error and up to one result in arg1
    53  func newJsFs(proc *processState) *jsVal {
    54  	return newJsVal(goos.RefJsfs, custom.NameFs).
    55  		addProperties(map[string]interface{}{
    56  			"constants": jsfsConstants, // = jsfs.Get("constants") // init
    57  		}).
    58  		addFunction(custom.NameFsOpen, &jsfsOpen{proc: proc}).
    59  		addFunction(custom.NameFsStat, &jsfsStat{proc: proc}).
    60  		addFunction(custom.NameFsFstat, jsfsFstat{}).
    61  		addFunction(custom.NameFsLstat, &jsfsLstat{proc: proc}).
    62  		addFunction(custom.NameFsClose, jsfsClose{}).
    63  		addFunction(custom.NameFsRead, jsfsRead{}).
    64  		addFunction(custom.NameFsWrite, jsfsWrite{}).
    65  		addFunction(custom.NameFsReaddir, &jsfsReaddir{proc: proc}).
    66  		addFunction(custom.NameFsMkdir, &jsfsMkdir{proc: proc}).
    67  		addFunction(custom.NameFsRmdir, &jsfsRmdir{proc: proc}).
    68  		addFunction(custom.NameFsRename, &jsfsRename{proc: proc}).
    69  		addFunction(custom.NameFsUnlink, &jsfsUnlink{proc: proc}).
    70  		addFunction(custom.NameFsUtimes, &jsfsUtimes{proc: proc}).
    71  		addFunction(custom.NameFsChmod, &jsfsChmod{proc: proc}).
    72  		addFunction(custom.NameFsFchmod, jsfsFchmod{}).
    73  		addFunction(custom.NameFsChown, &jsfsChown{proc: proc}).
    74  		addFunction(custom.NameFsFchown, jsfsFchown{}).
    75  		addFunction(custom.NameFsLchown, &jsfsLchown{proc: proc}).
    76  		addFunction(custom.NameFsTruncate, &jsfsTruncate{proc: proc}).
    77  		addFunction(custom.NameFsFtruncate, jsfsFtruncate{}).
    78  		addFunction(custom.NameFsReadlink, &jsfsReadlink{proc: proc}).
    79  		addFunction(custom.NameFsLink, &jsfsLink{proc: proc}).
    80  		addFunction(custom.NameFsSymlink, &jsfsSymlink{proc: proc}).
    81  		addFunction(custom.NameFsFsync, jsfsFsync{})
    82  }
    83  
    84  // jsfsOpen implements implements jsFn for syscall.Open
    85  //
    86  //	jsFD /* Int */, err := fsCall("open", path, flags, perm)
    87  type jsfsOpen struct {
    88  	proc *processState
    89  }
    90  
    91  func (o *jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
    92  	path := util.ResolvePath(o.proc.cwd, args[0].(string))
    93  	// Note: these are already sys.Flag because Go uses constants we define:
    94  	// https://github.com/golang/go/blob/go1.20/src/syscall/fs_js.go#L24-L31
    95  	flags := experimentalsys.Oflag(toUint64(args[1]))
    96  	perm := custom.FromJsMode(goos.ValueToUint32(args[2]), o.proc.umask)
    97  	callback := args[3].(funcWrapper)
    98  
    99  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   100  
   101  	fd, errno := fsc.OpenFile(fsc.RootFS(), path, flags, perm)
   102  
   103  	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first
   104  }
   105  
   106  // jsfsStat implements jsFn for syscall.Stat
   107  //
   108  //	jsSt, err := fsCall("stat", path)
   109  type jsfsStat struct {
   110  	proc *processState
   111  }
   112  
   113  func (s *jsfsStat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   114  	path := util.ResolvePath(s.proc.cwd, args[0].(string))
   115  	callback := args[1].(funcWrapper)
   116  
   117  	stat, err := syscallStat(mod, path)
   118  	return callback.invoke(ctx, mod, goos.RefJsfs, err, stat) // note: error first
   119  }
   120  
   121  // syscallStat is like syscall.Stat
   122  func syscallStat(mod api.Module, path string) (*jsSt, error) {
   123  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   124  
   125  	if st, errno := fsc.RootFS().Stat(path); errno != 0 {
   126  		return nil, errno
   127  	} else {
   128  		return newJsSt(st), nil
   129  	}
   130  }
   131  
   132  // jsfsLstat implements jsFn for syscall.Lstat
   133  //
   134  //	jsSt, err := fsCall("lstat", path)
   135  type jsfsLstat struct {
   136  	proc *processState
   137  }
   138  
   139  func (l *jsfsLstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   140  	path := util.ResolvePath(l.proc.cwd, args[0].(string))
   141  	callback := args[1].(funcWrapper)
   142  
   143  	lstat, err := syscallLstat(mod, path)
   144  
   145  	return callback.invoke(ctx, mod, goos.RefJsfs, err, lstat) // note: error first
   146  }
   147  
   148  // syscallLstat is like syscall.Lstat
   149  func syscallLstat(mod api.Module, path string) (*jsSt, error) {
   150  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   151  
   152  	if st, errno := fsc.RootFS().Lstat(path); errno != 0 {
   153  		return nil, errno
   154  	} else {
   155  		return newJsSt(st), nil
   156  	}
   157  }
   158  
   159  // jsfsFstat implements jsFn for syscall.Open
   160  //
   161  //	stat, err := fsCall("fstat", fd); err == nil && stat.Call("isDirectory").Bool()
   162  type jsfsFstat struct{}
   163  
   164  func (jsfsFstat) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   165  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   166  
   167  	fd := goos.ValueToInt32(args[0])
   168  	callback := args[1].(funcWrapper)
   169  
   170  	fstat, err := syscallFstat(fsc, fd)
   171  	return callback.invoke(ctx, mod, goos.RefJsfs, err, fstat) // note: error first
   172  }
   173  
   174  // syscallFstat is like syscall.Fstat
   175  func syscallFstat(fsc *internalsys.FSContext, fd int32) (*jsSt, error) {
   176  	f, ok := fsc.LookupFile(fd)
   177  	if !ok {
   178  		return nil, experimentalsys.EBADF
   179  	}
   180  
   181  	if st, errno := f.File.Stat(); errno != 0 {
   182  		return nil, errno
   183  	} else {
   184  		return newJsSt(st), nil
   185  	}
   186  }
   187  
   188  func newJsSt(st sys.Stat_t) *jsSt {
   189  	ret := &jsSt{}
   190  	ret.isDir = st.Mode.IsDir()
   191  	ret.dev = st.Dev
   192  	ret.ino = st.Ino
   193  	ret.mode = custom.ToJsMode(st.Mode)
   194  	ret.nlink = uint32(st.Nlink)
   195  	ret.size = st.Size
   196  	ret.atimeMs = st.Atim / 1e6
   197  	ret.mtimeMs = st.Mtim / 1e6
   198  	ret.ctimeMs = st.Ctim / 1e6
   199  	return ret
   200  }
   201  
   202  // jsfsClose implements jsFn for syscall.Close
   203  type jsfsClose struct{}
   204  
   205  func (jsfsClose) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   206  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   207  
   208  	fd := goos.ValueToInt32(args[0])
   209  	callback := args[1].(funcWrapper)
   210  
   211  	errno := fsc.CloseFile(fd)
   212  
   213  	return jsfsInvoke(ctx, mod, callback, errno)
   214  }
   215  
   216  // jsfsRead implements jsFn for syscall.Read and syscall.Pread, called by
   217  // src/internal/poll/fd_unix.go poll.Read.
   218  //
   219  //	n, err := fsCall("read", fd, buf, 0, len(b), nil)
   220  type jsfsRead struct{}
   221  
   222  func (jsfsRead) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   223  	fd := goos.ValueToInt32(args[0])
   224  	buf, ok := args[1].(*goos.ByteArray)
   225  	if !ok {
   226  		return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1])
   227  	}
   228  	offset := goos.ValueToUint32(args[2])
   229  	byteCount := goos.ValueToUint32(args[3])
   230  	fOffset := args[4] // nil unless Pread
   231  	callback := args[5].(funcWrapper)
   232  
   233  	var err error
   234  	n, errno := syscallRead(mod, fd, fOffset, buf.Unwrap()[offset:offset+byteCount])
   235  	if errno != 0 {
   236  		err = errno
   237  	}
   238  	// It is safe to cast to uint32 because n <= uint32(byteCount).
   239  	return callback.invoke(ctx, mod, goos.RefJsfs, err, uint32(n)) // note: error first
   240  }
   241  
   242  // syscallRead is like syscall.Read
   243  func syscallRead(mod api.Module, fd int32, offset interface{}, buf []byte) (n int, errno experimentalsys.Errno) {
   244  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   245  
   246  	if f, ok := fsc.LookupFile(fd); !ok {
   247  		return 0, experimentalsys.EBADF
   248  	} else if offset != nil {
   249  		return f.File.Pread(buf, toInt64(offset))
   250  	} else {
   251  		return f.File.Read(buf)
   252  	}
   253  }
   254  
   255  // jsfsWrite implements jsFn for syscall.Write and syscall.Pwrite.
   256  //
   257  // Notably, offset is non-nil in Pwrite.
   258  //
   259  //	n, err := fsCall("write", fd, buf, 0, len(b), nil)
   260  type jsfsWrite struct{}
   261  
   262  func (jsfsWrite) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   263  	fd := goos.ValueToInt32(args[0])
   264  	buf, ok := args[1].(*goos.ByteArray)
   265  	if !ok {
   266  		return nil, fmt.Errorf("arg[1] is %v not a []byte", args[1])
   267  	}
   268  	offset := goos.ValueToUint32(args[2])
   269  	byteCount := goos.ValueToUint32(args[3])
   270  	fOffset := args[4] // nil unless Pwrite
   271  	callback := args[5].(funcWrapper)
   272  
   273  	if byteCount > 0 { // empty is possible on EOF
   274  		n, errno := syscallWrite(mod, fd, fOffset, buf.Unwrap()[offset:offset+byteCount])
   275  		var err error
   276  		if errno != 0 {
   277  			err = errno
   278  		}
   279  		// It is safe to cast to uint32 because n <= uint32(byteCount).
   280  		return callback.invoke(ctx, mod, goos.RefJsfs, err, uint32(n)) // note: error first
   281  	}
   282  	return callback.invoke(ctx, mod, goos.RefJsfs, nil, goos.RefValueZero)
   283  }
   284  
   285  // syscallWrite is like syscall.Write
   286  func syscallWrite(mod api.Module, fd int32, offset interface{}, buf []byte) (n int, errno experimentalsys.Errno) {
   287  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   288  	if f, ok := fsc.LookupFile(fd); !ok {
   289  		errno = experimentalsys.EBADF
   290  	} else if offset != nil {
   291  		n, errno = f.File.Pwrite(buf, toInt64(offset))
   292  	} else {
   293  		n, errno = f.File.Write(buf)
   294  	}
   295  	if errno == experimentalsys.ENOSYS {
   296  		errno = experimentalsys.EBADF // e.g. unimplemented for write
   297  	}
   298  	return
   299  }
   300  
   301  // jsfsReaddir implements jsFn for syscall.Open
   302  //
   303  //	dir, err := fsCall("readdir", path)
   304  //		dir.Length(), dir.Index(i).String()
   305  type jsfsReaddir struct {
   306  	proc *processState
   307  }
   308  
   309  func (r *jsfsReaddir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   310  	path := util.ResolvePath(r.proc.cwd, args[0].(string))
   311  	callback := args[1].(funcWrapper)
   312  
   313  	stat, err := syscallReaddir(ctx, mod, path)
   314  	return callback.invoke(ctx, mod, goos.RefJsfs, err, stat) // note: error first
   315  }
   316  
   317  func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArray, error) {
   318  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   319  
   320  	// don't allocate a file descriptor
   321  	f, errno := fsc.RootFS().OpenFile(name, experimentalsys.O_RDONLY, 0)
   322  	if errno != 0 {
   323  		return nil, errno
   324  	}
   325  	defer f.Close() //nolint
   326  
   327  	if dirents, errno := f.Readdir(-1); errno != 0 {
   328  		return nil, errno
   329  	} else {
   330  		entries := make([]interface{}, 0, len(dirents))
   331  		for _, e := range dirents {
   332  			entries = append(entries, e.Name)
   333  		}
   334  		return &objectArray{entries}, nil
   335  	}
   336  }
   337  
   338  // jsfsMkdir implements implements jsFn for fs.Mkdir
   339  //
   340  //	jsFD /* Int */, err := fsCall("mkdir", path, perm)
   341  type jsfsMkdir struct {
   342  	proc *processState
   343  }
   344  
   345  func (m *jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   346  	path := util.ResolvePath(m.proc.cwd, args[0].(string))
   347  	perm := custom.FromJsMode(goos.ValueToUint32(args[1]), m.proc.umask)
   348  	callback := args[2].(funcWrapper)
   349  
   350  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   351  	root := fsc.RootFS()
   352  
   353  	var fd int32
   354  	var errno experimentalsys.Errno
   355  	// We need at least read access to open the file descriptor
   356  	if perm == 0 {
   357  		perm = 0o0500
   358  	}
   359  	if errno = root.Mkdir(path, perm); errno == 0 {
   360  		fd, errno = fsc.OpenFile(root, path, experimentalsys.O_RDONLY, 0)
   361  	}
   362  
   363  	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first
   364  }
   365  
   366  // jsfsRmdir implements jsFn for the following
   367  //
   368  //	_, err := fsCall("rmdir", path) // syscall.Rmdir
   369  type jsfsRmdir struct {
   370  	proc *processState
   371  }
   372  
   373  func (r *jsfsRmdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   374  	path := util.ResolvePath(r.proc.cwd, args[0].(string))
   375  	callback := args[1].(funcWrapper)
   376  
   377  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   378  	errno := fsc.RootFS().Rmdir(path)
   379  
   380  	return jsfsInvoke(ctx, mod, callback, errno)
   381  }
   382  
   383  // jsfsRename implements jsFn for the following
   384  //
   385  //	_, err := fsCall("rename", from, to) // syscall.Rename
   386  type jsfsRename struct {
   387  	proc *processState
   388  }
   389  
   390  func (r *jsfsRename) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   391  	cwd := r.proc.cwd
   392  	from := util.ResolvePath(cwd, args[0].(string))
   393  	to := util.ResolvePath(cwd, args[1].(string))
   394  	callback := args[2].(funcWrapper)
   395  
   396  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   397  	errno := fsc.RootFS().Rename(from, to)
   398  
   399  	return jsfsInvoke(ctx, mod, callback, errno)
   400  }
   401  
   402  // jsfsUnlink implements jsFn for the following
   403  //
   404  //	_, err := fsCall("unlink", path) // syscall.Unlink
   405  type jsfsUnlink struct {
   406  	proc *processState
   407  }
   408  
   409  func (u *jsfsUnlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   410  	path := util.ResolvePath(u.proc.cwd, args[0].(string))
   411  	callback := args[1].(funcWrapper)
   412  
   413  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   414  	errno := fsc.RootFS().Unlink(path)
   415  
   416  	return jsfsInvoke(ctx, mod, callback, errno)
   417  }
   418  
   419  // jsfsUtimes implements jsFn for the following
   420  //
   421  //	_, err := fsCall("utimes", path, atime, mtime) // syscall.Utimens
   422  type jsfsUtimes struct {
   423  	proc *processState
   424  }
   425  
   426  func (u *jsfsUtimes) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   427  	path := util.ResolvePath(u.proc.cwd, args[0].(string))
   428  	atimeSec := toInt64(args[1])
   429  	mtimeSec := toInt64(args[2])
   430  	callback := args[3].(funcWrapper)
   431  
   432  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   433  	errno := fsc.RootFS().Utimens(path, atimeSec*1e9, mtimeSec*1e9)
   434  
   435  	return jsfsInvoke(ctx, mod, callback, errno)
   436  }
   437  
   438  // jsfsChmod implements jsFn for the following
   439  //
   440  //	_, err := fsCall("chmod", path, mode) // syscall.Chmod
   441  type jsfsChmod struct {
   442  	proc *processState
   443  }
   444  
   445  func (c *jsfsChmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   446  	path := util.ResolvePath(c.proc.cwd, args[0].(string))
   447  	mode := custom.FromJsMode(goos.ValueToUint32(args[1]), 0)
   448  	callback := args[2].(funcWrapper)
   449  
   450  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   451  	errno := fsc.RootFS().Chmod(path, mode)
   452  
   453  	return jsfsInvoke(ctx, mod, callback, errno)
   454  }
   455  
   456  // jsfsFchmod implements jsFn for the following
   457  //
   458  //	_, err := fsCall("fchmod", fd, mode) // syscall.Fchmod
   459  type jsfsFchmod struct{}
   460  
   461  func (jsfsFchmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   462  	fd := goos.ValueToInt32(args[0])
   463  	_ = args[1] // mode
   464  	callback := args[2].(funcWrapper)
   465  
   466  	// Check to see if the file descriptor is available
   467  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   468  	var errno experimentalsys.Errno
   469  	if _, ok := fsc.LookupFile(fd); !ok {
   470  		errno = experimentalsys.EBADF
   471  	} else {
   472  		errno = experimentalsys.ENOSYS // We only support functions used in wasip1
   473  	}
   474  
   475  	return jsfsInvoke(ctx, mod, callback, errno)
   476  }
   477  
   478  // jsfsChown implements jsFn for the following
   479  //
   480  //	_, err := fsCall("chown", path, uint32(uid), uint32(gid)) // syscall.Chown
   481  type jsfsChown struct {
   482  	proc *processState
   483  }
   484  
   485  func (c *jsfsChown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   486  	_ = args[0] // path
   487  	_ = args[1] // uid
   488  	_ = args[2] // gid
   489  	callback := args[3].(funcWrapper)
   490  
   491  	errno := experimentalsys.ENOSYS // We only support functions used in wasip1
   492  
   493  	return jsfsInvoke(ctx, mod, callback, errno)
   494  }
   495  
   496  // jsfsFchown implements jsFn for the following
   497  //
   498  //	_, err := fsCall("fchown", fd, uint32(uid), uint32(gid)) // syscall.Fchown
   499  type jsfsFchown struct{}
   500  
   501  func (jsfsFchown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   502  	fd := goos.ValueToInt32(args[0])
   503  	_ = args[1] // uid
   504  	_ = args[2] // gid
   505  	callback := args[3].(funcWrapper)
   506  
   507  	// Check to see if the file descriptor is available
   508  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   509  	var errno experimentalsys.Errno
   510  	if _, ok := fsc.LookupFile(fd); !ok {
   511  		errno = experimentalsys.EBADF
   512  	} else {
   513  		errno = experimentalsys.ENOSYS // We only support functions used in wasip1
   514  	}
   515  
   516  	return jsfsInvoke(ctx, mod, callback, errno)
   517  }
   518  
   519  // jsfsLchown implements jsFn for the following
   520  //
   521  //	_, err := fsCall("lchown", path, uint32(uid), uint32(gid)) // syscall.Lchown
   522  type jsfsLchown struct {
   523  	proc *processState
   524  }
   525  
   526  func (l *jsfsLchown) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   527  	_ = args[0] // path
   528  	_ = args[1] // uid
   529  	_ = args[2] // gid
   530  	callback := args[3].(funcWrapper)
   531  
   532  	errno := experimentalsys.ENOSYS // We only support functions used in wasip1
   533  
   534  	return jsfsInvoke(ctx, mod, callback, errno)
   535  }
   536  
   537  // jsfsTruncate implements jsFn for the following
   538  //
   539  //	_, err := fsCall("truncate", path, length) // syscall.Truncate
   540  type jsfsTruncate struct {
   541  	proc *processState
   542  }
   543  
   544  func (t *jsfsTruncate) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   545  	_ = args[0] // path
   546  	_ = args[1] // length
   547  	callback := args[2].(funcWrapper)
   548  
   549  	errno := experimentalsys.ENOSYS // We only support functions used in wasip1
   550  
   551  	return jsfsInvoke(ctx, mod, callback, errno)
   552  }
   553  
   554  // jsfsFtruncate implements jsFn for the following
   555  //
   556  //	_, err := fsCall("ftruncate", fd, length) // syscall.Ftruncate
   557  type jsfsFtruncate struct{}
   558  
   559  func (jsfsFtruncate) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   560  	fd := goos.ValueToInt32(args[0])
   561  	length := toInt64(args[1])
   562  	callback := args[2].(funcWrapper)
   563  
   564  	// Check to see if the file descriptor is available
   565  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   566  	var errno experimentalsys.Errno
   567  	if f, ok := fsc.LookupFile(fd); !ok {
   568  		errno = experimentalsys.EBADF
   569  	} else {
   570  		errno = f.File.Truncate(length)
   571  	}
   572  
   573  	return jsfsInvoke(ctx, mod, callback, errno)
   574  }
   575  
   576  // jsfsReadlink implements jsFn for syscall.Readlink
   577  //
   578  //	dst, err := fsCall("readlink", path) // syscall.Readlink
   579  type jsfsReadlink struct {
   580  	proc *processState
   581  }
   582  
   583  func (r *jsfsReadlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   584  	path := util.ResolvePath(r.proc.cwd, args[0].(string))
   585  	callback := args[1].(funcWrapper)
   586  
   587  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   588  	dst, errno := fsc.RootFS().Readlink(path)
   589  
   590  	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), dst) // note: error first
   591  }
   592  
   593  // jsfsLink implements jsFn for the following
   594  //
   595  //	_, err := fsCall("link", path, link) // syscall.Link
   596  type jsfsLink struct {
   597  	proc *processState
   598  }
   599  
   600  func (l *jsfsLink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   601  	cwd := l.proc.cwd
   602  	path := util.ResolvePath(cwd, args[0].(string))
   603  	link := util.ResolvePath(cwd, args[1].(string))
   604  	callback := args[2].(funcWrapper)
   605  
   606  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   607  	errno := fsc.RootFS().Link(path, link)
   608  
   609  	return jsfsInvoke(ctx, mod, callback, errno)
   610  }
   611  
   612  // jsfsSymlink implements jsFn for the following
   613  //
   614  //	_, err := fsCall("symlink", path, link) // syscall.Symlink
   615  type jsfsSymlink struct {
   616  	proc *processState
   617  }
   618  
   619  func (s *jsfsSymlink) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   620  	dst := args[0].(string) // The dst of a symlink must not be resolved, as it should be resolved during readLink.
   621  	link := util.ResolvePath(s.proc.cwd, args[1].(string))
   622  	callback := args[2].(funcWrapper)
   623  
   624  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   625  	errno := fsc.RootFS().Symlink(dst, link)
   626  
   627  	return jsfsInvoke(ctx, mod, callback, errno)
   628  }
   629  
   630  // jsfsFsync implements jsFn for the following
   631  //
   632  //	_, err := fsCall("fsync", fd) // syscall.Fsync
   633  type jsfsFsync struct{}
   634  
   635  func (jsfsFsync) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
   636  	fd := goos.ValueToInt32(args[0])
   637  	callback := args[1].(funcWrapper)
   638  
   639  	// Check to see if the file descriptor is available
   640  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   641  	var errno experimentalsys.Errno
   642  	if f, ok := fsc.LookupFile(fd); !ok {
   643  		errno = experimentalsys.EBADF
   644  	} else {
   645  		errno = f.File.Sync()
   646  	}
   647  
   648  	return jsfsInvoke(ctx, mod, callback, errno)
   649  }
   650  
   651  // jsSt is pre-parsed from fs_js.go setStat to avoid thrashing
   652  type jsSt struct {
   653  	isDir   bool
   654  	dev     uint64
   655  	ino     uint64
   656  	mode    uint32
   657  	nlink   uint32
   658  	uid     uint32
   659  	gid     uint32
   660  	rdev    int64
   661  	size    int64
   662  	blksize int32
   663  	blocks  int32
   664  	atimeMs int64
   665  	mtimeMs int64
   666  	ctimeMs int64
   667  }
   668  
   669  // String implements fmt.Stringer
   670  func (s *jsSt) String() string {
   671  	return fmt.Sprintf("{isDir=%v,mode=%s,size=%d,mtimeMs=%d}", s.isDir, custom.FromJsMode(s.mode, 0), s.size, s.mtimeMs)
   672  }
   673  
   674  // Get implements the same method as documented on goos.GetFunction
   675  func (s *jsSt) Get(propertyKey string) interface{} {
   676  	switch propertyKey {
   677  	case "dev":
   678  		return s.dev
   679  	case "ino":
   680  		return s.ino
   681  	case "mode":
   682  		return s.mode
   683  	case "nlink":
   684  		return s.nlink
   685  	case "uid":
   686  		return s.uid
   687  	case "gid":
   688  		return s.gid
   689  	case "rdev":
   690  		return s.rdev
   691  	case "size":
   692  		return s.size
   693  	case "blksize":
   694  		return s.blksize
   695  	case "blocks":
   696  		return s.blocks
   697  	case "atimeMs":
   698  		return s.atimeMs
   699  	case "mtimeMs":
   700  		return s.mtimeMs
   701  	case "ctimeMs":
   702  		return s.ctimeMs
   703  	}
   704  	panic(fmt.Sprintf("TODO: stat.%s", propertyKey))
   705  }
   706  
   707  // call implements jsCall.call
   708  func (s *jsSt) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) {
   709  	if method == "isDirectory" {
   710  		return s.isDir, nil
   711  	}
   712  	panic(fmt.Sprintf("TODO: stat.%s", method))
   713  }
   714  
   715  func jsfsInvoke(ctx context.Context, mod api.Module, callback funcWrapper, err experimentalsys.Errno) (interface{}, error) {
   716  	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(err), err == 0) // note: error first
   717  }
   718  
   719  func maybeError(errno experimentalsys.Errno) error {
   720  	if errno != 0 {
   721  		return errno
   722  	}
   723  	return nil
   724  }