github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/fs_test.go (about)

     1  package wasi_snapshot_preview1_test
     2  
     3  import (
     4  	"bytes"
     5  	_ "embed"
     6  	"fmt"
     7  	"io"
     8  	"io/fs"
     9  	"math"
    10  	"os"
    11  	"path"
    12  	"runtime"
    13  	"strings"
    14  	"syscall"
    15  	"testing"
    16  	gofstest "testing/fstest"
    17  	"time"
    18  
    19  	"github.com/tetratelabs/wazero"
    20  	"github.com/tetratelabs/wazero/api"
    21  	"github.com/tetratelabs/wazero/internal/fsapi"
    22  	"github.com/tetratelabs/wazero/internal/fstest"
    23  	"github.com/tetratelabs/wazero/internal/platform"
    24  	"github.com/tetratelabs/wazero/internal/sys"
    25  	"github.com/tetratelabs/wazero/internal/sysfs"
    26  	"github.com/tetratelabs/wazero/internal/testing/require"
    27  	"github.com/tetratelabs/wazero/internal/u64"
    28  	"github.com/tetratelabs/wazero/internal/wasip1"
    29  	"github.com/tetratelabs/wazero/internal/wasm"
    30  )
    31  
    32  func Test_fdAdvise(t *testing.T) {
    33  	mod, r, _ := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
    34  	defer r.Close(testCtx)
    35  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceNormal))
    36  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceSequential))
    37  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceRandom))
    38  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceWillNeed))
    39  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceDontNeed))
    40  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceNoReuse))
    41  	requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAdviseName, uint64(3), 0, 0, uint64(wasip1.FdAdviceNoReuse+1))
    42  	requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdAdviseName, uint64(1111111), 0, 0, uint64(wasip1.FdAdviceNoReuse+1))
    43  }
    44  
    45  // Test_fdAllocate only tests it is stubbed for GrainLang per #271
    46  func Test_fdAllocate(t *testing.T) {
    47  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
    48  	const fileName = "file.txt"
    49  
    50  	// Create the target file.
    51  	realPath := joinPath(tmpDir, fileName)
    52  	require.NoError(t, os.WriteFile(realPath, []byte("0123456789"), 0o600))
    53  
    54  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(
    55  		wazero.NewFSConfig().WithDirMount(tmpDir, "/"),
    56  	))
    57  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
    58  	preopen := fsc.RootFS()
    59  	defer r.Close(testCtx)
    60  
    61  	fd, errno := fsc.OpenFile(preopen, fileName, os.O_RDWR, 0)
    62  	require.EqualErrno(t, 0, errno)
    63  
    64  	f, ok := fsc.LookupFile(fd)
    65  	require.True(t, ok)
    66  
    67  	requireSizeEqual := func(exp int64) {
    68  		st, errno := f.File.Stat()
    69  		require.EqualErrno(t, 0, errno)
    70  		require.Equal(t, exp, st.Size)
    71  	}
    72  
    73  	t.Run("errors", func(t *testing.T) {
    74  		requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdAllocateName, uint64(12345), 0, 0)
    75  		minusOne := int64(-1)
    76  		requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAllocateName, uint64(fd), uint64(minusOne), uint64(minusOne))
    77  		requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAllocateName, uint64(fd), 0, uint64(minusOne))
    78  		requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdAllocateName, uint64(fd), uint64(minusOne), 0)
    79  	})
    80  
    81  	t.Run("do not change size", func(t *testing.T) {
    82  		for _, tc := range []struct{ offset, length uint64 }{
    83  			{offset: 0, length: 10},
    84  			{offset: 5, length: 5},
    85  			{offset: 4, length: 0},
    86  			{offset: 10, length: 0},
    87  		} {
    88  			// This shouldn't change the size.
    89  			requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAllocateName,
    90  				uint64(fd), tc.offset, tc.length)
    91  			requireSizeEqual(10)
    92  		}
    93  	})
    94  
    95  	t.Run("increase", func(t *testing.T) {
    96  		// 10 + 10 > the current size -> increase the size.
    97  		requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdAllocateName,
    98  			uint64(fd), 10, 10)
    99  		requireSizeEqual(20)
   100  
   101  		// But the original content must be kept.
   102  		buf, err := os.ReadFile(realPath)
   103  		require.NoError(t, err)
   104  		require.Equal(t, "0123456789", string(buf[:10]))
   105  	})
   106  
   107  	require.Equal(t, `
   108  ==> wasi_snapshot_preview1.fd_allocate(fd=12345,offset=0,len=0)
   109  <== errno=EBADF
   110  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=-1,len=-1)
   111  <== errno=EINVAL
   112  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=0,len=-1)
   113  <== errno=EINVAL
   114  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=-1,len=0)
   115  <== errno=EINVAL
   116  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=0,len=10)
   117  <== errno=ESUCCESS
   118  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=5,len=5)
   119  <== errno=ESUCCESS
   120  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=4,len=0)
   121  <== errno=ESUCCESS
   122  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=10,len=0)
   123  <== errno=ESUCCESS
   124  ==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=10,len=10)
   125  <== errno=ESUCCESS
   126  `, "\n"+log.String())
   127  }
   128  
   129  func Test_fdClose(t *testing.T) {
   130  	// fd_close needs to close an open file descriptor. Open two files so that we can tell which is closed.
   131  	path1, path2 := "dir/-", "dir/a-"
   132  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
   133  	defer r.Close(testCtx)
   134  
   135  	// open both paths without using WASI
   136  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   137  	preopen := fsc.RootFS()
   138  
   139  	fdToClose, errno := fsc.OpenFile(preopen, path1, os.O_RDONLY, 0)
   140  	require.EqualErrno(t, 0, errno)
   141  
   142  	fdToKeep, errno := fsc.OpenFile(preopen, path2, os.O_RDONLY, 0)
   143  	require.EqualErrno(t, 0, errno)
   144  
   145  	// Close
   146  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdCloseName, uint64(fdToClose))
   147  	require.Equal(t, `
   148  ==> wasi_snapshot_preview1.fd_close(fd=4)
   149  <== errno=ESUCCESS
   150  `, "\n"+log.String())
   151  
   152  	// Verify fdToClose is closed and removed from the file descriptor table.
   153  	_, ok := fsc.LookupFile(fdToClose)
   154  	require.False(t, ok)
   155  
   156  	// Verify fdToKeep is not closed
   157  	_, ok = fsc.LookupFile(fdToKeep)
   158  	require.True(t, ok)
   159  
   160  	log.Reset()
   161  	t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) {
   162  		requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdCloseName, uint64(42)) // 42 is an arbitrary invalid Fd
   163  		require.Equal(t, `
   164  ==> wasi_snapshot_preview1.fd_close(fd=42)
   165  <== errno=EBADF
   166  `, "\n"+log.String())
   167  	})
   168  	log.Reset()
   169  	t.Run("Can close a pre-open", func(t *testing.T) {
   170  		requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdCloseName, uint64(sys.FdPreopen))
   171  		require.Equal(t, `
   172  ==> wasi_snapshot_preview1.fd_close(fd=3)
   173  <== errno=ESUCCESS
   174  `, "\n"+log.String())
   175  	})
   176  }
   177  
   178  // Test_fdDatasync only tests that the call succeeds; it's hard to test its effectiveness.
   179  func Test_fdDatasync(t *testing.T) {
   180  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
   181  	pathName := "test_path"
   182  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
   183  	defer r.Close(testCtx)
   184  
   185  	tests := []struct {
   186  		name          string
   187  		fd            int32
   188  		expectedErrno wasip1.Errno
   189  		expectedLog   string
   190  	}{
   191  		{
   192  			name:          "invalid FD",
   193  			fd:            42, // arbitrary invalid fd
   194  			expectedErrno: wasip1.ErrnoBadf,
   195  			expectedLog: `
   196  ==> wasi_snapshot_preview1.fd_datasync(fd=42)
   197  <== errno=EBADF
   198  `,
   199  		},
   200  		{
   201  			name:          "valid FD",
   202  			fd:            fd,
   203  			expectedErrno: wasip1.ErrnoSuccess,
   204  			expectedLog: `
   205  ==> wasi_snapshot_preview1.fd_datasync(fd=4)
   206  <== errno=ESUCCESS
   207  `,
   208  		},
   209  	}
   210  
   211  	for _, tt := range tests {
   212  		tc := tt
   213  		t.Run(tc.name, func(t *testing.T) {
   214  			defer log.Reset()
   215  
   216  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdDatasyncName, uint64(tc.fd))
   217  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   218  		})
   219  	}
   220  }
   221  
   222  func openPipe(t *testing.T) (*os.File, *os.File) {
   223  	r, w, err := os.Pipe()
   224  	require.NoError(t, err)
   225  	return r, w
   226  }
   227  
   228  func closePipe(r, w *os.File) {
   229  	r.Close()
   230  	w.Close()
   231  }
   232  
   233  func Test_fdFdstatGet(t *testing.T) {
   234  	file, dir := "animals.txt", "sub"
   235  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
   236  	defer r.Close(testCtx)
   237  	memorySize := mod.Memory().Size()
   238  
   239  	// open both paths without using WASI
   240  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   241  	preopen := fsc.RootFS()
   242  
   243  	// replace stdin with a fake TTY file.
   244  	// TODO: Make this easier once we have in-memory internalapi.File
   245  	stdin, _ := fsc.LookupFile(sys.FdStdin)
   246  	stdinFile, errno := sysfs.Adapt(&gofstest.MapFS{"stdin": &gofstest.MapFile{
   247  		Mode: fs.ModeDevice | fs.ModeCharDevice | 0o600,
   248  	}}).OpenFile("stdin", 0, 0)
   249  	require.EqualErrno(t, 0, errno)
   250  
   251  	stdin.File = stdinFile
   252  
   253  	// Make this file writeable, to ensure flags read-back correctly.
   254  	fileFD, errno := fsc.OpenFile(preopen, file, os.O_RDWR, 0)
   255  	require.EqualErrno(t, 0, errno)
   256  
   257  	dirFD, errno := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
   258  	require.EqualErrno(t, 0, errno)
   259  
   260  	tests := []struct {
   261  		name           string
   262  		fd             int32
   263  		resultFdstat   uint32
   264  		expectedMemory []byte
   265  		expectedErrno  wasip1.Errno
   266  		expectedLog    string
   267  	}{
   268  		{
   269  			name: "stdin is a tty",
   270  			fd:   sys.FdStdin,
   271  			expectedMemory: []byte{
   272  				2, 0, // fs_filetype
   273  				0, 0, 0, 0, 0, 0, // fs_flags
   274  				0xdb, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   275  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   276  			}, // We shouldn't see RIGHT_FD_SEEK|RIGHT_FD_TELL on a tty file:
   277  			expectedLog: `
   278  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=0)
   279  <== (stat={filetype=CHARACTER_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FDSTAT_SET_FLAGS|FD_SYNC|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   280  `,
   281  		},
   282  		{
   283  			name: "stdout",
   284  			fd:   sys.FdStdout,
   285  			expectedMemory: []byte{
   286  				1, 0, // fs_filetype
   287  				0, 0, 0, 0, 0, 0, // fs_flags
   288  				0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   289  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   290  			},
   291  			expectedLog: `
   292  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=1)
   293  <== (stat={filetype=BLOCK_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   294  `,
   295  		},
   296  		{
   297  			name: "stderr",
   298  			fd:   sys.FdStderr,
   299  			expectedMemory: []byte{
   300  				1, 0, // fs_filetype
   301  				0, 0, 0, 0, 0, 0, // fs_flags
   302  				0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   303  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   304  			},
   305  			expectedLog: `
   306  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=2)
   307  <== (stat={filetype=BLOCK_DEVICE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   308  `,
   309  		},
   310  		{
   311  			name: "root",
   312  			fd:   sys.FdPreopen,
   313  			expectedMemory: []byte{
   314  				3, 0, // fs_filetype
   315  				0, 0, 0, 0, 0, 0, // fs_flags
   316  				0x19, 0xfe, 0xbf, 0x7, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   317  				0xff, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, // fs_rights_inheriting
   318  			},
   319  			expectedLog: `
   320  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
   321  <== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
   322  `,
   323  		},
   324  		{
   325  			name: "file",
   326  			fd:   fileFD,
   327  			expectedMemory: []byte{
   328  				4, 0, // fs_filetype
   329  				0, 0, 0, 0, 0, 0, // fs_flags
   330  				0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   331  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   332  			},
   333  			expectedLog: `
   334  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=4)
   335  <== (stat={filetype=REGULAR_FILE,fdflags=,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   336  `,
   337  		},
   338  		{
   339  			name: "dir",
   340  			fd:   dirFD,
   341  			expectedMemory: []byte{
   342  				3, 0, // fs_filetype
   343  				0, 0, 0, 0, 0, 0, // fs_flags
   344  				0x19, 0xfe, 0xbf, 0x7, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   345  				0xff, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, // fs_rights_inheriting
   346  			},
   347  			expectedLog: `
   348  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=5)
   349  <== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=FD_DATASYNC|FDSTAT_SET_FLAGS|FD_SYNC|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK,fs_rights_inheriting=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE|PATH_CREATE_DIRECTORY|PATH_CREATE_FILE|PATH_LINK_SOURCE|PATH_LINK_TARGET|PATH_OPEN|FD_READDIR|PATH_READLINK},errno=ESUCCESS)
   350  `,
   351  		},
   352  		{
   353  			name:          "bad FD",
   354  			fd:            -1,
   355  			expectedErrno: wasip1.ErrnoBadf,
   356  			expectedLog: `
   357  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=-1)
   358  <== (stat=,errno=EBADF)
   359  `,
   360  		},
   361  		{
   362  			name:          "resultFdstat exceeds the maximum valid address by 1",
   363  			fd:            dirFD,
   364  			resultFdstat:  memorySize - 24 + 1,
   365  			expectedErrno: wasip1.ErrnoFault,
   366  			expectedLog: `
   367  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=5)
   368  <== (stat=,errno=EFAULT)
   369  `,
   370  		},
   371  	}
   372  
   373  	for _, tt := range tests {
   374  		tc := tt
   375  
   376  		t.Run(tc.name, func(t *testing.T) {
   377  			defer log.Reset()
   378  
   379  			maskMemory(t, mod, len(tc.expectedMemory))
   380  
   381  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFdstatGetName, uint64(tc.fd), uint64(tc.resultFdstat))
   382  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   383  
   384  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
   385  			require.True(t, ok)
   386  			require.Equal(t, tc.expectedMemory, actual)
   387  		})
   388  	}
   389  }
   390  
   391  func Test_fdFdstatGet_StdioNonblock(t *testing.T) {
   392  	stdinR, stdinW := openPipe(t)
   393  	defer closePipe(stdinR, stdinW)
   394  
   395  	stdoutR, stdoutW := openPipe(t)
   396  	defer closePipe(stdoutR, stdoutW)
   397  
   398  	stderrR, stderrW := openPipe(t)
   399  	defer closePipe(stderrR, stderrW)
   400  
   401  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
   402  		WithStdin(stdinR).
   403  		WithStdout(stdoutW).
   404  		WithStderr(stderrW))
   405  	defer r.Close(testCtx)
   406  
   407  	stdin, stdout, stderr := uint64(0), uint64(1), uint64(2)
   408  	requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdin, uint64(wasip1.FD_NONBLOCK))
   409  	requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdout, uint64(wasip1.FD_NONBLOCK))
   410  	requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stderr, uint64(wasip1.FD_NONBLOCK))
   411  	log.Reset()
   412  
   413  	tests := []struct {
   414  		name           string
   415  		fd             int32
   416  		resultFdstat   uint32
   417  		expectedMemory []byte
   418  		expectedErrno  wasip1.Errno
   419  		expectedLog    string
   420  	}{
   421  		{
   422  			name: "stdin",
   423  			fd:   sys.FdStdin,
   424  			expectedMemory: []byte{
   425  				0, 0, // fs_filetype
   426  				4, 0, 0, 0, 0, 0, // fs_flags
   427  				0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   428  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   429  			},
   430  			expectedLog: `
   431  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=0)
   432  <== (stat={filetype=UNKNOWN,fdflags=NONBLOCK,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   433  `,
   434  		},
   435  		{
   436  			name: "stdout",
   437  			fd:   sys.FdStdout,
   438  			expectedMemory: []byte{
   439  				0, 0, // fs_filetype
   440  				4, 0, 0, 0, 0, 0, // fs_flags
   441  				0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   442  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   443  			},
   444  			expectedLog: `
   445  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=1)
   446  <== (stat={filetype=UNKNOWN,fdflags=NONBLOCK,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   447  `,
   448  		},
   449  		{
   450  			name: "stderr",
   451  			fd:   sys.FdStderr,
   452  			expectedMemory: []byte{
   453  				0, 0, // fs_filetype
   454  				4, 0, 0, 0, 0, 0, // fs_flags
   455  				0xff, 0x1, 0xe0, 0x8, 0x0, 0x0, 0x0, 0x0, // fs_rights_base
   456  				0, 0, 0, 0, 0, 0, 0, 0, // fs_rights_inheriting
   457  			},
   458  			expectedLog: `
   459  ==> wasi_snapshot_preview1.fd_fdstat_get(fd=2)
   460  <== (stat={filetype=UNKNOWN,fdflags=NONBLOCK,fs_rights_base=FD_DATASYNC|FD_READ|FD_SEEK|FDSTAT_SET_FLAGS|FD_SYNC|FD_TELL|FD_WRITE|FD_ADVISE|FD_ALLOCATE,fs_rights_inheriting=},errno=ESUCCESS)
   461  `,
   462  		},
   463  	}
   464  
   465  	for _, tt := range tests {
   466  		tc := tt
   467  
   468  		t.Run(tc.name, func(t *testing.T) {
   469  			defer log.Reset()
   470  
   471  			maskMemory(t, mod, len(tc.expectedMemory))
   472  
   473  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFdstatGetName, uint64(tc.fd), uint64(tc.resultFdstat))
   474  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   475  
   476  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
   477  			require.True(t, ok)
   478  			require.Equal(t, tc.expectedMemory, actual)
   479  		})
   480  	}
   481  }
   482  
   483  func Test_fdFdstatSetFlags(t *testing.T) {
   484  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
   485  
   486  	stdinR, stdinW := openPipe(t)
   487  	defer closePipe(stdinR, stdinW)
   488  
   489  	stdoutR, stdoutW := openPipe(t)
   490  	defer closePipe(stdoutR, stdoutW)
   491  
   492  	stderrR, stderrW := openPipe(t)
   493  	defer closePipe(stderrR, stderrW)
   494  
   495  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
   496  		WithStdin(stdinR).
   497  		WithStdout(stdoutW).
   498  		WithStderr(stderrW).
   499  		WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "/")))
   500  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   501  	preopen := fsc.RootFS()
   502  	defer r.Close(testCtx)
   503  
   504  	// First, O_CREATE the file with O_APPEND. We use O_EXCL because that
   505  	// triggers an EEXIST error if called a second time with O_CREATE. Our
   506  	// logic should clear O_CREATE preventing this.
   507  	const fileName = "file.txt"
   508  	// Create the target file.
   509  	fd, errno := fsc.OpenFile(preopen, fileName, os.O_RDWR|os.O_APPEND|os.O_CREATE|syscall.O_EXCL, 0o600)
   510  	require.EqualErrno(t, 0, errno)
   511  
   512  	// Write the initial text to the file.
   513  	f, ok := fsc.LookupFile(fd)
   514  	require.True(t, ok)
   515  	_, errno = f.File.Write([]byte("0123456789"))
   516  	require.EqualErrno(t, 0, errno)
   517  
   518  	writeWazero := func() {
   519  		iovs := uint32(1) // arbitrary offset
   520  		initialMemory := []byte{
   521  			'?',         // `iovs` is after this
   522  			18, 0, 0, 0, // = iovs[0].offset
   523  			4, 0, 0, 0, // = iovs[0].length
   524  			23, 0, 0, 0, // = iovs[1].offset
   525  			2, 0, 0, 0, // = iovs[1].length
   526  			'?',                // iovs[0].offset is after this
   527  			'w', 'a', 'z', 'e', // iovs[0].length bytes
   528  			'?',      // iovs[1].offset is after this
   529  			'r', 'o', // iovs[1].length bytes
   530  			'?',
   531  		}
   532  		iovsCount := uint32(2)       // The count of iovs
   533  		resultNwritten := uint32(26) // arbitrary offset
   534  
   535  		ok := mod.Memory().Write(0, initialMemory)
   536  		require.True(t, ok)
   537  
   538  		requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
   539  		require.Equal(t, `
   540  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2)
   541  <== (nwritten=6,errno=ESUCCESS)
   542  `, "\n"+log.String())
   543  		log.Reset()
   544  	}
   545  
   546  	requireFileContent := func(exp string) {
   547  		buf, err := os.ReadFile(joinPath(tmpDir, fileName))
   548  		require.NoError(t, err)
   549  		require.Equal(t, exp, string(buf))
   550  	}
   551  
   552  	// with O_APPEND flag, the data is appended to buffer.
   553  	writeWazero()
   554  	requireFileContent("0123456789" + "wazero")
   555  
   556  	// Let's remove O_APPEND.
   557  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(0))
   558  	require.Equal(t, `
   559  ==> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=4,flags=)
   560  <== errno=ESUCCESS
   561  `, "\n"+log.String()) // FIXME? flags==0 prints 'flags='
   562  	log.Reset()
   563  
   564  	// Without O_APPEND flag, the data is written at the beginning.
   565  	writeWazero()
   566  	requireFileContent("wazero6789" + "wazero")
   567  
   568  	// Restore the O_APPEND flag.
   569  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_APPEND))
   570  	require.Equal(t, `
   571  ==> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=4,flags=APPEND)
   572  <== errno=ESUCCESS
   573  `, "\n"+log.String()) // FIXME? flags==1 prints 'flags=APPEND'
   574  	log.Reset()
   575  
   576  	// with O_APPEND flag, the data is appended to buffer.
   577  	writeWazero()
   578  	requireFileContent("wazero6789" + "wazero" + "wazero")
   579  
   580  	t.Run("nonblock", func(t *testing.T) {
   581  		stdin, stdout, stderr := uint64(0), uint64(1), uint64(2)
   582  		requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdin, uint64(wasip1.FD_NONBLOCK))
   583  		requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stdout, uint64(wasip1.FD_NONBLOCK))
   584  		requireErrnoResult(t, 0, mod, wasip1.FdFdstatSetFlagsName, stderr, uint64(wasip1.FD_NONBLOCK))
   585  	})
   586  
   587  	t.Run("errors", func(t *testing.T) {
   588  		requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_DSYNC))
   589  		requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_RSYNC))
   590  		requireErrnoResult(t, wasip1.ErrnoInval, mod, wasip1.FdFdstatSetFlagsName, uint64(fd), uint64(wasip1.FD_SYNC))
   591  		requireErrnoResult(t, wasip1.ErrnoBadf, mod, wasip1.FdFdstatSetFlagsName, uint64(12345), uint64(wasip1.FD_APPEND))
   592  		requireErrnoResult(t, wasip1.ErrnoIsdir, mod, wasip1.FdFdstatSetFlagsName, uint64(3) /* preopen */, uint64(wasip1.FD_APPEND))
   593  		requireErrnoResult(t, wasip1.ErrnoIsdir, mod, wasip1.FdFdstatSetFlagsName, uint64(3), uint64(wasip1.FD_NONBLOCK))
   594  	})
   595  }
   596  
   597  // Test_fdFdstatSetRights only tests it is stubbed for GrainLang per #271
   598  func Test_fdFdstatSetRights(t *testing.T) {
   599  	log := requireErrnoNosys(t, wasip1.FdFdstatSetRightsName, 0, 0, 0)
   600  	require.Equal(t, `
   601  ==> wasi_snapshot_preview1.fd_fdstat_set_rights(fd=0,fs_rights_base=,fs_rights_inheriting=)
   602  <== errno=ENOSYS
   603  `, log)
   604  }
   605  
   606  func Test_fdFilestatGet(t *testing.T) {
   607  	file, dir := "animals.txt", "sub"
   608  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
   609  	defer r.Close(testCtx)
   610  	memorySize := mod.Memory().Size()
   611  
   612  	// open both paths without using WASI
   613  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   614  	preopen := fsc.RootFS()
   615  
   616  	fileFD, errno := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
   617  	require.EqualErrno(t, 0, errno)
   618  
   619  	dirFD, errno := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
   620  	require.EqualErrno(t, 0, errno)
   621  
   622  	tests := []struct {
   623  		name           string
   624  		fd             int32
   625  		resultFilestat uint32
   626  		expectedMemory []byte
   627  		expectedErrno  wasip1.Errno
   628  		expectedLog    string
   629  	}{
   630  		{
   631  			name: "stdin",
   632  			fd:   sys.FdStdin,
   633  			expectedMemory: []byte{
   634  				0, 0, 0, 0, 0, 0, 0, 0, // dev
   635  				0, 0, 0, 0, 0, 0, 0, 0, // ino
   636  				// expect block device because stdin isn't a real file
   637  				1, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
   638  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
   639  				0, 0, 0, 0, 0, 0, 0, 0, // size
   640  				0, 0, 0, 0, 0, 0, 0, 0, // atim
   641  				0, 0, 0, 0, 0, 0, 0, 0, // mtim
   642  				0, 0, 0, 0, 0, 0, 0, 0, // ctim
   643  			},
   644  			expectedLog: `
   645  ==> wasi_snapshot_preview1.fd_filestat_get(fd=0)
   646  <== (filestat={filetype=BLOCK_DEVICE,size=0,mtim=0},errno=ESUCCESS)
   647  `,
   648  		},
   649  		{
   650  			name: "stdout",
   651  			fd:   sys.FdStdout,
   652  			expectedMemory: []byte{
   653  				0, 0, 0, 0, 0, 0, 0, 0, // dev
   654  				0, 0, 0, 0, 0, 0, 0, 0, // ino
   655  				// expect block device because stdout isn't a real file
   656  				1, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
   657  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
   658  				0, 0, 0, 0, 0, 0, 0, 0, // size
   659  				0, 0, 0, 0, 0, 0, 0, 0, // atim
   660  				0, 0, 0, 0, 0, 0, 0, 0, // mtim
   661  				0, 0, 0, 0, 0, 0, 0, 0, // ctim
   662  			},
   663  			expectedLog: `
   664  ==> wasi_snapshot_preview1.fd_filestat_get(fd=1)
   665  <== (filestat={filetype=BLOCK_DEVICE,size=0,mtim=0},errno=ESUCCESS)
   666  `,
   667  		},
   668  		{
   669  			name: "stderr",
   670  			fd:   sys.FdStderr,
   671  			expectedMemory: []byte{
   672  				0, 0, 0, 0, 0, 0, 0, 0, // dev
   673  				0, 0, 0, 0, 0, 0, 0, 0, // ino
   674  				// expect block device because stderr isn't a real file
   675  				1, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
   676  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
   677  				0, 0, 0, 0, 0, 0, 0, 0, // size
   678  				0, 0, 0, 0, 0, 0, 0, 0, // atim
   679  				0, 0, 0, 0, 0, 0, 0, 0, // mtim
   680  				0, 0, 0, 0, 0, 0, 0, 0, // ctim
   681  			},
   682  			expectedLog: `
   683  ==> wasi_snapshot_preview1.fd_filestat_get(fd=2)
   684  <== (filestat={filetype=BLOCK_DEVICE,size=0,mtim=0},errno=ESUCCESS)
   685  `,
   686  		},
   687  		{
   688  			name: "root",
   689  			fd:   sys.FdPreopen,
   690  			expectedMemory: []byte{
   691  				0, 0, 0, 0, 0, 0, 0, 0, // dev
   692  				0, 0, 0, 0, 0, 0, 0, 0, // ino
   693  				3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
   694  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
   695  				0, 0, 0, 0, 0, 0, 0, 0, // size
   696  				0x0, 0x0, 0x7c, 0x78, 0x9d, 0xf2, 0x55, 0x16, // atim
   697  				0x0, 0x0, 0x7c, 0x78, 0x9d, 0xf2, 0x55, 0x16, // mtim
   698  				0x0, 0x0, 0x7c, 0x78, 0x9d, 0xf2, 0x55, 0x16, // ctim
   699  			},
   700  			expectedLog: `
   701  ==> wasi_snapshot_preview1.fd_filestat_get(fd=3)
   702  <== (filestat={filetype=DIRECTORY,size=0,mtim=1609459200000000000},errno=ESUCCESS)
   703  `,
   704  		},
   705  		{
   706  			name: "file",
   707  			fd:   fileFD,
   708  			expectedMemory: []byte{
   709  				0, 0, 0, 0, 0, 0, 0, 0, // dev
   710  				0, 0, 0, 0, 0, 0, 0, 0, // ino
   711  				4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
   712  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
   713  				30, 0, 0, 0, 0, 0, 0, 0, // size
   714  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
   715  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
   716  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
   717  			},
   718  			expectedLog: `
   719  ==> wasi_snapshot_preview1.fd_filestat_get(fd=4)
   720  <== (filestat={filetype=REGULAR_FILE,size=30,mtim=1667482413000000000},errno=ESUCCESS)
   721  `,
   722  		},
   723  		{
   724  			name: "dir",
   725  			fd:   dirFD,
   726  			expectedMemory: []byte{
   727  				0, 0, 0, 0, 0, 0, 0, 0, // dev
   728  				0, 0, 0, 0, 0, 0, 0, 0, // ino
   729  				3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
   730  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
   731  				0, 0, 0, 0, 0, 0, 0, 0, // size
   732  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // atim
   733  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // mtim
   734  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // ctim
   735  			},
   736  			expectedLog: `
   737  ==> wasi_snapshot_preview1.fd_filestat_get(fd=5)
   738  <== (filestat={filetype=DIRECTORY,size=0,mtim=1640995200000000000},errno=ESUCCESS)
   739  `,
   740  		},
   741  		{
   742  			name:          "bad FD",
   743  			fd:            -1,
   744  			expectedErrno: wasip1.ErrnoBadf,
   745  			expectedLog: `
   746  ==> wasi_snapshot_preview1.fd_filestat_get(fd=-1)
   747  <== (filestat=,errno=EBADF)
   748  `,
   749  		},
   750  		{
   751  			name:           "resultFilestat exceeds the maximum valid address by 1",
   752  			fd:             dirFD,
   753  			resultFilestat: memorySize - 64 + 1,
   754  			expectedErrno:  wasip1.ErrnoFault,
   755  			expectedLog: `
   756  ==> wasi_snapshot_preview1.fd_filestat_get(fd=5)
   757  <== (filestat=,errno=EFAULT)
   758  `,
   759  		},
   760  	}
   761  
   762  	for _, tt := range tests {
   763  		tc := tt
   764  
   765  		t.Run(tc.name, func(t *testing.T) {
   766  			defer log.Reset()
   767  
   768  			maskMemory(t, mod, len(tc.expectedMemory))
   769  
   770  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFilestatGetName, uint64(tc.fd), uint64(tc.resultFilestat))
   771  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   772  
   773  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
   774  			require.True(t, ok)
   775  			require.Equal(t, tc.expectedMemory, actual)
   776  		})
   777  	}
   778  }
   779  
   780  func Test_fdFilestatSetSize(t *testing.T) {
   781  	tmpDir := t.TempDir()
   782  
   783  	tests := []struct {
   784  		name                     string
   785  		size                     uint32
   786  		content, expectedContent []byte
   787  		expectedLog              string
   788  		expectedErrno            wasip1.Errno
   789  	}{
   790  		{
   791  			name:            "badf",
   792  			content:         []byte("badf"),
   793  			expectedContent: []byte("badf"),
   794  			expectedErrno:   wasip1.ErrnoBadf,
   795  			expectedLog: `
   796  ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=5,size=0)
   797  <== errno=EBADF
   798  `,
   799  		},
   800  		{
   801  			name:            "truncate",
   802  			content:         []byte("123456"),
   803  			expectedContent: []byte("12345"),
   804  			size:            5,
   805  			expectedErrno:   wasip1.ErrnoSuccess,
   806  			expectedLog: `
   807  ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=5)
   808  <== errno=ESUCCESS
   809  `,
   810  		},
   811  		{
   812  			name:            "truncate to zero",
   813  			content:         []byte("123456"),
   814  			expectedContent: []byte(""),
   815  			size:            0,
   816  			expectedErrno:   wasip1.ErrnoSuccess,
   817  			expectedLog: `
   818  ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=0)
   819  <== errno=ESUCCESS
   820  `,
   821  		},
   822  		{
   823  			name:            "truncate to expand",
   824  			content:         []byte("123456"),
   825  			expectedContent: append([]byte("123456"), make([]byte, 100)...),
   826  			size:            106,
   827  			expectedErrno:   wasip1.ErrnoSuccess,
   828  			expectedLog: `
   829  ==> wasi_snapshot_preview1.fd_filestat_set_size(fd=4,size=106)
   830  <== errno=ESUCCESS
   831  `,
   832  		},
   833  	}
   834  
   835  	for _, tt := range tests {
   836  		tc := tt
   837  		t.Run(tc.name, func(t *testing.T) {
   838  			filepath := path.Base(t.Name())
   839  			mod, fd, log, r := requireOpenFile(t, tmpDir, filepath, tc.content, false)
   840  			defer r.Close(testCtx)
   841  
   842  			if filepath == "badf" {
   843  				fd++
   844  			}
   845  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFilestatSetSizeName, uint64(fd), uint64(tc.size))
   846  
   847  			actual, err := os.ReadFile(joinPath(tmpDir, filepath))
   848  			require.NoError(t, err)
   849  			require.Equal(t, tc.expectedContent, actual)
   850  
   851  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   852  		})
   853  	}
   854  }
   855  
   856  func Test_fdFilestatSetTimes(t *testing.T) {
   857  	tmpDir := t.TempDir()
   858  
   859  	tests := []struct {
   860  		name          string
   861  		mtime, atime  int64
   862  		flags         uint16
   863  		expectedLog   string
   864  		expectedErrno wasip1.Errno
   865  	}{
   866  		{
   867  			name:          "badf",
   868  			expectedErrno: wasip1.ErrnoBadf,
   869  			expectedLog: `
   870  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=-1,atim=0,mtim=0,fst_flags=)
   871  <== errno=EBADF
   872  `,
   873  		},
   874  		{
   875  			name:          "a=omit,m=omit",
   876  			mtime:         1234,   // Must be ignored.
   877  			atime:         123451, // Must be ignored.
   878  			expectedErrno: wasip1.ErrnoSuccess,
   879  			expectedLog: `
   880  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=)
   881  <== errno=ESUCCESS
   882  `,
   883  		},
   884  		{
   885  			name:          "a=now,m=omit",
   886  			expectedErrno: wasip1.ErrnoSuccess,
   887  			mtime:         1234,   // Must be ignored.
   888  			atime:         123451, // Must be ignored.
   889  			flags:         wasip1.FstflagsAtimNow,
   890  			expectedLog: `
   891  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=ATIM_NOW)
   892  <== errno=ESUCCESS
   893  `,
   894  		},
   895  		{
   896  			name:          "a=omit,m=now",
   897  			expectedErrno: wasip1.ErrnoSuccess,
   898  			mtime:         1234,   // Must be ignored.
   899  			atime:         123451, // Must be ignored.
   900  			flags:         wasip1.FstflagsMtimNow,
   901  			expectedLog: `
   902  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=MTIM_NOW)
   903  <== errno=ESUCCESS
   904  `,
   905  		},
   906  		{
   907  			name:          "a=now,m=now",
   908  			expectedErrno: wasip1.ErrnoSuccess,
   909  			mtime:         1234,   // Must be ignored.
   910  			atime:         123451, // Must be ignored.
   911  			flags:         wasip1.FstflagsAtimNow | wasip1.FstflagsMtimNow,
   912  			expectedLog: `
   913  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=123451,mtim=1234,fst_flags=ATIM_NOW|MTIM_NOW)
   914  <== errno=ESUCCESS
   915  `,
   916  		},
   917  		{
   918  			name:          "a=now,m=set",
   919  			expectedErrno: wasip1.ErrnoSuccess,
   920  			mtime:         55555500,
   921  			atime:         1234, // Must be ignored.
   922  			flags:         wasip1.FstflagsAtimNow | wasip1.FstflagsMtim,
   923  			expectedLog: `
   924  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=1234,mtim=55555500,fst_flags=ATIM_NOW|MTIM)
   925  <== errno=ESUCCESS
   926  `,
   927  		},
   928  		{
   929  			name:          "a=set,m=now",
   930  			expectedErrno: wasip1.ErrnoSuccess,
   931  			mtime:         1234, // Must be ignored.
   932  			atime:         55555500,
   933  			flags:         wasip1.FstflagsAtim | wasip1.FstflagsMtimNow,
   934  			expectedLog: `
   935  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=55555500,mtim=1234,fst_flags=ATIM|MTIM_NOW)
   936  <== errno=ESUCCESS
   937  `,
   938  		},
   939  		{
   940  			name:          "a=set,m=omit",
   941  			expectedErrno: wasip1.ErrnoSuccess,
   942  			mtime:         1234, // Must be ignored.
   943  			atime:         55555500,
   944  			flags:         wasip1.FstflagsAtim,
   945  			expectedLog: `
   946  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=55555500,mtim=1234,fst_flags=ATIM)
   947  <== errno=ESUCCESS
   948  `,
   949  		},
   950  
   951  		{
   952  			name:          "a=omit,m=set",
   953  			expectedErrno: wasip1.ErrnoSuccess,
   954  			mtime:         55555500,
   955  			atime:         1234, // Must be ignored.
   956  			flags:         wasip1.FstflagsMtim,
   957  			expectedLog: `
   958  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=1234,mtim=55555500,fst_flags=MTIM)
   959  <== errno=ESUCCESS
   960  `,
   961  		},
   962  
   963  		{
   964  			name:          "a=set,m=set",
   965  			expectedErrno: wasip1.ErrnoSuccess,
   966  			mtime:         55555500,
   967  			atime:         6666666600,
   968  			flags:         wasip1.FstflagsAtim | wasip1.FstflagsMtim,
   969  			expectedLog: `
   970  ==> wasi_snapshot_preview1.fd_filestat_set_times(fd=4,atim=6666666600,mtim=55555500,fst_flags=ATIM|MTIM)
   971  <== errno=ESUCCESS
   972  `,
   973  		},
   974  	}
   975  
   976  	for _, tt := range tests {
   977  		tc := tt
   978  		t.Run(tc.name, func(t *testing.T) {
   979  			filepath := path.Base(t.Name())
   980  			mod, fd, log, r := requireOpenFile(t, tmpDir, filepath, []byte("anything"), false)
   981  			defer r.Close(testCtx)
   982  
   983  			sys := mod.(*wasm.ModuleInstance).Sys
   984  			fsc := sys.FS()
   985  
   986  			paramFd := fd
   987  			if filepath == "badf" {
   988  				paramFd = -1
   989  			}
   990  
   991  			f, ok := fsc.LookupFile(fd)
   992  			require.True(t, ok)
   993  
   994  			st, errno := f.File.Stat()
   995  			require.EqualErrno(t, 0, errno)
   996  			prevAtime, prevMtime := st.Atim, st.Mtim
   997  
   998  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdFilestatSetTimesName,
   999  				uint64(paramFd), uint64(tc.atime), uint64(tc.mtime),
  1000  				uint64(tc.flags),
  1001  			)
  1002  
  1003  			if tc.expectedErrno == wasip1.ErrnoSuccess {
  1004  				f, ok := fsc.LookupFile(fd)
  1005  				require.True(t, ok)
  1006  
  1007  				st, errno = f.File.Stat()
  1008  				require.EqualErrno(t, 0, errno)
  1009  				if tc.flags&wasip1.FstflagsAtim != 0 {
  1010  					require.Equal(t, tc.atime, st.Atim)
  1011  				} else if tc.flags&wasip1.FstflagsAtimNow != 0 {
  1012  					require.True(t, (sys.WalltimeNanos()-st.Atim) < time.Second.Nanoseconds())
  1013  				} else {
  1014  					require.Equal(t, prevAtime, st.Atim)
  1015  				}
  1016  				if tc.flags&wasip1.FstflagsMtim != 0 {
  1017  					require.Equal(t, tc.mtime, st.Mtim)
  1018  				} else if tc.flags&wasip1.FstflagsMtimNow != 0 {
  1019  					require.True(t, (sys.WalltimeNanos()-st.Mtim) < time.Second.Nanoseconds())
  1020  				} else {
  1021  					require.Equal(t, prevMtime, st.Mtim)
  1022  				}
  1023  			}
  1024  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1025  		})
  1026  	}
  1027  }
  1028  
  1029  func Test_fdPread(t *testing.T) {
  1030  	tmpDir := t.TempDir()
  1031  	mod, fd, log, r := requireOpenFile(t, tmpDir, "test_path", []byte("wazero"), true)
  1032  	defer r.Close(testCtx)
  1033  
  1034  	iovs := uint32(1) // arbitrary offset
  1035  	initialMemory := []byte{
  1036  		'?',         // `iovs` is after this
  1037  		18, 0, 0, 0, // = iovs[0].offset
  1038  		4, 0, 0, 0, // = iovs[0].length
  1039  		23, 0, 0, 0, // = iovs[1].offset
  1040  		2, 0, 0, 0, // = iovs[1].length
  1041  		'?',
  1042  	}
  1043  
  1044  	iovsCount := uint32(2)    // The count of iovs
  1045  	resultNread := uint32(26) // arbitrary offset
  1046  
  1047  	tests := []struct {
  1048  		name           string
  1049  		offset         int64
  1050  		expectedMemory []byte
  1051  		expectedLog    string
  1052  	}{
  1053  		{
  1054  			name:   "offset zero",
  1055  			offset: 0,
  1056  			expectedMemory: append(
  1057  				initialMemory,
  1058  				'w', 'a', 'z', 'e', // iovs[0].length bytes
  1059  				'?',      // iovs[1].offset is after this
  1060  				'r', 'o', // iovs[1].length bytes
  1061  				'?',        // resultNread is after this
  1062  				6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1063  				'?',
  1064  			),
  1065  			expectedLog: `
  1066  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0)
  1067  <== (nread=6,errno=ESUCCESS)
  1068  `,
  1069  		},
  1070  		{
  1071  			name:   "offset 2",
  1072  			offset: 2,
  1073  			expectedMemory: append(
  1074  				initialMemory,
  1075  				'z', 'e', 'r', 'o', // iovs[0].length bytes
  1076  				'?', '?', '?', '?', // resultNread is after this
  1077  				4, 0, 0, 0, // sum(iovs[...].length) == length of "zero"
  1078  				'?',
  1079  			),
  1080  			expectedLog: `
  1081  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2)
  1082  <== (nread=4,errno=ESUCCESS)
  1083  `,
  1084  		},
  1085  	}
  1086  
  1087  	for _, tt := range tests {
  1088  		tc := tt
  1089  		t.Run(tc.name, func(t *testing.T) {
  1090  			defer log.Reset()
  1091  
  1092  			maskMemory(t, mod, len(tc.expectedMemory))
  1093  
  1094  			ok := mod.Memory().Write(0, initialMemory)
  1095  			require.True(t, ok)
  1096  
  1097  			requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPreadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(tc.offset), uint64(resultNread))
  1098  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1099  
  1100  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
  1101  			require.True(t, ok)
  1102  			require.Equal(t, tc.expectedMemory, actual)
  1103  		})
  1104  	}
  1105  }
  1106  
  1107  func Test_fdPread_offset(t *testing.T) {
  1108  	tmpDir := t.TempDir()
  1109  	mod, fd, log, r := requireOpenFile(t, tmpDir, "test_path", []byte("wazero"), true)
  1110  	defer r.Close(testCtx)
  1111  
  1112  	// Do an initial fdPread.
  1113  
  1114  	iovs := uint32(1) // arbitrary offset
  1115  	initialMemory := []byte{
  1116  		'?',         // `iovs` is after this
  1117  		18, 0, 0, 0, // = iovs[0].offset
  1118  		4, 0, 0, 0, // = iovs[0].length
  1119  		23, 0, 0, 0, // = iovs[1].offset
  1120  		2, 0, 0, 0, // = iovs[1].length
  1121  		'?',
  1122  	}
  1123  	iovsCount := uint32(2)    // The count of iovs
  1124  	resultNread := uint32(26) // arbitrary offset
  1125  
  1126  	expectedMemory := append(
  1127  		initialMemory,
  1128  		'z', 'e', 'r', 'o', // iovs[0].length bytes
  1129  		'?', '?', '?', '?', // resultNread is after this
  1130  		4, 0, 0, 0, // sum(iovs[...].length) == length of "zero"
  1131  		'?',
  1132  	)
  1133  
  1134  	maskMemory(t, mod, len(expectedMemory))
  1135  
  1136  	ok := mod.Memory().Write(0, initialMemory)
  1137  	require.True(t, ok)
  1138  
  1139  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPreadName, uint64(fd), uint64(iovs), uint64(iovsCount), 2, uint64(resultNread))
  1140  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  1141  	require.True(t, ok)
  1142  	require.Equal(t, expectedMemory, actual)
  1143  
  1144  	// Verify that the fdPread didn't affect the fdRead offset.
  1145  
  1146  	expectedMemory = append(
  1147  		initialMemory,
  1148  		'w', 'a', 'z', 'e', // iovs[0].length bytes
  1149  		'?',      // iovs[1].offset is after this
  1150  		'r', 'o', // iovs[1].length bytes
  1151  		'?',        // resultNread is after this
  1152  		6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1153  		'?',
  1154  	)
  1155  
  1156  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNread))
  1157  	actual, ok = mod.Memory().Read(0, uint32(len(expectedMemory)))
  1158  	require.True(t, ok)
  1159  	require.Equal(t, expectedMemory, actual)
  1160  
  1161  	expectedLog := `
  1162  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2)
  1163  <== (nread=4,errno=ESUCCESS)
  1164  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2)
  1165  <== (nread=6,errno=ESUCCESS)
  1166  `
  1167  	require.Equal(t, expectedLog, "\n"+log.String())
  1168  }
  1169  
  1170  func Test_fdPread_Errors(t *testing.T) {
  1171  	tmpDir := t.TempDir()
  1172  	contents := []byte("wazero")
  1173  	mod, fd, log, r := requireOpenFile(t, tmpDir, "test_path", contents, true)
  1174  	defer r.Close(testCtx)
  1175  
  1176  	tests := []struct {
  1177  		name                         string
  1178  		fd                           int32
  1179  		iovs, iovsCount, resultNread uint32
  1180  		offset                       int64
  1181  		memory                       []byte
  1182  		expectedErrno                wasip1.Errno
  1183  		expectedLog                  string
  1184  	}{
  1185  		{
  1186  			name:          "invalid FD",
  1187  			fd:            42,                         // arbitrary invalid fd
  1188  			memory:        []byte{'?', '?', '?', '?'}, // pass result.nread validation
  1189  			expectedErrno: wasip1.ErrnoBadf,
  1190  			expectedLog: `
  1191  ==> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65532,iovs_len=0,offset=0)
  1192  <== (nread=,errno=EBADF)
  1193  `,
  1194  		},
  1195  		{
  1196  			name:          "out-of-memory reading iovs[0].offset",
  1197  			fd:            fd,
  1198  			iovs:          1,
  1199  			memory:        []byte{'?'},
  1200  			expectedErrno: wasip1.ErrnoFault,
  1201  			expectedLog: `
  1202  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=0,offset=0)
  1203  <== (nread=,errno=EFAULT)
  1204  `,
  1205  		},
  1206  		{
  1207  			name: "out-of-memory reading iovs[0].length",
  1208  			fd:   fd,
  1209  			iovs: 1, iovsCount: 1,
  1210  			memory: []byte{
  1211  				'?',        // `iovs` is after this
  1212  				9, 0, 0, 0, // = iovs[0].offset
  1213  			},
  1214  			expectedErrno: wasip1.ErrnoFault,
  1215  			expectedLog: `
  1216  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=1,offset=0)
  1217  <== (nread=,errno=EFAULT)
  1218  `,
  1219  		},
  1220  		{
  1221  			name: "iovs[0].offset is outside memory",
  1222  			fd:   fd,
  1223  			iovs: 1, iovsCount: 1,
  1224  			memory: []byte{
  1225  				'?',          // `iovs` is after this
  1226  				0, 0, 0x1, 0, // = iovs[0].offset on the second page
  1227  				1, 0, 0, 0, // = iovs[0].length
  1228  			},
  1229  			expectedErrno: wasip1.ErrnoFault,
  1230  			expectedLog: `
  1231  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=1,offset=0)
  1232  <== (nread=,errno=EFAULT)
  1233  `,
  1234  		},
  1235  		{
  1236  			name: "length to read exceeds memory by 1",
  1237  			fd:   fd,
  1238  			iovs: 1, iovsCount: 1,
  1239  			memory: []byte{
  1240  				'?',        // `iovs` is after this
  1241  				9, 0, 0, 0, // = iovs[0].offset
  1242  				0, 0, 0x1, 0, // = iovs[0].length on the second page
  1243  				'?',
  1244  			},
  1245  			expectedErrno: wasip1.ErrnoFault,
  1246  			expectedLog: `
  1247  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=1,offset=0)
  1248  <== (nread=,errno=EFAULT)
  1249  `,
  1250  		},
  1251  		{
  1252  			name: "resultNread offset is outside memory",
  1253  			fd:   fd,
  1254  			iovs: 1, iovsCount: 1,
  1255  			resultNread: 10, // 1 past memory
  1256  			memory: []byte{
  1257  				'?',        // `iovs` is after this
  1258  				9, 0, 0, 0, // = iovs[0].offset
  1259  				1, 0, 0, 0, // = iovs[0].length
  1260  				'?',
  1261  			},
  1262  			expectedErrno: wasip1.ErrnoFault,
  1263  			expectedLog: `
  1264  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=1,offset=0)
  1265  <== (nread=,errno=EFAULT)
  1266  `,
  1267  		},
  1268  		{
  1269  			name: "offset negative",
  1270  			fd:   fd,
  1271  			iovs: 1, iovsCount: 1,
  1272  			resultNread: 10,
  1273  			memory: []byte{
  1274  				'?',        // `iovs` is after this
  1275  				9, 0, 0, 0, // = iovs[0].offset
  1276  				1, 0, 0, 0, // = iovs[0].length
  1277  				'?',
  1278  				'?', '?', '?', '?',
  1279  			},
  1280  			offset:        int64(-1),
  1281  			expectedErrno: wasip1.ErrnoIo,
  1282  			expectedLog: `
  1283  ==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65523,iovs_len=1,offset=-1)
  1284  <== (nread=,errno=EIO)
  1285  `,
  1286  		},
  1287  	}
  1288  
  1289  	for _, tt := range tests {
  1290  		tc := tt
  1291  		t.Run(tc.name, func(t *testing.T) {
  1292  			defer log.Reset()
  1293  
  1294  			offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
  1295  
  1296  			memoryWriteOK := mod.Memory().Write(offset, tc.memory)
  1297  			require.True(t, memoryWriteOK)
  1298  
  1299  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPreadName, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount), uint64(tc.offset), uint64(tc.resultNread+offset))
  1300  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1301  		})
  1302  	}
  1303  }
  1304  
  1305  func Test_fdPrestatGet(t *testing.T) {
  1306  	fsConfig := wazero.NewFSConfig().WithDirMount(t.TempDir(), "/")
  1307  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  1308  	defer r.Close(testCtx)
  1309  
  1310  	resultPrestat := uint32(1) // arbitrary offset
  1311  	expectedMemory := []byte{
  1312  		'?',     // resultPrestat after this
  1313  		0,       // 8-bit tag indicating `prestat_dir`, the only available tag
  1314  		0, 0, 0, // 3-byte padding
  1315  		// the result path length field after this
  1316  		1, 0, 0, 0, // = in little endian encoding
  1317  		'?',
  1318  	}
  1319  
  1320  	maskMemory(t, mod, len(expectedMemory))
  1321  
  1322  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPrestatGetName, uint64(sys.FdPreopen), uint64(resultPrestat))
  1323  	require.Equal(t, `
  1324  ==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
  1325  <== (prestat={pr_name_len=1},errno=ESUCCESS)
  1326  `, "\n"+log.String())
  1327  
  1328  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  1329  	require.True(t, ok)
  1330  	require.Equal(t, expectedMemory, actual)
  1331  }
  1332  
  1333  func Test_fdPrestatGet_Errors(t *testing.T) {
  1334  	mod, dirFD, log, r := requireOpenFile(t, t.TempDir(), "tmp", nil, true)
  1335  	defer r.Close(testCtx)
  1336  
  1337  	memorySize := mod.Memory().Size()
  1338  	tests := []struct {
  1339  		name          string
  1340  		fd            int32
  1341  		resultPrestat uint32
  1342  		expectedErrno wasip1.Errno
  1343  		expectedLog   string
  1344  	}{
  1345  		{
  1346  			name:          "unopened FD",
  1347  			fd:            42, // arbitrary invalid Fd
  1348  			resultPrestat: 0,  // valid offset
  1349  			expectedErrno: wasip1.ErrnoBadf,
  1350  			expectedLog: `
  1351  ==> wasi_snapshot_preview1.fd_prestat_get(fd=42)
  1352  <== (prestat=,errno=EBADF)
  1353  `,
  1354  		},
  1355  		{
  1356  			name:          "not pre-opened FD",
  1357  			fd:            dirFD,
  1358  			resultPrestat: 0, // valid offset
  1359  			expectedErrno: wasip1.ErrnoBadf,
  1360  			expectedLog: `
  1361  ==> wasi_snapshot_preview1.fd_prestat_get(fd=4)
  1362  <== (prestat=,errno=EBADF)
  1363  `,
  1364  		},
  1365  		{
  1366  			name:          "out-of-memory resultPrestat",
  1367  			fd:            sys.FdPreopen,
  1368  			resultPrestat: memorySize,
  1369  			expectedErrno: wasip1.ErrnoFault,
  1370  			expectedLog: `
  1371  ==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
  1372  <== (prestat=,errno=EFAULT)
  1373  `,
  1374  		},
  1375  	}
  1376  
  1377  	for _, tt := range tests {
  1378  		tc := tt
  1379  
  1380  		t.Run(tc.name, func(t *testing.T) {
  1381  			defer log.Reset()
  1382  
  1383  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPrestatGetName, uint64(tc.fd), uint64(tc.resultPrestat))
  1384  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1385  		})
  1386  	}
  1387  }
  1388  
  1389  func Test_fdPrestatDirName(t *testing.T) {
  1390  	fsConfig := wazero.NewFSConfig().WithDirMount(t.TempDir(), "/")
  1391  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  1392  	defer r.Close(testCtx)
  1393  
  1394  	path := uint32(1)    // arbitrary offset
  1395  	pathLen := uint32(0) // shorter than len("/") to prove truncation is ok
  1396  	expectedMemory := []byte{
  1397  		'?', '?', '?', '?',
  1398  	}
  1399  
  1400  	maskMemory(t, mod, len(expectedMemory))
  1401  
  1402  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPrestatDirNameName, uint64(sys.FdPreopen), uint64(path), uint64(pathLen))
  1403  	require.Equal(t, `
  1404  ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
  1405  <== (path=,errno=ESUCCESS)
  1406  `, "\n"+log.String())
  1407  
  1408  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  1409  	require.True(t, ok)
  1410  	require.Equal(t, expectedMemory, actual)
  1411  }
  1412  
  1413  func Test_fdPrestatDirName_Errors(t *testing.T) {
  1414  	mod, dirFD, log, r := requireOpenFile(t, t.TempDir(), "tmp", nil, true)
  1415  	defer r.Close(testCtx)
  1416  
  1417  	memorySize := mod.Memory().Size()
  1418  	maskMemory(t, mod, 10)
  1419  
  1420  	validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_dir_name. We chose 0 here.
  1421  	pathLen := uint32(len("/"))
  1422  
  1423  	tests := []struct {
  1424  		name          string
  1425  		fd            int32
  1426  		path          uint32
  1427  		pathLen       uint32
  1428  		expectedErrno wasip1.Errno
  1429  		expectedLog   string
  1430  	}{
  1431  		{
  1432  			name:          "out-of-memory path",
  1433  			fd:            sys.FdPreopen,
  1434  			path:          memorySize,
  1435  			pathLen:       pathLen,
  1436  			expectedErrno: wasip1.ErrnoFault,
  1437  			expectedLog: `
  1438  ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
  1439  <== (path=,errno=EFAULT)
  1440  `,
  1441  		},
  1442  		{
  1443  			name:          "path exceeds the maximum valid address by 1",
  1444  			fd:            sys.FdPreopen,
  1445  			path:          memorySize - pathLen + 1,
  1446  			pathLen:       pathLen,
  1447  			expectedErrno: wasip1.ErrnoFault,
  1448  			expectedLog: `
  1449  ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
  1450  <== (path=,errno=EFAULT)
  1451  `,
  1452  		},
  1453  		{
  1454  			name:          "pathLen exceeds the length of the dir name",
  1455  			fd:            sys.FdPreopen,
  1456  			path:          validAddress,
  1457  			pathLen:       pathLen + 1,
  1458  			expectedErrno: wasip1.ErrnoNametoolong,
  1459  			expectedLog: `
  1460  ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
  1461  <== (path=,errno=ENAMETOOLONG)
  1462  `,
  1463  		},
  1464  		{
  1465  			name:          "unopened FD",
  1466  			fd:            42, // arbitrary invalid fd
  1467  			path:          validAddress,
  1468  			pathLen:       pathLen,
  1469  			expectedErrno: wasip1.ErrnoBadf,
  1470  			expectedLog: `
  1471  ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=42)
  1472  <== (path=,errno=EBADF)
  1473  `,
  1474  		},
  1475  		{
  1476  			name:          "not pre-opened FD",
  1477  			fd:            dirFD,
  1478  			path:          validAddress,
  1479  			pathLen:       pathLen,
  1480  			expectedErrno: wasip1.ErrnoBadf,
  1481  			expectedLog: `
  1482  ==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4)
  1483  <== (path=,errno=EBADF)
  1484  `,
  1485  		},
  1486  	}
  1487  
  1488  	for _, tt := range tests {
  1489  		tc := tt
  1490  
  1491  		t.Run(tc.name, func(t *testing.T) {
  1492  			defer log.Reset()
  1493  
  1494  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPrestatDirNameName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
  1495  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1496  		})
  1497  	}
  1498  }
  1499  
  1500  func Test_fdPwrite(t *testing.T) {
  1501  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  1502  	pathName := "test_path"
  1503  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
  1504  	defer r.Close(testCtx)
  1505  
  1506  	iovs := uint32(1) // arbitrary offset
  1507  	initialMemory := []byte{
  1508  		'?',         // `iovs` is after this
  1509  		18, 0, 0, 0, // = iovs[0].offset
  1510  		4, 0, 0, 0, // = iovs[0].length
  1511  		23, 0, 0, 0, // = iovs[1].offset
  1512  		2, 0, 0, 0, // = iovs[1].length
  1513  		'?',
  1514  		'w', 'a', 'z', 'e', // iovs[0].length bytes
  1515  		'?',      // iovs[1].offset is after this
  1516  		'r', 'o', // iovs[1].length bytes
  1517  	}
  1518  
  1519  	iovsCount := uint32(2) // The count of iovs
  1520  	resultNwritten := len(initialMemory) + 1
  1521  
  1522  	tests := []struct {
  1523  		name             string
  1524  		offset           int64
  1525  		expectedMemory   []byte
  1526  		expectedContents string
  1527  		expectedLog      string
  1528  	}{
  1529  		{
  1530  			name:   "offset zero",
  1531  			offset: 0,
  1532  			expectedMemory: append(
  1533  				initialMemory,
  1534  				'?',        // resultNwritten is after this
  1535  				6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1536  				'?',
  1537  			),
  1538  			expectedContents: "wazero",
  1539  			expectedLog: `
  1540  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=1,iovs_len=2,offset=0)
  1541  <== (nwritten=6,errno=ESUCCESS)
  1542  `,
  1543  		},
  1544  		{
  1545  			name:   "offset 2",
  1546  			offset: 2,
  1547  			expectedMemory: append(
  1548  				initialMemory,
  1549  				'?',        // resultNwritten is after this
  1550  				6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1551  				'?',
  1552  			),
  1553  			expectedContents: "wawazero", // "wa" from the first test!
  1554  			expectedLog: `
  1555  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=1,iovs_len=2,offset=2)
  1556  <== (nwritten=6,errno=ESUCCESS)
  1557  `,
  1558  		},
  1559  	}
  1560  
  1561  	for _, tt := range tests {
  1562  		tc := tt
  1563  		t.Run(tc.name, func(t *testing.T) {
  1564  			defer log.Reset()
  1565  
  1566  			maskMemory(t, mod, len(tc.expectedMemory))
  1567  
  1568  			ok := mod.Memory().Write(0, initialMemory)
  1569  			require.True(t, ok)
  1570  
  1571  			requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPwriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(tc.offset), uint64(resultNwritten))
  1572  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1573  
  1574  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
  1575  			require.True(t, ok)
  1576  			require.Equal(t, tc.expectedMemory, actual)
  1577  
  1578  			// Ensure the contents were really written
  1579  			b, err := os.ReadFile(joinPath(tmpDir, pathName))
  1580  			require.NoError(t, err)
  1581  			require.Equal(t, tc.expectedContents, string(b))
  1582  		})
  1583  	}
  1584  }
  1585  
  1586  func Test_fdPwrite_offset(t *testing.T) {
  1587  	tmpDir := t.TempDir()
  1588  	pathName := "test_path"
  1589  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
  1590  	defer r.Close(testCtx)
  1591  
  1592  	// Do an initial fdPwrite.
  1593  
  1594  	iovs := uint32(1) // arbitrary offset
  1595  	pwriteMemory := []byte{
  1596  		'?',         // `iovs` is after this
  1597  		10, 0, 0, 0, // = iovs[0].offset
  1598  		3, 0, 0, 0, // = iovs[0].length
  1599  		'?',
  1600  		'e', 'r', 'o', // iovs[0].length bytes
  1601  		'?', // resultNwritten is after this
  1602  	}
  1603  	iovsCount := uint32(1) // The count of iovs
  1604  	resultNwritten := len(pwriteMemory) + 4
  1605  
  1606  	expectedMemory := append(
  1607  		pwriteMemory,
  1608  		'?', '?', '?', '?', // resultNwritten is after this
  1609  		3, 0, 0, 0, // sum(iovs[...].length) == length of "ero"
  1610  		'?',
  1611  	)
  1612  
  1613  	maskMemory(t, mod, len(expectedMemory))
  1614  
  1615  	ok := mod.Memory().Write(0, pwriteMemory)
  1616  	require.True(t, ok)
  1617  
  1618  	// Write the last half first, to offset 3
  1619  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdPwriteName, uint64(fd), uint64(iovs), uint64(iovsCount), 3, uint64(resultNwritten))
  1620  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  1621  	require.True(t, ok)
  1622  	require.Equal(t, expectedMemory, actual)
  1623  
  1624  	// Verify that the fdPwrite didn't affect the fdWrite offset.
  1625  	writeMemory := []byte{
  1626  		'?',         // `iovs` is after this
  1627  		10, 0, 0, 0, // = iovs[0].offset
  1628  		3, 0, 0, 0, // = iovs[0].length
  1629  		'?',
  1630  		'w', 'a', 'z', // iovs[0].length bytes
  1631  		'?', // resultNwritten is after this
  1632  	}
  1633  	expectedMemory = append(
  1634  		writeMemory,
  1635  		'?', '?', '?', '?', // resultNwritten is after this
  1636  		3, 0, 0, 0, // sum(iovs[...].length) == length of "waz"
  1637  		'?',
  1638  	)
  1639  
  1640  	ok = mod.Memory().Write(0, writeMemory)
  1641  	require.True(t, ok)
  1642  
  1643  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
  1644  	actual, ok = mod.Memory().Read(0, uint32(len(expectedMemory)))
  1645  	require.True(t, ok)
  1646  	require.Equal(t, expectedMemory, actual)
  1647  
  1648  	expectedLog := `
  1649  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=1,iovs_len=1,offset=3)
  1650  <== (nwritten=3,errno=ESUCCESS)
  1651  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=1)
  1652  <== (nwritten=3,errno=ESUCCESS)
  1653  `
  1654  	require.Equal(t, expectedLog, "\n"+log.String())
  1655  
  1656  	// Ensure the contents were really written
  1657  	b, err := os.ReadFile(joinPath(tmpDir, pathName))
  1658  	require.NoError(t, err)
  1659  	require.Equal(t, "wazero", string(b))
  1660  }
  1661  
  1662  func Test_fdPwrite_Errors(t *testing.T) {
  1663  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  1664  	pathName := "test_path"
  1665  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
  1666  	defer r.Close(testCtx)
  1667  
  1668  	tests := []struct {
  1669  		name                            string
  1670  		fd                              int32
  1671  		iovs, iovsCount, resultNwritten uint32
  1672  		offset                          int64
  1673  		memory                          []byte
  1674  		expectedErrno                   wasip1.Errno
  1675  		expectedLog                     string
  1676  	}{
  1677  		{
  1678  			name:          "invalid FD",
  1679  			fd:            42,                         // arbitrary invalid fd
  1680  			memory:        []byte{'?', '?', '?', '?'}, // pass result.nwritten validation
  1681  			expectedErrno: wasip1.ErrnoBadf,
  1682  			expectedLog: `
  1683  ==> wasi_snapshot_preview1.fd_pwrite(fd=42,iovs=65532,iovs_len=0,offset=0)
  1684  <== (nwritten=,errno=EBADF)
  1685  `,
  1686  		},
  1687  		{
  1688  			name:          "out-of-memory writing iovs[0].offset",
  1689  			fd:            fd,
  1690  			iovs:          1,
  1691  			memory:        []byte{'?'},
  1692  			expectedErrno: wasip1.ErrnoFault,
  1693  			expectedLog: `
  1694  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65536,iovs_len=0,offset=0)
  1695  <== (nwritten=,errno=EFAULT)
  1696  `,
  1697  		},
  1698  		{
  1699  			name: "out-of-memory writing iovs[0].length",
  1700  			fd:   fd,
  1701  			iovs: 1, iovsCount: 1,
  1702  			memory: []byte{
  1703  				'?',        // `iovs` is after this
  1704  				9, 0, 0, 0, // = iovs[0].offset
  1705  			},
  1706  			expectedErrno: wasip1.ErrnoFault,
  1707  			expectedLog: `
  1708  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65532,iovs_len=1,offset=0)
  1709  <== (nwritten=,errno=EFAULT)
  1710  `,
  1711  		},
  1712  		{
  1713  			name: "iovs[0].offset is outside memory",
  1714  			fd:   fd,
  1715  			iovs: 1, iovsCount: 1,
  1716  			memory: []byte{
  1717  				'?',          // `iovs` is after this
  1718  				0, 0, 0x1, 0, // = iovs[0].offset on the second page
  1719  				1, 0, 0, 0, // = iovs[0].length
  1720  			},
  1721  			expectedErrno: wasip1.ErrnoFault,
  1722  			expectedLog: `
  1723  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65528,iovs_len=1,offset=0)
  1724  <== (nwritten=,errno=EFAULT)
  1725  `,
  1726  		},
  1727  		{
  1728  			name: "length to write exceeds memory by 1",
  1729  			fd:   fd,
  1730  			iovs: 1, iovsCount: 1,
  1731  			memory: []byte{
  1732  				'?',        // `iovs` is after this
  1733  				9, 0, 0, 0, // = iovs[0].offset
  1734  				0, 0, 0x1, 0, // = iovs[0].length on the second page
  1735  				'?',
  1736  			},
  1737  			expectedErrno: wasip1.ErrnoFault,
  1738  			expectedLog: `
  1739  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65527,iovs_len=1,offset=0)
  1740  <== (nwritten=,errno=EFAULT)
  1741  `,
  1742  		},
  1743  		{
  1744  			name: "resultNwritten offset is outside memory",
  1745  			fd:   fd,
  1746  			iovs: 1, iovsCount: 1,
  1747  			resultNwritten: 10, // 1 past memory
  1748  			memory: []byte{
  1749  				'?',        // `iovs` is after this
  1750  				9, 0, 0, 0, // = iovs[0].offset
  1751  				1, 0, 0, 0, // = iovs[0].length
  1752  				'?',
  1753  			},
  1754  			expectedErrno: wasip1.ErrnoFault,
  1755  			expectedLog: `
  1756  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65527,iovs_len=1,offset=0)
  1757  <== (nwritten=,errno=EFAULT)
  1758  `,
  1759  		},
  1760  		{
  1761  			name: "offset negative",
  1762  			fd:   fd,
  1763  			iovs: 1, iovsCount: 1,
  1764  			resultNwritten: 10,
  1765  			memory: []byte{
  1766  				'?',        // `iovs` is after this
  1767  				9, 0, 0, 0, // = iovs[0].offset
  1768  				1, 0, 0, 0, // = iovs[0].length
  1769  				'?',
  1770  				'?', '?', '?', '?',
  1771  			},
  1772  			offset:        int64(-1),
  1773  			expectedErrno: wasip1.ErrnoIo,
  1774  			expectedLog: `
  1775  ==> wasi_snapshot_preview1.fd_pwrite(fd=4,iovs=65523,iovs_len=1,offset=-1)
  1776  <== (nwritten=,errno=EIO)
  1777  `,
  1778  		},
  1779  	}
  1780  
  1781  	for _, tt := range tests {
  1782  		tc := tt
  1783  		t.Run(tc.name, func(t *testing.T) {
  1784  			defer log.Reset()
  1785  
  1786  			offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
  1787  
  1788  			memoryWriteOK := mod.Memory().Write(offset, tc.memory)
  1789  			require.True(t, memoryWriteOK)
  1790  
  1791  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdPwriteName, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount), uint64(tc.offset), uint64(tc.resultNwritten+offset))
  1792  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1793  		})
  1794  	}
  1795  }
  1796  
  1797  func Test_fdRead(t *testing.T) {
  1798  	mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
  1799  	defer r.Close(testCtx)
  1800  
  1801  	iovs := uint32(1) // arbitrary offset
  1802  	initialMemory := []byte{
  1803  		'?',         // `iovs` is after this
  1804  		26, 0, 0, 0, // = iovs[0].offset
  1805  		4, 0, 0, 0, // = iovs[0].length
  1806  		31, 0, 0, 0, // = iovs[1].offset
  1807  		0, 0, 0, 0, // = iovs[1].length == 0 !!
  1808  		31, 0, 0, 0, // = iovs[2].offset
  1809  		2, 0, 0, 0, // = iovs[2].length
  1810  		'?',
  1811  	}
  1812  	iovsCount := uint32(3)    // The count of iovs
  1813  	resultNread := uint32(34) // arbitrary offset
  1814  	expectedMemory := append(
  1815  		initialMemory,
  1816  		'w', 'a', 'z', 'e', // iovs[0].length bytes
  1817  		'?',      // iovs[2].offset is after this
  1818  		'r', 'o', // iovs[2].length bytes
  1819  		'?',        // resultNread is after this
  1820  		6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1821  		'?',
  1822  	)
  1823  
  1824  	maskMemory(t, mod, len(expectedMemory))
  1825  
  1826  	ok := mod.Memory().Write(0, initialMemory)
  1827  	require.True(t, ok)
  1828  
  1829  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReadName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNread))
  1830  	require.Equal(t, `
  1831  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=3)
  1832  <== (nread=6,errno=ESUCCESS)
  1833  `, "\n"+log.String())
  1834  
  1835  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  1836  	require.True(t, ok)
  1837  	require.Equal(t, expectedMemory, actual)
  1838  }
  1839  
  1840  func Test_fdRead_Errors(t *testing.T) {
  1841  	mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
  1842  	defer r.Close(testCtx)
  1843  
  1844  	tests := []struct {
  1845  		name                         string
  1846  		fd                           int32
  1847  		iovs, iovsCount, resultNread uint32
  1848  		memory                       []byte
  1849  		expectedErrno                wasip1.Errno
  1850  		expectedLog                  string
  1851  	}{
  1852  		{
  1853  			name:          "invalid FD",
  1854  			fd:            42,                         // arbitrary invalid fd
  1855  			memory:        []byte{'?', '?', '?', '?'}, // pass result.nread validation
  1856  			expectedErrno: wasip1.ErrnoBadf,
  1857  			expectedLog: `
  1858  ==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65532,iovs_len=65532)
  1859  <== (nread=,errno=EBADF)
  1860  `,
  1861  		},
  1862  		{
  1863  			name:          "out-of-memory reading iovs[0].offset",
  1864  			fd:            fd,
  1865  			iovs:          1,
  1866  			memory:        []byte{'?'},
  1867  			expectedErrno: wasip1.ErrnoFault,
  1868  			expectedLog: `
  1869  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535)
  1870  <== (nread=,errno=EFAULT)
  1871  `,
  1872  		},
  1873  		{
  1874  			name: "out-of-memory reading iovs[0].length",
  1875  			fd:   fd,
  1876  			iovs: 1, iovsCount: 1,
  1877  			memory: []byte{
  1878  				'?',        // `iovs` is after this
  1879  				9, 0, 0, 0, // = iovs[0].offset
  1880  			},
  1881  			expectedErrno: wasip1.ErrnoFault,
  1882  			expectedLog: `
  1883  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532)
  1884  <== (nread=,errno=EFAULT)
  1885  `,
  1886  		},
  1887  		{
  1888  			name: "iovs[0].offset is outside memory",
  1889  			fd:   fd,
  1890  			iovs: 1, iovsCount: 1,
  1891  			memory: []byte{
  1892  				'?',          // `iovs` is after this
  1893  				0, 0, 0x1, 0, // = iovs[0].offset on the second page
  1894  				1, 0, 0, 0, // = iovs[0].length
  1895  			},
  1896  			expectedErrno: wasip1.ErrnoFault,
  1897  			expectedLog: `
  1898  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528)
  1899  <== (nread=,errno=EFAULT)
  1900  `,
  1901  		},
  1902  		{
  1903  			name: "length to read exceeds memory by 1",
  1904  			fd:   fd,
  1905  			iovs: 1, iovsCount: 1,
  1906  			memory: []byte{
  1907  				'?',        // `iovs` is after this
  1908  				9, 0, 0, 0, // = iovs[0].offset
  1909  				0, 0, 0x1, 0, // = iovs[0].length on the second page
  1910  				'?',
  1911  			},
  1912  			expectedErrno: wasip1.ErrnoFault,
  1913  			expectedLog: `
  1914  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527)
  1915  <== (nread=,errno=EFAULT)
  1916  `,
  1917  		},
  1918  		{
  1919  			name: "resultNread offset is outside memory",
  1920  			fd:   fd,
  1921  			iovs: 1, iovsCount: 1,
  1922  			resultNread: 10, // 1 past memory
  1923  			memory: []byte{
  1924  				'?',        // `iovs` is after this
  1925  				9, 0, 0, 0, // = iovs[0].offset
  1926  				1, 0, 0, 0, // = iovs[0].length
  1927  				'?',
  1928  			},
  1929  			expectedErrno: wasip1.ErrnoFault,
  1930  			expectedLog: `
  1931  ==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527)
  1932  <== (nread=,errno=EFAULT)
  1933  `,
  1934  		},
  1935  	}
  1936  
  1937  	for _, tt := range tests {
  1938  		tc := tt
  1939  		t.Run(tc.name, func(t *testing.T) {
  1940  			defer log.Reset()
  1941  
  1942  			offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
  1943  
  1944  			memoryWriteOK := mod.Memory().Write(offset, tc.memory)
  1945  			require.True(t, memoryWriteOK)
  1946  
  1947  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdReadName, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.resultNread+offset))
  1948  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1949  		})
  1950  	}
  1951  }
  1952  
  1953  var (
  1954  	direntDot = []byte{
  1955  		1, 0, 0, 0, 0, 0, 0, 0, // d_next = 1
  1956  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1957  		1, 0, 0, 0, // d_namlen = 1 character
  1958  		3, 0, 0, 0, // d_type =  directory
  1959  		'.', // name
  1960  	}
  1961  	direntDotDot = []byte{
  1962  		2, 0, 0, 0, 0, 0, 0, 0, // d_next = 2
  1963  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1964  		2, 0, 0, 0, // d_namlen = 2 characters
  1965  		3, 0, 0, 0, // d_type =  directory
  1966  		'.', '.', // name
  1967  	}
  1968  	dirent1 = []byte{
  1969  		3, 0, 0, 0, 0, 0, 0, 0, // d_next = 3
  1970  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1971  		1, 0, 0, 0, // d_namlen = 1 character
  1972  		4, 0, 0, 0, // d_type = regular_file
  1973  		'-', // name
  1974  	}
  1975  	dirent2 = []byte{
  1976  		4, 0, 0, 0, 0, 0, 0, 0, // d_next = 4
  1977  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1978  		2, 0, 0, 0, // d_namlen = 1 character
  1979  		3, 0, 0, 0, // d_type =  directory
  1980  		'a', '-', // name
  1981  	}
  1982  	dirent3 = []byte{
  1983  		5, 0, 0, 0, 0, 0, 0, 0, // d_next = 5
  1984  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1985  		3, 0, 0, 0, // d_namlen = 3 characters
  1986  		4, 0, 0, 0, // d_type = regular_file
  1987  		'a', 'b', '-', // name
  1988  	}
  1989  
  1990  	// TODO: this entry is intended to test reading of a symbolic link entry,
  1991  	// tho it requires modifying fstest.FS to contain this file.
  1992  	// dirent4 = []byte{
  1993  	// 	6, 0, 0, 0, 0, 0, 0, 0, // d_next = 6
  1994  	// 	0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1995  	// 	2, 0, 0, 0, // d_namlen = 2 characters
  1996  	// 	7, 0, 0, 0, // d_type = symbolic_link
  1997  	// 	'l', 'n', // name
  1998  	// }
  1999  
  2000  	dirents = bytes.Join([][]byte{
  2001  		direntDot,
  2002  		direntDotDot,
  2003  		dirent1,
  2004  		dirent2,
  2005  		dirent3,
  2006  		// dirent4,
  2007  	}, nil)
  2008  )
  2009  
  2010  func Test_fdReaddir(t *testing.T) {
  2011  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
  2012  	defer r.Close(testCtx)
  2013  
  2014  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2015  	preopen := fsc.RootFS()
  2016  
  2017  	fd, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
  2018  	require.EqualErrno(t, 0, errno)
  2019  
  2020  	tests := []struct {
  2021  		name            string
  2022  		initialDir      string
  2023  		dir             func()
  2024  		bufLen          uint32
  2025  		cookie          int64
  2026  		expectedMem     []byte
  2027  		expectedMemSize int
  2028  		expectedBufused uint32
  2029  		expectedCookie  uint64
  2030  	}{
  2031  		{
  2032  			name:            "empty dir",
  2033  			initialDir:      "emptydir",
  2034  			bufLen:          wasip1.DirentSize + 1, // size of one entry
  2035  			cookie:          0,
  2036  			expectedBufused: wasip1.DirentSize + 1, // one dot entry
  2037  			expectedMem:     direntDot,
  2038  			expectedCookie:  1,
  2039  		},
  2040  		{
  2041  			name:            "full read",
  2042  			initialDir:      "dir",
  2043  			bufLen:          4096,
  2044  			cookie:          0,
  2045  			expectedBufused: 129, // length of all entries
  2046  			expectedMem:     dirents,
  2047  			expectedCookie:  5,
  2048  		},
  2049  		{
  2050  			name:            "can't read name",
  2051  			initialDir:      "dir",
  2052  			bufLen:          wasip1.DirentSize, // length is long enough for first, but not the name.
  2053  			cookie:          0,
  2054  			expectedBufused: wasip1.DirentSize,             // == bufLen which is the size of the dirent
  2055  			expectedMem:     direntDot[:wasip1.DirentSize], // header without name
  2056  			expectedCookie:  0,
  2057  		},
  2058  		{
  2059  			name:            "read exactly first",
  2060  			initialDir:      "dir",
  2061  			bufLen:          25, // length is long enough for first + the name, but not more.
  2062  			cookie:          0,
  2063  			expectedBufused: 25, // length to read exactly first.
  2064  			expectedMem:     direntDot,
  2065  			expectedCookie:  1,
  2066  		},
  2067  		{
  2068  			name:       "read exactly second",
  2069  			initialDir: "dir",
  2070  			dir: func() {
  2071  				f, _ := fsc.LookupFile(fd)
  2072  				rdd, _ := fsc.LookupReaddir(fd, f)
  2073  				_ = rdd.Advance()
  2074  			},
  2075  
  2076  			bufLen:          27, // length is long enough for exactly second.
  2077  			cookie:          1,  // d_next of first
  2078  			expectedBufused: 27, // length to read exactly second.
  2079  			expectedMem:     direntDotDot,
  2080  			expectedCookie:  2,
  2081  		},
  2082  		{
  2083  			name:       "read second and a little more",
  2084  			initialDir: "dir",
  2085  			dir: func() {
  2086  				f, _ := fsc.LookupFile(fd)
  2087  				rdd, _ := fsc.LookupReaddir(fd, f)
  2088  				_ = rdd.Advance()
  2089  			},
  2090  			bufLen:          30, // length is longer than the second entry, but not long enough for a header.
  2091  			cookie:          1,  // d_next of first
  2092  			expectedBufused: 30, // length to read some more, but not enough for a header, so buf was exhausted.
  2093  			expectedMem:     direntDotDot,
  2094  			expectedMemSize: len(direntDotDot), // we do not want to compare the full buffer since we don't know what the leftover 4 bytes will contain.
  2095  			expectedCookie:  2,
  2096  		},
  2097  		{
  2098  			name:       "read second and header of third",
  2099  			initialDir: "dir",
  2100  			dir: func() {
  2101  				f, _ := fsc.LookupFile(fd)
  2102  				rdd, _ := fsc.LookupReaddir(fd, f)
  2103  				_ = rdd.Advance()
  2104  			},
  2105  			bufLen:          50, // length is longer than the second entry + enough for the header of third.
  2106  			cookie:          1,  // d_next of first
  2107  			expectedBufused: 50, // length to read exactly second and the header of third.
  2108  			expectedMem:     append(direntDotDot, dirent1[0:24]...),
  2109  			expectedCookie:  2,
  2110  		},
  2111  		{
  2112  			name:       "read second and third",
  2113  			initialDir: "dir",
  2114  			dir: func() {
  2115  				f, _ := fsc.LookupFile(fd)
  2116  				rdd, _ := fsc.LookupReaddir(fd, f)
  2117  				_ = rdd.Advance()
  2118  			},
  2119  			bufLen:          53, // length is long enough for second and third.
  2120  			cookie:          1,  // d_next of first
  2121  			expectedBufused: 53, // length to read exactly one second and third.
  2122  			expectedMem:     append(direntDotDot, dirent1...),
  2123  			expectedCookie:  3,
  2124  		},
  2125  		{
  2126  			name:       "read exactly third",
  2127  			initialDir: "dir",
  2128  			dir: func() {
  2129  				f, _ := fsc.LookupFile(fd)
  2130  				rdd, _ := fsc.LookupReaddir(fd, f)
  2131  				rdd.Skip(2)
  2132  			},
  2133  			bufLen:          27, // length is long enough for exactly third.
  2134  			cookie:          2,  // d_next of second.
  2135  			expectedBufused: 27, // length to read exactly third.
  2136  			expectedMem:     dirent1,
  2137  			expectedCookie:  3,
  2138  		},
  2139  		{
  2140  			name:       "read third and beyond",
  2141  			initialDir: "dir",
  2142  			dir: func() {
  2143  				f, _ := fsc.LookupFile(fd)
  2144  				rdd, _ := fsc.LookupReaddir(fd, f)
  2145  				rdd.Skip(2)
  2146  			},
  2147  			bufLen:          300, // length is long enough for third and more
  2148  			cookie:          2,   // d_next of second.
  2149  			expectedBufused: 78,  // length to read the rest
  2150  			expectedMem:     append(dirent1, dirent2...),
  2151  			expectedCookie:  5,
  2152  		},
  2153  		{
  2154  			name:       "read exhausted directory",
  2155  			initialDir: "dir",
  2156  			dir: func() {
  2157  				f, _ := fsc.LookupFile(fd)
  2158  				rdd, _ := fsc.LookupReaddir(fd, f)
  2159  				rdd.Skip(5)
  2160  			},
  2161  			bufLen:          300, // length is long enough for third and more
  2162  			cookie:          5,   // d_next after entries.
  2163  			expectedBufused: 0,   // nothing read
  2164  			expectedCookie:  5,
  2165  		},
  2166  	}
  2167  
  2168  	for _, tt := range tests {
  2169  		tc := tt
  2170  		t.Run(tc.name, func(t *testing.T) {
  2171  			defer log.Reset()
  2172  			defer fsc.CloseReaddir(fd)
  2173  
  2174  			dir, errno := preopen.OpenFile(tc.initialDir, os.O_RDONLY, 0)
  2175  			require.EqualErrno(t, 0, errno)
  2176  			file, _ := fsc.LookupFile(fd)
  2177  			file.File = dir
  2178  
  2179  			if tc.dir != nil {
  2180  				tc.dir()
  2181  			}
  2182  
  2183  			maskMemory(t, mod, int(tc.bufLen))
  2184  
  2185  			resultBufused := uint32(0) // where to write the amount used out of bufLen
  2186  			buf := uint32(8)           // where to start the dirents
  2187  			requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
  2188  				uint64(fd), uint64(buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(resultBufused))
  2189  
  2190  			// read back the bufused and compare memory against it
  2191  			bufUsed, ok := mod.Memory().ReadUint32Le(resultBufused)
  2192  			require.True(t, ok)
  2193  			require.Equal(t, tc.expectedBufused, bufUsed)
  2194  
  2195  			mem, ok := mod.Memory().Read(buf, bufUsed)
  2196  			require.True(t, ok)
  2197  
  2198  			if tc.expectedMem != nil {
  2199  				if tc.expectedMemSize == 0 {
  2200  					tc.expectedMemSize = len(tc.expectedMem)
  2201  				}
  2202  				require.Equal(t, tc.expectedMem, mem[:tc.expectedMemSize])
  2203  			}
  2204  
  2205  			rdd, errno := fsc.LookupReaddir(fd, file)
  2206  			require.Equal(t, syscall.Errno(0), errno)
  2207  			require.Equal(t, tc.expectedCookie, rdd.Cookie())
  2208  		})
  2209  	}
  2210  }
  2211  
  2212  func Test_fdReaddir_Rewind(t *testing.T) {
  2213  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
  2214  	defer r.Close(testCtx)
  2215  
  2216  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2217  
  2218  	fd, errno := fsc.OpenFile(fsc.RootFS(), "dir", os.O_RDONLY, 0)
  2219  	require.EqualErrno(t, 0, errno)
  2220  
  2221  	mem := mod.Memory()
  2222  	const resultBufused, buf, bufSize = 0, 8, 200
  2223  	read := func(cookie, bufSize uint64) (bufUsed uint32) {
  2224  		requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
  2225  			uint64(fd), buf, bufSize, cookie, uint64(resultBufused))
  2226  
  2227  		bufUsed, ok := mem.ReadUint32Le(resultBufused)
  2228  		require.True(t, ok)
  2229  		return bufUsed
  2230  	}
  2231  
  2232  	cookie := uint64(0)
  2233  	// Initial read.
  2234  	initialBufUsed := read(cookie, bufSize)
  2235  	// Ensure that all is read.
  2236  	require.Equal(t, len(dirents), int(initialBufUsed))
  2237  	resultBuf, ok := mem.Read(buf, initialBufUsed)
  2238  	require.True(t, ok)
  2239  	require.Equal(t, dirents, resultBuf)
  2240  
  2241  	// Mask the result.
  2242  	for i := range resultBuf {
  2243  		resultBuf[i] = '?'
  2244  	}
  2245  
  2246  	// Advance the cookie beyond the existing entries.
  2247  	cookie += 5
  2248  	// Nothing to read from, so bufUsed must be zero.
  2249  	require.Zero(t, int(read(cookie, bufSize)))
  2250  
  2251  	// Ensure buffer is intact.
  2252  	for i := range resultBuf {
  2253  		require.Equal(t, byte('?'), resultBuf[i])
  2254  	}
  2255  
  2256  	// Here, we rewind the directory by setting cookie=0 on the same file descriptor.
  2257  	cookie = 0
  2258  	usedAfterRewind := read(cookie, bufSize)
  2259  	// Ensure that all is read.
  2260  	require.Equal(t, len(dirents), int(usedAfterRewind))
  2261  	resultBuf, ok = mem.Read(buf, usedAfterRewind)
  2262  	require.True(t, ok)
  2263  	require.Equal(t, dirents, resultBuf)
  2264  	require.Equal(t, `
  2265  ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=8,buf_len=200,cookie=0)
  2266  <== (bufused=129,errno=ESUCCESS)
  2267  ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=8,buf_len=200,cookie=5)
  2268  <== (bufused=0,errno=ESUCCESS)
  2269  ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=8,buf_len=200,cookie=0)
  2270  <== (bufused=129,errno=ESUCCESS)
  2271  `, "\n"+log.String())
  2272  }
  2273  
  2274  func Test_fdReaddir_Errors(t *testing.T) {
  2275  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
  2276  	defer r.Close(testCtx)
  2277  	memLen := mod.Memory().Size()
  2278  
  2279  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2280  	preopen := fsc.RootFS()
  2281  
  2282  	fileFD, errno := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
  2283  	require.EqualErrno(t, 0, errno)
  2284  
  2285  	dirFD, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
  2286  	require.EqualErrno(t, 0, errno)
  2287  
  2288  	tests := []struct {
  2289  		name                       string
  2290  		fd                         int32
  2291  		buf, bufLen, resultBufused uint32
  2292  		cookie                     int64
  2293  		expectedErrno              wasip1.Errno
  2294  		expectedLog                string
  2295  	}{
  2296  		{
  2297  			name:          "out-of-memory reading buf",
  2298  			fd:            dirFD,
  2299  			buf:           memLen,
  2300  			bufLen:        1000,
  2301  			expectedErrno: wasip1.ErrnoFault,
  2302  			expectedLog: `
  2303  ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=65536,buf_len=1000,cookie=0)
  2304  <== (bufused=,errno=EFAULT)
  2305  `,
  2306  		},
  2307  		{
  2308  			name: "invalid FD",
  2309  			fd:   42,                           // arbitrary invalid fd
  2310  			buf:  0, bufLen: wasip1.DirentSize, // enough to read the dirent
  2311  			resultBufused: 1000, // arbitrary
  2312  			expectedErrno: wasip1.ErrnoBadf,
  2313  			expectedLog: `
  2314  ==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=24,cookie=0)
  2315  <== (bufused=,errno=EBADF)
  2316  `,
  2317  		},
  2318  		{
  2319  			name: "not a dir",
  2320  			fd:   fileFD,
  2321  			buf:  0, bufLen: wasip1.DirentSize, // enough to read the dirent
  2322  			resultBufused: 1000, // arbitrary
  2323  			expectedErrno: wasip1.ErrnoBadf,
  2324  			expectedLog: `
  2325  ==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=24,cookie=0)
  2326  <== (bufused=,errno=EBADF)
  2327  `,
  2328  		},
  2329  		{
  2330  			name:          "out-of-memory reading bufLen",
  2331  			fd:            dirFD,
  2332  			buf:           memLen - 1,
  2333  			bufLen:        1000,
  2334  			expectedErrno: wasip1.ErrnoFault,
  2335  			expectedLog: `
  2336  ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=65535,buf_len=1000,cookie=0)
  2337  <== (bufused=,errno=EFAULT)
  2338  `,
  2339  		},
  2340  		{
  2341  			name: "bufLen must be enough to write a struct",
  2342  			fd:   dirFD,
  2343  			buf:  0, bufLen: 1,
  2344  			resultBufused: 1000,
  2345  			expectedErrno: wasip1.ErrnoInval,
  2346  			expectedLog: `
  2347  ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=1,cookie=0)
  2348  <== (bufused=,errno=EINVAL)
  2349  `,
  2350  		},
  2351  		{
  2352  			name: "cookie invalid when no prior state",
  2353  			fd:   dirFD,
  2354  			buf:  0, bufLen: 1000,
  2355  			cookie:        1,
  2356  			resultBufused: 2000,
  2357  			expectedErrno: wasip1.ErrnoInval,
  2358  			expectedLog: `
  2359  ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=1000,cookie=1)
  2360  <== (bufused=,errno=EINVAL)
  2361  `,
  2362  		},
  2363  		{
  2364  			name: "negative cookie invalid",
  2365  			fd:   dirFD,
  2366  			buf:  0, bufLen: 1000,
  2367  			cookie:        -1,
  2368  			resultBufused: 2000,
  2369  			expectedErrno: wasip1.ErrnoInval,
  2370  			expectedLog: `
  2371  ==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=1000,cookie=-1)
  2372  <== (bufused=,errno=EINVAL)
  2373  `,
  2374  		},
  2375  	}
  2376  
  2377  	for _, tt := range tests {
  2378  		tc := tt
  2379  		t.Run(tc.name, func(t *testing.T) {
  2380  			defer log.Reset()
  2381  
  2382  			// Reset the directory so that tests don't taint each other.
  2383  			if file, ok := fsc.LookupFile(tc.fd); ok && tc.fd == dirFD {
  2384  				dir, errno := preopen.OpenFile("dir", os.O_RDONLY, 0)
  2385  				require.EqualErrno(t, 0, errno)
  2386  				defer dir.Close()
  2387  
  2388  				file.File = dir
  2389  				fsc.CloseReaddir(tc.fd)
  2390  			}
  2391  
  2392  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdReaddirName,
  2393  				uint64(tc.fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(tc.resultBufused))
  2394  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2395  		})
  2396  	}
  2397  }
  2398  
  2399  func Test_fdRenumber(t *testing.T) {
  2400  	const fileFD, dirFD = 4, 5
  2401  
  2402  	tests := []struct {
  2403  		name          string
  2404  		from, to      int32
  2405  		expectedErrno wasip1.Errno
  2406  		expectedLog   string
  2407  	}{
  2408  		{
  2409  			name:          "from=preopen",
  2410  			from:          sys.FdPreopen,
  2411  			to:            dirFD,
  2412  			expectedErrno: wasip1.ErrnoNotsup,
  2413  			expectedLog: `
  2414  ==> wasi_snapshot_preview1.fd_renumber(fd=3,to=5)
  2415  <== errno=ENOTSUP
  2416  `,
  2417  		},
  2418  		{
  2419  			name:          "from=badf",
  2420  			from:          -1,
  2421  			to:            sys.FdPreopen,
  2422  			expectedErrno: wasip1.ErrnoBadf,
  2423  			expectedLog: `
  2424  ==> wasi_snapshot_preview1.fd_renumber(fd=-1,to=3)
  2425  <== errno=EBADF
  2426  `,
  2427  		},
  2428  		{
  2429  			name:          "to=badf",
  2430  			from:          sys.FdPreopen,
  2431  			to:            -1,
  2432  			expectedErrno: wasip1.ErrnoBadf,
  2433  			expectedLog: `
  2434  ==> wasi_snapshot_preview1.fd_renumber(fd=3,to=-1)
  2435  <== errno=EBADF
  2436  `,
  2437  		},
  2438  		{
  2439  			name:          "to=preopen",
  2440  			from:          dirFD,
  2441  			to:            sys.FdPreopen,
  2442  			expectedErrno: wasip1.ErrnoNotsup,
  2443  			expectedLog: `
  2444  ==> wasi_snapshot_preview1.fd_renumber(fd=5,to=3)
  2445  <== errno=ENOTSUP
  2446  `,
  2447  		},
  2448  		{
  2449  			name:          "file to dir",
  2450  			from:          fileFD,
  2451  			to:            dirFD,
  2452  			expectedErrno: wasip1.ErrnoSuccess,
  2453  			expectedLog: `
  2454  ==> wasi_snapshot_preview1.fd_renumber(fd=4,to=5)
  2455  <== errno=ESUCCESS
  2456  `,
  2457  		},
  2458  		{
  2459  			name:          "dir to file",
  2460  			from:          dirFD,
  2461  			to:            fileFD,
  2462  			expectedErrno: wasip1.ErrnoSuccess,
  2463  			expectedLog: `
  2464  ==> wasi_snapshot_preview1.fd_renumber(fd=5,to=4)
  2465  <== errno=ESUCCESS
  2466  `,
  2467  		},
  2468  		{
  2469  			name:          "dir to any",
  2470  			from:          dirFD,
  2471  			to:            12345,
  2472  			expectedErrno: wasip1.ErrnoSuccess,
  2473  			expectedLog: `
  2474  ==> wasi_snapshot_preview1.fd_renumber(fd=5,to=12345)
  2475  <== errno=ESUCCESS
  2476  `,
  2477  		},
  2478  		{
  2479  			name:          "file to any",
  2480  			from:          fileFD,
  2481  			to:            54,
  2482  			expectedErrno: wasip1.ErrnoSuccess,
  2483  			expectedLog: `
  2484  ==> wasi_snapshot_preview1.fd_renumber(fd=4,to=54)
  2485  <== errno=ESUCCESS
  2486  `,
  2487  		},
  2488  	}
  2489  
  2490  	for _, tt := range tests {
  2491  		tc := tt
  2492  		t.Run(tc.name, func(t *testing.T) {
  2493  			mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
  2494  			defer r.Close(testCtx)
  2495  
  2496  			fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2497  			preopen := fsc.RootFS()
  2498  
  2499  			// Sanity check of the file descriptor assignment.
  2500  			fileFDAssigned, errno := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
  2501  			require.EqualErrno(t, 0, errno)
  2502  			require.Equal(t, int32(fileFD), fileFDAssigned)
  2503  
  2504  			dirFDAssigned, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
  2505  			require.EqualErrno(t, 0, errno)
  2506  			require.Equal(t, int32(dirFD), dirFDAssigned)
  2507  
  2508  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdRenumberName, uint64(tc.from), uint64(tc.to))
  2509  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2510  		})
  2511  	}
  2512  }
  2513  
  2514  func Test_fdSeek(t *testing.T) {
  2515  	mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
  2516  	defer r.Close(testCtx)
  2517  
  2518  	resultNewoffset := uint32(1) // arbitrary offset in api.Memory for the new offset value
  2519  
  2520  	tests := []struct {
  2521  		name           string
  2522  		offset         int64
  2523  		whence         int
  2524  		expectedOffset int64
  2525  		expectedMemory []byte
  2526  		expectedLog    string
  2527  	}{
  2528  		{
  2529  			name:           "SeekStart",
  2530  			offset:         4, // arbitrary offset
  2531  			whence:         io.SeekStart,
  2532  			expectedOffset: 4, // = offset
  2533  			expectedMemory: []byte{
  2534  				'?',                    // resultNewoffset is after this
  2535  				4, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  2536  				'?',
  2537  			},
  2538  			expectedLog: `
  2539  ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0)
  2540  <== (newoffset=4,errno=ESUCCESS)
  2541  `,
  2542  		},
  2543  		{
  2544  			name:           "SeekCurrent",
  2545  			offset:         1, // arbitrary offset
  2546  			whence:         io.SeekCurrent,
  2547  			expectedOffset: 2, // = 1 (the initial offset of the test file) + 1 (offset)
  2548  			expectedMemory: []byte{
  2549  				'?',                    // resultNewoffset is after this
  2550  				2, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  2551  				'?',
  2552  			},
  2553  			expectedLog: `
  2554  ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1)
  2555  <== (newoffset=2,errno=ESUCCESS)
  2556  `,
  2557  		},
  2558  		{
  2559  			name:           "SeekEnd",
  2560  			offset:         -1, // arbitrary offset, note that offset can be negative
  2561  			whence:         io.SeekEnd,
  2562  			expectedOffset: 5, // = 6 (the size of the test file with content "wazero") + -1 (offset)
  2563  			expectedMemory: []byte{
  2564  				'?',                    // resultNewoffset is after this
  2565  				5, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  2566  				'?',
  2567  			},
  2568  			expectedLog: `
  2569  ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=-1,whence=2)
  2570  <== (newoffset=5,errno=ESUCCESS)
  2571  `,
  2572  		},
  2573  	}
  2574  
  2575  	for _, tt := range tests {
  2576  		tc := tt
  2577  		t.Run(tc.name, func(t *testing.T) {
  2578  			defer log.Reset()
  2579  
  2580  			maskMemory(t, mod, len(tc.expectedMemory))
  2581  
  2582  			// Since we initialized this file, we know it is a seeker (because it is a MapFile)
  2583  			fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2584  			f, ok := fsc.LookupFile(fd)
  2585  			require.True(t, ok)
  2586  
  2587  			// set the initial offset of the file to 1
  2588  			offset, errno := f.File.Seek(1, io.SeekStart)
  2589  			require.EqualErrno(t, 0, errno)
  2590  			require.Equal(t, int64(1), offset)
  2591  
  2592  			requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdSeekName, uint64(fd), uint64(tc.offset), uint64(tc.whence), uint64(resultNewoffset))
  2593  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2594  
  2595  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
  2596  			require.True(t, ok)
  2597  			require.Equal(t, tc.expectedMemory, actual)
  2598  
  2599  			offset, errno = f.File.Seek(0, io.SeekCurrent)
  2600  			require.EqualErrno(t, 0, errno)
  2601  			require.Equal(t, tc.expectedOffset, offset) // test that the offset of file is actually updated.
  2602  		})
  2603  	}
  2604  }
  2605  
  2606  func Test_fdSeek_Errors(t *testing.T) {
  2607  	mod, fileFD, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), false)
  2608  	defer r.Close(testCtx)
  2609  
  2610  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2611  	require.Zero(t, fsc.RootFS().Mkdir("dir", 0o0777))
  2612  	dirFD := requireOpenFD(t, mod, "dir")
  2613  
  2614  	memorySize := mod.Memory().Size()
  2615  
  2616  	tests := []struct {
  2617  		name                    string
  2618  		fd                      int32
  2619  		offset                  uint64
  2620  		whence, resultNewoffset uint32
  2621  		expectedErrno           wasip1.Errno
  2622  		expectedLog             string
  2623  	}{
  2624  		{
  2625  			name:          "invalid FD",
  2626  			fd:            42, // arbitrary invalid fd
  2627  			expectedErrno: wasip1.ErrnoBadf,
  2628  			expectedLog: `
  2629  ==> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0)
  2630  <== (newoffset=,errno=EBADF)
  2631  `,
  2632  		},
  2633  		{
  2634  			name:          "invalid whence",
  2635  			fd:            fileFD,
  2636  			whence:        3, // invalid whence, the largest whence io.SeekEnd(2) + 1
  2637  			expectedErrno: wasip1.ErrnoInval,
  2638  			expectedLog: `
  2639  ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3)
  2640  <== (newoffset=,errno=EINVAL)
  2641  `,
  2642  		},
  2643  		{
  2644  			name:          "dir not file",
  2645  			fd:            dirFD,
  2646  			expectedErrno: wasip1.ErrnoIsdir,
  2647  			expectedLog: `
  2648  ==> wasi_snapshot_preview1.fd_seek(fd=5,offset=0,whence=0)
  2649  <== (newoffset=,errno=EISDIR)
  2650  `,
  2651  		},
  2652  		{
  2653  			name:            "out-of-memory writing resultNewoffset",
  2654  			fd:              fileFD,
  2655  			resultNewoffset: memorySize,
  2656  			expectedErrno:   wasip1.ErrnoFault,
  2657  			expectedLog: `
  2658  ==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0)
  2659  <== (newoffset=,errno=EFAULT)
  2660  `,
  2661  		},
  2662  	}
  2663  
  2664  	for _, tt := range tests {
  2665  		tc := tt
  2666  		t.Run(tc.name, func(t *testing.T) {
  2667  			defer log.Reset()
  2668  
  2669  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdSeekName, uint64(tc.fd), tc.offset, uint64(tc.whence), uint64(tc.resultNewoffset))
  2670  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2671  		})
  2672  	}
  2673  }
  2674  
  2675  // Test_fdSync only tests that the call succeeds; it's hard to test its effectiveness.
  2676  func Test_fdSync(t *testing.T) {
  2677  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  2678  	pathName := "test_path"
  2679  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
  2680  	defer r.Close(testCtx)
  2681  
  2682  	tests := []struct {
  2683  		name          string
  2684  		fd            int32
  2685  		expectedErrno wasip1.Errno
  2686  		expectedLog   string
  2687  	}{
  2688  		{
  2689  			name:          "invalid FD",
  2690  			fd:            42, // arbitrary invalid fd
  2691  			expectedErrno: wasip1.ErrnoBadf,
  2692  			expectedLog: `
  2693  ==> wasi_snapshot_preview1.fd_sync(fd=42)
  2694  <== errno=EBADF
  2695  `,
  2696  		},
  2697  		{
  2698  			name:          "valid FD",
  2699  			fd:            fd,
  2700  			expectedErrno: wasip1.ErrnoSuccess,
  2701  			expectedLog: `
  2702  ==> wasi_snapshot_preview1.fd_sync(fd=4)
  2703  <== errno=ESUCCESS
  2704  `,
  2705  		},
  2706  	}
  2707  
  2708  	for _, tt := range tests {
  2709  		tc := tt
  2710  		t.Run(tc.name, func(t *testing.T) {
  2711  			defer log.Reset()
  2712  
  2713  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdSyncName, uint64(tc.fd))
  2714  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2715  		})
  2716  	}
  2717  }
  2718  
  2719  func Test_fdTell(t *testing.T) {
  2720  	mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
  2721  	defer r.Close(testCtx)
  2722  	defer log.Reset()
  2723  
  2724  	resultNewoffset := uint32(1) // arbitrary offset in api.Memory for the new offset value
  2725  
  2726  	expectedOffset := int64(1) // = offset
  2727  	expectedMemory := []byte{
  2728  		'?',                    // resultNewoffset is after this
  2729  		1, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  2730  		'?',
  2731  	}
  2732  	expectedLog := `
  2733  ==> wasi_snapshot_preview1.fd_tell(fd=4,result.offset=1)
  2734  <== errno=ESUCCESS
  2735  `
  2736  
  2737  	maskMemory(t, mod, len(expectedMemory))
  2738  
  2739  	// Since we initialized this file, we know it is a seeker (because it is a MapFile)
  2740  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  2741  	f, ok := fsc.LookupFile(fd)
  2742  	require.True(t, ok)
  2743  
  2744  	// set the initial offset of the file to 1
  2745  	offset, errno := f.File.Seek(1, io.SeekStart)
  2746  	require.EqualErrno(t, 0, errno)
  2747  	require.Equal(t, int64(1), offset)
  2748  
  2749  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdTellName, uint64(fd), uint64(resultNewoffset))
  2750  	require.Equal(t, expectedLog, "\n"+log.String())
  2751  
  2752  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  2753  	require.True(t, ok)
  2754  	require.Equal(t, expectedMemory, actual)
  2755  
  2756  	offset, errno = f.File.Seek(0, io.SeekCurrent)
  2757  	require.EqualErrno(t, 0, errno)
  2758  	require.Equal(t, expectedOffset, offset) // test that the offset of file is actually updated.
  2759  }
  2760  
  2761  func Test_fdTell_Errors(t *testing.T) {
  2762  	mod, fd, log, r := requireOpenFile(t, t.TempDir(), "test_path", []byte("wazero"), true)
  2763  	defer r.Close(testCtx)
  2764  
  2765  	memorySize := mod.Memory().Size()
  2766  
  2767  	tests := []struct {
  2768  		name            string
  2769  		fd              int32
  2770  		resultNewoffset uint32
  2771  		expectedErrno   wasip1.Errno
  2772  		expectedLog     string
  2773  	}{
  2774  		{
  2775  			name:          "invalid FD",
  2776  			fd:            42, // arbitrary invalid fd
  2777  			expectedErrno: wasip1.ErrnoBadf,
  2778  			expectedLog: `
  2779  ==> wasi_snapshot_preview1.fd_tell(fd=42,result.offset=0)
  2780  <== errno=EBADF
  2781  `,
  2782  		},
  2783  		{
  2784  			name:            "out-of-memory writing resultNewoffset",
  2785  			fd:              fd,
  2786  			resultNewoffset: memorySize,
  2787  			expectedErrno:   wasip1.ErrnoFault,
  2788  			expectedLog: `
  2789  ==> wasi_snapshot_preview1.fd_tell(fd=4,result.offset=65536)
  2790  <== errno=EFAULT
  2791  `,
  2792  		},
  2793  	}
  2794  
  2795  	for _, tt := range tests {
  2796  		tc := tt
  2797  		t.Run(tc.name, func(t *testing.T) {
  2798  			defer log.Reset()
  2799  
  2800  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdTellName, uint64(tc.fd), uint64(tc.resultNewoffset))
  2801  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2802  		})
  2803  	}
  2804  }
  2805  
  2806  func Test_fdWrite(t *testing.T) {
  2807  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  2808  	pathName := "test_path"
  2809  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{}, false)
  2810  	defer r.Close(testCtx)
  2811  
  2812  	iovs := uint32(1) // arbitrary offset
  2813  	initialMemory := []byte{
  2814  		'?',         // `iovs` is after this
  2815  		18, 0, 0, 0, // = iovs[0].offset
  2816  		4, 0, 0, 0, // = iovs[0].length
  2817  		23, 0, 0, 0, // = iovs[1].offset
  2818  		2, 0, 0, 0, // = iovs[1].length
  2819  		'?',                // iovs[0].offset is after this
  2820  		'w', 'a', 'z', 'e', // iovs[0].length bytes
  2821  		'?',      // iovs[1].offset is after this
  2822  		'r', 'o', // iovs[1].length bytes
  2823  		'?',
  2824  	}
  2825  	iovsCount := uint32(2)       // The count of iovs
  2826  	resultNwritten := uint32(26) // arbitrary offset
  2827  	expectedMemory := append(
  2828  		initialMemory,
  2829  		6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  2830  		'?',
  2831  	)
  2832  
  2833  	maskMemory(t, mod, len(expectedMemory))
  2834  	ok := mod.Memory().Write(0, initialMemory)
  2835  	require.True(t, ok)
  2836  
  2837  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdWriteName, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten))
  2838  	require.Equal(t, `
  2839  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2)
  2840  <== (nwritten=6,errno=ESUCCESS)
  2841  `, "\n"+log.String())
  2842  
  2843  	actual, ok := mod.Memory().Read(0, uint32(len(expectedMemory)))
  2844  	require.True(t, ok)
  2845  	require.Equal(t, expectedMemory, actual)
  2846  
  2847  	// Since we initialized this file, we know we can read it by path
  2848  	buf, err := os.ReadFile(joinPath(tmpDir, pathName))
  2849  	require.NoError(t, err)
  2850  
  2851  	require.Equal(t, []byte("wazero"), buf) // verify the file was actually written
  2852  }
  2853  
  2854  func Test_fdWrite_Errors(t *testing.T) {
  2855  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  2856  	pathName := "test_path"
  2857  	mod, fd, log, r := requireOpenFile(t, tmpDir, pathName, []byte{1, 2, 3, 4}, false)
  2858  	defer r.Close(testCtx)
  2859  
  2860  	// Setup valid test memory
  2861  	iovsCount := uint32(1)
  2862  	memSize := mod.Memory().Size()
  2863  
  2864  	tests := []struct {
  2865  		name                 string
  2866  		fd                   int32
  2867  		iovs, resultNwritten uint32
  2868  		expectedErrno        wasip1.Errno
  2869  		expectedLog          string
  2870  	}{
  2871  		{
  2872  			name:          "invalid FD",
  2873  			fd:            42, // arbitrary invalid fd
  2874  			expectedErrno: wasip1.ErrnoBadf,
  2875  			expectedLog: `
  2876  ==> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1)
  2877  <== (nwritten=,errno=EBADF)
  2878  `,
  2879  		},
  2880  		{
  2881  			name:          "not writable FD",
  2882  			fd:            sys.FdStdin,
  2883  			expectedErrno: wasip1.ErrnoBadf,
  2884  			expectedLog:   "\n", // stdin is not sampled
  2885  		},
  2886  		{
  2887  			name:          "out-of-memory reading iovs[0].offset",
  2888  			fd:            fd,
  2889  			iovs:          memSize - 2,
  2890  			expectedErrno: wasip1.ErrnoFault,
  2891  			expectedLog: `
  2892  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65534,iovs_len=1)
  2893  <== (nwritten=,errno=EFAULT)
  2894  `,
  2895  		},
  2896  		{
  2897  			name:          "out-of-memory reading iovs[0].length",
  2898  			fd:            fd,
  2899  			iovs:          memSize - 4, // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()!
  2900  			expectedErrno: wasip1.ErrnoFault,
  2901  			expectedLog: `
  2902  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65532,iovs_len=1)
  2903  <== (nwritten=,errno=EFAULT)
  2904  `,
  2905  		},
  2906  		{
  2907  			name:          "iovs[0].offset is outside memory",
  2908  			fd:            fd,
  2909  			iovs:          memSize - 5, // iovs[0].offset (where to read "hi") is outside memory.
  2910  			expectedErrno: wasip1.ErrnoFault,
  2911  			expectedLog: `
  2912  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65531,iovs_len=1)
  2913  <== (nwritten=,errno=EFAULT)
  2914  `,
  2915  		},
  2916  		{
  2917  			name:          "length to read exceeds memory by 1",
  2918  			fd:            fd,
  2919  			iovs:          memSize - 7, // iovs[0].offset (where to read "hi") is in memory, but truncated.
  2920  			expectedErrno: wasip1.ErrnoFault,
  2921  			expectedLog: `
  2922  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=65529,iovs_len=1)
  2923  <== (nwritten=,errno=EFAULT)
  2924  `,
  2925  		},
  2926  		{
  2927  			name:           "resultNwritten offset is outside memory",
  2928  			fd:             fd,
  2929  			resultNwritten: memSize, // read was ok, but there wasn't enough memory to write the result.
  2930  			expectedErrno:  wasip1.ErrnoFault,
  2931  			expectedLog: `
  2932  ==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1)
  2933  <== (nwritten=,errno=EFAULT)
  2934  `,
  2935  		},
  2936  	}
  2937  
  2938  	for _, tt := range tests {
  2939  		tc := tt
  2940  		t.Run(tc.name, func(t *testing.T) {
  2941  			defer log.Reset()
  2942  
  2943  			mod.Memory().Write(tc.iovs, append(
  2944  				u64.LeBytes(uint64(tc.iovs+8)), // = iovs[0].offset (where the data "hi" begins)
  2945  				// = iovs[0].length (how many bytes are in "hi")
  2946  				2, 0, 0, 0,
  2947  				'h', 'i', // iovs[0].length bytes
  2948  			))
  2949  
  2950  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.FdWriteName, uint64(tc.fd), uint64(tc.iovs), uint64(iovsCount),
  2951  				uint64(tc.resultNwritten))
  2952  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2953  		})
  2954  	}
  2955  }
  2956  
  2957  func Test_pathCreateDirectory(t *testing.T) {
  2958  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  2959  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  2960  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  2961  	defer r.Close(testCtx)
  2962  
  2963  	// set up the initial memory to include the path name starting at an offset.
  2964  	pathName := "wazero"
  2965  	realPath := joinPath(tmpDir, pathName)
  2966  	ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
  2967  	require.True(t, ok)
  2968  
  2969  	fd := sys.FdPreopen
  2970  	name := 1
  2971  	nameLen := len(pathName)
  2972  
  2973  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathCreateDirectoryName, uint64(fd), uint64(name), uint64(nameLen))
  2974  	require.Equal(t, `
  2975  ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=wazero)
  2976  <== errno=ESUCCESS
  2977  `, "\n"+log.String())
  2978  
  2979  	// ensure the directory was created
  2980  	stat, err := os.Stat(realPath)
  2981  	require.NoError(t, err)
  2982  	require.True(t, stat.IsDir())
  2983  	require.Equal(t, pathName, stat.Name())
  2984  }
  2985  
  2986  func Test_pathCreateDirectory_Errors(t *testing.T) {
  2987  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  2988  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  2989  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  2990  	defer r.Close(testCtx)
  2991  
  2992  	file := "file"
  2993  	err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
  2994  	require.NoError(t, err)
  2995  	fileFD := requireOpenFD(t, mod, file)
  2996  
  2997  	dir := "dir"
  2998  	err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
  2999  	require.NoError(t, err)
  3000  
  3001  	tests := []struct {
  3002  		name, pathName string
  3003  		fd             int32
  3004  		path, pathLen  uint32
  3005  		expectedErrno  wasip1.Errno
  3006  		expectedLog    string
  3007  	}{
  3008  		{
  3009  			name:          "unopened FD",
  3010  			fd:            42, // arbitrary invalid fd
  3011  			expectedErrno: wasip1.ErrnoBadf,
  3012  			expectedLog: `
  3013  ==> wasi_snapshot_preview1.path_create_directory(fd=42,path=)
  3014  <== errno=EBADF
  3015  `,
  3016  		},
  3017  		{
  3018  			name:          "Fd not a directory",
  3019  			fd:            fileFD,
  3020  			pathName:      file,
  3021  			path:          0,
  3022  			pathLen:       uint32(len(file)),
  3023  			expectedErrno: wasip1.ErrnoNotdir,
  3024  			expectedLog: `
  3025  ==> wasi_snapshot_preview1.path_create_directory(fd=4,path=file)
  3026  <== errno=ENOTDIR
  3027  `,
  3028  		},
  3029  		{
  3030  			name:          "out-of-memory reading path",
  3031  			fd:            sys.FdPreopen,
  3032  			path:          mod.Memory().Size(),
  3033  			pathLen:       1,
  3034  			expectedErrno: wasip1.ErrnoFault,
  3035  			expectedLog: `
  3036  ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=OOM(65536,1))
  3037  <== errno=EFAULT
  3038  `,
  3039  		},
  3040  		{
  3041  			name:          "out-of-memory reading pathLen",
  3042  			fd:            sys.FdPreopen,
  3043  			path:          0,
  3044  			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
  3045  			expectedErrno: wasip1.ErrnoFault,
  3046  			expectedLog: `
  3047  ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=OOM(0,65537))
  3048  <== errno=EFAULT
  3049  `,
  3050  		},
  3051  		{
  3052  			name:          "file exists",
  3053  			fd:            sys.FdPreopen,
  3054  			pathName:      file,
  3055  			path:          0,
  3056  			pathLen:       uint32(len(file)),
  3057  			expectedErrno: wasip1.ErrnoExist,
  3058  			expectedLog: `
  3059  ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=file)
  3060  <== errno=EEXIST
  3061  `,
  3062  		},
  3063  		{
  3064  			name:          "dir exists",
  3065  			fd:            sys.FdPreopen,
  3066  			pathName:      dir,
  3067  			path:          0,
  3068  			pathLen:       uint32(len(dir)),
  3069  			expectedErrno: wasip1.ErrnoExist,
  3070  			expectedLog: `
  3071  ==> wasi_snapshot_preview1.path_create_directory(fd=3,path=dir)
  3072  <== errno=EEXIST
  3073  `,
  3074  		},
  3075  	}
  3076  
  3077  	for _, tt := range tests {
  3078  		tc := tt
  3079  		t.Run(tc.name, func(t *testing.T) {
  3080  			defer log.Reset()
  3081  
  3082  			mod.Memory().Write(tc.path, []byte(tc.pathName))
  3083  
  3084  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathCreateDirectoryName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
  3085  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  3086  		})
  3087  	}
  3088  }
  3089  
  3090  func Test_pathFilestatGet(t *testing.T) {
  3091  	file, dir, fileInDir := "animals.txt", "sub", "sub/test.txt"
  3092  
  3093  	initialMemoryFile := append([]byte{'?'}, file...)
  3094  	initialMemoryDir := append([]byte{'?'}, dir...)
  3095  	initialMemoryFileInDir := append([]byte{'?'}, fileInDir...)
  3096  	initialMemoryNotExists := []byte{'?', '?'}
  3097  
  3098  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fstest.FS))
  3099  	defer r.Close(testCtx)
  3100  	memorySize := mod.Memory().Size()
  3101  
  3102  	fileFD := requireOpenFD(t, mod, file)
  3103  
  3104  	tests := []struct {
  3105  		name                    string
  3106  		fd                      int32
  3107  		pathLen, resultFilestat uint32
  3108  		flags                   uint16
  3109  		memory, expectedMemory  []byte
  3110  		expectedErrno           wasip1.Errno
  3111  		expectedLog             string
  3112  	}{
  3113  		{
  3114  			name:           "file under root",
  3115  			fd:             sys.FdPreopen,
  3116  			memory:         initialMemoryFile,
  3117  			pathLen:        uint32(len(file)),
  3118  			resultFilestat: uint32(len(file)) + 1,
  3119  			expectedMemory: append(
  3120  				initialMemoryFile,
  3121  				0, 0, 0, 0, 0, 0, 0, 0, // dev
  3122  				0, 0, 0, 0, 0, 0, 0, 0, // ino
  3123  				4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
  3124  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
  3125  				30, 0, 0, 0, 0, 0, 0, 0, // size
  3126  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
  3127  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
  3128  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
  3129  			),
  3130  			expectedLog: `
  3131  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=animals.txt)
  3132  <== (filestat={filetype=REGULAR_FILE,size=30,mtim=1667482413000000000},errno=ESUCCESS)
  3133  `,
  3134  		},
  3135  		{
  3136  			name:           "file under dir",
  3137  			fd:             sys.FdPreopen, // root
  3138  			memory:         initialMemoryFileInDir,
  3139  			pathLen:        uint32(len(fileInDir)),
  3140  			resultFilestat: uint32(len(fileInDir)) + 1,
  3141  			expectedMemory: append(
  3142  				initialMemoryFileInDir,
  3143  				0, 0, 0, 0, 0, 0, 0, 0, // dev
  3144  				0, 0, 0, 0, 0, 0, 0, 0, // ino
  3145  				4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
  3146  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
  3147  				14, 0, 0, 0, 0, 0, 0, 0, // size
  3148  				0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17, // atim
  3149  				0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17, // mtim
  3150  				0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17, // ctim
  3151  			),
  3152  			expectedLog: `
  3153  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=sub/test.txt)
  3154  <== (filestat={filetype=REGULAR_FILE,size=14,mtim=1672531200000000000},errno=ESUCCESS)
  3155  `,
  3156  		},
  3157  		{
  3158  			name:           "dir under root",
  3159  			fd:             sys.FdPreopen,
  3160  			memory:         initialMemoryDir,
  3161  			pathLen:        uint32(len(dir)),
  3162  			resultFilestat: uint32(len(dir)) + 1,
  3163  			expectedMemory: append(
  3164  				initialMemoryDir,
  3165  				0, 0, 0, 0, 0, 0, 0, 0, // dev
  3166  				0, 0, 0, 0, 0, 0, 0, 0, // ino
  3167  				3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
  3168  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
  3169  				0, 0, 0, 0, 0, 0, 0, 0, // size
  3170  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // atim
  3171  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // mtim
  3172  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // ctim
  3173  			),
  3174  			expectedLog: `
  3175  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=sub)
  3176  <== (filestat={filetype=DIRECTORY,size=0,mtim=1640995200000000000},errno=ESUCCESS)
  3177  `,
  3178  		},
  3179  		{
  3180  			name:          "unopened FD",
  3181  			fd:            -1,
  3182  			expectedErrno: wasip1.ErrnoBadf,
  3183  			expectedLog: `
  3184  ==> wasi_snapshot_preview1.path_filestat_get(fd=-1,flags=,path=)
  3185  <== (filestat=,errno=EBADF)
  3186  `,
  3187  		},
  3188  		{
  3189  			name:           "Fd not a directory",
  3190  			fd:             fileFD,
  3191  			memory:         initialMemoryFile,
  3192  			pathLen:        uint32(len(file)),
  3193  			resultFilestat: 2,
  3194  			expectedErrno:  wasip1.ErrnoNotdir,
  3195  			expectedLog: `
  3196  ==> wasi_snapshot_preview1.path_filestat_get(fd=4,flags=,path=animals.txt)
  3197  <== (filestat=,errno=ENOTDIR)
  3198  `,
  3199  		},
  3200  		{
  3201  			name:           "path under root doesn't exist",
  3202  			fd:             sys.FdPreopen,
  3203  			memory:         initialMemoryNotExists,
  3204  			pathLen:        1,
  3205  			resultFilestat: 2,
  3206  			expectedErrno:  wasip1.ErrnoNoent,
  3207  			expectedLog: `
  3208  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=?)
  3209  <== (filestat=,errno=ENOENT)
  3210  `,
  3211  		},
  3212  		{
  3213  			name:          "path is out of memory",
  3214  			fd:            sys.FdPreopen,
  3215  			memory:        initialMemoryFile,
  3216  			pathLen:       memorySize,
  3217  			expectedErrno: wasip1.ErrnoFault,
  3218  			expectedLog: `
  3219  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=OOM(1,65536))
  3220  <== (filestat=,errno=EFAULT)
  3221  `,
  3222  		},
  3223  		{
  3224  			name:           "resultFilestat exceeds the maximum valid address by 1",
  3225  			fd:             sys.FdPreopen,
  3226  			memory:         initialMemoryFile,
  3227  			pathLen:        uint32(len(file)),
  3228  			resultFilestat: memorySize - 64 + 1,
  3229  			expectedErrno:  wasip1.ErrnoFault,
  3230  			expectedLog: `
  3231  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=animals.txt)
  3232  <== (filestat=,errno=EFAULT)
  3233  `,
  3234  		},
  3235  		{
  3236  			name:           "file under root (follow symlinks)",
  3237  			fd:             sys.FdPreopen,
  3238  			flags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
  3239  			memory:         initialMemoryFile,
  3240  			pathLen:        uint32(len(file)),
  3241  			resultFilestat: uint32(len(file)) + 1,
  3242  			expectedMemory: append(
  3243  				initialMemoryFile,
  3244  				0, 0, 0, 0, 0, 0, 0, 0, // dev
  3245  				0, 0, 0, 0, 0, 0, 0, 0, // ino
  3246  				4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
  3247  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
  3248  				30, 0, 0, 0, 0, 0, 0, 0, // size
  3249  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
  3250  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
  3251  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
  3252  			),
  3253  			expectedLog: `
  3254  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=animals.txt)
  3255  <== (filestat={filetype=REGULAR_FILE,size=30,mtim=1667482413000000000},errno=ESUCCESS)
  3256  `,
  3257  		},
  3258  		{
  3259  			name:           "file under dir (follow symlinks)",
  3260  			fd:             sys.FdPreopen, // root
  3261  			flags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
  3262  			memory:         initialMemoryFileInDir,
  3263  			pathLen:        uint32(len(fileInDir)),
  3264  			resultFilestat: uint32(len(fileInDir)) + 1,
  3265  			expectedMemory: append(
  3266  				initialMemoryFileInDir,
  3267  				0, 0, 0, 0, 0, 0, 0, 0, // dev
  3268  				0, 0, 0, 0, 0, 0, 0, 0, // ino
  3269  				4, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
  3270  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
  3271  				14, 0, 0, 0, 0, 0, 0, 0, // size
  3272  				0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17, // atim
  3273  				0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17, // mtim
  3274  				0x0, 0x0, 0xc2, 0xd3, 0x43, 0x6, 0x36, 0x17, // ctim
  3275  			),
  3276  			expectedLog: `
  3277  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=sub/test.txt)
  3278  <== (filestat={filetype=REGULAR_FILE,size=14,mtim=1672531200000000000},errno=ESUCCESS)
  3279  `,
  3280  		},
  3281  		{
  3282  			name:           "dir under root (follow symlinks)",
  3283  			fd:             sys.FdPreopen,
  3284  			flags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
  3285  			memory:         initialMemoryDir,
  3286  			pathLen:        uint32(len(dir)),
  3287  			resultFilestat: uint32(len(dir)) + 1,
  3288  			expectedMemory: append(
  3289  				initialMemoryDir,
  3290  				0, 0, 0, 0, 0, 0, 0, 0, // dev
  3291  				0, 0, 0, 0, 0, 0, 0, 0, // ino
  3292  				3, 0, 0, 0, 0, 0, 0, 0, // filetype + padding
  3293  				1, 0, 0, 0, 0, 0, 0, 0, // nlink
  3294  				0, 0, 0, 0, 0, 0, 0, 0, // size
  3295  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // atim
  3296  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // mtim
  3297  				0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // ctim
  3298  			),
  3299  			expectedLog: `
  3300  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=sub)
  3301  <== (filestat={filetype=DIRECTORY,size=0,mtim=1640995200000000000},errno=ESUCCESS)
  3302  `,
  3303  		},
  3304  		{
  3305  			name:          "unopened FD (follow symlinks)",
  3306  			fd:            -1,
  3307  			flags:         wasip1.LOOKUP_SYMLINK_FOLLOW,
  3308  			expectedErrno: wasip1.ErrnoBadf,
  3309  			expectedLog: `
  3310  ==> wasi_snapshot_preview1.path_filestat_get(fd=-1,flags=SYMLINK_FOLLOW,path=)
  3311  <== (filestat=,errno=EBADF)
  3312  `,
  3313  		},
  3314  		{
  3315  			name:           "Fd not a directory (follow symlinks)",
  3316  			fd:             fileFD,
  3317  			flags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
  3318  			memory:         initialMemoryFile,
  3319  			pathLen:        uint32(len(file)),
  3320  			resultFilestat: 2,
  3321  			expectedErrno:  wasip1.ErrnoNotdir,
  3322  			expectedLog: `
  3323  ==> wasi_snapshot_preview1.path_filestat_get(fd=4,flags=SYMLINK_FOLLOW,path=animals.txt)
  3324  <== (filestat=,errno=ENOTDIR)
  3325  `,
  3326  		},
  3327  		{
  3328  			name:           "path under root doesn't exist (follow symlinks)",
  3329  			fd:             sys.FdPreopen,
  3330  			flags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
  3331  			memory:         initialMemoryNotExists,
  3332  			pathLen:        1,
  3333  			resultFilestat: 2,
  3334  			expectedErrno:  wasip1.ErrnoNoent,
  3335  			expectedLog: `
  3336  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=?)
  3337  <== (filestat=,errno=ENOENT)
  3338  `,
  3339  		},
  3340  		{
  3341  			name:          "path is out of memory (follow symlinks)",
  3342  			fd:            sys.FdPreopen,
  3343  			flags:         wasip1.LOOKUP_SYMLINK_FOLLOW,
  3344  			memory:        initialMemoryFile,
  3345  			pathLen:       memorySize,
  3346  			expectedErrno: wasip1.ErrnoFault,
  3347  			expectedLog: `
  3348  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=OOM(1,65536))
  3349  <== (filestat=,errno=EFAULT)
  3350  `,
  3351  		},
  3352  		{
  3353  			name:           "resultFilestat exceeds the maximum valid address by 1 (follow symlinks)",
  3354  			fd:             sys.FdPreopen,
  3355  			flags:          wasip1.LOOKUP_SYMLINK_FOLLOW,
  3356  			memory:         initialMemoryFile,
  3357  			pathLen:        uint32(len(file)),
  3358  			resultFilestat: memorySize - 64 + 1,
  3359  			expectedErrno:  wasip1.ErrnoFault,
  3360  			expectedLog: `
  3361  ==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=animals.txt)
  3362  <== (filestat=,errno=EFAULT)
  3363  `,
  3364  		},
  3365  	}
  3366  
  3367  	for _, tt := range tests {
  3368  		tc := tt
  3369  
  3370  		t.Run(tc.name, func(t *testing.T) {
  3371  			defer log.Reset()
  3372  
  3373  			maskMemory(t, mod, len(tc.expectedMemory))
  3374  			mod.Memory().Write(0, tc.memory)
  3375  
  3376  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathFilestatGetName, uint64(tc.fd), uint64(tc.flags), uint64(1), uint64(tc.pathLen), uint64(tc.resultFilestat))
  3377  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  3378  
  3379  			actual, ok := mod.Memory().Read(0, uint32(len(tc.expectedMemory)))
  3380  			require.True(t, ok)
  3381  			require.Equal(t, tc.expectedMemory, actual)
  3382  		})
  3383  	}
  3384  }
  3385  
  3386  func Test_pathFilestatSetTimes(t *testing.T) {
  3387  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  3388  
  3389  	file := "file"
  3390  	writeFile(t, tmpDir, file, []byte("012"))
  3391  	link := file + "-link"
  3392  	require.NoError(t, os.Symlink(joinPath(tmpDir, file), joinPath(tmpDir, link)))
  3393  
  3394  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
  3395  		WithSysWalltime().
  3396  		WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "")))
  3397  	defer r.Close(testCtx)
  3398  
  3399  	tests := []struct {
  3400  		name          string
  3401  		flags         uint16
  3402  		pathName      string
  3403  		mtime, atime  int64
  3404  		fstFlags      uint16
  3405  		expectedLog   string
  3406  		expectedErrno wasip1.Errno
  3407  	}{
  3408  		{
  3409  			name:  "a=omit,m=omit",
  3410  			flags: wasip1.LOOKUP_SYMLINK_FOLLOW,
  3411  			atime: 123451, // Must be ignored.
  3412  			mtime: 1234,   // Must be ignored.
  3413  			expectedLog: `
  3414  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=)
  3415  <== errno=ESUCCESS
  3416  `,
  3417  		},
  3418  		{
  3419  			name:     "a=now,m=omit",
  3420  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3421  			atime:    123451, // Must be ignored.
  3422  			mtime:    1234,   // Must be ignored.
  3423  			fstFlags: wasip1.FstflagsAtimNow,
  3424  			expectedLog: `
  3425  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=ATIM_NOW)
  3426  <== errno=ESUCCESS
  3427  `,
  3428  		},
  3429  		{
  3430  			name:     "a=omit,m=now",
  3431  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3432  			atime:    123451, // Must be ignored.
  3433  			mtime:    1234,   // Must be ignored.
  3434  			fstFlags: wasip1.FstflagsMtimNow,
  3435  			expectedLog: `
  3436  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=MTIM_NOW)
  3437  <== errno=ESUCCESS
  3438  `,
  3439  		},
  3440  		{
  3441  			name:     "a=now,m=now",
  3442  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3443  			atime:    123451, // Must be ignored.
  3444  			mtime:    1234,   // Must be ignored.
  3445  			fstFlags: wasip1.FstflagsAtimNow | wasip1.FstflagsMtimNow,
  3446  			expectedLog: `
  3447  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=123451,mtim=1234,fst_flags=ATIM_NOW|MTIM_NOW)
  3448  <== errno=ESUCCESS
  3449  `,
  3450  		},
  3451  		{
  3452  			name:     "a=now,m=set",
  3453  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3454  			atime:    1234, // Must be ignored.
  3455  			mtime:    55555500,
  3456  			fstFlags: wasip1.FstflagsAtimNow | wasip1.FstflagsMtim,
  3457  			expectedLog: `
  3458  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=1234,mtim=55555500,fst_flags=ATIM_NOW|MTIM)
  3459  <== errno=ESUCCESS
  3460  `,
  3461  		},
  3462  		{
  3463  			name:     "a=set,m=now",
  3464  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3465  			atime:    55555500,
  3466  			mtime:    1234, // Must be ignored.
  3467  			fstFlags: wasip1.FstflagsAtim | wasip1.FstflagsMtimNow,
  3468  			expectedLog: `
  3469  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=55555500,mtim=1234,fst_flags=ATIM|MTIM_NOW)
  3470  <== errno=ESUCCESS
  3471  `,
  3472  		},
  3473  		{
  3474  			name:     "a=set,m=omit",
  3475  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3476  			atime:    55555500,
  3477  			mtime:    1234, // Must be ignored.
  3478  			fstFlags: wasip1.FstflagsAtim,
  3479  			expectedLog: `
  3480  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=55555500,mtim=1234,fst_flags=ATIM)
  3481  <== errno=ESUCCESS
  3482  `,
  3483  		},
  3484  		{
  3485  			name:     "a=omit,m=set",
  3486  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3487  			atime:    1234, // Must be ignored.
  3488  			mtime:    55555500,
  3489  			fstFlags: wasip1.FstflagsMtim,
  3490  			expectedLog: `
  3491  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=1234,mtim=55555500,fst_flags=MTIM)
  3492  <== errno=ESUCCESS
  3493  `,
  3494  		},
  3495  		{
  3496  			name:     "a=set,m=set",
  3497  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3498  			atime:    6666666600,
  3499  			mtime:    55555500,
  3500  			fstFlags: wasip1.FstflagsAtim | wasip1.FstflagsMtim,
  3501  			expectedLog: `
  3502  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=file,atim=6666666600,mtim=55555500,fst_flags=ATIM|MTIM)
  3503  <== errno=ESUCCESS
  3504  `,
  3505  		},
  3506  		{
  3507  			name:     "not found",
  3508  			pathName: "nope",
  3509  			flags:    wasip1.LOOKUP_SYMLINK_FOLLOW,
  3510  			fstFlags: wasip1.FstflagsAtimNow, // Choose one flag to ensure an update occurs
  3511  			expectedLog: `
  3512  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=SYMLINK_FOLLOW,path=nope,atim=0,mtim=0,fst_flags=ATIM_NOW)
  3513  <== errno=ENOENT
  3514  `,
  3515  			expectedErrno: wasip1.ErrnoNoent,
  3516  		},
  3517  		{
  3518  			name:     "no_symlink_follow",
  3519  			pathName: link,
  3520  			flags:    0,
  3521  			atime:    123451, // Must be ignored.
  3522  			mtime:    1234,   // Must be ignored.
  3523  			fstFlags: wasip1.FstflagsMtimNow,
  3524  			expectedLog: `
  3525  ==> wasi_snapshot_preview1.path_filestat_set_times(fd=3,flags=,path=file-link,atim=123451,mtim=1234,fst_flags=MTIM_NOW)
  3526  <== errno=ESUCCESS
  3527  `,
  3528  		},
  3529  	}
  3530  
  3531  	for _, tt := range tests {
  3532  		tc := tt
  3533  
  3534  		t.Run(tc.name, func(t *testing.T) {
  3535  			defer log.Reset()
  3536  
  3537  			if tc.flags == 0 && !sysfs.SupportsSymlinkNoFollow {
  3538  				tc.expectedErrno = wasip1.ErrnoNosys
  3539  				tc.expectedLog = strings.ReplaceAll(tc.expectedLog, "ESUCCESS", "ENOSYS")
  3540  			}
  3541  
  3542  			pathName := tc.pathName
  3543  			if pathName == "" {
  3544  				pathName = file
  3545  			}
  3546  			mod.Memory().Write(0, []byte(pathName))
  3547  
  3548  			fd := sys.FdPreopen
  3549  			path := 0
  3550  			pathLen := uint32(len(pathName))
  3551  
  3552  			sys := mod.(*wasm.ModuleInstance).Sys
  3553  			fsc := sys.FS()
  3554  
  3555  			var oldSt fsapi.Stat_t
  3556  			var errno syscall.Errno
  3557  			if tc.expectedErrno == wasip1.ErrnoSuccess {
  3558  				oldSt, errno = fsc.RootFS().Stat(pathName)
  3559  				require.EqualErrno(t, 0, errno)
  3560  			}
  3561  
  3562  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathFilestatSetTimesName, uint64(fd), uint64(tc.flags),
  3563  				uint64(path), uint64(pathLen), uint64(tc.atime), uint64(tc.mtime), uint64(tc.fstFlags))
  3564  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  3565  
  3566  			if tc.expectedErrno != wasip1.ErrnoSuccess {
  3567  				return
  3568  			}
  3569  
  3570  			newSt, errno := fsc.RootFS().Stat(pathName)
  3571  			require.EqualErrno(t, 0, errno)
  3572  
  3573  			if platform.CompilerSupported() {
  3574  				if tc.fstFlags&wasip1.FstflagsAtim != 0 {
  3575  					require.Equal(t, tc.atime, newSt.Atim)
  3576  				} else if tc.fstFlags&wasip1.FstflagsAtimNow != 0 {
  3577  					now := time.Now().UnixNano()
  3578  					require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
  3579  				} else { // omit
  3580  					require.Equal(t, oldSt.Atim, newSt.Atim)
  3581  				}
  3582  			}
  3583  
  3584  			// When compiler isn't supported, we can still check mtim.
  3585  			if tc.fstFlags&wasip1.FstflagsMtim != 0 {
  3586  				require.Equal(t, tc.mtime, newSt.Mtim)
  3587  			} else if tc.fstFlags&wasip1.FstflagsMtimNow != 0 {
  3588  				now := time.Now().UnixNano()
  3589  				require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
  3590  			} else { // omit
  3591  				require.Equal(t, oldSt.Mtim, newSt.Mtim)
  3592  			}
  3593  		})
  3594  	}
  3595  }
  3596  
  3597  func Test_pathLink(t *testing.T) {
  3598  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  3599  
  3600  	oldDirName := "my-old-dir"
  3601  	oldDirPath := joinPath(tmpDir, oldDirName)
  3602  	mod, oldFd, log, r := requireOpenFile(t, tmpDir, oldDirName, nil, false)
  3603  	defer r.Close(testCtx)
  3604  
  3605  	newDirName := "my-new-dir/sub"
  3606  	newDirPath := joinPath(tmpDir, newDirName)
  3607  	require.NoError(t, os.MkdirAll(joinPath(tmpDir, newDirName), 0o700))
  3608  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  3609  	newFd, errno := fsc.OpenFile(fsc.RootFS(), newDirName, 0o600, 0)
  3610  	require.EqualErrno(t, 0, errno)
  3611  
  3612  	mem := mod.Memory()
  3613  
  3614  	fileName := "file"
  3615  	err := os.WriteFile(joinPath(oldDirPath, fileName), []byte{1, 2, 3, 4}, 0o700)
  3616  	require.NoError(t, err)
  3617  
  3618  	file := uint32(0xff)
  3619  	ok := mem.Write(file, []byte(fileName))
  3620  	require.True(t, ok)
  3621  
  3622  	notFoundFile := uint32(0xaa)
  3623  	notFoundFileName := "nope"
  3624  	ok = mem.Write(notFoundFile, []byte(notFoundFileName))
  3625  	require.True(t, ok)
  3626  
  3627  	destination := uint32(0xcc)
  3628  	destinationName := "hard-linked"
  3629  	ok = mem.Write(destination, []byte(destinationName))
  3630  	require.True(t, ok)
  3631  
  3632  	destinationRealPath := joinPath(newDirPath, destinationName)
  3633  
  3634  	t.Run("success", func(t *testing.T) {
  3635  		requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathLinkName,
  3636  			uint64(oldFd), 0, uint64(file), uint64(len(fileName)),
  3637  			uint64(newFd), uint64(destination), uint64(len(destinationName)))
  3638  		require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess))
  3639  
  3640  		f := openFile(t, destinationRealPath, os.O_RDONLY, 0)
  3641  		defer f.Close()
  3642  
  3643  		st, errno := f.Stat()
  3644  		require.EqualErrno(t, 0, errno)
  3645  		require.False(t, st.Mode&os.ModeSymlink == os.ModeSymlink)
  3646  		require.Equal(t, uint64(2), st.Nlink)
  3647  	})
  3648  
  3649  	t.Run("errors", func(t *testing.T) {
  3650  		for _, tc := range []struct {
  3651  			errno wasip1.Errno
  3652  			oldFd int32
  3653  			/* oldFlags, */ oldPath, oldPathLen uint32
  3654  			newFd                               int32
  3655  			newPath, newPathLen                 uint32
  3656  		}{
  3657  			{errno: wasip1.ErrnoBadf, oldFd: 1000},
  3658  			{errno: wasip1.ErrnoBadf, oldFd: oldFd, newFd: 1000},
  3659  			{errno: wasip1.ErrnoNotdir, oldFd: oldFd, newFd: 1},
  3660  			{errno: wasip1.ErrnoNotdir, oldFd: 1, newFd: 1},
  3661  			{errno: wasip1.ErrnoNotdir, oldFd: 1, newFd: newFd},
  3662  			{errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd, oldPathLen: math.MaxUint32},
  3663  			{errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd, newPathLen: math.MaxUint32},
  3664  			{
  3665  				errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd,
  3666  				oldPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100,
  3667  			},
  3668  			{
  3669  				errno: wasip1.ErrnoFault, oldFd: oldFd, newFd: newFd,
  3670  				oldPath: 1, oldPathLen: 100, newPath: math.MaxUint32, newPathLen: 100,
  3671  			},
  3672  		} {
  3673  			name := wasip1.ErrnoName(tc.errno)
  3674  			t.Run(name, func(t *testing.T) {
  3675  				requireErrnoResult(t, tc.errno, mod, wasip1.PathLinkName,
  3676  					uint64(tc.oldFd), 0, uint64(tc.oldPath), uint64(tc.oldPathLen),
  3677  					uint64(tc.newFd), uint64(tc.newPath), uint64(tc.newPathLen))
  3678  				require.Contains(t, log.String(), name)
  3679  			})
  3680  		}
  3681  	})
  3682  }
  3683  
  3684  func Test_pathOpen(t *testing.T) {
  3685  	dir := t.TempDir() // open before loop to ensure no locking problems.
  3686  	writeFS := sysfs.NewDirFS(dir)
  3687  	readFS := sysfs.NewReadFS(writeFS)
  3688  
  3689  	fileName := "file"
  3690  	fileContents := []byte("012")
  3691  	writeFile(t, dir, fileName, fileContents)
  3692  
  3693  	appendName := "append"
  3694  	appendContents := []byte("345")
  3695  	writeFile(t, dir, appendName, appendContents)
  3696  
  3697  	truncName := "trunc"
  3698  	truncContents := []byte("678")
  3699  	writeFile(t, dir, truncName, truncContents)
  3700  
  3701  	dirName := "dir"
  3702  	mkdir(t, dir, dirName)
  3703  
  3704  	dirFileName := joinPath(dirName, fileName)
  3705  	dirFileContents := []byte("def")
  3706  	writeFile(t, dir, dirFileName, dirFileContents)
  3707  
  3708  	expectedOpenedFd := sys.FdPreopen + 1
  3709  
  3710  	tests := []struct {
  3711  		name          string
  3712  		fs            fsapi.FS
  3713  		path          func(t *testing.T) string
  3714  		oflags        uint16
  3715  		fdflags       uint16
  3716  		rights        uint32
  3717  		expected      func(t *testing.T, fsc *sys.FSContext)
  3718  		expectedErrno wasip1.Errno
  3719  		expectedLog   string
  3720  	}{
  3721  		{
  3722  			name: "sysfs.ReadFS",
  3723  			fs:   readFS,
  3724  			path: func(*testing.T) string { return fileName },
  3725  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3726  				requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
  3727  			},
  3728  			expectedLog: `
  3729  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3730  <== (opened_fd=4,errno=ESUCCESS)
  3731  `,
  3732  		},
  3733  		{
  3734  			name: "sysfs.DirFS",
  3735  			fs:   writeFS,
  3736  			path: func(*testing.T) string { return fileName },
  3737  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3738  				requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
  3739  			},
  3740  			expectedLog: `
  3741  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3742  <== (opened_fd=4,errno=ESUCCESS)
  3743  `,
  3744  		},
  3745  		{
  3746  			name:          "sysfs.ReadFS FD_APPEND",
  3747  			fs:            readFS,
  3748  			fdflags:       wasip1.FD_APPEND,
  3749  			path:          func(t *testing.T) (file string) { return appendName },
  3750  			expectedErrno: wasip1.ErrnoNosys,
  3751  			expectedLog: `
  3752  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=append,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=APPEND)
  3753  <== (opened_fd=,errno=ENOSYS)
  3754  `,
  3755  		},
  3756  		{
  3757  			name:    "sysfs.DirFS FD_APPEND",
  3758  			fs:      writeFS,
  3759  			path:    func(t *testing.T) (file string) { return appendName },
  3760  			fdflags: wasip1.FD_APPEND,
  3761  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3762  				contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
  3763  
  3764  				// verify the contents were appended
  3765  				b := readFile(t, dir, appendName)
  3766  				require.Equal(t, append(appendContents, contents...), b)
  3767  			},
  3768  			expectedLog: `
  3769  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=append,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=APPEND)
  3770  <== (opened_fd=4,errno=ESUCCESS)
  3771  `,
  3772  		},
  3773  		{
  3774  			name:          "sysfs.ReadFS O_CREAT",
  3775  			fs:            readFS,
  3776  			oflags:        wasip1.O_CREAT,
  3777  			expectedErrno: wasip1.ErrnoNosys,
  3778  			path:          func(*testing.T) string { return "creat" },
  3779  			expectedLog: `
  3780  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=creat,oflags=CREAT,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3781  <== (opened_fd=,errno=ENOSYS)
  3782  `,
  3783  		},
  3784  		{
  3785  			name:   "sysfs.DirFS O_CREAT",
  3786  			fs:     writeFS,
  3787  			path:   func(t *testing.T) (file string) { return "creat" },
  3788  			oflags: wasip1.O_CREAT,
  3789  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3790  				// expect to create a new file
  3791  				contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
  3792  
  3793  				// verify the contents were written
  3794  				b := readFile(t, dir, "creat")
  3795  				require.Equal(t, contents, b)
  3796  			},
  3797  			expectedLog: `
  3798  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=creat,oflags=CREAT,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3799  <== (opened_fd=4,errno=ESUCCESS)
  3800  `,
  3801  		},
  3802  		{
  3803  			name:          "sysfs.ReadFS O_CREAT O_TRUNC",
  3804  			fs:            readFS,
  3805  			oflags:        wasip1.O_CREAT | wasip1.O_TRUNC,
  3806  			expectedErrno: wasip1.ErrnoNosys,
  3807  			path:          func(t *testing.T) (file string) { return joinPath(dirName, "O_CREAT-O_TRUNC") },
  3808  			expectedLog: `
  3809  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/O_CREAT-O_TRUNC,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3810  <== (opened_fd=,errno=ENOSYS)
  3811  `,
  3812  		},
  3813  		{
  3814  			name:   "sysfs.DirFS O_CREAT O_TRUNC",
  3815  			fs:     writeFS,
  3816  			path:   func(t *testing.T) (file string) { return joinPath(dirName, "O_CREAT-O_TRUNC") },
  3817  			oflags: wasip1.O_CREAT | wasip1.O_TRUNC,
  3818  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3819  				// expect to create a new file
  3820  				contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
  3821  
  3822  				// verify the contents were written
  3823  				b := readFile(t, dir, joinPath(dirName, "O_CREAT-O_TRUNC"))
  3824  				require.Equal(t, contents, b)
  3825  			},
  3826  			expectedLog: `
  3827  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/O_CREAT-O_TRUNC,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3828  <== (opened_fd=4,errno=ESUCCESS)
  3829  `,
  3830  		},
  3831  		{
  3832  			name:   "sysfs.ReadFS O_DIRECTORY",
  3833  			fs:     readFS,
  3834  			oflags: wasip1.O_DIRECTORY,
  3835  			path:   func(*testing.T) string { return dirName },
  3836  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3837  				f, ok := fsc.LookupFile(expectedOpenedFd)
  3838  				require.True(t, ok)
  3839  				isDir, errno := f.File.IsDir()
  3840  				require.EqualErrno(t, 0, errno)
  3841  				require.True(t, isDir)
  3842  			},
  3843  			expectedLog: `
  3844  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3845  <== (opened_fd=4,errno=ESUCCESS)
  3846  `,
  3847  		},
  3848  		{
  3849  			name:   "sysfs.DirFS O_DIRECTORY",
  3850  			fs:     writeFS,
  3851  			path:   func(*testing.T) string { return dirName },
  3852  			oflags: wasip1.O_DIRECTORY,
  3853  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3854  				f, ok := fsc.LookupFile(expectedOpenedFd)
  3855  				require.True(t, ok)
  3856  				isDir, errno := f.File.IsDir()
  3857  				require.EqualErrno(t, 0, errno)
  3858  				require.True(t, isDir)
  3859  			},
  3860  			expectedLog: `
  3861  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3862  <== (opened_fd=4,errno=ESUCCESS)
  3863  `,
  3864  		},
  3865  		{
  3866  			name:          "sysfs.ReadFS O_TRUNC",
  3867  			fs:            readFS,
  3868  			oflags:        wasip1.O_TRUNC,
  3869  			expectedErrno: wasip1.ErrnoNosys,
  3870  			path:          func(*testing.T) string { return "trunc" },
  3871  			expectedLog: `
  3872  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=trunc,oflags=TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3873  <== (opened_fd=,errno=ENOSYS)
  3874  `,
  3875  		},
  3876  		{
  3877  			name:   "sysfs.DirFS O_TRUNC",
  3878  			fs:     writeFS,
  3879  			path:   func(t *testing.T) (file string) { return "trunc" },
  3880  			oflags: wasip1.O_TRUNC,
  3881  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3882  				contents := writeAndCloseFile(t, fsc, expectedOpenedFd)
  3883  
  3884  				// verify the contents were truncated
  3885  				b := readFile(t, dir, "trunc")
  3886  				require.Equal(t, contents, b)
  3887  			},
  3888  			expectedLog: `
  3889  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=trunc,oflags=TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  3890  <== (opened_fd=4,errno=ESUCCESS)
  3891  `,
  3892  		},
  3893  		{
  3894  			name:   "sysfs.DirFS RIGHT_FD_READ|RIGHT_FD_WRITE",
  3895  			fs:     writeFS,
  3896  			path:   func(*testing.T) string { return fileName },
  3897  			oflags: 0,
  3898  			rights: wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE,
  3899  			expected: func(t *testing.T, fsc *sys.FSContext) {
  3900  				requireContents(t, fsc, expectedOpenedFd, fileName, fileContents)
  3901  			},
  3902  			expectedLog: `
  3903  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=,fs_rights_base=FD_READ|FD_WRITE,fs_rights_inheriting=,fdflags=)
  3904  <== (opened_fd=4,errno=ESUCCESS)
  3905  `,
  3906  		},
  3907  	}
  3908  
  3909  	for _, tt := range tests {
  3910  		tc := tt
  3911  
  3912  		t.Run(tc.name, func(t *testing.T) {
  3913  			mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
  3914  			defer r.Close(testCtx)
  3915  
  3916  			mod.(*wasm.ModuleInstance).Sys = sys.DefaultContext(tc.fs)
  3917  
  3918  			pathName := tc.path(t)
  3919  			mod.Memory().Write(0, []byte(pathName))
  3920  
  3921  			path := uint32(0)
  3922  			pathLen := uint32(len(pathName))
  3923  			resultOpenedFd := pathLen
  3924  			fd := sys.FdPreopen
  3925  
  3926  			// TODO: dirflags is a lookupflags and it only has one bit: symlink_follow
  3927  			// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags
  3928  			dirflags := 0
  3929  
  3930  			// inherited rights aren't used
  3931  			fsRightsInheriting := uint64(0)
  3932  
  3933  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathOpenName, uint64(fd), uint64(dirflags), uint64(path),
  3934  				uint64(pathLen), uint64(tc.oflags), uint64(tc.rights), fsRightsInheriting, uint64(tc.fdflags), uint64(resultOpenedFd))
  3935  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  3936  
  3937  			if tc.expectedErrno == wasip1.ErrnoSuccess {
  3938  				openedFd, ok := mod.Memory().ReadUint32Le(pathLen)
  3939  				require.True(t, ok)
  3940  				require.Equal(t, expectedOpenedFd, int32(openedFd))
  3941  
  3942  				tc.expected(t, mod.(*wasm.ModuleInstance).Sys.FS())
  3943  			}
  3944  		})
  3945  	}
  3946  }
  3947  
  3948  func writeAndCloseFile(t *testing.T, fsc *sys.FSContext, fd int32) []byte {
  3949  	contents := []byte("hello")
  3950  	f, ok := fsc.LookupFile(fd)
  3951  	require.True(t, ok)
  3952  	_, errno := f.File.Write([]byte("hello"))
  3953  	require.EqualErrno(t, 0, errno)
  3954  	require.EqualErrno(t, 0, fsc.CloseFile(fd))
  3955  	return contents
  3956  }
  3957  
  3958  func requireOpenFD(t *testing.T, mod api.Module, path string) int32 {
  3959  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  3960  	preopen := fsc.RootFS()
  3961  
  3962  	fd, errno := fsc.OpenFile(preopen, path, os.O_RDONLY, 0)
  3963  	require.EqualErrno(t, 0, errno)
  3964  	return fd
  3965  }
  3966  
  3967  func requireContents(t *testing.T, fsc *sys.FSContext, expectedOpenedFd int32, fileName string, fileContents []byte) {
  3968  	// verify the file was actually opened
  3969  	f, ok := fsc.LookupFile(expectedOpenedFd)
  3970  	require.True(t, ok)
  3971  	require.Equal(t, fileName, f.Name)
  3972  
  3973  	// verify the contents are readable
  3974  	buf := readAll(t, f.File)
  3975  	require.Equal(t, fileContents, buf)
  3976  }
  3977  
  3978  func readAll(t *testing.T, f fsapi.File) []byte {
  3979  	st, errno := f.Stat()
  3980  	require.EqualErrno(t, 0, errno)
  3981  	buf := make([]byte, st.Size)
  3982  	_, errno = f.Read(buf)
  3983  	require.EqualErrno(t, 0, errno)
  3984  	return buf
  3985  }
  3986  
  3987  func mkdir(t *testing.T, tmpDir, dir string) {
  3988  	err := os.Mkdir(joinPath(tmpDir, dir), 0o700)
  3989  	require.NoError(t, err)
  3990  }
  3991  
  3992  func readFile(t *testing.T, tmpDir, file string) []byte {
  3993  	contents, err := os.ReadFile(joinPath(tmpDir, file))
  3994  	require.NoError(t, err)
  3995  	return contents
  3996  }
  3997  
  3998  func writeFile(t *testing.T, tmpDir, file string, contents []byte) {
  3999  	err := os.WriteFile(joinPath(tmpDir, file), contents, 0o600)
  4000  	require.NoError(t, err)
  4001  }
  4002  
  4003  func Test_pathOpen_Errors(t *testing.T) {
  4004  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4005  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4006  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4007  	defer r.Close(testCtx)
  4008  
  4009  	file := "file"
  4010  	err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
  4011  	require.NoError(t, err)
  4012  	fileFD := requireOpenFD(t, mod, file)
  4013  
  4014  	dir := "dir"
  4015  	err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
  4016  	require.NoError(t, err)
  4017  
  4018  	nested := "dir/nested"
  4019  	err = os.Mkdir(joinPath(tmpDir, nested), 0o700)
  4020  	require.NoError(t, err)
  4021  
  4022  	nestedFile := "dir/nested/file"
  4023  	err = os.WriteFile(joinPath(tmpDir, nestedFile), []byte{}, 0o700)
  4024  	require.NoError(t, err)
  4025  
  4026  	tests := []struct {
  4027  		name, pathName                        string
  4028  		fd                                    int32
  4029  		path, pathLen, oflags, resultOpenedFd uint32
  4030  		expectedErrno                         wasip1.Errno
  4031  		expectedLog                           string
  4032  	}{
  4033  		{
  4034  			name:          "unopened FD",
  4035  			fd:            42, // arbitrary invalid fd
  4036  			expectedErrno: wasip1.ErrnoBadf,
  4037  			expectedLog: `
  4038  ==> wasi_snapshot_preview1.path_open(fd=42,dirflags=,path=,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4039  <== (opened_fd=,errno=EBADF)
  4040  `,
  4041  		},
  4042  		{
  4043  			name:          "Fd not a directory",
  4044  			fd:            fileFD,
  4045  			pathName:      file,
  4046  			path:          0,
  4047  			pathLen:       uint32(len(file)),
  4048  			expectedErrno: wasip1.ErrnoNotdir,
  4049  			expectedLog: `
  4050  ==> wasi_snapshot_preview1.path_open(fd=4,dirflags=,path=file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4051  <== (opened_fd=,errno=ENOTDIR)
  4052  `,
  4053  		},
  4054  		{
  4055  			name:          "out-of-memory reading path",
  4056  			fd:            sys.FdPreopen,
  4057  			path:          mod.Memory().Size(),
  4058  			pathLen:       uint32(len(file)),
  4059  			expectedErrno: wasip1.ErrnoFault,
  4060  			expectedLog: `
  4061  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=OOM(65536,4),oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4062  <== (opened_fd=,errno=EFAULT)
  4063  `,
  4064  		},
  4065  		{
  4066  			name:          "out-of-memory reading pathLen",
  4067  			fd:            sys.FdPreopen,
  4068  			path:          0,
  4069  			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
  4070  			expectedErrno: wasip1.ErrnoFault,
  4071  			expectedLog: `
  4072  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=OOM(0,65537),oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4073  <== (opened_fd=,errno=EFAULT)
  4074  `,
  4075  		},
  4076  		{
  4077  			name:          "no such file exists",
  4078  			fd:            sys.FdPreopen,
  4079  			pathName:      dir,
  4080  			path:          0,
  4081  			pathLen:       uint32(len(dir)) - 1,
  4082  			expectedErrno: wasip1.ErrnoNoent,
  4083  			expectedLog: `
  4084  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=di,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4085  <== (opened_fd=,errno=ENOENT)
  4086  `,
  4087  		},
  4088  		{
  4089  			name:          "trailing slash on directory",
  4090  			fd:            sys.FdPreopen,
  4091  			pathName:      nested + "/",
  4092  			path:          0,
  4093  			pathLen:       uint32(len(nested)) + 1,
  4094  			expectedErrno: wasip1.ErrnoSuccess,
  4095  			expectedLog: `
  4096  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/nested/,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4097  <== (opened_fd=5,errno=ESUCCESS)
  4098  `,
  4099  		},
  4100  		{
  4101  			name:          "path under preopen",
  4102  			fd:            sys.FdPreopen,
  4103  			pathName:      "../" + file,
  4104  			path:          0,
  4105  			pathLen:       uint32(len(file)) + 3,
  4106  			expectedErrno: wasip1.ErrnoPerm,
  4107  			expectedLog: `
  4108  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=../file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4109  <== (opened_fd=,errno=EPERM)
  4110  `,
  4111  		},
  4112  		{
  4113  			name:          "rooted path",
  4114  			fd:            sys.FdPreopen,
  4115  			pathName:      "/" + file,
  4116  			path:          0,
  4117  			pathLen:       uint32(len(file)) + 1,
  4118  			expectedErrno: wasip1.ErrnoPerm,
  4119  			expectedLog: `
  4120  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=/file,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4121  <== (opened_fd=,errno=EPERM)
  4122  `,
  4123  		},
  4124  		{
  4125  			name:          "trailing slash on file",
  4126  			fd:            sys.FdPreopen,
  4127  			pathName:      nestedFile + "/",
  4128  			path:          0,
  4129  			pathLen:       uint32(len(nestedFile)) + 1,
  4130  			expectedErrno: wasip1.ErrnoNotdir,
  4131  			expectedLog: `
  4132  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir/nested/file/,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4133  <== (opened_fd=,errno=ENOTDIR)
  4134  `,
  4135  		},
  4136  		{
  4137  			name:           "out-of-memory writing resultOpenedFd",
  4138  			fd:             sys.FdPreopen,
  4139  			pathName:       dir,
  4140  			path:           0,
  4141  			pathLen:        uint32(len(dir)),
  4142  			resultOpenedFd: mod.Memory().Size(), // path and pathLen correctly point to the right path, but where to write the opened FD is outside memory.
  4143  			expectedErrno:  wasip1.ErrnoFault,
  4144  			expectedLog: `
  4145  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=dir,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4146  <== (opened_fd=,errno=EFAULT)
  4147  `,
  4148  		},
  4149  		{
  4150  			name:          "O_DIRECTORY, but not a directory",
  4151  			oflags:        uint32(wasip1.O_DIRECTORY),
  4152  			fd:            sys.FdPreopen,
  4153  			pathName:      file,
  4154  			path:          0,
  4155  			pathLen:       uint32(len(file)),
  4156  			expectedErrno: wasip1.ErrnoNotdir,
  4157  			expectedLog: `
  4158  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4159  <== (opened_fd=,errno=ENOTDIR)
  4160  `,
  4161  		},
  4162  		{
  4163  			name:          "oflags=directory and create invalid",
  4164  			oflags:        uint32(wasip1.O_DIRECTORY | wasip1.O_CREAT),
  4165  			fd:            sys.FdPreopen,
  4166  			pathName:      file,
  4167  			path:          0,
  4168  			pathLen:       uint32(len(file)),
  4169  			expectedErrno: wasip1.ErrnoInval,
  4170  			expectedLog: `
  4171  ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=file,oflags=CREAT|DIRECTORY,fs_rights_base=,fs_rights_inheriting=,fdflags=)
  4172  <== (opened_fd=,errno=EINVAL)
  4173  `,
  4174  		},
  4175  	}
  4176  
  4177  	for _, tt := range tests {
  4178  		tc := tt
  4179  		t.Run(tc.name, func(t *testing.T) {
  4180  			defer log.Reset()
  4181  
  4182  			mod.Memory().Write(tc.path, []byte(tc.pathName))
  4183  
  4184  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathOpenName, uint64(tc.fd), uint64(0), uint64(tc.path),
  4185  				uint64(tc.pathLen), uint64(tc.oflags), 0, 0, 0, uint64(tc.resultOpenedFd))
  4186  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  4187  		})
  4188  	}
  4189  }
  4190  
  4191  func Test_pathReadlink(t *testing.T) {
  4192  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4193  
  4194  	dirName := "dir"
  4195  	dirPath := joinPath(tmpDir, dirName)
  4196  	mod, dirFD, log, r := requireOpenFile(t, tmpDir, dirName, nil, false)
  4197  	defer r.Close(testCtx)
  4198  
  4199  	subDirName := "sub-dir"
  4200  	require.NoError(t, os.Mkdir(joinPath(dirPath, subDirName), 0o700))
  4201  
  4202  	mem := mod.Memory()
  4203  
  4204  	originalFileName := "top-original-file"
  4205  	destinationPath := uint32(0x77)
  4206  	destinationPathName := "top-symlinked"
  4207  	ok := mem.Write(destinationPath, []byte(destinationPathName))
  4208  	require.True(t, ok)
  4209  
  4210  	originalSubDirFileName := joinPath(subDirName, "subdir-original-file")
  4211  	destinationSubDirFileName := joinPath(subDirName, "subdir-symlinked")
  4212  	destinationSubDirPathNamePtr := uint32(0xcc)
  4213  	ok = mem.Write(destinationSubDirPathNamePtr, []byte(destinationSubDirFileName))
  4214  	require.True(t, ok)
  4215  
  4216  	// Create original file and symlink to the destination.
  4217  	originalRelativePath := joinPath(dirName, originalFileName)
  4218  	err := os.WriteFile(joinPath(tmpDir, originalRelativePath), []byte{4, 3, 2, 1}, 0o700)
  4219  	require.NoError(t, err)
  4220  	err = os.Symlink(originalRelativePath, joinPath(dirPath, destinationPathName))
  4221  	require.NoError(t, err)
  4222  	originalSubDirRelativePath := joinPath(dirName, originalSubDirFileName)
  4223  	err = os.WriteFile(joinPath(tmpDir, originalSubDirRelativePath), []byte{1, 2, 3, 4}, 0o700)
  4224  	require.NoError(t, err)
  4225  	err = os.Symlink(originalSubDirRelativePath, joinPath(dirPath, destinationSubDirFileName))
  4226  	require.NoError(t, err)
  4227  
  4228  	t.Run("ok", func(t *testing.T) {
  4229  		for _, tc := range []struct {
  4230  			name          string
  4231  			path, pathLen uint32
  4232  			expectedBuf   string
  4233  		}{
  4234  			{
  4235  				name:        "top",
  4236  				path:        destinationPath,
  4237  				pathLen:     uint32(len(destinationPathName)),
  4238  				expectedBuf: originalRelativePath,
  4239  			},
  4240  			{
  4241  				name:        "subdir",
  4242  				path:        destinationSubDirPathNamePtr,
  4243  				pathLen:     uint32(len(destinationSubDirFileName)),
  4244  				expectedBuf: originalSubDirRelativePath,
  4245  			},
  4246  		} {
  4247  			t.Run(tc.name, func(t *testing.T) {
  4248  				const buf, bufLen, resultBufused = 0x100, 0xff, 0x200
  4249  				requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathReadlinkName,
  4250  					uint64(dirFD), uint64(tc.path), uint64(tc.pathLen),
  4251  					buf, bufLen, resultBufused)
  4252  				require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess))
  4253  
  4254  				size, ok := mem.ReadUint32Le(resultBufused)
  4255  				require.True(t, ok)
  4256  				actual, ok := mem.Read(buf, size)
  4257  				require.True(t, ok)
  4258  				require.Equal(t, tc.expectedBuf, string(actual))
  4259  			})
  4260  		}
  4261  	})
  4262  
  4263  	t.Run("errors", func(t *testing.T) {
  4264  		for _, tc := range []struct {
  4265  			name                                      string
  4266  			fd                                        int32
  4267  			path, pathLen, buf, bufLen, resultBufused uint32
  4268  			expectedErrno                             wasip1.Errno
  4269  		}{
  4270  			{expectedErrno: wasip1.ErrnoInval},
  4271  			{expectedErrno: wasip1.ErrnoInval, pathLen: 100},
  4272  			{expectedErrno: wasip1.ErrnoInval, bufLen: 100},
  4273  			{
  4274  				name:          "bufLen too short",
  4275  				expectedErrno: wasip1.ErrnoFault,
  4276  				fd:            dirFD,
  4277  				bufLen:        10,
  4278  				path:          destinationPath,
  4279  				pathLen:       uint32(len(destinationPathName)),
  4280  				buf:           math.MaxUint32,
  4281  			},
  4282  			{
  4283  				name:          "path past memory",
  4284  				expectedErrno: wasip1.ErrnoFault,
  4285  				bufLen:        100,
  4286  				pathLen:       100,
  4287  				buf:           50,
  4288  				path:          math.MaxUint32,
  4289  			},
  4290  			{expectedErrno: wasip1.ErrnoNotdir, bufLen: 100, pathLen: 100, buf: 50, path: 50, fd: 1},
  4291  			{expectedErrno: wasip1.ErrnoBadf, bufLen: 100, pathLen: 100, buf: 50, path: 50, fd: 1000},
  4292  			{
  4293  				expectedErrno: wasip1.ErrnoNoent,
  4294  				bufLen:        100, buf: 50,
  4295  				path: destinationPath, pathLen: uint32(len(destinationPathName)) - 1,
  4296  				fd: dirFD,
  4297  			},
  4298  		} {
  4299  			name := tc.name
  4300  			if name == "" {
  4301  				name = wasip1.ErrnoName(tc.expectedErrno)
  4302  			}
  4303  			t.Run(name, func(t *testing.T) {
  4304  				requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathReadlinkName,
  4305  					uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen), uint64(tc.buf),
  4306  					uint64(tc.bufLen), uint64(tc.resultBufused))
  4307  				require.Contains(t, log.String(), wasip1.ErrnoName(tc.expectedErrno))
  4308  			})
  4309  		}
  4310  	})
  4311  }
  4312  
  4313  func Test_pathRemoveDirectory(t *testing.T) {
  4314  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4315  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4316  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4317  	defer r.Close(testCtx)
  4318  
  4319  	// set up the initial memory to include the path name starting at an offset.
  4320  	pathName := "wazero"
  4321  	realPath := joinPath(tmpDir, pathName)
  4322  	ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
  4323  	require.True(t, ok)
  4324  
  4325  	// create the directory
  4326  	err := os.Mkdir(realPath, 0o700)
  4327  	require.NoError(t, err)
  4328  
  4329  	fd := sys.FdPreopen
  4330  	name := 1
  4331  	nameLen := len(pathName)
  4332  
  4333  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathRemoveDirectoryName, uint64(fd), uint64(name), uint64(nameLen))
  4334  	require.Equal(t, `
  4335  ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=wazero)
  4336  <== errno=ESUCCESS
  4337  `, "\n"+log.String())
  4338  
  4339  	// ensure the directory was removed
  4340  	_, err = os.Stat(realPath)
  4341  	require.Error(t, err)
  4342  }
  4343  
  4344  func Test_pathRemoveDirectory_Errors(t *testing.T) {
  4345  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4346  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4347  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4348  	defer r.Close(testCtx)
  4349  
  4350  	file := "file"
  4351  	err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
  4352  	require.NoError(t, err)
  4353  	fileFD := requireOpenFD(t, mod, file)
  4354  
  4355  	dirNotEmpty := "notempty"
  4356  	dirNotEmptyPath := joinPath(tmpDir, dirNotEmpty)
  4357  	err = os.Mkdir(dirNotEmptyPath, 0o700)
  4358  	require.NoError(t, err)
  4359  
  4360  	dir := "dir"
  4361  	err = os.Mkdir(joinPath(dirNotEmptyPath, dir), 0o700)
  4362  	require.NoError(t, err)
  4363  
  4364  	tests := []struct {
  4365  		name, pathName string
  4366  		fd             int32
  4367  		path, pathLen  uint32
  4368  		expectedErrno  wasip1.Errno
  4369  		expectedLog    string
  4370  	}{
  4371  		{
  4372  			name:          "unopened FD",
  4373  			fd:            42, // arbitrary invalid fd
  4374  			expectedErrno: wasip1.ErrnoBadf,
  4375  			expectedLog: `
  4376  ==> wasi_snapshot_preview1.path_remove_directory(fd=42,path=)
  4377  <== errno=EBADF
  4378  `,
  4379  		},
  4380  		{
  4381  			name:          "Fd not a directory",
  4382  			fd:            fileFD,
  4383  			pathName:      file,
  4384  			path:          0,
  4385  			pathLen:       uint32(len(file)),
  4386  			expectedErrno: wasip1.ErrnoNotdir,
  4387  			expectedLog: `
  4388  ==> wasi_snapshot_preview1.path_remove_directory(fd=4,path=file)
  4389  <== errno=ENOTDIR
  4390  `,
  4391  		},
  4392  		{
  4393  			name:          "out-of-memory reading path",
  4394  			fd:            sys.FdPreopen,
  4395  			path:          mod.Memory().Size(),
  4396  			pathLen:       1,
  4397  			expectedErrno: wasip1.ErrnoFault,
  4398  			expectedLog: `
  4399  ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=OOM(65536,1))
  4400  <== errno=EFAULT
  4401  `,
  4402  		},
  4403  		{
  4404  			name:          "out-of-memory reading pathLen",
  4405  			fd:            sys.FdPreopen,
  4406  			path:          0,
  4407  			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
  4408  			expectedErrno: wasip1.ErrnoFault,
  4409  			expectedLog: `
  4410  ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=OOM(0,65537))
  4411  <== errno=EFAULT
  4412  `,
  4413  		},
  4414  		{
  4415  			name:          "no such file exists",
  4416  			fd:            sys.FdPreopen,
  4417  			pathName:      file,
  4418  			path:          0,
  4419  			pathLen:       uint32(len(file) - 1),
  4420  			expectedErrno: wasip1.ErrnoNoent,
  4421  			expectedLog: `
  4422  ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=fil)
  4423  <== errno=ENOENT
  4424  `,
  4425  		},
  4426  		{
  4427  			name:          "file not dir",
  4428  			fd:            sys.FdPreopen,
  4429  			pathName:      file,
  4430  			path:          0,
  4431  			pathLen:       uint32(len(file)),
  4432  			expectedErrno: wasip1.ErrnoNotdir,
  4433  			expectedLog: fmt.Sprintf(`
  4434  ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=file)
  4435  <== errno=%s
  4436  `, wasip1.ErrnoName(wasip1.ErrnoNotdir)),
  4437  		},
  4438  		{
  4439  			name:          "dir not empty",
  4440  			fd:            sys.FdPreopen,
  4441  			pathName:      dirNotEmpty,
  4442  			path:          0,
  4443  			pathLen:       uint32(len(dirNotEmpty)),
  4444  			expectedErrno: wasip1.ErrnoNotempty,
  4445  			expectedLog: `
  4446  ==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=notempty)
  4447  <== errno=ENOTEMPTY
  4448  `,
  4449  		},
  4450  	}
  4451  
  4452  	for _, tt := range tests {
  4453  		tc := tt
  4454  		t.Run(tc.name, func(t *testing.T) {
  4455  			defer log.Reset()
  4456  
  4457  			mod.Memory().Write(tc.path, []byte(tc.pathName))
  4458  
  4459  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathRemoveDirectoryName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
  4460  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  4461  		})
  4462  	}
  4463  }
  4464  
  4465  func Test_pathSymlink_errors(t *testing.T) {
  4466  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4467  
  4468  	dirName := "dir"
  4469  	dirPath := joinPath(tmpDir, dirName)
  4470  	mod, fd, log, r := requireOpenFile(t, tmpDir, dirName, nil, false)
  4471  	defer r.Close(testCtx)
  4472  
  4473  	mem := mod.Memory()
  4474  
  4475  	fileName := "file"
  4476  	err := os.WriteFile(joinPath(dirPath, fileName), []byte{1, 2, 3, 4}, 0o700)
  4477  	require.NoError(t, err)
  4478  
  4479  	file := uint32(0xff)
  4480  	ok := mem.Write(file, []byte(fileName))
  4481  	require.True(t, ok)
  4482  
  4483  	notFoundFile := uint32(0xaa)
  4484  	notFoundFileName := "nope"
  4485  	ok = mem.Write(notFoundFile, []byte(notFoundFileName))
  4486  	require.True(t, ok)
  4487  
  4488  	link := uint32(0xcc)
  4489  	linkName := fileName + "-link"
  4490  	ok = mem.Write(link, []byte(linkName))
  4491  	require.True(t, ok)
  4492  
  4493  	t.Run("success", func(t *testing.T) {
  4494  		requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathSymlinkName,
  4495  			uint64(file), uint64(len(fileName)), uint64(fd), uint64(link), uint64(len(linkName)))
  4496  		require.Contains(t, log.String(), wasip1.ErrnoName(wasip1.ErrnoSuccess))
  4497  		st, err := os.Lstat(joinPath(dirPath, linkName))
  4498  		require.NoError(t, err)
  4499  		require.Equal(t, st.Mode()&os.ModeSymlink, os.ModeSymlink)
  4500  	})
  4501  
  4502  	t.Run("errors", func(t *testing.T) {
  4503  		for _, tc := range []struct {
  4504  			errno               wasip1.Errno
  4505  			oldPath, oldPathLen uint32
  4506  			fd                  int32
  4507  			newPath, newPathLen uint32
  4508  		}{
  4509  			{errno: wasip1.ErrnoBadf, fd: 1000},
  4510  			{errno: wasip1.ErrnoNotdir, fd: 2},
  4511  			// Length zero buffer is not valid.
  4512  			{errno: wasip1.ErrnoInval, fd: fd},
  4513  			{errno: wasip1.ErrnoInval, oldPathLen: 100, fd: fd},
  4514  			{errno: wasip1.ErrnoInval, newPathLen: 100, fd: fd},
  4515  			// Invalid pointer to the names.
  4516  			{errno: wasip1.ErrnoFault, oldPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100, fd: fd},
  4517  			{errno: wasip1.ErrnoFault, newPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100, fd: fd},
  4518  			{errno: wasip1.ErrnoFault, oldPath: math.MaxUint32, newPath: math.MaxUint32, oldPathLen: 100, newPathLen: 100, fd: fd},
  4519  			// Non-existing path as source.
  4520  			{
  4521  				errno: wasip1.ErrnoInval, oldPath: notFoundFile, oldPathLen: uint32(len(notFoundFileName)),
  4522  				newPath: 0, newPathLen: 5, fd: fd,
  4523  			},
  4524  			// Linking to existing file.
  4525  			{
  4526  				errno: wasip1.ErrnoExist, oldPath: file, oldPathLen: uint32(len(fileName)),
  4527  				newPath: file, newPathLen: uint32(len(fileName)), fd: fd,
  4528  			},
  4529  		} {
  4530  			name := wasip1.ErrnoName(tc.errno)
  4531  			t.Run(name, func(t *testing.T) {
  4532  				requireErrnoResult(t, tc.errno, mod, wasip1.PathSymlinkName,
  4533  					uint64(tc.oldPath), uint64(tc.oldPathLen), uint64(tc.fd), uint64(tc.newPath), uint64(tc.newPathLen))
  4534  				require.Contains(t, log.String(), name)
  4535  			})
  4536  		}
  4537  	})
  4538  }
  4539  
  4540  func Test_pathRename(t *testing.T) {
  4541  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4542  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4543  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4544  	defer r.Close(testCtx)
  4545  
  4546  	// set up the initial memory to include the old path name starting at an offset.
  4547  	oldfd := sys.FdPreopen
  4548  	oldPathName := "wazero"
  4549  	realOldPath := joinPath(tmpDir, oldPathName)
  4550  	oldPath := uint32(0)
  4551  	oldPathLen := len(oldPathName)
  4552  	ok := mod.Memory().Write(oldPath, []byte(oldPathName))
  4553  	require.True(t, ok)
  4554  
  4555  	// create the file
  4556  	err := os.WriteFile(realOldPath, []byte{}, 0o600)
  4557  	require.NoError(t, err)
  4558  
  4559  	newfd := sys.FdPreopen
  4560  	newPathName := "wahzero"
  4561  	realNewPath := joinPath(tmpDir, newPathName)
  4562  	newPath := uint32(16)
  4563  	newPathLen := len(newPathName)
  4564  	ok = mod.Memory().Write(newPath, []byte(newPathName))
  4565  	require.True(t, ok)
  4566  
  4567  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathRenameName,
  4568  		uint64(oldfd), uint64(oldPath), uint64(oldPathLen),
  4569  		uint64(newfd), uint64(newPath), uint64(newPathLen))
  4570  	require.Equal(t, `
  4571  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=wazero,new_fd=3,new_path=wahzero)
  4572  <== errno=ESUCCESS
  4573  `, "\n"+log.String())
  4574  
  4575  	// ensure the file was renamed
  4576  	_, err = os.Stat(realOldPath)
  4577  	require.Error(t, err)
  4578  	_, err = os.Stat(realNewPath)
  4579  	require.NoError(t, err)
  4580  }
  4581  
  4582  func Test_pathRename_Errors(t *testing.T) {
  4583  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4584  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4585  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4586  	defer r.Close(testCtx)
  4587  
  4588  	file := "file"
  4589  	err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
  4590  	require.NoError(t, err)
  4591  
  4592  	// We have to test FD validation with a path not under test. Otherwise,
  4593  	// Windows may fail for the wrong reason, like:
  4594  	//	The process cannot access the file because it is being used by another process.
  4595  	file1 := "file1"
  4596  	err = os.WriteFile(joinPath(tmpDir, file1), []byte{}, 0o700)
  4597  	require.NoError(t, err)
  4598  	fileFD := requireOpenFD(t, mod, file1)
  4599  
  4600  	dirNotEmpty := "notempty"
  4601  	err = os.Mkdir(joinPath(tmpDir, dirNotEmpty), 0o700)
  4602  	require.NoError(t, err)
  4603  
  4604  	dir := joinPath(dirNotEmpty, "dir")
  4605  	err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
  4606  	require.NoError(t, err)
  4607  
  4608  	tests := []struct {
  4609  		name, oldPathName, newPathName string
  4610  		oldFd                          int32
  4611  		oldPath, oldPathLen            uint32
  4612  		newFd                          int32
  4613  		newPath, newPathLen            uint32
  4614  		expectedErrno                  wasip1.Errno
  4615  		expectedLog                    string
  4616  	}{
  4617  		{
  4618  			name:          "unopened old FD",
  4619  			oldFd:         42, // arbitrary invalid fd
  4620  			newFd:         sys.FdPreopen,
  4621  			expectedErrno: wasip1.ErrnoBadf,
  4622  			expectedLog: `
  4623  ==> wasi_snapshot_preview1.path_rename(fd=42,old_path=,new_fd=3,new_path=)
  4624  <== errno=EBADF
  4625  `,
  4626  		},
  4627  		{
  4628  			name:          "old FD not a directory",
  4629  			oldFd:         fileFD,
  4630  			newFd:         sys.FdPreopen,
  4631  			expectedErrno: wasip1.ErrnoNotdir,
  4632  			expectedLog: `
  4633  ==> wasi_snapshot_preview1.path_rename(fd=4,old_path=,new_fd=3,new_path=)
  4634  <== errno=ENOTDIR
  4635  `,
  4636  		},
  4637  		{
  4638  			name:          "unopened new FD",
  4639  			oldFd:         sys.FdPreopen,
  4640  			newFd:         42, // arbitrary invalid fd
  4641  			expectedErrno: wasip1.ErrnoBadf,
  4642  			expectedLog: `
  4643  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=,new_fd=42,new_path=)
  4644  <== errno=EBADF
  4645  `,
  4646  		},
  4647  		{
  4648  			name:          "new FD not a directory",
  4649  			oldFd:         sys.FdPreopen,
  4650  			newFd:         fileFD,
  4651  			expectedErrno: wasip1.ErrnoNotdir,
  4652  			expectedLog: `
  4653  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=,new_fd=4,new_path=)
  4654  <== errno=ENOTDIR
  4655  `,
  4656  		},
  4657  		{
  4658  			name:          "out-of-memory reading old path",
  4659  			oldFd:         sys.FdPreopen,
  4660  			newFd:         sys.FdPreopen,
  4661  			oldPath:       mod.Memory().Size(),
  4662  			oldPathLen:    1,
  4663  			expectedErrno: wasip1.ErrnoFault,
  4664  			expectedLog: `
  4665  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=OOM(65536,1),new_fd=3,new_path=)
  4666  <== errno=EFAULT
  4667  `,
  4668  		},
  4669  		{
  4670  			name:          "out-of-memory reading new path",
  4671  			oldFd:         sys.FdPreopen,
  4672  			newFd:         sys.FdPreopen,
  4673  			oldPath:       0,
  4674  			oldPathName:   "a",
  4675  			oldPathLen:    1,
  4676  			newPath:       mod.Memory().Size(),
  4677  			newPathLen:    1,
  4678  			expectedErrno: wasip1.ErrnoFault,
  4679  			expectedLog: `
  4680  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=a,new_fd=3,new_path=OOM(65536,1))
  4681  <== errno=EFAULT
  4682  `,
  4683  		},
  4684  		{
  4685  			name:          "out-of-memory reading old pathLen",
  4686  			oldFd:         sys.FdPreopen,
  4687  			newFd:         sys.FdPreopen,
  4688  			oldPath:       0,
  4689  			oldPathLen:    mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
  4690  			expectedErrno: wasip1.ErrnoFault,
  4691  			expectedLog: `
  4692  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=OOM(0,65537),new_fd=3,new_path=)
  4693  <== errno=EFAULT
  4694  `,
  4695  		},
  4696  		{
  4697  			name:          "out-of-memory reading new pathLen",
  4698  			oldFd:         sys.FdPreopen,
  4699  			newFd:         sys.FdPreopen,
  4700  			oldPathName:   file,
  4701  			oldPathLen:    uint32(len(file)),
  4702  			newPath:       0,
  4703  			newPathLen:    mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
  4704  			expectedErrno: wasip1.ErrnoFault,
  4705  			expectedLog: `
  4706  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=file,new_fd=3,new_path=OOM(0,65537))
  4707  <== errno=EFAULT
  4708  `,
  4709  		},
  4710  		{
  4711  			name:          "no such file exists",
  4712  			oldFd:         sys.FdPreopen,
  4713  			newFd:         sys.FdPreopen,
  4714  			oldPathName:   file,
  4715  			oldPathLen:    uint32(len(file)) - 1,
  4716  			newPath:       16,
  4717  			newPathName:   file,
  4718  			newPathLen:    uint32(len(file)),
  4719  			expectedErrno: wasip1.ErrnoNoent,
  4720  			expectedLog: `
  4721  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=fil,new_fd=3,new_path=file)
  4722  <== errno=ENOENT
  4723  `,
  4724  		},
  4725  		{
  4726  			name:          "dir not file",
  4727  			oldFd:         sys.FdPreopen,
  4728  			newFd:         sys.FdPreopen,
  4729  			oldPathName:   file,
  4730  			oldPathLen:    uint32(len(file)),
  4731  			newPath:       16,
  4732  			newPathName:   dir,
  4733  			newPathLen:    uint32(len(dir)),
  4734  			expectedErrno: wasip1.ErrnoIsdir,
  4735  			expectedLog: `
  4736  ==> wasi_snapshot_preview1.path_rename(fd=3,old_path=file,new_fd=3,new_path=notempty/dir)
  4737  <== errno=EISDIR
  4738  `,
  4739  		},
  4740  	}
  4741  
  4742  	for _, tt := range tests {
  4743  		tc := tt
  4744  		t.Run(tc.name, func(t *testing.T) {
  4745  			defer log.Reset()
  4746  
  4747  			mod.Memory().Write(tc.oldPath, []byte(tc.oldPathName))
  4748  			mod.Memory().Write(tc.newPath, []byte(tc.newPathName))
  4749  
  4750  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathRenameName,
  4751  				uint64(tc.oldFd), uint64(tc.oldPath), uint64(tc.oldPathLen),
  4752  				uint64(tc.newFd), uint64(tc.newPath), uint64(tc.newPathLen))
  4753  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  4754  		})
  4755  	}
  4756  }
  4757  
  4758  func Test_pathUnlinkFile(t *testing.T) {
  4759  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4760  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4761  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4762  	defer r.Close(testCtx)
  4763  
  4764  	// set up the initial memory to include the path name starting at an offset.
  4765  	pathName := "wazero"
  4766  	realPath := joinPath(tmpDir, pathName)
  4767  	ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
  4768  	require.True(t, ok)
  4769  
  4770  	// create the file
  4771  	err := os.WriteFile(realPath, []byte{}, 0o600)
  4772  	require.NoError(t, err)
  4773  
  4774  	fd := sys.FdPreopen
  4775  	name := 1
  4776  	nameLen := len(pathName)
  4777  
  4778  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathUnlinkFileName, uint64(fd), uint64(name), uint64(nameLen))
  4779  	require.Equal(t, `
  4780  ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=wazero)
  4781  <== errno=ESUCCESS
  4782  `, "\n"+log.String())
  4783  
  4784  	// ensure the file was removed
  4785  	_, err = os.Stat(realPath)
  4786  	require.Error(t, err)
  4787  }
  4788  
  4789  func Test_pathUnlinkFile_Errors(t *testing.T) {
  4790  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  4791  	fsConfig := wazero.NewFSConfig().WithDirMount(tmpDir, "/")
  4792  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4793  	defer r.Close(testCtx)
  4794  
  4795  	file := "file"
  4796  	err := os.WriteFile(joinPath(tmpDir, file), []byte{}, 0o700)
  4797  	require.NoError(t, err)
  4798  	fileFD := requireOpenFD(t, mod, file)
  4799  
  4800  	dir := "dir"
  4801  	err = os.Mkdir(joinPath(tmpDir, dir), 0o700)
  4802  	require.NoError(t, err)
  4803  
  4804  	tests := []struct {
  4805  		name, pathName string
  4806  		fd             int32
  4807  		path, pathLen  uint32
  4808  		expectedErrno  wasip1.Errno
  4809  		expectedLog    string
  4810  	}{
  4811  		{
  4812  			name:          "unopened FD",
  4813  			fd:            42, // arbitrary invalid fd
  4814  			expectedErrno: wasip1.ErrnoBadf,
  4815  			expectedLog: `
  4816  ==> wasi_snapshot_preview1.path_unlink_file(fd=42,path=)
  4817  <== errno=EBADF
  4818  `,
  4819  		},
  4820  		{
  4821  			name:          "Fd not a directory",
  4822  			fd:            fileFD,
  4823  			expectedErrno: wasip1.ErrnoNotdir,
  4824  			expectedLog: `
  4825  ==> wasi_snapshot_preview1.path_unlink_file(fd=4,path=)
  4826  <== errno=ENOTDIR
  4827  `,
  4828  		},
  4829  		{
  4830  			name:          "out-of-memory reading path",
  4831  			fd:            sys.FdPreopen,
  4832  			path:          mod.Memory().Size(),
  4833  			pathLen:       1,
  4834  			expectedErrno: wasip1.ErrnoFault,
  4835  			expectedLog: `
  4836  ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=OOM(65536,1))
  4837  <== errno=EFAULT
  4838  `,
  4839  		},
  4840  		{
  4841  			name:          "out-of-memory reading pathLen",
  4842  			fd:            sys.FdPreopen,
  4843  			path:          0,
  4844  			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
  4845  			expectedErrno: wasip1.ErrnoFault,
  4846  			expectedLog: `
  4847  ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=OOM(0,65537))
  4848  <== errno=EFAULT
  4849  `,
  4850  		},
  4851  		{
  4852  			name:          "no such file exists",
  4853  			fd:            sys.FdPreopen,
  4854  			pathName:      file,
  4855  			path:          0,
  4856  			pathLen:       uint32(len(file) - 1),
  4857  			expectedErrno: wasip1.ErrnoNoent,
  4858  			expectedLog: `
  4859  ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=fil)
  4860  <== errno=ENOENT
  4861  `,
  4862  		},
  4863  		{
  4864  			name:          "dir not file",
  4865  			fd:            sys.FdPreopen,
  4866  			pathName:      dir,
  4867  			path:          0,
  4868  			pathLen:       uint32(len(dir)),
  4869  			expectedErrno: wasip1.ErrnoIsdir,
  4870  			expectedLog: `
  4871  ==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=dir)
  4872  <== errno=EISDIR
  4873  `,
  4874  		},
  4875  	}
  4876  
  4877  	for _, tt := range tests {
  4878  		tc := tt
  4879  		t.Run(tc.name, func(t *testing.T) {
  4880  			defer log.Reset()
  4881  
  4882  			mod.Memory().Write(tc.path, []byte(tc.pathName))
  4883  
  4884  			requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PathUnlinkFileName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
  4885  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  4886  		})
  4887  	}
  4888  }
  4889  
  4890  func requireOpenFile(t *testing.T, tmpDir string, pathName string, data []byte, readOnly bool) (api.Module, int32, *bytes.Buffer, api.Closer) {
  4891  	oflags := os.O_RDWR
  4892  	if readOnly {
  4893  		oflags = os.O_RDONLY
  4894  	}
  4895  
  4896  	realPath := joinPath(tmpDir, pathName)
  4897  	if data == nil {
  4898  		oflags = os.O_RDONLY
  4899  		require.NoError(t, os.Mkdir(realPath, 0o700))
  4900  	} else {
  4901  		require.NoError(t, os.WriteFile(realPath, data, 0o600))
  4902  	}
  4903  
  4904  	fsConfig := wazero.NewFSConfig()
  4905  
  4906  	if readOnly {
  4907  		fsConfig = fsConfig.WithReadOnlyDirMount(tmpDir, "/")
  4908  	} else {
  4909  		fsConfig = fsConfig.WithDirMount(tmpDir, "/")
  4910  	}
  4911  
  4912  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(fsConfig))
  4913  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  4914  	preopen := fsc.RootFS()
  4915  
  4916  	fd, errno := fsc.OpenFile(preopen, pathName, oflags, 0)
  4917  	require.EqualErrno(t, 0, errno)
  4918  
  4919  	return mod, fd, log, r
  4920  }
  4921  
  4922  // Test_fdReaddir_dotEntriesHaveRealInodes because wasi-testsuite requires it.
  4923  func Test_fdReaddir_dotEntriesHaveRealInodes(t *testing.T) {
  4924  	if runtime.GOOS == "windows" && !platform.IsGo120 {
  4925  		t.Skip("windows before go 1.20 has trouble reading the inode information on directories.")
  4926  	}
  4927  
  4928  	root := t.TempDir()
  4929  	mod, r, _ := requireProxyModule(t, wazero.NewModuleConfig().
  4930  		WithFSConfig(wazero.NewFSConfig().WithDirMount(root, "/")),
  4931  	)
  4932  	defer r.Close(testCtx)
  4933  
  4934  	mem := mod.Memory()
  4935  
  4936  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  4937  	preopen := fsc.RootFS()
  4938  
  4939  	readDirTarget := "dir"
  4940  	mem.Write(0, []byte(readDirTarget))
  4941  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathCreateDirectoryName,
  4942  		uint64(sys.FdPreopen), uint64(0), uint64(len(readDirTarget)))
  4943  
  4944  	// Open the directory, before writing files!
  4945  	fd, errno := fsc.OpenFile(preopen, readDirTarget, os.O_RDONLY, 0)
  4946  	require.EqualErrno(t, 0, errno)
  4947  
  4948  	// get the real inode of the current directory
  4949  	st, errno := preopen.Stat(readDirTarget)
  4950  	require.EqualErrno(t, 0, errno)
  4951  	dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0}         // d_next = 1
  4952  	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
  4953  	dirents = append(dirents, 1, 0, 0, 0)             // d_namlen = 1 character
  4954  	dirents = append(dirents, 3, 0, 0, 0)             // d_type = directory
  4955  	dirents = append(dirents, '.')                    // name
  4956  
  4957  	// get the real inode of the parent directory
  4958  	st, errno = preopen.Stat(".")
  4959  	require.EqualErrno(t, 0, errno)
  4960  	dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2
  4961  	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
  4962  	dirents = append(dirents, 2, 0, 0, 0)             // d_namlen = 2 characters
  4963  	dirents = append(dirents, 3, 0, 0, 0)             // d_type = directory
  4964  	dirents = append(dirents, '.', '.')               // name
  4965  
  4966  	// Try to list them!
  4967  	resultBufused := uint32(0) // where to write the amount used out of bufLen
  4968  	buf := uint32(8)           // where to start the dirents
  4969  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
  4970  		uint64(fd), uint64(buf), uint64(0x2000), 0, uint64(resultBufused))
  4971  
  4972  	used, _ := mem.ReadUint32Le(resultBufused)
  4973  
  4974  	results, _ := mem.Read(buf, used)
  4975  	require.Equal(t, dirents, results)
  4976  }
  4977  
  4978  // Test_fdReaddir_opened_file_written ensures that writing files to the already-opened directory
  4979  // is visible. This is significant on Windows.
  4980  // https://github.com/ziglang/zig/blob/2ccff5115454bab4898bae3de88f5619310bc5c1/lib/std/fs/test.zig#L156-L184
  4981  func Test_fdReaddir_opened_file_written(t *testing.T) {
  4982  	if runtime.GOOS == "windows" && !platform.IsGo120 {
  4983  		t.Skip("windows before go 1.20 has trouble reading the inode information on directories.")
  4984  	}
  4985  
  4986  	tmpDir := t.TempDir()
  4987  	mod, r, _ := requireProxyModule(t, wazero.NewModuleConfig().
  4988  		WithFSConfig(wazero.NewFSConfig().WithDirMount(tmpDir, "/")),
  4989  	)
  4990  	defer r.Close(testCtx)
  4991  
  4992  	mem := mod.Memory()
  4993  
  4994  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  4995  	preopen := fsc.RootFS()
  4996  
  4997  	dirName := "dir"
  4998  	dirPath := joinPath(tmpDir, dirName)
  4999  	mem.Write(0, []byte(dirName))
  5000  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.PathCreateDirectoryName,
  5001  		uint64(sys.FdPreopen), uint64(0), uint64(len(dirName)))
  5002  
  5003  	// Open the directory, before writing files!
  5004  	dirFD, errno := fsc.OpenFile(preopen, dirName, os.O_RDONLY, 0)
  5005  	require.EqualErrno(t, 0, errno)
  5006  
  5007  	// Then write a file to the directory.
  5008  	f := openFile(t, joinPath(dirPath, "file"), os.O_CREATE, 0)
  5009  	defer f.Close()
  5010  
  5011  	// get the real inode of the current directory
  5012  	st, errno := preopen.Stat(dirName)
  5013  	require.EqualErrno(t, 0, errno)
  5014  	dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0}         // d_next = 1
  5015  	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
  5016  	dirents = append(dirents, 1, 0, 0, 0)             // d_namlen = 1 character
  5017  	dirents = append(dirents, 3, 0, 0, 0)             // d_type = directory
  5018  	dirents = append(dirents, '.')                    // name
  5019  
  5020  	// get the real inode of the parent directory
  5021  	st, errno = preopen.Stat(".")
  5022  	require.EqualErrno(t, 0, errno)
  5023  	dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2
  5024  	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
  5025  	dirents = append(dirents, 2, 0, 0, 0)             // d_namlen = 2 characters
  5026  	dirents = append(dirents, 3, 0, 0, 0)             // d_type = directory
  5027  	dirents = append(dirents, '.', '.')               // name
  5028  
  5029  	// get the real inode of the file
  5030  	st, errno = f.Stat()
  5031  	require.EqualErrno(t, 0, errno)
  5032  	dirents = append(dirents, 3, 0, 0, 0, 0, 0, 0, 0) // d_next = 3
  5033  	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
  5034  	dirents = append(dirents, 4, 0, 0, 0)             // d_namlen = 4 characters
  5035  	dirents = append(dirents, 4, 0, 0, 0)             // d_type = regular_file
  5036  	dirents = append(dirents, 'f', 'i', 'l', 'e')     // name
  5037  
  5038  	// Try to list them!
  5039  	resultBufused := uint32(0) // where to write the amount used out of bufLen
  5040  	buf := uint32(8)           // where to start the dirents
  5041  	requireErrnoResult(t, wasip1.ErrnoSuccess, mod, wasip1.FdReaddirName,
  5042  		uint64(dirFD), uint64(buf), uint64(0x2000), 0, uint64(resultBufused))
  5043  
  5044  	used, _ := mem.ReadUint32Le(resultBufused)
  5045  
  5046  	results, _ := mem.Read(buf, used)
  5047  	require.Equal(t, dirents, results)
  5048  }
  5049  
  5050  // joinPath avoids us having to rename fields just to avoid conflict with the
  5051  // path package.
  5052  func joinPath(dirName, baseName string) string {
  5053  	return path.Join(dirName, baseName)
  5054  }
  5055  
  5056  func openFile(t *testing.T, path string, flag int, perm fs.FileMode) fsapi.File {
  5057  	f, errno := sysfs.OpenOSFile(path, flag, perm)
  5058  	require.EqualErrno(t, 0, errno)
  5059  	return f
  5060  }