github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/imports/wasi_snapshot_preview1/fs_test.go (about)

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