wa-lang.org/wazero@v1.0.2/imports/wasi_snapshot_preview1/fs_test.go (about)

     1  package wasi_snapshot_preview1
     2  
     3  import (
     4  	"bytes"
     5  	_ "embed"
     6  	"io"
     7  	"io/fs"
     8  	"math"
     9  	"os"
    10  	"path"
    11  	"testing"
    12  	"testing/fstest"
    13  	"time"
    14  
    15  	"wa-lang.org/wazero"
    16  	"wa-lang.org/wazero/api"
    17  	internalsys "wa-lang.org/wazero/internal/sys"
    18  	"wa-lang.org/wazero/internal/testing/require"
    19  	"wa-lang.org/wazero/internal/wasm"
    20  )
    21  
    22  // Test_fdAdvise only tests it is stubbed for GrainLang per #271
    23  func Test_fdAdvise(t *testing.T) {
    24  	log := requireErrnoNosys(t, functionFdAdvise, 0, 0, 0, 0)
    25  	require.Equal(t, `
    26  --> proxy.fd_advise(fd=0,offset=0,len=0,result.advice=0)
    27  	--> wasi_snapshot_preview1.fd_advise(fd=0,offset=0,len=0,result.advice=0)
    28  	<-- ENOSYS
    29  <-- (52)
    30  `, log)
    31  }
    32  
    33  // Test_fdAllocate only tests it is stubbed for GrainLang per #271
    34  func Test_fdAllocate(t *testing.T) {
    35  	log := requireErrnoNosys(t, functionFdAllocate, 0, 0, 0)
    36  	require.Equal(t, `
    37  --> proxy.fd_allocate(fd=0,offset=0,len=0)
    38  	--> wasi_snapshot_preview1.fd_allocate(fd=0,offset=0,len=0)
    39  	<-- ENOSYS
    40  <-- (52)
    41  `, log)
    42  }
    43  
    44  func Test_fdClose(t *testing.T) {
    45  	// fd_close needs to close an open file descriptor. Open two files so that we can tell which is closed.
    46  	path1, path2 := "a", "b"
    47  	testFS := fstest.MapFS{path1: {Data: make([]byte, 0)}, path2: {Data: make([]byte, 0)}}
    48  
    49  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
    50  	defer r.Close(testCtx)
    51  
    52  	// open both paths without using WASI
    53  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
    54  
    55  	fdToClose, err := fsc.OpenFile(testCtx, path1)
    56  	require.NoError(t, err)
    57  
    58  	fdToKeep, err := fsc.OpenFile(testCtx, path2)
    59  	require.NoError(t, err)
    60  
    61  	// Close
    62  	requireErrno(t, ErrnoSuccess, mod, functionFdClose, uint64(fdToClose))
    63  	require.Equal(t, `
    64  --> proxy.fd_close(fd=4)
    65  	==> wasi_snapshot_preview1.fd_close(fd=4)
    66  	<== ESUCCESS
    67  <-- (0)
    68  `, "\n"+log.String())
    69  
    70  	// Verify fdToClose is closed and removed from the opened FDs.
    71  	_, ok := fsc.OpenedFile(testCtx, fdToClose)
    72  	require.False(t, ok)
    73  
    74  	// Verify fdToKeep is not closed
    75  	_, ok = fsc.OpenedFile(testCtx, fdToKeep)
    76  	require.True(t, ok)
    77  
    78  	log.Reset()
    79  	t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) {
    80  		requireErrno(t, ErrnoBadf, mod, functionFdClose, uint64(42)) // 42 is an arbitrary invalid FD
    81  		require.Equal(t, `
    82  --> proxy.fd_close(fd=42)
    83  	==> wasi_snapshot_preview1.fd_close(fd=42)
    84  	<== EBADF
    85  <-- (8)
    86  `, "\n"+log.String())
    87  	})
    88  }
    89  
    90  // Test_fdDatasync only tests it is stubbed for GrainLang per #271
    91  func Test_fdDatasync(t *testing.T) {
    92  	log := requireErrnoNosys(t, functionFdDatasync, 0)
    93  	require.Equal(t, `
    94  --> proxy.fd_datasync(fd=0)
    95  	--> wasi_snapshot_preview1.fd_datasync(fd=0)
    96  	<-- ENOSYS
    97  <-- (52)
    98  `, log)
    99  }
   100  
   101  func Test_fdFdstatGet(t *testing.T) {
   102  	file, dir := "a", "b"
   103  	testFS := fstest.MapFS{file: {Data: make([]byte, 0)}, dir: {Mode: fs.ModeDir}}
   104  
   105  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
   106  	defer r.Close(testCtx)
   107  	memorySize := mod.Memory().Size(testCtx)
   108  
   109  	// open both paths without using WASI
   110  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
   111  
   112  	fileFd, err := fsc.OpenFile(testCtx, file)
   113  	require.NoError(t, err)
   114  
   115  	dirFd, err := fsc.OpenFile(testCtx, dir)
   116  	require.NoError(t, err)
   117  
   118  	tests := []struct {
   119  		name           string
   120  		fd, resultStat uint32
   121  		// TODO: expectedMem
   122  		expectedErrno Errno
   123  		expectedLog   string
   124  	}{
   125  		{
   126  			name: "file",
   127  			fd:   fileFd,
   128  			// TODO: expectedMem for a file
   129  			expectedLog: `
   130  --> proxy.fd_fdstat_get(fd=4,result.stat=0)
   131  	==> wasi_snapshot_preview1.fd_fdstat_get(fd=4,result.stat=0)
   132  	<== ESUCCESS
   133  <-- (0)
   134  `,
   135  		},
   136  		{
   137  			name: "dir",
   138  			fd:   dirFd,
   139  			// TODO: expectedMem for a dir
   140  			expectedLog: `
   141  --> proxy.fd_fdstat_get(fd=5,result.stat=0)
   142  	==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=0)
   143  	<== ESUCCESS
   144  <-- (0)
   145  `,
   146  		},
   147  		{
   148  			name:          "bad FD",
   149  			fd:            math.MaxUint32,
   150  			expectedErrno: ErrnoBadf,
   151  			expectedLog: `
   152  --> proxy.fd_fdstat_get(fd=4294967295,result.stat=0)
   153  	==> wasi_snapshot_preview1.fd_fdstat_get(fd=4294967295,result.stat=0)
   154  	<== EBADF
   155  <-- (8)
   156  `,
   157  		},
   158  		{
   159  			name:       "resultStat exceeds the maximum valid address by 1",
   160  			fd:         dirFd,
   161  			resultStat: memorySize - 24 + 1,
   162  			// TODO: ErrnoFault
   163  			expectedLog: `
   164  --> proxy.fd_fdstat_get(fd=5,result.stat=65513)
   165  	==> wasi_snapshot_preview1.fd_fdstat_get(fd=5,result.stat=65513)
   166  	<== ESUCCESS
   167  <-- (0)
   168  `,
   169  		},
   170  	}
   171  
   172  	for _, tt := range tests {
   173  		tc := tt
   174  
   175  		t.Run(tc.name, func(t *testing.T) {
   176  			defer log.Reset()
   177  
   178  			requireErrno(t, tc.expectedErrno, mod, functionFdFdstatGet, uint64(tc.fd), uint64(tc.resultStat))
   179  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   180  		})
   181  	}
   182  }
   183  
   184  // Test_fdFdstatSetFlags only tests it is stubbed for GrainLang per #271
   185  func Test_fdFdstatSetFlags(t *testing.T) {
   186  	log := requireErrnoNosys(t, functionFdFdstatSetFlags, 0, 0)
   187  	require.Equal(t, `
   188  --> proxy.fd_fdstat_set_flags(fd=0,flags=0)
   189  	--> wasi_snapshot_preview1.fd_fdstat_set_flags(fd=0,flags=0)
   190  	<-- ENOSYS
   191  <-- (52)
   192  `, log)
   193  }
   194  
   195  // Test_fdFdstatSetRights only tests it is stubbed for GrainLang per #271
   196  func Test_fdFdstatSetRights(t *testing.T) {
   197  	log := requireErrnoNosys(t, functionFdFdstatSetRights, 0, 0, 0)
   198  	require.Equal(t, `
   199  --> proxy.fd_fdstat_set_rights(fd=0,fs_rights_base=0,fs_rights_inheriting=0)
   200  	--> wasi_snapshot_preview1.fd_fdstat_set_rights(fd=0,fs_rights_base=0,fs_rights_inheriting=0)
   201  	<-- ENOSYS
   202  <-- (52)
   203  `, log)
   204  }
   205  
   206  func Test_fdFilestatGet(t *testing.T) {
   207  	file, dir := "a", "b"
   208  	testFS := fstest.MapFS{file: {Data: make([]byte, 10), ModTime: time.Unix(1667482413, 0)}, dir: {Mode: fs.ModeDir, ModTime: time.Unix(1667482413, 0)}}
   209  
   210  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
   211  	defer r.Close(testCtx)
   212  	memorySize := mod.Memory().Size(testCtx)
   213  
   214  	// open both paths without using WASI
   215  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
   216  
   217  	fileFd, err := fsc.OpenFile(testCtx, file)
   218  	require.NoError(t, err)
   219  
   220  	dirFd, err := fsc.OpenFile(testCtx, dir)
   221  	require.NoError(t, err)
   222  
   223  	tests := []struct {
   224  		name               string
   225  		fd, resultFilestat uint32
   226  		expectedMemory     []byte
   227  		expectedErrno      Errno
   228  		expectedLog        string
   229  	}{
   230  		{
   231  			name: "file",
   232  			fd:   fileFd,
   233  			expectedMemory: []byte{
   234  				'?', '?', '?', '?', '?', '?', '?', '?', // dev
   235  				'?', '?', '?', '?', '?', '?', '?', '?', // ino
   236  				4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
   237  				'?', '?', '?', '?', '?', '?', '?', '?', // nlink
   238  				10, 0, 0, 0, 0, 0, 0, 0, // size
   239  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
   240  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
   241  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
   242  			},
   243  			expectedLog: `
   244  --> proxy.fd_filestat_get(fd=4,result.buf=0)
   245  	==> wasi_snapshot_preview1.fd_filestat_get(fd=4,result.buf=0)
   246  	<== ESUCCESS
   247  <-- (0)
   248  `,
   249  		},
   250  		{
   251  			name: "dir",
   252  			fd:   dirFd,
   253  			expectedMemory: []byte{
   254  				'?', '?', '?', '?', '?', '?', '?', '?', // dev
   255  				'?', '?', '?', '?', '?', '?', '?', '?', // ino
   256  				3, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
   257  				'?', '?', '?', '?', '?', '?', '?', '?', // nlink
   258  				0, 0, 0, 0, 0, 0, 0, 0, // size
   259  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
   260  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
   261  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
   262  			},
   263  			expectedLog: `
   264  --> proxy.fd_filestat_get(fd=5,result.buf=0)
   265  	==> wasi_snapshot_preview1.fd_filestat_get(fd=5,result.buf=0)
   266  	<== ESUCCESS
   267  <-- (0)
   268  `,
   269  		},
   270  		{
   271  			name:          "bad FD",
   272  			fd:            math.MaxUint32,
   273  			expectedErrno: ErrnoBadf,
   274  			expectedLog: `
   275  --> proxy.fd_filestat_get(fd=4294967295,result.buf=0)
   276  	==> wasi_snapshot_preview1.fd_filestat_get(fd=4294967295,result.buf=0)
   277  	<== EBADF
   278  <-- (8)
   279  `,
   280  		},
   281  		{
   282  			name:           "resultFilestat exceeds the maximum valid address by 1",
   283  			fd:             dirFd,
   284  			resultFilestat: memorySize - 64 + 1,
   285  			expectedErrno:  ErrnoFault,
   286  			expectedLog: `
   287  --> proxy.fd_filestat_get(fd=5,result.buf=65473)
   288  	==> wasi_snapshot_preview1.fd_filestat_get(fd=5,result.buf=65473)
   289  	<== EFAULT
   290  <-- (21)
   291  `,
   292  		},
   293  	}
   294  
   295  	for _, tt := range tests {
   296  		tc := tt
   297  
   298  		t.Run(tc.name, func(t *testing.T) {
   299  			defer log.Reset()
   300  
   301  			maskMemory(t, testCtx, mod, len(tc.expectedMemory))
   302  
   303  			requireErrno(t, tc.expectedErrno, mod, functionFdFilestatGet, uint64(tc.fd), uint64(tc.resultFilestat))
   304  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   305  
   306  			actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
   307  			require.True(t, ok)
   308  			require.Equal(t, tc.expectedMemory, actual)
   309  		})
   310  	}
   311  }
   312  
   313  // Test_fdFilestatSetSize only tests it is stubbed for GrainLang per #271
   314  func Test_fdFilestatSetSize(t *testing.T) {
   315  	log := requireErrnoNosys(t, functionFdFilestatSetSize, 0, 0)
   316  	require.Equal(t, `
   317  --> proxy.fd_filestat_set_size(fd=0,size=0)
   318  	--> wasi_snapshot_preview1.fd_filestat_set_size(fd=0,size=0)
   319  	<-- ENOSYS
   320  <-- (52)
   321  `, log)
   322  }
   323  
   324  // Test_fdFilestatSetTimes only tests it is stubbed for GrainLang per #271
   325  func Test_fdFilestatSetTimes(t *testing.T) {
   326  	log := requireErrnoNosys(t, functionFdFilestatSetTimes, 0, 0, 0, 0)
   327  	require.Equal(t, `
   328  --> proxy.fd_filestat_set_times(fd=0,atim=0,mtim=0,fst_flags=0)
   329  	--> wasi_snapshot_preview1.fd_filestat_set_times(fd=0,atim=0,mtim=0,fst_flags=0)
   330  	<-- ENOSYS
   331  <-- (52)
   332  `, log)
   333  }
   334  
   335  func Test_fdPread(t *testing.T) {
   336  	mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero"))
   337  	defer r.Close(testCtx)
   338  
   339  	iovs := uint32(1) // arbitrary offset
   340  	initialMemory := []byte{
   341  		'?',         // `iovs` is after this
   342  		18, 0, 0, 0, // = iovs[0].offset
   343  		4, 0, 0, 0, // = iovs[0].length
   344  		23, 0, 0, 0, // = iovs[1].offset
   345  		2, 0, 0, 0, // = iovs[1].length
   346  		'?',
   347  	}
   348  
   349  	iovsCount := uint32(2)   // The count of iovs
   350  	resultSize := uint32(26) // arbitrary offset
   351  
   352  	tests := []struct {
   353  		name           string
   354  		offset         int64
   355  		expectedMemory []byte
   356  		expectedLog    string
   357  	}{
   358  		{
   359  			name:   "offset zero",
   360  			offset: 0,
   361  			expectedMemory: append(
   362  				initialMemory,
   363  				'w', 'a', 'z', 'e', // iovs[0].length bytes
   364  				'?',      // iovs[1].offset is after this
   365  				'r', 'o', // iovs[1].length bytes
   366  				'?',        // resultSize is after this
   367  				6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
   368  				'?',
   369  			),
   370  			expectedLog: `
   371  --> proxy.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.size=26)
   372  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=0,result.size=26)
   373  	<== ESUCCESS
   374  <-- (0)
   375  `,
   376  		},
   377  		{
   378  			name:   "offset 2",
   379  			offset: 2,
   380  			expectedMemory: append(
   381  				initialMemory,
   382  				'z', 'e', 'r', 'o', // iovs[0].length bytes
   383  				'?', '?', '?', '?', // resultSize is after this
   384  				4, 0, 0, 0, // sum(iovs[...].length) == length of "zero"
   385  				'?',
   386  			),
   387  			expectedLog: `
   388  --> proxy.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.size=26)
   389  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=1,iovs_len=2,offset=2,result.size=26)
   390  	<== ESUCCESS
   391  <-- (0)
   392  `,
   393  		},
   394  	}
   395  
   396  	for _, tt := range tests {
   397  		tc := tt
   398  		t.Run(tc.name, func(t *testing.T) {
   399  			defer log.Reset()
   400  
   401  			maskMemory(t, testCtx, mod, len(tc.expectedMemory))
   402  
   403  			ok := mod.Memory().Write(testCtx, 0, initialMemory)
   404  			require.True(t, ok)
   405  
   406  			requireErrno(t, ErrnoSuccess, mod, functionFdPread, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(tc.offset), uint64(resultSize))
   407  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   408  
   409  			actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
   410  			require.True(t, ok)
   411  			require.Equal(t, tc.expectedMemory, actual)
   412  		})
   413  	}
   414  }
   415  
   416  func Test_fdPread_Errors(t *testing.T) {
   417  	contents := []byte("wazero")
   418  	mod, fd, log, r := requireOpenFile(t, "/test_path", contents)
   419  	defer r.Close(testCtx)
   420  
   421  	tests := []struct {
   422  		name                            string
   423  		fd, iovs, iovsCount, resultSize uint32
   424  		offset                          int64
   425  		memory                          []byte
   426  		expectedErrno                   Errno
   427  		expectedLog                     string
   428  	}{
   429  		{
   430  			name:          "invalid fd",
   431  			fd:            42, // arbitrary invalid fd
   432  			expectedErrno: ErrnoBadf,
   433  			expectedLog: `
   434  --> proxy.fd_pread(fd=42,iovs=65536,iovs_len=65536,offset=0,result.size=65536)
   435  	==> wasi_snapshot_preview1.fd_pread(fd=42,iovs=65536,iovs_len=65536,offset=0,result.size=65536)
   436  	<== EBADF
   437  <-- (8)
   438  `,
   439  		},
   440  		{
   441  			name:          "seek past file",
   442  			fd:            fd,
   443  			offset:        int64(len(contents) + 1),
   444  			expectedErrno: ErrnoFault,
   445  			expectedLog: `
   446  --> proxy.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.size=65536)
   447  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65536,offset=7,result.size=65536)
   448  	<== EFAULT
   449  <-- (21)
   450  `,
   451  		},
   452  		{
   453  			name:          "out-of-memory reading iovs[0].offset",
   454  			fd:            fd,
   455  			iovs:          1,
   456  			memory:        []byte{'?'},
   457  			expectedErrno: ErrnoFault,
   458  			expectedLog: `
   459  --> proxy.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.size=65535)
   460  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65536,iovs_len=65535,offset=0,result.size=65535)
   461  	<== EFAULT
   462  <-- (21)
   463  `,
   464  		},
   465  		{
   466  			name: "out-of-memory reading iovs[0].length",
   467  			fd:   fd,
   468  			iovs: 1, iovsCount: 1,
   469  			memory: []byte{
   470  				'?',        // `iovs` is after this
   471  				9, 0, 0, 0, // = iovs[0].offset
   472  			},
   473  			expectedErrno: ErrnoFault,
   474  			expectedLog: `
   475  --> proxy.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.size=65531)
   476  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65532,iovs_len=65532,offset=0,result.size=65531)
   477  	<== EFAULT
   478  <-- (21)
   479  `,
   480  		},
   481  		{
   482  			name: "iovs[0].offset is outside memory",
   483  			fd:   fd,
   484  			iovs: 1, iovsCount: 1,
   485  			memory: []byte{
   486  				'?',          // `iovs` is after this
   487  				0, 0, 0x1, 0, // = iovs[0].offset on the second page
   488  				1, 0, 0, 0, // = iovs[0].length
   489  			},
   490  			expectedErrno: ErrnoFault,
   491  			expectedLog: `
   492  --> proxy.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.size=65527)
   493  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65528,iovs_len=65528,offset=0,result.size=65527)
   494  	<== EFAULT
   495  <-- (21)
   496  `,
   497  		},
   498  		{
   499  			name: "length to read exceeds memory by 1",
   500  			fd:   fd,
   501  			iovs: 1, iovsCount: 1,
   502  			memory: []byte{
   503  				'?',        // `iovs` is after this
   504  				9, 0, 0, 0, // = iovs[0].offset
   505  				0, 0, 0x1, 0, // = iovs[0].length on the second page
   506  				'?',
   507  			},
   508  			expectedErrno: ErrnoFault,
   509  			expectedLog: `
   510  --> proxy.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65526)
   511  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65526)
   512  	<== EFAULT
   513  <-- (21)
   514  `,
   515  		},
   516  		{
   517  			name: "resultSize offset is outside memory",
   518  			fd:   fd,
   519  			iovs: 1, iovsCount: 1,
   520  			resultSize: 10, // 1 past memory
   521  			memory: []byte{
   522  				'?',        // `iovs` is after this
   523  				9, 0, 0, 0, // = iovs[0].offset
   524  				1, 0, 0, 0, // = iovs[0].length
   525  				'?',
   526  			},
   527  			expectedErrno: ErrnoFault,
   528  			expectedLog: `
   529  --> proxy.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65536)
   530  	==> wasi_snapshot_preview1.fd_pread(fd=4,iovs=65527,iovs_len=65527,offset=0,result.size=65536)
   531  	<== EFAULT
   532  <-- (21)
   533  `,
   534  		},
   535  	}
   536  
   537  	for _, tt := range tests {
   538  		tc := tt
   539  		t.Run(tc.name, func(t *testing.T) {
   540  			defer log.Reset()
   541  
   542  			offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
   543  
   544  			memoryWriteOK := mod.Memory().Write(testCtx, offset, tc.memory)
   545  			require.True(t, memoryWriteOK)
   546  
   547  			requireErrno(t, tc.expectedErrno, mod, functionFdPread, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.offset), uint64(tc.resultSize+offset))
   548  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   549  		})
   550  	}
   551  }
   552  
   553  func Test_fdPrestatGet(t *testing.T) {
   554  	pathName := "/tmp"
   555  	mod, fd, log, r := requireOpenFile(t, pathName, nil)
   556  	defer r.Close(testCtx)
   557  
   558  	resultPrestat := uint32(1) // arbitrary offset
   559  	expectedMemory := []byte{
   560  		'?',     // resultPrestat after this
   561  		0,       // 8-bit tag indicating `prestat_dir`, the only available tag
   562  		0, 0, 0, // 3-byte padding
   563  		// the result path length field after this
   564  		byte(len(pathName)), 0, 0, 0, // = in little endian encoding
   565  		'?',
   566  	}
   567  
   568  	maskMemory(t, testCtx, mod, len(expectedMemory))
   569  
   570  	requireErrno(t, ErrnoSuccess, mod, functionFdPrestatGet, uint64(fd), uint64(resultPrestat))
   571  	require.Equal(t, `
   572  --> proxy.fd_prestat_get(fd=4,result.prestat=1)
   573  	==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=1)
   574  	<== ESUCCESS
   575  <-- (0)
   576  `, "\n"+log.String())
   577  
   578  	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
   579  	require.True(t, ok)
   580  	require.Equal(t, expectedMemory, actual)
   581  }
   582  
   583  func Test_fdPrestatGet_Errors(t *testing.T) {
   584  	pathName := "/tmp"
   585  	mod, fd, log, r := requireOpenFile(t, pathName, nil)
   586  	defer r.Close(testCtx)
   587  
   588  	memorySize := mod.Memory().Size(testCtx)
   589  	tests := []struct {
   590  		name          string
   591  		fd            uint32
   592  		resultPrestat uint32
   593  		expectedErrno Errno
   594  		expectedLog   string
   595  	}{
   596  		{
   597  			name:          "invalid FD",
   598  			fd:            42, // arbitrary invalid FD
   599  			resultPrestat: 0,  // valid offset
   600  			expectedErrno: ErrnoBadf,
   601  			expectedLog: `
   602  --> proxy.fd_prestat_get(fd=42,result.prestat=0)
   603  	==> wasi_snapshot_preview1.fd_prestat_get(fd=42,result.prestat=0)
   604  	<== EBADF
   605  <-- (8)
   606  `,
   607  		},
   608  		{
   609  			name:          "out-of-memory resultPrestat",
   610  			fd:            fd,
   611  			resultPrestat: memorySize,
   612  			expectedErrno: ErrnoFault,
   613  			expectedLog: `
   614  --> proxy.fd_prestat_get(fd=4,result.prestat=65536)
   615  	==> wasi_snapshot_preview1.fd_prestat_get(fd=4,result.prestat=65536)
   616  	<== EFAULT
   617  <-- (21)
   618  `,
   619  		},
   620  		// TODO: non pre-opened file == api.ErrnoBadf
   621  	}
   622  
   623  	for _, tt := range tests {
   624  		tc := tt
   625  
   626  		t.Run(tc.name, func(t *testing.T) {
   627  			defer log.Reset()
   628  
   629  			requireErrno(t, tc.expectedErrno, mod, functionFdPrestatGet, uint64(tc.fd), uint64(tc.resultPrestat))
   630  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   631  		})
   632  	}
   633  }
   634  
   635  func Test_fdPrestatDirName(t *testing.T) {
   636  	pathName := "/tmp"
   637  	mod, fd, log, r := requireOpenFile(t, pathName, nil)
   638  	defer r.Close(testCtx)
   639  
   640  	path := uint32(1)    // arbitrary offset
   641  	pathLen := uint32(3) // shorter than len("/tmp") to test the path is written for the length of pathLen
   642  	expectedMemory := []byte{
   643  		'?',
   644  		'/', 't', 'm',
   645  		'?', '?', '?',
   646  	}
   647  
   648  	maskMemory(t, testCtx, mod, len(expectedMemory))
   649  
   650  	requireErrno(t, ErrnoSuccess, mod, functionFdPrestatDirName, uint64(fd), uint64(path), uint64(pathLen))
   651  	require.Equal(t, `
   652  --> proxy.fd_prestat_dir_name(fd=4,path=1,path_len=3)
   653  	==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=1,path_len=3)
   654  	<== ESUCCESS
   655  <-- (0)
   656  `, "\n"+log.String())
   657  
   658  	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
   659  	require.True(t, ok)
   660  	require.Equal(t, expectedMemory, actual)
   661  }
   662  
   663  func Test_fdPrestatDirName_Errors(t *testing.T) {
   664  	pathName := "/tmp"
   665  	mod, fd, log, r := requireOpenFile(t, pathName, nil)
   666  	defer r.Close(testCtx)
   667  
   668  	memorySize := mod.Memory().Size(testCtx)
   669  	validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_dir_name. We chose 0 here.
   670  	pathLen := uint32(len("/tmp"))
   671  
   672  	tests := []struct {
   673  		name          string
   674  		fd            uint32
   675  		path          uint32
   676  		pathLen       uint32
   677  		expectedErrno Errno
   678  		expectedLog   string
   679  	}{
   680  		{
   681  			name:          "out-of-memory path",
   682  			fd:            fd,
   683  			path:          memorySize,
   684  			pathLen:       pathLen,
   685  			expectedErrno: ErrnoFault,
   686  			expectedLog: `
   687  --> proxy.fd_prestat_dir_name(fd=4,path=65536,path_len=4)
   688  	==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=65536,path_len=4)
   689  	<== EFAULT
   690  <-- (21)
   691  `,
   692  		},
   693  		{
   694  			name:          "path exceeds the maximum valid address by 1",
   695  			fd:            fd,
   696  			path:          memorySize - pathLen + 1,
   697  			pathLen:       pathLen,
   698  			expectedErrno: ErrnoFault,
   699  			expectedLog: `
   700  --> proxy.fd_prestat_dir_name(fd=4,path=65533,path_len=4)
   701  	==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=65533,path_len=4)
   702  	<== EFAULT
   703  <-- (21)
   704  `,
   705  		},
   706  		{
   707  			name:          "pathLen exceeds the length of the dir name",
   708  			fd:            fd,
   709  			path:          validAddress,
   710  			pathLen:       pathLen + 1,
   711  			expectedErrno: ErrnoNametoolong,
   712  			expectedLog: `
   713  --> proxy.fd_prestat_dir_name(fd=4,path=0,path_len=5)
   714  	==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=4,path=0,path_len=5)
   715  	<== ENAMETOOLONG
   716  <-- (37)
   717  `,
   718  		},
   719  		{
   720  			name:          "invalid fd",
   721  			fd:            42, // arbitrary invalid fd
   722  			path:          validAddress,
   723  			pathLen:       pathLen,
   724  			expectedErrno: ErrnoBadf,
   725  			expectedLog: `
   726  --> proxy.fd_prestat_dir_name(fd=42,path=0,path_len=4)
   727  	==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=42,path=0,path_len=4)
   728  	<== EBADF
   729  <-- (8)
   730  `,
   731  		},
   732  		// TODO: non pre-opened file == ErrnoBadf
   733  	}
   734  
   735  	for _, tt := range tests {
   736  		tc := tt
   737  
   738  		t.Run(tc.name, func(t *testing.T) {
   739  			defer log.Reset()
   740  
   741  			requireErrno(t, tc.expectedErrno, mod, functionFdPrestatDirName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
   742  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   743  		})
   744  	}
   745  }
   746  
   747  // Test_fdPwrite only tests it is stubbed for GrainLang per #271
   748  func Test_fdPwrite(t *testing.T) {
   749  	log := requireErrnoNosys(t, functionFdPwrite, 0, 0, 0, 0, 0)
   750  	require.Equal(t, `
   751  --> proxy.fd_pwrite(fd=0,iovs=0,iovs_len=0,offset=0,result.nwritten=0)
   752  	--> wasi_snapshot_preview1.fd_pwrite(fd=0,iovs=0,iovs_len=0,offset=0,result.nwritten=0)
   753  	<-- ENOSYS
   754  <-- (52)
   755  `, log)
   756  }
   757  
   758  func Test_fdRead(t *testing.T) {
   759  	mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero"))
   760  	defer r.Close(testCtx)
   761  
   762  	iovs := uint32(1) // arbitrary offset
   763  	initialMemory := []byte{
   764  		'?',         // `iovs` is after this
   765  		18, 0, 0, 0, // = iovs[0].offset
   766  		4, 0, 0, 0, // = iovs[0].length
   767  		23, 0, 0, 0, // = iovs[1].offset
   768  		2, 0, 0, 0, // = iovs[1].length
   769  		'?',
   770  	}
   771  	iovsCount := uint32(2)   // The count of iovs
   772  	resultSize := uint32(26) // arbitrary offset
   773  	expectedMemory := append(
   774  		initialMemory,
   775  		'w', 'a', 'z', 'e', // iovs[0].length bytes
   776  		'?',      // iovs[1].offset is after this
   777  		'r', 'o', // iovs[1].length bytes
   778  		'?',        // resultSize is after this
   779  		6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
   780  		'?',
   781  	)
   782  
   783  	maskMemory(t, testCtx, mod, len(expectedMemory))
   784  
   785  	ok := mod.Memory().Write(testCtx, 0, initialMemory)
   786  	require.True(t, ok)
   787  
   788  	requireErrno(t, ErrnoSuccess, mod, functionFdRead, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
   789  	require.Equal(t, `
   790  --> proxy.fd_read(fd=4,iovs=1,iovs_len=2,result.size=26)
   791  	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=1,iovs_len=2,result.size=26)
   792  	<== ESUCCESS
   793  <-- (0)
   794  `, "\n"+log.String())
   795  
   796  	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
   797  	require.True(t, ok)
   798  	require.Equal(t, expectedMemory, actual)
   799  }
   800  
   801  func Test_fdRead_Errors(t *testing.T) {
   802  	mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero"))
   803  	defer r.Close(testCtx)
   804  
   805  	tests := []struct {
   806  		name                            string
   807  		fd, iovs, iovsCount, resultSize uint32
   808  		memory                          []byte
   809  		expectedErrno                   Errno
   810  		expectedLog                     string
   811  	}{
   812  		{
   813  			name:          "invalid fd",
   814  			fd:            42, // arbitrary invalid fd
   815  			expectedErrno: ErrnoBadf,
   816  			expectedLog: `
   817  --> proxy.fd_read(fd=42,iovs=65536,iovs_len=65536,result.size=65536)
   818  	==> wasi_snapshot_preview1.fd_read(fd=42,iovs=65536,iovs_len=65536,result.size=65536)
   819  	<== EBADF
   820  <-- (8)
   821  `,
   822  		},
   823  		{
   824  			name:          "out-of-memory reading iovs[0].offset",
   825  			fd:            fd,
   826  			iovs:          1,
   827  			memory:        []byte{'?'},
   828  			expectedErrno: ErrnoFault,
   829  			expectedLog: `
   830  --> proxy.fd_read(fd=4,iovs=65536,iovs_len=65535,result.size=65535)
   831  	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65536,iovs_len=65535,result.size=65535)
   832  	<== EFAULT
   833  <-- (21)
   834  `,
   835  		},
   836  		{
   837  			name: "out-of-memory reading iovs[0].length",
   838  			fd:   fd,
   839  			iovs: 1, iovsCount: 1,
   840  			memory: []byte{
   841  				'?',        // `iovs` is after this
   842  				9, 0, 0, 0, // = iovs[0].offset
   843  			},
   844  			expectedErrno: ErrnoFault,
   845  			expectedLog: `
   846  --> proxy.fd_read(fd=4,iovs=65532,iovs_len=65532,result.size=65531)
   847  	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65532,iovs_len=65532,result.size=65531)
   848  	<== EFAULT
   849  <-- (21)
   850  `,
   851  		},
   852  		{
   853  			name: "iovs[0].offset is outside memory",
   854  			fd:   fd,
   855  			iovs: 1, iovsCount: 1,
   856  			memory: []byte{
   857  				'?',          // `iovs` is after this
   858  				0, 0, 0x1, 0, // = iovs[0].offset on the second page
   859  				1, 0, 0, 0, // = iovs[0].length
   860  			},
   861  			expectedErrno: ErrnoFault,
   862  			expectedLog: `
   863  --> proxy.fd_read(fd=4,iovs=65528,iovs_len=65528,result.size=65527)
   864  	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65528,iovs_len=65528,result.size=65527)
   865  	<== EFAULT
   866  <-- (21)
   867  `,
   868  		},
   869  		{
   870  			name: "length to read exceeds memory by 1",
   871  			fd:   fd,
   872  			iovs: 1, iovsCount: 1,
   873  			memory: []byte{
   874  				'?',        // `iovs` is after this
   875  				9, 0, 0, 0, // = iovs[0].offset
   876  				0, 0, 0x1, 0, // = iovs[0].length on the second page
   877  				'?',
   878  			},
   879  			expectedErrno: ErrnoFault,
   880  			expectedLog: `
   881  --> proxy.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65526)
   882  	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65526)
   883  	<== EFAULT
   884  <-- (21)
   885  `,
   886  		},
   887  		{
   888  			name: "resultSize offset is outside memory",
   889  			fd:   fd,
   890  			iovs: 1, iovsCount: 1,
   891  			resultSize: 10, // 1 past memory
   892  			memory: []byte{
   893  				'?',        // `iovs` is after this
   894  				9, 0, 0, 0, // = iovs[0].offset
   895  				1, 0, 0, 0, // = iovs[0].length
   896  				'?',
   897  			},
   898  			expectedErrno: ErrnoFault,
   899  			expectedLog: `
   900  --> proxy.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65536)
   901  	==> wasi_snapshot_preview1.fd_read(fd=4,iovs=65527,iovs_len=65527,result.size=65536)
   902  	<== EFAULT
   903  <-- (21)
   904  `,
   905  		},
   906  	}
   907  
   908  	for _, tt := range tests {
   909  		tc := tt
   910  		t.Run(tc.name, func(t *testing.T) {
   911  			defer log.Reset()
   912  
   913  			offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
   914  
   915  			memoryWriteOK := mod.Memory().Write(testCtx, offset, tc.memory)
   916  			require.True(t, memoryWriteOK)
   917  
   918  			requireErrno(t, tc.expectedErrno, mod, functionFdRead, uint64(tc.fd), uint64(tc.iovs+offset), uint64(tc.iovsCount+offset), uint64(tc.resultSize+offset))
   919  			require.Equal(t, tc.expectedLog, "\n"+log.String())
   920  		})
   921  	}
   922  }
   923  
   924  func Test_fdRead_shouldContinueRead(t *testing.T) {
   925  	tests := []struct {
   926  		name          string
   927  		n, l          uint32
   928  		err           error
   929  		expectedOk    bool
   930  		expectedErrno Errno
   931  	}{
   932  		{
   933  			name: "break when nothing to read",
   934  			n:    0,
   935  			l:    0,
   936  		},
   937  		{
   938  			name: "break when nothing read",
   939  			n:    0,
   940  			l:    4,
   941  		},
   942  		{
   943  			name: "break on partial read",
   944  			n:    3,
   945  			l:    4,
   946  		},
   947  		{
   948  			name:       "continue on full read",
   949  			n:          4,
   950  			l:          4,
   951  			expectedOk: true,
   952  		},
   953  		{
   954  			name: "break on EOF on nothing to read",
   955  			err:  io.EOF,
   956  		},
   957  		{
   958  			name: "break on EOF on nothing read",
   959  			l:    4,
   960  			err:  io.EOF,
   961  		},
   962  		{
   963  			name: "break on EOF on partial read",
   964  			n:    3,
   965  			l:    4,
   966  			err:  io.EOF,
   967  		},
   968  		{
   969  			name: "break on EOF on full read",
   970  			n:    4,
   971  			l:    4,
   972  			err:  io.EOF,
   973  		},
   974  		{
   975  			name:          "return ErrnoIo on error on nothing to read",
   976  			err:           io.ErrClosedPipe,
   977  			expectedErrno: ErrnoIo,
   978  		},
   979  		{
   980  			name:          "return ErrnoIo on error on nothing read",
   981  			l:             4,
   982  			err:           io.ErrClosedPipe,
   983  			expectedErrno: ErrnoIo,
   984  		},
   985  		{ // Special case, allows processing data before err
   986  			name: "break on error on partial read",
   987  			n:    3,
   988  			l:    4,
   989  			err:  io.ErrClosedPipe,
   990  		},
   991  		{ // Special case, allows processing data before err
   992  			name: "break on error on full read",
   993  			n:    4,
   994  			l:    4,
   995  			err:  io.ErrClosedPipe,
   996  		},
   997  	}
   998  	for _, tt := range tests {
   999  		tc := tt
  1000  
  1001  		t.Run(tc.name, func(t *testing.T) {
  1002  			ok, errno := fdRead_shouldContinueRead(tc.n, tc.l, tc.err)
  1003  			require.Equal(t, tc.expectedOk, ok)
  1004  			require.Equal(t, tc.expectedErrno, errno)
  1005  		})
  1006  	}
  1007  }
  1008  
  1009  var (
  1010  	fdReadDirFs = fstest.MapFS{
  1011  		"notdir":   {},
  1012  		"emptydir": {Mode: fs.ModeDir},
  1013  		"dir":      {Mode: fs.ModeDir},
  1014  		"dir/-":    {},                 // len = 24+1 = 25
  1015  		"dir/a-":   {Mode: fs.ModeDir}, // len = 24+2 = 26
  1016  		"dir/ab-":  {},                 // len = 24+3 = 27
  1017  	}
  1018  
  1019  	testDirEntries = func() []fs.DirEntry {
  1020  		entries, err := fdReadDirFs.ReadDir("dir")
  1021  		if err != nil {
  1022  			panic(err)
  1023  		}
  1024  		return entries
  1025  	}()
  1026  
  1027  	dirent1 = []byte{
  1028  		1, 0, 0, 0, 0, 0, 0, 0, // d_next = 1
  1029  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1030  		1, 0, 0, 0, // d_namlen = 1 character
  1031  		4, 0, 0, 0, // d_type = regular_file
  1032  		'-', // name
  1033  	}
  1034  	dirent2 = []byte{
  1035  		2, 0, 0, 0, 0, 0, 0, 0, // d_next = 2
  1036  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1037  		2, 0, 0, 0, // d_namlen = 1 character
  1038  		3, 0, 0, 0, // d_type =  directory
  1039  		'a', '-', // name
  1040  	}
  1041  	dirent3 = []byte{
  1042  		3, 0, 0, 0, 0, 0, 0, 0, // d_next = 3
  1043  		0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0
  1044  		3, 0, 0, 0, // d_namlen = 3 characters
  1045  		4, 0, 0, 0, // d_type = regular_file
  1046  		'a', 'b', '-', // name
  1047  	}
  1048  )
  1049  
  1050  func Test_fdReaddir(t *testing.T) {
  1051  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs))
  1052  	defer r.Close(testCtx)
  1053  
  1054  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  1055  
  1056  	fd, err := fsc.OpenFile(testCtx, "dir")
  1057  	require.NoError(t, err)
  1058  
  1059  	tests := []struct {
  1060  		name            string
  1061  		dir             func() *internalsys.FileEntry
  1062  		buf, bufLen     uint32
  1063  		cookie          int64
  1064  		expectedMem     []byte
  1065  		expectedMemSize int
  1066  		expectedBufused uint32
  1067  		expectedReadDir *internalsys.ReadDir
  1068  	}{
  1069  		{
  1070  			name: "empty dir",
  1071  			dir: func() *internalsys.FileEntry {
  1072  				dir, err := fdReadDirFs.Open("emptydir")
  1073  				require.NoError(t, err)
  1074  
  1075  				return &internalsys.FileEntry{File: dir}
  1076  			},
  1077  			buf: 0, bufLen: 1,
  1078  			cookie:          0,
  1079  			expectedBufused: 0,
  1080  			expectedMem:     []byte{},
  1081  			expectedReadDir: &internalsys.ReadDir{},
  1082  		},
  1083  		{
  1084  			name: "full read",
  1085  			dir: func() *internalsys.FileEntry {
  1086  				dir, err := fdReadDirFs.Open("dir")
  1087  				require.NoError(t, err)
  1088  
  1089  				return &internalsys.FileEntry{File: dir}
  1090  			},
  1091  			buf: 0, bufLen: 4096,
  1092  			cookie:          0,
  1093  			expectedBufused: 78, // length of all entries
  1094  			expectedMem:     append(append(dirent1, dirent2...), dirent3...),
  1095  			expectedReadDir: &internalsys.ReadDir{
  1096  				CountRead: 3,
  1097  				Entries:   testDirEntries,
  1098  			},
  1099  		},
  1100  		{
  1101  			name: "can't read",
  1102  			dir: func() *internalsys.FileEntry {
  1103  				dir, err := fdReadDirFs.Open("dir")
  1104  				require.NoError(t, err)
  1105  
  1106  				return &internalsys.FileEntry{File: dir}
  1107  			},
  1108  			buf: 0, bufLen: 23, // length is too short for header
  1109  			cookie:          0,
  1110  			expectedBufused: 23, // == bufLen which is the size of the dirent
  1111  			expectedMem:     nil,
  1112  			expectedReadDir: &internalsys.ReadDir{
  1113  				CountRead: 2,
  1114  				Entries:   testDirEntries[:2],
  1115  			},
  1116  		},
  1117  		{
  1118  			name: "can't read name",
  1119  			dir: func() *internalsys.FileEntry {
  1120  				dir, err := fdReadDirFs.Open("dir")
  1121  				require.NoError(t, err)
  1122  
  1123  				return &internalsys.FileEntry{File: dir}
  1124  			},
  1125  			buf: 0, bufLen: 24, // length is long enough for first, but not the name.
  1126  			cookie:          0,
  1127  			expectedBufused: 24,           // == bufLen which is the size of the dirent
  1128  			expectedMem:     dirent1[:24], // header without name
  1129  			expectedReadDir: &internalsys.ReadDir{
  1130  				CountRead: 3,
  1131  				Entries:   testDirEntries,
  1132  			},
  1133  		},
  1134  		{
  1135  			name: "read exactly first",
  1136  			dir: func() *internalsys.FileEntry {
  1137  				dir, err := fdReadDirFs.Open("dir")
  1138  				require.NoError(t, err)
  1139  
  1140  				return &internalsys.FileEntry{File: dir}
  1141  			},
  1142  			buf: 0, bufLen: 25, // length is long enough for first + the name, but not more.
  1143  			cookie:          0,
  1144  			expectedBufused: 25, // length to read exactly first.
  1145  			expectedMem:     dirent1,
  1146  			expectedReadDir: &internalsys.ReadDir{
  1147  				CountRead: 3,
  1148  				Entries:   testDirEntries,
  1149  			},
  1150  		},
  1151  		{
  1152  			name: "read exactly second",
  1153  			dir: func() *internalsys.FileEntry {
  1154  				dir, err := fdReadDirFs.Open("dir")
  1155  				require.NoError(t, err)
  1156  				entry, err := dir.(fs.ReadDirFile).ReadDir(1)
  1157  				require.NoError(t, err)
  1158  
  1159  				return &internalsys.FileEntry{
  1160  					File: dir,
  1161  					ReadDir: &internalsys.ReadDir{
  1162  						CountRead: 1,
  1163  						Entries:   entry,
  1164  					},
  1165  				}
  1166  			},
  1167  			buf: 0, bufLen: 26, // length is long enough for exactly second.
  1168  			cookie:          1,  // d_next of first
  1169  			expectedBufused: 26, // length to read exactly second.
  1170  			expectedMem:     dirent2,
  1171  			expectedReadDir: &internalsys.ReadDir{
  1172  				CountRead: 3,
  1173  				Entries:   testDirEntries[1:],
  1174  			},
  1175  		},
  1176  		{
  1177  			name: "read second and a little more",
  1178  			dir: func() *internalsys.FileEntry {
  1179  				dir, err := fdReadDirFs.Open("dir")
  1180  				require.NoError(t, err)
  1181  				entry, err := dir.(fs.ReadDirFile).ReadDir(1)
  1182  				require.NoError(t, err)
  1183  
  1184  				return &internalsys.FileEntry{
  1185  					File: dir,
  1186  					ReadDir: &internalsys.ReadDir{
  1187  						CountRead: 1,
  1188  						Entries:   entry,
  1189  					},
  1190  				}
  1191  			},
  1192  			buf: 0, bufLen: 30, // length is longer than the second entry, but not long enough for a header.
  1193  			cookie:          1,  // d_next of first
  1194  			expectedBufused: 30, // length to read some more, but not enough for a header, so buf was exhausted.
  1195  			expectedMem:     dirent2,
  1196  			expectedMemSize: len(dirent2), // we do not want to compare the full buffer since we don't know what the leftover 4 bytes will contain.
  1197  			expectedReadDir: &internalsys.ReadDir{
  1198  				CountRead: 3,
  1199  				Entries:   testDirEntries[1:],
  1200  			},
  1201  		},
  1202  		{
  1203  			name: "read second and header of third",
  1204  			dir: func() *internalsys.FileEntry {
  1205  				dir, err := fdReadDirFs.Open("dir")
  1206  				require.NoError(t, err)
  1207  				entry, err := dir.(fs.ReadDirFile).ReadDir(1)
  1208  				require.NoError(t, err)
  1209  
  1210  				return &internalsys.FileEntry{
  1211  					File: dir,
  1212  					ReadDir: &internalsys.ReadDir{
  1213  						CountRead: 1,
  1214  						Entries:   entry,
  1215  					},
  1216  				}
  1217  			},
  1218  			buf: 0, bufLen: 50, // length is longer than the second entry + enough for the header of third.
  1219  			cookie:          1,  // d_next of first
  1220  			expectedBufused: 50, // length to read exactly second and the header of third.
  1221  			expectedMem:     append(dirent2, dirent3[0:24]...),
  1222  			expectedReadDir: &internalsys.ReadDir{
  1223  				CountRead: 3,
  1224  				Entries:   testDirEntries[1:],
  1225  			},
  1226  		},
  1227  		{
  1228  			name: "read second and third",
  1229  			dir: func() *internalsys.FileEntry {
  1230  				dir, err := fdReadDirFs.Open("dir")
  1231  				require.NoError(t, err)
  1232  				entry, err := dir.(fs.ReadDirFile).ReadDir(1)
  1233  				require.NoError(t, err)
  1234  
  1235  				return &internalsys.FileEntry{
  1236  					File: dir,
  1237  					ReadDir: &internalsys.ReadDir{
  1238  						CountRead: 1,
  1239  						Entries:   entry,
  1240  					},
  1241  				}
  1242  			},
  1243  			buf: 0, bufLen: 53, // length is long enough for second and third.
  1244  			cookie:          1,  // d_next of first
  1245  			expectedBufused: 53, // length to read exactly one second and third.
  1246  			expectedMem:     append(dirent2, dirent3...),
  1247  			expectedReadDir: &internalsys.ReadDir{
  1248  				CountRead: 3,
  1249  				Entries:   testDirEntries[1:],
  1250  			},
  1251  		},
  1252  		{
  1253  			name: "read exactly third",
  1254  			dir: func() *internalsys.FileEntry {
  1255  				dir, err := fdReadDirFs.Open("dir")
  1256  				require.NoError(t, err)
  1257  				two, err := dir.(fs.ReadDirFile).ReadDir(2)
  1258  				require.NoError(t, err)
  1259  
  1260  				return &internalsys.FileEntry{
  1261  					File: dir,
  1262  					ReadDir: &internalsys.ReadDir{
  1263  						CountRead: 2,
  1264  						Entries:   two[1:],
  1265  					},
  1266  				}
  1267  			},
  1268  			buf: 0, bufLen: 27, // length is long enough for exactly third.
  1269  			cookie:          2,  // d_next of second.
  1270  			expectedBufused: 27, // length to read exactly third.
  1271  			expectedMem:     dirent3,
  1272  			expectedReadDir: &internalsys.ReadDir{
  1273  				CountRead: 3,
  1274  				Entries:   testDirEntries[2:],
  1275  			},
  1276  		},
  1277  		{
  1278  			name: "read third and beyond",
  1279  			dir: func() *internalsys.FileEntry {
  1280  				dir, err := fdReadDirFs.Open("dir")
  1281  				require.NoError(t, err)
  1282  				two, err := dir.(fs.ReadDirFile).ReadDir(2)
  1283  				require.NoError(t, err)
  1284  
  1285  				return &internalsys.FileEntry{
  1286  					File: dir,
  1287  					ReadDir: &internalsys.ReadDir{
  1288  						CountRead: 2,
  1289  						Entries:   two[1:],
  1290  					},
  1291  				}
  1292  			},
  1293  			buf: 0, bufLen: 100, // length is long enough for third and more, but there is nothing more.
  1294  			cookie:          2,  // d_next of second.
  1295  			expectedBufused: 27, // length to read exactly third.
  1296  			expectedMem:     dirent3,
  1297  			expectedReadDir: &internalsys.ReadDir{
  1298  				CountRead: 3,
  1299  				Entries:   testDirEntries[2:],
  1300  			},
  1301  		},
  1302  	}
  1303  
  1304  	for _, tt := range tests {
  1305  		tc := tt
  1306  		t.Run(tc.name, func(t *testing.T) {
  1307  			defer log.Reset()
  1308  
  1309  			// Assign the state we are testing
  1310  			file, ok := fsc.OpenedFile(testCtx, fd)
  1311  			require.True(t, ok)
  1312  			dir := tc.dir()
  1313  			defer dir.File.Close()
  1314  
  1315  			file.File = dir.File
  1316  			file.ReadDir = dir.ReadDir
  1317  
  1318  			maskMemory(t, testCtx, mod, int(tc.bufLen))
  1319  
  1320  			// use an arbitrarily high value for the buf used position.
  1321  			resultBufused := uint32(16192)
  1322  			requireErrno(t, ErrnoSuccess, mod, functionFdReaddir,
  1323  				uint64(fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(resultBufused))
  1324  
  1325  			// read back the bufused and compare memory against it
  1326  			bufUsed, ok := mod.Memory().ReadUint32Le(testCtx, resultBufused)
  1327  			require.True(t, ok)
  1328  			require.Equal(t, tc.expectedBufused, bufUsed)
  1329  
  1330  			mem, ok := mod.Memory().Read(testCtx, tc.buf, bufUsed)
  1331  			require.True(t, ok)
  1332  
  1333  			if tc.expectedMem != nil {
  1334  				if tc.expectedMemSize == 0 {
  1335  					tc.expectedMemSize = len(tc.expectedMem)
  1336  				}
  1337  				require.Equal(t, tc.expectedMem, mem[:tc.expectedMemSize])
  1338  			}
  1339  
  1340  			require.Equal(t, tc.expectedReadDir, file.ReadDir)
  1341  		})
  1342  	}
  1343  }
  1344  
  1345  func Test_fdReaddir_Errors(t *testing.T) {
  1346  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(fdReadDirFs))
  1347  	defer r.Close(testCtx)
  1348  	memLen := mod.Memory().Size(testCtx)
  1349  
  1350  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  1351  
  1352  	dirFD, err := fsc.OpenFile(testCtx, "dir")
  1353  	require.NoError(t, err)
  1354  
  1355  	fileFD, err := fsc.OpenFile(testCtx, "notdir")
  1356  	require.NoError(t, err)
  1357  
  1358  	tests := []struct {
  1359  		name                           string
  1360  		dir                            func() *internalsys.FileEntry
  1361  		fd, buf, bufLen, resultBufused uint32
  1362  		cookie                         int64
  1363  		readDir                        *internalsys.ReadDir
  1364  		expectedErrno                  Errno
  1365  		expectedLog                    string
  1366  	}{
  1367  		{
  1368  			name:          "out-of-memory reading buf",
  1369  			fd:            dirFD,
  1370  			buf:           memLen,
  1371  			bufLen:        1000,
  1372  			expectedErrno: ErrnoFault,
  1373  			expectedLog: `
  1374  --> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
  1375  	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
  1376  	<== EFAULT
  1377  <-- (21)
  1378  `,
  1379  		},
  1380  		{
  1381  			name:          "invalid fd",
  1382  			fd:            42, // arbitrary invalid fd
  1383  			expectedErrno: ErrnoBadf,
  1384  			expectedLog: `
  1385  --> proxy.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0)
  1386  	==> wasi_snapshot_preview1.fd_readdir(fd=42,buf=0,buf_len=0,cookie=0,result.bufused=0)
  1387  	<== EBADF
  1388  <-- (8)
  1389  `,
  1390  		},
  1391  		{
  1392  			name:          "not a dir",
  1393  			fd:            fileFD,
  1394  			expectedErrno: ErrnoNotdir,
  1395  			expectedLog: `
  1396  --> proxy.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0)
  1397  	==> wasi_snapshot_preview1.fd_readdir(fd=5,buf=0,buf_len=0,cookie=0,result.bufused=0)
  1398  	<== ENOTDIR
  1399  <-- (54)
  1400  `,
  1401  		},
  1402  		{
  1403  			name:          "out-of-memory reading buf",
  1404  			fd:            dirFD,
  1405  			buf:           memLen,
  1406  			bufLen:        1000,
  1407  			expectedErrno: ErrnoFault,
  1408  			expectedLog: `
  1409  --> proxy.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
  1410  	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65536,buf_len=1000,cookie=0,result.bufused=0)
  1411  	<== EFAULT
  1412  <-- (21)
  1413  `,
  1414  		},
  1415  		{
  1416  			name:          "out-of-memory reading bufLen",
  1417  			fd:            dirFD,
  1418  			buf:           memLen - 1,
  1419  			bufLen:        1000,
  1420  			expectedErrno: ErrnoFault,
  1421  			expectedLog: `
  1422  --> proxy.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0)
  1423  	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=65535,buf_len=1000,cookie=0,result.bufused=0)
  1424  	<== EFAULT
  1425  <-- (21)
  1426  `,
  1427  		},
  1428  		{
  1429  			name: "resultBufused is outside memory",
  1430  			fd:   dirFD,
  1431  			buf:  0, bufLen: 1,
  1432  			resultBufused: memLen,
  1433  			expectedErrno: ErrnoFault,
  1434  			expectedLog: `
  1435  --> proxy.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536)
  1436  	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1,cookie=0,result.bufused=65536)
  1437  	<== EFAULT
  1438  <-- (21)
  1439  `,
  1440  		},
  1441  		{
  1442  			name: "cookie invalid when no prior state",
  1443  			fd:   dirFD,
  1444  			buf:  0, bufLen: 1000,
  1445  			cookie:        1,
  1446  			resultBufused: 2000,
  1447  			expectedErrno: ErrnoInval,
  1448  			expectedLog: `
  1449  --> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
  1450  	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=1,result.bufused=2000)
  1451  	<== EINVAL
  1452  <-- (28)
  1453  `,
  1454  		},
  1455  		{
  1456  			name: "negative cookie invalid",
  1457  			fd:   dirFD,
  1458  			buf:  0, bufLen: 1000,
  1459  			cookie:        -1,
  1460  			readDir:       &internalsys.ReadDir{CountRead: 1},
  1461  			resultBufused: 2000,
  1462  			expectedErrno: ErrnoInval,
  1463  			expectedLog: `
  1464  --> proxy.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=18446744073709551615,result.bufused=2000)
  1465  	==> wasi_snapshot_preview1.fd_readdir(fd=4,buf=0,buf_len=1000,cookie=18446744073709551615,result.bufused=2000)
  1466  	<== EINVAL
  1467  <-- (28)
  1468  `,
  1469  		},
  1470  	}
  1471  
  1472  	for _, tt := range tests {
  1473  		tc := tt
  1474  		t.Run(tc.name, func(t *testing.T) {
  1475  			defer log.Reset()
  1476  
  1477  			// Reset the directory so that tests don't taint each other.
  1478  			if file, ok := fsc.OpenedFile(testCtx, tc.fd); ok && tc.fd == dirFD {
  1479  				dir, err := fdReadDirFs.Open("dir")
  1480  				require.NoError(t, err)
  1481  				defer dir.Close()
  1482  
  1483  				file.File = dir
  1484  				file.ReadDir = nil
  1485  			}
  1486  
  1487  			requireErrno(t, tc.expectedErrno, mod, functionFdReaddir,
  1488  				uint64(tc.fd), uint64(tc.buf), uint64(tc.bufLen), uint64(tc.cookie), uint64(tc.resultBufused))
  1489  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1490  		})
  1491  	}
  1492  }
  1493  
  1494  func Test_lastDirEntries(t *testing.T) {
  1495  	tests := []struct {
  1496  		name            string
  1497  		f               *internalsys.ReadDir
  1498  		cookie          int64
  1499  		expectedEntries []fs.DirEntry
  1500  		expectedErrno   Errno
  1501  	}{
  1502  		{
  1503  			name: "no prior call",
  1504  		},
  1505  		{
  1506  			name:          "no prior call, but passed a cookie",
  1507  			cookie:        1,
  1508  			expectedErrno: ErrnoInval,
  1509  		},
  1510  		{
  1511  			name: "cookie is negative",
  1512  			f: &internalsys.ReadDir{
  1513  				CountRead: 3,
  1514  				Entries:   testDirEntries,
  1515  			},
  1516  			cookie:        -1,
  1517  			expectedErrno: ErrnoInval,
  1518  		},
  1519  		{
  1520  			name: "cookie is greater than last d_next",
  1521  			f: &internalsys.ReadDir{
  1522  				CountRead: 3,
  1523  				Entries:   testDirEntries,
  1524  			},
  1525  			cookie:        5,
  1526  			expectedErrno: ErrnoInval,
  1527  		},
  1528  		{
  1529  			name: "cookie is last pos",
  1530  			f: &internalsys.ReadDir{
  1531  				CountRead: 3,
  1532  				Entries:   testDirEntries,
  1533  			},
  1534  			cookie:          3,
  1535  			expectedEntries: nil,
  1536  		},
  1537  		{
  1538  			name: "cookie is one before last pos",
  1539  			f: &internalsys.ReadDir{
  1540  				CountRead: 3,
  1541  				Entries:   testDirEntries,
  1542  			},
  1543  			cookie:          2,
  1544  			expectedEntries: testDirEntries[2:],
  1545  		},
  1546  		{
  1547  			name: "cookie is before current entries",
  1548  			f: &internalsys.ReadDir{
  1549  				CountRead: 5,
  1550  				Entries:   testDirEntries,
  1551  			},
  1552  			cookie:        1,
  1553  			expectedErrno: ErrnoNosys, // not implemented
  1554  		},
  1555  	}
  1556  
  1557  	for _, tt := range tests {
  1558  		tc := tt
  1559  
  1560  		t.Run(tc.name, func(t *testing.T) {
  1561  			f := tc.f
  1562  			if f == nil {
  1563  				f = &internalsys.ReadDir{}
  1564  			}
  1565  			entries, errno := lastDirEntries(f, tc.cookie)
  1566  			require.Equal(t, tc.expectedErrno, errno)
  1567  			require.Equal(t, tc.expectedEntries, entries)
  1568  		})
  1569  	}
  1570  }
  1571  
  1572  func Test_maxDirents(t *testing.T) {
  1573  	tests := []struct {
  1574  		name                        string
  1575  		entries                     []fs.DirEntry
  1576  		maxLen                      uint32
  1577  		expectedCount               uint32
  1578  		expectedwriteTruncatedEntry bool
  1579  		expectedBufused             uint32
  1580  	}{
  1581  		{
  1582  			name: "no entries",
  1583  		},
  1584  		{
  1585  			name:                        "can't fit one",
  1586  			entries:                     testDirEntries,
  1587  			maxLen:                      23,
  1588  			expectedBufused:             23,
  1589  			expectedwriteTruncatedEntry: false,
  1590  		},
  1591  		{
  1592  			name:                        "only fits header",
  1593  			entries:                     testDirEntries,
  1594  			maxLen:                      24,
  1595  			expectedBufused:             24,
  1596  			expectedwriteTruncatedEntry: true,
  1597  		},
  1598  		{
  1599  			name:            "one",
  1600  			entries:         testDirEntries,
  1601  			maxLen:          25,
  1602  			expectedCount:   1,
  1603  			expectedBufused: 25,
  1604  		},
  1605  		{
  1606  			name:                        "one but not room for two's name",
  1607  			entries:                     testDirEntries,
  1608  			maxLen:                      25 + 25,
  1609  			expectedCount:               1,
  1610  			expectedwriteTruncatedEntry: true, // can write direntSize
  1611  			expectedBufused:             25 + 25,
  1612  		},
  1613  		{
  1614  			name:            "two",
  1615  			entries:         testDirEntries,
  1616  			maxLen:          25 + 26,
  1617  			expectedCount:   2,
  1618  			expectedBufused: 25 + 26,
  1619  		},
  1620  		{
  1621  			name:                        "two but not three's dirent",
  1622  			entries:                     testDirEntries,
  1623  			maxLen:                      25 + 26 + 20,
  1624  			expectedCount:               2,
  1625  			expectedwriteTruncatedEntry: false, // 20 + 4 == direntSize
  1626  			expectedBufused:             25 + 26 + 20,
  1627  		},
  1628  		{
  1629  			name:                        "two but not three's name",
  1630  			entries:                     testDirEntries,
  1631  			maxLen:                      25 + 26 + 26,
  1632  			expectedCount:               2,
  1633  			expectedwriteTruncatedEntry: true, // can write direntSize
  1634  			expectedBufused:             25 + 26 + 26,
  1635  		},
  1636  		{
  1637  			name:                        "three",
  1638  			entries:                     testDirEntries,
  1639  			maxLen:                      25 + 26 + 27,
  1640  			expectedCount:               3,
  1641  			expectedwriteTruncatedEntry: false, // end of dir
  1642  			expectedBufused:             25 + 26 + 27,
  1643  		},
  1644  		{
  1645  			name:                        "max",
  1646  			entries:                     testDirEntries,
  1647  			maxLen:                      100,
  1648  			expectedCount:               3,
  1649  			expectedwriteTruncatedEntry: false, // end of dir
  1650  			expectedBufused:             25 + 26 + 27,
  1651  		},
  1652  	}
  1653  
  1654  	for _, tt := range tests {
  1655  		tc := tt
  1656  
  1657  		t.Run(tc.name, func(t *testing.T) {
  1658  			bufused, direntCount, writeTruncatedEntry := maxDirents(tc.entries, tc.maxLen)
  1659  			require.Equal(t, tc.expectedCount, direntCount)
  1660  			require.Equal(t, tc.expectedwriteTruncatedEntry, writeTruncatedEntry)
  1661  			require.Equal(t, tc.expectedBufused, bufused)
  1662  		})
  1663  	}
  1664  }
  1665  
  1666  func Test_writeDirents(t *testing.T) {
  1667  	tests := []struct {
  1668  		name                string
  1669  		entries             []fs.DirEntry
  1670  		entryCount          uint32
  1671  		writeTruncatedEntry bool
  1672  		expectedEntriesBuf  []byte
  1673  	}{
  1674  		{
  1675  			name:    "none",
  1676  			entries: testDirEntries,
  1677  		},
  1678  		{
  1679  			name:               "one",
  1680  			entries:            testDirEntries,
  1681  			entryCount:         1,
  1682  			expectedEntriesBuf: dirent1,
  1683  		},
  1684  		{
  1685  			name:               "two",
  1686  			entries:            testDirEntries,
  1687  			entryCount:         2,
  1688  			expectedEntriesBuf: append(dirent1, dirent2...),
  1689  		},
  1690  		{
  1691  			name:                "two with truncated",
  1692  			entries:             testDirEntries,
  1693  			entryCount:          2,
  1694  			writeTruncatedEntry: true,
  1695  			expectedEntriesBuf:  append(append(dirent1, dirent2...), dirent3[0:10]...),
  1696  		},
  1697  		{
  1698  			name:               "three",
  1699  			entries:            testDirEntries,
  1700  			entryCount:         3,
  1701  			expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3...),
  1702  		},
  1703  	}
  1704  
  1705  	for _, tt := range tests {
  1706  		tc := tt
  1707  
  1708  		t.Run(tc.name, func(t *testing.T) {
  1709  			cookie := uint64(1)
  1710  			entriesBuf := make([]byte, len(tc.expectedEntriesBuf))
  1711  			writeDirents(tc.entries, tc.entryCount, tc.writeTruncatedEntry, entriesBuf, cookie)
  1712  			require.Equal(t, tc.expectedEntriesBuf, entriesBuf)
  1713  		})
  1714  	}
  1715  }
  1716  
  1717  // Test_fdRenumber only tests it is stubbed for GrainLang per #271
  1718  func Test_fdRenumber(t *testing.T) {
  1719  	log := requireErrnoNosys(t, functionFdRenumber, 0, 0)
  1720  	require.Equal(t, `
  1721  --> proxy.fd_renumber(fd=0,to=0)
  1722  	--> wasi_snapshot_preview1.fd_renumber(fd=0,to=0)
  1723  	<-- ENOSYS
  1724  <-- (52)
  1725  `, log)
  1726  }
  1727  
  1728  func Test_fdSeek(t *testing.T) {
  1729  	mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero"))
  1730  	defer r.Close(testCtx)
  1731  
  1732  	resultNewoffset := uint32(1) // arbitrary offset in api.Memory for the new offset value
  1733  
  1734  	tests := []struct {
  1735  		name           string
  1736  		offset         int64
  1737  		whence         int
  1738  		expectedOffset int64
  1739  		expectedMemory []byte
  1740  		expectedLog    string
  1741  	}{
  1742  		{
  1743  			name:           "SeekStart",
  1744  			offset:         4, // arbitrary offset
  1745  			whence:         io.SeekStart,
  1746  			expectedOffset: 4, // = offset
  1747  			expectedMemory: []byte{
  1748  				'?',                    // resultNewoffset is after this
  1749  				4, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  1750  				'?',
  1751  			},
  1752  			expectedLog: `
  1753  --> proxy.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1)
  1754  	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=4,whence=0,result.newoffset=1)
  1755  	<== ESUCCESS
  1756  <-- (0)
  1757  `,
  1758  		},
  1759  		{
  1760  			name:           "SeekCurrent",
  1761  			offset:         1, // arbitrary offset
  1762  			whence:         io.SeekCurrent,
  1763  			expectedOffset: 2, // = 1 (the initial offset of the test file) + 1 (offset)
  1764  			expectedMemory: []byte{
  1765  				'?',                    // resultNewoffset is after this
  1766  				2, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  1767  				'?',
  1768  			},
  1769  			expectedLog: `
  1770  --> proxy.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1)
  1771  	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=1,whence=1,result.newoffset=1)
  1772  	<== ESUCCESS
  1773  <-- (0)
  1774  `,
  1775  		},
  1776  		{
  1777  			name:           "SeekEnd",
  1778  			offset:         -1, // arbitrary offset, note that offset can be negative
  1779  			whence:         io.SeekEnd,
  1780  			expectedOffset: 5, // = 6 (the size of the test file with content "wazero") + -1 (offset)
  1781  			expectedMemory: []byte{
  1782  				'?',                    // resultNewoffset is after this
  1783  				5, 0, 0, 0, 0, 0, 0, 0, // = expectedOffset
  1784  				'?',
  1785  			},
  1786  			expectedLog: `
  1787  --> proxy.fd_seek(fd=4,offset=18446744073709551615,whence=2,result.newoffset=1)
  1788  	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=18446744073709551615,whence=2,result.newoffset=1)
  1789  	<== ESUCCESS
  1790  <-- (0)
  1791  `,
  1792  		},
  1793  	}
  1794  
  1795  	for _, tt := range tests {
  1796  		tc := tt
  1797  		t.Run(tc.name, func(t *testing.T) {
  1798  			defer log.Reset()
  1799  
  1800  			maskMemory(t, testCtx, mod, len(tc.expectedMemory))
  1801  
  1802  			// Since we initialized this file, we know it is a seeker (because it is a MapFile)
  1803  			fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  1804  			f, ok := fsc.OpenedFile(testCtx, fd)
  1805  			require.True(t, ok)
  1806  			seeker := f.File.(io.Seeker)
  1807  
  1808  			// set the initial offset of the file to 1
  1809  			offset, err := seeker.Seek(1, io.SeekStart)
  1810  			require.NoError(t, err)
  1811  			require.Equal(t, int64(1), offset)
  1812  
  1813  			requireErrno(t, ErrnoSuccess, mod, functionFdSeek, uint64(fd), uint64(tc.offset), uint64(tc.whence), uint64(resultNewoffset))
  1814  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1815  
  1816  			actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
  1817  			require.True(t, ok)
  1818  			require.Equal(t, tc.expectedMemory, actual)
  1819  
  1820  			offset, err = seeker.Seek(0, io.SeekCurrent)
  1821  			require.NoError(t, err)
  1822  			require.Equal(t, tc.expectedOffset, offset) // test that the offset of file is actually updated.
  1823  		})
  1824  	}
  1825  }
  1826  
  1827  func Test_fdSeek_Errors(t *testing.T) {
  1828  	mod, fd, log, r := requireOpenFile(t, "/test_path", []byte("wazero"))
  1829  	defer r.Close(testCtx)
  1830  
  1831  	memorySize := mod.Memory().Size(testCtx)
  1832  
  1833  	tests := []struct {
  1834  		name                    string
  1835  		fd                      uint32
  1836  		offset                  uint64
  1837  		whence, resultNewoffset uint32
  1838  		expectedErrno           Errno
  1839  		expectedLog             string
  1840  	}{
  1841  		{
  1842  			name:          "invalid fd",
  1843  			fd:            42, // arbitrary invalid fd
  1844  			expectedErrno: ErrnoBadf,
  1845  			expectedLog: `
  1846  --> proxy.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0)
  1847  	==> wasi_snapshot_preview1.fd_seek(fd=42,offset=0,whence=0,result.newoffset=0)
  1848  	<== EBADF
  1849  <-- (8)
  1850  `,
  1851  		},
  1852  		{
  1853  			name:          "invalid whence",
  1854  			fd:            fd,
  1855  			whence:        3, // invalid whence, the largest whence io.SeekEnd(2) + 1
  1856  			expectedErrno: ErrnoInval,
  1857  			expectedLog: `
  1858  --> proxy.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0)
  1859  	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=3,result.newoffset=0)
  1860  	<== EINVAL
  1861  <-- (28)
  1862  `,
  1863  		},
  1864  		{
  1865  			name:            "out-of-memory writing resultNewoffset",
  1866  			fd:              fd,
  1867  			resultNewoffset: memorySize,
  1868  			expectedErrno:   ErrnoFault,
  1869  			expectedLog: `
  1870  --> proxy.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536)
  1871  	==> wasi_snapshot_preview1.fd_seek(fd=4,offset=0,whence=0,result.newoffset=65536)
  1872  	<== EFAULT
  1873  <-- (21)
  1874  `,
  1875  		},
  1876  	}
  1877  
  1878  	for _, tt := range tests {
  1879  		tc := tt
  1880  		t.Run(tc.name, func(t *testing.T) {
  1881  			defer log.Reset()
  1882  
  1883  			requireErrno(t, tc.expectedErrno, mod, functionFdSeek, uint64(tc.fd), tc.offset, uint64(tc.whence), uint64(tc.resultNewoffset))
  1884  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  1885  		})
  1886  	}
  1887  }
  1888  
  1889  // Test_fdSync only tests it is stubbed for GrainLang per #271
  1890  func Test_fdSync(t *testing.T) {
  1891  	log := requireErrnoNosys(t, functionFdSync, 0)
  1892  	require.Equal(t, `
  1893  --> proxy.fd_sync(fd=0)
  1894  	--> wasi_snapshot_preview1.fd_sync(fd=0)
  1895  	<-- ENOSYS
  1896  <-- (52)
  1897  `, log)
  1898  }
  1899  
  1900  // Test_fdTell only tests it is stubbed for GrainLang per #271
  1901  func Test_fdTell(t *testing.T) {
  1902  	log := requireErrnoNosys(t, functionFdTell, 0, 0)
  1903  	require.Equal(t, `
  1904  --> proxy.fd_tell(fd=0,result.offset=0)
  1905  	--> wasi_snapshot_preview1.fd_tell(fd=0,result.offset=0)
  1906  	<-- ENOSYS
  1907  <-- (52)
  1908  `, log)
  1909  }
  1910  
  1911  func Test_fdWrite(t *testing.T) {
  1912  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  1913  	pathName := "test_path"
  1914  	mod, fd, log, r := requireOpenWritableFile(t, tmpDir, pathName)
  1915  	defer r.Close(testCtx)
  1916  
  1917  	iovs := uint32(1) // arbitrary offset
  1918  	initialMemory := []byte{
  1919  		'?',         // `iovs` is after this
  1920  		18, 0, 0, 0, // = iovs[0].offset
  1921  		4, 0, 0, 0, // = iovs[0].length
  1922  		23, 0, 0, 0, // = iovs[1].offset
  1923  		2, 0, 0, 0, // = iovs[1].length
  1924  		'?',                // iovs[0].offset is after this
  1925  		'w', 'a', 'z', 'e', // iovs[0].length bytes
  1926  		'?',      // iovs[1].offset is after this
  1927  		'r', 'o', // iovs[1].length bytes
  1928  		'?',
  1929  	}
  1930  	iovsCount := uint32(2)   // The count of iovs
  1931  	resultSize := uint32(26) // arbitrary offset
  1932  	expectedMemory := append(
  1933  		initialMemory,
  1934  		6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1935  		'?',
  1936  	)
  1937  
  1938  	maskMemory(t, testCtx, mod, len(expectedMemory))
  1939  	ok := mod.Memory().Write(testCtx, 0, initialMemory)
  1940  	require.True(t, ok)
  1941  
  1942  	requireErrno(t, ErrnoSuccess, mod, functionFdWrite, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
  1943  	require.Equal(t, `
  1944  --> proxy.fd_write(fd=4,iovs=1,iovs_len=2,result.size=26)
  1945  	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=1,iovs_len=2,result.size=26)
  1946  	<== ESUCCESS
  1947  <-- (0)
  1948  `, "\n"+log.String())
  1949  
  1950  	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
  1951  	require.True(t, ok)
  1952  	require.Equal(t, expectedMemory, actual)
  1953  
  1954  	// Since we initialized this file, we know we can read it by path
  1955  	buf, err := os.ReadFile(path.Join(tmpDir, pathName))
  1956  	require.NoError(t, err)
  1957  
  1958  	require.Equal(t, []byte("wazero"), buf) // verify the file was actually written
  1959  }
  1960  
  1961  // Test_fdWrite_discard ensures default configuration doesn't add needless
  1962  // overhead, but still returns valid data. For example, writing to STDOUT when
  1963  // it is io.Discard.
  1964  func Test_fdWrite_discard(t *testing.T) {
  1965  	// Default has io.Discard as stdout/stderr
  1966  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
  1967  	defer r.Close(testCtx)
  1968  
  1969  	iovs := uint32(1) // arbitrary offset
  1970  	initialMemory := []byte{
  1971  		'?',         // `iovs` is after this
  1972  		18, 0, 0, 0, // = iovs[0].offset
  1973  		4, 0, 0, 0, // = iovs[0].length
  1974  		23, 0, 0, 0, // = iovs[1].offset
  1975  		2, 0, 0, 0, // = iovs[1].length
  1976  		'?',                // iovs[0].offset is after this
  1977  		'w', 'a', 'z', 'e', // iovs[0].length bytes
  1978  		'?',      // iovs[1].offset is after this
  1979  		'r', 'o', // iovs[1].length bytes
  1980  		'?',
  1981  	}
  1982  	iovsCount := uint32(2)   // The count of iovs
  1983  	resultSize := uint32(26) // arbitrary offset
  1984  	expectedMemory := append(
  1985  		initialMemory,
  1986  		6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
  1987  		'?',
  1988  	)
  1989  
  1990  	maskMemory(t, testCtx, mod, len(expectedMemory))
  1991  	ok := mod.Memory().Write(testCtx, 0, initialMemory)
  1992  	require.True(t, ok)
  1993  
  1994  	fd := 1 // stdout
  1995  	requireErrno(t, ErrnoSuccess, mod, functionFdWrite, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
  1996  	require.Equal(t, `
  1997  --> proxy.fd_write(fd=1,iovs=1,iovs_len=2,result.size=26)
  1998  	==> wasi_snapshot_preview1.fd_write(fd=1,iovs=1,iovs_len=2,result.size=26)
  1999  	<== ESUCCESS
  2000  <-- (0)
  2001  `, "\n"+log.String())
  2002  
  2003  	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
  2004  	require.True(t, ok)
  2005  	require.Equal(t, expectedMemory, actual)
  2006  }
  2007  
  2008  func Test_fdWrite_Errors(t *testing.T) {
  2009  	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
  2010  	pathName := "test_path"
  2011  	mod, fd, log, r := requireOpenWritableFile(t, tmpDir, pathName)
  2012  	defer r.Close(testCtx)
  2013  
  2014  	// Setup valid test memory
  2015  	iovs, iovsCount := uint32(0), uint32(1)
  2016  	memory := []byte{
  2017  		8, 0, 0, 0, // = iovs[0].offset (where the data "hi" begins)
  2018  		2, 0, 0, 0, // = iovs[0].length (how many bytes are in "hi")
  2019  		'h', 'i', // iovs[0].length bytes
  2020  	}
  2021  
  2022  	tests := []struct {
  2023  		name           string
  2024  		fd, resultSize uint32
  2025  		memory         []byte
  2026  		expectedErrno  Errno
  2027  		expectedLog    string
  2028  	}{
  2029  		{
  2030  			name:          "invalid fd",
  2031  			fd:            42, // arbitrary invalid fd
  2032  			expectedErrno: ErrnoBadf,
  2033  			expectedLog: `
  2034  --> proxy.fd_write(fd=42,iovs=0,iovs_len=1,result.size=0)
  2035  	==> wasi_snapshot_preview1.fd_write(fd=42,iovs=0,iovs_len=1,result.size=0)
  2036  	<== EBADF
  2037  <-- (8)
  2038  `,
  2039  		},
  2040  		{
  2041  			name:          "out-of-memory reading iovs[0].offset",
  2042  			fd:            fd,
  2043  			memory:        []byte{},
  2044  			expectedErrno: ErrnoFault,
  2045  			expectedLog: `
  2046  --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2047  	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2048  	<== EFAULT
  2049  <-- (21)
  2050  `,
  2051  		},
  2052  		{
  2053  			name:          "out-of-memory reading iovs[0].length",
  2054  			fd:            fd,
  2055  			memory:        memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()!
  2056  			expectedErrno: ErrnoFault,
  2057  			expectedLog: `
  2058  --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2059  	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2060  	<== EFAULT
  2061  <-- (21)
  2062  `,
  2063  		},
  2064  		{
  2065  			name:          "iovs[0].offset is outside memory",
  2066  			fd:            fd,
  2067  			memory:        memory[0:8], // iovs[0].offset (where to read "hi") is outside memory.
  2068  			expectedErrno: ErrnoFault,
  2069  			expectedLog: `
  2070  --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2071  	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2072  	<== EFAULT
  2073  <-- (21)
  2074  `,
  2075  		},
  2076  		{
  2077  			name:          "length to read exceeds memory by 1",
  2078  			fd:            fd,
  2079  			memory:        memory[0:9], // iovs[0].offset (where to read "hi") is in memory, but truncated.
  2080  			expectedErrno: ErrnoFault,
  2081  			expectedLog: `
  2082  --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2083  	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=0)
  2084  	<== EFAULT
  2085  <-- (21)
  2086  `,
  2087  		},
  2088  		{
  2089  			name:          "resultSize offset is outside memory",
  2090  			fd:            fd,
  2091  			memory:        memory,
  2092  			resultSize:    uint32(len(memory)), // read was ok, but there wasn't enough memory to write the result.
  2093  			expectedErrno: ErrnoFault,
  2094  			expectedLog: `
  2095  --> proxy.fd_write(fd=4,iovs=0,iovs_len=1,result.size=10)
  2096  	==> wasi_snapshot_preview1.fd_write(fd=4,iovs=0,iovs_len=1,result.size=10)
  2097  	<== EFAULT
  2098  <-- (21)
  2099  `,
  2100  		},
  2101  	}
  2102  
  2103  	for _, tt := range tests {
  2104  		tc := tt
  2105  		t.Run(tc.name, func(t *testing.T) {
  2106  			defer log.Reset()
  2107  
  2108  			mod.Memory().(*wasm.MemoryInstance).Buffer = tc.memory
  2109  
  2110  			requireErrno(t, tc.expectedErrno, mod, functionFdWrite, uint64(tc.fd), uint64(iovs), uint64(iovsCount),
  2111  				uint64(tc.resultSize))
  2112  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2113  		})
  2114  	}
  2115  }
  2116  
  2117  // Test_pathCreateDirectory only tests it is stubbed for GrainLang per #271
  2118  func Test_pathCreateDirectory(t *testing.T) {
  2119  	log := requireErrnoNosys(t, functionPathCreateDirectory, 0, 0, 0)
  2120  	require.Equal(t, `
  2121  --> proxy.path_create_directory(fd=0,path=0,path_len=0)
  2122  	--> wasi_snapshot_preview1.path_create_directory(fd=0,path=0,path_len=0)
  2123  	<-- ENOSYS
  2124  <-- (52)
  2125  `, log)
  2126  }
  2127  
  2128  func Test_pathFilestatGet(t *testing.T) {
  2129  	file, dir := "a", "b"
  2130  	testFS := fstest.MapFS{
  2131  		file:             {Data: make([]byte, 10), ModTime: time.Unix(1667482413, 0)},
  2132  		dir:              {Mode: fs.ModeDir, ModTime: time.Unix(1667482413, 0)},
  2133  		dir + "/" + file: {Data: make([]byte, 20), ModTime: time.Unix(1667482413, 0)},
  2134  	}
  2135  
  2136  	initialMemoryFile := append([]byte{'?'}, file...)
  2137  	initialMemoryDir := append([]byte{'?'}, dir...)
  2138  	initialMemoryNotExists := []byte{'?', '?'}
  2139  
  2140  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
  2141  	defer r.Close(testCtx)
  2142  	memorySize := mod.Memory().Size(testCtx)
  2143  
  2144  	// open both paths without using WASI
  2145  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  2146  
  2147  	rootFd := uint32(3) // after stderr
  2148  
  2149  	fileFd, err := fsc.OpenFile(testCtx, file)
  2150  	require.NoError(t, err)
  2151  
  2152  	dirFd, err := fsc.OpenFile(testCtx, dir)
  2153  	require.NoError(t, err)
  2154  
  2155  	tests := []struct {
  2156  		name                        string
  2157  		fd, pathLen, resultFilestat uint32
  2158  		memory, expectedMemory      []byte
  2159  		expectedErrno               Errno
  2160  		expectedLog                 string
  2161  	}{
  2162  		{
  2163  			name:           "file under root",
  2164  			fd:             rootFd,
  2165  			memory:         initialMemoryFile,
  2166  			pathLen:        1,
  2167  			resultFilestat: 2,
  2168  			expectedMemory: append(
  2169  				initialMemoryFile,
  2170  				'?', '?', '?', '?', '?', '?', '?', '?', // dev
  2171  				'?', '?', '?', '?', '?', '?', '?', '?', // ino
  2172  				4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
  2173  				'?', '?', '?', '?', '?', '?', '?', '?', // nlink
  2174  				10, 0, 0, 0, 0, 0, 0, 0, // size
  2175  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
  2176  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
  2177  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
  2178  			),
  2179  			expectedLog: `
  2180  --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2)
  2181  	==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2)
  2182  	<== ESUCCESS
  2183  <-- (0)
  2184  `,
  2185  		},
  2186  		{
  2187  			name:           "file under dir",
  2188  			fd:             dirFd, // root
  2189  			memory:         initialMemoryFile,
  2190  			pathLen:        1,
  2191  			resultFilestat: 2,
  2192  			expectedMemory: append(
  2193  				initialMemoryFile,
  2194  				'?', '?', '?', '?', '?', '?', '?', '?', // dev
  2195  				'?', '?', '?', '?', '?', '?', '?', '?', // ino
  2196  				4, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
  2197  				'?', '?', '?', '?', '?', '?', '?', '?', // nlink
  2198  				20, 0, 0, 0, 0, 0, 0, 0, // size
  2199  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
  2200  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
  2201  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
  2202  			),
  2203  			expectedLog: `
  2204  --> proxy.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2)
  2205  	==> wasi_snapshot_preview1.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2)
  2206  	<== ESUCCESS
  2207  <-- (0)
  2208  `,
  2209  		},
  2210  		{
  2211  			name:           "dir under root",
  2212  			fd:             rootFd,
  2213  			memory:         initialMemoryDir,
  2214  			pathLen:        1,
  2215  			resultFilestat: 2,
  2216  			expectedMemory: append(
  2217  				initialMemoryDir,
  2218  				'?', '?', '?', '?', '?', '?', '?', '?', // dev
  2219  				'?', '?', '?', '?', '?', '?', '?', '?', // ino
  2220  				3, '?', '?', '?', '?', '?', '?', '?', // filetype + padding
  2221  				'?', '?', '?', '?', '?', '?', '?', '?', // nlink
  2222  				0, 0, 0, 0, 0, 0, 0, 0, // size
  2223  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // atim
  2224  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // mtim
  2225  				0x0, 0x82, 0x13, 0x80, 0x6b, 0x16, 0x24, 0x17, // ctim
  2226  			),
  2227  			expectedLog: `
  2228  --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2)
  2229  	==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2)
  2230  	<== ESUCCESS
  2231  <-- (0)
  2232  `,
  2233  		},
  2234  		{
  2235  			name:          "bad FD - not opened",
  2236  			fd:            math.MaxUint32,
  2237  			expectedErrno: ErrnoBadf,
  2238  			expectedLog: `
  2239  --> proxy.path_filestat_get(fd=4294967295,flags=0,path=1,path_len=0,result.buf=0)
  2240  	==> wasi_snapshot_preview1.path_filestat_get(fd=4294967295,flags=0,path=1,path_len=0,result.buf=0)
  2241  	<== EBADF
  2242  <-- (8)
  2243  `,
  2244  		},
  2245  		{
  2246  			name:           "bad FD - not dir",
  2247  			fd:             fileFd,
  2248  			memory:         initialMemoryFile,
  2249  			pathLen:        1,
  2250  			resultFilestat: 2,
  2251  			expectedErrno:  ErrnoNotdir,
  2252  			expectedLog: `
  2253  --> proxy.path_filestat_get(fd=4,flags=0,path=1,path_len=1,result.buf=2)
  2254  	==> wasi_snapshot_preview1.path_filestat_get(fd=4,flags=0,path=1,path_len=1,result.buf=2)
  2255  	<== ENOTDIR
  2256  <-- (54)
  2257  `,
  2258  		},
  2259  		{
  2260  			name:           "path under root doesn't exist",
  2261  			fd:             rootFd,
  2262  			memory:         initialMemoryNotExists,
  2263  			pathLen:        1,
  2264  			resultFilestat: 2,
  2265  			expectedErrno:  ErrnoNoent,
  2266  			expectedLog: `
  2267  --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2)
  2268  	==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=2)
  2269  	<== ENOENT
  2270  <-- (44)
  2271  `,
  2272  		},
  2273  		{
  2274  			name:           "path under dir doesn't exist",
  2275  			fd:             dirFd,
  2276  			memory:         initialMemoryNotExists,
  2277  			pathLen:        1,
  2278  			resultFilestat: 2,
  2279  			expectedErrno:  ErrnoNoent,
  2280  			expectedLog: `
  2281  --> proxy.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2)
  2282  	==> wasi_snapshot_preview1.path_filestat_get(fd=5,flags=0,path=1,path_len=1,result.buf=2)
  2283  	<== ENOENT
  2284  <-- (44)
  2285  `,
  2286  		},
  2287  		{
  2288  			name:           "path invalid",
  2289  			fd:             dirFd,
  2290  			memory:         []byte("?../foo"),
  2291  			pathLen:        6,
  2292  			resultFilestat: 7,
  2293  			expectedErrno:  ErrnoNoent,
  2294  			expectedLog: `
  2295  --> proxy.path_filestat_get(fd=5,flags=0,path=1,path_len=6,result.buf=7)
  2296  	==> wasi_snapshot_preview1.path_filestat_get(fd=5,flags=0,path=1,path_len=6,result.buf=7)
  2297  	<== ENOENT
  2298  <-- (44)
  2299  `,
  2300  		},
  2301  		{
  2302  			name:          "path is out of memory",
  2303  			fd:            rootFd,
  2304  			memory:        initialMemoryFile,
  2305  			pathLen:       memorySize,
  2306  			expectedErrno: ErrnoNametoolong,
  2307  			expectedLog: `
  2308  --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=65536,result.buf=0)
  2309  	==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=65536,result.buf=0)
  2310  	<== ENAMETOOLONG
  2311  <-- (37)
  2312  `,
  2313  		},
  2314  		{
  2315  			name:           "resultFilestat exceeds the maximum valid address by 1",
  2316  			fd:             rootFd,
  2317  			memory:         initialMemoryFile,
  2318  			pathLen:        1,
  2319  			resultFilestat: memorySize - 64 + 1,
  2320  			expectedErrno:  ErrnoFault,
  2321  			expectedLog: `
  2322  --> proxy.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=65473)
  2323  	==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=0,path=1,path_len=1,result.buf=65473)
  2324  	<== EFAULT
  2325  <-- (21)
  2326  `,
  2327  		},
  2328  	}
  2329  
  2330  	for _, tt := range tests {
  2331  		tc := tt
  2332  
  2333  		t.Run(tc.name, func(t *testing.T) {
  2334  			defer log.Reset()
  2335  
  2336  			maskMemory(t, testCtx, mod, len(tc.expectedMemory))
  2337  			mod.Memory().Write(testCtx, 0, tc.memory)
  2338  
  2339  			requireErrno(t, tc.expectedErrno, mod, functionPathFilestatGet, uint64(tc.fd), uint64(0), uint64(1), uint64(tc.pathLen), uint64(tc.resultFilestat))
  2340  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2341  
  2342  			actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
  2343  			require.True(t, ok)
  2344  			require.Equal(t, tc.expectedMemory, actual)
  2345  		})
  2346  	}
  2347  }
  2348  
  2349  // Test_pathFilestatSetTimes only tests it is stubbed for GrainLang per #271
  2350  func Test_pathFilestatSetTimes(t *testing.T) {
  2351  	log := requireErrnoNosys(t, functionPathFilestatSetTimes, 0, 0, 0, 0, 0, 0, 0)
  2352  	require.Equal(t, `
  2353  --> proxy.path_filestat_set_times(fd=0,flags=0,path=0,path_len=0,atim=0,mtim=0,fst_flags=0)
  2354  	--> wasi_snapshot_preview1.path_filestat_set_times(fd=0,flags=0,path=0,path_len=0,atim=0,mtim=0,fst_flags=0)
  2355  	<-- ENOSYS
  2356  <-- (52)
  2357  `, log)
  2358  }
  2359  
  2360  // Test_pathLink only tests it is stubbed for GrainLang per #271
  2361  func Test_pathLink(t *testing.T) {
  2362  	log := requireErrnoNosys(t, functionPathLink, 0, 0, 0, 0, 0, 0, 0)
  2363  	require.Equal(t, `
  2364  --> proxy.path_link(old_fd=0,old_flags=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0)
  2365  	--> wasi_snapshot_preview1.path_link(old_fd=0,old_flags=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0)
  2366  	<-- ENOSYS
  2367  <-- (52)
  2368  `, log)
  2369  }
  2370  
  2371  func Test_pathOpen(t *testing.T) {
  2372  	rootFD := uint32(3) // after 0, 1, and 2, that are stdin/out/err
  2373  	expectedFD := rootFD + 1
  2374  	// set up the initial memory to include the path name starting at an offset.
  2375  	pathName := "wazero"
  2376  	initialMemory := append([]byte{'?'}, pathName...)
  2377  
  2378  	expectedMemory := append(
  2379  		initialMemory,
  2380  		'?', // `resultOpenedFd` is after this
  2381  		byte(expectedFD), 0, 0, 0,
  2382  		'?',
  2383  	)
  2384  
  2385  	dirflags := uint32(0)
  2386  	path := uint32(1)
  2387  	pathLen := uint32(len(pathName))
  2388  	oflags := uint32(0)
  2389  	fsRightsBase := uint64(1)       // ignored: rights were removed from WASI.
  2390  	fsRightsInheriting := uint64(2) // ignored: rights were removed from WASI.
  2391  	fdflags := uint32(0)
  2392  	resultOpenedFd := uint32(len(initialMemory) + 1)
  2393  
  2394  	testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}}
  2395  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
  2396  	defer r.Close(testCtx)
  2397  
  2398  	maskMemory(t, testCtx, mod, len(expectedMemory))
  2399  	ok := mod.Memory().Write(testCtx, 0, initialMemory)
  2400  	require.True(t, ok)
  2401  
  2402  	requireErrno(t, ErrnoSuccess, mod, functionPathOpen, uint64(rootFD), uint64(dirflags), uint64(path),
  2403  		uint64(pathLen), uint64(oflags), fsRightsBase, fsRightsInheriting, uint64(fdflags), uint64(resultOpenedFd))
  2404  	require.Equal(t, `
  2405  --> proxy.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8)
  2406  	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=1,path_len=6,oflags=0,fs_rights_base=1,fs_rights_inheriting=2,fdflags=0,result.opened_fd=8)
  2407  	<== ESUCCESS
  2408  <-- (0)
  2409  `, "\n"+log.String())
  2410  
  2411  	actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
  2412  	require.True(t, ok)
  2413  	require.Equal(t, expectedMemory, actual)
  2414  
  2415  	// verify the file was actually opened
  2416  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  2417  	f, ok := fsc.OpenedFile(testCtx, expectedFD)
  2418  	require.True(t, ok)
  2419  	require.Equal(t, pathName, f.Path)
  2420  }
  2421  
  2422  func Test_pathOpen_Errors(t *testing.T) {
  2423  	validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
  2424  	pathName := "wazero"
  2425  	testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}}
  2426  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
  2427  	defer r.Close(testCtx)
  2428  
  2429  	validPath := uint32(0)    // arbitrary offset
  2430  	validPathLen := uint32(6) // the length of "wazero"
  2431  
  2432  	tests := []struct {
  2433  		name, pathName                            string
  2434  		fd, path, pathLen, oflags, resultOpenedFd uint32
  2435  		expectedErrno                             Errno
  2436  		expectedLog                               string
  2437  	}{
  2438  		{
  2439  			name:          "invalid fd",
  2440  			fd:            42, // arbitrary invalid fd
  2441  			expectedErrno: ErrnoBadf,
  2442  			expectedLog: `
  2443  --> proxy.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2444  	==> wasi_snapshot_preview1.path_open(fd=42,dirflags=0,path=0,path_len=0,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2445  	<== EBADF
  2446  <-- (8)
  2447  `,
  2448  		},
  2449  		{
  2450  			name:          "out-of-memory reading path",
  2451  			fd:            validFD,
  2452  			path:          mod.Memory().Size(testCtx),
  2453  			pathLen:       validPathLen,
  2454  			expectedErrno: ErrnoFault,
  2455  			expectedLog: `
  2456  --> proxy.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2457  	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=65536,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2458  	<== EFAULT
  2459  <-- (21)
  2460  `,
  2461  		},
  2462  		{
  2463  			name:     "path invalid",
  2464  			fd:       validFD,
  2465  			pathName: "../foo",
  2466  			pathLen:  6,
  2467  			// fstest.MapFS returns file not found instead of invalid on invalid path
  2468  			expectedErrno: ErrnoNoent,
  2469  			expectedLog: `
  2470  --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2471  	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2472  	<== ENOENT
  2473  <-- (44)
  2474  `,
  2475  		},
  2476  		{
  2477  			name:          "out-of-memory reading pathLen",
  2478  			fd:            validFD,
  2479  			path:          validPath,
  2480  			pathLen:       mod.Memory().Size(testCtx) + 1, // path is in the valid memory range, but pathLen is out-of-memory for path
  2481  			expectedErrno: ErrnoFault,
  2482  			expectedLog: `
  2483  --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2484  	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=65537,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2485  	<== EFAULT
  2486  <-- (21)
  2487  `,
  2488  		},
  2489  		{
  2490  			name:          "no such file exists",
  2491  			fd:            validFD,
  2492  			pathName:      pathName,
  2493  			path:          validPath,
  2494  			pathLen:       validPathLen - 1, // this make the path "wazer", which doesn't exit
  2495  			expectedErrno: ErrnoNoent,
  2496  			expectedLog: `
  2497  --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2498  	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=5,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=0)
  2499  	<== ENOENT
  2500  <-- (44)
  2501  `,
  2502  		},
  2503  		{
  2504  			name:           "out-of-memory writing resultOpenedFd",
  2505  			fd:             validFD,
  2506  			pathName:       pathName,
  2507  			path:           validPath,
  2508  			pathLen:        validPathLen,
  2509  			resultOpenedFd: mod.Memory().Size(testCtx), // path and pathLen correctly point to the right path, but where to write the opened FD is outside memory.
  2510  			expectedErrno:  ErrnoFault,
  2511  			expectedLog: `
  2512  --> proxy.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536)
  2513  	==> wasi_snapshot_preview1.path_open(fd=3,dirflags=0,path=0,path_len=6,oflags=0,fs_rights_base=0,fs_rights_inheriting=0,fdflags=0,result.opened_fd=65536)
  2514  	<== EFAULT
  2515  <-- (21)
  2516  `,
  2517  		},
  2518  	}
  2519  
  2520  	for _, tt := range tests {
  2521  		tc := tt
  2522  		t.Run(tc.name, func(t *testing.T) {
  2523  			defer log.Reset()
  2524  
  2525  			mod.Memory().Write(testCtx, validPath, []byte(tc.pathName))
  2526  
  2527  			requireErrno(t, tc.expectedErrno, mod, functionPathOpen, uint64(tc.fd), uint64(0), uint64(tc.path),
  2528  				uint64(tc.pathLen), uint64(tc.oflags), 0, 0, 0, uint64(tc.resultOpenedFd))
  2529  			require.Equal(t, tc.expectedLog, "\n"+log.String())
  2530  		})
  2531  	}
  2532  }
  2533  
  2534  // Test_pathReadlink only tests it is stubbed for GrainLang per #271
  2535  func Test_pathReadlink(t *testing.T) {
  2536  	log := requireErrnoNosys(t, functionPathReadlink, 0, 0, 0, 0, 0, 0)
  2537  	require.Equal(t, `
  2538  --> proxy.path_readlink(fd=0,path=0,path_len=0,buf=0,buf_len=0,result.bufused=0)
  2539  	--> wasi_snapshot_preview1.path_readlink(fd=0,path=0,path_len=0,buf=0,buf_len=0,result.bufused=0)
  2540  	<-- ENOSYS
  2541  <-- (52)
  2542  `, log)
  2543  }
  2544  
  2545  // Test_pathRemoveDirectory only tests it is stubbed for GrainLang per #271
  2546  func Test_pathRemoveDirectory(t *testing.T) {
  2547  	log := requireErrnoNosys(t, functionPathRemoveDirectory, 0, 0, 0)
  2548  	require.Equal(t, `
  2549  --> proxy.path_remove_directory(fd=0,path=0,path_len=0)
  2550  	--> wasi_snapshot_preview1.path_remove_directory(fd=0,path=0,path_len=0)
  2551  	<-- ENOSYS
  2552  <-- (52)
  2553  `, log)
  2554  }
  2555  
  2556  // Test_pathRename only tests it is stubbed for GrainLang per #271
  2557  func Test_pathRename(t *testing.T) {
  2558  	log := requireErrnoNosys(t, functionPathRename, 0, 0, 0, 0, 0, 0)
  2559  	require.Equal(t, `
  2560  --> proxy.path_rename(fd=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0)
  2561  	--> wasi_snapshot_preview1.path_rename(fd=0,old_path=0,old_path_len=0,new_fd=0,new_path=0,new_path_len=0)
  2562  	<-- ENOSYS
  2563  <-- (52)
  2564  `, log)
  2565  }
  2566  
  2567  // Test_pathSymlink only tests it is stubbed for GrainLang per #271
  2568  func Test_pathSymlink(t *testing.T) {
  2569  	log := requireErrnoNosys(t, functionPathSymlink, 0, 0, 0, 0, 0)
  2570  	require.Equal(t, `
  2571  --> proxy.path_symlink(old_path=0,old_path_len=0,fd=0,new_path=0,new_path_len=0)
  2572  	--> wasi_snapshot_preview1.path_symlink(old_path=0,old_path_len=0,fd=0,new_path=0,new_path_len=0)
  2573  	<-- ENOSYS
  2574  <-- (52)
  2575  `, log)
  2576  }
  2577  
  2578  // Test_pathUnlinkFile only tests it is stubbed for GrainLang per #271
  2579  func Test_pathUnlinkFile(t *testing.T) {
  2580  	log := requireErrnoNosys(t, functionPathUnlinkFile, 0, 0, 0)
  2581  	require.Equal(t, `
  2582  --> proxy.path_unlink_file(fd=0,path=0,path_len=0)
  2583  	--> wasi_snapshot_preview1.path_unlink_file(fd=0,path=0,path_len=0)
  2584  	<-- ENOSYS
  2585  <-- (52)
  2586  `, log)
  2587  }
  2588  
  2589  func requireOpenFile(t *testing.T, pathName string, data []byte) (api.Module, uint32, *bytes.Buffer, api.Closer) {
  2590  	mapFile := &fstest.MapFile{Data: data}
  2591  	if data == nil {
  2592  		mapFile.Mode = os.ModeDir
  2593  	}
  2594  	testFS := fstest.MapFS{pathName[1:]: mapFile} // strip the leading slash
  2595  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
  2596  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  2597  	fd, err := fsc.OpenFile(testCtx, pathName)
  2598  	require.NoError(t, err)
  2599  	return mod, fd, log, r
  2600  }
  2601  
  2602  // requireOpenWritableFile is temporary until we add the ability to open files for writing.
  2603  func requireOpenWritableFile(t *testing.T, tmpDir string, pathName string) (api.Module, uint32, *bytes.Buffer, api.Closer) {
  2604  	writeable, testFS := createWriteableFile(t, tmpDir, pathName, []byte{})
  2605  	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(testFS))
  2606  	fsc := mod.(*wasm.CallContext).Sys.FS(testCtx)
  2607  	fd, err := fsc.OpenFile(testCtx, pathName)
  2608  	require.NoError(t, err)
  2609  
  2610  	// Swap the read-only file with a writeable one until #390
  2611  	f, ok := fsc.OpenedFile(testCtx, fd)
  2612  	require.True(t, ok)
  2613  	f.File.Close()
  2614  	f.File = writeable
  2615  
  2616  	return mod, fd, log, r
  2617  }
  2618  
  2619  // createWriteableFile uses real files when io.Writer tests are needed.
  2620  func createWriteableFile(t *testing.T, tmpDir string, pathName string, data []byte) (fs.File, fs.FS) {
  2621  	require.NotNil(t, data)
  2622  	absolutePath := path.Join(tmpDir, pathName)
  2623  	require.NoError(t, os.WriteFile(absolutePath, data, 0o600))
  2624  
  2625  	// open the file for writing in a custom way until #390
  2626  	f, err := os.OpenFile(absolutePath, os.O_RDWR, 0o600)
  2627  	require.NoError(t, err)
  2628  	return f, os.DirFS(tmpDir)
  2629  }