Repository: tetratelabs/wazero
Pull request #976 (2022-12-29 09:53:58)

wasi: implements path_(create|remove)_directory path_unlink_file
This implements path_(create|remove)_directory path_unlink_file in wasi, particularly needed to use TinyGo tests to verify our interpretation of WASI. Use of this requires the experimental `writefs.DirFS`.

After installing via `go install ./cmd/wazero/...`, tinygo os package tests pass except those requiring rename which isn't implemented yet.

```bash
$ ./build/tinygo test -target wasi -c -o os.wasm os
$ mkdir tmp
$ wazero run -hostlogging=filesystem -mount=.:/ os.wasm -test.v
==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
<== (prestat={pr_name_len=1},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
<== (path=/,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_prestat_get(fd=4)
<== (prestat=,errno=EBADF)
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/_os_test_TestTempDir)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/_os_test_TestTempDir)
<== errno=ENOENT
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/_os_test_TestTempDir,oflags=CREAT,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=4,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=4)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/_os_test_TestTempDir)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=_os_test_TestChDir)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=_os_test_TestChDir)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_create_directory(fd=3,path=_os_test_TestChDir)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=_os_test_TestChDir)
<== (filestat={filetype=DIRECTORY,size=64,mtim=1672307357179596719},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=_os_test_TestChDir/_os_test_TestTempDir.dat,oflags=CREAT,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=6,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=6)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=.)
<== (filestat={filetype=DIRECTORY,size=1664,mtim=1672307357179606402},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=_os_test_TestChDir/_os_test_TestTempDir.dat)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=_os_test_TestChDir)
<== errno=EISDIR
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=_os_test_TestChDir)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=_os_test_TestTempDir.dat)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=_os_test_TestTempDir.dat)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=_os_test_TestChDir)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=_os_test_TestChDir)
<== errno=ENOENT
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestFd.txt194962073,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=7,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=7,iovs=170104,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_pread(fd=7,iovs=170136,iovs_len=1,offset=0)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=7)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_close(fd=7)
<== errno=EBADF
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestFd.txt194962073)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_create_directory(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=EISDIR
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/TestMkdir18446744073529124615)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/not-exist/really-not-exist)
<== (filestat=,errno=ENOENT)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestFstat,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=9,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=9,iovs=169832,iovs_len=1)
<== (nwritten=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=9)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestFstat,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=10,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_filestat_get(fd=10)
<== (filestat={filetype=REGULAR_FILE,size=5,mtim=1672307357180853154},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=10,iovs=169704,iovs_len=1)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=10,iovs=169704,iovs_len=1)
<== (nread=0,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=10)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestFstat,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=11,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_filestat_get(fd=11)
<== (filestat={filetype=REGULAR_FILE,size=5,mtim=1672307357180853154},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=11)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestFstat)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRemove18446744073527595615)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/TestRemove18446744073527595615)
<== errno=ENOENT
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRemove18446744073527595615,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=12,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=12,iovs=169912,iovs_len=1)
<== (nwritten=3,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=12)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRemove18446744073527595615,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_filestat_get(fd=13)
<== (filestat={filetype=REGULAR_FILE,size=3,mtim=1672307357182098605},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=13,iovs=169784,iovs_len=1)
<== (nread=3,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=13,iovs=169784,iovs_len=1)
<== (nread=0,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=13)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRemove18446744073527595615)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRename-from,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=14,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=14)
<== errno=ESUCCESS
--> wasi_snapshot_preview1.path_rename(fd=3,old_path=tmp/TestRename-from,new_fd=3,new_path=tmp/TestRename-to)
<-- errno=ENOSYS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/TestRename-to)
<== (filestat=,errno=ENOENT)
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRename-to)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/TestRename-to)
<== errno=ENOENT
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRename-from)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRenameOverwrite-to,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=15,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=15,iovs=169864,iovs_len=1)
<== (nwritten=2,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=15)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRenameOverwrite-from,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=16,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=16,iovs=169864,iovs_len=1)
<== (nwritten=4,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=16)
<== errno=ESUCCESS
--> wasi_snapshot_preview1.path_rename(fd=3,old_path=tmp/TestRenameOverwrite-from,new_fd=3,new_path=tmp/TestRenameOverwrite-to)
<-- errno=ENOSYS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/TestRenameOverwrite-from)
<== (filestat={filetype=REGULAR_FILE,size=4,mtim=1672307357184009603},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/TestRenameOverwrite-to)
<== (filestat={filetype=REGULAR_FILE,size=2,mtim=1672307357183844075},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRenameOverwrite-from)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRenameOverwrite-to)
<== errno=ESUCCESS
--> wasi_snapshot_preview1.path_rename(fd=3,old_path=tmp/RenameFailed-from,new_fd=3,new_path=tmp/RenameFailed-to)
<-- errno=ENOSYS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRead0204048555,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=17,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=17,iovs=170040,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=17)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestRead0204048555,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=18,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=18,iovs=170088,iovs_len=1)
<== (nread=0,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=18,iovs=170088,iovs_len=1)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=17)
<== errno=EBADF
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestRead0204048555)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAt0194055903,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=19,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=19,iovs=170088,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_pread(fd=19,iovs=170120,iovs_len=1,offset=0)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=19)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestReadAt0194055903)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestSeek217295813,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=20,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=20,iovs=169976,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=0,whence=1,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=0,whence=0,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=5,whence=0,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=0,whence=2,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=0,whence=0,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=-1,whence=2,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=8589934592,whence=0,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=8589934592,whence=2,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=4294967295,whence=0,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=0,whence=1,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=8589934591,whence=0,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_seek(fd=20,offset=0,whence=1,result.newoffset=170072)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_close(fd=20)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestSeek217295813)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAt239190629,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=21,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=21,iovs=170072,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_pread(fd=21,iovs=170104,iovs_len=1,offset=7)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=21)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestReadAt239190629)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAtOffset25058207,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=22,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=22,iovs=169944,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=22)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAtOffset25058207,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=23,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_pread(fd=23,iovs=169976,iovs_len=1,offset=7)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_read(fd=23,iovs=169992,iovs_len=1)
<== (nread=5,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=22)
<== errno=EBADF
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestReadAtOffset25058207)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAtNegativeOffset14554411,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=24,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=24,iovs=170056,iovs_len=1)
<== (nwritten=13,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=24)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAtNegativeOffset14554411,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=25,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=24)
<== errno=EBADF
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestReadAtNegativeOffset14554411)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/TestReadAtEOF60424249,oflags=CREAT|EXCL,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=26,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_pread(fd=26,iovs=170184,iovs_len=1,offset=0)
<== (nread=0,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=26)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/TestReadAtEOF60424249)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/./dir2)
<== (filestat=,errno=ENOENT)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/.)
<== (filestat={filetype=DIRECTORY,size=64,mtim=1672303976638408952},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_create_directory(fd=3,path=tmp/_TestMkdirAll_/dir/./dir2)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/./dir2)
<== (filestat={filetype=DIRECTORY,size=64,mtim=1672307357189467689},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/./dir2/file,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=28,errno=ESUCCESS)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/./dir2/file)
<== (filestat={filetype=REGULAR_FILE,size=0,mtim=1672307357189587248},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/./dir2/file/subdir)
<== (filestat=,errno=ENOTDIR)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/_TestMkdirAll_/dir/./dir2/file)
<== (filestat={filetype=REGULAR_FILE,size=0,mtim=1672307357189587248},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=28)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/_TestMkdirAll_/dir/./dir2/file)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/_TestMkdirAll_)
<== errno=EISDIR
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/_TestMkdirAll_)
<== errno=ENOTEMPTY
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/_TestMkdirAll_/dir)
<== errno=EISDIR
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/_TestMkdirAll_/dir)
<== errno=ENOTEMPTY
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/_TestMkdirAll_/dir/dir2)
<== errno=EISDIR
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/_TestMkdirAll_/dir/dir2)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=tmp/dir)
<== errno=EISDIR
==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=tmp/dir)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_create_directory(fd=3,path=tmp/dir)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/dir)
<== (filestat={filetype=DIRECTORY,size=64,mtim=1672307357190058546},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=tmp/dir)
<== (filestat={filetype=DIRECTORY,size=64,mtim=1672307357190058546},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=APPEND,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=tmp/file,oflags=CREAT|TRUNC,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=30,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=30,iovs=170168,iovs_len=1)
<== (nwritten=7,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_close(fd=30)
<== errno=ESUCCESS
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=SYMLINK_FOLLOW,path=tmp/file)
<== (filestat={filetype=REGULAR_FILE,size=7,mtim=1672307357190955127},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_filestat_get(fd=3,flags=,path=tmp/file)
<== (filestat={filetype=REGULAR_FILE,size=7,mtim=1672307357190955127},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=1,iovs=170568,iovs_len=1)
=== RUN   TestExpand
--- PASS: TestExpand (0.00s)
=== RUN   TestConsistentEnviron
--- PASS: TestConsistentEnviron (0.00s)
=== RUN   TestUnsetenv
--- PASS: TestUnsetenv (0.00s)
=== RUN   TestClearenv
--- PASS: TestClearenv (0.00s)
=== RUN   TestLookupEnv
--- PASS: TestLookupEnv (0.00s)
=== RUN   TestEnvironConsistency
--- PASS: TestEnvironConsistency (0.00s)
=== RUN   TestSetenvUnixEinval
--- PASS: TestSetenvUnixEinval (0.00s)
=== RUN   TestExpandEnvShellSpecialVar
--- PASS: TestExpandEnvShellSpecialVar (0.00s)
=== RUN   TestTempDir
--- PASS: TestTempDir (0.00s)
=== RUN   TestChdir
--- PASS: TestChdir (0.00s)
=== RUN   TestStandardFd
--- PASS: TestStandardFd (0.00s)
=== RUN   TestFd
--- PASS: TestFd (0.00s)
=== RUN   TestGetpagesize
--- PASS: TestGetpagesize (0.00s)
=== RUN   TestMkdir
--- PASS: TestMkdir (0.00s)
=== RUN   TestStatBadDir
--- PASS: TestStatBadDir (0.00s)
=== RUN   TestFstat
--- PASS: TestFstat (0.00s)
=== RUN   TestRemove
--- PASS: TestRemove (0.00s)
=== RUN   TestRename
--- FAIL: TestRename (0.00s)
    rename "/tmp/TestRename-to", "/tmp/TestRename-from" failed: rename /tmp/TestRename-from /tmp/TestRename-to: errno 52
    FailNow is incomplete, requires runtime.Goexit()
    stat "/tmp/TestRename-to" failed: stat /tmp/TestRename-to: file does not exist
=== RUN   TestRenameOverwriteDest
--- FAIL: TestRenameOverwriteDest (0.00s)
    rename "/tmp/TestRenameOverwrite-to", "/tmp/TestRenameOverwrite-from" failed: rename /tmp/TestRenameOverwrite-from /tmp/TestRenameOverwrite-to: errno 52
    FailNow is incomplete, requires runtime.Goexit()
    from file "/tmp/TestRenameOverwrite-from" still exists
    "to" size = 2; want 4 (old "from" size)
=== RUN   TestRenameFailed
--- PASS: TestRenameFailed (0.00s)
=== RUN   TestUserHomeDir
--- PASS: TestUserHomeDir (0.00s)
    UserHomeDir failed: $HOME is not defined
=== RUN   TestDirFS
--- PASS: TestDirFS (0.00s)
    TODO: allow foo/bar/. as synonym for path foo/bar on wasi?
=== RUN   TestDirFSPathsValid
--- PASS: TestDirFSPathsValid (0.00s)
    skipping on wasi because it fails on wasi on windows
=== RUN   TestRead0
--- PASS: TestRead0 (0.00s)
=== RUN   TestReadAt0
--- PASS: TestReadAt0 (0.00s)
=== RUN   TestSeek
--- PASS: TestSeek (0.00s)
=== RUN   TestReadAt
--- PASS: TestReadAt (0.00s)
=== RUN   TestReadAtOffset
--- PASS: TestReadAtOffset (0.00s)
=== RUN   TestReadAtNegativeOffset
--- PASS: TestReadAtNegativeOffset (0.00s)
=== RUN   TestReadAtEOF
--- PASS: TestReadAtEOF (0.00s)
=== RUN   TestMkdirAll
--- PASS: TestMkdirAll (0.00s)
=== RUN   TestDirAndSymlinkStats
--- PASS: TestDirAndSymlinkStats (0.00s)
=== RUN   TestFileAndSymlinkStats
--- PASS: TestFileAndSymlinkStats (0.00s)
<== (nwritten=2675,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_write(fd=1,iovs=170808,iovs_len=1)
FAIL
<== (nwritten=5,errno=ESUCCESS)
```

Signed-off-by: Adrian Cole <adrian@tetrate.io>

codefromthecrypt: will address unit test TODOs after dinner
codefromthecrypt: added tests
codefromthecrypt: another windows glitch. I'll squash it tomorrow

```
--- FAIL: Test_pathRemoveDirectory_Errors (0.01s)
[26](https://github.com/tetratelabs/wazero/actions/runs/3800321232/jobs/6463684977#step:4:27)
    --- FAIL: Test_pathRemoveDirectory_Errors/file_not_dir (0.00s)
[27](https://github.com/tetratelabs/wazero/actions/runs/3800321232/jobs/6463684977#step:4:28)
        require.go:312: expected 54, but was 44: ENOENT
[28](https://github.com/tetratelabs/wazero/actions/runs/3800321232/jobs/6463684977#step:4:29)
            D:/a/wazero/wazero/imports/wasi_snapshot_preview1/wasi_test.go:145
[29](https://github.com/tetratelabs/wazero/actions/runs/3800321232/jobs/6463684977#step:4:30)
            D:/a/wazero/wazero/imports/wasi_snapshot_preview1/fs_test.go:2551
```
codefromthecrypt: will follow-up this PR with tests around open flags, ideally to squash the other WASI error raised when running tinygo tests.

Patch:
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index e6abd141..66bf697b 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -1030,7 +1030,7 @@ var fdWrite = newHostFunc(
 	"fd", "iovs", "iovs_len", "result.nwritten",
 )
 
-func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func fdWriteFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
@@ -1078,16 +1078,54 @@ func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	return ErrnoSuccess
 }
 
-// pathCreateDirectory is the WASI function named PathCreateDirectoryName
-// which creates a directory.
+// pathCreateDirectory is the WASI function named PathCreateDirectoryName which
+// creates a directory.
+//
+// # Parameters
+//
+//   - fd: file descriptor of a directory that `path` is relative to
+//   - path: offset in api.Memory to read the path string from
+//   - pathLen: length of `path`
+//
+// # Result (Errno)
+//
+// The return value is ErrnoSuccess except the following error conditions:
+//   - ErrnoBadf: `fd` is invalid
+//   - ErrnoNoent: `path` does not exist.
+//   - ErrnoNotdir: `path` is a file
+//
+// # Notes
+//   - This is similar to mkdirat in POSIX.
+//     See https://linux.die.net/man/2/mkdirat
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno
-var pathCreateDirectory = stubFunction(
-	PathCreateDirectoryName,
+var pathCreateDirectory = newHostFunc(
+	PathCreateDirectoryName, pathCreateDirectoryFn,
 	[]wasm.ValueType{i32, i32, i32},
 	"fd", "path", "path_len",
 )
 
+func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) Errno {
+	fsc := mod.(*wasm.CallContext).Sys.FS()
+
+	dirfd := uint32(params[0])
+	path := uint32(params[1])
+	pathLen := uint32(params[2])
+
+	pathName, errno := atPath(fsc, mod.Memory(), dirfd, path, pathLen)
+	if errno != ErrnoSuccess {
+		return errno
+	}
+
+	if fd, err := fsc.Mkdir(pathName, 0o700); err != nil {
+		return ToErrno(err)
+	} else {
+		_ = fsc.CloseFile(fd)
+	}
+
+	return ErrnoSuccess
+}
+
 // pathFilestatGet is the WASI function named PathFilestatGetName which
 // returns the stat attributes of a file or directory.
 //
@@ -1272,29 +1310,13 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	fdflags := uint16(params[7])
 	resultOpenedFd := uint32(params[8])
 
-	// Note: We don't handle AT_FDCWD, as that's resolved in the compiler.
-	// There's no working directory function in WASI, so CWD cannot be handled
-	// here in any way except assuming it is "/".
-	//
-	// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c#L24-L26
-	if _, ok := fsc.OpenedFile(dirfd); !ok {
-		return ErrnoBadf
-	}
-
-	b, ok := mod.Memory().Read(path, pathLen)
-	if !ok {
-		return ErrnoFault
+	pathName, errno := atPath(fsc, mod.Memory(), dirfd, path, pathLen)
+	if errno != ErrnoSuccess {
+		return errno
 	}
 
 	fileOpenFlags, isDir := openFlags(oflags, fdflags)
 
-	// TODO: path is not precise here, as it should be a path relative to the
-	// FD, which isn't always rootFD (3). This means the path for Open may need
-	// to be built up. For example, if dirfd represents "/tmp/foo" and
-	// path="bar", this should open "/tmp/foo/bar" not "/bar".
-	//
-	// See https://linux.die.net/man/2/openat
-	pathName := string(b)
 	var newFD uint32
 	var err error
 	if isDir && oflags&O_CREAT != 0 {
@@ -1321,6 +1343,30 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	return ErrnoSuccess
 }
 
+// Note: We don't handle AT_FDCWD, as that's resolved in the compiler.
+// There's no working directory function in WASI, so CWD cannot be handled
+// here in any way except assuming it is "/".
+//
+// See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c#L24-L26
+//
+// TODO: path is not precise here, as it should be a path relative to the
+// FD, which isn't always rootFD (3). This means the path for Open may need
+// to be built up. For example, if dirfd represents "/tmp/foo" and
+// path="bar", this should open "/tmp/foo/bar" not "/bar".
+//
+// See https://linux.die.net/man/2/openat
+func atPath(fsc *sys.FSContext, mem api.Memory, dirfd, path, pathLen uint32) (string, Errno) {
+	if _, ok := fsc.OpenedFile(dirfd); !ok {
+		return "", ErrnoBadf
+	}
+
+	b, ok := mem.Read(path, pathLen)
+	if !ok {
+		return "", ErrnoFault
+	}
+	return string(b), ErrnoSuccess
+}
+
 func openFlags(oflags, fdflags uint16) (openFlags int, isDir bool) {
 	isDir = oflags&O_DIRECTORY != 0
 	openFlags = os.O_RDONLY
@@ -1366,16 +1412,53 @@ var pathReadlink = stubFunction(
 	"fd", "path", "path_len", "buf", "buf_len", "result.bufused",
 )
 
-// pathRemoveDirectory is the WASI function named PathRemoveDirectoryName
-// which removes a directory.
+// pathRemoveDirectory is the WASI function named PathRemoveDirectoryName which
+// removes a directory.
+//
+// # Parameters
+//
+//   - fd: file descriptor of a directory that `path` is relative to
+//   - path: offset in api.Memory to read the path string from
+//   - pathLen: length of `path`
+//
+// # Result (Errno)
+//
+// The return value is ErrnoSuccess except the following error conditions:
+//   - ErrnoBadf: `fd` is invalid
+//   - ErrnoNoent: `path` does not exist.
+//   - ErrnoNotempty: `path` is not empty
+//   - ErrnoNotdir: `path` is a file
+//
+// # Notes
+//   - This is similar to unlinkat with AT_REMOVEDIR in POSIX.
+//     See https://linux.die.net/man/2/unlinkat
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno
-var pathRemoveDirectory = stubFunction(
-	PathRemoveDirectoryName,
+var pathRemoveDirectory = newHostFunc(
+	PathRemoveDirectoryName, pathRemoveDirectoryFn,
 	[]wasm.ValueType{i32, i32, i32},
 	"fd", "path", "path_len",
 )
 
+func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) Errno {
+	fsc := mod.(*wasm.CallContext).Sys.FS()
+
+	dirfd := uint32(params[0])
+	path := uint32(params[1])
+	pathLen := uint32(params[2])
+
+	pathName, errno := atPath(fsc, mod.Memory(), dirfd, path, pathLen)
+	if errno != ErrnoSuccess {
+		return errno
+	}
+
+	if err := fsc.Rmdir(pathName); err != nil {
+		return ToErrno(err)
+	}
+
+	return ErrnoSuccess
+}
+
 // pathRename is the WASI function named PathRenameName which renames a
 // file or directory.
 var pathRename = stubFunction(
@@ -1394,16 +1477,52 @@ var pathSymlink = stubFunction(
 	"old_path", "old_path_len", "fd", "new_path", "new_path_len",
 )
 
-// pathUnlinkFile is the WASI function named PathUnlinkFileName which
-// unlinks a file.
+// pathUnlinkFile is the WASI function named PathUnlinkFileName which unlinks a
+// file.
+//
+// # Parameters
+//
+//   - fd: file descriptor of a directory that `path` is relative to
+//   - path: offset in api.Memory to read the path string from
+//   - pathLen: length of `path`
+//
+// # Result (Errno)
+//
+// The return value is ErrnoSuccess except the following error conditions:
+//   - ErrnoBadf: `fd` is invalid
+//   - ErrnoNoent: `path` does not exist.
+//   - ErrnoIsdir: `path` is a directory
+//
+// # Notes
+//   - This is similar to unlinkat without AT_REMOVEDIR in POSIX.
+//     See https://linux.die.net/man/2/unlinkat
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno
-var pathUnlinkFile = stubFunction(
-	PathUnlinkFileName,
+var pathUnlinkFile = newHostFunc(
+	PathUnlinkFileName, pathUnlinkFileFn,
 	[]wasm.ValueType{i32, i32, i32},
 	"fd", "path", "path_len",
 )
 
+func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) Errno {
+	fsc := mod.(*wasm.CallContext).Sys.FS()
+
+	dirfd := uint32(params[0])
+	path := uint32(params[1])
+	pathLen := uint32(params[2])
+
+	pathName, errno := atPath(fsc, mod.Memory(), dirfd, path, pathLen)
+	if errno != ErrnoSuccess {
+		return errno
+	}
+
+	if err := fsc.Unlink(pathName); err != nil {
+		return ToErrno(err)
+	}
+
+	return ErrnoSuccess
+}
+
 // statFile attempts to stat the file at the given path. Errors coerce to WASI
 // Errno.
 func statFile(fsc *sys.FSContext, name string) (stat fs.FileInfo, errno Errno) {
diff --git a/internal/sys/fs.go b/internal/sys/fs.go
index d8ce5e90..7504b0e7 100644
--- a/internal/sys/fs.go
+++ b/internal/sys/fs.go
@@ -375,13 +375,16 @@ func (c *FSContext) openFile(name string) (fs.File, error) {
 }
 
 func (c *FSContext) cleanPath(name string) string {
+	if len(name) == 0 {
+		return name
+	}
 	// fs.ValidFile cannot be rooted (start with '/')
-	fsOpenPath := name
+	cleaned := name
 	if name[0] == '/' {
-		fsOpenPath = name[1:]
+		cleaned = name[1:]
 	}
-	fsOpenPath = path.Clean(fsOpenPath) // e.g. "sub/." -> "sub"
-	return fsOpenPath
+	cleaned = path.Clean(cleaned) // e.g. "sub/." -> "sub"
+	return cleaned
 }
 
 // FdWriter returns a valid writer for the given file descriptor or nil if syscall.EBADF.
diff --git a/internal/wasi_snapshot_preview1/errno.go b/internal/wasi_snapshot_preview1/errno.go
index 03dc3ea0..e51536e1 100644
--- a/internal/wasi_snapshot_preview1/errno.go
+++ b/internal/wasi_snapshot_preview1/errno.go
@@ -266,20 +266,23 @@ var errnoToString = [...]string{
 // error codes. For example, wasi-filesystem and GOOS=js don't map to these
 // Errno.
 func ToErrno(err error) Errno {
-	// handle all the cases of FS.Open or wasi_snapshot_preview1 to FSContext.OpenFile
 	switch {
-	case errors.Is(err, syscall.ENOSYS):
-		return ErrnoNosys
-	case errors.Is(err, fs.ErrInvalid):
+	case errors.Is(err, syscall.EBADF), errors.Is(err, fs.ErrClosed):
+		return ErrnoBadf
+	case errors.Is(err, syscall.EINVAL), errors.Is(err, fs.ErrInvalid):
 		return ErrnoInval
-	case errors.Is(err, fs.ErrNotExist):
-		// fs.FS is allowed to return this instead of ErrInvalid on an invalid path
-		return ErrnoNoent
-	case errors.Is(err, fs.ErrExist):
+	case errors.Is(err, syscall.EISDIR):
+		return ErrnoIsdir
+	case errors.Is(err, syscall.ENOTEMPTY):
+		return ErrnoNotempty
+	case errors.Is(err, syscall.EEXIST), errors.Is(err, fs.ErrExist):
 		return ErrnoExist
-	case errors.Is(err, syscall.EBADF):
-		// fsc.OpenFile currently returns this on out of file descriptors
-		return ErrnoBadf
+	case errors.Is(err, syscall.ENOENT), errors.Is(err, fs.ErrNotExist):
+		return ErrnoNoent
+	case errors.Is(err, syscall.ENOSYS):
+		return ErrnoNosys
+	case errors.Is(err, syscall.ENOTDIR):
+		return ErrnoNotdir
 	default:
 		return ErrnoIo
 	}
diff --git a/internal/writefs/writefs.go b/internal/writefs/writefs.go
index 1935f277..9907d7fd 100644
--- a/internal/writefs/writefs.go
+++ b/internal/writefs/writefs.go
@@ -33,6 +33,10 @@ type FS interface {
 	//   - syscall.ENOENT: `path` doesn't exist.
 	//   - syscall.ENOTDIR: `path` exists, but isn't a directory.
 	//   - syscall.ENOTEMPTY: `path` exists, but isn't empty.
+	//
+	// # Notes
+	//
+	//   - As of Go 1.19, Windows maps syscall.ENOTDIR to syscall.ENOENT.
 	Rmdir(path string) error
 
 	// Unlink is similar to syscall.Unlink, except the path is relative to this


Test patch:
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index 7b769861..b8c55b23 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -3,17 +3,20 @@ package wasi_snapshot_preview1_test
 import (
 	"bytes"
 	_ "embed"
+	"fmt"
 	"io"
 	"io/fs"
 	"math"
 	"os"
 	"path"
+	"runtime"
 	"testing"
 	"testing/fstest"
 	"time"
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/experimental/writefs"
 	"github.com/tetratelabs/wazero/internal/leb128"
 	"github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
@@ -1887,13 +1890,132 @@ func Test_fdWrite_Errors(t *testing.T) {
 	}
 }
 
-// Test_pathCreateDirectory only tests it is stubbed for GrainLang per #271
 func Test_pathCreateDirectory(t *testing.T) {
-	log := requireErrnoNosys(t, PathCreateDirectoryName, 0, 0, 0)
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(writefs.DirFS(tmpDir)))
+	defer r.Close(testCtx)
+
+	// set up the initial memory to include the path name starting at an offset.
+	pathName := "wazero"
+	realPath := path.Join(tmpDir, pathName)
+	ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
+	require.True(t, ok)
+
+	dirFD := sys.FdRoot
+	name := 1
+	nameLen := len(pathName)
+
+	requireErrno(t, ErrnoSuccess, mod, PathCreateDirectoryName, uint64(dirFD), uint64(name), uint64(nameLen))
 	require.Equal(t, `
---> wasi_snapshot_preview1.path_create_directory(fd=0,path=)
-<-- errno=ENOSYS
-`, log)
+==> wasi_snapshot_preview1.path_create_directory(fd=3,path=wazero)
+<== errno=ESUCCESS
+`, "\n"+log.String())
+
+	// ensure the directory was created
+	stat, err := os.Stat(realPath)
+	require.NoError(t, err)
+	require.True(t, stat.IsDir())
+	require.Equal(t, pathName, stat.Name())
+}
+
+func Test_pathCreateDirectory_Errors(t *testing.T) {
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(writefs.DirFS(tmpDir)))
+	defer r.Close(testCtx)
+
+	file := "file"
+	err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700)
+	require.NoError(t, err)
+
+	dir := "dir"
+	err = os.Mkdir(path.Join(tmpDir, dir), 0o700)
+	require.NoError(t, err)
+
+	tests := []struct {
+		name, pathName    string
+		fd, path, pathLen uint32
+		expectedErrno     Errno
+		expectedLog       string
+	}{
+		{
+			name:          "invalid fd",
+			fd:            42, // arbitrary invalid fd
+			expectedErrno: ErrnoBadf,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_create_directory(fd=42,path=)
+<== errno=EBADF
+`,
+		},
+		{
+			name:          "out-of-memory reading path",
+			fd:            sys.FdRoot,
+			path:          mod.Memory().Size(),
+			pathLen:       1,
+			expectedErrno: ErrnoFault,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_create_directory(fd=3,path=OOM(65536,1))
+<== errno=EFAULT
+`,
+		},
+		{
+			name:          "path invalid",
+			fd:            sys.FdRoot,
+			pathName:      "../foo",
+			pathLen:       6,
+			expectedErrno: ErrnoInval,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_create_directory(fd=3,path=../foo)
+<== errno=EINVAL
+`,
+		},
+		{
+			name:          "out-of-memory reading pathLen",
+			fd:            sys.FdRoot,
+			path:          0,
+			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
+			expectedErrno: ErrnoFault,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_create_directory(fd=3,path=OOM(0,65537))
+<== errno=EFAULT
+`,
+		},
+		{
+			name:          "file exists",
+			fd:            sys.FdRoot,
+			pathName:      file,
+			path:          0,
+			pathLen:       uint32(len(file)),
+			expectedErrno: ErrnoExist,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_create_directory(fd=3,path=file)
+<== errno=EEXIST
+`,
+		},
+		{
+			name:          "dir exists",
+			fd:            sys.FdRoot,
+			pathName:      dir,
+			path:          0,
+			pathLen:       uint32(len(dir)),
+			expectedErrno: ErrnoExist,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_create_directory(fd=3,path=dir)
+<== errno=EEXIST
+`,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			defer log.Reset()
+
+			mod.Memory().Write(tc.path, []byte(tc.pathName))
+
+			requireErrno(t, tc.expectedErrno, mod, PathCreateDirectoryName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
+			require.Equal(t, tc.expectedLog, "\n"+log.String())
+		})
+	}
 }
 
 func Test_pathFilestatGet(t *testing.T) {
@@ -1915,8 +2037,6 @@ func Test_pathFilestatGet(t *testing.T) {
 	// open both paths without using WASI
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	rootFd := uint32(3) // after stderr
-
 	fileFd, err := fsc.OpenFile(file, os.O_RDONLY, 0)
 	require.NoError(t, err)
 
@@ -1932,7 +2052,7 @@ func Test_pathFilestatGet(t *testing.T) {
 	}{
 		{
 			name:           "file under root",
-			fd:             rootFd,
+			fd:             sys.FdRoot,
 			memory:         initialMemoryFile,
 			pathLen:        1,
 			resultFilestat: 2,
@@ -1976,7 +2096,7 @@ func Test_pathFilestatGet(t *testing.T) {
 		},
 		{
 			name:           "dir under root",
-			fd:             rootFd,
+			fd:             sys.FdRoot,
 			memory:         initialMemoryDir,
 			pathLen:        1,
 			resultFilestat: 2,
@@ -2019,7 +2139,7 @@ func Test_pathFilestatGet(t *testing.T) {
 		},
 		{
 			name:           "path under root doesn't exist",
-			fd:             rootFd,
+			fd:             sys.FdRoot,
 			memory:         initialMemoryNotExists,
 			pathLen:        1,
 			resultFilestat: 2,
@@ -2055,7 +2175,7 @@ func Test_pathFilestatGet(t *testing.T) {
 		},
 		{
 			name:          "path is out of memory",
-			fd:            rootFd,
+			fd:            sys.FdRoot,
 			memory:        initialMemoryFile,
 			pathLen:       memorySize,
 			expectedErrno: ErrnoNametoolong,
@@ -2066,7 +2186,7 @@ func Test_pathFilestatGet(t *testing.T) {
 		},
 		{
 			name:           "resultFilestat exceeds the maximum valid address by 1",
-			fd:             rootFd,
+			fd:             sys.FdRoot,
 			memory:         initialMemoryFile,
 			pathLen:        1,
 			resultFilestat: memorySize - 64 + 1,
@@ -2117,8 +2237,7 @@ func Test_pathLink(t *testing.T) {
 }
 
 func Test_pathOpen(t *testing.T) {
-	rootFD := uint32(3) // after 0, 1, and 2, that are stdin/out/err
-	expectedFD := rootFD + 1
+	expectedFD := sys.FdRoot + 1
 	// set up the initial memory to include the path name starting at an offset.
 	pathName := "wazero"
 	initialMemory := append([]byte{'?'}, pathName...)
@@ -2147,7 +2266,7 @@ func Test_pathOpen(t *testing.T) {
 	ok := mod.Memory().Write(0, initialMemory)
 	require.True(t, ok)
 
-	requireErrno(t, ErrnoSuccess, mod, PathOpenName, uint64(rootFD), uint64(dirflags), uint64(path),
+	requireErrno(t, ErrnoSuccess, mod, PathOpenName, uint64(sys.FdRoot), uint64(dirflags), uint64(path),
 		uint64(pathLen), uint64(oflags), fsRightsBase, fsRightsInheriting, uint64(fdflags), uint64(resultOpenedFd))
 	require.Equal(t, `
 ==> wasi_snapshot_preview1.path_open(fd=3,dirflags=,path=wazero,oflags=,fs_rights_base=FD_DATASYNC,fs_rights_inheriting=FD_READ,fdflags=)
@@ -2291,13 +2410,158 @@ func Test_pathReadlink(t *testing.T) {
 `, log)
 }
 
-// Test_pathRemoveDirectory only tests it is stubbed for GrainLang per #271
 func Test_pathRemoveDirectory(t *testing.T) {
-	log := requireErrnoNosys(t, PathRemoveDirectoryName, 0, 0, 0)
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(writefs.DirFS(tmpDir)))
+	defer r.Close(testCtx)
+
+	// set up the initial memory to include the path name starting at an offset.
+	pathName := "wazero"
+	realPath := path.Join(tmpDir, pathName)
+	ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
+	require.True(t, ok)
+
+	// create the directory
+	err := os.Mkdir(realPath, 0o700)
+	require.NoError(t, err)
+
+	dirFD := sys.FdRoot
+	name := 1
+	nameLen := len(pathName)
+
+	requireErrno(t, ErrnoSuccess, mod, PathRemoveDirectoryName, uint64(dirFD), uint64(name), uint64(nameLen))
 	require.Equal(t, `
---> wasi_snapshot_preview1.path_remove_directory(fd=0,path=)
-<-- errno=ENOSYS
-`, log)
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=wazero)
+<== errno=ESUCCESS
+`, "\n"+log.String())
+
+	// ensure the directory was removed
+	_, err = os.Stat(realPath)
+	require.Error(t, err)
+}
+
+func Test_pathRemoveDirectory_Errors(t *testing.T) {
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(writefs.DirFS(tmpDir)))
+	defer r.Close(testCtx)
+
+	file := "file"
+	err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700)
+	require.NoError(t, err)
+
+	dirNotEmpty := "notempty"
+	err = os.Mkdir(path.Join(tmpDir, dirNotEmpty), 0o700)
+	require.NoError(t, err)
+
+	dir := "dir"
+	err = os.Mkdir(path.Join(tmpDir, dirNotEmpty, dir), 0o700)
+	require.NoError(t, err)
+
+	tests := []struct {
+		name, pathName    string
+		fd, path, pathLen uint32
+		expectedErrno     Errno
+		expectedLog       string
+	}{
+		{
+			name:          "invalid fd",
+			fd:            42, // arbitrary invalid fd
+			expectedErrno: ErrnoBadf,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_remove_directory(fd=42,path=)
+<== errno=EBADF
+`,
+		},
+		{
+			name:          "out-of-memory reading path",
+			fd:            sys.FdRoot,
+			path:          mod.Memory().Size(),
+			pathLen:       1,
+			expectedErrno: ErrnoFault,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=OOM(65536,1))
+<== errno=EFAULT
+`,
+		},
+		{
+			name:          "path invalid",
+			fd:            sys.FdRoot,
+			pathName:      "../foo",
+			pathLen:       6,
+			expectedErrno: ErrnoInval,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=../foo)
+<== errno=EINVAL
+`,
+		},
+		{
+			name:          "out-of-memory reading pathLen",
+			fd:            sys.FdRoot,
+			path:          0,
+			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
+			expectedErrno: ErrnoFault,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=OOM(0,65537))
+<== errno=EFAULT
+`,
+		},
+		{
+			name:          "no such file exists",
+			fd:            sys.FdRoot,
+			pathName:      file,
+			path:          0,
+			pathLen:       uint32(len(file) - 1),
+			expectedErrno: ErrnoNoent,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=fil)
+<== errno=ENOENT
+`,
+		},
+		{
+			name:          "file not dir",
+			fd:            sys.FdRoot,
+			pathName:      file,
+			path:          0,
+			pathLen:       uint32(len(file)),
+			expectedErrno: errNotDir(),
+			expectedLog: fmt.Sprintf(`
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=file)
+<== errno=%s
+`, ErrnoName(errNotDir())),
+		},
+		{
+			name:          "dir not empty",
+			fd:            sys.FdRoot,
+			pathName:      dirNotEmpty,
+			path:          0,
+			pathLen:       uint32(len(dirNotEmpty)),
+			expectedErrno: ErrnoNotempty,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_remove_directory(fd=3,path=notempty)
+<== errno=ENOTEMPTY
+`,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			defer log.Reset()
+
+			mod.Memory().Write(tc.path, []byte(tc.pathName))
+
+			requireErrno(t, tc.expectedErrno, mod, PathRemoveDirectoryName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
+			require.Equal(t, tc.expectedLog, "\n"+log.String())
+		})
+	}
+}
+
+func errNotDir() Errno {
+	if runtime.GOOS == "windows" {
+		// As of Go 1.19, Windows maps syscall.ENOTDIR to syscall.ENOENT
+		return ErrnoNoent
+	}
+	return ErrnoNotdir
 }
 
 // Test_pathRename only tests it is stubbed for GrainLang per #271
@@ -2318,13 +2582,134 @@ func Test_pathSymlink(t *testing.T) {
 `, log)
 }
 
-// Test_pathUnlinkFile only tests it is stubbed for GrainLang per #271
 func Test_pathUnlinkFile(t *testing.T) {
-	log := requireErrnoNosys(t, PathUnlinkFileName, 0, 0, 0)
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(writefs.DirFS(tmpDir)))
+	defer r.Close(testCtx)
+
+	// set up the initial memory to include the path name starting at an offset.
+	pathName := "wazero"
+	realPath := path.Join(tmpDir, pathName)
+	ok := mod.Memory().Write(0, append([]byte{'?'}, pathName...))
+	require.True(t, ok)
+
+	// create the file
+	err := os.WriteFile(realPath, []byte{}, 0o600)
+	require.NoError(t, err)
+
+	dirFD := sys.FdRoot
+	name := 1
+	nameLen := len(pathName)
+
+	requireErrno(t, ErrnoSuccess, mod, PathUnlinkFileName, uint64(dirFD), uint64(name), uint64(nameLen))
 	require.Equal(t, `
---> wasi_snapshot_preview1.path_unlink_file(fd=0,path=)
-<-- errno=ENOSYS
-`, log)
+==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=wazero)
+<== errno=ESUCCESS
+`, "\n"+log.String())
+
+	// ensure the file was removed
+	_, err = os.Stat(realPath)
+	require.Error(t, err)
+}
+
+func Test_pathUnlinkFile_Errors(t *testing.T) {
+	tmpDir := t.TempDir() // open before loop to ensure no locking problems.
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().WithFS(writefs.DirFS(tmpDir)))
+	defer r.Close(testCtx)
+
+	file := "file"
+	err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700)
+	require.NoError(t, err)
+
+	dir := "dir"
+	err = os.Mkdir(path.Join(tmpDir, dir), 0o700)
+	require.NoError(t, err)
+
+	tests := []struct {
+		name, pathName    string
+		fd, path, pathLen uint32
+		expectedErrno     Errno
+		expectedLog       string
+	}{
+		{
+			name:          "invalid fd",
+			fd:            42, // arbitrary invalid fd
+			expectedErrno: ErrnoBadf,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_unlink_file(fd=42,path=)
+<== errno=EBADF
+`,
+		},
+		{
+			name:          "out-of-memory reading path",
+			fd:            sys.FdRoot,
+			path:          mod.Memory().Size(),
+			pathLen:       1,
+			expectedErrno: ErrnoFault,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=OOM(65536,1))
+<== errno=EFAULT
+`,
+		},
+		{
+			name:          "path invalid",
+			fd:            sys.FdRoot,
+			pathName:      "../foo",
+			pathLen:       6,
+			expectedErrno: ErrnoInval,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=../foo)
+<== errno=EINVAL
+`,
+		},
+		{
+			name:          "out-of-memory reading pathLen",
+			fd:            sys.FdRoot,
+			path:          0,
+			pathLen:       mod.Memory().Size() + 1, // path is in the valid memory range, but pathLen is OOM for path
+			expectedErrno: ErrnoFault,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=OOM(0,65537))
+<== errno=EFAULT
+`,
+		},
+		{
+			name:          "no such file exists",
+			fd:            sys.FdRoot,
+			pathName:      file,
+			path:          0,
+			pathLen:       uint32(len(file) - 1),
+			expectedErrno: ErrnoNoent,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=fil)
+<== errno=ENOENT
+`,
+		},
+		{
+			name:          "dir not file",
+			fd:            sys.FdRoot,
+			pathName:      dir,
+			path:          0,
+			pathLen:       uint32(len(dir)),
+			expectedErrno: ErrnoIsdir,
+			expectedLog: `
+==> wasi_snapshot_preview1.path_unlink_file(fd=3,path=dir)
+<== errno=EISDIR
+`,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			defer log.Reset()
+
+			mod.Memory().Write(tc.path, []byte(tc.pathName))
+
+			requireErrno(t, tc.expectedErrno, mod, PathUnlinkFileName, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
+			require.Equal(t, tc.expectedLog, "\n"+log.String())
+		})
+	}
 }
 
 func requireOpenFile(t *testing.T, pathName string, data []byte) (api.Module, uint32, *bytes.Buffer, api.Closer) {
diff --git a/internal/writefs/writefs_test.go b/internal/writefs/writefs_test.go
index db8f9a79..430008d7 100644
--- a/internal/writefs/writefs_test.go
+++ b/internal/writefs/writefs_test.go
@@ -100,7 +100,7 @@ func TestDirFS_Rmdir(t *testing.T) {
 		require.Error(t, err)
 	})
 
-	t.Run("file exists", func(t *testing.T) {
+	t.Run("not directory", func(t *testing.T) {
 		require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
 
 		err := testFS.Rmdir(name)
@@ -123,7 +123,7 @@ func TestDirFS_Unlink(t *testing.T) {
 		require.Equal(t, syscall.ENOENT, err)
 	})
 
-	t.Run("dir exists", func(t *testing.T) {
+	t.Run("not file", func(t *testing.T) {
 		require.NoError(t, os.Mkdir(realPath, 0o700))
 
 		err := testFS.Unlink(name)