github.com/tetratelabs/wazero@v1.2.1/internal/gojs/fs.go (about)

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