github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/imports/wasi_snapshot_preview1/fs_test.go (about)

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