Repository: tetratelabs/wazero
Pull request #1102 (2023-02-06 07:58:17)

wasi: implements fd_allocate
part of #1036. With this only one case left:
> Test suites: 1 failed, 0 total
Tests:       1 failed, 9 passed, 1 skipped, 1 total



Patch:
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index d39be295..4625a8d7 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -67,12 +67,55 @@ func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // allocation of space in a file.
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno
-var fdAllocate = stubFunction(
-	FdAllocateName,
+var fdAllocate = newHostFunc(
+	FdAllocateName, fdAllocateFn,
 	[]wasm.ValueType{i32, i64, i64},
 	"fd", "offset", "len",
 )
 
+func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) Errno {
+	fd := uint32(params[0])
+	offset := params[1]
+	length := params[2]
+
+	fsc := mod.(*wasm.CallContext).Sys.FS()
+	f, ok := fsc.LookupFile(fd)
+	if !ok {
+		return ErrnoBadf
+	}
+
+	tail := int64(offset + length)
+	if tail < 0 {
+		return ErrnoInval
+	}
+
+	st, err := f.Stat()
+	if err != nil {
+		return ToErrno(err)
+	}
+
+	if st.Size() >= tail {
+		// We already have enough space.
+		return ErrnoSuccess
+	}
+
+	// This is implemented the implementation of fs.File by all platforms.
+	// TODO: this should be removed once we have fs.File.
+	type truncatable interface {
+		Truncate(size int64) error
+	}
+
+	osf, ok := f.File.(truncatable)
+	if !ok {
+		return ErrnoBadf
+	}
+
+	if err = osf.Truncate(tail); err != nil {
+		return ToErrno(err)
+	}
+	return ErrnoSuccess
+}
+
 // fdClose is the WASI function named FdCloseName which closes a file
 // descriptor.
 //


Test patch:
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index 91c6a306..e27fde30 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -39,11 +39,86 @@ func Test_fdAdvise(t *testing.T) {
 
 // Test_fdAllocate only tests it is stubbed for GrainLang per #271
 func Test_fdAllocate(t *testing.T) {
-	log := requireErrnoNosys(t, FdAllocateName, 0, 0, 0)
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	const fileName = "file.txt"
+
+	// Create the target file.
+	realPath := path.Join(tmpDir, fileName)
+	require.NoError(t, os.WriteFile(realPath, []byte("0123456789"), 0o600))
+
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFSConfig(
+		wazero.NewFSConfig().WithDirMount(tmpDir, "/"),
+	))
+	fsc := mod.(*wasm.CallContext).Sys.FS()
+	preopen := fsc.RootFS()
+	defer r.Close(testCtx)
+
+	fd, err := fsc.OpenFile(preopen, fileName, os.O_RDWR, 0)
+	require.NoError(t, err)
+
+	f, ok := fsc.LookupFile(fd)
+	require.True(t, ok)
+
+	requireSizeEqual := func(exp int64) {
+		st, err := f.Stat()
+		require.NoError(t, err)
+		require.Equal(t, exp, st.Size())
+	}
+
+	t.Run("errors", func(t *testing.T) {
+		requireErrno(t, ErrnoBadf, mod, FdAllocateName, uint64(12345), 0, 0)
+		minusOne := int64(-1)
+		requireErrno(t, ErrnoInval, mod, FdAllocateName, uint64(fd), uint64(minusOne), uint64(minusOne))
+		requireErrno(t, ErrnoInval, mod, FdAllocateName, uint64(fd), 0, uint64(minusOne))
+		requireErrno(t, ErrnoInval, mod, FdAllocateName, uint64(fd), uint64(minusOne), 0)
+	})
+
+	t.Run("do not change size", func(t *testing.T) {
+		for _, tc := range []struct{ offset, length uint64 }{
+			{offset: 0, length: 10},
+			{offset: 5, length: 5},
+			{offset: 4, length: 0},
+			{offset: 10, length: 0},
+		} {
+			// This shouldn't change the size.
+			requireErrno(t, ErrnoSuccess, mod, FdAllocateName,
+				uint64(fd), tc.offset, tc.length)
+			requireSizeEqual(10)
+		}
+	})
+
+	t.Run("increase", func(t *testing.T) {
+		// 10 + 10 > the current size -> increase the size.
+		requireErrno(t, ErrnoSuccess, mod, FdAllocateName,
+			uint64(fd), 10, 10)
+		requireSizeEqual(20)
+
+		// But the original content must be kept.
+		buf, err := os.ReadFile(realPath)
+		require.NoError(t, err)
+		require.Equal(t, "0123456789", string(buf[:10]))
+	})
+
 	require.Equal(t, `
---> wasi_snapshot_preview1.fd_allocate(fd=0,offset=0,len=0)
-<-- errno=ENOSYS
-`, log)
+==> wasi_snapshot_preview1.fd_allocate(fd=12345,offset=0,len=0)
+<== errno=EBADF
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=-1,len=-1)
+<== errno=EINVAL
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=0,len=-1)
+<== errno=EINVAL
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=-1,len=0)
+<== errno=EINVAL
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=0,len=10)
+<== errno=ESUCCESS
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=5,len=5)
+<== errno=ESUCCESS
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=4,len=0)
+<== errno=ESUCCESS
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=10,len=0)
+<== errno=ESUCCESS
+==> wasi_snapshot_preview1.fd_allocate(fd=4,offset=10,len=10)
+<== errno=ESUCCESS
+`, "\n"+log.String())
 }
 
 func Test_fdClose(t *testing.T) {