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