diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2023-04-05 01:14:43 +0330 |
---|---|---|
committer | Ali Mohammad Pur <Ali.mpfard@gmail.com> | 2023-04-26 03:47:15 +0330 |
commit | 7e4e9fdb8ff5ce21b541f1d2df9b9215fc3757d9 (patch) | |
tree | 61ef99c006fe070b299fc1e1c9dde6ca7b51abe9 /Userland/Libraries | |
parent | eceb244befb0992c47dd13b80bada954b4689053 (diff) | |
download | serenity-7e4e9fdb8ff5ce21b541f1d2df9b9215fc3757d9.zip |
LibWasm: Start implementing WASI
This commit starts adding support for WASI, along with the framework to
implement all the functions (though only a couple are currently
implemented).
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/Forward.h | 5 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/WASI/Wasi.cpp | 1017 | ||||
-rw-r--r-- | Userland/Libraries/LibWasm/Wasi.h | 943 |
5 files changed, 1967 insertions, 1 deletions
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 4c8ca6261f..dac570e96f 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -121,7 +121,7 @@ public: ALWAYS_INLINE Value& operator=(Value const& value) = default; template<typename T> - ALWAYS_INLINE Optional<T> to() + ALWAYS_INLINE Optional<T> to() const { Optional<T> result; m_value.visit( diff --git a/Userland/Libraries/LibWasm/CMakeLists.txt b/Userland/Libraries/LibWasm/CMakeLists.txt index 5b78f92da6..729156278c 100644 --- a/Userland/Libraries/LibWasm/CMakeLists.txt +++ b/Userland/Libraries/LibWasm/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES AbstractMachine/Validator.cpp Parser/Parser.cpp Printer/Printer.cpp + WASI/Wasi.cpp ) serenity_lib(LibWasm wasm) diff --git a/Userland/Libraries/LibWasm/Forward.h b/Userland/Libraries/LibWasm/Forward.h index 4bb2c85f84..84006190ef 100644 --- a/Userland/Libraries/LibWasm/Forward.h +++ b/Userland/Libraries/LibWasm/Forward.h @@ -11,5 +11,10 @@ namespace Wasm { class AbstractMachine; class Validator; struct ValidationError; +struct Interpreter; + +namespace Wasi { +struct Implementation; +} } diff --git a/Userland/Libraries/LibWasm/WASI/Wasi.cpp b/Userland/Libraries/LibWasm/WASI/Wasi.cpp new file mode 100644 index 0000000000..22cc5b369a --- /dev/null +++ b/Userland/Libraries/LibWasm/WASI/Wasi.cpp @@ -0,0 +1,1017 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/ByteReader.h> +#include <AK/Debug.h> +#include <AK/FlyString.h> +#include <AK/SourceLocation.h> +#include <AK/Tuple.h> +#include <LibCore/File.h> +#include <LibWasm/AbstractMachine/Interpreter.h> +#include <LibWasm/Printer/Printer.h> +#include <LibWasm/Wasi.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +namespace Wasm::Wasi::ABI { + +template<typename T> +Wasm::Value CompatibleValue<T>::to_wasm_value() const +{ + return Wasm::Value(value); +} + +template<typename T> +T deserialize(CompatibleValue<T> const& data) +{ + return deserialize<T>(Array { ReadonlyBytes { &data.value, sizeof(data.value) } }); +} + +template<typename T, size_t N> +void serialize(T const& value, Array<Bytes, N> bytes) +{ + if constexpr (IsEnum<T>) + return serialize(to_underlying(value), move(bytes)); + else if constexpr (IsIntegral<T>) + ReadonlyBytes { &value, sizeof(value) }.copy_to(bytes[0]); + else if constexpr (IsSpecializationOf<T, DistinctNumeric>) + return serialize(value.value(), move(bytes)); + else + return value.serialize_into(move(bytes)); +} + +template<typename T, size_t N> +T deserialize(Array<ReadonlyBytes, N> const& bytes) +{ + if constexpr (IsEnum<T>) { + return static_cast<T>(deserialize<UnderlyingType<T>>(bytes)); + } else if constexpr (IsIntegral<T>) { + T value; + ByteReader::load(bytes[0].data(), value); + return value; + } else if constexpr (IsSpecializationOf<T, DistinctNumeric>) { + return deserialize<RemoveCVReference<decltype(T(0).value())>>(bytes); + } else { + return T::read_from(bytes); + } +} + +template<typename T> +CompatibleValue<T> to_compatible_value(Wasm::Value const& value) +{ + using Type = typename ToCompatibleValue<T>::Type; + // Note: the type can't be something else, we've already checked before through the function type's runtime checker. + auto converted_value = *value.template to<Type>(); + return { .value = converted_value }; +} + +} + +namespace Wasm::Wasi { + +void ArgsSizes::serialize_into(Array<Bytes, 2> bytes) const +{ + ABI::serialize(count, Array { bytes[0] }); + ABI::serialize(size, Array { bytes[1] }); +} + +void EnvironSizes::serialize_into(Array<Bytes, 2> bytes) const +{ + ABI::serialize(count, Array { bytes[0] }); + ABI::serialize(size, Array { bytes[1] }); +} + +void SockRecvResult::serialize_into(Array<Bytes, 2> bytes) const +{ + ABI::serialize(size, Array { bytes[0] }); + ABI::serialize(roflags, Array { bytes[1] }); +} + +void ROFlags::serialize_into(Array<Bytes, 1> bytes) const +{ + ABI::serialize(data, Array { bytes[0] }); +} + +template<typename T> +void LittleEndian<T>::serialize_into(Array<Bytes, 1> bytes) const +{ + ABI::serialize(m_value, move(bytes)); +} + +template<typename T> +LittleEndian<T> LittleEndian<T>::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + auto swapped = ABI::deserialize<T>(bytes); + return bit_cast<LittleEndian<T>>(swapped); +} + +Rights Rights::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + Rights rights { .data = 0 }; + bytes[0].copy_to(rights.data.bytes()); + return rights; +} + +void Rights::serialize_into(Array<Bytes, 1> bytes) const +{ + data.bytes().copy_to(bytes[0]); +} + +void FDFlags::serialize_into(Array<Bytes, 1> bytes) const +{ + ReadonlyBytes { &data, sizeof(data) }.copy_to(bytes[0]); +} + +FDFlags FDFlags::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + FDFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +FSTFlags FSTFlags::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + FSTFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +OFlags OFlags::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + OFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +SDFlags SDFlags::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + SDFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +void FDStat::serialize_into(Array<Bytes, 1> bytes) const +{ + auto data = bytes[0]; + ABI::serialize(fs_filetype, Array { data.slice(offsetof(FDStat, fs_filetype), sizeof(fs_filetype)) }); + ABI::serialize(fs_flags, Array { data.slice(offsetof(FDStat, fs_flags), sizeof(fs_flags)) }); + ABI::serialize(fs_rights_base, Array { data.slice(offsetof(FDStat, fs_rights_base), sizeof(fs_rights_base)) }); + ABI::serialize(fs_rights_inheriting, Array { data.slice(offsetof(FDStat, fs_rights_inheriting), sizeof(fs_rights_inheriting)) }); +} + +void PreStat::serialize_into(Array<Bytes, 1> bytes) const +{ + auto data = bytes[0]; + ABI::serialize(tag, Array { data.slice(0, sizeof(tag)) }); + if (tag == 0) + ABI::serialize(dir, Array { data.slice(offsetof(PreStat, dir), sizeof(dir)) }); + else + VERIFY_NOT_REACHED(); +} + +void PreStatDir::serialize_into(Array<Bytes, 1> bytes) const +{ + ABI::serialize(pr_name_len, move(bytes)); +} + +void FileStat::serialize_into(Array<Bytes, 1> bytes) const +{ + auto data = bytes[0]; + ABI::serialize(dev, Array { data.slice(0, sizeof(dev)) }); + ABI::serialize(ino, Array { data.slice(offsetof(FileStat, ino), sizeof(ino)) }); + ABI::serialize(filetype, Array { data.slice(offsetof(FileStat, filetype), sizeof(filetype)) }); + ABI::serialize(nlink, Array { data.slice(offsetof(FileStat, nlink), sizeof(nlink)) }); + ABI::serialize(size, Array { data.slice(offsetof(FileStat, size), sizeof(size)) }); + ABI::serialize(atim, Array { data.slice(offsetof(FileStat, atim), sizeof(atim)) }); + ABI::serialize(mtim, Array { data.slice(offsetof(FileStat, mtim), sizeof(mtim)) }); + ABI::serialize(ctim, Array { data.slice(offsetof(FileStat, ctim), sizeof(ctim)) }); +} + +RIFlags RIFlags::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + RIFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +LookupFlags LookupFlags::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + LookupFlags flags { .data = 0 }; + bytes[0].copy_to(flags.data.bytes()); + return flags; +} + +CIOVec CIOVec::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + return CIOVec { + .buf = ABI::deserialize<decltype(buf)>(Array { bytes[0].slice(offsetof(CIOVec, buf), sizeof(buf)) }), + .buf_len = ABI::deserialize<decltype(buf_len)>(Array { bytes[0].slice(offsetof(CIOVec, buf_len), sizeof(buf_len)) }), + }; +} + +IOVec IOVec::read_from(Array<ReadonlyBytes, 1> const& bytes) +{ + return IOVec { + .buf = ABI::deserialize<decltype(buf)>(Array { bytes[0].slice(offsetof(IOVec, buf), sizeof(buf)) }), + .buf_len = ABI::deserialize<decltype(buf_len)>(Array { bytes[0].slice(offsetof(IOVec, buf_len), sizeof(buf_len)) }), + }; +} + +template<typename T> +ErrorOr<Vector<T>> copy_typed_array(Configuration& configuration, Pointer<T> source, Size count) +{ + Vector<T> values; + TRY(values.try_ensure_capacity(count)); + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + UnderlyingPointerType address = source.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + (size * count)) { + return Error::from_errno(ENOBUFS); + } + + for (Size i = 0; i < count; i += 1) { + values.unchecked_append(T::read_from(Array { ReadonlyBytes { memory->data().bytes().slice(address, size) } })); + address += size; + } + + return values; +} + +template<typename T> +ErrorOr<void> copy_typed_value_to(Configuration& configuration, T const& value, Pointer<T> destination) +{ + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + UnderlyingPointerType address = destination.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + size) { + return Error::from_errno(ENOBUFS); + } + + ABI::serialize(value, Array { Bytes { memory->data().bytes().slice(address, size) } }); + return {}; +} + +template<typename T> +ErrorOr<Span<T>> slice_typed_memory(Configuration& configuration, Pointer<T> source, Size count) +{ + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + auto address = source.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + (size * count)) + return Error::from_errno(ENOBUFS); + + auto untyped_slice = memory->data().bytes().slice(address, size * count); + return Span<T>(untyped_slice.data(), count); +} + +template<typename T> +ErrorOr<Span<T const>> slice_typed_memory(Configuration& configuration, ConstPointer<T> source, Size count) +{ + auto* memory = configuration.store().get(MemoryAddress { 0 }); + if (!memory) + return Error::from_errno(ENOMEM); + + auto address = source.value(); + auto size = sizeof(T); + if (memory->size() < address || memory->size() <= address + (size * count)) + return Error::from_errno(ENOBUFS); + + auto untyped_slice = memory->data().bytes().slice(address, size * count); + return Span<T const>(untyped_slice.data(), count); +} + +static ErrorOr<size_t> copy_string_including_terminating_null(Configuration& configuration, StringView string, Pointer<u8> target) +{ + auto slice = TRY(slice_typed_memory(configuration, target, string.bytes().size() + 1)); + string.bytes().copy_to(slice); + slice[string.bytes().size()] = 0; + return slice.size(); +} + +static ErrorOr<size_t> copy_string_excluding_terminating_null(Configuration& configuration, StringView string, Pointer<u8> target, Size target_length) +{ + auto byte_count = min(string.bytes().size(), target_length); + auto slice = TRY(slice_typed_memory(configuration, target, byte_count)); + string.bytes().copy_trimmed_to(slice); + return byte_count; +} + +static Errno errno_value_from_errno(int value); + +Vector<AK::String> const& Implementation::arguments() const +{ + if (!cache.cached_arguments.has_value()) { + cache.cached_arguments.lazy_emplace([&] { + if (provide_arguments) + return provide_arguments(); + return Vector<AK::String> {}; + }); + } + + return *cache.cached_arguments; +} + +Vector<AK::String> const& Implementation::environment() const +{ + if (!cache.cached_environment.has_value()) { + cache.cached_environment.lazy_emplace([&] { + if (provide_environment) + return provide_environment(); + return Vector<AK::String> {}; + }); + } + + return *cache.cached_environment; +} + +Vector<Implementation::MappedPath> const& Implementation::preopened_directories() const +{ + if (!cache.cached_preopened_directories.has_value()) { + cache.cached_preopened_directories.lazy_emplace([&] { + if (provide_preopened_directories) + return provide_preopened_directories(); + return Vector<MappedPath> {}; + }); + } + + return *cache.cached_preopened_directories; +} + +Implementation::Descriptor Implementation::map_fd(FD fd) +{ + u32 fd_value = fd.value(); + if (auto* value = m_fd_map.find(fd_value)) + return value->downcast<Descriptor>(); + + return UnmappedDescriptor(fd_value); +} + +ErrorOr<Result<void>> Implementation::impl$args_get(Configuration& configuration, Pointer<Pointer<u8>> argv, Pointer<u8> argv_buf) +{ + UnderlyingPointerType raw_argv_buffer = argv_buf.value(); + UnderlyingPointerType raw_argv = argv.value(); + + for (auto& entry : arguments()) { + auto ptr = Pointer<u8> { raw_argv_buffer }; + auto byte_count = TRY(copy_string_including_terminating_null(configuration, entry.bytes_as_string_view(), ptr)); + raw_argv_buffer += byte_count; + + TRY(copy_typed_value_to(configuration, ptr, Pointer<Pointer<u8>> { raw_argv })); + raw_argv += sizeof(ptr); + } + + return Result<void> {}; +} + +ErrorOr<Result<ArgsSizes>> Implementation::impl$args_sizes_get(Configuration&) +{ + size_t count = 0; + size_t total_size = 0; + for (auto& entry : arguments()) { + count += 1; + total_size += entry.bytes().size() + 1; // 1 extra byte for terminating null. + } + + return Result<ArgsSizes>(ArgsSizes { + count, + total_size, + }); +} + +ErrorOr<Result<void>> Implementation::impl$environ_get(Configuration& configuration, Pointer<Pointer<u8>> environ, Pointer<u8> environ_buf) +{ + UnderlyingPointerType raw_environ_buffer = environ_buf.value(); + UnderlyingPointerType raw_environ = environ.value(); + + for (auto& entry : environment()) { + auto ptr = Pointer<u8> { raw_environ_buffer }; + auto byte_count = TRY(copy_string_including_terminating_null(configuration, entry.bytes_as_string_view(), ptr)); + raw_environ_buffer += byte_count; + + TRY(copy_typed_value_to(configuration, ptr, Pointer<Pointer<u8>> { raw_environ })); + raw_environ += sizeof(ptr); + } + + return Result<void> {}; +} + +ErrorOr<Result<EnvironSizes>> Implementation::impl$environ_sizes_get(Configuration&) +{ + size_t count = 0; + size_t total_size = 0; + for (auto& entry : environment()) { + count += 1; + total_size += entry.bytes().size() + 1; // 1 extra byte for terminating null. + } + + return Result<EnvironSizes>(EnvironSizes { + count, + total_size, + }); +} + +ErrorOr<Result<void>> Implementation::impl$proc_exit(Configuration&, ExitCode exit_code) +{ + exit(exit_code); +} + +ErrorOr<Result<void>> Implementation::impl$fd_close(Configuration&, FD fd) +{ + return map_fd(fd).visit( + [&](u32 fd) -> Result<void> { + if (close(bit_cast<i32>(fd)) != 0) + return errno_value_from_errno(errno); + return {}; + }, + [&](PreopenedDirectoryDescriptor) -> Result<void> { + return errno_value_from_errno(EISDIR); + }, + [&](UnmappedDescriptor) -> Result<void> { + return errno_value_from_errno(EBADF); + }); +} + +ErrorOr<Result<Size>> Implementation::impl$fd_write(Configuration& configuration, FD fd, Pointer<CIOVec> iovs, Size iovs_len) +{ + auto mapped_fd = map_fd(fd); + if (!mapped_fd.has<u32>()) + return errno_value_from_errno(EBADF); + + u32 fd_value = mapped_fd.get<u32>(); + Size bytes_written = 0; + for (auto& iovec : TRY(copy_typed_array(configuration, iovs, iovs_len))) { + auto slice = TRY(slice_typed_memory(configuration, iovec.buf, iovec.buf_len)); + auto result = write(fd_value, slice.data(), slice.size()); + if (result < 0) + return errno_value_from_errno(errno); + bytes_written += static_cast<Size>(result); + } + return bytes_written; +} + +ErrorOr<Result<PreStat>> Implementation::impl$fd_prestat_get(Configuration&, FD fd) +{ + auto& paths = preopened_directories(); + return map_fd(fd).visit( + [&](UnmappedDescriptor unmapped_fd) -> Result<PreStat> { + // Map the new fd to the next available directory. + if (m_first_unmapped_preopened_directory_index >= paths.size()) + return errno_value_from_errno(EBADF); + + auto index = m_first_unmapped_preopened_directory_index++; + m_fd_map.insert(unmapped_fd.value(), PreopenedDirectoryDescriptor(index)); + return PreStat { + .tag = 0, + .dir = PreStatDir { + .pr_name_len = paths[index].mapped_path.string().bytes().size(), + }, + }; + }, + [&](u32) -> Result<PreStat> { + return errno_value_from_errno(EBADF); + }, + [&](PreopenedDirectoryDescriptor fd) -> Result<PreStat> { + return PreStat { + .tag = 0, + .dir = PreStatDir { + .pr_name_len = paths[fd.value()].mapped_path.string().bytes().size(), + }, + }; + }); +} + +ErrorOr<Result<void>> Implementation::impl$fd_prestat_dir_name(Configuration& configuration, FD fd, Pointer<u8> path, Size path_len) +{ + auto mapped_fd = map_fd(fd); + if (!mapped_fd.has<PreopenedDirectoryDescriptor>()) + return errno_value_from_errno(EBADF); + + auto& entry = preopened_directories()[mapped_fd.get<PreopenedDirectoryDescriptor>().value()]; + auto byte_count = TRY(copy_string_excluding_terminating_null(configuration, entry.mapped_path.string().view(), path, path_len)); + if (byte_count < path_len.value()) + return errno_value_from_errno(ENOBUFS); + + return Result<void> {}; +} + +ErrorOr<Result<FileStat>> Implementation::impl$path_filestat_get(Configuration& configuration, FD fd, LookupFlags flags, ConstPointer<u8> path, Size path_len) +{ + int dir_fd = AT_FDCWD; + + auto mapped_fd = map_fd(fd); + if (mapped_fd.has<PreopenedDirectoryDescriptor>()) { + auto& entry = preopened_directories()[mapped_fd.get<PreopenedDirectoryDescriptor>().value()]; + dir_fd = entry.opened_fd.value_or_lazy_evaluated([&] { + DeprecatedString path = entry.host_path.string(); + return open(path.characters(), O_DIRECTORY, 0755); + }); + entry.opened_fd = dir_fd; + } + + if (dir_fd < 0 && dir_fd != AT_FDCWD) + return errno_value_from_errno(errno); + + int options = 0; + if (!flags.bits.symlink_follow) + options |= AT_SYMLINK_NOFOLLOW; + + auto slice = TRY(slice_typed_memory(configuration, path, path_len)); + auto null_terminated_string = DeprecatedString::copy(slice); + + struct stat stat_buf; + if (fstatat(dir_fd, null_terminated_string.characters(), &stat_buf, options) < 0) + return errno_value_from_errno(errno); + + constexpr auto file_type_of = [](struct stat const& buf) { + if (S_ISDIR(buf.st_mode)) + return FileType::Directory; + if (S_ISCHR(buf.st_mode)) + return FileType::CharacterDevice; + if (S_ISBLK(buf.st_mode)) + return FileType::BlockDevice; + if (S_ISREG(buf.st_mode)) + return FileType::RegularFile; + if (S_ISFIFO(buf.st_mode)) + return FileType::Unknown; // no Pipe? :yakfused: + if (S_ISLNK(buf.st_mode)) + return FileType::SymbolicLink; + if (S_ISSOCK(buf.st_mode)) + return FileType::SocketDGram; // :shrug: + return FileType::Unknown; + }; + + return Result(FileStat { + .dev = stat_buf.st_dev, + .ino = stat_buf.st_ino, + .filetype = file_type_of(stat_buf), + .nlink = stat_buf.st_nlink, + .size = stat_buf.st_size, + .atim = stat_buf.st_atime, + .mtim = stat_buf.st_mtime, + .ctim = stat_buf.st_ctime, + }); +} + +ErrorOr<Result<void>> Implementation::impl$path_create_directory(Configuration& configuration, FD fd, Pointer<u8> path, Size path_len) +{ + int dir_fd = AT_FDCWD; + + auto mapped_fd = map_fd(fd); + if (mapped_fd.has<PreopenedDirectoryDescriptor>()) { + auto& entry = preopened_directories()[mapped_fd.get<PreopenedDirectoryDescriptor>().value()]; + dir_fd = entry.opened_fd.value_or_lazy_evaluated([&] { + DeprecatedString path = entry.host_path.string(); + return open(path.characters(), O_DIRECTORY, 0755); + }); + entry.opened_fd = dir_fd; + } + + if (dir_fd < 0 && dir_fd != AT_FDCWD) + return errno_value_from_errno(errno); + + auto slice = TRY(slice_typed_memory(configuration, path, path_len)); + auto null_terminated_string = DeprecatedString::copy(slice); + + if (mkdirat(dir_fd, null_terminated_string.characters(), 0755) < 0) + return errno_value_from_errno(errno); + + return Result<void> {}; +} + +ErrorOr<Result<FD>> Implementation::impl$path_open(Configuration& configuration, FD fd, LookupFlags lookup_flags, Pointer<u8> path, Size path_len, OFlags o_flags, Rights, Rights, FDFlags fd_flags) +{ + int dir_fd = AT_FDCWD; + + auto mapped_fd = map_fd(fd); + if (mapped_fd.has<PreopenedDirectoryDescriptor>()) { + auto& entry = preopened_directories()[mapped_fd.get<PreopenedDirectoryDescriptor>().value()]; + dir_fd = entry.opened_fd.value_or_lazy_evaluated([&] { + DeprecatedString path = entry.host_path.string(); + return open(path.characters(), O_DIRECTORY, 0755); + }); + entry.opened_fd = dir_fd; + } + + if (dir_fd < 0 && dir_fd != AT_FDCWD) + return errno_value_from_errno(errno); + + // FIXME: What should we do with dsync/rsync? + + int open_flags = 0; + if (fd_flags.bits.append) + open_flags |= O_APPEND; + if (fd_flags.bits.nonblock) + open_flags |= O_NONBLOCK; + if (fd_flags.bits.sync) + open_flags |= O_SYNC; + + if (o_flags.bits.trunc) + open_flags |= O_TRUNC; + if (o_flags.bits.creat) + open_flags |= O_CREAT; + if (o_flags.bits.directory) + open_flags |= O_DIRECTORY; + if (o_flags.bits.excl) + open_flags |= O_EXCL; + + if (!lookup_flags.bits.symlink_follow) + open_flags |= O_NOFOLLOW; + + auto path_data = TRY(slice_typed_memory(configuration, path, path_len)); + auto path_string = DeprecatedString::copy(path_data); + + int opened_fd = openat(dir_fd, path_string.characters(), open_flags, 0644); + if (opened_fd < 0) + return errno_value_from_errno(errno); + + // FIXME: Implement Rights and RightsInheriting. + + return FD(opened_fd); +} + +ErrorOr<Result<Timestamp>> Implementation::impl$clock_time_get(Configuration&, ClockID id, Timestamp precision) +{ + constexpr u64 nanoseconds_in_millisecond = 1000'000ull; + constexpr u64 nanoseconds_in_second = 1000'000'000ull; + + clockid_t clock_id; + switch (id) { + case ClockID::Realtime: + if (precision >= nanoseconds_in_millisecond) + clock_id = CLOCK_REALTIME_COARSE; + else + clock_id = CLOCK_REALTIME; + break; + case ClockID::Monotonic: + if (precision >= nanoseconds_in_millisecond) + clock_id = CLOCK_MONOTONIC_COARSE; + else + clock_id = CLOCK_MONOTONIC; + break; + case ClockID::ProcessCPUTimeID: + case ClockID::ThreadCPUTimeID: + return Errno::NoSys; + break; + } + + struct timespec ts; + if (clock_gettime(clock_id, &ts) < 0) + return errno_value_from_errno(errno); + + return Result<Timestamp> { static_cast<u64>(ts.tv_sec) * nanoseconds_in_second + static_cast<u64>(ts.tv_nsec) }; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +ErrorOr<Result<Timestamp>> Implementation::impl$clock_res_get(Configuration&, ClockID id) +{ + return Errno::NoSys; +} +ErrorOr<Result<void>> Implementation::impl$fd_advise(Configuration&, FD, FileSize offset, FileSize len, Advice) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_allocate(Configuration&, FD, FileSize offset, FileSize len) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_datasync(Configuration&, FD) { return Errno::NoSys; } +ErrorOr<Result<FDStat>> Implementation::impl$fd_fdstat_get(Configuration&, FD) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_fdstat_set_flags(Configuration&, FD, FDFlags) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_fdstat_set_rights(Configuration&, FD, Rights fs_rights_base, Rights fs_rights_inheriting) { return Errno::NoSys; } +ErrorOr<Result<FileStat>> Implementation::impl$fd_filestat_get(Configuration&, FD) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_filestat_set_size(Configuration&, FD, FileSize) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_filestat_set_times(Configuration&, FD, Timestamp atim, Timestamp mtim, FSTFlags) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$fd_pread(Configuration&, FD, Pointer<IOVec> iovs, Size iovs_len, FileSize offset) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$fd_pwrite(Configuration&, FD, Pointer<CIOVec> iovs, Size iovs_len, FileSize offset) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$fd_read(Configuration&, FD, Pointer<IOVec> iovs, Size iovs_len) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$fd_readdir(Configuration&, FD, Pointer<u8> buf, Size buf_len, DirCookie cookie) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_renumber(Configuration&, FD from, FD to) { return Errno::NoSys; } +ErrorOr<Result<FileSize>> Implementation::impl$fd_seek(Configuration&, FD, FileDelta offset, Whence whence) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$fd_sync(Configuration&, FD) { return Errno::NoSys; } +ErrorOr<Result<FileSize>> Implementation::impl$fd_tell(Configuration&, FD) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$path_filestat_set_times(Configuration&, FD, LookupFlags, Pointer<u8> path, Size path_len, Timestamp atim, Timestamp mtim, FSTFlags) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$path_link(Configuration&, FD, LookupFlags, Pointer<u8> old_path, Size old_path_len, FD, Pointer<u8> new_path, Size new_path_len) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$path_readlink(Configuration&, FD, LookupFlags, Pointer<u8> path, Size path_len, Pointer<u8> buf, Size buf_len) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$path_remove_directory(Configuration&, FD, Pointer<u8> path, Size path_len) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$path_rename(Configuration&, FD, Pointer<u8> old_path, Size old_path_len, FD, Pointer<u8> new_path, Size new_path_len) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$path_symlink(Configuration&, Pointer<u8> old_path, Size old_path_len, FD, Pointer<u8> new_path, Size new_path_len) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$path_unlink_file(Configuration&, FD, Pointer<u8> path, Size path_len) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$poll_oneoff(Configuration&, ConstPointer<Subscription> in, Pointer<Event> out, Size nsubscriptions) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$proc_raise(Configuration&, Signal) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$sched_yield(Configuration&) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$random_get(Configuration&, Pointer<u8> buf, Size buf_len) { return Errno::NoSys; } +ErrorOr<Result<FD>> Implementation::impl$sock_accept(Configuration&, FD fd, FDFlags fd_flags) { return Errno::NoSys; } +ErrorOr<Result<SockRecvResult>> Implementation::impl$sock_recv(Configuration&, FD fd, Pointer<IOVec> ri_data, Size ri_data_len, RIFlags ri_flags) { return Errno::NoSys; } +ErrorOr<Result<Size>> Implementation::impl$sock_send(Configuration&, FD fd, Pointer<CIOVec> si_data, Size si_data_len, SIFlags si_flags) { return Errno::NoSys; } +ErrorOr<Result<void>> Implementation::impl$sock_shutdown(Configuration&, FD fd, SDFlags how) { return Errno::NoSys; } + +#pragma GCC diagnostic pop + +template<size_t N> +static Array<Bytes, N> address_spans(Span<Value> values, Configuration& configuration) +{ + Array<Bytes, N> result; + auto memory = configuration.store().get(MemoryAddress { 0 })->data().span(); + for (size_t i = 0; i < N; ++i) + result[i] = memory.slice(*values[i].to<i32>()); + return result; +} + +#define ENUMERATE_FUNCTION_NAMES(M) \ + M(args_get) \ + M(args_sizes_get) \ + M(environ_get) \ + M(environ_sizes_get) \ + M(clock_res_get) \ + M(clock_time_get) \ + M(fd_advise) \ + M(fd_allocate) \ + M(fd_close) \ + M(fd_datasync) \ + M(fd_fdstat_get) \ + M(fd_fdstat_set_flags) \ + M(fd_fdstat_set_rights) \ + M(fd_filestat_get) \ + M(fd_filestat_set_size) \ + M(fd_filestat_set_times) \ + M(fd_pread) \ + M(fd_prestat_get) \ + M(fd_prestat_dir_name) \ + M(fd_pwrite) \ + M(fd_read) \ + M(fd_readdir) \ + M(fd_renumber) \ + M(fd_seek) \ + M(fd_sync) \ + M(fd_tell) \ + M(fd_write) \ + M(path_create_directory) \ + M(path_filestat_get) \ + M(path_filestat_set_times) \ + M(path_link) \ + M(path_open) \ + M(path_readlink) \ + M(path_remove_directory) \ + M(path_rename) \ + M(path_symlink) \ + M(path_unlink_file) \ + M(poll_oneoff) \ + M(proc_exit) \ + M(proc_raise) \ + M(sched_yield) \ + M(random_get) \ + M(sock_accept) \ + M(sock_recv) \ + M(sock_send) \ + M(sock_shutdown) + +struct Names { +#define NAME(x) FlyString x; + ENUMERATE_FUNCTION_NAMES(NAME) +#undef NAME + + static ErrorOr<Names> construct() + { + return Names { +#define NAME(x) .x = TRY(FlyString::from_utf8(#x##sv)), + ENUMERATE_FUNCTION_NAMES(NAME) +#undef NAME + }; + } +}; + +ErrorOr<HostFunction> Implementation::function_by_name(StringView name) +{ + auto name_for_comparison = TRY(FlyString::from_utf8(name)); + static auto names = TRY(Names::construct()); + +#define IMPL(x) \ + if (name_for_comparison == names.x) \ + return invocation_of<&Implementation::impl$##x>(#x##sv); + + ENUMERATE_FUNCTION_NAMES(IMPL) + +#undef IMPL + + return Error::from_string_literal("No such host function"); +} + +namespace ABI { + +template<typename T> +auto CompatibleValueType = IsOneOf<T, i8, i16, i32> + ? Wasm::ValueType(Wasm::ValueType::I32) + : Wasm::ValueType(Wasm::ValueType::I64); + +template<typename R, typename... Args, ErrorOr<Result<R>> (Implementation::*impl)(Configuration&, Args...)> +struct InvocationOf<impl> { + HostFunction operator()(Implementation& self, StringView function_name) + { + Vector<ValueType> arguments_types { CompatibleValueType<typename ABI::ToCompatibleValue<Args>::Type>... }; + if constexpr (!IsVoid<R>) { + if constexpr (requires { declval<typename R::SerializationComponents>(); }) { + for_each_type<typename R::SerializationComponents>([&]<typename T>(TypeWrapper<T>) { + arguments_types.append(CompatibleValueType<typename ABI::ToCompatibleValue<Pointer<T>>::Type>); + }); + } else { + arguments_types.append(CompatibleValueType<typename ABI::ToCompatibleValue<Pointer<R>>::Type>); + } + } + + return HostFunction( + [&self, function_name](Configuration& configuration, Vector<Value>& arguments) -> Wasm::Result { + Tuple args = [&]<typename... Ts, auto... Is>(IndexSequence<Is...>) + { + return Tuple { ABI::deserialize(ABI::to_compatible_value<Ts>(arguments[Is]))... }; + } + .template operator()<Args...>(MakeIndexSequence<sizeof...(Args)>()); + + auto result = args.apply_as_args([&](auto&&... impl_args) { return (self.*impl)(configuration, impl_args...); }); + dbgln_if(WASI_DEBUG, "WASI: Called {}", function_name); + + if (result.is_error()) + return Wasm::Trap { DeprecatedString::formatted("Invalid call to {}() = {}", function_name, result.error()) }; + + auto value = result.release_value(); + if (value.is_error()) + return Wasm::Result { Vector { Value { ValueType(ValueType::I32), static_cast<u64>(to_underlying(value.error().value())) } } }; + + if constexpr (!IsVoid<R>) { + // Return values are passed as pointers, after the arguments + if constexpr (requires { &R::serialize_into; }) { + constexpr auto ResultCount = []<auto N>(void(R::*)(Array<Bytes, N>) const) { return N; } + (&R::serialize_into); + ABI::serialize(*value.result(), address_spans<ResultCount>(arguments.span().slice(sizeof...(Args)), configuration)); + } else { + ABI::serialize(*value.result(), address_spans<1>(arguments.span().slice(sizeof...(Args)), configuration)); + } + } + // Return value is errno, we have nothing to return. + return Wasm::Result { Vector<Value> { Value(ValueType(ValueType::I32), 0ull) } }; + }, + FunctionType { + move(arguments_types), + { ValueType(ValueType::I32) }, + }); + } +}; + +}; + +Errno errno_value_from_errno(int value) +{ + switch (value) { +#ifdef ESUCCESS + case ESUCCESS: + return Errno::Success; +#endif + case E2BIG: + return Errno::TooBig; + case EACCES: + return Errno::Access; + case EADDRINUSE: + return Errno::AddressInUse; + case EADDRNOTAVAIL: + return Errno::AddressNotAvailable; + case EAFNOSUPPORT: + return Errno::AFNotSupported; + case EAGAIN: + return Errno::Again; + case EALREADY: + return Errno::Already; + case EBADF: + return Errno::BadF; + case EBUSY: + return Errno::Busy; + case ECANCELED: + return Errno::Canceled; + case ECHILD: + return Errno::Child; + case ECONNABORTED: + return Errno::ConnectionAborted; + case ECONNREFUSED: + return Errno::ConnectionRefused; + case ECONNRESET: + return Errno::ConnectionReset; + case EDEADLK: + return Errno::Deadlock; + case EDESTADDRREQ: + return Errno::DestinationAddressRequired; + case EDOM: + return Errno::Domain; + case EEXIST: + return Errno::Exist; + case EFAULT: + return Errno::Fault; + case EFBIG: + return Errno::FBig; + case EHOSTUNREACH: + return Errno::HostUnreachable; + case EILSEQ: + return Errno::IllegalSequence; + case EINPROGRESS: + return Errno::InProgress; + case EINTR: + return Errno::Interrupted; + case EINVAL: + return Errno::Invalid; + case EIO: + return Errno::IO; + case EISCONN: + return Errno::IsConnected; + case EISDIR: + return Errno::IsDirectory; + case ELOOP: + return Errno::Loop; + case EMFILE: + return Errno::MFile; + case EMLINK: + return Errno::MLink; + case EMSGSIZE: + return Errno::MessageSize; + case ENAMETOOLONG: + return Errno::NameTooLong; + case ENETDOWN: + return Errno::NetworkDown; + case ENETRESET: + return Errno::NetworkReset; + case ENETUNREACH: + return Errno::NetworkUnreachable; + case ENFILE: + return Errno::NFile; + case ENOBUFS: + return Errno::NoBufferSpace; + case ENODEV: + return Errno::NoDevice; + case ENOENT: + return Errno::NoEntry; + case ENOEXEC: + return Errno::NoExec; + case ENOLCK: + return Errno::NoLock; + case ENOMEM: + return Errno::NoMemory; + case ENOPROTOOPT: + return Errno::NoProtocolOption; + case ENOSPC: + return Errno::NoSpace; + case ENOSYS: + return Errno::NoSys; + case ENOTCONN: + return Errno::NotConnected; + case ENOTDIR: + return Errno::NotDirectory; + case ENOTEMPTY: + return Errno::NotEmpty; + case ENOTRECOVERABLE: + return Errno::NotRecoverable; + case ENOTSOCK: + return Errno::NotSocket; + case ENOTSUP: + return Errno::NotSupported; + case ENOTTY: + return Errno::NoTTY; + case ENXIO: + return Errno::NXIO; + case EOVERFLOW: + return Errno::Overflow; + case EPERM: + return Errno::Permission; + case EPIPE: + return Errno::Pipe; + case EPROTO: + return Errno::Protocol; + case EPROTONOSUPPORT: + return Errno::ProtocolNotSupported; + case EPROTOTYPE: + return Errno::ProtocolType; + case ERANGE: + return Errno::Range; + case ESPIPE: + return Errno::SPipe; + case ESRCH: + return Errno::SRCH; + case ESTALE: + return Errno::Stale; + case ETIMEDOUT: + return Errno::TimedOut; + case ETXTBSY: + return Errno::TextBusy; + case EXDEV: + return Errno::XDev; + default: + return Errno::Invalid; + } +} +} diff --git a/Userland/Libraries/LibWasm/Wasi.h b/Userland/Libraries/LibWasm/Wasi.h new file mode 100644 index 0000000000..0dedce568d --- /dev/null +++ b/Userland/Libraries/LibWasm/Wasi.h @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/DistinctNumeric.h> +#include <AK/Endian.h> +#include <AK/Function.h> +#include <AK/LexicalPath.h> +#include <AK/RedBlackTree.h> +#include <AK/String.h> +#include <AK/Vector.h> +#include <LibWasm/AbstractMachine/AbstractMachine.h> +#include <LibWasm/Forward.h> + +namespace Wasm::Wasi::ABI { + +// NOTE: The "real" ABI used in the wild is described by [api.h from libc-bottom-half](https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h) +// This is *not* the same ABI as the one described in the WASI spec, nor is it the same ABI as api.h on wasi-libc/master. +// The highlights of the ABI are: +// - (most) structs are passed as pointers to heap. +// - arrays are fat pointers splat across two arguments +// - return object locations are also passed as arguments, the number of arguments depends on the return type itself: +// - ArgsSizes / EnvironSizes / the return type of sock_recv use two arguments +// - everything else is passed like a normal struct + +template<auto impl> +struct InvocationOf { + HostFunction operator()(Implementation&, StringView name); +}; + +template<typename T, size_t N> +void serialize(T const&, Array<Bytes, N>); + +template<typename T, size_t N> +T deserialize(Array<ReadonlyBytes, N> const&); + +template<typename T> +struct ToCompatibleValue { + using Type = void; +}; + +template<typename T> +struct CompatibleValue { + typename ToCompatibleValue<T>::Type value; + + Wasm::Value to_wasm_value() const; +}; + +template<typename T> +CompatibleValue<T> to_compatible_value(Wasm::Value const&); + +template<typename T> +T deserialize(CompatibleValue<T> const&); + +} + +namespace Wasm::Wasi { + +// NOTE: This is a copy of LittleEndian from Endian.h, +// we can't use those because they have a packed attribute, and depend on it; +// but we want proper alignment on these types. +template<typename T> +class alignas(T) LittleEndian { +public: + constexpr LittleEndian() = default; + + constexpr LittleEndian(T value) + : m_value(AK::convert_between_host_and_little_endian(value)) + { + } + + constexpr operator T() const { return AK::convert_between_host_and_little_endian(m_value); } + constexpr T value() const { return AK::convert_between_host_and_little_endian(m_value); } + + LittleEndian& operator+=(T other) + { + m_value = AK::convert_between_host_and_little_endian(AK::convert_between_host_and_little_endian(m_value) + other); + return *this; + } + + // This returns the internal representation. In this case, that is the value stored in little endian format. + constexpr Bytes bytes() { return Bytes { &m_value, sizeof(m_value) }; } + constexpr ReadonlyBytes bytes() const { return ReadonlyBytes { &m_value, sizeof(m_value) }; } + + void serialize_into(Array<Bytes, 1> bytes) const; + static LittleEndian read_from(Array<ReadonlyBytes, 1> const& bytes); + +private: + T m_value { 0 }; +}; + +using Size = LittleEndian<u32>; +using FileSize = LittleEndian<u64>; +using Timestamp = LittleEndian<u64>; + +namespace Detail { +template<typename> +struct __Pointer_tag; +template<typename> +struct __ConstPointer_tag; +} + +// NOTE: Might need to be updated if WASI ever supports memory64. +using UnderlyingPointerType = u32; + +template<typename T> +using Pointer = DistinctNumeric<LittleEndian<UnderlyingPointerType>, Detail::__Pointer_tag<T>, AK::DistinctNumericFeature::Comparison>; +template<typename T> +using ConstPointer = DistinctNumeric<LittleEndian<UnderlyingPointerType>, Detail::__ConstPointer_tag<T>, AK::DistinctNumericFeature::Comparison>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L70 +enum class ClockID : u32 { + Realtime, + Monotonic, + ProcessCPUTimeID, + ThreadCPUTimeID, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L105 +enum class Errno : u16 { + Success, + TooBig, + Access, + AddressInUse, + AddressNotAvailable, + AFNotSupported, + Again, + Already, + BadF, + BadMessage, + Busy, + Canceled, + Child, + ConnectionAborted, + ConnectionRefused, + ConnectionReset, + Deadlock, + DestinationAddressRequired, + Domain, + DQuot, // Reserved, Unused. + Exist, + Fault, + FBig, + HostUnreachable, + IdentifierRemoved, + IllegalSequence, + InProgress, + Interrupted, + Invalid, + IO, + IsConnected, + IsDirectory, + Loop, + MFile, + MLink, + MessageSize, + MultiHop, // Reserved, Unused. + NameTooLong, + NetworkDown, + NetworkReset, + NetworkUnreachable, + NFile, + NoBufferSpace, + NoDevice, + NoEntry, + NoExec, + NoLock, + NoLink, + NoMemory, + NoMessage, + NoProtocolOption, + NoSpace, + NoSys, + NotConnected, + NotDirectory, + NotEmpty, + NotRecoverable, + NotSocket, + NotSupported, + NoTTY, + NXIO, + Overflow, + OwnerDead, + Permission, + Pipe, + Protocol, + ProtocolNotSupported, + ProtocolType, + Range, + ReadOnlyFS, + SPipe, + SRCH, + Stale, + TimedOut, + TextBusy, + XDev, + NotCapable, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L498 +struct Rights { + using CompatibleType = u64; + + struct Bits { + bool fd_datasync : 1; + bool fd_read : 1; + bool fd_seek : 1; + bool fd_fdstat_set_flags : 1; + bool fd_sync : 1; + bool fd_tell : 1; + bool fd_write : 1; + bool fd_advise : 1; + bool fd_allocate : 1; + bool path_create_directory : 1; + bool path_create_file : 1; + bool path_link_source : 1; + bool path_link_target : 1; + bool path_open : 1; + bool fd_readdir : 1; + bool path_readlink : 1; + bool path_rename_source : 1; + bool path_rename_target : 1; + bool path_filestat_get : 1; + bool path_filestat_set_size : 1; + bool path_filestat_set_times : 1; + bool fd_filestat_get : 1; + bool fd_filestat_set_size : 1; + bool fd_filestat_set_times : 1; + bool path_symlink : 1; + bool path_remove_directory : 1; + bool path_unlink_file : 1; + bool poll_fd_readwrite : 1; + bool sock_shutdown : 1; + bool sock_accept : 1; + + u64 _unused : 34; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static Rights read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L663 +using FD = DistinctNumeric<LittleEndian<u32>, struct __FD_tag, AK::DistinctNumericFeature::Comparison>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L671 +struct IOVec { + Pointer<u8> buf; + Size buf_len; + + static IOVec read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L692 +struct CIOVec { + ConstPointer<u8> buf; + Size buf_len; + + static CIOVec read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L713 +using FileDelta = LittleEndian<i64>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L721 +enum class Whence : u8 { + Set, + Cur, + End, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L746 +using DirCookie = LittleEndian<u64>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L754 +using DirNameLen = LittleEndian<u32>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L762 +using INode = LittleEndian<u64>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L770 +enum class FileType : u8 { + Unknown, + BlockDevice, + CharacterDevice, + Directory, + RegularFile, + SocketDGram, + SocketStream, + SymbolicLink, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L818 +struct DirEnt { + DirCookie d_next; + INode d_ino; + DirNameLen d_namlen; + FileType d_type; + u8 _padding[3] { 0 }; // Not part of the API, but the struct is required to be 24 bytes - even though it has no explicit padding. +}; +static_assert(sizeof(DirEnt) == 24); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L851 +enum class Advice : u8 { + Normal, + Sequential, + Random, + WillNeed, + DontNeed, + NoReuse, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L889 +struct FDFlags { + using CompatibleType = u16; + + struct Bits { + bool append : 1; + bool dsync : 1; + bool nonblock : 1; + bool rsync : 1; + bool sync : 1; + u16 _unused : 11; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static FDFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L924 +struct FDStat { + FileType fs_filetype; + FDFlags fs_flags; + Rights fs_rights_base; + Rights fs_rights_inheriting; + + void serialize_into(Array<Bytes, 1> bytes) const; +}; +static_assert(sizeof(FDStat) == 24); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L959 +using Device = LittleEndian<u64>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L967 +struct FSTFlags { + using CompatibleType = u16; + + struct Bits { + bool atim : 1; + bool atim_now : 1; + bool mtim : 1; + bool mtim_now : 1; + u16 _unused : 12; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static FSTFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; +static_assert(sizeof(FSTFlags) == 2); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L995 +struct LookupFlags { + using CompatibleType = u32; + + struct Bits { + bool symlink_follow : 1; + u32 _unused : 31; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static LookupFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; +static_assert(sizeof(LookupFlags) == 4); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1008 +struct OFlags { + using CompatibleType = u16; + + struct Bits { + bool creat : 1; + bool directory : 1; + bool excl : 1; + bool trunc : 1; + + u16 _unused : 12; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + static OFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; +static_assert(sizeof(OFlags) == 2); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1036 +using LinkCount = LittleEndian<u64>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1044 +struct FileStat { + Device dev; + INode ino; + FileType filetype; + LinkCount nlink; + FileSize size; + Timestamp atim; + Timestamp mtim; + Timestamp ctim; + + void serialize_into(Array<Bytes, 1> bytes) const; +}; +static_assert(sizeof(FileStat) == 64); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1102 +using UserData = LittleEndian<u64>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1110 +enum class EventType : u8 { + Clock, + FDRead, + FDWrite, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1137 +struct EventRWFlags { + using CompatibleType = u16; + + struct Bits { + bool fd_readwrite_hangup : 1; + + u16 _unused : 15; + }; + + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static EventRWFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1151 +struct EventFDReadWrite { + FileSize nbytes; + EventRWFlags flags; + + void serialize_into(Array<Bytes, 1> bytes) const; + static EventFDReadWrite read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1186 +struct Event { + UserData userdata; + Errno errno_; + EventType type; + EventFDReadWrite fd_readwrite; + + void serialize_into(Array<Bytes, 1> bytes) const; + static Event read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1220 +struct SubClockFlags { + using CompatibleType = u16; + + struct Bits { + bool subscription_clock_abstime : 1; + + u16 _unused : 15; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static SubClockFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1237 +struct SubscriptionClock { + ClockID id; + Timestamp timeout; + Timestamp precision; + SubClockFlags flags; + + void serialize_into(Array<Bytes, 1> bytes) const; + static SubscriptionClock read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1272 +struct SubscriptionFDReadWrite { + FD file_descriptor; + + void serialize_into(Array<Bytes, 1> bytes) const; + static SubscriptionFDReadWrite read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1287 +struct SubscriptionU { + u8 tag; + union { + SubscriptionClock clock; + SubscriptionFDReadWrite fd_read; + SubscriptionFDReadWrite fd_write; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static SubscriptionU read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1306 +struct Subscription { + UserData userdata; + SubscriptionU u; + + void serialize_into(Array<Bytes, 1> bytes) const; + static Subscription read_from(Array<ReadonlyBytes, 1> const& bytes); +}; +static_assert(sizeof(Subscription) == 48); +static_assert(alignof(Subscription) == 8); + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1334 +using ExitCode = LittleEndian<u32>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1342 +enum class Signal : u8 { + None, + HUP, + INT, + QUIT, + ILL, + TRAP, + ABRT, + BUS, + FPE, + KILL, + USR1, + SEGV, + USR2, + PIPE, + ALRM, + TERM, + CHLD, + CONT, + STOP, + TSTP, + TTIN, + TTOU, + URG, + XCPU, + XFSZ, + VTALRM, + PROF, + WINCH, + POLL, + PWR, + SYS, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1536 +struct RIFlags { + using CompatibleType = u16; + + struct Bits { + bool recv_peek : 1; + bool recv_waitall : 1; + + u16 _unused : 14; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + static RIFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1554 +struct ROFlags { + using CompatibleType = u16; + + struct Bits { + bool recv_data_truncated : 1; + + u16 _unused : 15; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1568 +using SIFlags = LittleEndian<u16>; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1576 +struct SDFlags { + using CompatibleType = u8; + + struct Bits { + bool rd : 1; + bool wr : 1; + + u8 _unused : 6; + }; + static_assert(sizeof(Bits) == sizeof(CompatibleType)); + + union { + Bits bits; + LittleEndian<CompatibleType> data; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; + static SDFlags read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1594 +enum class PreOpenType : u8 { + Dir, +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1607 +struct PreStatDir { + Size pr_name_len; + + void serialize_into(Array<Bytes, 1> bytes) const; + static PreStatDir read_from(Array<ReadonlyBytes, 1> const& bytes); +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1636 +struct PreStat { + u8 tag; + union { + PreStatDir dir; + }; + + void serialize_into(Array<Bytes, 1> bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1676 +struct ArgsSizes { + Size count; + Size size; + + using SerializationComponents = TypeList<Size, Size>; + + void serialize_into(Array<Bytes, 2> bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L1708 +struct EnvironSizes { + Size count; + Size size; + + using SerializationComponents = TypeList<Size, Size>; + + void serialize_into(Array<Bytes, 2> bytes) const; +}; + +// https://github.com/WebAssembly/wasi-libc/blob/2c2fc9a2fddd0927a66f1c142e65c8dab6f5c5d7/libc-bottom-half/headers/public/wasi/api.h#L2664 +struct SockRecvResult { + Size size; + ROFlags roflags; + + using SerializationComponents = TypeList<Size, ROFlags>; + + void serialize_into(Array<Bytes, 2> bytes) const; +}; + +template<typename TResult, typename Tag = u32> +struct Result { + Result(TResult&& result) + : bits {} + , tag(0) + { + new (&bits) TResult(move(result)); + } + + Result(Errno&& error) + : bits {} + , tag(1) + { + new (&bits) Errno(error); + } + + Optional<TResult&> result() const + { + if (tag == 0) + return *bit_cast<TResult*>(&bits[0]); + return {}; + } + + Optional<Errno&> error() const + { + if (tag == 1) + return *bit_cast<Errno*>(&bits[0]); + return {}; + } + + bool is_error() const { return tag == 1; } + + template<size_t N> + Errno serialize_into(Array<Bytes, N>&& spans) const + { + if (tag == 1) + return error().value(); + + ABI::serialize(*result(), move(spans)); + return Errno::Success; + } + +private: + alignas(max(alignof(TResult), alignof(Errno))) u8 bits[max(sizeof(TResult), sizeof(Errno))]; + LittleEndian<Tag> tag; +}; + +template<typename Tag> +struct Result<void, Tag> { + Result() + : error_bits {} + , tag(0) + { + } + + Result(Errno&& error) + : error_bits {} + , tag(1) + { + new (&error_bits) Errno(error); + } + + Optional<Empty> result() const + { + if (tag == 0) + return { Empty {} }; + return {}; + } + Optional<Errno&> error() const + { + if (tag == 1) + return *bit_cast<Errno*>(&error_bits[0]); + return {}; + } + bool is_error() const { return tag == 1; } + +private: + alignas(Errno) u8 error_bits[sizeof(Errno)]; + LittleEndian<Tag> tag; +}; + +struct Implementation { + struct MappedPath { + LexicalPath host_path; + LexicalPath mapped_path; + mutable Optional<int> opened_fd {}; + }; + + struct Details { + Function<Vector<AK::String>()> provide_arguments; + Function<Vector<AK::String>()> provide_environment; + Function<Vector<MappedPath>()> provide_preopened_directories; + }; + + explicit Implementation(Details&& details) + : provide_arguments(move(details.provide_arguments)) + , provide_environment(move(details.provide_environment)) + , provide_preopened_directories(move(details.provide_preopened_directories)) + { + // Map all of std{in,out,err} by default. + m_fd_map.insert(0, 0); + m_fd_map.insert(1, 1); + m_fd_map.insert(2, 2); + } + + ErrorOr<HostFunction> function_by_name(StringView); + +private: + template<auto impl> + HostFunction invocation_of(StringView name) { return ABI::InvocationOf<impl> {}(*this, name); } + + ErrorOr<Result<void>> impl$args_get(Configuration&, Pointer<Pointer<u8>> argv, Pointer<u8> argv_buf); + ErrorOr<Result<ArgsSizes>> impl$args_sizes_get(Configuration&); + ErrorOr<Result<void>> impl$environ_get(Configuration&, Pointer<Pointer<u8>> environ, Pointer<u8> environ_buf); + ErrorOr<Result<EnvironSizes>> impl$environ_sizes_get(Configuration&); + ErrorOr<Result<Timestamp>> impl$clock_res_get(Configuration&, ClockID id); + ErrorOr<Result<Timestamp>> impl$clock_time_get(Configuration&, ClockID id, Timestamp precision); + ErrorOr<Result<void>> impl$fd_advise(Configuration&, FD, FileSize offset, FileSize len, Advice); + ErrorOr<Result<void>> impl$fd_allocate(Configuration&, FD, FileSize offset, FileSize len); + ErrorOr<Result<void>> impl$fd_close(Configuration&, FD); + ErrorOr<Result<void>> impl$fd_datasync(Configuration&, FD); + ErrorOr<Result<FDStat>> impl$fd_fdstat_get(Configuration&, FD); + ErrorOr<Result<void>> impl$fd_fdstat_set_flags(Configuration&, FD, FDFlags); + ErrorOr<Result<void>> impl$fd_fdstat_set_rights(Configuration&, FD, Rights fs_rights_base, Rights fs_rights_inheriting); + ErrorOr<Result<FileStat>> impl$fd_filestat_get(Configuration&, FD); + ErrorOr<Result<void>> impl$fd_filestat_set_size(Configuration&, FD, FileSize); + ErrorOr<Result<void>> impl$fd_filestat_set_times(Configuration&, FD, Timestamp atim, Timestamp mtim, FSTFlags); + ErrorOr<Result<Size>> impl$fd_pread(Configuration&, FD, Pointer<IOVec> iovs, Size iovs_len, FileSize offset); + ErrorOr<Result<PreStat>> impl$fd_prestat_get(Configuration&, FD); + ErrorOr<Result<void>> impl$fd_prestat_dir_name(Configuration&, FD, Pointer<u8> path, Size path_len); + ErrorOr<Result<Size>> impl$fd_pwrite(Configuration&, FD, Pointer<CIOVec> iovs, Size iovs_len, FileSize offset); + ErrorOr<Result<Size>> impl$fd_read(Configuration&, FD, Pointer<IOVec> iovs, Size iovs_len); + ErrorOr<Result<Size>> impl$fd_readdir(Configuration&, FD, Pointer<u8> buf, Size buf_len, DirCookie cookie); + ErrorOr<Result<void>> impl$fd_renumber(Configuration&, FD from, FD to); + ErrorOr<Result<FileSize>> impl$fd_seek(Configuration&, FD, FileDelta offset, Whence whence); + ErrorOr<Result<void>> impl$fd_sync(Configuration&, FD); + ErrorOr<Result<FileSize>> impl$fd_tell(Configuration&, FD); + ErrorOr<Result<Size>> impl$fd_write(Configuration&, FD, Pointer<CIOVec> iovs, Size iovs_len); + ErrorOr<Result<void>> impl$path_create_directory(Configuration&, FD, Pointer<u8> path, Size path_len); + ErrorOr<Result<FileStat>> impl$path_filestat_get(Configuration&, FD, LookupFlags, ConstPointer<u8> path, Size path_len); + ErrorOr<Result<void>> impl$path_filestat_set_times(Configuration&, FD, LookupFlags, Pointer<u8> path, Size path_len, Timestamp atim, Timestamp mtim, FSTFlags); + ErrorOr<Result<void>> impl$path_link(Configuration&, FD, LookupFlags, Pointer<u8> old_path, Size old_path_len, FD, Pointer<u8> new_path, Size new_path_len); + ErrorOr<Result<FD>> impl$path_open(Configuration&, FD, LookupFlags, Pointer<u8> path, Size path_len, OFlags, Rights fs_rights_base, Rights fs_rights_inheriting, FDFlags fd_flags); + ErrorOr<Result<Size>> impl$path_readlink(Configuration&, FD, LookupFlags, Pointer<u8> path, Size path_len, Pointer<u8> buf, Size buf_len); + ErrorOr<Result<void>> impl$path_remove_directory(Configuration&, FD, Pointer<u8> path, Size path_len); + ErrorOr<Result<void>> impl$path_rename(Configuration&, FD, Pointer<u8> old_path, Size old_path_len, FD, Pointer<u8> new_path, Size new_path_len); + ErrorOr<Result<void>> impl$path_symlink(Configuration&, Pointer<u8> old_path, Size old_path_len, FD, Pointer<u8> new_path, Size new_path_len); + ErrorOr<Result<void>> impl$path_unlink_file(Configuration&, FD, Pointer<u8> path, Size path_len); + ErrorOr<Result<Size>> impl$poll_oneoff(Configuration&, ConstPointer<Subscription> in, Pointer<Event> out, Size nsubscriptions); + ErrorOr<Result<void>> impl$proc_exit(Configuration&, ExitCode); // Note: noreturn. + ErrorOr<Result<void>> impl$proc_raise(Configuration&, Signal); + ErrorOr<Result<void>> impl$sched_yield(Configuration&); + ErrorOr<Result<void>> impl$random_get(Configuration&, Pointer<u8> buf, Size buf_len); + ErrorOr<Result<FD>> impl$sock_accept(Configuration&, FD fd, FDFlags fd_flags); + ErrorOr<Result<SockRecvResult>> impl$sock_recv(Configuration&, FD fd, Pointer<IOVec> ri_data, Size ri_data_len, RIFlags ri_flags); + ErrorOr<Result<Size>> impl$sock_send(Configuration&, FD fd, Pointer<CIOVec> si_data, Size ri_data_len, SIFlags si_flags); + ErrorOr<Result<void>> impl$sock_shutdown(Configuration&, FD fd, SDFlags how); + + Vector<AK::String> const& arguments() const; + Vector<AK::String> const& environment() const; + Vector<MappedPath> const& preopened_directories() const; + + using PreopenedDirectoryDescriptor = DistinctNumeric<LittleEndian<size_t>, struct PreopenedDirectoryDescriptor_tag, AK::DistinctNumericFeature::Comparison, AK::DistinctNumericFeature::CastToUnderlying, AK::DistinctNumericFeature::Increment>; + using UnmappedDescriptor = DistinctNumeric<LittleEndian<size_t>, struct UnmappedDescriptor_tag, AK::DistinctNumericFeature::Comparison, AK::DistinctNumericFeature::CastToUnderlying>; + using MappedDescriptor = Variant<u32, PreopenedDirectoryDescriptor>; + using Descriptor = Variant<u32, PreopenedDirectoryDescriptor, UnmappedDescriptor>; + + Descriptor map_fd(FD); + +public: + Function<Vector<AK::String>()> provide_arguments; + Function<Vector<AK::String>()> provide_environment; + Function<Vector<MappedPath>()> provide_preopened_directories; + +private: + struct Cache { + Optional<Vector<AK::String>> cached_arguments; + Optional<Vector<AK::String>> cached_environment; + Optional<Vector<MappedPath>> cached_preopened_directories; + }; + + mutable Cache cache {}; + + RedBlackTree<u32, MappedDescriptor> m_fd_map; + size_t m_first_unmapped_preopened_directory_index { 0 }; +}; + +#undef IMPL + +} + +namespace Wasm::Wasi::ABI { + +template<typename T, typename... Args> +struct ToCompatibleValue<DistinctNumeric<T, Args...>> { + using Type = typename ToCompatibleValue<T>::Type; +}; + +template<typename T> +struct ToCompatibleValue<LittleEndian<T>> { + using Type = MakeSigned<T>; +}; + +template<typename T> +requires(requires { declval<typename T::CompatibleType>(); }) +struct ToCompatibleValue<T> { + using Type = MakeSigned<typename T::CompatibleType>; +}; + +template<Integral T> +struct ToCompatibleValue<T> { + using Type = MakeSigned<T>; +}; + +template<Enum T> +struct ToCompatibleValue<T> { + using Type = MakeSigned<UnderlyingType<T>>; +}; + +} + +template<typename T> +struct AK::Formatter<Wasm::Wasi::LittleEndian<T>> : AK::Formatter<T> { + ErrorOr<void> format(FormatBuilder& builder, Wasm::Wasi::LittleEndian<T> value) + { + return Formatter<T>::format(builder, value.operator T()); + } +}; |