diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go index d39be2952a..4625a8d74b 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. // diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go index 91c6a3061d..e27fde308d 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) {