From a37b8f0ff92d0efa9290a44cc1a82b003729f373 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Sat, 9 May 2026 13:46:53 -0400 Subject: [PATCH 1/9] Fork Wasmtime's wasi-common --- Cargo.lock | 86 +- Cargo.toml | 17 +- crates/wasi-common/Cargo.toml | 89 + crates/wasi-common/LICENSE | 220 +++ crates/wasi-common/README.md | 61 + crates/wasi-common/src/clocks.rs | 73 + crates/wasi-common/src/ctx.rs | 128 ++ crates/wasi-common/src/dir.rs | 140 ++ crates/wasi-common/src/error.rs | 26 + crates/wasi-common/src/file.rs | 269 +++ crates/wasi-common/src/lib.rs | 190 ++ crates/wasi-common/src/pipe.rs | 201 +++ crates/wasi-common/src/random.rs | 51 + crates/wasi-common/src/sched.rs | 97 + crates/wasi-common/src/sched/subscription.rs | 80 + crates/wasi-common/src/snapshots/mod.rs | 24 + crates/wasi-common/src/snapshots/preview_0.rs | 1082 +++++++++++ crates/wasi-common/src/snapshots/preview_1.rs | 1581 +++++++++++++++++ .../src/snapshots/preview_1/error.rs | 264 +++ crates/wasi-common/src/string_array.rs | 75 + crates/wasi-common/src/sync/clocks.rs | 41 + crates/wasi-common/src/sync/dir.rs | 335 ++++ crates/wasi-common/src/sync/file.rs | 250 +++ crates/wasi-common/src/sync/mod.rs | 137 ++ crates/wasi-common/src/sync/net.rs | 393 ++++ crates/wasi-common/src/sync/sched.rs | 40 + crates/wasi-common/src/sync/sched/unix.rs | 83 + crates/wasi-common/src/sync/sched/windows.rs | 220 +++ crates/wasi-common/src/sync/stdio.rs | 196 ++ crates/wasi-common/src/table.rs | 114 ++ crates/wasi-common/src/tokio/dir.rs | 220 +++ crates/wasi-common/src/tokio/file.rs | 247 +++ crates/wasi-common/src/tokio/mod.rs | 135 ++ crates/wasi-common/src/tokio/net.rs | 6 + crates/wasi-common/src/tokio/sched.rs | 35 + crates/wasi-common/src/tokio/sched/unix.rs | 103 ++ crates/wasi-common/src/tokio/sched/windows.rs | 15 + crates/wasi-common/src/tokio/stdio.rs | 1 + .../wasi-common/witx/preview0/typenames.witx | 746 ++++++++ .../witx/preview0/wasi_unstable.witx | 513 ++++++ .../wasi-common/witx/preview1/typenames.witx | 750 ++++++++ .../witx/preview1/wasi_snapshot_preview1.witx | 521 ++++++ crates/wasip1/Cargo.toml | 4 +- crates/wasip1/src/fs/dev/mod.rs | 41 +- crates/wasip1/src/fs/dev/root.rs | 6 +- crates/wasip1/src/fs/dev/wakeup.rs | 24 +- crates/wasip1/src/fs/mod.rs | 224 ++- crates/wasip1/src/lib.rs | 29 +- crates/wasmtime/Cargo.toml | 3 +- crates/wasmtime/src/runtime.rs | 2 +- crates/wasmtime/src/state.rs | 2 +- web/Cargo.toml | 2 +- 52 files changed, 9996 insertions(+), 196 deletions(-) create mode 100644 crates/wasi-common/Cargo.toml create mode 100644 crates/wasi-common/LICENSE create mode 100644 crates/wasi-common/README.md create mode 100644 crates/wasi-common/src/clocks.rs create mode 100644 crates/wasi-common/src/ctx.rs create mode 100644 crates/wasi-common/src/dir.rs create mode 100644 crates/wasi-common/src/error.rs create mode 100644 crates/wasi-common/src/file.rs create mode 100644 crates/wasi-common/src/lib.rs create mode 100644 crates/wasi-common/src/pipe.rs create mode 100644 crates/wasi-common/src/random.rs create mode 100644 crates/wasi-common/src/sched.rs create mode 100644 crates/wasi-common/src/sched/subscription.rs create mode 100644 crates/wasi-common/src/snapshots/mod.rs create mode 100644 crates/wasi-common/src/snapshots/preview_0.rs create mode 100644 crates/wasi-common/src/snapshots/preview_1.rs create mode 100644 crates/wasi-common/src/snapshots/preview_1/error.rs create mode 100644 crates/wasi-common/src/string_array.rs create mode 100644 crates/wasi-common/src/sync/clocks.rs create mode 100644 crates/wasi-common/src/sync/dir.rs create mode 100644 crates/wasi-common/src/sync/file.rs create mode 100644 crates/wasi-common/src/sync/mod.rs create mode 100644 crates/wasi-common/src/sync/net.rs create mode 100644 crates/wasi-common/src/sync/sched.rs create mode 100644 crates/wasi-common/src/sync/sched/unix.rs create mode 100644 crates/wasi-common/src/sync/sched/windows.rs create mode 100644 crates/wasi-common/src/sync/stdio.rs create mode 100644 crates/wasi-common/src/table.rs create mode 100644 crates/wasi-common/src/tokio/dir.rs create mode 100644 crates/wasi-common/src/tokio/file.rs create mode 100644 crates/wasi-common/src/tokio/mod.rs create mode 100644 crates/wasi-common/src/tokio/net.rs create mode 100644 crates/wasi-common/src/tokio/sched.rs create mode 100644 crates/wasi-common/src/tokio/sched/unix.rs create mode 100644 crates/wasi-common/src/tokio/sched/windows.rs create mode 100644 crates/wasi-common/src/tokio/stdio.rs create mode 100644 crates/wasi-common/witx/preview0/typenames.witx create mode 100644 crates/wasi-common/witx/preview0/wasi_unstable.witx create mode 100644 crates/wasi-common/witx/preview1/typenames.witx create mode 100644 crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx diff --git a/Cargo.lock b/Cargo.lock index 543df5eb..a1ceb36c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4379,12 +4379,12 @@ dependencies = [ [[package]] name = "object" -version = "0.39.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63944c133d03f44e75866bbd160b95af0ec3f6a13d936d69d31c81078cbc5baf" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" dependencies = [ "crc32fast", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "indexmap", "memchr", ] @@ -7265,31 +7265,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi-common" -version = "45.0.0" -dependencies = [ - "async-trait", - "bitflags 2.11.0", - "cap-fs-ext", - "cap-std", - "cap-time-ext", - "fs-set-times", - "io-extras", - "io-lifetimes", - "libc", - "log", - "rand 0.10.1", - "rustix 1.1.4", - "system-interface", - "thiserror 2.0.18", - "tracing", - "wasmtime", - "wasmtime-environ", - "wiggle", - "windows-sys 0.61.2", -] - [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -7498,7 +7473,7 @@ dependencies = [ "log", "mach2", "memfd", - "object 0.39.0", + "object 0.39.1", "once_cell", "postcard", "pulley-interpreter", @@ -7538,7 +7513,7 @@ dependencies = [ "hashbrown 0.17.0", "indexmap", "log", - "object 0.39.0", + "object 0.39.1", "postcard", "rustc-demangle", "semver", @@ -7612,7 +7587,7 @@ dependencies = [ "gimli", "itertools 0.14.0", "log", - "object 0.39.0", + "object 0.39.1", "pulley-interpreter", "smallvec", "target-lexicon 0.13.5", @@ -7674,7 +7649,7 @@ dependencies = [ "cfg-if", "cranelift-codegen", "log", - "object 0.39.0", + "object 0.39.1", "wasmtime-environ", ] @@ -7694,7 +7669,7 @@ dependencies = [ "cranelift-codegen", "gimli", "log", - "object 0.39.0", + "object 0.39.1", "target-lexicon 0.13.5", "wasmparser 0.246.2", "wasmtime-environ", @@ -7751,17 +7726,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "wasmtime-wasi-threads" -version = "45.0.0" -dependencies = [ - "log", - "rand 0.10.1", - "wasi-common", - "wasmtime", - "wasmtime-wasi", -] - [[package]] name = "wast" version = "35.0.2" @@ -8413,6 +8377,32 @@ dependencies = [ "webrogue-wasmtime", ] +[[package]] +name = "webrogue-wasi-common" +version = "0.1.0" +dependencies = [ + "async-trait", + "bitflags 2.11.0", + "cap-fs-ext", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes", + "libc", + "log", + "rand 0.10.1", + "rustix 1.1.4", + "system-interface", + "thiserror 2.0.18", + "tokio", + "tracing", + "wasmtime", + "wasmtime-internal-core", + "wiggle", + "windows-sys 0.61.2", +] + [[package]] name = "webrogue-wasip1" version = "0.1.0" @@ -8424,7 +8414,7 @@ dependencies = [ "rand 0.10.1", "rand_core 0.10.0", "rustix 1.1.4", - "wasi-common", + "webrogue-wasi-common", "webrogue-wrapp", "wiggle", "windows-sys 0.61.2", @@ -8435,12 +8425,11 @@ name = "webrogue-wasmtime" version = "0.1.0" dependencies = [ "anyhow", - "wasi-common", "wasmtime", - "wasmtime-wasi-threads", "webrogue-aot-data", "webrogue-gfx", "webrogue-gfxstream", + "webrogue-wasi-common", "webrogue-wasip1", "webrogue-wrapp", "wiggle", @@ -8454,7 +8443,6 @@ dependencies = [ "getrandom 0.2.17", "lazy_static", "parking_lot", - "wasi-common", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -8463,6 +8451,7 @@ dependencies = [ "web-sys", "webrogue-gfx", "webrogue-gfx-winit", + "webrogue-wasi-common", "webrogue-wasip1", "webrogue-wrapp", "wiggle", @@ -8720,6 +8709,7 @@ dependencies = [ "wasmtime", "wasmtime-environ", "wiggle-macro", + "witx", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 746deb8c..a1456b43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ members = [ "crates/launcher/server-openapi", "crates/launcher-dev", "crates/lld", + "crates/wasi-common", "crates/wasip1", "crates/wasmtime", "crates/wrapp", @@ -100,6 +101,7 @@ webrogue-gfx-winit = { path = "crates/gfx-winit" } webrogue-gfxstream = { path = "crates/gfxstream" } webrogue-gfxstream-lib = { path = "crates/gfxstream-lib" } webrogue-lld = { path = "crates/lld" } +webrogue-wasi-common = { path = "crates/wasi-common", default-features = false } webrogue-wasip1 = { path = "crates/wasip1" } webrogue-wrapp = { path = "crates/wrapp" } webrogue-launcher = { path = "crates/launcher" } @@ -108,11 +110,10 @@ webrogue-launcher-server-openapi = { path = "crates/launcher/server-openapi", de # Wasmtime crates wasmtime = { path = "external/wasmtime/crates/wasmtime", default-features = false } wiggle = { path = "external/wasmtime/crates/wiggle", default-features = false } -wasi-common = { path = "external/wasmtime/crates/wasi-common", default-features = false } wasmtime-environ = { path = "external/wasmtime/crates/environ", default-features = false } wiggle-generate = { path = "external/wasmtime/crates/wiggle/generate", default-features = false } -wasmtime-wasi-threads = { path = "external/wasmtime/crates/wasi-threads" } wasmtime-internal-debugger = { path = "external/wasmtime/crates/debugger" } +wasmtime-internal-core = { path = "external/wasmtime/crates/core" } # other crates image = { version = "0.25", default-features = false } @@ -195,6 +196,18 @@ spinners = "4.2.0" headers = "0.4" axum-extra = { version = "0.12" } rustls = { version = "0.23", default-features = false } +bitflags = "2.9.4" + +# To remove +cap-fs-ext = "3.4.5" +cap-time-ext = "3.4.5" +fs-set-times = "0.20.3" +io-lifetimes = { version = "2.0.3", default-features = false } +system-interface = { version = "0.27.3", features = ["cap_std_impls"] } +thiserror = "2.0.17" +tempfile = "3.27.0" +test-log = { version = "0.2.18", default-features = false, features = ["trace"] } +cap-std = "3.4.5" [profile.dev] panic = "abort" diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml new file mode 100644 index 00000000..06891bf0 --- /dev/null +++ b/crates/wasi-common/Cargo.toml @@ -0,0 +1,89 @@ +[package] +name = "webrogue-wasi-common" +version.workspace = true +authors = ["The Wasmtime Project Developers", "Artem Borovik"] +description = "WASI implementation in Rust" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/webrogue-runtime/webrogue" +readme = "README.md" +edition.workspace = true +include = ["src/**/*", "tests/**/*", "witx", "README.md", "LICENSE", "build.rs"] + +[dependencies] +wasmtime-internal-core = { workspace = true } +thiserror = { workspace = true } +wiggle = { workspace = true } +tracing = { workspace = true } +bitflags = { workspace = true } +log = { workspace = true } +rand = { workspace = true, features = ['std_rng', 'thread_rng'] } +async-trait = { workspace = true } + +# Optional, enabled by wasmtime feature: +wasmtime = { workspace = true, optional = true, features = ['runtime', 'component-model'] } +# Optional, enabled by sync feature: +cap-fs-ext = { workspace = true, optional = true } +cap-time-ext = { workspace = true, optional = true } +fs-set-times = { workspace = true, optional = true } +system-interface = { workspace = true, features = ["cap_std_impls"], optional = true } +io-lifetimes = { workspace = true, optional = true } +# Optional, enabled by tokio feature: +tokio = { workspace = true, features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"], optional = true } + +cap-std = { workspace = true, optional = true } + +# Optional, enabled by exit feature: +libc = { workspace = true, optional = true } + +[target.'cfg(unix)'.dependencies] +rustix = { workspace = true, features = ["fs", "event"] } + +[target.'cfg(windows)'.dependencies] +io-extras = { workspace = true } +rustix = { workspace = true, features = ["net"] } + +[target.'cfg(windows)'.dependencies.windows-sys] +workspace = true +features = [ + "Win32_Foundation", + "Win32_Networking_WinSock", +] + +[features] +default = ["trace_log", "wasmtime", "sync"] +# This feature enables the `tracing` logs in the calls to target the `log` +# ecosystem of backends (e.g. `env_logger`. Disable this if you want to use +# `tracing-subscriber`. +trace_log = [ "wiggle/tracing_log", "tracing/log" ] +# Need to make the wiggle_metadata feature available to consumers of this +# crate if they want the snapshots to have metadata available. +wiggle_metadata = ["wiggle/wiggle_metadata"] +# This feature enables integration with wasmtime. +wasmtime = [ + "dep:wasmtime", + "wiggle/wasmtime", +] +# This feature enables an implementation of the Wasi traits for a +# synchronous wasmtime embedding. +sync = [ + "dep:cap-fs-ext", + "dep:cap-time-ext", + "dep:fs-set-times", + "dep:system-interface", + "dep:io-lifetimes", + "use_cap_std", +] +tokio = [ + "sync", + "wasmtime/async", + "wiggle/wasmtime_async", + "dep:tokio", + "use_cap_std", +] +use_cap_std = ["dep:cap-std"] +exit = [ "wasmtime", "dep:libc" ] + +[package.metadata.docs.rs] +all-features = true diff --git a/crates/wasi-common/LICENSE b/crates/wasi-common/LICENSE new file mode 100644 index 00000000..f9d81955 --- /dev/null +++ b/crates/wasi-common/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/crates/wasi-common/README.md b/crates/wasi-common/README.md new file mode 100644 index 00000000..a6770dbb --- /dev/null +++ b/crates/wasi-common/README.md @@ -0,0 +1,61 @@ +
+

wasi-common

+ +A Bytecode Alliance project + +

+ A library providing a common implementation of WASI hostcalls for re-use in any WASI-enabled runtime. +

+ +

+ Crates.io version + Download + docs.rs docs +

+
+ +The `wasi-common` crate will ultimately serve as a library providing a common implementation of +WASI hostcalls for re-use in any WASI (and potentially non-WASI) runtimes +such as [Wasmtime] and [Lucet]. + +The library is an adaption of [lucet-wasi] crate from the [Lucet] project, and it is +currently based on [40ae1df][lucet-wasi-tracker] git revision. + +Please note that the library requires Rust compiler version at least 1.37.0. + +[Wasmtime]: https://github.com/bytecodealliance/wasmtime +[Lucet]: https://github.com/fastly/lucet +[lucet-wasi]: https://github.com/fastly/lucet/tree/master/lucet-wasi +[lucet-wasi-tracker]: https://github.com/fastly/lucet/commit/40ae1df64536250a2b6ab67e7f167d22f4aa7f94 + +## Supported syscalls + +### *nix +In our *nix implementation, we currently support the entire [WASI API] +with the exception of the `proc_raise` hostcall, as it is expected to +be dropped entirely from WASI. + +[WASI API]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md + +### Windows +In our Windows implementation, we currently support the minimal subset of [WASI API] +which allows for running the very basic "Hello world!" style WASM apps. More coming shortly, +so stay tuned! + +## Development hints +When testing the crate, you may want to enable and run full wasm32 integration testsuite. This +requires `wasm32-wasip1` target installed which can be done as follows using [rustup] + +``` +rustup target add wasm32-wasip1 +``` + +[rustup]: https://rustup.rs + +Now, you should be able to run the integration testsuite by running `cargo test` on the +`test-programs` package with `test-programs/test_programs` feature enabled: + +``` +cargo test --features test-programs/test_programs --package test-programs +``` + diff --git a/crates/wasi-common/src/clocks.rs b/crates/wasi-common/src/clocks.rs new file mode 100644 index 00000000..f3535d45 --- /dev/null +++ b/crates/wasi-common/src/clocks.rs @@ -0,0 +1,73 @@ +use crate::{Error, ErrorExt}; +#[cfg(feature = "use_cap_std")] +use cap_std::time::{Duration, Instant, SystemTime}; +#[cfg(not(feature = "use_cap_std"))] +use std::time::{Duration, Instant, SystemTime}; + +pub enum SystemTimeSpec { + SymbolicNow, + Absolute(SystemTime), +} + +pub trait WasiSystemClock: Send + Sync { + fn resolution(&self) -> Duration; + fn now(&self, precision: Duration) -> SystemTime; +} + +pub trait WasiMonotonicClock: Send + Sync { + fn resolution(&self) -> Duration; + fn now(&self, precision: Duration) -> Instant; +} + +pub struct WasiMonotonicOffsetClock { + #[cfg(feature = "use_cap_std")] + pub creation_time: cap_std::time::Instant, + #[cfg(not(feature = "use_cap_std"))] + pub creation_time: std::time::Instant, + pub abs_clock: Box, +} + +impl WasiMonotonicOffsetClock { + pub fn new(clock: impl 'static + WasiMonotonicClock) -> Self { + Self { + creation_time: clock.now(clock.resolution()), + abs_clock: Box::new(clock), + } + } +} + +pub struct WasiClocks { + pub system: Option>, + pub monotonic: Option, +} + +impl WasiClocks { + pub fn new() -> Self { + Self { + system: None, + monotonic: None, + } + } + + pub fn with_system(mut self, clock: impl 'static + WasiSystemClock) -> Self { + self.system = Some(Box::new(clock)); + self + } + + pub fn with_monotonic(mut self, clock: impl 'static + WasiMonotonicClock) -> Self { + self.monotonic = Some(WasiMonotonicOffsetClock::new(clock)); + self + } + + pub fn system(&self) -> Result<&dyn WasiSystemClock, Error> { + self.system + .as_deref() + .ok_or_else(|| Error::badf().context("system clock is not supported")) + } + + pub fn monotonic(&self) -> Result<&WasiMonotonicOffsetClock, Error> { + self.monotonic + .as_ref() + .ok_or_else(|| Error::badf().context("monotonic clock is not supported")) + } +} diff --git a/crates/wasi-common/src/ctx.rs b/crates/wasi-common/src/ctx.rs new file mode 100644 index 00000000..3894b232 --- /dev/null +++ b/crates/wasi-common/src/ctx.rs @@ -0,0 +1,128 @@ +use crate::clocks::WasiClocks; +use crate::dir::{DirEntry, WasiDir}; +use crate::file::{FileAccessMode, FileEntry, WasiFile}; +use crate::sched::WasiSched; +use crate::string_array::StringArray; +use crate::table::Table; +use crate::{Error, StringArrayError}; +use rand::Rng; +use std::ops::Deref; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +/// An `Arc`-wrapper around the wasi-common context to allow mutable access to +/// the file descriptor table. This wrapper is only necessary due to the +/// signature of `fd_fdstat_set_flags`; if that changes, there are a variety of +/// improvements that can be made (TODO: +/// . +#[derive(Clone)] +pub struct WasiCtx(Arc); + +pub struct WasiCtxInner { + pub args: StringArray, + pub env: StringArray, + // TODO: this mutex should not be necessary, it forces threads to serialize + // their access to randomness unnecessarily + // (https://github.com/bytecodealliance/wasmtime/issues/5660). + pub random: Mutex>, + pub clocks: WasiClocks, + pub sched: Box, + pub table: Table, +} + +impl WasiCtx { + pub fn new( + random: Box, + clocks: WasiClocks, + sched: Box, + table: Table, + ) -> Self { + let s = WasiCtx(Arc::new(WasiCtxInner { + args: StringArray::new(), + env: StringArray::new(), + random: Mutex::new(random), + clocks, + sched, + table, + })); + s.set_stdin(Box::new(crate::pipe::ReadPipe::new(std::io::empty()))); + s.set_stdout(Box::new(crate::pipe::WritePipe::new(std::io::sink()))); + s.set_stderr(Box::new(crate::pipe::WritePipe::new(std::io::sink()))); + s + } + + pub fn insert_file(&self, fd: u32, file: Box, access_mode: FileAccessMode) { + self.table() + .insert_at(fd, Arc::new(FileEntry::new(file, access_mode))); + } + + pub fn push_file( + &self, + file: Box, + access_mode: FileAccessMode, + ) -> Result { + self.table() + .push(Arc::new(FileEntry::new(file, access_mode))) + } + + pub fn insert_dir(&self, fd: u32, dir: Box, path: PathBuf) { + self.table() + .insert_at(fd, Arc::new(DirEntry::new(Some(path), dir))); + } + + pub fn push_dir(&self, dir: Box, path: PathBuf) -> Result { + self.table().push(Arc::new(DirEntry::new(Some(path), dir))) + } + + pub fn table(&self) -> &Table { + &self.table + } + + pub fn table_mut(&mut self) -> Option<&mut Table> { + Arc::get_mut(&mut self.0).map(|c| &mut c.table) + } + + pub fn push_arg(&mut self, arg: &str) -> Result<(), StringArrayError> { + let s = Arc::get_mut(&mut self.0).expect( + "`push_arg` should only be used during initialization before the context is cloned", + ); + s.args.push(arg.to_owned()) + } + + pub fn push_env(&mut self, var: &str, value: &str) -> Result<(), StringArrayError> { + let s = Arc::get_mut(&mut self.0).expect( + "`push_env` should only be used during initialization before the context is cloned", + ); + s.env.push(format!("{var}={value}"))?; + Ok(()) + } + + pub fn set_stdin(&self, f: Box) { + self.insert_file(0, f, FileAccessMode::READ); + } + + pub fn set_stdout(&self, f: Box) { + self.insert_file(1, f, FileAccessMode::WRITE); + } + + pub fn set_stderr(&self, f: Box) { + self.insert_file(2, f, FileAccessMode::WRITE); + } + + pub fn push_preopened_dir( + &self, + dir: Box, + path: impl AsRef, + ) -> Result<(), Error> { + self.table() + .push(Arc::new(DirEntry::new(Some(path.as_ref().to_owned()), dir)))?; + Ok(()) + } +} + +impl Deref for WasiCtx { + type Target = WasiCtxInner; + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/wasi-common/src/dir.rs b/crates/wasi-common/src/dir.rs new file mode 100644 index 00000000..058e0d3d --- /dev/null +++ b/crates/wasi-common/src/dir.rs @@ -0,0 +1,140 @@ +use crate::file::{FdFlags, FileType, Filestat, OFlags, WasiFile}; +use crate::{Error, ErrorExt, SystemTimeSpec}; +use std::any::Any; +use std::path::PathBuf; +use std::sync::Arc; + +pub enum OpenResult { + File(Box), + Dir(Box), +} + +#[async_trait::async_trait] +pub trait WasiDir: Send + Sync { + fn as_any(&self) -> &dyn Any; + + async fn open_file( + &self, + _symlink_follow: bool, + _path: &str, + _oflags: OFlags, + _read: bool, + _write: bool, + _fdflags: FdFlags, + ) -> Result { + Err(Error::not_supported()) + } + + async fn create_dir(&self, _path: &str) -> Result<(), Error> { + Err(Error::not_supported()) + } + + // XXX the iterator here needs to be asyncified as well! + async fn readdir( + &self, + _cursor: ReaddirCursor, + ) -> Result> + Send>, Error> { + Err(Error::not_supported()) + } + + async fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<(), Error> { + Err(Error::not_supported()) + } + + async fn remove_dir(&self, _path: &str) -> Result<(), Error> { + Err(Error::not_supported()) + } + + async fn unlink_file(&self, _path: &str) -> Result<(), Error> { + Err(Error::not_supported()) + } + + async fn read_link(&self, _path: &str) -> Result { + Err(Error::not_supported()) + } + + async fn get_filestat(&self) -> Result { + Err(Error::not_supported()) + } + + async fn get_path_filestat( + &self, + _path: &str, + _follow_symlinks: bool, + ) -> Result { + Err(Error::not_supported()) + } + + async fn rename( + &self, + _path: &str, + _dest_dir: &dyn WasiDir, + _dest_path: &str, + ) -> Result<(), Error> { + Err(Error::not_supported()) + } + + async fn hard_link( + &self, + _path: &str, + _target_dir: &dyn WasiDir, + _target_path: &str, + ) -> Result<(), Error> { + Err(Error::not_supported()) + } + + async fn set_times( + &self, + _path: &str, + _atime: Option, + _mtime: Option, + _follow_symlinks: bool, + ) -> Result<(), Error> { + Err(Error::not_supported()) + } +} + +pub(crate) struct DirEntry { + preopen_path: Option, // precondition: PathBuf is valid unicode + pub dir: Box, +} + +impl DirEntry { + pub fn new(preopen_path: Option, dir: Box) -> Self { + DirEntry { preopen_path, dir } + } + pub fn preopen_path(&self) -> &Option { + &self.preopen_path + } +} + +pub(crate) trait TableDirExt { + fn get_dir(&self, fd: u32) -> Result, Error>; +} + +impl TableDirExt for crate::table::Table { + fn get_dir(&self, fd: u32) -> Result, Error> { + self.get(fd) + } +} + +#[derive(Debug, Clone)] +pub struct ReaddirEntity { + pub next: ReaddirCursor, + pub inode: u64, + pub name: String, + pub filetype: FileType, +} + +#[derive(Debug, Copy, Clone)] +pub struct ReaddirCursor(u64); +impl From for ReaddirCursor { + fn from(c: u64) -> ReaddirCursor { + ReaddirCursor(c) + } +} +impl From for u64 { + fn from(c: ReaddirCursor) -> u64 { + c.0 + } +} diff --git a/crates/wasi-common/src/error.rs b/crates/wasi-common/src/error.rs new file mode 100644 index 00000000..a4e52a4e --- /dev/null +++ b/crates/wasi-common/src/error.rs @@ -0,0 +1,26 @@ +//! wasi-common uses an [`Error`] type which represents either a preview 1 [`Errno`] enum, on +//! [`wasmtime_environ::error::Error`] for trapping execution. +//! +//! The user can construct an [`Error`] out of an [`Errno`] using the `From`/`Into` traits. +//! They may also use [`Error::trap`] to construct an error that traps execution. The contents +//! can be inspected with [`Error::downcast`] and [`Error::downcast_ref`]. Additional context +//! can be provided with the [`Error::context`] method. This context is only observable with the +//! `Display` and `Debug` impls of the error. + +pub use crate::snapshots::preview_1::error::{Error, ErrorExt}; +use std::fmt; + +/// An error returned from the `proc_exit` host syscall. +/// +/// Embedders can test if an error returned from wasm is this error, in which +/// case it may signal a non-fatal trap. +#[derive(Debug)] +pub struct I32Exit(pub i32); + +impl fmt::Display for I32Exit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Exited with i32 exit status {}", self.0) + } +} + +impl std::error::Error for I32Exit {} diff --git a/crates/wasi-common/src/file.rs b/crates/wasi-common/src/file.rs new file mode 100644 index 00000000..514892e5 --- /dev/null +++ b/crates/wasi-common/src/file.rs @@ -0,0 +1,269 @@ +use crate::{Error, ErrorExt, SystemTimeSpec}; +use bitflags::bitflags; +use std::any::Any; +use std::sync::Arc; + +#[async_trait::async_trait] +pub trait WasiFile: Send + Sync { + fn as_any(&self) -> &dyn Any; + async fn get_filetype(&self) -> Result; + + #[cfg(unix)] + fn pollable(&self) -> Option> { + None + } + + #[cfg(windows)] + fn pollable(&self) -> Option { + None + } + + fn isatty(&self) -> bool { + false + } + + async fn sock_accept(&self, _fdflags: FdFlags) -> Result, Error> { + Err(Error::badf()) + } + + async fn sock_recv<'a>( + &self, + _ri_data: &mut [std::io::IoSliceMut<'a>], + _ri_flags: RiFlags, + ) -> Result<(u64, RoFlags), Error> { + Err(Error::badf()) + } + + async fn sock_send<'a>( + &self, + _si_data: &[std::io::IoSlice<'a>], + _si_flags: SiFlags, + ) -> Result { + Err(Error::badf()) + } + + async fn sock_shutdown(&self, _how: SdFlags) -> Result<(), Error> { + Err(Error::badf()) + } + + async fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + + async fn sync(&self) -> Result<(), Error> { + Ok(()) + } + + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::empty()) + } + + async fn set_fdflags(&mut self, _flags: FdFlags) -> Result<(), Error> { + Err(Error::badf()) + } + + async fn get_filestat(&self) -> Result { + Ok(Filestat { + device_id: 0, + inode: 0, + filetype: self.get_filetype().await?, + nlink: 0, + size: 0, // XXX no way to get a size out of a Read :( + atim: None, + mtim: None, + ctim: None, + }) + } + + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + Err(Error::badf()) + } + + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Err(Error::badf()) + } + + async fn set_times( + &self, + _atime: Option, + _mtime: Option, + ) -> Result<(), Error> { + Err(Error::badf()) + } + + async fn read_vectored<'a>(&self, _bufs: &mut [std::io::IoSliceMut<'a>]) -> Result { + Err(Error::badf()) + } + + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [std::io::IoSliceMut<'a>], + _offset: u64, + ) -> Result { + Err(Error::badf()) + } + + async fn write_vectored<'a>(&self, _bufs: &[std::io::IoSlice<'a>]) -> Result { + Err(Error::badf()) + } + + async fn write_vectored_at<'a>( + &self, + _bufs: &[std::io::IoSlice<'a>], + _offset: u64, + ) -> Result { + Err(Error::badf()) + } + + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { + Err(Error::badf()) + } + + async fn peek(&self, _buf: &mut [u8]) -> Result { + Err(Error::badf()) + } + + fn num_ready_bytes(&self) -> Result { + Ok(0) + } + + async fn readable(&self) -> Result<(), Error> { + Err(Error::badf()) + } + + async fn writable(&self) -> Result<(), Error> { + Err(Error::badf()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FileType { + Unknown, + BlockDevice, + CharacterDevice, + Directory, + RegularFile, + SocketDgram, + SocketStream, + SymbolicLink, + Pipe, +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct FdFlags: u32 { + const APPEND = 0b1; + const DSYNC = 0b10; + const NONBLOCK = 0b100; + const RSYNC = 0b1000; + const SYNC = 0b10000; + } +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct SdFlags: u32 { + const RD = 0b1; + const WR = 0b10; + } +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct SiFlags: u32 { + } +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct RiFlags: u32 { + const RECV_PEEK = 0b1; + const RECV_WAITALL = 0b10; + } +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct RoFlags: u32 { + const RECV_DATA_TRUNCATED = 0b1; + } +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct OFlags: u32 { + const CREATE = 0b1; + const DIRECTORY = 0b10; + const EXCLUSIVE = 0b100; + const TRUNCATE = 0b1000; + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Filestat { + pub device_id: u64, + pub inode: u64, + pub filetype: FileType, + pub nlink: u64, + pub size: u64, // this is a read field, the rest are file fields + pub atim: Option, + pub mtim: Option, + pub ctim: Option, +} + +pub(crate) trait TableFileExt { + fn get_file(&self, fd: u32) -> Result, Error>; + fn get_file_mut(&mut self, fd: u32) -> Result<&mut FileEntry, Error>; +} +impl TableFileExt for crate::table::Table { + fn get_file(&self, fd: u32) -> Result, Error> { + self.get(fd) + } + fn get_file_mut(&mut self, fd: u32) -> Result<&mut FileEntry, Error> { + self.get_mut(fd) + } +} + +pub(crate) struct FileEntry { + pub file: Box, + pub access_mode: FileAccessMode, +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct FileAccessMode : u32 { + const READ = 0b1; + const WRITE= 0b10; + } +} + +impl FileEntry { + pub fn new(file: Box, access_mode: FileAccessMode) -> Self { + FileEntry { file, access_mode } + } + + pub async fn get_fdstat(&self) -> Result { + Ok(FdStat { + filetype: self.file.get_filetype().await?, + flags: self.file.get_fdflags().await?, + access_mode: self.access_mode, + }) + } +} + +#[derive(Debug, Clone)] +pub struct FdStat { + pub filetype: FileType, + pub flags: FdFlags, + pub access_mode: FileAccessMode, +} + +#[derive(Debug, Clone)] +pub enum Advice { + Normal, + Sequential, + Random, + WillNeed, + DontNeed, + NoReuse, +} diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs new file mode 100644 index 00000000..fa78a3b0 --- /dev/null +++ b/crates/wasi-common/src/lib.rs @@ -0,0 +1,190 @@ +//! # wasi-common +//! +//! This is Wasmtime's legacy implementation of WASI 0.1 (Preview 1). The +//! Wasmtime maintainers suggest all users upgrade to the implementation +//! of WASI 0.1 and 0.2 provided by the `wasmtime-wasi` crate. This +//! implementation remains in the wasmtime tree because it is required to use +//! the `wasmtime-wasi-threads` crate, an implementation of the `wasi-threads` +//! proposal which is not compatible with WASI 0.2. +//! +//! In addition to integration with Wasmtime, this implementation may be used +//! by other runtimes by disabling the `wasmtime` feature on this crate. +//! +//! ## The `WasiFile` and `WasiDir` traits +//! +//! The WASI specification only defines one `handle` type, `fd`, on which all +//! operations on both files and directories (aka dirfds) are defined. We +//! believe this is a design mistake, and are architecting wasi-common to make +//! this straightforward to correct in future snapshots of WASI. Wasi-common +//! internally treats files and directories as two distinct resource types in +//! the table - `Box` and `Box`. The snapshot 0 and +//! 1 interfaces via `fd` will attempt to downcast a table element to one or +//! both of these interfaces depending on what is appropriate - e.g. +//! `fd_close` operates on both files and directories, `fd_read` only operates +//! on files, and `fd_readdir` only operates on directories. + +//! The `WasiFile` and `WasiDir` traits are defined by `wasi-common` in terms +//! of types defined directly in the crate's source code (I decided it should +//! NOT those generated by the `wiggle` proc macros, see snapshot architecture +//! below), as well as the `cap_std::time` family of types. And, importantly, +//! `wasi-common` itself provides no implementation of `WasiDir`, and only two +//! trivial implementations of `WasiFile` on the `crate::pipe::{ReadPipe, +//! WritePipe}` types, which in turn just delegate to `std::io::{Read, +//! Write}`. In order for `wasi-common` to access the local filesystem at all, +//! you need to provide `WasiFile` and `WasiDir` impls through either the new +//! `wasi-cap-std-sync` crate found at `crates/wasi-common/cap-std-sync` - see +//! the section on that crate below - or by providing your own implementation +//! from elsewhere. +//! +//! This design makes it possible for `wasi-common` embedders to statically +//! reason about access to the local filesystem by examining what impls are +//! linked into an application. We found that this separation of concerns also +//! makes it pretty enjoyable to write alternative implementations, e.g. a +//! virtual filesystem. +//! +//! Implementations of the `WasiFile` and `WasiDir` traits are provided +//! for synchronous embeddings in `wasi_common::sync` and for Tokio embeddings +//! in `wasi_common::tokio`. +//! +//! ## Traits for the rest of WASI's features +//! +//! Other aspects of a WASI implementation are not yet considered resources +//! and accessed by `handle`. We plan to correct this design deficiency in +//! WASI in the future, but for now we have designed the following traits to +//! provide embedders with the same sort of implementation flexibility they +//! get with WasiFile/WasiDir: +//! +//! * Timekeeping: `WasiSystemClock` and `WasiMonotonicClock` provide the two +//! interfaces for a clock. `WasiSystemClock` represents time as a +//! `cap_std::time::SystemTime`, and `WasiMonotonicClock` represents time as +//! `cap_std::time::Instant`. * Randomness: we re-use the `cap_rand::RngCore` +//! trait to represent a randomness source. A trivial `Deterministic` impl is +//! provided. * Scheduling: The `WasiSched` trait abstracts over the +//! `sched_yield` and `poll_oneoff` functions. +//! +//! Users can provide implementations of each of these interfaces to the +//! `WasiCtx::builder(...)` function. The +//! `wasi_cap_std_sync::WasiCtxBuilder::new()` function uses this public +//! interface to plug in its own implementations of each of these resources. + +#![warn(clippy::cast_sign_loss)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub mod clocks; +mod ctx; +pub mod dir; +mod error; +pub mod file; +pub mod pipe; +pub mod random; +pub mod sched; +pub mod snapshots; +mod string_array; +#[cfg_attr(docsrs, doc(cfg(feature = "sync")))] +#[cfg(feature = "sync")] +pub mod sync; +pub mod table; +#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] +#[cfg(feature = "tokio")] +pub mod tokio; + +pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock}; +pub use ctx::WasiCtx; +pub use dir::WasiDir; +pub use error::{Error, ErrorExt, I32Exit}; +pub use file::WasiFile; +pub use rand::TryRng; +pub use sched::{Poll, WasiSched}; +pub use string_array::{StringArray, StringArrayError}; +pub use table::Table; + +pub(crate) use wasmtime_internal_core::error::Error as EnvError; + +// The only difference between these definitions for sync vs async is whether +// the wasmtime::Funcs generated are async (& therefore need an async Store and an executor to run) +// or whether they have an internal "dummy executor" that expects the implementation of all +// the async funcs to poll to Ready immediately. +#[cfg(feature = "wasmtime")] +#[doc(hidden)] +#[macro_export] +macro_rules! define_wasi { + ($async_mode:tt $($bounds:tt)*) => { + + use wasmtime::Linker; + use wasmtime_internal_core::error::Result as EnvResult; + + pub fn add_to_linker( + linker: &mut Linker, + get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> EnvResult<()> + where U: Send + + crate::snapshots::preview_0::wasi_unstable::WasiUnstable + + crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1, + T: 'static, + $($bounds)* + { + snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?; + snapshots::preview_0::add_wasi_unstable_to_linker(linker, get_cx)?; + Ok(()) + } + + pub mod snapshots { + pub mod preview_1 { + wiggle::wasmtime_integration!({ + // The wiggle code to integrate with lives here: + target: crate::snapshots::preview_1, + witx: ["witx/preview1/wasi_snapshot_preview1.witx"], + errors: { errno => trappable Error }, + $async_mode: * + }); + } + pub mod preview_0 { + wiggle::wasmtime_integration!({ + // The wiggle code to integrate with lives here: + target: crate::snapshots::preview_0, + witx: ["witx/preview0/wasi_unstable.witx"], + errors: { errno => trappable Error }, + $async_mode: * + }); + } + } +}} + +/// Exit the process with a conventional OS error code as long as Wasmtime +/// understands the error. If the error is not an `I32Exit` or `Trap`, return +/// the error back to the caller for it to decide what to do. +/// +/// Note: this function is designed for usage where it is acceptable for +/// Wasmtime failures to terminate the parent process, such as in the Wasmtime +/// CLI; this would not be suitable for use in multi-tenant embeddings. +#[cfg_attr(docsrs, doc(cfg(feature = "exit")))] +#[cfg(feature = "exit")] +pub fn maybe_exit_on_error(e: EnvError) -> EnvError { + use std::process; + use wasmtime::Trap; + + // If a specific WASI error code was requested then that's + // forwarded through to the process here without printing any + // extra error information. + if let Some(exit) = e.downcast_ref::() { + process::exit(exit.0); + } + + // If the program exited because of a trap, return an error code + // to the outside environment indicating a more severe problem + // than a simple failure. + if e.is::() { + eprintln!("Error: {e:?}"); + + if cfg!(unix) { + // On Unix, return the error code of an abort. + process::exit(128 + libc::SIGABRT); + } else if cfg!(windows) { + // On Windows, return 3. + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 + process::exit(3); + } + } + + e +} diff --git a/crates/wasi-common/src/pipe.rs b/crates/wasi-common/src/pipe.rs new file mode 100644 index 00000000..a7e00242 --- /dev/null +++ b/crates/wasi-common/src/pipe.rs @@ -0,0 +1,201 @@ +//! Virtual pipes. +//! +//! These types provide easy implementations of `WasiFile` that mimic much of the behavior of Unix +//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other +//! than OS files. +//! +//! Some convenience constructors are included for common backing types like `Vec` and `String`, +//! but the virtual pipes can be instantiated with any `Read` or `Write` type. +//! +use crate::Error; +use crate::file::{FdFlags, FileType, WasiFile}; +use std::any::Any; +use std::io::{self, Read, Write}; +use std::sync::{Arc, RwLock}; + +/// A virtual pipe read end. +/// +/// A variety of `From` impls are provided so that common pipe types are easy to create. For example: +/// +/// ```rust +/// use wasi_common::{pipe::ReadPipe, WasiCtx, Table}; +/// let stdin = ReadPipe::from("hello from stdin!"); +/// // Bring these instances from elsewhere (e.g. wasi-cap-std-sync or wasi-cap-std-tokio): +/// use wasi_common::sync::{random_ctx, clocks_ctx, sched_ctx}; +/// let random = random_ctx(); +/// let clocks = clocks_ctx(); +/// let sched = sched_ctx(); +/// let table = Table::new(); +/// let mut ctx = WasiCtx::new(random, clocks, sched, table); +/// ctx.set_stdin(Box::new(stdin.clone())); +/// ``` +#[derive(Debug)] +pub struct ReadPipe { + reader: Arc>, +} + +impl Clone for ReadPipe { + fn clone(&self) -> Self { + Self { + reader: self.reader.clone(), + } + } +} + +impl ReadPipe { + /// Create a new pipe from a `Read` type. + /// + /// All `Handle` read operations delegate to reading from this underlying reader. + pub fn new(r: R) -> Self { + Self::from_shared(Arc::new(RwLock::new(r))) + } + + /// Create a new pipe from a shareable `Read` type. + /// + /// All `Handle` read operations delegate to reading from this underlying reader. + pub fn from_shared(reader: Arc>) -> Self { + Self { reader } + } + + /// Try to convert this `ReadPipe` back to the underlying `R` type. + /// + /// This will fail with `Err(self)` if multiple references to the underlying `R` exist. + pub fn try_into_inner(mut self) -> Result { + match Arc::try_unwrap(self.reader) { + Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()), + Err(reader) => { + self.reader = reader; + Err(self) + } + } + } + fn borrow(&self) -> std::sync::RwLockWriteGuard<'_, R> { + RwLock::write(&self.reader).unwrap() + } +} + +impl From> for ReadPipe>> { + fn from(r: Vec) -> Self { + Self::new(io::Cursor::new(r)) + } +} + +impl From<&[u8]> for ReadPipe>> { + fn from(r: &[u8]) -> Self { + Self::from(r.to_vec()) + } +} + +impl From for ReadPipe> { + fn from(r: String) -> Self { + Self::new(io::Cursor::new(r)) + } +} + +impl From<&str> for ReadPipe> { + fn from(r: &str) -> Self { + Self::from(r.to_string()) + } +} + +#[async_trait::async_trait] +impl WasiFile for ReadPipe { + fn as_any(&self) -> &dyn Any { + self + } + async fn get_filetype(&self) -> Result { + Ok(FileType::Pipe) + } + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { + let n = self.borrow().read_vectored(bufs)?; + Ok(n.try_into()?) + } +} + +/// A virtual pipe write end. +/// +/// ```rust +/// use wasi_common::{pipe::WritePipe, WasiCtx, Table}; +/// let stdout = WritePipe::new_in_memory(); +/// // Bring these instances from elsewhere (e.g. wasi-cap-std-sync or wasi-cap-std-tokio): +/// use wasi_common::sync::{random_ctx, clocks_ctx, sched_ctx}; +/// let random = random_ctx(); +/// let clocks = clocks_ctx(); +/// let sched = sched_ctx(); +/// let table = Table::new(); +/// let mut ctx = WasiCtx::new(random, clocks, sched, table); +/// ctx.set_stdout(Box::new(stdout.clone())); +/// // use ctx in an instance, then make sure it is dropped: +/// drop(ctx); +/// let contents: Vec = stdout.try_into_inner().expect("sole remaining reference to WritePipe").into_inner(); +/// println!("contents of stdout: {:?}", contents); +/// ``` +#[derive(Debug)] +pub struct WritePipe { + writer: Arc>, +} + +impl Clone for WritePipe { + fn clone(&self) -> Self { + Self { + writer: self.writer.clone(), + } + } +} + +impl WritePipe { + /// Create a new pipe from a `Write` type. + /// + /// All `Handle` write operations delegate to writing to this underlying writer. + pub fn new(w: W) -> Self { + Self::from_shared(Arc::new(RwLock::new(w))) + } + + /// Create a new pipe from a shareable `Write` type. + /// + /// All `Handle` write operations delegate to writing to this underlying writer. + pub fn from_shared(writer: Arc>) -> Self { + Self { writer } + } + + /// Try to convert this `WritePipe` back to the underlying `W` type. + /// + /// This will fail with `Err(self)` if multiple references to the underlying `W` exist. + pub fn try_into_inner(mut self) -> Result { + match Arc::try_unwrap(self.writer) { + Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()), + Err(writer) => { + self.writer = writer; + Err(self) + } + } + } + + fn borrow(&self) -> std::sync::RwLockWriteGuard<'_, W> { + RwLock::write(&self.writer).unwrap() + } +} + +impl WritePipe>> { + /// Create a new writable virtual pipe backed by a `Vec` buffer. + pub fn new_in_memory() -> Self { + Self::new(io::Cursor::new(vec![])) + } +} + +#[async_trait::async_trait] +impl WasiFile for WritePipe { + fn as_any(&self) -> &dyn Any { + self + } + async fn get_filetype(&self) -> Result { + Ok(FileType::Pipe) + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::APPEND) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + let n = self.borrow().write_vectored(bufs)?; + Ok(n.try_into()?) + } +} diff --git a/crates/wasi-common/src/random.rs b/crates/wasi-common/src/random.rs new file mode 100644 index 00000000..e28d318d --- /dev/null +++ b/crates/wasi-common/src/random.rs @@ -0,0 +1,51 @@ +use core::convert::Infallible; +use rand::{Rng, TryRng}; + +/// Implement `WasiRandom` using a deterministic cycle of bytes. +pub struct Deterministic { + cycle: std::iter::Cycle>, +} + +impl Deterministic { + pub fn new(bytes: Vec) -> Self { + Deterministic { + cycle: bytes.into_iter().cycle(), + } + } +} + +impl TryRng for Deterministic { + type Error = Infallible; + fn try_next_u32(&mut self) -> Result { + let b0 = self.cycle.next().expect("infinite sequence"); + let b1 = self.cycle.next().expect("infinite sequence"); + let b2 = self.cycle.next().expect("infinite sequence"); + let b3 = self.cycle.next().expect("infinite sequence"); + Ok(((b0 as u32) << 24) + ((b1 as u32) << 16) + ((b2 as u32) << 8) + (b3 as u32)) + } + fn try_next_u64(&mut self) -> Result { + let w0 = self.next_u32(); + let w1 = self.next_u32(); + Ok(((w0 as u64) << 32) + (w1 as u64)) + } + fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), Infallible> { + for b in buf.iter_mut() { + *b = self.cycle.next().expect("infinite sequence"); + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn deterministic() { + let mut det = Deterministic::new(vec![1, 2, 3, 4]); + let mut buf = vec![0; 1024]; + det.try_fill_bytes(&mut buf).expect("get randomness"); + for (ix, b) in buf.iter().enumerate() { + assert_eq!(*b, (ix % 4) as u8 + 1) + } + } +} diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs new file mode 100644 index 00000000..8413b0fc --- /dev/null +++ b/crates/wasi-common/src/sched.rs @@ -0,0 +1,97 @@ +use crate::Error; +use crate::clocks::WasiMonotonicClock; +use crate::file::WasiFile; +#[cfg(feature = "use_cap_std")] +use cap_std::time::Instant; +#[cfg(not(feature = "use_cap_std"))] +use std::time::Instant; +pub mod subscription; +#[cfg(feature = "use_cap_std")] +pub use cap_std::time::Duration; +#[cfg(not(feature = "use_cap_std"))] +pub use std::time::Duration; + +pub use subscription::{ + MonotonicClockSubscription, RwEventFlags, RwSubscription, Subscription, SubscriptionResult, +}; + +#[async_trait::async_trait] +pub trait WasiSched: Send + Sync { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error>; + async fn sched_yield(&self) -> Result<(), Error>; + async fn sleep(&self, duration: Duration) -> Result<(), Error>; +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Userdata(u64); +impl From for Userdata { + fn from(u: u64) -> Userdata { + Userdata(u) + } +} + +impl From for u64 { + fn from(u: Userdata) -> u64 { + u.0 + } +} + +pub type PollResults = Vec<(SubscriptionResult, Userdata)>; + +pub struct Poll<'a> { + subs: Vec<(Subscription<'a>, Userdata)>, +} + +impl<'a> Poll<'a> { + pub fn new() -> Self { + Self { subs: Vec::new() } + } + pub fn subscribe_monotonic_clock( + &mut self, + clock: &'a dyn WasiMonotonicClock, + deadline: Instant, + precision: Duration, + ud: Userdata, + ) { + self.subs.push(( + Subscription::MonotonicClock(MonotonicClockSubscription { + clock, + deadline, + precision, + }), + ud, + )); + } + pub fn subscribe_read(&mut self, file: &'a dyn WasiFile, ud: Userdata) { + self.subs + .push((Subscription::Read(RwSubscription::new(file)), ud)); + } + pub fn subscribe_write(&mut self, file: &'a dyn WasiFile, ud: Userdata) { + self.subs + .push((Subscription::Write(RwSubscription::new(file)), ud)); + } + pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> { + self.subs + .into_iter() + .filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud))) + .collect() + } + pub fn is_empty(&self) -> bool { + self.subs.is_empty() + } + pub fn earliest_clock_deadline(&self) -> Option<&MonotonicClockSubscription<'a>> { + self.subs + .iter() + .filter_map(|(s, _ud)| match s { + Subscription::MonotonicClock(t) => Some(t), + _ => None, + }) + .min_by(|a, b| a.deadline.cmp(&b.deadline)) + } + pub fn rw_subscriptions<'b>(&'b mut self) -> impl Iterator> { + self.subs.iter_mut().filter_map(|(s, _ud)| match s { + Subscription::Read { .. } | Subscription::Write { .. } => Some(s), + _ => None, + }) + } +} diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs new file mode 100644 index 00000000..782aca91 --- /dev/null +++ b/crates/wasi-common/src/sched/subscription.rs @@ -0,0 +1,80 @@ +use crate::Error; +use crate::clocks::WasiMonotonicClock; +use crate::file::WasiFile; +use bitflags::bitflags; +#[cfg(feature = "use_cap_std")] +use cap_std::time::{Duration, Instant}; +#[cfg(not(feature = "use_cap_std"))] +use std::time::{Duration, Instant}; + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct RwEventFlags: u32 { + const HANGUP = 0b1; + } +} + +pub struct RwSubscription<'a> { + pub file: &'a dyn WasiFile, + status: Option>, +} + +impl<'a> RwSubscription<'a> { + pub fn new(file: &'a dyn WasiFile) -> Self { + Self { file, status: None } + } + pub fn complete(&mut self, size: u64, flags: RwEventFlags) { + self.status = Some(Ok((size, flags))) + } + pub fn error(&mut self, error: Error) { + self.status = Some(Err(error)) + } + pub fn result(&mut self) -> Option> { + self.status.take() + } +} + +pub struct MonotonicClockSubscription<'a> { + pub clock: &'a dyn WasiMonotonicClock, + pub deadline: Instant, + pub precision: Duration, +} + +impl<'a> MonotonicClockSubscription<'a> { + pub fn now(&self) -> Instant { + self.clock.now(self.precision) + } + pub fn duration_until(&self) -> Option { + self.deadline.checked_duration_since(self.now()) + } + pub fn result(&self) -> Option> { + if self.now().checked_duration_since(self.deadline).is_some() { + Some(Ok(())) + } else { + None + } + } +} + +pub enum Subscription<'a> { + Read(RwSubscription<'a>), + Write(RwSubscription<'a>), + MonotonicClock(MonotonicClockSubscription<'a>), +} + +#[derive(Debug)] +pub enum SubscriptionResult { + Read(Result<(u64, RwEventFlags), Error>), + Write(Result<(u64, RwEventFlags), Error>), + MonotonicClock(Result<(), Error>), +} + +impl SubscriptionResult { + pub fn from_subscription(s: Subscription) -> Option { + match s { + Subscription::Read(mut s) => s.result().map(SubscriptionResult::Read), + Subscription::Write(mut s) => s.result().map(SubscriptionResult::Write), + Subscription::MonotonicClock(s) => s.result().map(SubscriptionResult::MonotonicClock), + } + } +} diff --git a/crates/wasi-common/src/snapshots/mod.rs b/crates/wasi-common/src/snapshots/mod.rs new file mode 100644 index 00000000..a8d50a61 --- /dev/null +++ b/crates/wasi-common/src/snapshots/mod.rs @@ -0,0 +1,24 @@ +//! One goal of `wasi-common` is for multiple WASI snapshots to provide an +//! interface to the same underlying `crate::WasiCtx`. This provides us a path +//! to evolve WASI by allowing the same WASI Command to import functions from +//! different snapshots - e.g. the user could use Rust's `std` which imports +//! snapshot 1, but also depend directly on the `wasi` crate which imports +//! some future snapshot 2. Right now, this amounts to supporting snapshot 1 +//! and "snapshot 0" aka wasi_unstable at once. +//! +//! The architectural rules for snapshots are: +//! +//! * Snapshots are arranged into modules under `crate::snapshots::`. +//! * Each snapshot should invoke `wiggle::from_witx!` with `ctx: +//! crate::WasiCtx` in its module, and impl all of the required traits. +//! * Snapshots can be implemented in terms of other snapshots. For example, +//! snapshot 0 is mostly implemented by calling the snapshot 1 implementation, +//! and converting its own types back and forth with the snapshot 1 types. In a +//! few cases, that is not feasible, so snapshot 0 carries its own +//! implementations in terms of the `WasiFile` and `WasiSched` traits. +//! * Snapshots can be implemented in terms of the `Wasi*` traits given by +//! `WasiCtx`. No further downcasting via the `as_any` escape hatch is +//! permitted. + +pub mod preview_0; +pub mod preview_1; diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs new file mode 100644 index 00000000..3a63ab8a --- /dev/null +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -0,0 +1,1082 @@ +use crate::file::TableFileExt; +use crate::sched::{ + Poll, Userdata, + subscription::{RwEventFlags, SubscriptionResult}, +}; +use crate::snapshots::preview_1::types as snapshot1_types; +use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; +use crate::{EnvError, ErrorExt, WasiCtx}; +#[cfg(feature = "use_cap_std")] +use cap_std::time::Duration; +use std::collections::HashSet; +#[cfg(not(feature = "use_cap_std"))] +use std::time::Duration; +use wiggle::{GuestMemory, GuestPtr}; + +wiggle::from_witx!({ + witx: ["witx/preview0/wasi_unstable.witx"], + errors: { errno => trappable Error }, + async: *, + wasmtime: false, +}); + +use types::Error; + +impl ErrorExt for Error { + fn not_found() -> Self { + types::Errno::Noent.into() + } + fn too_big() -> Self { + types::Errno::TooBig.into() + } + fn badf() -> Self { + types::Errno::Badf.into() + } + fn exist() -> Self { + types::Errno::Exist.into() + } + fn illegal_byte_sequence() -> Self { + types::Errno::Ilseq.into() + } + fn invalid_argument() -> Self { + types::Errno::Inval.into() + } + fn io() -> Self { + types::Errno::Io.into() + } + fn name_too_long() -> Self { + types::Errno::Nametoolong.into() + } + fn not_dir() -> Self { + types::Errno::Notdir.into() + } + fn not_supported() -> Self { + types::Errno::Notsup.into() + } + fn overflow() -> Self { + types::Errno::Overflow.into() + } + fn range() -> Self { + types::Errno::Range.into() + } + fn seek_pipe() -> Self { + types::Errno::Spipe.into() + } + fn perm() -> Self { + types::Errno::Perm.into() + } +} + +impl wiggle::GuestErrorType for types::Errno { + fn success() -> Self { + Self::Success + } +} + +impl From for Error { + fn from(err: wiggle::GuestError) -> Error { + snapshot1_types::Error::from(err).into() + } +} + +impl From for Error { + fn from(error: snapshot1_types::Error) -> Error { + match error.downcast() { + Ok(errno) => Error::from(types::Errno::from(errno)), + Err(trap) => Error::trap(trap), + } + } +} + +impl From for Error { + fn from(_err: std::num::TryFromIntError) -> Error { + types::Errno::Overflow.into() + } +} + +// Type conversions +// The vast majority of the types defined in `types` and `snapshot1_types` are identical. However, +// since they are defined in separate places for mechanical (wiggle) reasons, we need to manually +// define conversion functions between them. +// Below we have defined these functions as they are needed. + +/// Fd is a newtype wrapper around u32. Unwrap and wrap it. +impl From for snapshot1_types::Fd { + fn from(fd: types::Fd) -> snapshot1_types::Fd { + u32::from(fd).into() + } +} +/// Fd is a newtype wrapper around u32. Unwrap and wrap it. +impl From for types::Fd { + fn from(fd: snapshot1_types::Fd) -> types::Fd { + u32::from(fd).into() + } +} + +/// Trivial conversion between two c-style enums that have the exact same set of variants. +/// Could we do something unsafe and not list all these variants out? Probably, but doing +/// it this way doesn't bother me much. I copy-pasted the list of variants out of the +/// rendered rustdocs. +/// LLVM ought to compile these From impls into no-ops, inshallah +macro_rules! convert_enum { + ($from:ty, $to:ty, $($var:ident),+) => { + impl From<$from> for $to { + fn from(e: $from) -> $to { + match e { + $( <$from>::$var => <$to>::$var, )+ + } + } + } + } +} +convert_enum!( + snapshot1_types::Errno, + types::Errno, + Success, + TooBig, + Acces, + Addrinuse, + Addrnotavail, + Afnosupport, + Again, + Already, + Badf, + Badmsg, + Busy, + Canceled, + Child, + Connaborted, + Connrefused, + Connreset, + Deadlk, + Destaddrreq, + Dom, + Dquot, + Exist, + Fault, + Fbig, + Hostunreach, + Idrm, + Ilseq, + Inprogress, + Intr, + Inval, + Io, + Isconn, + Isdir, + Loop, + Mfile, + Mlink, + Msgsize, + Multihop, + Nametoolong, + Netdown, + Netreset, + Netunreach, + Nfile, + Nobufs, + Nodev, + Noent, + Noexec, + Nolck, + Nolink, + Nomem, + Nomsg, + Noprotoopt, + Nospc, + Nosys, + Notconn, + Notdir, + Notempty, + Notrecoverable, + Notsock, + Notsup, + Notty, + Nxio, + Overflow, + Ownerdead, + Perm, + Pipe, + Proto, + Protonosupport, + Prototype, + Range, + Rofs, + Spipe, + Srch, + Stale, + Timedout, + Txtbsy, + Xdev, + Notcapable +); +convert_enum!( + types::Clockid, + snapshot1_types::Clockid, + Realtime, + Monotonic, + ProcessCputimeId, + ThreadCputimeId +); + +convert_enum!( + types::Advice, + snapshot1_types::Advice, + Normal, + Sequential, + Random, + Willneed, + Dontneed, + Noreuse +); +convert_enum!( + snapshot1_types::Filetype, + types::Filetype, + Directory, + BlockDevice, + CharacterDevice, + RegularFile, + SocketDgram, + SocketStream, + SymbolicLink, + Unknown +); +convert_enum!(types::Whence, snapshot1_types::Whence, Cur, End, Set); + +/// Prestat isn't a c-style enum, its a union where the variant has a payload. Its the only one of +/// those we need to convert, so write it by hand. +impl From for types::Prestat { + fn from(p: snapshot1_types::Prestat) -> types::Prestat { + match p { + snapshot1_types::Prestat::Dir(d) => types::Prestat::Dir(d.into()), + } + } +} + +/// Trivial conversion between two structs that have the exact same set of fields, +/// with recursive descent into the field types. +macro_rules! convert_struct { + ($from:ty, $to:path, $($field:ident),+) => { + impl From<$from> for $to { + fn from(e: $from) -> $to { + $to { + $( $field: e.$field.into(), )+ + } + } + } + } +} + +convert_struct!(snapshot1_types::PrestatDir, types::PrestatDir, pr_name_len); +convert_struct!( + snapshot1_types::Fdstat, + types::Fdstat, + fs_filetype, + fs_rights_base, + fs_rights_inheriting, + fs_flags +); + +/// Snapshot1 Filestat is incompatible with Snapshot0 Filestat - the nlink +/// field is u32 on this Filestat, and u64 on theirs. If you've got more than +/// 2^32 links I don't know what to tell you +impl From for types::Filestat { + fn from(f: snapshot1_types::Filestat) -> types::Filestat { + types::Filestat { + dev: f.dev, + ino: f.ino, + filetype: f.filetype.into(), + nlink: f.nlink.try_into().unwrap_or(u32::MAX), + size: f.size, + atim: f.atim, + mtim: f.mtim, + ctim: f.ctim, + } + } +} + +/// Trivial conversion between two bitflags that have the exact same set of flags. +macro_rules! convert_flags { + ($from:ty, $to:ty, $($flag:ident),+) => { + impl From<$from> for $to { + fn from(f: $from) -> $to { + let mut out = <$to>::empty(); + $( + if f.contains(<$from>::$flag) { + out |= <$to>::$flag; + } + )+ + out + } + } + } +} + +/// Need to convert in both directions? This saves listing out the flags twice +macro_rules! convert_flags_bidirectional { + ($from:ty, $to:ty, $($flag:tt)*) => { + convert_flags!($from, $to, $($flag)*); + convert_flags!($to, $from, $($flag)*); + } +} + +convert_flags_bidirectional!( + snapshot1_types::Fdflags, + types::Fdflags, + APPEND, + DSYNC, + NONBLOCK, + RSYNC, + SYNC +); +convert_flags!( + types::Lookupflags, + snapshot1_types::Lookupflags, + SYMLINK_FOLLOW +); +convert_flags!( + types::Fstflags, + snapshot1_types::Fstflags, + ATIM, + ATIM_NOW, + MTIM, + MTIM_NOW +); +convert_flags!( + types::Oflags, + snapshot1_types::Oflags, + CREAT, + DIRECTORY, + EXCL, + TRUNC +); +convert_flags_bidirectional!( + types::Rights, + snapshot1_types::Rights, + FD_DATASYNC, + FD_READ, + FD_SEEK, + FD_FDSTAT_SET_FLAGS, + FD_SYNC, + FD_TELL, + FD_WRITE, + FD_ADVISE, + FD_ALLOCATE, + PATH_CREATE_DIRECTORY, + PATH_CREATE_FILE, + PATH_LINK_SOURCE, + PATH_LINK_TARGET, + PATH_OPEN, + FD_READDIR, + PATH_READLINK, + PATH_RENAME_SOURCE, + PATH_RENAME_TARGET, + PATH_FILESTAT_GET, + PATH_FILESTAT_SET_SIZE, + PATH_FILESTAT_SET_TIMES, + FD_FILESTAT_GET, + FD_FILESTAT_SET_SIZE, + FD_FILESTAT_SET_TIMES, + PATH_SYMLINK, + PATH_REMOVE_DIRECTORY, + PATH_UNLINK_FILE, + POLL_FD_READWRITE, + SOCK_SHUTDOWN +); + +// This implementation, wherever possible, delegates directly to the Snapshot1 implementation, +// performing the no-op type conversions along the way. +impl wasi_unstable::WasiUnstable for WasiCtx { + async fn args_get( + &mut self, + memory: &mut GuestMemory<'_>, + argv: GuestPtr>, + argv_buf: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::args_get(self, memory, argv, argv_buf).await?; + Ok(()) + } + + async fn args_sizes_get( + &mut self, + memory: &mut GuestMemory<'_>, + ) -> Result<(types::Size, types::Size), Error> { + let s = Snapshot1::args_sizes_get(self, memory).await?; + Ok(s) + } + + async fn environ_get( + &mut self, + memory: &mut GuestMemory<'_>, + environ: GuestPtr>, + environ_buf: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::environ_get(self, memory, environ, environ_buf).await?; + Ok(()) + } + + async fn environ_sizes_get( + &mut self, + memory: &mut GuestMemory<'_>, + ) -> Result<(types::Size, types::Size), Error> { + let s = Snapshot1::environ_sizes_get(self, memory).await?; + Ok(s) + } + + async fn clock_res_get( + &mut self, + memory: &mut GuestMemory<'_>, + id: types::Clockid, + ) -> Result { + let t = Snapshot1::clock_res_get(self, memory, id.into()).await?; + Ok(t) + } + + async fn clock_time_get( + &mut self, + memory: &mut GuestMemory<'_>, + id: types::Clockid, + precision: types::Timestamp, + ) -> Result { + let t = Snapshot1::clock_time_get(self, memory, id.into(), precision).await?; + Ok(t) + } + + async fn fd_advise( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + offset: types::Filesize, + len: types::Filesize, + advice: types::Advice, + ) -> Result<(), Error> { + Snapshot1::fd_advise(self, memory, fd.into(), offset, len, advice.into()).await?; + Ok(()) + } + + async fn fd_allocate( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + offset: types::Filesize, + len: types::Filesize, + ) -> Result<(), Error> { + Snapshot1::fd_allocate(self, memory, fd.into(), offset, len).await?; + Ok(()) + } + + async fn fd_close(&mut self, memory: &mut GuestMemory<'_>, fd: types::Fd) -> Result<(), Error> { + Snapshot1::fd_close(self, memory, fd.into()).await?; + Ok(()) + } + + async fn fd_datasync( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result<(), Error> { + Snapshot1::fd_datasync(self, memory, fd.into()).await?; + Ok(()) + } + + async fn fd_fdstat_get( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + Ok(Snapshot1::fd_fdstat_get(self, memory, fd.into()) + .await? + .into()) + } + + async fn fd_fdstat_set_flags( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + flags: types::Fdflags, + ) -> Result<(), Error> { + Snapshot1::fd_fdstat_set_flags(self, memory, fd.into(), flags.into()).await?; + Ok(()) + } + + async fn fd_fdstat_set_rights( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + fs_rights_base: types::Rights, + fs_rights_inheriting: types::Rights, + ) -> Result<(), Error> { + Snapshot1::fd_fdstat_set_rights( + self, + memory, + fd.into(), + fs_rights_base.into(), + fs_rights_inheriting.into(), + ) + .await?; + Ok(()) + } + + async fn fd_filestat_get( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + Ok(Snapshot1::fd_filestat_get(self, memory, fd.into()) + .await? + .into()) + } + + async fn fd_filestat_set_size( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + size: types::Filesize, + ) -> Result<(), Error> { + Snapshot1::fd_filestat_set_size(self, memory, fd.into(), size).await?; + Ok(()) + } + + async fn fd_filestat_set_times( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<(), Error> { + Snapshot1::fd_filestat_set_times(self, memory, fd.into(), atim, mtim, fst_flags.into()) + .await?; + Ok(()) + } + + // NOTE on fd_read, fd_pread, fd_write, fd_pwrite implementations: + // these cast their pointers from preview0 vectors to preview1 vectors and + // this only works because the representation didn't change between preview0 + // and preview1. + + async fn fd_read( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + iovs: types::IovecArray, + ) -> Result { + Ok(Snapshot1::fd_read(self, memory, fd.into(), iovs.cast()).await?) + } + + async fn fd_pread( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + iovs: types::IovecArray, + offset: types::Filesize, + ) -> Result { + Ok(Snapshot1::fd_pread(self, memory, fd.into(), iovs.cast(), offset).await?) + } + + async fn fd_write( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ciovs: types::CiovecArray, + ) -> Result { + Ok(Snapshot1::fd_write(self, memory, fd.into(), ciovs.cast()).await?) + } + + async fn fd_pwrite( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ciovs: types::CiovecArray, + offset: types::Filesize, + ) -> Result { + Ok(Snapshot1::fd_pwrite(self, memory, fd.into(), ciovs.cast(), offset).await?) + } + + async fn fd_prestat_get( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + Ok(Snapshot1::fd_prestat_get(self, memory, fd.into()) + .await? + .into()) + } + + async fn fd_prestat_dir_name( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + path: GuestPtr, + path_max_len: types::Size, + ) -> Result<(), Error> { + Snapshot1::fd_prestat_dir_name(self, memory, fd.into(), path, path_max_len).await?; + Ok(()) + } + + async fn fd_renumber( + &mut self, + memory: &mut GuestMemory<'_>, + from: types::Fd, + to: types::Fd, + ) -> Result<(), Error> { + Snapshot1::fd_renumber(self, memory, from.into(), to.into()).await?; + Ok(()) + } + + async fn fd_seek( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + offset: types::Filedelta, + whence: types::Whence, + ) -> Result { + Ok(Snapshot1::fd_seek(self, memory, fd.into(), offset, whence.into()).await?) + } + + async fn fd_sync(&mut self, memory: &mut GuestMemory<'_>, fd: types::Fd) -> Result<(), Error> { + Snapshot1::fd_sync(self, memory, fd.into()).await?; + Ok(()) + } + + async fn fd_tell( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + Ok(Snapshot1::fd_tell(self, memory, fd.into()).await?) + } + + async fn fd_readdir( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + buf: GuestPtr, + buf_len: types::Size, + cookie: types::Dircookie, + ) -> Result { + Ok(Snapshot1::fd_readdir(self, memory, fd.into(), buf, buf_len, cookie).await?) + } + + async fn path_create_directory( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::path_create_directory(self, memory, dirfd.into(), path).await?; + Ok(()) + } + + async fn path_filestat_get( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + flags: types::Lookupflags, + path: GuestPtr, + ) -> Result { + Ok( + Snapshot1::path_filestat_get(self, memory, dirfd.into(), flags.into(), path) + .await? + .into(), + ) + } + + async fn path_filestat_set_times( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + flags: types::Lookupflags, + path: GuestPtr, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<(), Error> { + Snapshot1::path_filestat_set_times( + self, + memory, + dirfd.into(), + flags.into(), + path, + atim, + mtim, + fst_flags.into(), + ) + .await?; + Ok(()) + } + + async fn path_link( + &mut self, + memory: &mut GuestMemory<'_>, + src_fd: types::Fd, + src_flags: types::Lookupflags, + src_path: GuestPtr, + target_fd: types::Fd, + target_path: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::path_link( + self, + memory, + src_fd.into(), + src_flags.into(), + src_path, + target_fd.into(), + target_path, + ) + .await?; + Ok(()) + } + + async fn path_open( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + dirflags: types::Lookupflags, + path: GuestPtr, + oflags: types::Oflags, + fs_rights_base: types::Rights, + fs_rights_inheriting: types::Rights, + fdflags: types::Fdflags, + ) -> Result { + Ok(Snapshot1::path_open( + self, + memory, + dirfd.into(), + dirflags.into(), + path, + oflags.into(), + fs_rights_base.into(), + fs_rights_inheriting.into(), + fdflags.into(), + ) + .await? + .into()) + } + + async fn path_readlink( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + buf: GuestPtr, + buf_len: types::Size, + ) -> Result { + Ok(Snapshot1::path_readlink(self, memory, dirfd.into(), path, buf, buf_len).await?) + } + + async fn path_remove_directory( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::path_remove_directory(self, memory, dirfd.into(), path).await?; + Ok(()) + } + + async fn path_rename( + &mut self, + memory: &mut GuestMemory<'_>, + src_fd: types::Fd, + src_path: GuestPtr, + dest_fd: types::Fd, + dest_path: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::path_rename( + self, + memory, + src_fd.into(), + src_path, + dest_fd.into(), + dest_path, + ) + .await?; + Ok(()) + } + + async fn path_symlink( + &mut self, + memory: &mut GuestMemory<'_>, + src_path: GuestPtr, + dirfd: types::Fd, + dest_path: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::path_symlink(self, memory, src_path, dirfd.into(), dest_path).await?; + Ok(()) + } + + async fn path_unlink_file( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + ) -> Result<(), Error> { + Snapshot1::path_unlink_file(self, memory, dirfd.into(), path).await?; + Ok(()) + } + + // NOTE on poll_oneoff implementation: + // Like fd_write and friends, the arguments and return values are behind GuestPtrs, + // so they are not values we can convert and pass to the poll_oneoff in Snapshot1. + // Instead, we have copied the implementation of these functions from the Snapshot1 code. + // The implementations are identical, but the `types::` in scope locally is different. + // The bodies of these functions is mostly about converting the GuestPtr and types::-based + // representation to use the Poll abstraction. + async fn poll_oneoff( + &mut self, + memory: &mut GuestMemory<'_>, + subs: GuestPtr, + events: GuestPtr, + nsubscriptions: types::Size, + ) -> Result { + if nsubscriptions == 0 { + return Err(Error::invalid_argument().context("nsubscriptions must be nonzero")); + } + + // Special-case a `poll_oneoff` which is just sleeping on a single + // relative timer event, such as what WASI libc uses to implement sleep + // functions. This supports all clock IDs, because POSIX says that + // `clock_settime` doesn't effect relative sleeps. + if nsubscriptions == 1 { + let sub = memory.read(subs)?; + if let types::SubscriptionU::Clock(clocksub) = sub.u { + if !clocksub + .flags + .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) + { + self.sched + .sleep(Duration::from_nanos(clocksub.timeout)) + .await?; + memory.write( + events, + types::Event { + userdata: sub.userdata, + error: types::Errno::Success, + type_: types::Eventtype::Clock, + fd_readwrite: fd_readwrite_empty(), + }, + )?; + return Ok(1); + } + } + } + + let table = &self.table; + let mut sub_fds: HashSet = HashSet::new(); + // We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside + let mut reads: Vec<(u32, Userdata)> = Vec::new(); + let mut writes: Vec<(u32, Userdata)> = Vec::new(); + let mut poll = Poll::new(); + + let subs = subs.as_array(nsubscriptions); + for sub_elem in subs.iter() { + let sub_ptr = sub_elem?; + let sub = memory.read(sub_ptr)?; + match sub.u { + types::SubscriptionU::Clock(clocksub) => match clocksub.id { + types::Clockid::Monotonic => { + let clock = self.clocks.monotonic()?; + let precision = Duration::from_nanos(clocksub.precision); + let duration = Duration::from_nanos(clocksub.timeout); + let start = if clocksub + .flags + .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) + { + clock.creation_time + } else { + clock.abs_clock.now(precision) + }; + let deadline = start + .checked_add(duration) + .ok_or_else(|| Error::overflow().context("deadline"))?; + poll.subscribe_monotonic_clock( + &*clock.abs_clock, + deadline, + precision, + sub.userdata.into(), + ) + } + _ => Err(Error::invalid_argument() + .context("timer subscriptions only support monotonic timer"))?, + }, + types::SubscriptionU::FdRead(readsub) => { + let fd = readsub.file_descriptor; + if sub_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll")); + } else { + sub_fds.insert(fd); + } + table.get_file(u32::from(fd))?; + reads.push((u32::from(fd), sub.userdata.into())); + } + types::SubscriptionU::FdWrite(writesub) => { + let fd = writesub.file_descriptor; + if sub_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll")); + } else { + sub_fds.insert(fd); + } + table.get_file(u32::from(fd))?; + writes.push((u32::from(fd), sub.userdata.into())); + } + } + } + + self.sched.poll_oneoff(&mut poll).await?; + + let results = poll.results(); + let num_results = results.len(); + assert!( + num_results <= nsubscriptions as usize, + "results exceeds subscriptions" + ); + let events = events.as_array( + num_results + .try_into() + .expect("not greater than nsubscriptions"), + ); + for ((result, userdata), event_elem) in results.into_iter().zip(events.iter()) { + let event_ptr = event_elem?; + let userdata: types::Userdata = userdata.into(); + memory.write( + event_ptr, + match result { + SubscriptionResult::Read(r) => { + let type_ = types::Eventtype::FdRead; + match r { + Ok((nbytes, flags)) => types::Event { + userdata, + error: types::Errno::Success, + type_, + fd_readwrite: types::EventFdReadwrite { + nbytes, + flags: types::Eventrwflags::from(&flags), + }, + }, + Err(e) => types::Event { + userdata, + error: types::Errno::from(e.downcast().map_err(Error::trap)?), + type_, + fd_readwrite: fd_readwrite_empty(), + }, + } + } + SubscriptionResult::Write(r) => { + let type_ = types::Eventtype::FdWrite; + match r { + Ok((nbytes, flags)) => types::Event { + userdata, + error: types::Errno::Success, + type_, + fd_readwrite: types::EventFdReadwrite { + nbytes, + flags: types::Eventrwflags::from(&flags), + }, + }, + Err(e) => types::Event { + userdata, + error: types::Errno::from(e.downcast().map_err(Error::trap)?), + type_, + fd_readwrite: fd_readwrite_empty(), + }, + } + } + SubscriptionResult::MonotonicClock(r) => { + let type_ = types::Eventtype::Clock; + types::Event { + userdata, + error: match r { + Ok(()) => types::Errno::Success, + Err(e) => types::Errno::from(e.downcast().map_err(Error::trap)?), + }, + type_, + fd_readwrite: fd_readwrite_empty(), + } + } + }, + )?; + } + + Ok(num_results.try_into().expect("results fit into memory")) + } + + async fn proc_exit( + &mut self, + memory: &mut GuestMemory<'_>, + status: types::Exitcode, + ) -> EnvError { + Snapshot1::proc_exit(self, memory, status).await + } + + async fn proc_raise( + &mut self, + _memory: &mut GuestMemory<'_>, + _sig: types::Signal, + ) -> Result<(), Error> { + Err(Error::trap(EnvError::msg("proc_raise unsupported"))) + } + + async fn sched_yield(&mut self, memory: &mut GuestMemory<'_>) -> Result<(), Error> { + Snapshot1::sched_yield(self, memory).await?; + Ok(()) + } + + async fn random_get( + &mut self, + memory: &mut GuestMemory<'_>, + buf: GuestPtr, + buf_len: types::Size, + ) -> Result<(), Error> { + Snapshot1::random_get(self, memory, buf, buf_len).await?; + Ok(()) + } + + async fn sock_recv( + &mut self, + _memory: &mut GuestMemory<'_>, + _fd: types::Fd, + _ri_data: types::IovecArray, + _ri_flags: types::Riflags, + ) -> Result<(types::Size, types::Roflags), Error> { + Err(Error::trap(EnvError::msg("sock_recv unsupported"))) + } + + async fn sock_send( + &mut self, + _memory: &mut GuestMemory<'_>, + _fd: types::Fd, + _si_data: types::CiovecArray, + _si_flags: types::Siflags, + ) -> Result { + Err(Error::trap(EnvError::msg("sock_send unsupported"))) + } + + async fn sock_shutdown( + &mut self, + _memory: &mut GuestMemory<'_>, + _fd: types::Fd, + _how: types::Sdflags, + ) -> Result<(), Error> { + Err(Error::trap(EnvError::msg("sock_shutdown unsupported"))) + } +} + +impl From<&RwEventFlags> for types::Eventrwflags { + fn from(flags: &RwEventFlags) -> types::Eventrwflags { + let mut out = types::Eventrwflags::empty(); + if flags.contains(RwEventFlags::HANGUP) { + out = out | types::Eventrwflags::FD_READWRITE_HANGUP; + } + out + } +} + +fn fd_readwrite_empty() -> types::EventFdReadwrite { + types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::empty(), + } +} diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs new file mode 100644 index 00000000..02106e2d --- /dev/null +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -0,0 +1,1581 @@ +use crate::{ + EnvError, I32Exit, SystemTimeSpec, WasiCtx, + dir::{DirEntry, OpenResult, ReaddirCursor, ReaddirEntity, TableDirExt}, + file::{ + Advice, FdFlags, FdStat, FileAccessMode, FileEntry, FileType, Filestat, OFlags, RiFlags, + RoFlags, SdFlags, SiFlags, TableFileExt, WasiFile, + }, + sched::{ + Poll, Userdata, + subscription::{RwEventFlags, SubscriptionResult}, + }, +}; +#[cfg(feature = "use_cap_std")] +use cap_std::time::{Duration, SystemClock}; +use std::borrow::Cow; +use std::io::{IoSlice, IoSliceMut}; +use std::ops::Deref; +use std::sync::Arc; +#[cfg(not(feature = "use_cap_std"))] +use std::time::Duration; +use wiggle::GuestMemory; +use wiggle::GuestPtr; + +pub mod error; +use error::{Error, ErrorExt}; + +// Limit the size of intermediate buffers when copying to WebAssembly shared +// memory. +pub(crate) const MAX_SHARED_BUFFER_SIZE: usize = 1 << 16; + +wiggle::from_witx!({ + witx: ["witx/preview1/wasi_snapshot_preview1.witx"], + errors: { errno => trappable Error }, + // Note: not every function actually needs to be async, however, nearly all of them do, and + // keeping that set the same in this macro and the wasmtime_wiggle / lucet_wiggle macros is + // tedious, and there is no cost to having a sync function be async in this case. + async: *, + wasmtime: false, +}); + +impl wiggle::GuestErrorType for types::Errno { + fn success() -> Self { + Self::Success + } +} + +impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { + async fn args_get( + &mut self, + memory: &mut GuestMemory<'_>, + argv: GuestPtr>, + argv_buf: GuestPtr, + ) -> Result<(), Error> { + self.args.write_to_guest(memory, argv_buf, argv) + } + + async fn args_sizes_get( + &mut self, + _memory: &mut GuestMemory<'_>, + ) -> Result<(types::Size, types::Size), Error> { + Ok((self.args.number_elements(), self.args.cumulative_size())) + } + + async fn environ_get( + &mut self, + memory: &mut GuestMemory<'_>, + environ: GuestPtr>, + environ_buf: GuestPtr, + ) -> Result<(), Error> { + self.env.write_to_guest(memory, environ_buf, environ) + } + + async fn environ_sizes_get( + &mut self, + _memory: &mut GuestMemory<'_>, + ) -> Result<(types::Size, types::Size), Error> { + Ok((self.env.number_elements(), self.env.cumulative_size())) + } + + async fn clock_res_get( + &mut self, + _memory: &mut GuestMemory<'_>, + id: types::Clockid, + ) -> Result { + let resolution = match id { + types::Clockid::Realtime => Ok(self.clocks.system()?.resolution()), + types::Clockid::Monotonic => Ok(self.clocks.monotonic()?.abs_clock.resolution()), + types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { + Err(Error::badf().context("process and thread clocks are not supported")) + } + }?; + Ok(resolution.as_nanos().try_into()?) + } + + async fn clock_time_get( + &mut self, + _memory: &mut GuestMemory<'_>, + id: types::Clockid, + precision: types::Timestamp, + ) -> Result { + let precision = Duration::from_nanos(precision); + match id { + types::Clockid::Realtime => { + #[cfg(feature = "use_cap_std")] + let now = self.clocks.system()?.now(precision).into_std(); + #[cfg(not(feature = "use_cap_std"))] + let now = self.clocks.system()?.now(precision); + let d = now + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .map_err(|_| Error::trap(EnvError::msg("current time before unix epoch")))?; + Ok(d.as_nanos().try_into()?) + } + types::Clockid::Monotonic => { + let clock = self.clocks.monotonic()?; + let now = clock.abs_clock.now(precision); + let d = now.duration_since(clock.creation_time); + Ok(d.as_nanos().try_into()?) + } + types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { + Err(Error::badf().context("process and thread clocks are not supported")) + } + } + } + + async fn fd_advise( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + offset: types::Filesize, + len: types::Filesize, + advice: types::Advice, + ) -> Result<(), Error> { + self.table() + .get_file(u32::from(fd))? + .file + .advise(offset, len, advice.into()) + .await?; + Ok(()) + } + + async fn fd_allocate( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<(), Error> { + // Check if fd is a file, and has rights, just to reject those cases + // with the errors expected: + let _ = self.table().get_file(u32::from(fd))?; + // This operation from cloudabi is linux-specific, isn't even + // supported across all linux filesystems, and has no support on macos + // or windows. Rather than ship spotty support, it has been removed + // from preview 2, and we are no longer supporting it in preview 1 as + // well. + Err(Error::not_supported()) + } + + async fn fd_close( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result<(), Error> { + let table = self.table(); + let fd = u32::from(fd); + + // Fail fast: If not present in table, Badf + if !table.contains_key(fd) { + return Err(Error::badf().context("key not in table")); + } + // fd_close must close either a File or a Dir handle + if table.is::(fd) { + let _ = table.delete::(fd); + } else if table.is::(fd) { + let _ = table.delete::(fd); + } else { + return Err(Error::badf().context("key does not refer to file or directory")); + } + + Ok(()) + } + + async fn fd_datasync( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result<(), Error> { + self.table() + .get_file(u32::from(fd))? + .file + .datasync() + .await?; + Ok(()) + } + + async fn fd_fdstat_get( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + let table = self.table(); + let fd = u32::from(fd); + if table.is::(fd) { + let file_entry: Arc = table.get(fd)?; + let fdstat = file_entry.get_fdstat().await?; + Ok(types::Fdstat::from(&fdstat)) + } else if table.is::(fd) { + let _dir_entry: Arc = table.get(fd)?; + let dir_fdstat = types::Fdstat { + fs_filetype: types::Filetype::Directory, + fs_rights_base: directory_base_rights(), + fs_rights_inheriting: directory_inheriting_rights(), + fs_flags: types::Fdflags::empty(), + }; + Ok(dir_fdstat) + } else { + Err(Error::badf()) + } + } + + async fn fd_fdstat_set_flags( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + flags: types::Fdflags, + ) -> Result<(), Error> { + if let Some(table) = self.table_mut() { + table + .get_file_mut(u32::from(fd))? + .file + .set_fdflags(FdFlags::from(flags)) + .await + } else { + log::warn!( + "`fd_fdstat_set_flags` does not work with wasi-threads enabled; see https://github.com/bytecodealliance/wasmtime/issues/5643" + ); + Err(Error::not_supported()) + } + } + + async fn fd_fdstat_set_rights( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + _fs_rights_base: types::Rights, + _fs_rights_inheriting: types::Rights, + ) -> Result<(), Error> { + let table = self.table(); + let fd = u32::from(fd); + if table.is::(fd) { + let _file_entry: Arc = table.get(fd)?; + Err(Error::not_supported()) + } else if table.is::(fd) { + let _dir_entry: Arc = table.get(fd)?; + Err(Error::not_supported()) + } else { + Err(Error::badf()) + } + } + + async fn fd_filestat_get( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + let table = self.table(); + let fd = u32::from(fd); + if table.is::(fd) { + let filestat = table.get_file(fd)?.file.get_filestat().await?; + Ok(filestat.into()) + } else if table.is::(fd) { + let filestat = table.get_dir(fd)?.dir.get_filestat().await?; + Ok(filestat.into()) + } else { + Err(Error::badf()) + } + } + + async fn fd_filestat_set_size( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + size: types::Filesize, + ) -> Result<(), Error> { + self.table() + .get_file(u32::from(fd))? + .file + .set_filestat_size(size) + .await?; + Ok(()) + } + + async fn fd_filestat_set_times( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<(), Error> { + let fd = u32::from(fd); + let table = self.table(); + // Validate flags + let set_atim = fst_flags.contains(types::Fstflags::ATIM); + let set_atim_now = fst_flags.contains(types::Fstflags::ATIM_NOW); + let set_mtim = fst_flags.contains(types::Fstflags::MTIM); + let set_mtim_now = fst_flags.contains(types::Fstflags::MTIM_NOW); + + let atim = systimespec(set_atim, atim, set_atim_now).map_err(|e| e.context("atim"))?; + let mtim = systimespec(set_mtim, mtim, set_mtim_now).map_err(|e| e.context("mtim"))?; + + if table.is::(fd) { + table + .get_file(fd) + .expect("checked that entry is file") + .file + .set_times(atim, mtim) + .await + } else if table.is::(fd) { + table + .get_dir(fd) + .expect("checked that entry is dir") + .dir + .set_times(".", atim, mtim, false) + .await + } else { + Err(Error::badf()) + } + } + + async fn fd_read( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + iovs: types::IovecArray, + ) -> Result { + let f = self.table().get_file(u32::from(fd))?; + // Access mode check normalizes error returned (windows would prefer ACCES here) + if !f.access_mode.contains(FileAccessMode::READ) { + Err(types::Errno::Badf)? + } + let f = &f.file; + + let iovs: Vec> = iovs + .iter() + .map(|iov_ptr| { + let iov_ptr = iov_ptr?; + let iov: types::Iovec = memory.read(iov_ptr)?; + Ok(iov.buf.as_array(iov.buf_len)) + }) + .collect::>()?; + + // If the first iov structure is from shared memory we can safely assume + // all the rest will be. We then read into memory based on the memory's + // shared-ness: + // - if not shared, we copy directly into the Wasm memory + // - if shared, we use an intermediate buffer; this avoids Rust unsafety + // due to holding on to a `&mut [u8]` of Wasm memory when we cannot + // guarantee the `&mut` exclusivity--other threads could be modifying + // the data as this functions writes to it. Though likely there is no + // issue with OS writing to io structs in multi-threaded scenarios, + // since we do not know here if `&dyn WasiFile` does anything else + // (e.g., read), we cautiously incur some performance overhead by + // copying twice. + let is_shared_memory = memory.is_shared_memory(); + let bytes_read: u64 = if is_shared_memory { + // For shared memory, read into an intermediate buffer. Only the + // first iov will be filled and even then the read is capped by the + // `MAX_SHARED_BUFFER_SIZE`, so users are expected to re-call. + let iov = iovs.into_iter().next(); + if let Some(iov) = iov { + let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)]; + let bytes_read = f.read_vectored(&mut [IoSliceMut::new(&mut buffer)]).await?; + let iov = iov + .get_range(0..bytes_read.try_into()?) + .expect("it should always be possible to slice the iov smaller"); + memory.copy_from_slice(&buffer[0..bytes_read.try_into()?], iov)?; + bytes_read + } else { + return Ok(0); + } + } else { + // Convert the first unsafe guest slice into a safe one--Wiggle + // can only track mutable borrows for an entire region, and converting + // all guest pointers to slices would cause a runtime borrow-checking + // error. As read is allowed to return less than the requested amount, + // it's valid (though not as efficient) for us to only perform the + // read of the first buffer. + let guest_slice: &mut [u8] = match iovs.into_iter().filter(|iov| iov.len() > 0).next() { + Some(iov) => memory.as_slice_mut(iov)?.unwrap(), + None => return Ok(0), + }; + + // Read directly into the Wasm memory. + f.read_vectored(&mut [IoSliceMut::new(guest_slice)]).await? + }; + + Ok(types::Size::try_from(bytes_read)?) + } + + async fn fd_pread( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + iovs: types::IovecArray, + offset: types::Filesize, + ) -> Result { + let f = self.table().get_file(u32::from(fd))?; + // Access mode check normalizes error returned (windows would prefer ACCES here) + if !f.access_mode.contains(FileAccessMode::READ) { + Err(types::Errno::Badf)? + } + let f = &f.file; + + let iovs: Vec> = iovs + .iter() + .map(|iov_ptr| { + let iov_ptr = iov_ptr?; + let iov: types::Iovec = memory.read(iov_ptr)?; + Ok(iov.buf.as_array(iov.buf_len)) + }) + .collect::>()?; + + // If the first iov structure is from shared memory we can safely assume + // all the rest will be. We then read into memory based on the memory's + // shared-ness: + // - if not shared, we copy directly into the Wasm memory + // - if shared, we use an intermediate buffer; this avoids Rust unsafety + // due to holding on to a `&mut [u8]` of Wasm memory when we cannot + // guarantee the `&mut` exclusivity--other threads could be modifying + // the data as this functions writes to it. Though likely there is no + // issue with OS writing to io structs in multi-threaded scenarios, + // since we do not know here if `&dyn WasiFile` does anything else + // (e.g., read), we cautiously incur some performance overhead by + // copying twice. + let is_shared_memory = memory.is_shared_memory(); + let bytes_read: u64 = if is_shared_memory { + // For shared memory, read into an intermediate buffer. Only the + // first iov will be filled and even then the read is capped by the + // `MAX_SHARED_BUFFER_SIZE`, so users are expected to re-call. + let iov = iovs.into_iter().next(); + if let Some(iov) = iov { + let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)]; + let bytes_read = f + .read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset) + .await?; + let iov = iov + .get_range(0..bytes_read.try_into()?) + .expect("it should always be possible to slice the iov smaller"); + memory.copy_from_slice(&buffer[0..bytes_read.try_into()?], iov)?; + bytes_read + } else { + return Ok(0); + } + } else { + // Convert unsafe guest slices to safe ones. + let guest_slice: &mut [u8] = match iovs.into_iter().filter(|iov| iov.len() > 0).next() { + Some(iov) => memory.as_slice_mut(iov)?.unwrap(), + None => return Ok(0), + }; + + // Read directly into the Wasm memory. + f.read_vectored_at(&mut [IoSliceMut::new(guest_slice)], offset) + .await? + }; + + Ok(types::Size::try_from(bytes_read)?) + } + + async fn fd_write( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ciovs: types::CiovecArray, + ) -> Result { + let f = self.table().get_file(u32::from(fd))?; + // Access mode check normalizes error returned (windows would prefer ACCES here) + if !f.access_mode.contains(FileAccessMode::WRITE) { + Err(types::Errno::Badf)? + } + let f = &f.file; + + let guest_slices: Vec> = ciovs + .iter() + .map(|iov_ptr| { + let iov_ptr = iov_ptr?; + let iov: types::Ciovec = memory.read(iov_ptr)?; + Ok(memory.as_cow(iov.buf.as_array(iov.buf_len))?) + }) + .collect::>()?; + + let ioslices: Vec = guest_slices + .iter() + .map(|s| IoSlice::new(s.deref())) + .collect(); + let bytes_written = f.write_vectored(&ioslices).await?; + + Ok(types::Size::try_from(bytes_written)?) + } + + async fn fd_pwrite( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ciovs: types::CiovecArray, + offset: types::Filesize, + ) -> Result { + let f = self.table().get_file(u32::from(fd))?; + // Access mode check normalizes error returned (windows would prefer ACCES here) + if !f.access_mode.contains(FileAccessMode::WRITE) { + Err(types::Errno::Badf)? + } + let f = &f.file; + + let guest_slices: Vec> = ciovs + .iter() + .map(|iov_ptr| { + let iov_ptr = iov_ptr?; + let iov: types::Ciovec = memory.read(iov_ptr)?; + Ok(memory.as_cow(iov.buf.as_array(iov.buf_len))?) + }) + .collect::>()?; + + let ioslices: Vec = guest_slices + .iter() + .map(|s| IoSlice::new(s.deref())) + .collect(); + let bytes_written = f.write_vectored_at(&ioslices, offset).await?; + + Ok(types::Size::try_from(bytes_written)?) + } + + async fn fd_prestat_get( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + let table = self.table(); + let dir_entry: Arc = table.get(u32::from(fd)).map_err(|_| Error::badf())?; + if let Some(preopen) = dir_entry.preopen_path() { + let path_str = preopen.to_str().ok_or_else(|| Error::not_supported())?; + let pr_name_len = u32::try_from(path_str.as_bytes().len())?; + Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len })) + } else { + Err(Error::not_supported().context("file is not a preopen")) + } + } + + async fn fd_prestat_dir_name( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + path: GuestPtr, + path_max_len: types::Size, + ) -> Result<(), Error> { + let table = self.table(); + let dir_entry: Arc = table.get(u32::from(fd)).map_err(|_| Error::not_dir())?; + if let Some(preopen) = dir_entry.preopen_path() { + let path_bytes = preopen + .to_str() + .ok_or_else(|| Error::not_supported())? + .as_bytes(); + let path_len = path_bytes.len(); + if path_len > path_max_len as usize { + return Err(Error::name_too_long()); + } + let path = path.as_array(path_len as u32); + memory.copy_from_slice(path_bytes, path)?; + Ok(()) + } else { + Err(Error::not_supported()) + } + } + async fn fd_renumber( + &mut self, + _memory: &mut GuestMemory<'_>, + from: types::Fd, + to: types::Fd, + ) -> Result<(), Error> { + let table = self.table(); + let from = u32::from(from); + let to = u32::from(to); + if !table.contains_key(from) { + return Err(Error::badf()); + } + if !table.contains_key(to) { + return Err(Error::badf()); + } + table.renumber(from, to) + } + + async fn fd_seek( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + offset: types::Filedelta, + whence: types::Whence, + ) -> Result { + use std::io::SeekFrom; + let whence = match whence { + types::Whence::Cur => SeekFrom::Current(offset), + types::Whence::End => SeekFrom::End(offset), + types::Whence::Set => { + SeekFrom::Start(offset.try_into().map_err(|_| Error::invalid_argument())?) + } + }; + let newoffset = self + .table() + .get_file(u32::from(fd))? + .file + .seek(whence) + .await?; + Ok(newoffset) + } + + async fn fd_sync(&mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd) -> Result<(), Error> { + self.table().get_file(u32::from(fd))?.file.sync().await?; + Ok(()) + } + + async fn fd_tell( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + ) -> Result { + let offset = self + .table() + .get_file(u32::from(fd))? + .file + .seek(std::io::SeekFrom::Current(0)) + .await?; + Ok(offset) + } + + async fn fd_readdir( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + mut buf: GuestPtr, + buf_len: types::Size, + cookie: types::Dircookie, + ) -> Result { + let mut bufused = 0; + for entity in self + .table() + .get_dir(u32::from(fd))? + .dir + .readdir(ReaddirCursor::from(cookie)) + .await? + { + let entity = entity?; + let dirent_raw = dirent_bytes(types::Dirent::try_from(&entity)?); + let dirent_len: types::Size = dirent_raw.len().try_into()?; + let name_raw = entity.name.as_bytes(); + let name_len: types::Size = name_raw.len().try_into()?; + + // Copy as many bytes of the dirent as we can, up to the end of the buffer + let dirent_copy_len = std::cmp::min(dirent_len, buf_len - bufused); + let raw = buf.as_array(dirent_copy_len); + memory.copy_from_slice(&dirent_raw[..dirent_copy_len as usize], raw)?; + + // If the dirent struct wasn't compiled entirely, return that we filled the buffer, which + // tells libc that we're not at EOF. + if dirent_copy_len < dirent_len { + return Ok(buf_len); + } + + buf = buf.add(dirent_copy_len)?; + bufused += dirent_copy_len; + + // Copy as many bytes of the name as we can, up to the end of the buffer + let name_copy_len = std::cmp::min(name_len, buf_len - bufused); + let raw = buf.as_array(name_copy_len); + memory.copy_from_slice(&name_raw[..name_copy_len as usize], raw)?; + + // If the dirent struct wasn't copied entirely, return that we filled the buffer, which + // tells libc that we're not at EOF + + if name_copy_len < name_len { + return Ok(buf_len); + } + + buf = buf.add(name_copy_len)?; + bufused += name_copy_len; + } + Ok(bufused) + } + + async fn path_create_directory( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + ) -> Result<(), Error> { + self.table() + .get_dir(u32::from(dirfd))? + .dir + .create_dir(memory.as_cow_str(path)?.deref()) + .await + } + + async fn path_filestat_get( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + flags: types::Lookupflags, + path: GuestPtr, + ) -> Result { + let filestat = self + .table() + .get_dir(u32::from(dirfd))? + .dir + .get_path_filestat( + memory.as_cow_str(path)?.deref(), + flags.contains(types::Lookupflags::SYMLINK_FOLLOW), + ) + .await?; + Ok(types::Filestat::from(filestat)) + } + + async fn path_filestat_set_times( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + flags: types::Lookupflags, + path: GuestPtr, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<(), Error> { + let set_atim = fst_flags.contains(types::Fstflags::ATIM); + let set_atim_now = fst_flags.contains(types::Fstflags::ATIM_NOW); + let set_mtim = fst_flags.contains(types::Fstflags::MTIM); + let set_mtim_now = fst_flags.contains(types::Fstflags::MTIM_NOW); + + let atim = systimespec(set_atim, atim, set_atim_now).map_err(|e| e.context("atim"))?; + let mtim = systimespec(set_mtim, mtim, set_mtim_now).map_err(|e| e.context("mtim"))?; + self.table() + .get_dir(u32::from(dirfd))? + .dir + .set_times( + memory.as_cow_str(path)?.deref(), + atim, + mtim, + flags.contains(types::Lookupflags::SYMLINK_FOLLOW), + ) + .await + } + + async fn path_link( + &mut self, + memory: &mut GuestMemory<'_>, + src_fd: types::Fd, + src_flags: types::Lookupflags, + src_path: GuestPtr, + target_fd: types::Fd, + target_path: GuestPtr, + ) -> Result<(), Error> { + let table = self.table(); + let src_dir = table.get_dir(u32::from(src_fd))?; + let target_dir = table.get_dir(u32::from(target_fd))?; + let symlink_follow = src_flags.contains(types::Lookupflags::SYMLINK_FOLLOW); + if symlink_follow { + return Err(Error::invalid_argument() + .context("symlink following on path_link is not supported")); + } + + src_dir + .dir + .hard_link( + memory.as_cow_str(src_path)?.deref(), + target_dir.dir.deref(), + memory.as_cow_str(target_path)?.deref(), + ) + .await + } + + async fn path_open( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + dirflags: types::Lookupflags, + path: GuestPtr, + oflags: types::Oflags, + fs_rights_base: types::Rights, + _fs_rights_inheriting: types::Rights, + fdflags: types::Fdflags, + ) -> Result { + let table = self.table(); + let dirfd = u32::from(dirfd); + if table.is::(dirfd) { + return Err(Error::not_dir()); + } + let dir_entry = table.get_dir(dirfd)?; + + let symlink_follow = dirflags.contains(types::Lookupflags::SYMLINK_FOLLOW); + + let oflags = OFlags::from(&oflags); + let fdflags = FdFlags::from(fdflags); + let path = memory.as_cow_str(path)?; + + let read = fs_rights_base.contains(types::Rights::FD_READ); + let write = fs_rights_base.contains(types::Rights::FD_WRITE); + let access_mode = if read { + FileAccessMode::READ + } else { + FileAccessMode::empty() + } | if write { + FileAccessMode::WRITE + } else { + FileAccessMode::empty() + }; + + let file = dir_entry + .dir + .open_file(symlink_follow, path.deref(), oflags, read, write, fdflags) + .await?; + drop(dir_entry); + + let fd = match file { + // Paper over a divergence between Windows and POSIX, where + // POSIX returns EISDIR if you open a directory with the + // WRITE flag: https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html#:~:text=EISDIR + #[cfg(windows)] + OpenResult::Dir(_) if write => { + return Err(types::Errno::Isdir.into()); + } + OpenResult::File(file) => table.push(Arc::new(FileEntry::new(file, access_mode)))?, + OpenResult::Dir(child_dir) => table.push(Arc::new(DirEntry::new(None, child_dir)))?, + }; + Ok(types::Fd::from(fd)) + } + + async fn path_readlink( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + buf: GuestPtr, + buf_len: types::Size, + ) -> Result { + let link = self + .table() + .get_dir(u32::from(dirfd))? + .dir + .read_link(memory.as_cow_str(path)?.deref()) + .await? + .into_os_string() + .into_string() + .map_err(|_| Error::illegal_byte_sequence().context("link contents"))?; + let link_bytes = link.as_bytes(); + // Like posix readlink(2), silently truncate links when they are larger than the + // destination buffer: + let link_len = std::cmp::min(link_bytes.len(), buf_len as usize); + let buf = buf.as_array(link_len as u32); + memory.copy_from_slice(&link_bytes[..link_len], buf)?; + Ok(link_len as types::Size) + } + + async fn path_remove_directory( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + ) -> Result<(), Error> { + self.table() + .get_dir(u32::from(dirfd))? + .dir + .remove_dir(memory.as_cow_str(path)?.deref()) + .await + } + + async fn path_rename( + &mut self, + memory: &mut GuestMemory<'_>, + src_fd: types::Fd, + src_path: GuestPtr, + dest_fd: types::Fd, + dest_path: GuestPtr, + ) -> Result<(), Error> { + let table = self.table(); + let src_dir = table.get_dir(u32::from(src_fd))?; + let dest_dir = table.get_dir(u32::from(dest_fd))?; + src_dir + .dir + .rename( + memory.as_cow_str(src_path)?.deref(), + dest_dir.dir.deref(), + memory.as_cow_str(dest_path)?.deref(), + ) + .await + } + + async fn path_symlink( + &mut self, + memory: &mut GuestMemory<'_>, + src_path: GuestPtr, + dirfd: types::Fd, + dest_path: GuestPtr, + ) -> Result<(), Error> { + self.table() + .get_dir(u32::from(dirfd))? + .dir + .symlink( + memory.as_cow_str(src_path)?.deref(), + memory.as_cow_str(dest_path)?.deref(), + ) + .await + } + + async fn path_unlink_file( + &mut self, + memory: &mut GuestMemory<'_>, + dirfd: types::Fd, + path: GuestPtr, + ) -> Result<(), Error> { + self.table() + .get_dir(u32::from(dirfd))? + .dir + .unlink_file(memory.as_cow_str(path)?.deref()) + .await + } + + async fn poll_oneoff( + &mut self, + memory: &mut GuestMemory<'_>, + subs: GuestPtr, + events: GuestPtr, + nsubscriptions: types::Size, + ) -> Result { + if nsubscriptions == 0 { + return Err(Error::invalid_argument().context("nsubscriptions must be nonzero")); + } + + // Special-case a `poll_oneoff` which is just sleeping on a single + // relative timer event, such as what WASI libc uses to implement sleep + // functions. This supports all clock IDs, because POSIX says that + // `clock_settime` doesn't effect relative sleeps. + if nsubscriptions == 1 { + let sub = memory.read(subs)?; + if let types::SubscriptionU::Clock(clocksub) = sub.u { + if !clocksub + .flags + .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) + { + self.sched + .sleep(Duration::from_nanos(clocksub.timeout)) + .await?; + memory.write( + events, + types::Event { + userdata: sub.userdata, + error: types::Errno::Success, + type_: types::Eventtype::Clock, + fd_readwrite: fd_readwrite_empty(), + }, + )?; + return Ok(1); + } + } + } + + let table = &self.table; + // We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside + let mut read_refs: Vec<(Arc, Option)> = Vec::new(); + let mut write_refs: Vec<(Arc, Option)> = Vec::new(); + + let mut poll = Poll::new(); + + let subs = subs.as_array(nsubscriptions); + for sub_elem in subs.iter() { + let sub_ptr = sub_elem?; + let sub = memory.read(sub_ptr)?; + match sub.u { + types::SubscriptionU::Clock(clocksub) => match clocksub.id { + types::Clockid::Monotonic => { + let clock = self.clocks.monotonic()?; + let precision = Duration::from_nanos(clocksub.precision); + let duration = Duration::from_nanos(clocksub.timeout); + let start = if clocksub + .flags + .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) + { + clock.creation_time + } else { + clock.abs_clock.now(precision) + }; + let deadline = start + .checked_add(duration) + .ok_or_else(|| Error::overflow().context("deadline"))?; + poll.subscribe_monotonic_clock( + &*clock.abs_clock, + deadline, + precision, + sub.userdata.into(), + ) + } + types::Clockid::Realtime => { + // POSIX specifies that functions like `nanosleep` and others use the + // `REALTIME` clock. But it also says that `clock_settime` has no effect + // on threads waiting in these functions. MONOTONIC should always have + // resolution at least as good as REALTIME, so we can translate a + // non-absolute `REALTIME` request into a `MONOTONIC` request. + let clock = self.clocks.monotonic()?; + let precision = Duration::from_nanos(clocksub.precision); + let duration = Duration::from_nanos(clocksub.timeout); + let deadline = if clocksub + .flags + .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) + { + return Err(Error::not_supported()); + } else { + clock + .abs_clock + .now(precision) + .checked_add(duration) + .ok_or_else(|| Error::overflow().context("deadline"))? + }; + poll.subscribe_monotonic_clock( + &*clock.abs_clock, + deadline, + precision, + sub.userdata.into(), + ) + } + _ => Err(Error::invalid_argument() + .context("timer subscriptions only support monotonic timer"))?, + }, + types::SubscriptionU::FdRead(readsub) => { + let fd = readsub.file_descriptor; + let file_ref = table.get_file(u32::from(fd))?; + read_refs.push((file_ref, Some(sub.userdata.into()))); + } + types::SubscriptionU::FdWrite(writesub) => { + let fd = writesub.file_descriptor; + let file_ref = table.get_file(u32::from(fd))?; + write_refs.push((file_ref, Some(sub.userdata.into()))); + } + } + } + + let mut read_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new(); + for (file_lock, userdata) in read_refs.iter_mut() { + read_mut_refs.push((file_lock.file.deref(), userdata.take().unwrap())); + } + + for (f, ud) in read_mut_refs.iter_mut() { + poll.subscribe_read(*f, *ud); + } + + let mut write_mut_refs: Vec<(&dyn WasiFile, Userdata)> = Vec::new(); + for (file_lock, userdata) in write_refs.iter_mut() { + write_mut_refs.push((file_lock.file.deref(), userdata.take().unwrap())); + } + + for (f, ud) in write_mut_refs.iter_mut() { + poll.subscribe_write(*f, *ud); + } + + self.sched.poll_oneoff(&mut poll).await?; + + let results = poll.results(); + let num_results = results.len(); + assert!( + num_results <= nsubscriptions as usize, + "results exceeds subscriptions" + ); + let events = events.as_array( + num_results + .try_into() + .expect("not greater than nsubscriptions"), + ); + for ((result, userdata), event_elem) in results.into_iter().zip(events.iter()) { + let event_ptr = event_elem?; + let userdata: types::Userdata = userdata.into(); + memory.write( + event_ptr, + match result { + SubscriptionResult::Read(r) => { + let type_ = types::Eventtype::FdRead; + match r { + Ok((nbytes, flags)) => types::Event { + userdata, + error: types::Errno::Success, + type_, + fd_readwrite: types::EventFdReadwrite { + nbytes, + flags: types::Eventrwflags::from(&flags), + }, + }, + Err(e) => types::Event { + userdata, + error: e.downcast().map_err(Error::trap)?, + type_, + fd_readwrite: fd_readwrite_empty(), + }, + } + } + SubscriptionResult::Write(r) => { + let type_ = types::Eventtype::FdWrite; + match r { + Ok((nbytes, flags)) => types::Event { + userdata, + error: types::Errno::Success, + type_, + fd_readwrite: types::EventFdReadwrite { + nbytes, + flags: types::Eventrwflags::from(&flags), + }, + }, + Err(e) => types::Event { + userdata, + error: e.downcast().map_err(Error::trap)?, + type_, + fd_readwrite: fd_readwrite_empty(), + }, + } + } + SubscriptionResult::MonotonicClock(r) => { + let type_ = types::Eventtype::Clock; + types::Event { + userdata, + error: match r { + Ok(()) => types::Errno::Success, + Err(e) => e.downcast().map_err(Error::trap)?, + }, + type_, + fd_readwrite: fd_readwrite_empty(), + } + } + }, + )?; + } + + Ok(num_results.try_into().expect("results fit into memory")) + } + + async fn proc_exit( + &mut self, + _memory: &mut GuestMemory<'_>, + status: types::Exitcode, + ) -> EnvError { + // Check that the status is within WASI's range. + if status < 126 { + I32Exit(status as i32).into() + } else { + EnvError::msg("exit with invalid exit status outside of [0..126)") + } + } + + async fn proc_raise( + &mut self, + _memory: &mut GuestMemory<'_>, + _sig: types::Signal, + ) -> Result<(), Error> { + Err(Error::trap(EnvError::msg("proc_raise unsupported"))) + } + + async fn sched_yield(&mut self, _memory: &mut GuestMemory<'_>) -> Result<(), Error> { + self.sched.sched_yield().await + } + + async fn random_get( + &mut self, + memory: &mut GuestMemory<'_>, + buf: GuestPtr, + buf_len: types::Size, + ) -> Result<(), Error> { + let buf = buf.as_array(buf_len); + if memory.is_shared_memory() { + // If the Wasm memory is shared, copy to an intermediate buffer to + // avoid Rust unsafety (i.e., the called function could rely on + // `&mut [u8]`'s exclusive ownership which is not guaranteed due to + // potential access from other threads). + let mut copied: u32 = 0; + while copied < buf.len() { + let len = (buf.len() - copied).min(MAX_SHARED_BUFFER_SIZE as u32); + let mut tmp = vec![0; len as usize]; + self.random.lock().unwrap().fill_bytes(&mut tmp); + let dest = buf.get_range(copied..copied + len).unwrap(); + memory.copy_from_slice(&tmp, dest)?; + copied += len; + } + } else { + // If the Wasm memory is non-shared, copy directly into the linear + // memory. + let mem = &mut memory.as_slice_mut(buf)?.unwrap(); + self.random.lock().unwrap().fill_bytes(mem); + } + Ok(()) + } + + async fn sock_accept( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + flags: types::Fdflags, + ) -> Result { + let table = self.table(); + let f = table.get_file(u32::from(fd))?; + let file = f.file.sock_accept(FdFlags::from(flags)).await?; + let fd = table.push(Arc::new(FileEntry::new(file, FileAccessMode::all())))?; + Ok(types::Fd::from(fd)) + } + + async fn sock_recv( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + ri_data: types::IovecArray, + ri_flags: types::Riflags, + ) -> Result<(types::Size, types::Roflags), Error> { + let f = self.table().get_file(u32::from(fd))?; + + let iovs: Vec> = ri_data + .iter() + .map(|iov_ptr| { + let iov_ptr = iov_ptr?; + let iov: types::Iovec = memory.read(iov_ptr)?; + Ok(iov.buf.as_array(iov.buf_len)) + }) + .collect::>()?; + + // If the first iov structure is from shared memory we can safely assume + // all the rest will be. We then read into memory based on the memory's + // shared-ness: + // - if not shared, we copy directly into the Wasm memory + // - if shared, we use an intermediate buffer; this avoids Rust unsafety + // due to holding on to a `&mut [u8]` of Wasm memory when we cannot + // guarantee the `&mut` exclusivity--other threads could be modifying + // the data as this functions writes to it. Though likely there is no + // issue with OS writing to io structs in multi-threaded scenarios, + // since we do not know here if `&dyn WasiFile` does anything else + // (e.g., read), we cautiously incur some performance overhead by + // copying twice. + let is_shared_memory = memory.is_shared_memory(); + let (bytes_read, ro_flags) = if is_shared_memory { + // For shared memory, read into an intermediate buffer. Only the + // first iov will be filled and even then the read is capped by the + // `MAX_SHARED_BUFFER_SIZE`, so users are expected to re-call. + let iov = iovs.into_iter().next(); + if let Some(iov) = iov { + let mut buffer = vec![0; (iov.len() as usize).min(MAX_SHARED_BUFFER_SIZE)]; + let (bytes_read, ro_flags) = f + .file + .sock_recv(&mut [IoSliceMut::new(&mut buffer)], RiFlags::from(ri_flags)) + .await?; + let iov = iov + .get_range(0..bytes_read.try_into()?) + .expect("it should always be possible to slice the iov smaller"); + memory.copy_from_slice(&buffer[0..bytes_read.try_into()?], iov)?; + (bytes_read, ro_flags) + } else { + return Ok((0, RoFlags::empty().into())); + } + } else { + // Convert all of the unsafe guest slices to safe ones--this uses + // Wiggle's internal borrow checker to ensure no overlaps. We assume + // here that, because the memory is not shared, there are no other + // threads to access it while it is written to. + let guest_slice: &mut [u8] = match iovs.into_iter().filter(|iov| iov.len() > 0).next() { + Some(iov) => memory.as_slice_mut(iov)?.unwrap(), + None => &mut [], + }; + + // Read directly into the Wasm memory. + f.file + .sock_recv(&mut [IoSliceMut::new(guest_slice)], RiFlags::from(ri_flags)) + .await? + }; + + Ok((types::Size::try_from(bytes_read)?, ro_flags.into())) + } + + async fn sock_send( + &mut self, + memory: &mut GuestMemory<'_>, + fd: types::Fd, + si_data: types::CiovecArray, + _si_flags: types::Siflags, + ) -> Result { + let f = self.table().get_file(u32::from(fd))?; + + let guest_slices: Vec> = si_data + .iter() + .map(|iov_ptr| { + let iov_ptr = iov_ptr?; + let iov: types::Ciovec = memory.read(iov_ptr)?; + Ok(memory.as_cow(iov.buf.as_array(iov.buf_len))?) + }) + .collect::>()?; + + let ioslices: Vec = guest_slices + .iter() + .map(|s| IoSlice::new(s.deref())) + .collect(); + let bytes_written = f.file.sock_send(&ioslices, SiFlags::empty()).await?; + + Ok(types::Size::try_from(bytes_written)?) + } + + async fn sock_shutdown( + &mut self, + _memory: &mut GuestMemory<'_>, + fd: types::Fd, + how: types::Sdflags, + ) -> Result<(), Error> { + let f = self.table().get_file(u32::from(fd))?; + + f.file.sock_shutdown(SdFlags::from(how)).await + } +} + +impl From for Advice { + fn from(advice: types::Advice) -> Advice { + match advice { + types::Advice::Normal => Advice::Normal, + types::Advice::Sequential => Advice::Sequential, + types::Advice::Random => Advice::Random, + types::Advice::Willneed => Advice::WillNeed, + types::Advice::Dontneed => Advice::DontNeed, + types::Advice::Noreuse => Advice::NoReuse, + } + } +} + +impl From<&FdStat> for types::Fdstat { + fn from(fdstat: &FdStat) -> types::Fdstat { + let mut fs_rights_base = types::Rights::empty(); + if fdstat.access_mode.contains(FileAccessMode::READ) { + fs_rights_base |= types::Rights::FD_READ; + } + if fdstat.access_mode.contains(FileAccessMode::WRITE) { + fs_rights_base |= types::Rights::FD_WRITE; + } + types::Fdstat { + fs_filetype: types::Filetype::from(&fdstat.filetype), + fs_rights_base, + fs_rights_inheriting: types::Rights::empty(), + fs_flags: types::Fdflags::from(fdstat.flags), + } + } +} + +impl From<&FileType> for types::Filetype { + fn from(ft: &FileType) -> types::Filetype { + match ft { + FileType::Directory => types::Filetype::Directory, + FileType::BlockDevice => types::Filetype::BlockDevice, + FileType::CharacterDevice => types::Filetype::CharacterDevice, + FileType::RegularFile => types::Filetype::RegularFile, + FileType::SocketDgram => types::Filetype::SocketDgram, + FileType::SocketStream => types::Filetype::SocketStream, + FileType::SymbolicLink => types::Filetype::SymbolicLink, + FileType::Unknown => types::Filetype::Unknown, + FileType::Pipe => types::Filetype::Unknown, + } + } +} + +macro_rules! convert_flags { + ($from:ty, $to:ty, $($flag:ident),+) => { + impl From<$from> for $to { + fn from(f: $from) -> $to { + let mut out = <$to>::empty(); + $( + if f.contains(<$from>::$flag) { + out |= <$to>::$flag; + } + )+ + out + } + } + } +} + +macro_rules! convert_flags_bidirectional { + ($from:ty, $to:ty, $($rest:tt)*) => { + convert_flags!($from, $to, $($rest)*); + convert_flags!($to, $from, $($rest)*); + } +} + +convert_flags_bidirectional!( + FdFlags, + types::Fdflags, + APPEND, + DSYNC, + NONBLOCK, + RSYNC, + SYNC +); + +convert_flags_bidirectional!(RiFlags, types::Riflags, RECV_PEEK, RECV_WAITALL); + +convert_flags_bidirectional!(RoFlags, types::Roflags, RECV_DATA_TRUNCATED); + +convert_flags_bidirectional!(SdFlags, types::Sdflags, RD, WR); + +impl From<&types::Oflags> for OFlags { + fn from(oflags: &types::Oflags) -> OFlags { + let mut out = OFlags::empty(); + if oflags.contains(types::Oflags::CREAT) { + out = out | OFlags::CREATE; + } + if oflags.contains(types::Oflags::DIRECTORY) { + out = out | OFlags::DIRECTORY; + } + if oflags.contains(types::Oflags::EXCL) { + out = out | OFlags::EXCLUSIVE; + } + if oflags.contains(types::Oflags::TRUNC) { + out = out | OFlags::TRUNCATE; + } + out + } +} + +impl From<&OFlags> for types::Oflags { + fn from(oflags: &OFlags) -> types::Oflags { + let mut out = types::Oflags::empty(); + if oflags.contains(OFlags::CREATE) { + out = out | types::Oflags::CREAT; + } + if oflags.contains(OFlags::DIRECTORY) { + out = out | types::Oflags::DIRECTORY; + } + if oflags.contains(OFlags::EXCLUSIVE) { + out = out | types::Oflags::EXCL; + } + if oflags.contains(OFlags::TRUNCATE) { + out = out | types::Oflags::TRUNC; + } + out + } +} +impl From for types::Filestat { + fn from(stat: Filestat) -> types::Filestat { + types::Filestat { + dev: stat.device_id, + ino: stat.inode, + filetype: types::Filetype::from(&stat.filetype), + nlink: stat.nlink, + size: stat.size, + atim: stat + .atim + .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64) + .unwrap_or(0), + mtim: stat + .mtim + .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64) + .unwrap_or(0), + ctim: stat + .ctim + .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as u64) + .unwrap_or(0), + } + } +} + +impl TryFrom<&ReaddirEntity> for types::Dirent { + type Error = Error; + fn try_from(e: &ReaddirEntity) -> Result { + Ok(types::Dirent { + d_ino: e.inode, + d_namlen: e.name.as_bytes().len().try_into()?, + d_type: types::Filetype::from(&e.filetype), + d_next: e.next.into(), + }) + } +} + +fn dirent_bytes(dirent: types::Dirent) -> Vec { + use wiggle::GuestType; + assert_eq!( + types::Dirent::guest_size(), + std::mem::size_of::() as u32, + "Dirent guest repr and host repr should match" + ); + assert_eq!( + 1, + std::mem::size_of_val(&dirent.d_type), + "Dirent member d_type should be endian-invariant" + ); + let size = types::Dirent::guest_size() + .try_into() + .expect("Dirent is smaller than 2^32"); + let mut bytes = Vec::with_capacity(size); + bytes.resize(size, 0); + let ptr = bytes.as_mut_ptr().cast::(); + let guest_dirent = types::Dirent { + d_ino: dirent.d_ino.to_le(), + d_namlen: dirent.d_namlen.to_le(), + d_type: dirent.d_type, // endian-invariant + d_next: dirent.d_next.to_le(), + }; + unsafe { ptr.write_unaligned(guest_dirent) }; + bytes +} + +impl From<&RwEventFlags> for types::Eventrwflags { + fn from(flags: &RwEventFlags) -> types::Eventrwflags { + let mut out = types::Eventrwflags::empty(); + if flags.contains(RwEventFlags::HANGUP) { + out = out | types::Eventrwflags::FD_READWRITE_HANGUP; + } + out + } +} + +fn fd_readwrite_empty() -> types::EventFdReadwrite { + types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::empty(), + } +} + +fn systimespec( + set: bool, + ts: types::Timestamp, + now: bool, +) -> Result, Error> { + if set && now { + Err(Error::invalid_argument()) + } else if set { + #[cfg(feature = "use_cap_std")] + return Ok(Some(SystemTimeSpec::Absolute( + SystemClock::UNIX_EPOCH + Duration::from_nanos(ts), + ))); + #[cfg(not(feature = "use_cap_std"))] + Ok(Some(SystemTimeSpec::Absolute( + std::time::SystemTime::UNIX_EPOCH + Duration::from_nanos(ts), + ))) + } else if now { + Ok(Some(SystemTimeSpec::SymbolicNow)) + } else { + Ok(None) + } +} + +// This is the default subset of base Rights reported for directories prior to +// https://github.com/bytecodealliance/wasmtime/pull/6265. Some +// implementations still expect this set of rights to be reported. +pub(crate) fn directory_base_rights() -> types::Rights { + types::Rights::PATH_CREATE_DIRECTORY + | types::Rights::PATH_CREATE_FILE + | types::Rights::PATH_LINK_SOURCE + | types::Rights::PATH_LINK_TARGET + | types::Rights::PATH_OPEN + | types::Rights::FD_READDIR + | types::Rights::PATH_READLINK + | types::Rights::PATH_RENAME_SOURCE + | types::Rights::PATH_RENAME_TARGET + | types::Rights::PATH_SYMLINK + | types::Rights::PATH_REMOVE_DIRECTORY + | types::Rights::PATH_UNLINK_FILE + | types::Rights::PATH_FILESTAT_GET + | types::Rights::PATH_FILESTAT_SET_TIMES + | types::Rights::FD_FILESTAT_GET + | types::Rights::FD_FILESTAT_SET_TIMES +} + +// This is the default subset of inheriting Rights reported for directories +// prior to https://github.com/bytecodealliance/wasmtime/pull/6265. Some +// implementations still expect this set of rights to be reported. +pub(crate) fn directory_inheriting_rights() -> types::Rights { + types::Rights::FD_DATASYNC + | types::Rights::FD_READ + | types::Rights::FD_SEEK + | types::Rights::FD_FDSTAT_SET_FLAGS + | types::Rights::FD_SYNC + | types::Rights::FD_TELL + | types::Rights::FD_WRITE + | types::Rights::FD_ADVISE + | types::Rights::FD_ALLOCATE + | types::Rights::FD_FILESTAT_GET + | types::Rights::FD_FILESTAT_SET_SIZE + | types::Rights::FD_FILESTAT_SET_TIMES + | types::Rights::POLL_FD_READWRITE + | directory_base_rights() +} diff --git a/crates/wasi-common/src/snapshots/preview_1/error.rs b/crates/wasi-common/src/snapshots/preview_1/error.rs new file mode 100644 index 00000000..6bd4957f --- /dev/null +++ b/crates/wasi-common/src/snapshots/preview_1/error.rs @@ -0,0 +1,264 @@ +use wasmtime_internal_core::error::format_err; + +pub use super::types::{Errno, Error}; + +pub trait ErrorExt { + fn not_found() -> Self; + fn too_big() -> Self; + fn badf() -> Self; + fn exist() -> Self; + fn illegal_byte_sequence() -> Self; + fn invalid_argument() -> Self; + fn io() -> Self; + fn name_too_long() -> Self; + fn not_dir() -> Self; + fn not_supported() -> Self; + fn overflow() -> Self; + fn range() -> Self; + fn seek_pipe() -> Self; + fn perm() -> Self; +} + +impl ErrorExt for Error { + fn not_found() -> Self { + Errno::Noent.into() + } + fn too_big() -> Self { + Errno::TooBig.into() + } + fn badf() -> Self { + Errno::Badf.into() + } + fn exist() -> Self { + Errno::Exist.into() + } + fn illegal_byte_sequence() -> Self { + Errno::Ilseq.into() + } + fn invalid_argument() -> Self { + Errno::Inval.into() + } + fn io() -> Self { + Errno::Io.into() + } + fn name_too_long() -> Self { + Errno::Nametoolong.into() + } + fn not_dir() -> Self { + Errno::Notdir.into() + } + fn not_supported() -> Self { + Errno::Notsup.into() + } + fn overflow() -> Self { + Errno::Overflow.into() + } + fn range() -> Self { + Errno::Range.into() + } + fn seek_pipe() -> Self { + Errno::Spipe.into() + } + fn perm() -> Self { + Errno::Perm.into() + } +} + +#[cfg(target_arch = "wasm32")] +fn from_raw_os_error(err: Option) -> Option { + None +} + +#[cfg(unix)] +fn from_raw_os_error(err: Option) -> Option { + use rustix::io::Errno as RustixErrno; + if err.is_none() { + return None; + } + Some(match RustixErrno::from_raw_os_error(err.unwrap()) { + RustixErrno::AGAIN => Errno::Again.into(), + RustixErrno::PIPE => Errno::Pipe.into(), + RustixErrno::PERM => Errno::Perm.into(), + RustixErrno::NOENT => Errno::Noent.into(), + RustixErrno::NOMEM => Errno::Nomem.into(), + RustixErrno::TOOBIG => Errno::TooBig.into(), + RustixErrno::IO => Errno::Io.into(), + RustixErrno::BADF => Errno::Badf.into(), + RustixErrno::BUSY => Errno::Busy.into(), + RustixErrno::ACCESS => Errno::Acces.into(), + RustixErrno::FAULT => Errno::Fault.into(), + RustixErrno::NOTDIR => Errno::Notdir.into(), + RustixErrno::ISDIR => Errno::Isdir.into(), + RustixErrno::INVAL => Errno::Inval.into(), + RustixErrno::EXIST => Errno::Exist.into(), + RustixErrno::FBIG => Errno::Fbig.into(), + RustixErrno::NOSPC => Errno::Nospc.into(), + RustixErrno::SPIPE => Errno::Spipe.into(), + RustixErrno::MFILE => Errno::Mfile.into(), + RustixErrno::MLINK => Errno::Mlink.into(), + RustixErrno::NAMETOOLONG => Errno::Nametoolong.into(), + RustixErrno::NFILE => Errno::Nfile.into(), + RustixErrno::NOTEMPTY => Errno::Notempty.into(), + RustixErrno::LOOP => Errno::Loop.into(), + RustixErrno::OVERFLOW => Errno::Overflow.into(), + RustixErrno::ILSEQ => Errno::Ilseq.into(), + RustixErrno::NOTSUP => Errno::Notsup.into(), + RustixErrno::ADDRINUSE => Errno::Addrinuse.into(), + RustixErrno::CANCELED => Errno::Canceled.into(), + RustixErrno::ADDRNOTAVAIL => Errno::Addrnotavail.into(), + RustixErrno::AFNOSUPPORT => Errno::Afnosupport.into(), + RustixErrno::ALREADY => Errno::Already.into(), + RustixErrno::CONNABORTED => Errno::Connaborted.into(), + RustixErrno::CONNREFUSED => Errno::Connrefused.into(), + RustixErrno::CONNRESET => Errno::Connreset.into(), + RustixErrno::DESTADDRREQ => Errno::Destaddrreq.into(), + RustixErrno::DQUOT => Errno::Dquot.into(), + RustixErrno::HOSTUNREACH => Errno::Hostunreach.into(), + RustixErrno::INPROGRESS => Errno::Inprogress.into(), + RustixErrno::INTR => Errno::Intr.into(), + RustixErrno::ISCONN => Errno::Isconn.into(), + RustixErrno::MSGSIZE => Errno::Msgsize.into(), + RustixErrno::NETDOWN => Errno::Netdown.into(), + RustixErrno::NETRESET => Errno::Netreset.into(), + RustixErrno::NETUNREACH => Errno::Netunreach.into(), + RustixErrno::NOBUFS => Errno::Nobufs.into(), + RustixErrno::NOPROTOOPT => Errno::Noprotoopt.into(), + RustixErrno::NOTCONN => Errno::Notconn.into(), + RustixErrno::NOTSOCK => Errno::Notsock.into(), + RustixErrno::PROTONOSUPPORT => Errno::Protonosupport.into(), + RustixErrno::PROTOTYPE => Errno::Prototype.into(), + RustixErrno::STALE => Errno::Stale.into(), + RustixErrno::TIMEDOUT => Errno::Timedout.into(), + + // On some platforms.into(), these have the same value as other errno values. + #[allow(unreachable_patterns, reason = "see comment")] + RustixErrno::WOULDBLOCK => Errno::Again.into(), + #[allow(unreachable_patterns, reason = "see comment")] + RustixErrno::OPNOTSUPP => Errno::Notsup.into(), + + _ => return None, + }) +} +#[cfg(windows)] +fn from_raw_os_error(raw_os_error: Option) -> Option { + use windows_sys::Win32::Foundation; + use windows_sys::Win32::Networking::WinSock; + + match raw_os_error.map(|code| code as u32) { + Some(Foundation::ERROR_BAD_ENVIRONMENT) => return Some(Errno::TooBig.into()), + Some(Foundation::ERROR_FILE_NOT_FOUND) => return Some(Errno::Noent.into()), + Some(Foundation::ERROR_PATH_NOT_FOUND) => return Some(Errno::Noent.into()), + Some(Foundation::ERROR_TOO_MANY_OPEN_FILES) => return Some(Errno::Nfile.into()), + Some(Foundation::ERROR_ACCESS_DENIED) => return Some(Errno::Acces.into()), + Some(Foundation::ERROR_SHARING_VIOLATION) => return Some(Errno::Acces.into()), + Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => return Some(Errno::Perm.into()), + Some(Foundation::ERROR_INVALID_HANDLE) => return Some(Errno::Badf.into()), + Some(Foundation::ERROR_INVALID_NAME) => return Some(Errno::Noent.into()), + Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => return Some(Errno::Nomem.into()), + Some(Foundation::ERROR_OUTOFMEMORY) => return Some(Errno::Nomem.into()), + Some(Foundation::ERROR_DIR_NOT_EMPTY) => return Some(Errno::Notempty.into()), + Some(Foundation::ERROR_NOT_READY) => return Some(Errno::Busy.into()), + Some(Foundation::ERROR_BUSY) => return Some(Errno::Busy.into()), + Some(Foundation::ERROR_NOT_SUPPORTED) => return Some(Errno::Notsup.into()), + Some(Foundation::ERROR_FILE_EXISTS) => return Some(Errno::Exist.into()), + Some(Foundation::ERROR_BROKEN_PIPE) => return Some(Errno::Pipe.into()), + Some(Foundation::ERROR_BUFFER_OVERFLOW) => return Some(Errno::Nametoolong.into()), + Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => return Some(Errno::Inval.into()), + Some(Foundation::ERROR_NEGATIVE_SEEK) => return Some(Errno::Inval.into()), + Some(Foundation::ERROR_DIRECTORY) => return Some(Errno::Notdir.into()), + Some(Foundation::ERROR_ALREADY_EXISTS) => return Some(Errno::Exist.into()), + Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => return Some(Errno::Loop.into()), + Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => return Some(Errno::Isdir.into()), + _ => {} + } + + match raw_os_error { + Some(WinSock::WSAEWOULDBLOCK) => Some(Errno::Again.into()), + Some(WinSock::WSAECANCELLED) => Some(Errno::Canceled.into()), + Some(WinSock::WSA_E_CANCELLED) => Some(Errno::Canceled.into()), + Some(WinSock::WSAEBADF) => Some(Errno::Badf.into()), + Some(WinSock::WSAEFAULT) => Some(Errno::Fault.into()), + Some(WinSock::WSAEINVAL) => Some(Errno::Inval.into()), + Some(WinSock::WSAEMFILE) => Some(Errno::Mfile.into()), + Some(WinSock::WSAENAMETOOLONG) => Some(Errno::Nametoolong.into()), + Some(WinSock::WSAENOTEMPTY) => Some(Errno::Notempty.into()), + Some(WinSock::WSAELOOP) => Some(Errno::Loop.into()), + Some(WinSock::WSAEOPNOTSUPP) => Some(Errno::Notsup.into()), + Some(WinSock::WSAEADDRINUSE) => Some(Errno::Addrinuse.into()), + Some(WinSock::WSAEACCES) => Some(Errno::Acces.into()), + Some(WinSock::WSAEADDRNOTAVAIL) => Some(Errno::Addrnotavail.into()), + Some(WinSock::WSAEAFNOSUPPORT) => Some(Errno::Afnosupport.into()), + Some(WinSock::WSAEALREADY) => Some(Errno::Already.into()), + Some(WinSock::WSAECONNABORTED) => Some(Errno::Connaborted.into()), + Some(WinSock::WSAECONNREFUSED) => Some(Errno::Connrefused.into()), + Some(WinSock::WSAECONNRESET) => Some(Errno::Connreset.into()), + Some(WinSock::WSAEDESTADDRREQ) => Some(Errno::Destaddrreq.into()), + Some(WinSock::WSAEDQUOT) => Some(Errno::Dquot.into()), + Some(WinSock::WSAEHOSTUNREACH) => Some(Errno::Hostunreach.into()), + Some(WinSock::WSAEINPROGRESS) => Some(Errno::Inprogress.into()), + Some(WinSock::WSAEINTR) => Some(Errno::Intr.into()), + Some(WinSock::WSAEISCONN) => Some(Errno::Isconn.into()), + Some(WinSock::WSAEMSGSIZE) => Some(Errno::Msgsize.into()), + Some(WinSock::WSAENETDOWN) => Some(Errno::Netdown.into()), + Some(WinSock::WSAENETRESET) => Some(Errno::Netreset.into()), + Some(WinSock::WSAENETUNREACH) => Some(Errno::Netunreach.into()), + Some(WinSock::WSAENOBUFS) => Some(Errno::Nobufs.into()), + Some(WinSock::WSAENOPROTOOPT) => Some(Errno::Noprotoopt.into()), + Some(WinSock::WSAENOTCONN) => Some(Errno::Notconn.into()), + Some(WinSock::WSAENOTSOCK) => Some(Errno::Notsock.into()), + Some(WinSock::WSAEPROTONOSUPPORT) => Some(Errno::Protonosupport.into()), + Some(WinSock::WSAEPROTOTYPE) => Some(Errno::Prototype.into()), + Some(WinSock::WSAESTALE) => Some(Errno::Stale.into()), + Some(WinSock::WSAETIMEDOUT) => Some(Errno::Timedout.into()), + _ => None, + } +} + +impl From for Error { + fn from(err: std::io::Error) -> Error { + match from_raw_os_error(err.raw_os_error()) { + Some(errno) => errno, + None => match err.kind() { + std::io::ErrorKind::NotFound => Errno::Noent.into(), + std::io::ErrorKind::PermissionDenied => Errno::Perm.into(), + std::io::ErrorKind::AlreadyExists => Errno::Exist.into(), + std::io::ErrorKind::InvalidInput => Errno::Inval.into(), + std::io::ErrorKind::WouldBlock => Errno::Again.into(), + _ => Error::trap(format_err!(err).context("Unknown OS error")), + }, + } + } +} + +impl From for Error { + fn from(err: wiggle::GuestError) -> Error { + use wiggle::GuestError::*; + match err { + InvalidFlagValue { .. } => Errno::Inval.into(), + InvalidEnumValue { .. } => Errno::Inval.into(), + // As per + // https://github.com/WebAssembly/wasi/blob/main/legacy/tools/witx-docs.md#pointers + // + // > If a misaligned pointer is passed to a function, the function + // > shall trap. + // > + // > If an out-of-bounds pointer is passed to a function and the + // > function needs to dereference it, the function shall trap. + // + // so this turns OOB and misalignment errors into traps. + PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => { + Error::trap(err.into()) + } + InvalidUtf8 { .. } => Errno::Ilseq.into(), + TryFromIntError { .. } => Errno::Overflow.into(), + SliceLengthsDiffer { .. } => Errno::Fault.into(), + InFunc { err, .. } => Error::from(*err), + } + } +} + +impl From for Error { + fn from(_err: std::num::TryFromIntError) -> Error { + Errno::Overflow.into() + } +} diff --git a/crates/wasi-common/src/string_array.rs b/crates/wasi-common/src/string_array.rs new file mode 100644 index 00000000..f35efe03 --- /dev/null +++ b/crates/wasi-common/src/string_array.rs @@ -0,0 +1,75 @@ +use crate::{Error, ErrorExt}; +use wiggle::{GuestMemory, GuestPtr}; + +pub struct StringArray { + elems: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum StringArrayError { + #[error("Number of elements exceeds 2^32")] + NumberElements, + #[error("Element size exceeds 2^32")] + ElementSize, + #[error("Cumulative size exceeds 2^32")] + CumulativeSize, +} + +impl StringArray { + pub fn new() -> Self { + StringArray { elems: Vec::new() } + } + + pub fn push(&mut self, elem: String) -> Result<(), StringArrayError> { + if self.elems.len() + 1 > std::u32::MAX as usize { + return Err(StringArrayError::NumberElements); + } + if elem.as_bytes().len() + 1 > std::u32::MAX as usize { + return Err(StringArrayError::ElementSize); + } + if self.cumulative_size() as usize + elem.as_bytes().len() + 1 > std::u32::MAX as usize { + return Err(StringArrayError::CumulativeSize); + } + self.elems.push(elem); + Ok(()) + } + + pub fn number_elements(&self) -> u32 { + self.elems.len() as u32 + } + + pub fn cumulative_size(&self) -> u32 { + self.elems + .iter() + .map(|e| e.as_bytes().len() + 1) + .sum::() as u32 + } + + pub fn write_to_guest( + &self, + memory: &mut GuestMemory<'_>, + buffer: GuestPtr, + element_heads: GuestPtr>, + ) -> Result<(), Error> { + let element_heads = element_heads.as_array(self.number_elements()); + let buffer = buffer.as_array(self.cumulative_size()); + let mut cursor = 0; + for (elem, head) in self.elems.iter().zip(element_heads.iter()) { + let bytes = elem.as_bytes(); + let len = bytes.len() as u32; + { + let elem_buffer = buffer + .get_range(cursor..(cursor + len)) + .ok_or(Error::invalid_argument())?; // Elements don't fit in buffer provided + memory.copy_from_slice(bytes, elem_buffer)?; + } + memory.write( + buffer.get(cursor + len).ok_or(Error::invalid_argument())?, + 0, + )?; // 0 terminate + memory.write(head?, buffer.get(cursor).expect("already validated"))?; + cursor += len + 1; + } + Ok(()) + } +} diff --git a/crates/wasi-common/src/sync/clocks.rs b/crates/wasi-common/src/sync/clocks.rs new file mode 100644 index 00000000..bd333e10 --- /dev/null +++ b/crates/wasi-common/src/sync/clocks.rs @@ -0,0 +1,41 @@ +use crate::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock}; +use cap_std::time::{Duration, Instant, SystemTime}; +use cap_std::{AmbientAuthority, ambient_authority}; +use cap_time_ext::{MonotonicClockExt, SystemClockExt}; + +pub struct SystemClock(cap_std::time::SystemClock); + +impl SystemClock { + pub fn new(ambient_authority: AmbientAuthority) -> Self { + SystemClock(cap_std::time::SystemClock::new(ambient_authority)) + } +} +impl WasiSystemClock for SystemClock { + fn resolution(&self) -> Duration { + self.0.resolution() + } + fn now(&self, precision: Duration) -> SystemTime { + self.0.now_with(precision) + } +} + +pub struct MonotonicClock(cap_std::time::MonotonicClock); +impl MonotonicClock { + pub fn new(ambient_authority: AmbientAuthority) -> Self { + MonotonicClock(cap_std::time::MonotonicClock::new(ambient_authority)) + } +} +impl WasiMonotonicClock for MonotonicClock { + fn resolution(&self) -> Duration { + self.0.resolution() + } + fn now(&self, precision: Duration) -> Instant { + self.0.now_with(precision) + } +} + +pub fn clocks_ctx() -> WasiClocks { + WasiClocks::new() + .with_system(SystemClock::new(ambient_authority())) + .with_monotonic(MonotonicClock::new(ambient_authority())) +} diff --git a/crates/wasi-common/src/sync/dir.rs b/crates/wasi-common/src/sync/dir.rs new file mode 100644 index 00000000..ff94f726 --- /dev/null +++ b/crates/wasi-common/src/sync/dir.rs @@ -0,0 +1,335 @@ +use crate::sync::file::{filetype_from, File}; +use crate::{ + dir::{ReaddirCursor, ReaddirEntity, WasiDir}, + file::{FdFlags, FileType, Filestat, OFlags}, + Error, ErrorExt, +}; +use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, OpenOptionsMaybeDirExt, SystemTimeSpec}; +use cap_std::fs; +use std::any::Any; +use std::path::{Path, PathBuf}; +use system_interface::fs::GetSetFdFlags; + +pub struct Dir(fs::Dir); + +pub enum OpenResult { + File(File), + Dir(Dir), +} + +impl Dir { + pub fn from_cap_std(dir: fs::Dir) -> Self { + Dir(dir) + } + + pub fn open_file_( + &self, + symlink_follow: bool, + path: &str, + oflags: OFlags, + read: bool, + write: bool, + fdflags: FdFlags, + ) -> Result { + use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; + + let mut opts = fs::OpenOptions::new(); + opts.maybe_dir(true); + + if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) { + opts.create_new(true); + opts.write(true); + } else if oflags.contains(OFlags::CREATE) { + opts.create(true); + opts.write(true); + } + if oflags.contains(OFlags::TRUNCATE) { + opts.truncate(true); + } + if read { + opts.read(true); + } + if write { + opts.write(true); + } else { + // If not opened write, open read. This way the OS lets us open the file. + // If FileCaps::READ is not set, read calls will be rejected at the + // get_cap check. + opts.read(true); + } + if fdflags.contains(FdFlags::APPEND) { + opts.append(true); + } + + if symlink_follow { + opts.follow(FollowSymlinks::Yes); + } else { + opts.follow(FollowSymlinks::No); + } + // the DSYNC, SYNC, and RSYNC flags are ignored! We do not + // have support for them in cap-std yet. + // ideally OpenOptions would just support this though: + // https://github.com/bytecodealliance/cap-std/issues/146 + if fdflags.intersects( + crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC, + ) { + return Err(Error::not_supported().context("SYNC family of FdFlags")); + } + + if oflags.contains(OFlags::DIRECTORY) { + if oflags.contains(OFlags::CREATE) + || oflags.contains(OFlags::EXCLUSIVE) + || oflags.contains(OFlags::TRUNCATE) + { + return Err(Error::invalid_argument().context("directory oflags")); + } + } + + let mut f = self.0.open_with(Path::new(path), &opts)?; + if f.metadata()?.is_dir() { + Ok(OpenResult::Dir(Dir::from_cap_std(fs::Dir::from_std_file( + f.into_std(), + )))) + } else if oflags.contains(OFlags::DIRECTORY) { + Err(Error::not_dir().context("expected directory but got file")) + } else { + // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: + if fdflags.contains(crate::file::FdFlags::NONBLOCK) { + let set_fd_flags = f.new_set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?; + f.set_fd_flags(set_fd_flags)?; + } + Ok(OpenResult::File(File::from_cap_std(f))) + } + } + + pub fn rename_(&self, src_path: &str, dest_dir: &Self, dest_path: &str) -> Result<(), Error> { + self.0 + .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?; + Ok(()) + } + pub fn hard_link_( + &self, + src_path: &str, + target_dir: &Self, + target_path: &str, + ) -> Result<(), Error> { + let src_path = Path::new(src_path); + let target_path = Path::new(target_path); + self.0.hard_link(src_path, &target_dir.0, target_path)?; + Ok(()) + } +} + +#[async_trait::async_trait] +impl WasiDir for Dir { + fn as_any(&self) -> &dyn Any { + self + } + async fn open_file( + &self, + symlink_follow: bool, + path: &str, + oflags: OFlags, + read: bool, + write: bool, + fdflags: FdFlags, + ) -> Result { + let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?; + match f { + OpenResult::File(f) => Ok(crate::dir::OpenResult::File(Box::new(f))), + OpenResult::Dir(d) => Ok(crate::dir::OpenResult::Dir(Box::new(d))), + } + } + + async fn create_dir(&self, path: &str) -> Result<(), Error> { + self.0.create_dir(Path::new(path))?; + Ok(()) + } + async fn readdir( + &self, + cursor: ReaddirCursor, + ) -> Result> + Send>, Error> { + // We need to keep a full-fidelity io Error around to check for a special failure mode + // on windows, but also this function can fail due to an illegal byte sequence in a + // filename, which we can't construct an io Error to represent. + enum ReaddirError { + Io(std::io::Error), + IllegalSequence, + } + impl From for ReaddirError { + fn from(e: std::io::Error) -> ReaddirError { + ReaddirError::Io(e) + } + } + + // cap_std's read_dir does not include . and .., we should prepend these. + // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't + // have enough info to make a ReaddirEntity yet. + let dir_meta = self.0.dir_metadata()?; + let rd = vec![ + { + let name = ".".to_owned(); + Ok::<_, ReaddirError>((FileType::Directory, dir_meta.ino(), name)) + }, + { + let name = "..".to_owned(); + Ok((FileType::Directory, dir_meta.ino(), name)) + }, + ] + .into_iter() + .chain({ + // Now process the `DirEntry`s: + let entries = self.0.entries()?.map(|entry| { + let entry = entry?; + let meta = entry.full_metadata()?; + let inode = meta.ino(); + let filetype = filetype_from(&meta.file_type()); + let name = entry + .file_name() + .into_string() + .map_err(|_| ReaddirError::IllegalSequence)?; + Ok((filetype, inode, name)) + }); + + // On Windows, filter out files like `C:\DumpStack.log.tmp` which we + // can't get a full metadata for. + #[cfg(windows)] + let entries = entries.filter(|entry| { + use windows_sys::Win32::Foundation::{ + ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION, + }; + if let Err(ReaddirError::Io(err)) = entry { + if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32) + || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32) + { + return false; + } + } + true + }); + + entries + }) + // Enumeration of the iterator makes it possible to define the ReaddirCursor + .enumerate() + .map(|(ix, r)| match r { + Ok((filetype, inode, name)) => Ok(ReaddirEntity { + next: ReaddirCursor::from(ix as u64 + 1), + filetype, + inode, + name, + }), + Err(ReaddirError::Io(e)) => Err(e.into()), + Err(ReaddirError::IllegalSequence) => Err(Error::illegal_byte_sequence()), + }) + .skip(u64::from(cursor) as usize); + + Ok(Box::new(rd)) + } + + async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { + self.0.symlink(src_path, dest_path)?; + Ok(()) + } + async fn remove_dir(&self, path: &str) -> Result<(), Error> { + self.0.remove_dir(Path::new(path))?; + Ok(()) + } + + async fn unlink_file(&self, path: &str) -> Result<(), Error> { + self.0.remove_file_or_symlink(Path::new(path))?; + Ok(()) + } + async fn read_link(&self, path: &str) -> Result { + let link = self.0.read_link(Path::new(path))?; + Ok(link) + } + async fn get_filestat(&self) -> Result { + let meta = self.0.dir_metadata()?; + Ok(Filestat { + device_id: meta.dev(), + inode: meta.ino(), + filetype: filetype_from(&meta.file_type()), + nlink: meta.nlink(), + size: meta.len(), + atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), + mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), + ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + }) + } + async fn get_path_filestat( + &self, + path: &str, + follow_symlinks: bool, + ) -> Result { + let meta = if follow_symlinks { + self.0.metadata(Path::new(path))? + } else { + self.0.symlink_metadata(Path::new(path))? + }; + Ok(Filestat { + device_id: meta.dev(), + inode: meta.ino(), + filetype: filetype_from(&meta.file_type()), + nlink: meta.nlink(), + size: meta.len(), + atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), + mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), + ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + }) + } + async fn rename( + &self, + src_path: &str, + dest_dir: &dyn WasiDir, + dest_path: &str, + ) -> Result<(), Error> { + let dest_dir = dest_dir + .as_any() + .downcast_ref::() + .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; + self.rename_(src_path, dest_dir, dest_path) + } + async fn hard_link( + &self, + src_path: &str, + target_dir: &dyn WasiDir, + target_path: &str, + ) -> Result<(), Error> { + let target_dir = target_dir + .as_any() + .downcast_ref::() + .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; + self.hard_link_(src_path, target_dir, target_path) + } + async fn set_times( + &self, + path: &str, + atime: Option, + mtime: Option, + follow_symlinks: bool, + ) -> Result<(), Error> { + if follow_symlinks { + self.0.set_times( + Path::new(path), + convert_systimespec(atime), + convert_systimespec(mtime), + )?; + } else { + self.0.set_symlink_times( + Path::new(path), + convert_systimespec(atime), + convert_systimespec(mtime), + )?; + } + Ok(()) + } +} + +fn convert_systimespec(t: Option) -> Option { + match t { + Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), + Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), + None => None, + } +} diff --git a/crates/wasi-common/src/sync/file.rs b/crates/wasi-common/src/sync/file.rs new file mode 100644 index 00000000..dcb024c6 --- /dev/null +++ b/crates/wasi-common/src/sync/file.rs @@ -0,0 +1,250 @@ +use crate::{ + Error, ErrorExt, + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, +}; +use cap_fs_ext::MetadataExt; +use fs_set_times::{SetTimes, SystemTimeSpec}; +use io_lifetimes::AsFilelike; +use std::any::Any; +use std::io::{self, IsTerminal}; +use system_interface::{ + fs::{FileIoExt, GetSetFdFlags}, + io::{IoExt, ReadReady}, +}; + +pub struct File(cap_std::fs::File); + +impl File { + pub fn from_cap_std(file: cap_std::fs::File) -> Self { + File(file) + } +} + +#[async_trait::async_trait] +impl WasiFile for File { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.0.as_fd()) + } + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.0.as_raw_handle_or_socket()) + } + async fn datasync(&self) -> Result<(), Error> { + self.0.sync_data()?; + Ok(()) + } + async fn sync(&self) -> Result<(), Error> { + self.0.sync_all()?; + Ok(()) + } + async fn get_filetype(&self) -> Result { + let meta = self.0.metadata()?; + Ok(filetype_from(&meta.file_type())) + } + async fn get_fdflags(&self) -> Result { + let fdflags = get_fd_flags(&self.0)?; + Ok(fdflags) + } + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + if fdflags.intersects( + crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC, + ) { + return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); + } + let set_fd_flags = self.0.new_set_fd_flags(to_sysif_fdflags(fdflags))?; + self.0.set_fd_flags(set_fd_flags)?; + Ok(()) + } + async fn get_filestat(&self) -> Result { + let meta = self.0.metadata()?; + Ok(Filestat { + device_id: meta.dev(), + inode: meta.ino(), + filetype: filetype_from(&meta.file_type()), + nlink: meta.nlink(), + size: meta.len(), + atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), + mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), + ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + }) + } + async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + self.0.set_len(size)?; + Ok(()) + } + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + self.0.advise(offset, len, convert_advice(advice))?; + Ok(()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + Ok(()) + } + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { + let n = self.0.read_vectored(bufs)?; + Ok(n.try_into()?) + } + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { + let n = self.0.read_vectored_at(bufs, offset)?; + Ok(n.try_into()?) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + let n = self.0.write_vectored(bufs)?; + Ok(n.try_into()?) + } + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { + if bufs.iter().map(|i| i.len()).sum::() == 0 { + return Ok(0); + } + let n = self.0.write_vectored_at(bufs, offset)?; + Ok(n.try_into()?) + } + async fn seek(&self, pos: std::io::SeekFrom) -> Result { + Ok(self.0.seek(pos)?) + } + async fn peek(&self, buf: &mut [u8]) -> Result { + let n = self.0.peek(buf)?; + Ok(n.try_into()?) + } + fn num_ready_bytes(&self) -> Result { + Ok(self.0.num_ready_bytes()?) + } + fn isatty(&self) -> bool { + #[cfg(unix)] + return self.0.as_fd().is_terminal(); + #[cfg(windows)] + return self.0.as_handle().is_terminal(); + } +} + +pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { + use cap_fs_ext::FileTypeExt; + if ft.is_dir() { + FileType::Directory + } else if ft.is_symlink() { + FileType::SymbolicLink + } else if ft.is_socket() { + if ft.is_block_device() { + FileType::SocketDgram + } else { + FileType::SocketStream + } + } else if ft.is_block_device() { + FileType::BlockDevice + } else if ft.is_char_device() { + FileType::CharacterDevice + } else if ft.is_file() { + FileType::RegularFile + } else { + FileType::Unknown + } +} + +#[cfg(windows)] +use io_lifetimes::{AsHandle, BorrowedHandle}; +#[cfg(windows)] +impl AsHandle for File { + fn as_handle(&self) -> BorrowedHandle<'_> { + self.0.as_handle() + } +} + +#[cfg(windows)] +use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; +#[cfg(windows)] +impl AsRawHandleOrSocket for File { + #[inline] + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.as_raw_handle_or_socket() + } +} + +#[cfg(unix)] +use io_lifetimes::{AsFd, BorrowedFd}; + +#[cfg(unix)] +impl AsFd for File { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +pub(crate) fn convert_systimespec(t: Option) -> Option { + match t { + Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())), + Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), + None => None, + } +} + +pub(crate) fn to_sysif_fdflags(f: crate::file::FdFlags) -> system_interface::fs::FdFlags { + let mut out = system_interface::fs::FdFlags::empty(); + if f.contains(crate::file::FdFlags::APPEND) { + out |= system_interface::fs::FdFlags::APPEND; + } + if f.contains(crate::file::FdFlags::DSYNC) { + out |= system_interface::fs::FdFlags::DSYNC; + } + if f.contains(crate::file::FdFlags::NONBLOCK) { + out |= system_interface::fs::FdFlags::NONBLOCK; + } + if f.contains(crate::file::FdFlags::RSYNC) { + out |= system_interface::fs::FdFlags::RSYNC; + } + if f.contains(crate::file::FdFlags::SYNC) { + out |= system_interface::fs::FdFlags::SYNC; + } + out +} + +/// Return the file-descriptor flags for a given file-like object. +/// +/// This returns the flags needed to implement [`WasiFile::get_fdflags`]. +pub fn get_fd_flags(f: Filelike) -> io::Result { + let f = f.as_filelike().get_fd_flags()?; + let mut out = crate::file::FdFlags::empty(); + if f.contains(system_interface::fs::FdFlags::APPEND) { + out |= crate::file::FdFlags::APPEND; + } + if f.contains(system_interface::fs::FdFlags::DSYNC) { + out |= crate::file::FdFlags::DSYNC; + } + if f.contains(system_interface::fs::FdFlags::NONBLOCK) { + out |= crate::file::FdFlags::NONBLOCK; + } + if f.contains(system_interface::fs::FdFlags::RSYNC) { + out |= crate::file::FdFlags::RSYNC; + } + if f.contains(system_interface::fs::FdFlags::SYNC) { + out |= crate::file::FdFlags::SYNC; + } + Ok(out) +} + +fn convert_advice(advice: Advice) -> system_interface::fs::Advice { + match advice { + Advice::Normal => system_interface::fs::Advice::Normal, + Advice::Sequential => system_interface::fs::Advice::Sequential, + Advice::Random => system_interface::fs::Advice::Random, + Advice::WillNeed => system_interface::fs::Advice::WillNeed, + Advice::DontNeed => system_interface::fs::Advice::DontNeed, + Advice::NoReuse => system_interface::fs::Advice::NoReuse, + } +} diff --git a/crates/wasi-common/src/sync/mod.rs b/crates/wasi-common/src/sync/mod.rs new file mode 100644 index 00000000..4308cc60 --- /dev/null +++ b/crates/wasi-common/src/sync/mod.rs @@ -0,0 +1,137 @@ +//! The `wasi-cap-std-sync` crate provides impl of `WasiFile` and `WasiDir` in +//! terms of `cap_std::fs::{File, Dir}`. These types provide sandboxed access +//! to the local filesystem on both Unix and Windows. +//! +//! All syscalls are hidden behind the `cap-std` hierarchy, with the lone +//! exception of the `sched` implementation, which is provided for both unix +//! and windows in separate modules. +//! +//! Any `wasi_common::{WasiCtx, WasiCtxBuilder}` is interoperable with the +//! implementations provided in `wasi_common::sync`. However, for convenience, +//! this module provides its own `WasiCtxBuilder` that hooks up to all of the +//! crate's components, i.e. it fills in all of the arguments to +//! `WasiCtx::builder(...)`, presents `preopen_dir` in terms of +//! `cap_std::fs::Dir`, and provides convenience methods for inheriting the +//! parent process's stdio, args, and env. + +pub mod clocks; +pub mod dir; +pub mod file; +pub mod net; +pub mod sched; +pub mod stdio; + +pub use cap_std::ambient_authority; +pub use cap_std::fs::Dir; +pub use cap_std::net::TcpListener; +pub use clocks::clocks_ctx; +pub use sched::sched_ctx; + +use self::net::Socket; +use crate::{Error, WasiCtx, WasiFile, file::FileAccessMode, table::Table}; +use rand::{Rng, SeedableRng}; +use std::mem; +use std::path::Path; + +pub struct WasiCtxBuilder { + ctx: WasiCtx, + built: bool, +} + +impl WasiCtxBuilder { + pub fn new() -> Self { + WasiCtxBuilder { + ctx: WasiCtx::new(random_ctx(), clocks_ctx(), sched_ctx(), Table::new()), + built: false, + } + } + pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> { + self.ctx.push_env(var, value)?; + Ok(self) + } + pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> { + for (k, v) in env { + self.ctx.push_env(k, v)?; + } + Ok(self) + } + pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> { + for (key, value) in std::env::vars() { + self.ctx.push_env(&key, &value)?; + } + Ok(self) + } + pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> { + self.ctx.push_arg(arg)?; + Ok(self) + } + pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> { + for a in arg { + self.ctx.push_arg(&a)?; + } + Ok(self) + } + pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> { + for arg in std::env::args() { + self.ctx.push_arg(&arg)?; + } + Ok(self) + } + pub fn stdin(&mut self, f: Box) -> &mut Self { + self.ctx.set_stdin(f); + self + } + pub fn stdout(&mut self, f: Box) -> &mut Self { + self.ctx.set_stdout(f); + self + } + pub fn stderr(&mut self, f: Box) -> &mut Self { + self.ctx.set_stderr(f); + self + } + pub fn inherit_stdin(&mut self) -> &mut Self { + self.stdin(Box::new(crate::sync::stdio::stdin())) + } + pub fn inherit_stdout(&mut self) -> &mut Self { + self.stdout(Box::new(crate::sync::stdio::stdout())) + } + pub fn inherit_stderr(&mut self) -> &mut Self { + self.stderr(Box::new(crate::sync::stdio::stderr())) + } + pub fn inherit_stdio(&mut self) -> &mut Self { + self.inherit_stdin().inherit_stdout().inherit_stderr() + } + pub fn preopened_dir( + &mut self, + dir: Dir, + guest_path: impl AsRef, + ) -> Result<&mut Self, Error> { + let dir = Box::new(crate::sync::dir::Dir::from_cap_std(dir)); + self.ctx.push_preopened_dir(dir, guest_path)?; + Ok(self) + } + pub fn preopened_socket( + &mut self, + fd: u32, + socket: impl Into, + ) -> Result<&mut Self, Error> { + let socket: Socket = socket.into(); + let file: Box = socket.into(); + self.ctx + .insert_file(fd, file, FileAccessMode::READ | FileAccessMode::WRITE); + Ok(self) + } + pub fn build(&mut self) -> WasiCtx { + assert!(!self.built); + let WasiCtxBuilder { ctx, .. } = mem::replace(self, Self::new()); + self.built = true; + ctx + } +} + +pub fn random_ctx() -> Box { + Box::new(rand::rngs::StdRng::from_seed(rand::random())) +} + +#[cfg(feature = "wasmtime")] +super::define_wasi!(block_on); diff --git a/crates/wasi-common/src/sync/net.rs b/crates/wasi-common/src/sync/net.rs new file mode 100644 index 00000000..55602f6f --- /dev/null +++ b/crates/wasi-common/src/sync/net.rs @@ -0,0 +1,393 @@ +use crate::{ + Error, ErrorExt, + file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags, WasiFile}, +}; +#[cfg(windows)] +use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; +use io_lifetimes::AsSocketlike; +#[cfg(unix)] +use io_lifetimes::{AsFd, BorrowedFd}; +#[cfg(windows)] +use io_lifetimes::{AsSocket, BorrowedSocket}; +use std::any::Any; +use std::io; +#[cfg(unix)] +use system_interface::fs::GetSetFdFlags; +use system_interface::io::IoExt; +use system_interface::io::IsReadWrite; +use system_interface::io::ReadReady; + +pub enum Socket { + TcpListener(cap_std::net::TcpListener), + TcpStream(cap_std::net::TcpStream), + #[cfg(unix)] + UnixStream(cap_std::os::unix::net::UnixStream), + #[cfg(unix)] + UnixListener(cap_std::os::unix::net::UnixListener), +} + +impl From for Socket { + fn from(listener: cap_std::net::TcpListener) -> Self { + Self::TcpListener(listener) + } +} + +impl From for Socket { + fn from(stream: cap_std::net::TcpStream) -> Self { + Self::TcpStream(stream) + } +} + +#[cfg(unix)] +impl From for Socket { + fn from(listener: cap_std::os::unix::net::UnixListener) -> Self { + Self::UnixListener(listener) + } +} + +#[cfg(unix)] +impl From for Socket { + fn from(stream: cap_std::os::unix::net::UnixStream) -> Self { + Self::UnixStream(stream) + } +} + +#[cfg(unix)] +impl From for Box { + fn from(listener: Socket) -> Self { + match listener { + Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)), + Socket::UnixListener(l) => Box::new(crate::sync::net::UnixListener::from_cap_std(l)), + Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)), + Socket::UnixStream(l) => Box::new(crate::sync::net::UnixStream::from_cap_std(l)), + } + } +} + +#[cfg(windows)] +impl From for Box { + fn from(listener: Socket) -> Self { + match listener { + Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)), + Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)), + } + } +} + +macro_rules! wasi_listen_write_impl { + ($ty:ty, $stream:ty) => { + #[async_trait::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.0.as_fd()) + } + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.0.as_raw_handle_or_socket()) + } + async fn sock_accept(&self, fdflags: FdFlags) -> Result, Error> { + let (stream, _) = self.0.accept()?; + let mut stream = <$stream>::from_cap_std(stream); + stream.set_fdflags(fdflags).await?; + Ok(Box::new(stream)) + } + async fn get_filetype(&self) -> Result { + Ok(FileType::SocketStream) + } + #[cfg(unix)] + async fn get_fdflags(&self) -> Result { + let fdflags = get_fd_flags(&self.0)?; + Ok(fdflags) + } + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + if fdflags == crate::file::FdFlags::NONBLOCK { + self.0.set_nonblocking(true)?; + } else if fdflags.is_empty() { + self.0.set_nonblocking(false)?; + } else { + return Err( + Error::invalid_argument().context("cannot set anything else than NONBLOCK") + ); + } + Ok(()) + } + fn num_ready_bytes(&self) -> Result { + Ok(1) + } + } + + #[cfg(windows)] + impl AsSocket for $ty { + #[inline] + fn as_socket(&self) -> BorrowedSocket<'_> { + self.0.as_socket() + } + } + + #[cfg(windows)] + impl AsRawHandleOrSocket for $ty { + #[inline] + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.as_raw_handle_or_socket() + } + } + + #[cfg(unix)] + impl AsFd for $ty { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + } + }; +} + +pub struct TcpListener(cap_std::net::TcpListener); + +impl TcpListener { + pub fn from_cap_std(cap_std: cap_std::net::TcpListener) -> Self { + TcpListener(cap_std) + } +} +wasi_listen_write_impl!(TcpListener, TcpStream); + +#[cfg(unix)] +pub struct UnixListener(cap_std::os::unix::net::UnixListener); + +#[cfg(unix)] +impl UnixListener { + pub fn from_cap_std(cap_std: cap_std::os::unix::net::UnixListener) -> Self { + UnixListener(cap_std) + } +} + +#[cfg(unix)] +wasi_listen_write_impl!(UnixListener, UnixStream); + +macro_rules! wasi_stream_write_impl { + ($ty:ty, $std_ty:ty) => { + #[async_trait::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.0.as_fd()) + } + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.0.as_raw_handle_or_socket()) + } + async fn get_filetype(&self) -> Result { + Ok(FileType::SocketStream) + } + #[cfg(unix)] + async fn get_fdflags(&self) -> Result { + let fdflags = get_fd_flags(&self.0)?; + Ok(fdflags) + } + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + if fdflags == crate::file::FdFlags::NONBLOCK { + self.0.set_nonblocking(true)?; + } else if fdflags.is_empty() { + self.0.set_nonblocking(false)?; + } else { + return Err( + Error::invalid_argument().context("cannot set anything else than NONBLOCK") + ); + } + Ok(()) + } + async fn read_vectored<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + ) -> Result { + use std::io::Read; + let n = Read::read_vectored(&mut &*self.as_socketlike_view::<$std_ty>(), bufs)?; + Ok(n.try_into()?) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + use std::io::Write; + let n = Write::write_vectored(&mut &*self.as_socketlike_view::<$std_ty>(), bufs)?; + Ok(n.try_into()?) + } + async fn peek(&self, buf: &mut [u8]) -> Result { + let n = self.0.peek(buf)?; + Ok(n.try_into()?) + } + fn num_ready_bytes(&self) -> Result { + let val = self.as_socketlike_view::<$std_ty>().num_ready_bytes()?; + Ok(val) + } + async fn readable(&self) -> Result<(), Error> { + let (readable, _writeable) = is_read_write(&self.0)?; + if readable { Ok(()) } else { Err(Error::io()) } + } + async fn writable(&self) -> Result<(), Error> { + let (_readable, writeable) = is_read_write(&self.0)?; + if writeable { Ok(()) } else { Err(Error::io()) } + } + + async fn sock_recv<'a>( + &self, + ri_data: &mut [std::io::IoSliceMut<'a>], + ri_flags: RiFlags, + ) -> Result<(u64, RoFlags), Error> { + if (ri_flags & !(RiFlags::RECV_PEEK | RiFlags::RECV_WAITALL)) != RiFlags::empty() { + return Err(Error::not_supported()); + } + + if ri_flags.contains(RiFlags::RECV_PEEK) { + if let Some(first) = ri_data.iter_mut().next() { + let n = self.0.peek(first)?; + return Ok((n as u64, RoFlags::empty())); + } else { + return Ok((0, RoFlags::empty())); + } + } + + if ri_flags.contains(RiFlags::RECV_WAITALL) { + let n: usize = ri_data.iter().map(|buf| buf.len()).sum(); + self.0.read_exact_vectored(ri_data)?; + return Ok((n as u64, RoFlags::empty())); + } + + let n = self.0.read_vectored(ri_data)?; + Ok((n as u64, RoFlags::empty())) + } + + async fn sock_send<'a>( + &self, + si_data: &[std::io::IoSlice<'a>], + si_flags: SiFlags, + ) -> Result { + if si_flags != SiFlags::empty() { + return Err(Error::not_supported()); + } + + let n = self.0.write_vectored(si_data)?; + Ok(n as u64) + } + + async fn sock_shutdown(&self, how: SdFlags) -> Result<(), Error> { + let how = if how == SdFlags::RD | SdFlags::WR { + cap_std::net::Shutdown::Both + } else if how == SdFlags::RD { + cap_std::net::Shutdown::Read + } else if how == SdFlags::WR { + cap_std::net::Shutdown::Write + } else { + return Err(Error::invalid_argument()); + }; + self.0.shutdown(how)?; + Ok(()) + } + } + #[cfg(unix)] + impl AsFd for $ty { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + } + + #[cfg(windows)] + impl AsSocket for $ty { + /// Borrows the socket. + fn as_socket(&self) -> BorrowedSocket<'_> { + self.0.as_socket() + } + } + + #[cfg(windows)] + impl AsRawHandleOrSocket for TcpStream { + #[inline] + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.as_raw_handle_or_socket() + } + } + }; +} + +pub struct TcpStream(cap_std::net::TcpStream); + +impl TcpStream { + pub fn from_cap_std(socket: cap_std::net::TcpStream) -> Self { + TcpStream(socket) + } +} + +wasi_stream_write_impl!(TcpStream, std::net::TcpStream); + +#[cfg(unix)] +pub struct UnixStream(cap_std::os::unix::net::UnixStream); + +#[cfg(unix)] +impl UnixStream { + pub fn from_cap_std(socket: cap_std::os::unix::net::UnixStream) -> Self { + UnixStream(socket) + } +} + +#[cfg(unix)] +wasi_stream_write_impl!(UnixStream, std::os::unix::net::UnixStream); + +pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { + use cap_fs_ext::FileTypeExt; + if ft.is_block_device() { + FileType::SocketDgram + } else { + FileType::SocketStream + } +} + +/// Return the file-descriptor flags for a given file-like object. +/// +/// This returns the flags needed to implement [`WasiFile::get_fdflags`]. +pub fn get_fd_flags(f: Socketlike) -> io::Result { + // On Unix-family platforms, we can use the same system call that we'd use + // for files on sockets here. + #[cfg(not(windows))] + { + let mut out = crate::file::FdFlags::empty(); + if f.get_fd_flags()? + .contains(system_interface::fs::FdFlags::NONBLOCK) + { + out |= crate::file::FdFlags::NONBLOCK; + } + Ok(out) + } + + // On Windows, sockets are different, and there is no direct way to + // query for the non-blocking flag. We can get a sufficient approximation + // by testing whether a zero-length `recv` appears to block. + #[cfg(windows)] + let buf: &mut [u8] = &mut []; + #[cfg(windows)] + match rustix::net::recv(f, buf, rustix::net::RecvFlags::empty()) { + Ok(_) => Ok(crate::file::FdFlags::empty()), + Err(rustix::io::Errno::WOULDBLOCK) => Ok(crate::file::FdFlags::NONBLOCK), + Err(e) => Err(e.into()), + } +} + +/// Return the file-descriptor flags for a given file-like object. +/// +/// This returns the flags needed to implement [`WasiFile::get_fdflags`]. +pub fn is_read_write(f: Socketlike) -> io::Result<(bool, bool)> { + // On Unix-family platforms, we have an `IsReadWrite` impl. + #[cfg(not(windows))] + { + f.is_read_write() + } + + // On Windows, we only have a `TcpStream` impl, so make a view first. + #[cfg(windows)] + { + f.as_socketlike_view::() + .is_read_write() + } +} diff --git a/crates/wasi-common/src/sync/sched.rs b/crates/wasi-common/src/sync/sched.rs new file mode 100644 index 00000000..1b2fa9fa --- /dev/null +++ b/crates/wasi-common/src/sync/sched.rs @@ -0,0 +1,40 @@ +#[cfg(unix)] +pub mod unix; +#[cfg(unix)] +pub use unix::poll_oneoff; + +#[cfg(windows)] +pub mod windows; +#[cfg(windows)] +pub use windows::poll_oneoff; + +use crate::{ + Error, + sched::{Poll, WasiSched}, +}; +use std::thread; +use std::time::Duration; + +pub struct SyncSched {} +impl SyncSched { + pub fn new() -> Self { + Self {} + } +} +#[async_trait::async_trait] +impl WasiSched for SyncSched { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { + poll_oneoff(poll).await + } + async fn sched_yield(&self) -> Result<(), Error> { + thread::yield_now(); + Ok(()) + } + async fn sleep(&self, duration: Duration) -> Result<(), Error> { + std::thread::sleep(duration); + Ok(()) + } +} +pub fn sched_ctx() -> Box { + Box::new(SyncSched::new()) +} diff --git a/crates/wasi-common/src/sync/sched/unix.rs b/crates/wasi-common/src/sync/sched/unix.rs new file mode 100644 index 00000000..a20fb230 --- /dev/null +++ b/crates/wasi-common/src/sync/sched/unix.rs @@ -0,0 +1,83 @@ +use crate::sched::subscription::{RwEventFlags, Subscription}; +use crate::{Error, ErrorExt, sched::Poll}; +use cap_std::time::Duration; +use rustix::event::{PollFd, PollFlags}; + +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); + } + let mut pollfds = Vec::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(f) => { + let fd = f + .file + .pollable() + .ok_or(Error::invalid_argument().context("file is not pollable"))?; + pollfds.push(PollFd::from_borrowed_fd(fd, PollFlags::IN)); + } + + Subscription::Write(f) => { + let fd = f + .file + .pollable() + .ok_or(Error::invalid_argument().context("file is not pollable"))?; + pollfds.push(PollFd::from_borrowed_fd(fd, PollFlags::OUT)); + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + + let ready = loop { + let poll_timeout = if let Some(t) = poll.earliest_clock_deadline() { + let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); + Some( + duration + .try_into() + .map_err(|_| Error::overflow().context("poll timeout"))?, + ) + } else { + None + }; + tracing::debug!( + poll_timeout = tracing::field::debug(poll_timeout), + poll_fds = tracing::field::debug(&pollfds), + "poll" + ); + match rustix::event::poll(&mut pollfds, poll_timeout.as_ref()) { + Ok(ready) => break ready, + Err(rustix::io::Errno::INTR) => continue, + Err(err) => return Err(std::io::Error::from(err).into()), + } + }; + if ready > 0 { + for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) { + let revents = pollfd.revents(); + let (nbytes, rwsub) = match rwsub { + Subscription::Read(sub) => { + let ready = sub.file.num_ready_bytes()?; + (std::cmp::max(ready, 1), sub) + } + Subscription::Write(sub) => (0, sub), + _ => unreachable!(), + }; + if revents.contains(PollFlags::NVAL) { + rwsub.error(Error::badf()); + } else if revents.contains(PollFlags::ERR) { + rwsub.error(Error::io()); + } else if revents.contains(PollFlags::HUP) { + rwsub.complete(nbytes, RwEventFlags::HANGUP); + } else { + rwsub.complete(nbytes, RwEventFlags::empty()); + }; + } + } else { + poll.earliest_clock_deadline() + .expect("timed out") + .result() + .expect("timer deadline is past") + .unwrap() + } + Ok(()) +} diff --git a/crates/wasi-common/src/sync/sched/windows.rs b/crates/wasi-common/src/sync/sched/windows.rs new file mode 100644 index 00000000..a6f4f1fb --- /dev/null +++ b/crates/wasi-common/src/sync/sched/windows.rs @@ -0,0 +1,220 @@ +// The windows scheduler is unmaintained and due for a rewrite. +// +// Rather than use a polling mechanism for file read/write readiness, +// it checks readiness just once, before sleeping for any timer subscriptions. +// Checking stdin readiness uses a worker thread which, once started, lives for the +// lifetime of the process. +// +// We suspect there are bugs in this scheduler, however, we have not +// taken the time to improve it. See bug #2880. + +use crate::sched::subscription::{RwEventFlags, Subscription}; +use crate::{EnvError, Error, ErrorExt, file::WasiFile, sched::Poll}; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; +use std::sync::{LazyLock, Mutex}; +use std::thread; +use std::time::Duration; + +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + poll_oneoff_(poll, wasi_file_is_stdin).await +} + +pub async fn poll_oneoff_<'a>( + poll: &mut Poll<'a>, + file_is_stdin: impl Fn(&dyn WasiFile) -> bool, +) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); + } + + let mut ready = false; + let waitmode = if let Some(t) = poll.earliest_clock_deadline() { + if let Some(duration) = t.duration_until() { + WaitMode::Timeout(duration) + } else { + WaitMode::Immediate + } + } else { + if ready { + WaitMode::Immediate + } else { + WaitMode::Infinite + } + }; + + let mut stdin_read_subs = Vec::new(); + let mut immediate_reads = Vec::new(); + let mut immediate_writes = Vec::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(r) => { + if file_is_stdin(r.file) { + stdin_read_subs.push(r); + } else if r.file.pollable().is_some() { + immediate_reads.push(r); + } else { + return Err(Error::invalid_argument().context("file is not pollable")); + } + } + Subscription::Write(w) => { + if w.file.pollable().is_some() { + immediate_writes.push(w); + } else { + return Err(Error::invalid_argument().context("file is not pollable")); + } + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + + if !stdin_read_subs.is_empty() { + let state = STDIN_POLL + .lock() + .map_err(|_| Error::trap(EnvError::msg("failed to take lock of STDIN_POLL")))? + .poll(waitmode)?; + for readsub in stdin_read_subs.into_iter() { + match state { + PollState::Ready => { + readsub.complete(1, RwEventFlags::empty()); + ready = true; + } + PollState::NotReady | PollState::TimedOut => {} + PollState::Error(ref e) => { + // Unfortunately, we need to deliver the Error to each of the + // subscriptions, but there is no Clone on std::io::Error. So, we convert it to the + // kind, and then back to std::io::Error, and finally to EnvError. + // When its time to turn this into an errno elsewhere, the error kind will + // be inspected. + let ekind = e.kind(); + let ioerror = std::io::Error::from(ekind); + readsub.error(ioerror.into()); + ready = true; + } + } + } + } + for r in immediate_reads { + match r.file.num_ready_bytes() { + Ok(ready_bytes) => { + r.complete(ready_bytes, RwEventFlags::empty()); + ready = true; + } + Err(e) => { + r.error(e); + ready = true; + } + } + } + for w in immediate_writes { + // Everything is always ready for writing, apparently? + w.complete(0, RwEventFlags::empty()); + ready = true; + } + + if !ready { + if let WaitMode::Timeout(duration) = waitmode { + thread::sleep(duration); + } + } + + Ok(()) +} + +pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { + f.as_any().is::() +} + +enum PollState { + Ready, + NotReady, // Not ready, but did not wait + TimedOut, // Not ready, waited until timeout + Error(std::io::Error), +} + +#[derive(Copy, Clone)] +enum WaitMode { + Timeout(Duration), + Infinite, + Immediate, +} + +struct StdinPoll { + request_tx: Sender<()>, + notify_rx: Receiver, +} + +static STDIN_POLL: LazyLock> = LazyLock::new(StdinPoll::new); + +impl StdinPoll { + pub fn new() -> Mutex { + let (request_tx, request_rx) = mpsc::channel(); + let (notify_tx, notify_rx) = mpsc::channel(); + thread::spawn(move || Self::event_loop(request_rx, notify_tx)); + Mutex::new(StdinPoll { + request_tx, + notify_rx, + }) + } + + // This function should not be used directly. + // Correctness of this function crucially depends on the fact that + // mpsc::Receiver is !Sync. + fn poll(&self, wait_mode: WaitMode) -> Result { + match self.notify_rx.try_recv() { + // Clean up possibly unread result from previous poll. + Ok(_) | Err(TryRecvError::Empty) => {} + Err(TryRecvError::Disconnected) => { + return Err(Error::trap(EnvError::msg( + "StdinPoll notify_rx channel closed", + ))); + } + } + + // Notify the worker thread to poll stdin + self.request_tx + .send(()) + .map_err(|_| Error::trap(EnvError::msg("request_tx channel closed")))?; + + // Wait for the worker thread to send a readiness notification + match wait_mode { + WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) { + Ok(r) => Ok(r), + Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut), + Err(RecvTimeoutError::Disconnected) => Err(Error::trap(EnvError::msg( + "StdinPoll notify_rx channel closed", + ))), + }, + WaitMode::Infinite => self + .notify_rx + .recv() + .map_err(|_| Error::trap(EnvError::msg("StdinPoll notify_rx channel closed"))), + WaitMode::Immediate => match self.notify_rx.try_recv() { + Ok(r) => Ok(r), + Err(TryRecvError::Empty) => Ok(PollState::NotReady), + Err(TryRecvError::Disconnected) => Err(Error::trap(EnvError::msg( + "StdinPoll notify_rx channel closed", + ))), + }, + } + } + + fn event_loop(request_rx: Receiver<()>, notify_tx: Sender) -> ! { + use std::io::BufRead; + loop { + // Wait on a request: + request_rx.recv().expect("request_rx channel"); + // Wait for data to appear in stdin. If fill_buf returns any slice, it means + // that either: + // (a) there is some data in stdin, if non-empty, + // (b) EOF was received, if its empty + // Linux returns `POLLIN` in both cases, so we imitate this behavior. + let resp = match std::io::stdin().lock().fill_buf() { + Ok(_) => PollState::Ready, + Err(e) => PollState::Error(e), + }; + // Notify about data in stdin. If the read on this channel has timed out, the + // next poller will have to clean the channel. + notify_tx.send(resp).expect("notify_tx channel"); + } + } +} diff --git a/crates/wasi-common/src/sync/stdio.rs b/crates/wasi-common/src/sync/stdio.rs new file mode 100644 index 00000000..2975a087 --- /dev/null +++ b/crates/wasi-common/src/sync/stdio.rs @@ -0,0 +1,196 @@ +use crate::sync::file::convert_systimespec; +use fs_set_times::SetTimes; +use std::any::Any; +use std::io::{self, IsTerminal, Read, Write}; +use system_interface::io::ReadReady; + +use crate::{ + Error, ErrorExt, + file::{FdFlags, FileType, WasiFile}, +}; +#[cfg(windows)] +use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; +#[cfg(unix)] +use io_lifetimes::{AsFd, BorrowedFd}; +#[cfg(windows)] +use io_lifetimes::{AsHandle, BorrowedHandle}; + +pub struct Stdin(std::io::Stdin); + +pub fn stdin() -> Stdin { + Stdin(std::io::stdin()) +} + +#[async_trait::async_trait] +impl WasiFile for Stdin { + fn as_any(&self) -> &dyn Any { + self + } + + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.0.as_fd()) + } + + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.0.as_raw_handle_or_socket()) + } + + async fn get_filetype(&self) -> Result { + if self.isatty() { + Ok(FileType::CharacterDevice) + } else { + Ok(FileType::Unknown) + } + } + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { + let n = self.0.lock().read_vectored(bufs)?; + Ok(n.try_into().map_err(|_| Error::range())?) + } + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + _offset: u64, + ) -> Result { + Err(Error::seek_pipe()) + } + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { + Err(Error::seek_pipe()) + } + async fn peek(&self, _buf: &mut [u8]) -> Result { + Err(Error::seek_pipe()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + Ok(()) + } + fn num_ready_bytes(&self) -> Result { + Ok(self.0.num_ready_bytes()?) + } + fn isatty(&self) -> bool { + #[cfg(unix)] + return self.0.as_fd().is_terminal(); + #[cfg(windows)] + return self.0.as_handle().is_terminal(); + } +} +#[cfg(windows)] +impl AsHandle for Stdin { + fn as_handle(&self) -> BorrowedHandle<'_> { + self.0.as_handle() + } +} +#[cfg(windows)] +impl AsRawHandleOrSocket for Stdin { + #[inline] + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.as_raw_handle_or_socket() + } +} +#[cfg(unix)] +impl AsFd for Stdin { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +macro_rules! wasi_file_write_impl { + ($ty:ty, $ident:ident) => { + #[async_trait::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.0.as_fd()) + } + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.0.as_raw_handle_or_socket()) + } + async fn get_filetype(&self) -> Result { + if self.isatty() { + Ok(FileType::CharacterDevice) + } else { + Ok(FileType::Unknown) + } + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::APPEND) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + let mut io = self.0.lock(); + let n = io.write_vectored(bufs)?; + // On a successful write additionally flush out the bytes to + // handle stdio buffering done by libstd since WASI interfaces + // here aren't buffered. + io.flush()?; + Ok(n.try_into().map_err(|_| { + Error::range().context("converting write_vectored total length") + })?) + } + async fn write_vectored_at<'a>( + &self, + _bufs: &[io::IoSlice<'a>], + _offset: u64, + ) -> Result { + Err(Error::seek_pipe()) + } + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { + Err(Error::seek_pipe()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + Ok(()) + } + fn isatty(&self) -> bool { + self.0.is_terminal() + } + } + #[cfg(windows)] + impl AsHandle for $ty { + fn as_handle(&self) -> BorrowedHandle<'_> { + self.0.as_handle() + } + } + #[cfg(unix)] + impl AsFd for $ty { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + } + #[cfg(windows)] + impl AsRawHandleOrSocket for $ty { + #[inline] + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.as_raw_handle_or_socket() + } + } + }; +} + +pub struct Stdout(std::io::Stdout); + +pub fn stdout() -> Stdout { + Stdout(std::io::stdout()) +} +wasi_file_write_impl!(Stdout, Stdout); + +pub struct Stderr(std::io::Stderr); + +pub fn stderr() -> Stderr { + Stderr(std::io::stderr()) +} +wasi_file_write_impl!(Stderr, Stderr); diff --git a/crates/wasi-common/src/table.rs b/crates/wasi-common/src/table.rs new file mode 100644 index 00000000..cd817d38 --- /dev/null +++ b/crates/wasi-common/src/table.rs @@ -0,0 +1,114 @@ +use crate::{EnvError, Error, ErrorExt}; +use std::any::Any; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +/// The `Table` type is designed to map u32 handles to resources. The table is now part of the +/// public interface to a `WasiCtx` - it is reference counted so that it can be shared beyond a +/// `WasiCtx` with other WASI proposals (e.g. `wasi-crypto` and `wasi-nn`) to manage their +/// resources. Elements in the `Table` are `Any` typed. +/// +/// The `Table` type is intended to model how the Interface Types concept of Resources is shaping +/// up. Right now it is just an approximation. +pub struct Table(RwLock); + +struct Inner { + map: HashMap>, + next_key: u32, +} + +impl Table { + /// Create an empty table. New insertions will begin at 3, above stdio. + pub fn new() -> Self { + Table(RwLock::new(Inner { + map: HashMap::new(), + next_key: 3, // 0, 1 and 2 are reserved for stdio + })) + } + + /// Insert a resource at a certain index. + pub fn insert_at(&self, key: u32, a: Arc) { + self.0.write().unwrap().map.insert(key, a); + } + + /// Insert a resource at the next available index. + pub fn push(&self, a: Arc) -> Result { + let mut inner = self.0.write().unwrap(); + // NOTE: The performance of this new key calculation could be very bad once keys wrap + // around. + if inner.map.len() == u32::MAX as usize { + return Err(Error::trap(EnvError::msg("table has no free keys"))); + } + loop { + let key = inner.next_key; + inner.next_key += 1; + if inner.map.contains_key(&key) { + continue; + } + inner.map.insert(key, a); + return Ok(key); + } + } + + /// Check if the table has a resource at the given index. + pub fn contains_key(&self, key: u32) -> bool { + self.0.read().unwrap().map.contains_key(&key) + } + + /// Check if the resource at a given index can be downcast to a given type. + /// Note: this will always fail if the resource is already borrowed. + pub fn is(&self, key: u32) -> bool { + if let Some(r) = self.0.read().unwrap().map.get(&key) { + r.is::() + } else { + false + } + } + + /// Get an Arc reference to a resource of a given type at a given index. Multiple + /// immutable references can be borrowed at any given time. + pub fn get(&self, key: u32) -> Result, Error> { + if let Some(r) = self.0.read().unwrap().map.get(&key).cloned() { + r.downcast::() + .map_err(|_| Error::badf().context("element is a different type")) + } else { + Err(Error::badf().context("key not in table")) + } + } + + /// Get a mutable reference to a resource of a given type at a given index. + /// Only one such reference can be borrowed at any given time. + pub fn get_mut(&mut self, key: u32) -> Result<&mut T, Error> { + let entry = match self.0.get_mut().unwrap().map.get_mut(&key) { + Some(entry) => entry, + None => return Err(Error::badf().context("key not in table")), + }; + let entry = match Arc::get_mut(entry) { + Some(entry) => entry, + None => return Err(Error::badf().context("cannot mutably borrow shared file")), + }; + entry + .downcast_mut::() + .ok_or_else(|| Error::badf().context("element is a different type")) + } + + /// Remove a resource at a given index from the table. Returns the resource + /// if it was present. + pub fn delete(&self, key: u32) -> Option> { + self.0 + .write() + .unwrap() + .map + .remove(&key) + .map(|r| r.downcast::().unwrap()) + } + + /// Remove a resource at a given index from the table. Returns the resource + /// if it was present. + pub fn renumber(&self, from: u32, to: u32) -> Result<(), Error> { + let map = &mut self.0.write().unwrap().map; + let from_entry = map.remove(&from).ok_or(Error::badf())?; + map.insert(to, from_entry); + Ok(()) + } +} diff --git a/crates/wasi-common/src/tokio/dir.rs b/crates/wasi-common/src/tokio/dir.rs new file mode 100644 index 00000000..74b18e33 --- /dev/null +++ b/crates/wasi-common/src/tokio/dir.rs @@ -0,0 +1,220 @@ +use crate::tokio::{block_on_dummy_executor, file::File}; +use crate::{ + Error, ErrorExt, + dir::{ReaddirCursor, ReaddirEntity, WasiDir}, + file::{FdFlags, Filestat, OFlags}, +}; +use std::any::Any; +use std::path::PathBuf; + +pub struct Dir(crate::sync::dir::Dir); + +impl Dir { + pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self { + Dir(crate::sync::dir::Dir::from_cap_std(dir)) + } +} + +#[async_trait::async_trait] +impl WasiDir for Dir { + fn as_any(&self) -> &dyn Any { + self + } + async fn open_file( + &self, + symlink_follow: bool, + path: &str, + oflags: OFlags, + read: bool, + write: bool, + fdflags: FdFlags, + ) -> Result { + let f = block_on_dummy_executor(move || async move { + self.0 + .open_file_(symlink_follow, path, oflags, read, write, fdflags) + })?; + match f { + crate::sync::dir::OpenResult::File(f) => { + Ok(crate::dir::OpenResult::File(Box::new(File::from_inner(f)))) + } + crate::sync::dir::OpenResult::Dir(d) => { + Ok(crate::dir::OpenResult::Dir(Box::new(Dir(d)))) + } + } + } + + async fn create_dir(&self, path: &str) -> Result<(), Error> { + block_on_dummy_executor(|| self.0.create_dir(path)) + } + async fn readdir( + &self, + cursor: ReaddirCursor, + ) -> Result> + Send>, Error> { + struct I(Box> + Send>); + impl Iterator for I { + type Item = Result; + fn next(&mut self) -> Option { + tokio::task::block_in_place(move || self.0.next()) + } + } + + let inner = block_on_dummy_executor(move || self.0.readdir(cursor))?; + Ok(Box::new(I(inner))) + } + + async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.symlink(src_path, dest_path)) + } + async fn remove_dir(&self, path: &str) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.remove_dir(path)) + } + + async fn unlink_file(&self, path: &str) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.unlink_file(path)) + } + async fn read_link(&self, path: &str) -> Result { + block_on_dummy_executor(move || self.0.read_link(path)) + } + async fn get_filestat(&self) -> Result { + block_on_dummy_executor(|| self.0.get_filestat()) + } + async fn get_path_filestat( + &self, + path: &str, + follow_symlinks: bool, + ) -> Result { + block_on_dummy_executor(move || self.0.get_path_filestat(path, follow_symlinks)) + } + async fn rename( + &self, + src_path: &str, + dest_dir: &dyn WasiDir, + dest_path: &str, + ) -> Result<(), Error> { + let dest_dir = dest_dir + .as_any() + .downcast_ref::() + .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; + block_on_dummy_executor( + move || async move { self.0.rename_(src_path, &dest_dir.0, dest_path) }, + ) + } + async fn hard_link( + &self, + src_path: &str, + target_dir: &dyn WasiDir, + target_path: &str, + ) -> Result<(), Error> { + let target_dir = target_dir + .as_any() + .downcast_ref::() + .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; + block_on_dummy_executor(move || async move { + self.0.hard_link_(src_path, &target_dir.0, target_path) + }) + } + async fn set_times( + &self, + path: &str, + atime: Option, + mtime: Option, + follow_symlinks: bool, + ) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks)) + } +} + +#[cfg(test)] +mod test { + use super::Dir; + use crate::file::{FdFlags, OFlags}; + use cap_std::ambient_authority; + + #[tokio::test(flavor = "multi_thread")] + async fn scratch_dir() { + let tempdir = tempfile::Builder::new() + .prefix("cap-std-sync") + .tempdir() + .expect("create temporary dir"); + let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority()) + .expect("open ambient temporary dir"); + let preopen_dir = Dir::from_cap_std(preopen_dir); + crate::WasiDir::open_file( + &preopen_dir, + false, + ".", + OFlags::empty(), + false, + false, + FdFlags::empty(), + ) + .await + .expect("open the same directory via WasiDir abstraction"); + } + + // Readdir does not work on windows, so we won't test it there. + #[cfg(not(windows))] + #[tokio::test(flavor = "multi_thread")] + async fn readdir() { + use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir}; + use crate::file::{FdFlags, FileType, OFlags}; + use std::collections::HashMap; + + async fn readdir_into_map(dir: &dyn WasiDir) -> HashMap { + let mut out = HashMap::new(); + for readdir_result in dir + .readdir(ReaddirCursor::from(0)) + .await + .expect("readdir succeeds") + { + let entity = readdir_result.expect("readdir entry is valid"); + out.insert(entity.name.clone(), entity); + } + out + } + + let tempdir = tempfile::Builder::new() + .prefix("cap-std-sync") + .tempdir() + .expect("create temporary dir"); + let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority()) + .expect("open ambient temporary dir"); + let preopen_dir = Dir::from_cap_std(preopen_dir); + + let entities = readdir_into_map(&preopen_dir).await; + assert_eq!( + entities.len(), + 2, + "should just be . and .. in empty dir: {entities:?}" + ); + assert!(entities.get(".").is_some()); + assert!(entities.get("..").is_some()); + + preopen_dir + .open_file( + false, + "file1", + OFlags::CREATE, + true, + false, + FdFlags::empty(), + ) + .await + .expect("create file1"); + + let entities = readdir_into_map(&preopen_dir).await; + assert_eq!(entities.len(), 3, "should be ., .., file1 {entities:?}"); + assert_eq!( + entities.get(".").expect(". entry").filetype, + FileType::Directory + ); + assert_eq!( + entities.get("..").expect(".. entry").filetype, + FileType::Directory + ); + assert_eq!( + entities.get("file1").expect("file1 entry").filetype, + FileType::RegularFile + ); + } +} diff --git a/crates/wasi-common/src/tokio/file.rs b/crates/wasi-common/src/tokio/file.rs new file mode 100644 index 00000000..fc13ed9b --- /dev/null +++ b/crates/wasi-common/src/tokio/file.rs @@ -0,0 +1,247 @@ +use crate::tokio::block_on_dummy_executor; +use crate::{ + Error, + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, +}; +#[cfg(windows)] +use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; +#[cfg(not(windows))] +use io_lifetimes::AsFd; +use std::any::Any; +use std::borrow::Borrow; +use std::io; + +pub struct File(crate::sync::file::File); + +impl File { + pub(crate) fn from_inner(file: crate::sync::file::File) -> Self { + File(file) + } + pub fn from_cap_std(file: cap_std::fs::File) -> Self { + Self::from_inner(crate::sync::file::File::from_cap_std(file)) + } +} + +pub struct TcpListener(crate::sync::net::TcpListener); + +impl TcpListener { + pub(crate) fn from_inner(listener: crate::sync::net::TcpListener) -> Self { + TcpListener(listener) + } + pub fn from_cap_std(listener: cap_std::net::TcpListener) -> Self { + Self::from_inner(crate::sync::net::TcpListener::from_cap_std(listener)) + } +} + +pub struct TcpStream(crate::sync::net::TcpStream); + +impl TcpStream { + pub(crate) fn from_inner(stream: crate::sync::net::TcpStream) -> Self { + TcpStream(stream) + } + pub fn from_cap_std(stream: cap_std::net::TcpStream) -> Self { + Self::from_inner(crate::sync::net::TcpStream::from_cap_std(stream)) + } +} + +#[cfg(unix)] +pub struct UnixListener(crate::sync::net::UnixListener); + +#[cfg(unix)] +impl UnixListener { + pub(crate) fn from_inner(listener: crate::sync::net::UnixListener) -> Self { + UnixListener(listener) + } + pub fn from_cap_std(listener: cap_std::os::unix::net::UnixListener) -> Self { + Self::from_inner(crate::sync::net::UnixListener::from_cap_std(listener)) + } +} + +#[cfg(unix)] +pub struct UnixStream(crate::sync::net::UnixStream); + +#[cfg(unix)] +impl UnixStream { + fn from_inner(stream: crate::sync::net::UnixStream) -> Self { + UnixStream(stream) + } + pub fn from_cap_std(stream: cap_std::os::unix::net::UnixStream) -> Self { + Self::from_inner(crate::sync::net::UnixStream::from_cap_std(stream)) + } +} + +pub struct Stdin(crate::sync::stdio::Stdin); + +pub fn stdin() -> Stdin { + Stdin(crate::sync::stdio::stdin()) +} + +pub struct Stdout(crate::sync::stdio::Stdout); + +pub fn stdout() -> Stdout { + Stdout(crate::sync::stdio::stdout()) +} + +pub struct Stderr(crate::sync::stdio::Stderr); + +pub fn stderr() -> Stderr { + Stderr(crate::sync::stdio::stderr()) +} + +macro_rules! wasi_file_impl { + ($ty:ty) => { + #[async_trait::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.0.as_fd()) + } + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.0.as_raw_handle_or_socket()) + } + async fn datasync(&self) -> Result<(), Error> { + block_on_dummy_executor(|| self.0.datasync()) + } + async fn sync(&self) -> Result<(), Error> { + block_on_dummy_executor(|| self.0.sync()) + } + async fn get_filetype(&self) -> Result { + block_on_dummy_executor(|| self.0.get_filetype()) + } + async fn get_fdflags(&self) -> Result { + block_on_dummy_executor(|| self.0.get_fdflags()) + } + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + block_on_dummy_executor(|| self.0.set_fdflags(fdflags)) + } + async fn get_filestat(&self) -> Result { + block_on_dummy_executor(|| self.0.get_filestat()) + } + async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.set_filestat_size(size)) + } + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.advise(offset, len, advice)) + } + async fn read_vectored<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + ) -> Result { + block_on_dummy_executor(move || self.0.read_vectored(bufs)) + } + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { + block_on_dummy_executor(move || self.0.read_vectored_at(bufs, offset)) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + block_on_dummy_executor(move || self.0.write_vectored(bufs)) + } + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { + if bufs.iter().map(|i| i.len()).sum::() == 0 { + return Ok(0); + } + block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset)) + } + async fn seek(&self, pos: std::io::SeekFrom) -> Result { + block_on_dummy_executor(move || self.0.seek(pos)) + } + async fn peek(&self, buf: &mut [u8]) -> Result { + block_on_dummy_executor(move || self.0.peek(buf)) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + block_on_dummy_executor(move || self.0.set_times(atime, mtime)) + } + fn num_ready_bytes(&self) -> Result { + self.0.num_ready_bytes() + } + fn isatty(&self) -> bool { + self.0.isatty() + } + + #[cfg(not(windows))] + async fn readable(&self) -> Result<(), Error> { + // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. + // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce + // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this + // async method to ensure this is the only Future which can access the RawFd during the + // lifetime of the AsyncFd. + use std::os::unix::io::AsRawFd; + use tokio::io::{Interest, unix::AsyncFd}; + let rawfd = self.0.borrow().as_fd().as_raw_fd(); + match AsyncFd::with_interest(rawfd, Interest::READABLE) { + Ok(asyncfd) => { + let _ = asyncfd.readable().await?; + Ok(()) + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + // if e is EPERM, this file isn't supported by epoll because it is immediately + // available for reading: + Ok(()) + } + Err(e) => Err(e.into()), + } + } + + #[cfg(not(windows))] + async fn writable(&self) -> Result<(), Error> { + // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. + // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce + // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this + // async method to ensure this is the only Future which can access the RawFd during the + // lifetime of the AsyncFd. + use std::os::unix::io::AsRawFd; + use tokio::io::{Interest, unix::AsyncFd}; + let rawfd = self.0.borrow().as_fd().as_raw_fd(); + match AsyncFd::with_interest(rawfd, Interest::WRITABLE) { + Ok(asyncfd) => { + let _ = asyncfd.writable().await?; + Ok(()) + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + // if e is EPERM, this file isn't supported by epoll because it is immediately + // available for writing: + Ok(()) + } + Err(e) => Err(e.into()), + } + } + + async fn sock_accept(&self, fdflags: FdFlags) -> Result, Error> { + block_on_dummy_executor(|| self.0.sock_accept(fdflags)) + } + } + #[cfg(windows)] + impl AsRawHandleOrSocket for $ty { + #[inline] + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.borrow().as_raw_handle_or_socket() + } + } + }; +} + +wasi_file_impl!(File); +wasi_file_impl!(TcpListener); +wasi_file_impl!(TcpStream); +#[cfg(unix)] +wasi_file_impl!(UnixListener); +#[cfg(unix)] +wasi_file_impl!(UnixStream); +wasi_file_impl!(Stdin); +wasi_file_impl!(Stdout); +wasi_file_impl!(Stderr); diff --git a/crates/wasi-common/src/tokio/mod.rs b/crates/wasi-common/src/tokio/mod.rs new file mode 100644 index 00000000..ce786587 --- /dev/null +++ b/crates/wasi-common/src/tokio/mod.rs @@ -0,0 +1,135 @@ +mod dir; +mod file; +pub mod net; +pub mod sched; +pub mod stdio; + +use self::sched::sched_ctx; +use crate::sync::net::Socket; +pub use crate::sync::{clocks_ctx, random_ctx}; +use crate::{Error, Table, WasiCtx, WasiFile, file::FileAccessMode}; +pub use dir::Dir; +pub use file::File; +pub use net::*; +use std::future::Future; +use std::mem; +use std::path::Path; + +pub struct WasiCtxBuilder { + ctx: WasiCtx, + built: bool, +} + +impl WasiCtxBuilder { + pub fn new() -> Self { + WasiCtxBuilder { + ctx: WasiCtx::new(random_ctx(), clocks_ctx(), sched_ctx(), Table::new()), + built: false, + } + } + pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> { + self.ctx.push_env(var, value)?; + Ok(self) + } + pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> { + for (k, v) in env { + self.ctx.push_env(k, v)?; + } + Ok(self) + } + pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> { + for (key, value) in std::env::vars() { + self.ctx.push_env(&key, &value)?; + } + Ok(self) + } + pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> { + self.ctx.push_arg(arg)?; + Ok(self) + } + pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> { + for a in arg { + self.ctx.push_arg(&a)?; + } + Ok(self) + } + pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> { + for arg in std::env::args() { + self.ctx.push_arg(&arg)?; + } + Ok(self) + } + pub fn stdin(&mut self, f: Box) -> &mut Self { + self.ctx.set_stdin(f); + self + } + pub fn stdout(&mut self, f: Box) -> &mut Self { + self.ctx.set_stdout(f); + self + } + pub fn stderr(&mut self, f: Box) -> &mut Self { + self.ctx.set_stderr(f); + self + } + pub fn inherit_stdin(&mut self) -> &mut Self { + self.stdin(Box::new(crate::tokio::stdio::stdin())) + } + pub fn inherit_stdout(&mut self) -> &mut Self { + self.stdout(Box::new(crate::tokio::stdio::stdout())) + } + pub fn inherit_stderr(&mut self) -> &mut Self { + self.stderr(Box::new(crate::tokio::stdio::stderr())) + } + pub fn inherit_stdio(&mut self) -> &mut Self { + self.inherit_stdin().inherit_stdout().inherit_stderr() + } + pub fn preopened_dir( + &mut self, + dir: cap_std::fs::Dir, + guest_path: impl AsRef, + ) -> Result<&mut Self, Error> { + let dir = Box::new(crate::tokio::dir::Dir::from_cap_std(dir)); + self.ctx.push_preopened_dir(dir, guest_path)?; + Ok(self) + } + pub fn preopened_socket( + &mut self, + fd: u32, + socket: impl Into, + ) -> Result<&mut Self, Error> { + let socket: Socket = socket.into(); + let file: Box = socket.into(); + self.ctx + .insert_file(fd, file, FileAccessMode::READ | FileAccessMode::WRITE); + Ok(self) + } + + pub fn build(&mut self) -> WasiCtx { + assert!(!self.built); + let WasiCtxBuilder { ctx, .. } = mem::replace(self, Self::new()); + self.built = true; + ctx + } +} + +// Much of this mod is implemented in terms of `async` methods from the +// wasmtime_wasi::p2::sync module. These methods may be async in signature, however, +// they are synchronous in implementation (always Poll::Ready on first poll) +// and perform blocking syscalls. +// +// This function takes this blocking code and executes it using a dummy executor +// to assert its immediate readiness. We tell tokio this is a blocking operation +// with the block_in_place function. +pub(crate) fn block_on_dummy_executor<'a, F, Fut, T>(f: F) -> Result +where + F: FnOnce() -> Fut + Send + 'a, + Fut: Future>, + T: Send + 'static, +{ + tokio::task::block_in_place(move || { + wiggle::run_in_dummy_executor(f()).expect("wrapped operation should be synchronous") + }) +} + +#[cfg(feature = "wasmtime")] +super::define_wasi!(async T: Send); diff --git a/crates/wasi-common/src/tokio/net.rs b/crates/wasi-common/src/tokio/net.rs new file mode 100644 index 00000000..93a807ec --- /dev/null +++ b/crates/wasi-common/src/tokio/net.rs @@ -0,0 +1,6 @@ +pub use super::file::TcpListener; +pub use super::file::TcpStream; +#[cfg(unix)] +pub use super::file::UnixListener; +#[cfg(unix)] +pub use super::file::UnixStream; diff --git a/crates/wasi-common/src/tokio/sched.rs b/crates/wasi-common/src/tokio/sched.rs new file mode 100644 index 00000000..41a77b4d --- /dev/null +++ b/crates/wasi-common/src/tokio/sched.rs @@ -0,0 +1,35 @@ +#[cfg(unix)] +mod unix; +#[cfg(unix)] +pub use unix::poll_oneoff; + +#[cfg(windows)] +mod windows; +#[cfg(windows)] +pub use windows::poll_oneoff; + +use crate::{ + Error, + sched::{Duration, Poll, WasiSched}, +}; + +pub fn sched_ctx() -> Box { + struct AsyncSched; + + #[async_trait::async_trait] + impl WasiSched for AsyncSched { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { + poll_oneoff(poll).await + } + async fn sched_yield(&self) -> Result<(), Error> { + tokio::task::yield_now().await; + Ok(()) + } + async fn sleep(&self, duration: Duration) -> Result<(), Error> { + tokio::time::sleep(duration).await; + Ok(()) + } + } + + Box::new(AsyncSched) +} diff --git a/crates/wasi-common/src/tokio/sched/unix.rs b/crates/wasi-common/src/tokio/sched/unix.rs new file mode 100644 index 00000000..dc063e4d --- /dev/null +++ b/crates/wasi-common/src/tokio/sched/unix.rs @@ -0,0 +1,103 @@ +use crate::{ + Error, + sched::{ + Poll, + subscription::{RwEventFlags, Subscription}, + }, +}; +use std::future::{self, Future}; +use std::pin::{Pin, pin}; +use std::task::{Context, Poll as FPoll}; + +struct FirstReady<'a, T>(Vec + Send + 'a>>>); + +impl<'a, T> FirstReady<'a, T> { + fn new() -> Self { + FirstReady(Vec::new()) + } + fn push(&mut self, f: impl Future + Send + 'a) { + self.0.push(Box::pin(f)); + } +} + +impl<'a, T> Future for FirstReady<'a, T> { + type Output = T; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> FPoll { + let mut result = FPoll::Pending; + for f in self.as_mut().0.iter_mut() { + match f.as_mut().poll(cx) { + FPoll::Ready(r) => match result { + // First ready gets to set the result. But, continue the loop so all futures + // which are ready simultaneously (often on first poll) get to report their + // readiness. + FPoll::Pending => { + result = FPoll::Ready(r); + } + _ => {} + }, + _ => continue, + } + } + return result; + } +} + +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); + } + + let duration = poll + .earliest_clock_deadline() + .map(|sub| sub.duration_until()); + + let mut futures = FirstReady::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(f) => { + futures.push(async move { + f.file + .readable() + .await + .map_err(|e| e.context("readable future"))?; + f.complete( + f.file + .num_ready_bytes() + .map_err(|e| e.context("read num_ready_bytes"))?, + RwEventFlags::empty(), + ); + Ok::<(), Error>(()) + }); + } + + Subscription::Write(f) => { + futures.push(async move { + f.file + .writable() + .await + .map_err(|e| e.context("writable future"))?; + f.complete(0, RwEventFlags::empty()); + Ok(()) + }); + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + match duration { + Some(Some(remaining)) => match tokio::time::timeout(remaining, futures).await { + Ok(r) => r?, + Err(_deadline_elapsed) => {} + }, + Some(None) => { + let mut futures = pin!(futures); + future::poll_fn(|cx| match futures.as_mut().poll(cx) { + FPoll::Ready(e) => FPoll::Ready(e), + FPoll::Pending => FPoll::Ready(Ok(())), + }) + .await? + } + None => futures.await?, + } + + Ok(()) +} diff --git a/crates/wasi-common/src/tokio/sched/windows.rs b/crates/wasi-common/src/tokio/sched/windows.rs new file mode 100644 index 00000000..f0d90fd0 --- /dev/null +++ b/crates/wasi-common/src/tokio/sched/windows.rs @@ -0,0 +1,15 @@ +use crate::sync::sched::windows::poll_oneoff_; +use crate::tokio::block_on_dummy_executor; +use crate::{Error, file::WasiFile, sched::Poll}; + +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + // Tokio doesn't provide us the AsyncFd primitive on Windows, so instead + // we use the blocking poll_oneoff implementation from the wasi_common::sync impl. + // We provide a function specific to this impl's WasiFile types for downcasting + // to a RawHandle. + block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin)) +} + +pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { + f.as_any().is::() +} diff --git a/crates/wasi-common/src/tokio/stdio.rs b/crates/wasi-common/src/tokio/stdio.rs new file mode 100644 index 00000000..c796fc72 --- /dev/null +++ b/crates/wasi-common/src/tokio/stdio.rs @@ -0,0 +1 @@ +pub use super::file::{Stderr, Stdin, Stdout, stderr, stdin, stdout}; diff --git a/crates/wasi-common/witx/preview0/typenames.witx b/crates/wasi-common/witx/preview0/typenames.witx new file mode 100644 index 00000000..c3213743 --- /dev/null +++ b/crates/wasi-common/witx/preview0/typenames.witx @@ -0,0 +1,746 @@ +;; Type names used by low-level WASI interfaces. +;; +;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). +;; +;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/main/docs/witx.md) +;; for an explanation of what that means. + +(typename $size u32) + +;;; Non-negative file size or length of a region within a file. +(typename $filesize u64) + +;;; Timestamp in nanoseconds. +(typename $timestamp u64) + +;;; Identifiers for clocks. +(typename $clockid + (enum (@witx tag u32) + ;;; The clock measuring real time. Time value zero corresponds with + ;;; 1970-01-01T00:00:00Z. + $realtime + ;;; The store-wide monotonic clock, which is defined as a clock measuring + ;;; real time, whose value cannot be adjusted and which cannot have negative + ;;; clock jumps. The epoch of this clock is undefined. The absolute time + ;;; value of this clock therefore has no meaning. + $monotonic + ;;; The CPU-time clock associated with the current process. + $process_cputime_id + ;;; The CPU-time clock associated with the current thread. + $thread_cputime_id + ) +) + +;;; Error codes returned by functions. +;;; Not all of these error codes are returned by the functions provided by this +;;; API; some are used in higher-level library layers, and others are provided +;;; merely for alignment with POSIX. +(typename $errno + (enum (@witx tag u16) + ;;; No error occurred. System call completed successfully. + $success + ;;; Argument list too long. + $2big + ;;; Permission denied. + $acces + ;;; Address in use. + $addrinuse + ;;; Address not available. + $addrnotavail + ;;; Address family not supported. + $afnosupport + ;;; Resource unavailable, or operation would block. + $again + ;;; Connection already in progress. + $already + ;;; Bad file descriptor. + $badf + ;;; Bad message. + $badmsg + ;;; Device or resource busy. + $busy + ;;; Operation canceled. + $canceled + ;;; No child processes. + $child + ;;; Connection aborted. + $connaborted + ;;; Connection refused. + $connrefused + ;;; Connection reset. + $connreset + ;;; Resource deadlock would occur. + $deadlk + ;;; Destination address required. + $destaddrreq + ;;; Mathematics argument out of domain of function. + $dom + ;;; Reserved. + $dquot + ;;; File exists. + $exist + ;;; Bad address. + $fault + ;;; File too large. + $fbig + ;;; Host is unreachable. + $hostunreach + ;;; Identifier removed. + $idrm + ;;; Illegal byte sequence. + $ilseq + ;;; Operation in progress. + $inprogress + ;;; Interrupted function. + $intr + ;;; Invalid argument. + $inval + ;;; I/O error. + $io + ;;; Socket is connected. + $isconn + ;;; Is a directory. + $isdir + ;;; Too many levels of symbolic links. + $loop + ;;; File descriptor value too large. + $mfile + ;;; Too many links. + $mlink + ;;; Message too large. + $msgsize + ;;; Reserved. + $multihop + ;;; Filename too long. + $nametoolong + ;;; Network is down. + $netdown + ;;; Connection aborted by network. + $netreset + ;;; Network unreachable. + $netunreach + ;;; Too many files open in system. + $nfile + ;;; No buffer space available. + $nobufs + ;;; No such device. + $nodev + ;;; No such file or directory. + $noent + ;;; Executable file format error. + $noexec + ;;; No locks available. + $nolck + ;;; Reserved. + $nolink + ;;; Not enough space. + $nomem + ;;; No message of the desired type. + $nomsg + ;;; Protocol not available. + $noprotoopt + ;;; No space left on device. + $nospc + ;;; Function not supported. + $nosys + ;;; The socket is not connected. + $notconn + ;;; Not a directory or a symbolic link to a directory. + $notdir + ;;; Directory not empty. + $notempty + ;;; State not recoverable. + $notrecoverable + ;;; Not a socket. + $notsock + ;;; Not supported, or operation not supported on socket. + $notsup + ;;; Inappropriate I/O control operation. + $notty + ;;; No such device or address. + $nxio + ;;; Value too large to be stored in data type. + $overflow + ;;; Previous owner died. + $ownerdead + ;;; Operation not permitted. + $perm + ;;; Broken pipe. + $pipe + ;;; Protocol error. + $proto + ;;; Protocol not supported. + $protonosupport + ;;; Protocol wrong type for socket. + $prototype + ;;; Result too large. + $range + ;;; Read-only file system. + $rofs + ;;; Invalid seek. + $spipe + ;;; No such process. + $srch + ;;; Reserved. + $stale + ;;; Connection timed out. + $timedout + ;;; Text file busy. + $txtbsy + ;;; Cross-device link. + $xdev + ;;; Extension: Capabilities insufficient. + $notcapable + ) +) + +;;; File descriptor rights, determining which actions may be performed. +(typename $rights + (flags (@witx repr u64) + ;;; The right to invoke `fd_datasync`. + ;; + ;;; If `rights::path_open` is set, includes the right to invoke + ;;; `path_open` with `fdflags::dsync`. + $fd_datasync + ;;; The right to invoke `fd_read` and `sock_recv`. + ;; + ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pread`. + $fd_read + ;;; The right to invoke `fd_seek`. This flag implies `rights::fd_tell`. + $fd_seek + ;;; The right to invoke `fd_fdstat_set_flags`. + $fd_fdstat_set_flags + ;;; The right to invoke `fd_sync`. + ;; + ;;; If `rights::path_open` is set, includes the right to invoke + ;;; `path_open` with `fdflags::rsync` and `fdflags::dsync`. + $fd_sync + ;;; The right to invoke `fd_seek` in such a way that the file offset + ;;; remains unaltered (i.e., `whence::cur` with offset zero), or to + ;;; invoke `fd_tell`. + $fd_tell + ;;; The right to invoke `fd_write` and `sock_send`. + ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`. + $fd_write + ;;; The right to invoke `fd_advise`. + $fd_advise + ;;; The right to invoke `fd_allocate`. + $fd_allocate + ;;; The right to invoke `path_create_directory`. + $path_create_directory + ;;; If `rights::path_open` is set, the right to invoke `path_open` with `oflags::creat`. + $path_create_file + ;;; The right to invoke `path_link` with the file descriptor as the + ;;; source directory. + $path_link_source + ;;; The right to invoke `path_link` with the file descriptor as the + ;;; target directory. + $path_link_target + ;;; The right to invoke `path_open`. + $path_open + ;;; The right to invoke `fd_readdir`. + $fd_readdir + ;;; The right to invoke `path_readlink`. + $path_readlink + ;;; The right to invoke `path_rename` with the file descriptor as the source directory. + $path_rename_source + ;;; The right to invoke `path_rename` with the file descriptor as the target directory. + $path_rename_target + ;;; The right to invoke `path_filestat_get`. + $path_filestat_get + ;;; The right to change a file's size (there is no `path_filestat_set_size`). + ;;; If `rights::path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`. + $path_filestat_set_size + ;;; The right to invoke `path_filestat_set_times`. + $path_filestat_set_times + ;;; The right to invoke `fd_filestat_get`. + $fd_filestat_get + ;;; The right to invoke `fd_filestat_set_size`. + $fd_filestat_set_size + ;;; The right to invoke `fd_filestat_set_times`. + $fd_filestat_set_times + ;;; The right to invoke `path_symlink`. + $path_symlink + ;;; The right to invoke `path_remove_directory`. + $path_remove_directory + ;;; The right to invoke `path_unlink_file`. + $path_unlink_file + ;;; If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`. + ;;; If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`. + $poll_fd_readwrite + ;;; The right to invoke `sock_shutdown`. + $sock_shutdown + ) +) + +;;; A file descriptor handle. +(typename $fd (handle)) + +;;; A region of memory for scatter/gather reads. +(typename $iovec + (record + ;;; The address of the buffer to be filled. + (field $buf (@witx pointer u8)) + ;;; The length of the buffer to be filled. + (field $buf_len $size) + ) +) + +;;; A region of memory for scatter/gather writes. +(typename $ciovec + (record + ;;; The address of the buffer to be written. + (field $buf (@witx const_pointer u8)) + ;;; The length of the buffer to be written. + (field $buf_len $size) + ) +) + +(typename $iovec_array (list $iovec)) +(typename $ciovec_array (list $ciovec)) + +;;; Relative offset within a file. +(typename $filedelta s64) + +;;; The position relative to which to set the offset of the file descriptor. +(typename $whence + (enum (@witx tag u8) + ;;; Seek relative to current position. + $cur + ;;; Seek relative to end-of-file. + $end + ;;; Seek relative to start-of-file. + $set + ) +) + +;;; A reference to the offset of a directory entry. +(typename $dircookie u64) + +;;; The type for the `dirent::d_namlen` field of `dirent` struct. +(typename $dirnamlen u32) + +;;; File serial number that is unique within its file system. +(typename $inode u64) + +;;; The type of a file descriptor or file. +(typename $filetype + (enum (@witx tag u8) + ;;; The type of the file descriptor or file is unknown or is different from any of the other types specified. + $unknown + ;;; The file descriptor or file refers to a block device inode. + $block_device + ;;; The file descriptor or file refers to a character device inode. + $character_device + ;;; The file descriptor or file refers to a directory inode. + $directory + ;;; The file descriptor or file refers to a regular file inode. + $regular_file + ;;; The file descriptor or file refers to a datagram socket. + $socket_dgram + ;;; The file descriptor or file refers to a byte-stream socket. + $socket_stream + ;;; The file refers to a symbolic link inode. + $symbolic_link + ) +) + +;;; A directory entry. +(typename $dirent + (record + ;;; The offset of the next directory entry stored in this directory. + (field $d_next $dircookie) + ;;; The serial number of the file referred to by this directory entry. + (field $d_ino $inode) + ;;; The length of the name of the directory entry. + (field $d_namlen $dirnamlen) + ;;; The type of the file referred to by this directory entry. + (field $d_type $filetype) + ) +) + +;;; File or memory access pattern advisory information. +(typename $advice + (enum (@witx tag u8) + ;;; The application has no advice to give on its behavior with respect to the specified data. + $normal + ;;; The application expects to access the specified data sequentially from lower offsets to higher offsets. + $sequential + ;;; The application expects to access the specified data in a random order. + $random + ;;; The application expects to access the specified data in the near future. + $willneed + ;;; The application expects that it will not access the specified data in the near future. + $dontneed + ;;; The application expects to access the specified data once and then not reuse it thereafter. + $noreuse + ) +) + +;;; File descriptor flags. +(typename $fdflags + (flags (@witx repr u16) + ;;; Append mode: Data written to the file is always appended to the file's end. + $append + ;;; Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. + $dsync + ;;; Non-blocking mode. + $nonblock + ;;; Synchronized read I/O operations. + $rsync + ;;; Write according to synchronized I/O file integrity completion. In + ;;; addition to synchronizing the data stored in the file, the implementation + ;;; may also synchronously update the file's metadata. + $sync + ) +) + +;;; File descriptor attributes. +(typename $fdstat + (record + ;;; File type. + (field $fs_filetype $filetype) + ;;; File descriptor flags. + (field $fs_flags $fdflags) + ;;; Rights that apply to this file descriptor. + (field $fs_rights_base $rights) + ;;; Maximum set of rights that may be installed on new file descriptors that + ;;; are created through this file descriptor, e.g., through `path_open`. + (field $fs_rights_inheriting $rights) + ) +) + +;;; Identifier for a device containing a file system. Can be used in combination +;;; with `inode` to uniquely identify a file or directory in the filesystem. +(typename $device u64) + +;;; Which file time attributes to adjust. +(typename $fstflags + (flags (@witx repr u16) + ;;; Adjust the last data access timestamp to the value stored in `filestat::atim`. + $atim + ;;; Adjust the last data access timestamp to the time of clock `clockid::realtime`. + $atim_now + ;;; Adjust the last data modification timestamp to the value stored in `filestat::mtim`. + $mtim + ;;; Adjust the last data modification timestamp to the time of clock `clockid::realtime`. + $mtim_now + ) +) + +;;; Flags determining the method of how paths are resolved. +(typename $lookupflags + (flags (@witx repr u32) + ;;; As long as the resolved path corresponds to a symbolic link, it is expanded. + $symlink_follow + ) +) + +;;; Open flags used by `path_open`. +(typename $oflags + (flags (@witx repr u16) + ;;; Create file if it does not exist. + $creat + ;;; Fail if not a directory. + $directory + ;;; Fail if file already exists. + $excl + ;;; Truncate file to size 0. + $trunc + ) +) + +;;; Number of hard links to an inode. +(typename $linkcount u32) + +;;; File attributes. +(typename $filestat + (record + ;;; Device ID of device containing the file. + (field $dev $device) + ;;; File serial number. + (field $ino $inode) + ;;; File type. + (field $filetype $filetype) + ;;; Number of hard links to the file. + (field $nlink $linkcount) + ;;; For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. + (field $size $filesize) + ;;; Last data access timestamp. + (field $atim $timestamp) + ;;; Last data modification timestamp. + (field $mtim $timestamp) + ;;; Last file status change timestamp. + (field $ctim $timestamp) + ) +) + +;;; User-provided value that may be attached to objects that is retained when +;;; extracted from the implementation. +(typename $userdata u64) + +;;; Type of a subscription to an event or its occurrence. +(typename $eventtype + (enum (@witx tag u8) + ;;; The time value of clock `subscription_clock::id` has + ;;; reached timestamp `subscription_clock::timeout`. + $clock + ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has data + ;;; available for reading. This event always triggers for regular files. + $fd_read + ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has capacity + ;;; available for writing. This event always triggers for regular files. + $fd_write + ) +) + +;;; The state of the file descriptor subscribed to with +;;; `eventtype::fd_read` or `eventtype::fd_write`. +(typename $eventrwflags + (flags (@witx repr u16) + ;;; The peer of this socket has closed or disconnected. + $fd_readwrite_hangup + ) +) + +;;; The contents of an `event` for the `eventtype::fd_read` and +;;; `eventtype::fd_write` variants +(typename $event_fd_readwrite + (record + ;;; The number of bytes available for reading or writing. + (field $nbytes $filesize) + ;;; The state of the file descriptor. + (field $flags $eventrwflags) + ) +) + +;;; An event that occurred. +(typename $event + (record + ;;; User-provided value that got attached to `subscription::userdata`. + (field $userdata $userdata) + ;;; If non-zero, an error that occurred while processing the subscription request. + (field $error $errno) + ;;; The type of event that occurred + (field $type $eventtype) + ;;; The contents of the event, if it is an `eventtype::fd_read` or + ;;; `eventtype::fd_write`. `eventtype::clock` events ignore this field. + (field $fd_readwrite $event_fd_readwrite) + ) +) + +;;; Flags determining how to interpret the timestamp provided in +;;; `subscription_clock::timeout`. +(typename $subclockflags + (flags (@witx repr u16) + ;;; If set, treat the timestamp provided in + ;;; `subscription_clock::timeout` as an absolute timestamp of clock + ;;; `subscription_clock::id`. If clear, treat the timestamp + ;;; provided in `subscription_clock::timeout` relative to the + ;;; current time value of clock `subscription_clock::id`. + $subscription_clock_abstime + ) +) + +;;; The contents of a `subscription` when type is `eventtype::clock`. +(typename $subscription_clock + (record + ;;; The user-defined unique identifier of the clock. + (field $identifier $userdata) + ;;; The clock against which to compare the timestamp. + (field $id $clockid) + ;;; The absolute or relative timestamp. + (field $timeout $timestamp) + ;;; The amount of time that the implementation may wait additionally + ;;; to coalesce with other events. + (field $precision $timestamp) + ;;; Flags specifying whether the timeout is absolute or relative + (field $flags $subclockflags) + ) +) + +;;; The contents of a `subscription` when the variant is +;;; `eventtype::fd_read` or `eventtype::fd_write`. +(typename $subscription_fd_readwrite + (record + ;;; The file descriptor on which to wait for it to become ready for reading or writing. + (field $file_descriptor $fd) + ) +) + +;;; The contents of a `subscription`. +(typename $subscription_u + (union (@witx tag $eventtype) + $subscription_clock + $subscription_fd_readwrite + $subscription_fd_readwrite + ) +) + +;;; Subscription to an event. +(typename $subscription + (record + ;;; User-provided value that is attached to the subscription in the + ;;; implementation and returned through `event::userdata`. + (field $userdata $userdata) + ;;; The type of the event to which to subscribe. + (field $u $subscription_u) + ) +) + +;;; Exit code generated by a process when exiting. +(typename $exitcode u32) + +;;; Signal condition. +(typename $signal + (enum (@witx tag u8) + ;;; No signal. Note that POSIX has special semantics for `kill(pid, 0)`, + ;;; so this value is reserved. + $none + ;;; Hangup. + ;;; Action: Terminates the process. + $hup + ;;; Terminate interrupt signal. + ;;; Action: Terminates the process. + $int + ;;; Terminal quit signal. + ;;; Action: Terminates the process. + $quit + ;;; Illegal instruction. + ;;; Action: Terminates the process. + $ill + ;;; Trace/breakpoint trap. + ;;; Action: Terminates the process. + $trap + ;;; Process abort signal. + ;;; Action: Terminates the process. + $abrt + ;;; Access to an undefined portion of a memory object. + ;;; Action: Terminates the process. + $bus + ;;; Erroneous arithmetic operation. + ;;; Action: Terminates the process. + $fpe + ;;; Kill. + ;;; Action: Terminates the process. + $kill + ;;; User-defined signal 1. + ;;; Action: Terminates the process. + $usr1 + ;;; Invalid memory reference. + ;;; Action: Terminates the process. + $segv + ;;; User-defined signal 2. + ;;; Action: Terminates the process. + $usr2 + ;;; Write on a pipe with no one to read it. + ;;; Action: Ignored. + $pipe + ;;; Alarm clock. + ;;; Action: Terminates the process. + $alrm + ;;; Termination signal. + ;;; Action: Terminates the process. + $term + ;;; Child process terminated, stopped, or continued. + ;;; Action: Ignored. + $chld + ;;; Continue executing, if stopped. + ;;; Action: Continues executing, if stopped. + $cont + ;;; Stop executing. + ;;; Action: Stops executing. + $stop + ;;; Terminal stop signal. + ;;; Action: Stops executing. + $tstp + ;;; Background process attempting read. + ;;; Action: Stops executing. + $ttin + ;;; Background process attempting write. + ;;; Action: Stops executing. + $ttou + ;;; High bandwidth data is available at a socket. + ;;; Action: Ignored. + $urg + ;;; CPU time limit exceeded. + ;;; Action: Terminates the process. + $xcpu + ;;; File size limit exceeded. + ;;; Action: Terminates the process. + $xfsz + ;;; Virtual timer expired. + ;;; Action: Terminates the process. + $vtalrm + ;;; Profiling timer expired. + ;;; Action: Terminates the process. + $prof + ;;; Window changed. + ;;; Action: Ignored. + $winch + ;;; I/O possible. + ;;; Action: Terminates the process. + $poll + ;;; Power failure. + ;;; Action: Terminates the process. + $pwr + ;;; Bad system call. + ;;; Action: Terminates the process. + $sys + ) +) + +;;; Flags provided to `sock_recv`. +(typename $riflags + (flags (@witx repr u16) + ;;; Returns the message without removing it from the socket's receive queue. + $recv_peek + ;;; On byte-stream sockets, block until the full amount of data can be returned. + $recv_waitall + ) +) + +;;; Flags returned by `sock_recv`. +(typename $roflags + (flags (@witx repr u16) + ;;; Returned by `sock_recv`: Message data has been truncated. + $recv_data_truncated + ) +) + +;;; Flags provided to `sock_send`. As there are currently no flags +;;; defined, it must be set to zero. +(typename $siflags u16) + +;;; Which channels on a socket to shut down. +(typename $sdflags + (flags (@witx repr u8) + ;;; Disables further receive operations. + $rd + ;;; Disables further send operations. + $wr + ) +) + +;;; Identifiers for preopened capabilities. +(typename $preopentype + (enum (@witx tag u8) + ;;; A pre-opened directory. + $dir + ) +) + +;;; The contents of a $prestat when type is `preopentype::dir`. +(typename $prestat_dir + (record + ;;; The length of the directory name for use with `fd_prestat_dir_name`. + (field $pr_name_len $size) + ) +) + +;;; Information about a pre-opened capability. +(typename $prestat + (union (@witx tag $preopentype) + $prestat_dir + ) +) diff --git a/crates/wasi-common/witx/preview0/wasi_unstable.witx b/crates/wasi-common/witx/preview0/wasi_unstable.witx new file mode 100644 index 00000000..ee01abcf --- /dev/null +++ b/crates/wasi-common/witx/preview0/wasi_unstable.witx @@ -0,0 +1,513 @@ +;; WASI Preview. This is an evolution of the API that WASI initially +;; launched with. +;; +;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). +;; +;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/blob/main/legacy/tools/witx-docs.md) +;; for an explanation of what that means. + +(use "typenames.witx") + +;;; This API predated the convention of naming modules with a `wasi_unstable_` +;;; prefix and a version number. It is preserved here for compatibility, but +;;; we shouldn't follow this pattern in new APIs. +(module $wasi_unstable + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + + ;;; Read command-line argument data. + ;;; The size of the array should match that returned by `args_sizes_get`. + ;;; Each argument is expected to be `\0` terminated. + (@interface func (export "args_get") + (param $argv (@witx pointer (@witx pointer u8))) + (param $argv_buf (@witx pointer u8)) + (result $error (expected (error $errno))) + ) + ;;; Return command-line argument data sizes. + (@interface func (export "args_sizes_get") + ;;; Returns the number of arguments and the size of the argument string + ;;; data, or an error. + (result $error (expected (tuple $size $size) (error $errno))) + ) + + ;;; Read environment variable data. + ;;; The sizes of the buffers should match that returned by `environ_sizes_get`. + ;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s. + (@interface func (export "environ_get") + (param $environ (@witx pointer (@witx pointer u8))) + (param $environ_buf (@witx pointer u8)) + (result $error (expected (error $errno))) + ) + ;;; Return environment variable data sizes. + (@interface func (export "environ_sizes_get") + ;;; Returns the number of environment variable arguments and the size of the + ;;; environment variable data. + (result $error (expected (tuple $size $size) (error $errno))) + ) + + ;;; Return the resolution of a clock. + ;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, return + ;;; `errno::inval`. + ;;; Note: This is similar to `clock_getres` in POSIX. + (@interface func (export "clock_res_get") + ;;; The clock for which to return the resolution. + (param $id $clockid) + ;;; The resolution of the clock, or an error if one happened. + (result $error (expected $timestamp (error $errno))) + ) + ;;; Return the time value of a clock. + ;;; Note: This is similar to `clock_gettime` in POSIX. + (@interface func (export "clock_time_get") + ;;; The clock for which to return the time. + (param $id $clockid) + ;;; The maximum lag (exclusive) that the returned time value may have, compared to its actual value. + (param $precision $timestamp) + ;;; The time value of the clock. + (result $error (expected $timestamp (error $errno))) + ) + + ;;; Provide file advisory information on a file descriptor. + ;;; Note: This is similar to `posix_fadvise` in POSIX. + (@interface func (export "fd_advise") + (param $fd $fd) + ;;; The offset within the file to which the advisory applies. + (param $offset $filesize) + ;;; The length of the region to which the advisory applies. + (param $len $filesize) + ;;; The advice. + (param $advice $advice) + (result $error (expected (error $errno))) + ) + + ;;; Force the allocation of space in a file. + ;;; Note: This is similar to `posix_fallocate` in POSIX. + (@interface func (export "fd_allocate") + (param $fd $fd) + ;;; The offset at which to start the allocation. + (param $offset $filesize) + ;;; The length of the area that is allocated. + (param $len $filesize) + (result $error (expected (error $errno))) + ) + + ;;; Close a file descriptor. + ;;; Note: This is similar to `close` in POSIX. + (@interface func (export "fd_close") + (param $fd $fd) + (result $error (expected (error $errno))) + ) + + ;;; Synchronize the data of a file to disk. + ;;; Note: This is similar to `fdatasync` in POSIX. + (@interface func (export "fd_datasync") + (param $fd $fd) + (result $error (expected (error $errno))) + ) + + ;;; Get the attributes of a file descriptor. + ;;; Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. + (@interface func (export "fd_fdstat_get") + (param $fd $fd) + ;;; The buffer where the file descriptor's attributes are stored. + (result $error (expected $fdstat (error $errno))) + ) + + ;;; Adjust the flags associated with a file descriptor. + ;;; Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. + (@interface func (export "fd_fdstat_set_flags") + (param $fd $fd) + ;;; The desired values of the file descriptor flags. + (param $flags $fdflags) + (result $error (expected (error $errno))) + ) + + ;;; Adjust the rights associated with a file descriptor. + ;;; This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights + (@interface func (export "fd_fdstat_set_rights") + (param $fd $fd) + ;;; The desired rights of the file descriptor. + (param $fs_rights_base $rights) + (param $fs_rights_inheriting $rights) + (result $error (expected (error $errno))) + ) + + ;;; Return the attributes of an open file. + (@interface func (export "fd_filestat_get") + (param $fd $fd) + ;;; The buffer where the file's attributes are stored. + (result $error (expected $filestat (error $errno))) + ) + + ;;; Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. + ;;; Note: This is similar to `ftruncate` in POSIX. + (@interface func (export "fd_filestat_set_size") + (param $fd $fd) + ;;; The desired file size. + (param $size $filesize) + (result $error (expected (error $errno))) + ) + + ;;; Adjust the timestamps of an open file or directory. + ;;; Note: This is similar to `futimens` in POSIX. + (@interface func (export "fd_filestat_set_times") + (param $fd $fd) + ;;; The desired values of the data access timestamp. + (param $atim $timestamp) + ;;; The desired values of the data modification timestamp. + (param $mtim $timestamp) + ;;; A bitmask indicating which timestamps to adjust. + (param $fst_flags $fstflags) + (result $error (expected (error $errno))) + ) + + ;;; Read from a file descriptor, without using and updating the file descriptor's offset. + ;;; Note: This is similar to `preadv` in POSIX. + (@interface func (export "fd_pread") + (param $fd $fd) + ;;; List of scatter/gather vectors in which to store data. + (param $iovs $iovec_array) + ;;; The offset within the file at which to read. + (param $offset $filesize) + ;;; The number of bytes read. + (result $error (expected $size (error $errno))) + ) + + ;;; Return a description of the given preopened file descriptor. + (@interface func (export "fd_prestat_get") + (param $fd $fd) + ;;; The buffer where the description is stored. + (result $error (expected $prestat (error $errno))) + ) + + ;;; Return a description of the given preopened file descriptor. + (@interface func (export "fd_prestat_dir_name") + (param $fd $fd) + ;;; A buffer into which to write the preopened directory name. + (param $path (@witx pointer u8)) + (param $path_len $size) + (result $error (expected (error $errno))) + ) + + ;;; Write to a file descriptor, without using and updating the file descriptor's offset. + ;;; Note: This is similar to `pwritev` in POSIX. + (@interface func (export "fd_pwrite") + (param $fd $fd) + ;;; List of scatter/gather vectors from which to retrieve data. + (param $iovs $ciovec_array) + ;;; The offset within the file at which to write. + (param $offset $filesize) + ;;; The number of bytes written. + (result $error (expected $size (error $errno))) + ) + + ;;; Read from a file descriptor. + ;;; Note: This is similar to `readv` in POSIX. + (@interface func (export "fd_read") + (param $fd $fd) + ;;; List of scatter/gather vectors to which to store data. + (param $iovs $iovec_array) + ;;; The number of bytes read. + (result $error (expected $size (error $errno))) + ) + + ;;; Read directory entries from a directory. + ;;; When successful, the contents of the output buffer consist of a sequence of + ;;; directory entries. Each directory entry consists of a `dirent` object, + ;;; followed by `dirent::d_namlen` bytes holding the name of the directory + ;;; entry. + ;; + ;;; This function fills the output buffer as much as possible, potentially + ;;; truncating the last directory entry. This allows the caller to grow its + ;;; read buffer size in case it's too small to fit a single large directory + ;;; entry, or skip the oversized directory entry. + (@interface func (export "fd_readdir") + (param $fd $fd) + ;;; The buffer where directory entries are stored + (param $buf (@witx pointer u8)) + (param $buf_len $size) + ;;; The location within the directory to start reading + (param $cookie $dircookie) + ;;; The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached. + (result $error (expected $size (error $errno))) + ) + + ;;; Atomically replace a file descriptor by renumbering another file descriptor. + ;; + ;;; Due to the strong focus on thread safety, this environment does not provide + ;;; a mechanism to duplicate or renumber a file descriptor to an arbitrary + ;;; number, like `dup2()`. This would be prone to race conditions, as an actual + ;;; file descriptor with the same number could be allocated by a different + ;;; thread at the same time. + ;; + ;;; This function provides a way to atomically renumber file descriptors, which + ;;; would disappear if `dup2()` were to be removed entirely. + (@interface func (export "fd_renumber") + (param $fd $fd) + ;;; The file descriptor to overwrite. + (param $to $fd) + (result $error (expected (error $errno))) + ) + + ;;; Move the offset of a file descriptor. + ;;; Note: This is similar to `lseek` in POSIX. + (@interface func (export "fd_seek") + (param $fd $fd) + ;;; The number of bytes to move. + (param $offset $filedelta) + ;;; The base from which the offset is relative. + (param $whence $whence) + ;;; The new offset of the file descriptor, relative to the start of the file. + (result $error (expected $filesize (error $errno))) + ) + + ;;; Synchronize the data and metadata of a file to disk. + ;;; Note: This is similar to `fsync` in POSIX. + (@interface func (export "fd_sync") + (param $fd $fd) + (result $error (expected (error $errno))) + ) + + ;;; Return the current offset of a file descriptor. + ;;; Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. + (@interface func (export "fd_tell") + (param $fd $fd) + ;;; The current offset of the file descriptor, relative to the start of the file. + (result $error (expected $filesize (error $errno))) + ) + + ;;; Write to a file descriptor. + ;;; Note: This is similar to `writev` in POSIX. + (@interface func (export "fd_write") + (param $fd $fd) + ;;; List of scatter/gather vectors from which to retrieve data. + (param $iovs $ciovec_array) + (result $error (expected $size (error $errno))) + ) + + ;;; Create a directory. + ;;; Note: This is similar to `mkdirat` in POSIX. + (@interface func (export "path_create_directory") + (param $fd $fd) + ;;; The path at which to create the directory. + (param $path string) + (result $error (expected (error $errno))) + ) + + ;;; Return the attributes of a file or directory. + ;;; Note: This is similar to `stat` in POSIX. + (@interface func (export "path_filestat_get") + (param $fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $flags $lookupflags) + ;;; The path of the file or directory to inspect. + (param $path string) + ;;; The buffer where the file's attributes are stored. + (result $error (expected $filestat (error $errno))) + ) + + ;;; Adjust the timestamps of a file or directory. + ;;; Note: This is similar to `utimensat` in POSIX. + (@interface func (export "path_filestat_set_times") + (param $fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $flags $lookupflags) + ;;; The path of the file or directory to operate on. + (param $path string) + ;;; The desired values of the data access timestamp. + (param $atim $timestamp) + ;;; The desired values of the data modification timestamp. + (param $mtim $timestamp) + ;;; A bitmask indicating which timestamps to adjust. + (param $fst_flags $fstflags) + (result $error (expected (error $errno))) + ) + + ;;; Create a hard link. + ;;; Note: This is similar to `linkat` in POSIX. + (@interface func (export "path_link") + (param $old_fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $old_flags $lookupflags) + ;;; The source path from which to link. + (param $old_path string) + ;;; The working directory at which the resolution of the new path starts. + (param $new_fd $fd) + ;;; The destination path at which to create the hard link. + (param $new_path string) + (result $error (expected (error $errno))) + ) + + ;;; Open a file or directory. + ;; + ;;; The returned file descriptor is not guaranteed to be the lowest-numbered + ;;; file descriptor not currently open; it is randomized to prevent + ;;; applications from depending on making assumptions about indexes, since this + ;;; is error-prone in multi-threaded contexts. The returned file descriptor is + ;;; guaranteed to be less than 2**31. + ;; + ;;; Note: This is similar to `openat` in POSIX. + (@interface func (export "path_open") + (param $fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $dirflags $lookupflags) + ;;; The relative path of the file or directory to open, relative to the + ;;; `path_open::fd` directory. + (param $path string) + ;;; The method by which to open the file. + (param $oflags $oflags) + ;;; The initial rights of the newly created file descriptor. The + ;;; implementation is allowed to return a file descriptor with fewer rights + ;;; than specified, if and only if those rights do not apply to the type of + ;;; file being opened. + ;; + ;;; The *base* rights are rights that will apply to operations using the file + ;;; descriptor itself, while the *inheriting* rights are rights that apply to + ;;; file descriptors derived from it. + (param $fs_rights_base $rights) + (param $fs_rights_inheriting $rights) + (param $fdflags $fdflags) + ;;; The file descriptor of the file that has been opened. + (result $error (expected $fd (error $errno))) + ) + + ;;; Read the contents of a symbolic link. + ;;; Note: This is similar to `readlinkat` in POSIX. + (@interface func (export "path_readlink") + (param $fd $fd) + ;;; The path of the symbolic link from which to read. + (param $path string) + ;;; The buffer to which to write the contents of the symbolic link. + (param $buf (@witx pointer u8)) + (param $buf_len $size) + ;;; The number of bytes placed in the buffer. + (result $error (expected $size (error $errno))) + ) + + ;;; Remove a directory. + ;;; Return `errno::notempty` if the directory is not empty. + ;;; Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + (@interface func (export "path_remove_directory") + (param $fd $fd) + ;;; The path to a directory to remove. + (param $path string) + (result $error (expected (error $errno))) + ) + + ;;; Rename a file or directory. + ;;; Note: This is similar to `renameat` in POSIX. + (@interface func (export "path_rename") + (param $fd $fd) + ;;; The source path of the file or directory to rename. + (param $old_path string) + ;;; The working directory at which the resolution of the new path starts. + (param $new_fd $fd) + ;;; The destination path to which to rename the file or directory. + (param $new_path string) + (result $error (expected (error $errno))) + ) + + ;;; Create a symbolic link. + ;;; Note: This is similar to `symlinkat` in POSIX. + (@interface func (export "path_symlink") + ;;; The contents of the symbolic link. + (param $old_path string) + (param $fd $fd) + ;;; The destination path at which to create the symbolic link. + (param $new_path string) + (result $error (expected (error $errno))) + ) + + + ;;; Unlink a file. + ;;; Return `errno::isdir` if the path refers to a directory. + ;;; Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + (@interface func (export "path_unlink_file") + (param $fd $fd) + ;;; The path to a file to unlink. + (param $path string) + (result $error (expected (error $errno))) + ) + + ;;; Concurrently poll for the occurrence of a set of events. + (@interface func (export "poll_oneoff") + ;;; The events to which to subscribe. + (param $in (@witx const_pointer $subscription)) + ;;; The events that have occurred. + (param $out (@witx pointer $event)) + ;;; Both the number of subscriptions and events. + (param $nsubscriptions $size) + ;;; The number of events stored. + (result $error (expected $size (error $errno))) + ) + + ;;; Terminate the process normally. An exit code of 0 indicates successful + ;;; termination of the program. The meanings of other values is dependent on + ;;; the environment. + (@interface func (export "proc_exit") + ;;; The exit code returned by the process. + (param $rval $exitcode) + (@witx noreturn) + ) + + ;;; Send a signal to the process of the calling thread. + ;;; Note: This is similar to `raise` in POSIX. + (@interface func (export "proc_raise") + ;;; The signal condition to trigger. + (param $sig $signal) + (result $error (expected (error $errno))) + ) + + ;;; Temporarily yield execution of the calling thread. + ;;; Note: This is similar to `sched_yield` in POSIX. + (@interface func (export "sched_yield") + (result $error (expected (error $errno))) + ) + + ;;; Write high-quality random data into a buffer. + ;;; This function blocks when the implementation is unable to immediately + ;;; provide sufficient high-quality random data. + ;;; This function may execute slowly, so when large mounts of random data are + ;;; required, it's advisable to use this function to seed a pseudo-random + ;;; number generator, rather than to provide the random data directly. + (@interface func (export "random_get") + ;;; The buffer to fill with random data. + (param $buf (@witx pointer u8)) + (param $buf_len $size) + (result $error (expected (error $errno))) + ) + + ;;; Receive a message from a socket. + ;;; Note: This is similar to `recv` in POSIX, though it also supports reading + ;;; the data into multiple buffers in the manner of `readv`. + (@interface func (export "sock_recv") + (param $fd $fd) + ;;; List of scatter/gather vectors to which to store data. + (param $ri_data $iovec_array) + ;;; Message flags. + (param $ri_flags $riflags) + ;;; Number of bytes stored in ri_data and message flags. + (result $error (expected (tuple $size $roflags) (error $errno))) + ) + + ;;; Send a message on a socket. + ;;; Note: This is similar to `send` in POSIX, though it also supports writing + ;;; the data from multiple buffers in the manner of `writev`. + (@interface func (export "sock_send") + (param $fd $fd) + ;;; List of scatter/gather vectors to which to retrieve data + (param $si_data $ciovec_array) + ;;; Message flags. + (param $si_flags $siflags) + ;;; Number of bytes transmitted. + (result $error (expected $size (error $errno))) + ) + + ;;; Shut down socket send and receive channels. + ;;; Note: This is similar to `shutdown` in POSIX. + (@interface func (export "sock_shutdown") + (param $fd $fd) + ;;; Which channels on the socket to shut down. + (param $how $sdflags) + (result $error (expected (error $errno))) + ) +) diff --git a/crates/wasi-common/witx/preview1/typenames.witx b/crates/wasi-common/witx/preview1/typenames.witx new file mode 100644 index 00000000..82ea2764 --- /dev/null +++ b/crates/wasi-common/witx/preview1/typenames.witx @@ -0,0 +1,750 @@ +;; Type names used by low-level WASI interfaces. +;; +;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). +;; +;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/blob/main/legacy/tools/witx-docs.md) +;; for an explanation of what that means. + +(typename $size u32) + +;;; Non-negative file size or length of a region within a file. +(typename $filesize u64) + +;;; Timestamp in nanoseconds. +(typename $timestamp u64) + +;;; Identifiers for clocks. +(typename $clockid + (enum (@witx tag u32) + ;;; The clock measuring real time. Time value zero corresponds with + ;;; 1970-01-01T00:00:00Z. + $realtime + ;;; The store-wide monotonic clock, which is defined as a clock measuring + ;;; real time, whose value cannot be adjusted and which cannot have negative + ;;; clock jumps. The epoch of this clock is undefined. The absolute time + ;;; value of this clock therefore has no meaning. + $monotonic + ;;; The CPU-time clock associated with the current process. + $process_cputime_id + ;;; The CPU-time clock associated with the current thread. + $thread_cputime_id + ) +) + +;;; Error codes returned by functions. +;;; Not all of these error codes are returned by the functions provided by this +;;; API; some are used in higher-level library layers, and others are provided +;;; merely for alignment with POSIX. +(typename $errno + (enum (@witx tag u16) + ;;; No error occurred. System call completed successfully. + $success + ;;; Argument list too long. + $2big + ;;; Permission denied. + $acces + ;;; Address in use. + $addrinuse + ;;; Address not available. + $addrnotavail + ;;; Address family not supported. + $afnosupport + ;;; Resource unavailable, or operation would block. + $again + ;;; Connection already in progress. + $already + ;;; Bad file descriptor. + $badf + ;;; Bad message. + $badmsg + ;;; Device or resource busy. + $busy + ;;; Operation canceled. + $canceled + ;;; No child processes. + $child + ;;; Connection aborted. + $connaborted + ;;; Connection refused. + $connrefused + ;;; Connection reset. + $connreset + ;;; Resource deadlock would occur. + $deadlk + ;;; Destination address required. + $destaddrreq + ;;; Mathematics argument out of domain of function. + $dom + ;;; Reserved. + $dquot + ;;; File exists. + $exist + ;;; Bad address. + $fault + ;;; File too large. + $fbig + ;;; Host is unreachable. + $hostunreach + ;;; Identifier removed. + $idrm + ;;; Illegal byte sequence. + $ilseq + ;;; Operation in progress. + $inprogress + ;;; Interrupted function. + $intr + ;;; Invalid argument. + $inval + ;;; I/O error. + $io + ;;; Socket is connected. + $isconn + ;;; Is a directory. + $isdir + ;;; Too many levels of symbolic links. + $loop + ;;; File descriptor value too large. + $mfile + ;;; Too many links. + $mlink + ;;; Message too large. + $msgsize + ;;; Reserved. + $multihop + ;;; Filename too long. + $nametoolong + ;;; Network is down. + $netdown + ;;; Connection aborted by network. + $netreset + ;;; Network unreachable. + $netunreach + ;;; Too many files open in system. + $nfile + ;;; No buffer space available. + $nobufs + ;;; No such device. + $nodev + ;;; No such file or directory. + $noent + ;;; Executable file format error. + $noexec + ;;; No locks available. + $nolck + ;;; Reserved. + $nolink + ;;; Not enough space. + $nomem + ;;; No message of the desired type. + $nomsg + ;;; Protocol not available. + $noprotoopt + ;;; No space left on device. + $nospc + ;;; Function not supported. + $nosys + ;;; The socket is not connected. + $notconn + ;;; Not a directory or a symbolic link to a directory. + $notdir + ;;; Directory not empty. + $notempty + ;;; State not recoverable. + $notrecoverable + ;;; Not a socket. + $notsock + ;;; Not supported, or operation not supported on socket. + $notsup + ;;; Inappropriate I/O control operation. + $notty + ;;; No such device or address. + $nxio + ;;; Value too large to be stored in data type. + $overflow + ;;; Previous owner died. + $ownerdead + ;;; Operation not permitted. + $perm + ;;; Broken pipe. + $pipe + ;;; Protocol error. + $proto + ;;; Protocol not supported. + $protonosupport + ;;; Protocol wrong type for socket. + $prototype + ;;; Result too large. + $range + ;;; Read-only file system. + $rofs + ;;; Invalid seek. + $spipe + ;;; No such process. + $srch + ;;; Reserved. + $stale + ;;; Connection timed out. + $timedout + ;;; Text file busy. + $txtbsy + ;;; Cross-device link. + $xdev + ;;; Extension: Capabilities insufficient. + $notcapable + ) +) + +;;; File descriptor rights, determining which actions may be performed. +(typename $rights + (flags (@witx repr u64) + ;;; The right to invoke `fd_datasync`. + ;; + ;;; If `path_open` is set, includes the right to invoke + ;;; `path_open` with `fdflags::dsync`. + $fd_datasync + ;;; The right to invoke `fd_read` and `sock_recv`. + ;; + ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pread`. + $fd_read + ;;; The right to invoke `fd_seek`. This flag implies `rights::fd_tell`. + $fd_seek + ;;; The right to invoke `fd_fdstat_set_flags`. + $fd_fdstat_set_flags + ;;; The right to invoke `fd_sync`. + ;; + ;;; If `path_open` is set, includes the right to invoke + ;;; `path_open` with `fdflags::rsync` and `fdflags::dsync`. + $fd_sync + ;;; The right to invoke `fd_seek` in such a way that the file offset + ;;; remains unaltered (i.e., `whence::cur` with offset zero), or to + ;;; invoke `fd_tell`. + $fd_tell + ;;; The right to invoke `fd_write` and `sock_send`. + ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`. + $fd_write + ;;; The right to invoke `fd_advise`. + $fd_advise + ;;; The right to invoke `fd_allocate`. + $fd_allocate + ;;; The right to invoke `path_create_directory`. + $path_create_directory + ;;; If `path_open` is set, the right to invoke `path_open` with `oflags::creat`. + $path_create_file + ;;; The right to invoke `path_link` with the file descriptor as the + ;;; source directory. + $path_link_source + ;;; The right to invoke `path_link` with the file descriptor as the + ;;; target directory. + $path_link_target + ;;; The right to invoke `path_open`. + $path_open + ;;; The right to invoke `fd_readdir`. + $fd_readdir + ;;; The right to invoke `path_readlink`. + $path_readlink + ;;; The right to invoke `path_rename` with the file descriptor as the source directory. + $path_rename_source + ;;; The right to invoke `path_rename` with the file descriptor as the target directory. + $path_rename_target + ;;; The right to invoke `path_filestat_get`. + $path_filestat_get + ;;; The right to change a file's size (there is no `path_filestat_set_size`). + ;;; If `path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`. + $path_filestat_set_size + ;;; The right to invoke `path_filestat_set_times`. + $path_filestat_set_times + ;;; The right to invoke `fd_filestat_get`. + $fd_filestat_get + ;;; The right to invoke `fd_filestat_set_size`. + $fd_filestat_set_size + ;;; The right to invoke `fd_filestat_set_times`. + $fd_filestat_set_times + ;;; The right to invoke `path_symlink`. + $path_symlink + ;;; The right to invoke `path_remove_directory`. + $path_remove_directory + ;;; The right to invoke `path_unlink_file`. + $path_unlink_file + ;;; If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`. + ;;; If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`. + $poll_fd_readwrite + ;;; The right to invoke `sock_shutdown`. + $sock_shutdown + ;;; The right to invoke `sock_accept`. + $sock_accept + ) +) + +;;; A file descriptor handle. +(typename $fd (handle)) + +;;; A region of memory for scatter/gather reads. +(typename $iovec + (record + ;;; The address of the buffer to be filled. + (field $buf (@witx pointer u8)) + ;;; The length of the buffer to be filled. + (field $buf_len $size) + ) +) + +;;; A region of memory for scatter/gather writes. +(typename $ciovec + (record + ;;; The address of the buffer to be written. + (field $buf (@witx const_pointer u8)) + ;;; The length of the buffer to be written. + (field $buf_len $size) + ) +) + +(typename $iovec_array (list $iovec)) +(typename $ciovec_array (list $ciovec)) + +;;; Relative offset within a file. +(typename $filedelta s64) + +;;; The position relative to which to set the offset of the file descriptor. +(typename $whence + (enum (@witx tag u8) + ;;; Seek relative to start-of-file. + $set + ;;; Seek relative to current position. + $cur + ;;; Seek relative to end-of-file. + $end + ) +) + +;;; A reference to the offset of a directory entry. +;;; +;;; The value 0 signifies the start of the directory. +(typename $dircookie u64) + +;;; The type for the `dirent::d_namlen` field of `dirent` struct. +(typename $dirnamlen u32) + +;;; File serial number that is unique within its file system. +(typename $inode u64) + +;;; The type of a file descriptor or file. +(typename $filetype + (enum (@witx tag u8) + ;;; The type of the file descriptor or file is unknown or is different from any of the other types specified. + $unknown + ;;; The file descriptor or file refers to a block device inode. + $block_device + ;;; The file descriptor or file refers to a character device inode. + $character_device + ;;; The file descriptor or file refers to a directory inode. + $directory + ;;; The file descriptor or file refers to a regular file inode. + $regular_file + ;;; The file descriptor or file refers to a datagram socket. + $socket_dgram + ;;; The file descriptor or file refers to a byte-stream socket. + $socket_stream + ;;; The file refers to a symbolic link inode. + $symbolic_link + ) +) + +;;; A directory entry. +(typename $dirent + (record + ;;; The offset of the next directory entry stored in this directory. + (field $d_next $dircookie) + ;;; The serial number of the file referred to by this directory entry. + (field $d_ino $inode) + ;;; The length of the name of the directory entry. + (field $d_namlen $dirnamlen) + ;;; The type of the file referred to by this directory entry. + (field $d_type $filetype) + ) +) + +;;; File or memory access pattern advisory information. +(typename $advice + (enum (@witx tag u8) + ;;; The application has no advice to give on its behavior with respect to the specified data. + $normal + ;;; The application expects to access the specified data sequentially from lower offsets to higher offsets. + $sequential + ;;; The application expects to access the specified data in a random order. + $random + ;;; The application expects to access the specified data in the near future. + $willneed + ;;; The application expects that it will not access the specified data in the near future. + $dontneed + ;;; The application expects to access the specified data once and then not reuse it thereafter. + $noreuse + ) +) + +;;; File descriptor flags. +(typename $fdflags + (flags (@witx repr u16) + ;;; Append mode: Data written to the file is always appended to the file's end. + $append + ;;; Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. + $dsync + ;;; Non-blocking mode. + $nonblock + ;;; Synchronized read I/O operations. + $rsync + ;;; Write according to synchronized I/O file integrity completion. In + ;;; addition to synchronizing the data stored in the file, the implementation + ;;; may also synchronously update the file's metadata. + $sync + ) +) + +;;; File descriptor attributes. +(typename $fdstat + (record + ;;; File type. + (field $fs_filetype $filetype) + ;;; File descriptor flags. + (field $fs_flags $fdflags) + ;;; Rights that apply to this file descriptor. + (field $fs_rights_base $rights) + ;;; Maximum set of rights that may be installed on new file descriptors that + ;;; are created through this file descriptor, e.g., through `path_open`. + (field $fs_rights_inheriting $rights) + ) +) + +;;; Identifier for a device containing a file system. Can be used in combination +;;; with `inode` to uniquely identify a file or directory in the filesystem. +(typename $device u64) + +;;; Which file time attributes to adjust. +(typename $fstflags + (flags (@witx repr u16) + ;;; Adjust the last data access timestamp to the value stored in `filestat::atim`. + $atim + ;;; Adjust the last data access timestamp to the time of clock `clockid::realtime`. + $atim_now + ;;; Adjust the last data modification timestamp to the value stored in `filestat::mtim`. + $mtim + ;;; Adjust the last data modification timestamp to the time of clock `clockid::realtime`. + $mtim_now + ) +) + +;;; Flags determining the method of how paths are resolved. +(typename $lookupflags + (flags (@witx repr u32) + ;;; As long as the resolved path corresponds to a symbolic link, it is expanded. + $symlink_follow + ) +) + +;;; Open flags used by `path_open`. +(typename $oflags + (flags (@witx repr u16) + ;;; Create file if it does not exist. + $creat + ;;; Fail if not a directory. + $directory + ;;; Fail if file already exists. + $excl + ;;; Truncate file to size 0. + $trunc + ) +) + +;;; Number of hard links to an inode. +(typename $linkcount u64) + +;;; File attributes. +(typename $filestat + (record + ;;; Device ID of device containing the file. + (field $dev $device) + ;;; File serial number. + (field $ino $inode) + ;;; File type. + (field $filetype $filetype) + ;;; Number of hard links to the file. + (field $nlink $linkcount) + ;;; For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. + (field $size $filesize) + ;;; Last data access timestamp. + (field $atim $timestamp) + ;;; Last data modification timestamp. + (field $mtim $timestamp) + ;;; Last file status change timestamp. + (field $ctim $timestamp) + ) +) + +;;; User-provided value that may be attached to objects that is retained when +;;; extracted from the implementation. +(typename $userdata u64) + +;;; Type of a subscription to an event or its occurrence. +(typename $eventtype + (enum (@witx tag u8) + ;;; The time value of clock `subscription_clock::id` has + ;;; reached timestamp `subscription_clock::timeout`. + $clock + ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has data + ;;; available for reading. This event always triggers for regular files. + $fd_read + ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has capacity + ;;; available for writing. This event always triggers for regular files. + $fd_write + ) +) + +;;; The state of the file descriptor subscribed to with +;;; `eventtype::fd_read` or `eventtype::fd_write`. +(typename $eventrwflags + (flags (@witx repr u16) + ;;; The peer of this socket has closed or disconnected. + $fd_readwrite_hangup + ) +) + +;;; The contents of an `event` when type is `eventtype::fd_read` or +;;; `eventtype::fd_write`. +(typename $event_fd_readwrite + (record + ;;; The number of bytes available for reading or writing. + (field $nbytes $filesize) + ;;; The state of the file descriptor. + (field $flags $eventrwflags) + ) +) + +;;; An event that occurred. +(typename $event + (record + ;;; User-provided value that got attached to `subscription::userdata`. + (field $userdata $userdata) + ;;; If non-zero, an error that occurred while processing the subscription request. + (field $error $errno) + ;;; The type of event that occurred + (field $type $eventtype) + ;;; The contents of the event, if it is an `eventtype::fd_read` or + ;;; `eventtype::fd_write`. `eventtype::clock` events ignore this field. + (field $fd_readwrite $event_fd_readwrite) + ) +) + +;;; Flags determining how to interpret the timestamp provided in +;;; `subscription_clock::timeout`. +(typename $subclockflags + (flags (@witx repr u16) + ;;; If set, treat the timestamp provided in + ;;; `subscription_clock::timeout` as an absolute timestamp of clock + ;;; `subscription_clock::id`. If clear, treat the timestamp + ;;; provided in `subscription_clock::timeout` relative to the + ;;; current time value of clock `subscription_clock::id`. + $subscription_clock_abstime + ) +) + +;;; The contents of a `subscription` when type is `eventtype::clock`. +(typename $subscription_clock + (record + ;;; The clock against which to compare the timestamp. + (field $id $clockid) + ;;; The absolute or relative timestamp. + (field $timeout $timestamp) + ;;; The amount of time that the implementation may wait additionally + ;;; to coalesce with other events. + (field $precision $timestamp) + ;;; Flags specifying whether the timeout is absolute or relative + (field $flags $subclockflags) + ) +) + +;;; The contents of a `subscription` when type is type is +;;; `eventtype::fd_read` or `eventtype::fd_write`. +(typename $subscription_fd_readwrite + (record + ;;; The file descriptor on which to wait for it to become ready for reading or writing. + (field $file_descriptor $fd) + ) +) + +;;; The contents of a `subscription`. +(typename $subscription_u + (union + (@witx tag $eventtype) + $subscription_clock + $subscription_fd_readwrite + $subscription_fd_readwrite + ) +) + +;;; Subscription to an event. +(typename $subscription + (record + ;;; User-provided value that is attached to the subscription in the + ;;; implementation and returned through `event::userdata`. + (field $userdata $userdata) + ;;; The type of the event to which to subscribe, and its contents + (field $u $subscription_u) + ) +) + +;;; Exit code generated by a process when exiting. +(typename $exitcode u32) + +;;; Signal condition. +(typename $signal + (enum (@witx tag u8) + ;;; No signal. Note that POSIX has special semantics for `kill(pid, 0)`, + ;;; so this value is reserved. + $none + ;;; Hangup. + ;;; Action: Terminates the process. + $hup + ;;; Terminate interrupt signal. + ;;; Action: Terminates the process. + $int + ;;; Terminal quit signal. + ;;; Action: Terminates the process. + $quit + ;;; Illegal instruction. + ;;; Action: Terminates the process. + $ill + ;;; Trace/breakpoint trap. + ;;; Action: Terminates the process. + $trap + ;;; Process abort signal. + ;;; Action: Terminates the process. + $abrt + ;;; Access to an undefined portion of a memory object. + ;;; Action: Terminates the process. + $bus + ;;; Erroneous arithmetic operation. + ;;; Action: Terminates the process. + $fpe + ;;; Kill. + ;;; Action: Terminates the process. + $kill + ;;; User-defined signal 1. + ;;; Action: Terminates the process. + $usr1 + ;;; Invalid memory reference. + ;;; Action: Terminates the process. + $segv + ;;; User-defined signal 2. + ;;; Action: Terminates the process. + $usr2 + ;;; Write on a pipe with no one to read it. + ;;; Action: Ignored. + $pipe + ;;; Alarm clock. + ;;; Action: Terminates the process. + $alrm + ;;; Termination signal. + ;;; Action: Terminates the process. + $term + ;;; Child process terminated, stopped, or continued. + ;;; Action: Ignored. + $chld + ;;; Continue executing, if stopped. + ;;; Action: Continues executing, if stopped. + $cont + ;;; Stop executing. + ;;; Action: Stops executing. + $stop + ;;; Terminal stop signal. + ;;; Action: Stops executing. + $tstp + ;;; Background process attempting read. + ;;; Action: Stops executing. + $ttin + ;;; Background process attempting write. + ;;; Action: Stops executing. + $ttou + ;;; High bandwidth data is available at a socket. + ;;; Action: Ignored. + $urg + ;;; CPU time limit exceeded. + ;;; Action: Terminates the process. + $xcpu + ;;; File size limit exceeded. + ;;; Action: Terminates the process. + $xfsz + ;;; Virtual timer expired. + ;;; Action: Terminates the process. + $vtalrm + ;;; Profiling timer expired. + ;;; Action: Terminates the process. + $prof + ;;; Window changed. + ;;; Action: Ignored. + $winch + ;;; I/O possible. + ;;; Action: Terminates the process. + $poll + ;;; Power failure. + ;;; Action: Terminates the process. + $pwr + ;;; Bad system call. + ;;; Action: Terminates the process. + $sys + ) +) + +;;; Flags provided to `sock_recv`. +(typename $riflags + (flags (@witx repr u16) + ;;; Returns the message without removing it from the socket's receive queue. + $recv_peek + ;;; On byte-stream sockets, block until the full amount of data can be returned. + $recv_waitall + ) +) + +;;; Flags returned by `sock_recv`. +(typename $roflags + (flags (@witx repr u16) + ;;; Returned by `sock_recv`: Message data has been truncated. + $recv_data_truncated + ) +) + +;;; Flags provided to `sock_send`. As there are currently no flags +;;; defined, it must be set to zero. +(typename $siflags u16) + +;;; Which channels on a socket to shut down. +(typename $sdflags + (flags (@witx repr u8) + ;;; Disables further receive operations. + $rd + ;;; Disables further send operations. + $wr + ) +) + +;;; Identifiers for preopened capabilities. +(typename $preopentype + (enum (@witx tag u8) + ;;; A pre-opened directory. + $dir + ) +) + +;;; The contents of a $prestat when type is `preopentype::dir`. +(typename $prestat_dir + (record + ;;; The length of the directory name for use with `fd_prestat_dir_name`. + (field $pr_name_len $size) + ) +) + +;;; Information about a pre-opened capability. +(typename $prestat + (union (@witx tag $preopentype) + $prestat_dir + ) +) + diff --git a/crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx b/crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx new file mode 100644 index 00000000..f9e2aa2c --- /dev/null +++ b/crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx @@ -0,0 +1,521 @@ +;; WASI Preview. This is an evolution of the API that WASI initially +;; launched with. +;; +;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). +;; +;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/blob/main/legacy/tools/witx-docs.md) +;; for an explanation of what that means. + +(use "typenames.witx") + +(module $wasi_snapshot_preview1 + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + + ;;; Read command-line argument data. + ;;; The size of the array should match that returned by `args_sizes_get`. + ;;; Each argument is expected to be `\0` terminated. + (@interface func (export "args_get") + (param $argv (@witx pointer (@witx pointer u8))) + (param $argv_buf (@witx pointer u8)) + (result $error (expected (error $errno))) + ) + ;;; Return command-line argument data sizes. + (@interface func (export "args_sizes_get") + ;;; Returns the number of arguments and the size of the argument string + ;;; data, or an error. + (result $error (expected (tuple $size $size) (error $errno))) + ) + + ;;; Read environment variable data. + ;;; The sizes of the buffers should match that returned by `environ_sizes_get`. + ;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s. + (@interface func (export "environ_get") + (param $environ (@witx pointer (@witx pointer u8))) + (param $environ_buf (@witx pointer u8)) + (result $error (expected (error $errno))) + ) + ;;; Return environment variable data sizes. + (@interface func (export "environ_sizes_get") + ;;; Returns the number of environment variable arguments and the size of the + ;;; environment variable data. + (result $error (expected (tuple $size $size) (error $errno))) + ) + + ;;; Return the resolution of a clock. + ;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, + ;;; return `errno::inval`. + ;;; Note: This is similar to `clock_getres` in POSIX. + (@interface func (export "clock_res_get") + ;;; The clock for which to return the resolution. + (param $id $clockid) + ;;; The resolution of the clock, or an error if one happened. + (result $error (expected $timestamp (error $errno))) + ) + ;;; Return the time value of a clock. + ;;; Note: This is similar to `clock_gettime` in POSIX. + (@interface func (export "clock_time_get") + ;;; The clock for which to return the time. + (param $id $clockid) + ;;; The maximum lag (exclusive) that the returned time value may have, compared to its actual value. + (param $precision $timestamp) + ;;; The time value of the clock. + (result $error (expected $timestamp (error $errno))) + ) + + ;;; Provide file advisory information on a file descriptor. + ;;; Note: This is similar to `posix_fadvise` in POSIX. + (@interface func (export "fd_advise") + (param $fd $fd) + ;;; The offset within the file to which the advisory applies. + (param $offset $filesize) + ;;; The length of the region to which the advisory applies. + (param $len $filesize) + ;;; The advice. + (param $advice $advice) + (result $error (expected (error $errno))) + ) + + ;;; Force the allocation of space in a file. + ;;; Note: This is similar to `posix_fallocate` in POSIX. + (@interface func (export "fd_allocate") + (param $fd $fd) + ;;; The offset at which to start the allocation. + (param $offset $filesize) + ;;; The length of the area that is allocated. + (param $len $filesize) + (result $error (expected (error $errno))) + ) + + ;;; Close a file descriptor. + ;;; Note: This is similar to `close` in POSIX. + (@interface func (export "fd_close") + (param $fd $fd) + (result $error (expected (error $errno))) + ) + + ;;; Synchronize the data of a file to disk. + ;;; Note: This is similar to `fdatasync` in POSIX. + (@interface func (export "fd_datasync") + (param $fd $fd) + (result $error (expected (error $errno))) + ) + + ;;; Get the attributes of a file descriptor. + ;;; Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. + (@interface func (export "fd_fdstat_get") + (param $fd $fd) + ;;; The buffer where the file descriptor's attributes are stored. + (result $error (expected $fdstat (error $errno))) + ) + + ;;; Adjust the flags associated with a file descriptor. + ;;; Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. + (@interface func (export "fd_fdstat_set_flags") + (param $fd $fd) + ;;; The desired values of the file descriptor flags. + (param $flags $fdflags) + (result $error (expected (error $errno))) + ) + + ;;; Adjust the rights associated with a file descriptor. + ;;; This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights + (@interface func (export "fd_fdstat_set_rights") + (param $fd $fd) + ;;; The desired rights of the file descriptor. + (param $fs_rights_base $rights) + (param $fs_rights_inheriting $rights) + (result $error (expected (error $errno))) + ) + + ;;; Return the attributes of an open file. + (@interface func (export "fd_filestat_get") + (param $fd $fd) + ;;; The buffer where the file's attributes are stored. + (result $error (expected $filestat (error $errno))) + ) + + ;;; Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. + ;;; Note: This is similar to `ftruncate` in POSIX. + (@interface func (export "fd_filestat_set_size") + (param $fd $fd) + ;;; The desired file size. + (param $size $filesize) + (result $error (expected (error $errno))) + ) + + ;;; Adjust the timestamps of an open file or directory. + ;;; Note: This is similar to `futimens` in POSIX. + (@interface func (export "fd_filestat_set_times") + (param $fd $fd) + ;;; The desired values of the data access timestamp. + (param $atim $timestamp) + ;;; The desired values of the data modification timestamp. + (param $mtim $timestamp) + ;;; A bitmask indicating which timestamps to adjust. + (param $fst_flags $fstflags) + (result $error (expected (error $errno))) + ) + + ;;; Read from a file descriptor, without using and updating the file descriptor's offset. + ;;; Note: This is similar to `preadv` in POSIX. + (@interface func (export "fd_pread") + (param $fd $fd) + ;;; List of scatter/gather vectors in which to store data. + (param $iovs $iovec_array) + ;;; The offset within the file at which to read. + (param $offset $filesize) + ;;; The number of bytes read. + (result $error (expected $size (error $errno))) + ) + + ;;; Return a description of the given preopened file descriptor. + (@interface func (export "fd_prestat_get") + (param $fd $fd) + ;;; The buffer where the description is stored. + (result $error (expected $prestat (error $errno))) + ) + + ;;; Return a description of the given preopened file descriptor. + (@interface func (export "fd_prestat_dir_name") + (param $fd $fd) + ;;; A buffer into which to write the preopened directory name. + (param $path (@witx pointer u8)) + (param $path_len $size) + (result $error (expected (error $errno))) + ) + + ;;; Write to a file descriptor, without using and updating the file descriptor's offset. + ;;; Note: This is similar to `pwritev` in POSIX. + (@interface func (export "fd_pwrite") + (param $fd $fd) + ;;; List of scatter/gather vectors from which to retrieve data. + (param $iovs $ciovec_array) + ;;; The offset within the file at which to write. + (param $offset $filesize) + ;;; The number of bytes written. + (result $error (expected $size (error $errno))) + ) + + ;;; Read from a file descriptor. + ;;; Note: This is similar to `readv` in POSIX. + (@interface func (export "fd_read") + (param $fd $fd) + ;;; List of scatter/gather vectors to which to store data. + (param $iovs $iovec_array) + ;;; The number of bytes read. + (result $error (expected $size (error $errno))) + ) + + ;;; Read directory entries from a directory. + ;;; When successful, the contents of the output buffer consist of a sequence of + ;;; directory entries. Each directory entry consists of a `dirent` object, + ;;; followed by `dirent::d_namlen` bytes holding the name of the directory + ;;; entry. + ;; + ;;; This function fills the output buffer as much as possible, potentially + ;;; truncating the last directory entry. This allows the caller to grow its + ;;; read buffer size in case it's too small to fit a single large directory + ;;; entry, or skip the oversized directory entry. + (@interface func (export "fd_readdir") + (param $fd $fd) + ;;; The buffer where directory entries are stored + (param $buf (@witx pointer u8)) + (param $buf_len $size) + ;;; The location within the directory to start reading + (param $cookie $dircookie) + ;;; The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached. + (result $error (expected $size (error $errno))) + ) + + ;;; Atomically replace a file descriptor by renumbering another file descriptor. + ;; + ;;; Due to the strong focus on thread safety, this environment does not provide + ;;; a mechanism to duplicate or renumber a file descriptor to an arbitrary + ;;; number, like `dup2()`. This would be prone to race conditions, as an actual + ;;; file descriptor with the same number could be allocated by a different + ;;; thread at the same time. + ;; + ;;; This function provides a way to atomically renumber file descriptors, which + ;;; would disappear if `dup2()` were to be removed entirely. + (@interface func (export "fd_renumber") + (param $fd $fd) + ;;; The file descriptor to overwrite. + (param $to $fd) + (result $error (expected (error $errno))) + ) + + ;;; Move the offset of a file descriptor. + ;;; Note: This is similar to `lseek` in POSIX. + (@interface func (export "fd_seek") + (param $fd $fd) + ;;; The number of bytes to move. + (param $offset $filedelta) + ;;; The base from which the offset is relative. + (param $whence $whence) + ;;; The new offset of the file descriptor, relative to the start of the file. + (result $error (expected $filesize (error $errno))) + ) + + ;;; Synchronize the data and metadata of a file to disk. + ;;; Note: This is similar to `fsync` in POSIX. + (@interface func (export "fd_sync") + (param $fd $fd) + (result $error (expected (error $errno))) + ) + + ;;; Return the current offset of a file descriptor. + ;;; Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. + (@interface func (export "fd_tell") + (param $fd $fd) + ;;; The current offset of the file descriptor, relative to the start of the file. + (result $error (expected $filesize (error $errno))) + ) + + ;;; Write to a file descriptor. + ;;; Note: This is similar to `writev` in POSIX. + (@interface func (export "fd_write") + (param $fd $fd) + ;;; List of scatter/gather vectors from which to retrieve data. + (param $iovs $ciovec_array) + (result $error (expected $size (error $errno))) + ) + + ;;; Create a directory. + ;;; Note: This is similar to `mkdirat` in POSIX. + (@interface func (export "path_create_directory") + (param $fd $fd) + ;;; The path at which to create the directory. + (param $path string) + (result $error (expected (error $errno))) + ) + + ;;; Return the attributes of a file or directory. + ;;; Note: This is similar to `stat` in POSIX. + (@interface func (export "path_filestat_get") + (param $fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $flags $lookupflags) + ;;; The path of the file or directory to inspect. + (param $path string) + ;;; The buffer where the file's attributes are stored. + (result $error (expected $filestat (error $errno))) + ) + + ;;; Adjust the timestamps of a file or directory. + ;;; Note: This is similar to `utimensat` in POSIX. + (@interface func (export "path_filestat_set_times") + (param $fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $flags $lookupflags) + ;;; The path of the file or directory to operate on. + (param $path string) + ;;; The desired values of the data access timestamp. + (param $atim $timestamp) + ;;; The desired values of the data modification timestamp. + (param $mtim $timestamp) + ;;; A bitmask indicating which timestamps to adjust. + (param $fst_flags $fstflags) + (result $error (expected (error $errno))) + ) + + ;;; Create a hard link. + ;;; Note: This is similar to `linkat` in POSIX. + (@interface func (export "path_link") + (param $old_fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $old_flags $lookupflags) + ;;; The source path from which to link. + (param $old_path string) + ;;; The working directory at which the resolution of the new path starts. + (param $new_fd $fd) + ;;; The destination path at which to create the hard link. + (param $new_path string) + (result $error (expected (error $errno))) + ) + + ;;; Open a file or directory. + ;; + ;;; The returned file descriptor is not guaranteed to be the lowest-numbered + ;;; file descriptor not currently open; it is randomized to prevent + ;;; applications from depending on making assumptions about indexes, since this + ;;; is error-prone in multi-threaded contexts. The returned file descriptor is + ;;; guaranteed to be less than 2**31. + ;; + ;;; Note: This is similar to `openat` in POSIX. + (@interface func (export "path_open") + (param $fd $fd) + ;;; Flags determining the method of how the path is resolved. + (param $dirflags $lookupflags) + ;;; The relative path of the file or directory to open, relative to the + ;;; `path_open::fd` directory. + (param $path string) + ;;; The method by which to open the file. + (param $oflags $oflags) + ;;; The initial rights of the newly created file descriptor. The + ;;; implementation is allowed to return a file descriptor with fewer rights + ;;; than specified, if and only if those rights do not apply to the type of + ;;; file being opened. + ;; + ;;; The *base* rights are rights that will apply to operations using the file + ;;; descriptor itself, while the *inheriting* rights are rights that apply to + ;;; file descriptors derived from it. + (param $fs_rights_base $rights) + (param $fs_rights_inheriting $rights) + (param $fdflags $fdflags) + ;;; The file descriptor of the file that has been opened. + (result $error (expected $fd (error $errno))) + ) + + ;;; Read the contents of a symbolic link. + ;;; Note: This is similar to `readlinkat` in POSIX. + (@interface func (export "path_readlink") + (param $fd $fd) + ;;; The path of the symbolic link from which to read. + (param $path string) + ;;; The buffer to which to write the contents of the symbolic link. + (param $buf (@witx pointer u8)) + (param $buf_len $size) + ;;; The number of bytes placed in the buffer. + (result $error (expected $size (error $errno))) + ) + + ;;; Remove a directory. + ;;; Return `errno::notempty` if the directory is not empty. + ;;; Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + (@interface func (export "path_remove_directory") + (param $fd $fd) + ;;; The path to a directory to remove. + (param $path string) + (result $error (expected (error $errno))) + ) + + ;;; Rename a file or directory. + ;;; Note: This is similar to `renameat` in POSIX. + (@interface func (export "path_rename") + (param $fd $fd) + ;;; The source path of the file or directory to rename. + (param $old_path string) + ;;; The working directory at which the resolution of the new path starts. + (param $new_fd $fd) + ;;; The destination path to which to rename the file or directory. + (param $new_path string) + (result $error (expected (error $errno))) + ) + + ;;; Create a symbolic link. + ;;; Note: This is similar to `symlinkat` in POSIX. + (@interface func (export "path_symlink") + ;;; The contents of the symbolic link. + (param $old_path string) + (param $fd $fd) + ;;; The destination path at which to create the symbolic link. + (param $new_path string) + (result $error (expected (error $errno))) + ) + + + ;;; Unlink a file. + ;;; Return `errno::isdir` if the path refers to a directory. + ;;; Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + (@interface func (export "path_unlink_file") + (param $fd $fd) + ;;; The path to a file to unlink. + (param $path string) + (result $error (expected (error $errno))) + ) + + ;;; Concurrently poll for the occurrence of a set of events. + (@interface func (export "poll_oneoff") + ;;; The events to which to subscribe. + (param $in (@witx const_pointer $subscription)) + ;;; The events that have occurred. + (param $out (@witx pointer $event)) + ;;; Both the number of subscriptions and events. + (param $nsubscriptions $size) + ;;; The number of events stored. + (result $error (expected $size (error $errno))) + ) + + ;;; Terminate the process normally. An exit code of 0 indicates successful + ;;; termination of the program. The meanings of other values is dependent on + ;;; the environment. + (@interface func (export "proc_exit") + ;;; The exit code returned by the process. + (param $rval $exitcode) + (@witx noreturn) + ) + + ;;; Send a signal to the process of the calling thread. + ;;; Note: This is similar to `raise` in POSIX. + (@interface func (export "proc_raise") + ;;; The signal condition to trigger. + (param $sig $signal) + (result $error (expected (error $errno))) + ) + + ;;; Temporarily yield execution of the calling thread. + ;;; Note: This is similar to `sched_yield` in POSIX. + (@interface func (export "sched_yield") + (result $error (expected (error $errno))) + ) + + ;;; Write high-quality random data into a buffer. + ;;; This function blocks when the implementation is unable to immediately + ;;; provide sufficient high-quality random data. + ;;; This function may execute slowly, so when large mounts of random data are + ;;; required, it's advisable to use this function to seed a pseudo-random + ;;; number generator, rather than to provide the random data directly. + (@interface func (export "random_get") + ;;; The buffer to fill with random data. + (param $buf (@witx pointer u8)) + (param $buf_len $size) + (result $error (expected (error $errno))) + ) + + ;;; Accept a new incoming connection. + ;;; Note: This is similar to `accept` in POSIX. + (@interface func (export "sock_accept") + ;;; The listening socket. + (param $fd $fd) + ;;; The desired values of the file descriptor flags. + (param $flags $fdflags) + ;;; New socket connection + (result $error (expected $fd (error $errno))) + ) + + ;;; Receive a message from a socket. + ;;; Note: This is similar to `recv` in POSIX, though it also supports reading + ;;; the data into multiple buffers in the manner of `readv`. + (@interface func (export "sock_recv") + (param $fd $fd) + ;;; List of scatter/gather vectors to which to store data. + (param $ri_data $iovec_array) + ;;; Message flags. + (param $ri_flags $riflags) + ;;; Number of bytes stored in ri_data and message flags. + (result $error (expected (tuple $size $roflags) (error $errno))) + ) + + ;;; Send a message on a socket. + ;;; Note: This is similar to `send` in POSIX, though it also supports writing + ;;; the data from multiple buffers in the manner of `writev`. + (@interface func (export "sock_send") + (param $fd $fd) + ;;; List of scatter/gather vectors to which to retrieve data + (param $si_data $ciovec_array) + ;;; Message flags. + (param $si_flags $siflags) + ;;; Number of bytes transmitted. + (result $error (expected $size (error $errno))) + ) + + ;;; Shut down socket send and receive channels. + ;;; Note: This is similar to `shutdown` in POSIX. + (@interface func (export "sock_shutdown") + (param $fd $fd) + ;;; Which channels on the socket to shut down. + (param $how $sdflags) + (result $error (expected (error $errno))) + ) +) diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 780dbee8..20d3646a 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -10,7 +10,7 @@ edition.workspace = true [dependencies] anyhow = { workspace = true } webrogue-wrapp = { workspace = true } -wasi-common = { workspace = true, default-features = false, features = [] } +webrogue-wasi-common = { workspace = true, default-features = false, features = [] } wiggle = { workspace = true, default-features = false } async-trait = { workspace = true } rand_core = { workspace = true } @@ -25,7 +25,7 @@ io-extras = { workspace = true } windows-sys = { workspace = true, features = ["Win32_System_Threading", "Win32_Security"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -wasi-common = { workspace = true, default-features = false, features = ["sync"] } +webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/wasip1/src/fs/dev/mod.rs b/crates/wasip1/src/fs/dev/mod.rs index 6bd64e57..943d08f6 100644 --- a/crates/wasip1/src/fs/dev/mod.rs +++ b/crates/wasip1/src/fs/dev/mod.rs @@ -1,6 +1,6 @@ use std::{any::Any, sync::Arc}; -use wasi_common::{ +use webrogue_wasi_common::{ file::{FdFlags, OFlags}, ErrorExt as _, WasiDir, WasiFile, }; @@ -12,13 +12,13 @@ mod wakeup; struct DevState {} trait IDevDir { - fn content(&self, state: &DevState) -> Vec<(String, wasi_common::file::FileType)>; + fn content(&self, state: &DevState) -> Vec<(String, webrogue_wasi_common::file::FileType)>; fn open( &self, filename: &str, create: bool, state: &DevState, - ) -> Result; + ) -> Result; } struct DevDirAdapter { @@ -47,14 +47,14 @@ impl WasiDir for DevDirAdapter { _read: bool, _write: bool, _fdflags: FdFlags, - ) -> Result { + ) -> Result { let mut path_components = self.path_components.clone(); for path_component in path.split('/') { match path_component { "." => {} ".." => { if path_components.pop().is_none() { - return Err(wasi_common::Error::badf()); + return Err(webrogue_wasi_common::Error::badf()); } } path_component => path_components.push(path_component.to_owned()), @@ -73,27 +73,34 @@ impl WasiDir for DevDirAdapter { } OpenResult::File(file) => { if !is_last_component { - return Err(wasi_common::Error::not_dir()); + return Err(webrogue_wasi_common::Error::not_dir()); } - return Ok(wasi_common::dir::OpenResult::File(file)); + return Ok(webrogue_wasi_common::dir::OpenResult::File(file)); } } } - return Ok(wasi_common::dir::OpenResult::Dir(Box::new(DevDirAdapter { - state: self.state.clone(), - path_components, - inner: dir, - }))); + return Ok(webrogue_wasi_common::dir::OpenResult::Dir(Box::new( + DevDirAdapter { + state: self.state.clone(), + path_components, + inner: dir, + }, + ))); } async fn readdir( &self, - cursor: wasi_common::dir::ReaddirCursor, + cursor: webrogue_wasi_common::dir::ReaddirCursor, ) -> Result< Box< - dyn Iterator> + Send, + dyn Iterator< + Item = Result< + webrogue_wasi_common::dir::ReaddirEntity, + webrogue_wasi_common::Error, + >, + > + Send, >, - wasi_common::Error, + webrogue_wasi_common::Error, > { let result = self .inner @@ -101,8 +108,8 @@ impl WasiDir for DevDirAdapter { .into_iter() .enumerate() .map(|(index, (name, ty))| { - Ok(wasi_common::dir::ReaddirEntity { - next: wasi_common::dir::ReaddirCursor::from(index as u64 + 1), + Ok(webrogue_wasi_common::dir::ReaddirEntity { + next: webrogue_wasi_common::dir::ReaddirCursor::from(index as u64 + 1), inode: 0, name, filetype: ty, diff --git a/crates/wasip1/src/fs/dev/root.rs b/crates/wasip1/src/fs/dev/root.rs index 12076d6f..1ed3ca3d 100644 --- a/crates/wasip1/src/fs/dev/root.rs +++ b/crates/wasip1/src/fs/dev/root.rs @@ -1,4 +1,4 @@ -use wasi_common::{file::FileType, ErrorExt}; +use webrogue_wasi_common::{file::FileType, ErrorExt}; use crate::fs::dev::{DevState, IDevDir, OpenResult}; @@ -16,11 +16,11 @@ impl IDevDir for Dir { filename: &str, _create: bool, _state: &DevState, - ) -> Result { + ) -> Result { match filename { #[cfg(not(target_arch = "wasm32"))] "wakeup" => Ok(OpenResult::File(Box::new(wakeup::File::new()?))), - _ => Err(wasi_common::Error::not_found()), + _ => Err(webrogue_wasi_common::Error::not_found()), } } } diff --git a/crates/wasip1/src/fs/dev/wakeup.rs b/crates/wasip1/src/fs/dev/wakeup.rs index b183d15d..07ca6fa4 100644 --- a/crates/wasip1/src/fs/dev/wakeup.rs +++ b/crates/wasip1/src/fs/dev/wakeup.rs @@ -1,10 +1,10 @@ -use wasi_common::{file::FileType, ErrorExt as _, WasiFile}; +use webrogue_wasi_common::{file::FileType, ErrorExt as _, WasiFile}; #[cfg(unix)] mod unix { use std::os::fd::AsFd; - use wasi_common::ErrorExt as _; + use webrogue_wasi_common::ErrorExt as _; pub struct File { read_fd: std::os::fd::OwnedFd, @@ -12,16 +12,16 @@ mod unix { } impl File { - pub fn new() -> Result { + pub fn new() -> Result { // let (read_fd, write_fd) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::NONBLOCK | rustix::pipe::PipeFlags::CLOEXEC) // .map_err(|_err| wasi_common::Error::not_supported())?; - let (read_fd, write_fd) = - rustix::pipe::pipe().map_err(|_err| wasi_common::Error::not_supported())?; + let (read_fd, write_fd) = rustix::pipe::pipe() + .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; for fd in [read_fd.as_fd(), read_fd.as_fd()] { rustix::io::fcntl_setfd(fd, rustix::io::FdFlags::CLOEXEC) - .map_err(|_err| wasi_common::Error::not_supported())?; + .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; rustix::io::ioctl_fionbio(fd, true) - .map_err(|_err| wasi_common::Error::not_supported())?; + .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; } Ok(Self { read_fd, write_fd }) } @@ -120,7 +120,7 @@ impl WasiFile for File { self } - async fn get_filetype(&self) -> Result { + async fn get_filetype(&self) -> Result { Ok(FileType::Pipe) } @@ -137,18 +137,18 @@ impl WasiFile for File { async fn read_vectored<'a>( &self, _bufs: &mut [std::io::IoSliceMut<'a>], - ) -> Result { + ) -> Result { self.acknowledge() - .map_err(|_| wasi_common::Error::not_supported())?; + .map_err(|_| webrogue_wasi_common::Error::not_supported())?; Ok(0) } async fn write_vectored<'a>( &self, _bufs: &[std::io::IoSlice<'a>], - ) -> Result { + ) -> Result { self.signal() - .map_err(|_| wasi_common::Error::not_supported())?; + .map_err(|_| webrogue_wasi_common::Error::not_supported())?; Ok(_bufs.iter().map(|slice| slice.len()).sum::() as u64) } diff --git a/crates/wasip1/src/fs/mod.rs b/crates/wasip1/src/fs/mod.rs index eebc7796..c5703410 100644 --- a/crates/wasip1/src/fs/mod.rs +++ b/crates/wasip1/src/fs/mod.rs @@ -7,7 +7,7 @@ use std::io::Seek; use std::sync::{Arc, Mutex, Weak}; use webrogue_wrapp::IFilePosition; -use wasi_common::ErrorExt as _; +use webrogue_wasi_common::ErrorExt as _; pub struct DirInner { dirs: std::collections::BTreeMap>>, @@ -127,7 +127,9 @@ impl Dir { } #[async_trait::async_trait] -impl wasi_common::WasiDir for Dir { +impl webrogue_wasi_common::WasiDir + for Dir +{ fn as_any(&self) -> &dyn std::any::Any { self } @@ -136,56 +138,75 @@ impl wasi_common::WasiDir for D &self, _symlink_follow: bool, path: &str, - _oflags: wasi_common::file::OFlags, + _oflags: webrogue_wasi_common::file::OFlags, _read: bool, write: bool, - _fdflags: wasi_common::file::FdFlags, - ) -> Result { + _fdflags: webrogue_wasi_common::file::FdFlags, + ) -> Result { if write { - return Err(wasi_common::Error::not_supported()); + return Err(webrogue_wasi_common::Error::not_supported()); } if let Some(search_result) = self.search(path) { match search_result { - SearchResult::File(file) => Ok(wasi_common::dir::OpenResult::File(Box::new( - OpenFile::open(file), - ))), - SearchResult::Dir(dir) => Ok(wasi_common::dir::OpenResult::Dir(Box::new(dir))), + SearchResult::File(file) => Ok(webrogue_wasi_common::dir::OpenResult::File( + Box::new(OpenFile::open(file)), + )), + SearchResult::Dir(dir) => { + Ok(webrogue_wasi_common::dir::OpenResult::Dir(Box::new(dir))) + } } } else { - Err(wasi_common::Error::not_found()) + Err(webrogue_wasi_common::Error::not_found()) } } - async fn create_dir(&self, _path: &str) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + async fn create_dir(&self, _path: &str) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } async fn readdir( &self, - cursor: wasi_common::dir::ReaddirCursor, + cursor: webrogue_wasi_common::dir::ReaddirCursor, ) -> Result< Box< - dyn Iterator> + Send, + dyn Iterator< + Item = Result< + webrogue_wasi_common::dir::ReaddirEntity, + webrogue_wasi_common::Error, + >, + > + Send, >, - wasi_common::Error, + webrogue_wasi_common::Error, > { - let mut result = vec![(".".to_owned(), wasi_common::file::FileType::Directory)]; + let mut result = vec![( + ".".to_owned(), + webrogue_wasi_common::file::FileType::Directory, + )]; if self.inner.parent.is_some() { - result.push(("..".to_owned(), wasi_common::file::FileType::Directory)); + result.push(( + "..".to_owned(), + webrogue_wasi_common::file::FileType::Directory, + )); } for dir in self.inner.dirs.keys() { - result.push((dir.to_owned(), wasi_common::file::FileType::Directory)); + result.push(( + dir.to_owned(), + webrogue_wasi_common::file::FileType::Directory, + )); } for file in self.inner.files.keys() { - result.push((file.to_owned(), wasi_common::file::FileType::RegularFile)); + result.push(( + file.to_owned(), + webrogue_wasi_common::file::FileType::RegularFile, + )); } let result = result .into_iter() .enumerate() .map(|(index, (name, ty))| { - Ok(wasi_common::dir::ReaddirEntity { - next: wasi_common::dir::ReaddirCursor::from(index as u64 + 1), + Ok(webrogue_wasi_common::dir::ReaddirEntity { + next: webrogue_wasi_common::dir::ReaddirCursor::from(index as u64 + 1), inode: 0, name, filetype: ty, @@ -196,23 +217,32 @@ impl wasi_common::WasiDir for D Ok(Box::new(result)) } - async fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + async fn symlink( + &self, + _old_path: &str, + _new_path: &str, + ) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } - async fn remove_dir(&self, _path: &str) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + async fn remove_dir(&self, _path: &str) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } - async fn unlink_file(&self, _path: &str) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + async fn unlink_file(&self, _path: &str) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } - async fn read_link(&self, _path: &str) -> Result { - Err(wasi_common::Error::not_supported()) + async fn read_link( + &self, + _path: &str, + ) -> Result { + Err(webrogue_wasi_common::Error::not_supported()) } - async fn get_filestat(&self) -> Result { + async fn get_filestat( + &self, + ) -> Result { todo!() // Err(Error::not_supported()) } @@ -221,23 +251,23 @@ impl wasi_common::WasiDir for D &self, path: &str, _follow_symlinks: bool, - ) -> Result { + ) -> Result { if let Some(search_result) = self.search(path) { match search_result { - SearchResult::File(file) => Ok(wasi_common::file::Filestat { + SearchResult::File(file) => Ok(webrogue_wasi_common::file::Filestat { device_id: 0, inode: 0, - filetype: wasi_common::file::FileType::RegularFile, + filetype: webrogue_wasi_common::file::FileType::RegularFile, nlink: 0, size: file.position.get_size() as u64, atim: None, mtim: None, ctim: None, }), - SearchResult::Dir(_dir) => Ok(wasi_common::file::Filestat { + SearchResult::Dir(_dir) => Ok(webrogue_wasi_common::file::Filestat { device_id: 0, inode: 0, - filetype: wasi_common::file::FileType::Directory, + filetype: webrogue_wasi_common::file::FileType::Directory, nlink: 0, size: 0, atim: None, @@ -246,36 +276,36 @@ impl wasi_common::WasiDir for D }), } } else { - Err(wasi_common::Error::not_found()) + Err(webrogue_wasi_common::Error::not_found()) } } async fn rename( &self, _path: &str, - _dest_dir: &dyn wasi_common::WasiDir, + _dest_dir: &dyn webrogue_wasi_common::WasiDir, _dest_path: &str, - ) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + ) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } async fn hard_link( &self, _path: &str, - _target_dir: &dyn wasi_common::WasiDir, + _target_dir: &dyn webrogue_wasi_common::WasiDir, _target_path: &str, - ) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + ) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } async fn set_times( &self, _path: &str, - _atime: Option, - _mtime: Option, + _atime: Option, + _mtime: Option, _follow_symlinks: bool, - ) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::not_supported()) + ) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::not_supported()) } } @@ -320,15 +350,17 @@ impl OpenFile { } #[async_trait::async_trait] -impl wasi_common::WasiFile +impl webrogue_wasi_common::WasiFile for OpenFile { fn as_any(&self) -> &dyn std::any::Any { self } - async fn get_filetype(&self) -> Result { - Ok(wasi_common::file::FileType::RegularFile) + async fn get_filetype( + &self, + ) -> Result { + Ok(webrogue_wasi_common::file::FileType::RegularFile) } fn isatty(&self) -> bool { @@ -337,60 +369,64 @@ impl wasi_common::WasiFile async fn sock_accept( &self, - _fdflags: wasi_common::file::FdFlags, - ) -> Result, wasi_common::Error> { - Err(wasi_common::Error::badf()) + _fdflags: webrogue_wasi_common::file::FdFlags, + ) -> Result, webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::badf()) } async fn sock_recv<'a>( &self, _ri_data: &mut [std::io::IoSliceMut<'a>], - _ri_flags: wasi_common::file::RiFlags, - ) -> Result<(u64, wasi_common::file::RoFlags), wasi_common::Error> { - Err(wasi_common::Error::badf()) + _ri_flags: webrogue_wasi_common::file::RiFlags, + ) -> Result<(u64, webrogue_wasi_common::file::RoFlags), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::badf()) } async fn sock_send<'a>( &self, _si_data: &[std::io::IoSlice<'a>], - _si_flags: wasi_common::file::SiFlags, - ) -> Result { - Err(wasi_common::Error::badf()) + _si_flags: webrogue_wasi_common::file::SiFlags, + ) -> Result { + Err(webrogue_wasi_common::Error::badf()) } async fn sock_shutdown( &self, - _how: wasi_common::file::SdFlags, - ) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::badf()) + _how: webrogue_wasi_common::file::SdFlags, + ) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::badf()) } - async fn datasync(&self) -> Result<(), wasi_common::Error> { + async fn datasync(&self) -> Result<(), webrogue_wasi_common::Error> { Ok(()) } - async fn sync(&self) -> Result<(), wasi_common::Error> { + async fn sync(&self) -> Result<(), webrogue_wasi_common::Error> { Ok(()) } - async fn get_fdflags(&self) -> Result { + async fn get_fdflags( + &self, + ) -> Result { todo!(); - // Ok(wasi_common::file::FdFlags::empty()) + // Ok(webrogue_wasi_common::file::FdFlags::empty()) } async fn set_fdflags( &mut self, - _flags: wasi_common::file::FdFlags, - ) -> Result<(), wasi_common::Error> { + _flags: webrogue_wasi_common::file::FdFlags, + ) -> Result<(), webrogue_wasi_common::Error> { todo!(); - // Err(wasi_common::Error::badf()) + // Err(webrogue_wasi_common::Error::badf()) } - async fn get_filestat(&self) -> Result { - Ok(wasi_common::file::Filestat { + async fn get_filestat( + &self, + ) -> Result { + Ok(webrogue_wasi_common::file::Filestat { device_id: 0, inode: 0, - filetype: wasi_common::file::FileType::RegularFile, + filetype: webrogue_wasi_common::file::FileType::RegularFile, nlink: 0, size: self.pos.get_size() as u64, atim: None, @@ -399,32 +435,32 @@ impl wasi_common::WasiFile }) } - async fn set_filestat_size(&self, _size: u64) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::badf()) + async fn set_filestat_size(&self, _size: u64) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::badf()) } async fn advise( &self, _offset: u64, _len: u64, - _advice: wasi_common::file::Advice, - ) -> Result<(), wasi_common::Error> { + _advice: webrogue_wasi_common::file::Advice, + ) -> Result<(), webrogue_wasi_common::Error> { Ok(()) } async fn set_times( &self, - _atime: Option, - _mtime: Option, - ) -> Result<(), wasi_common::Error> { + _atime: Option, + _mtime: Option, + ) -> Result<(), webrogue_wasi_common::Error> { todo!(); - // Err(wasi_common::Error::badf()) + // Err(webrogue_wasi_common::Error::badf()) } async fn read_vectored<'a>( &self, bufs: &mut [std::io::IoSliceMut<'a>], - ) -> Result { + ) -> Result { Ok(self.reader.lock().unwrap().read_vectored(bufs)? as u64) } @@ -432,31 +468,31 @@ impl wasi_common::WasiFile &self, _bufs: &mut [std::io::IoSliceMut<'a>], _offset: u64, - ) -> Result { + ) -> Result { todo!(); - // Err(wasi_common::Error::badf()) + // Err(webrogue_wasi_common::Error::badf()) } async fn write_vectored<'a>( &self, _bufs: &[std::io::IoSlice<'a>], - ) -> Result { - Err(wasi_common::Error::badf()) + ) -> Result { + Err(webrogue_wasi_common::Error::badf()) } async fn write_vectored_at<'a>( &self, _bufs: &[std::io::IoSlice<'a>], _offset: u64, - ) -> Result { - Err(wasi_common::Error::badf()) + ) -> Result { + Err(webrogue_wasi_common::Error::badf()) } - async fn seek(&self, pos: std::io::SeekFrom) -> Result { + async fn seek(&self, pos: std::io::SeekFrom) -> Result { Ok(self.reader.lock().unwrap().seek(pos)?) } - async fn peek(&self, buf: &mut [u8]) -> Result { + async fn peek(&self, buf: &mut [u8]) -> Result { let mut reader = self.reader.lock().unwrap(); let pos = reader.seek(std::io::SeekFrom::Current(0))?; let result = reader.read(buf); @@ -464,15 +500,15 @@ impl wasi_common::WasiFile Ok(result? as u64) } - fn num_ready_bytes(&self) -> Result { + fn num_ready_bytes(&self) -> Result { Ok(0) } - async fn readable(&self) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::badf()) + async fn readable(&self) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::badf()) } - async fn writable(&self) -> Result<(), wasi_common::Error> { - Err(wasi_common::Error::badf()) + async fn writable(&self) -> Result<(), webrogue_wasi_common::Error> { + Err(webrogue_wasi_common::Error::badf()) } } diff --git a/crates/wasip1/src/lib.rs b/crates/wasip1/src/lib.rs index f09ab90a..8b9740cb 100644 --- a/crates/wasip1/src/lib.rs +++ b/crates/wasip1/src/lib.rs @@ -5,10 +5,10 @@ pub fn make_ctx( handle: VFSHandle, config: &webrogue_wrapp::config::Config, persistent_dir: &std::path::Path, -) -> anyhow::Result { +) -> anyhow::Result { #[cfg(not(target_arch = "wasm32"))] let mut wasi_ctx = { - let mut builder = wasi_common::sync::WasiCtxBuilder::new(); + let mut builder = webrogue_wasi_common::sync::WasiCtxBuilder::new(); // builder.inherit_stdio(); // builder.stdout(Box::new(stdout::STDOutFile {})); // builder.stderr(Box::new(stdout::STDOutFile {})); @@ -19,10 +19,10 @@ pub fn make_ctx( let random = Box::new(cap_rand::rngs::StdRng::from_seed( cap_rand::thread_rng(cap_rand::ambient_authority()).r#gen(), )); - let clocks = wasi_common::WasiClocks::new(); + let clocks = webrogue_wasi_common::WasiClocks::new(); let sched = Box::new(Sched {}); - let table = wasi_common::Table::new(); - wasi_common::WasiCtx::new(random, clocks, sched, table) + let table = webrogue_wasi_common::Table::new(); + webrogue_wasi_common::WasiCtx::new(random, clocks, sched, table) }; let app_dir = fs::Dir::root(handle); @@ -46,10 +46,10 @@ pub fn make_ctx( if !real_path.is_dir() { std::fs::create_dir_all(&real_path)?; } - let home_dir = wasi_common::sync::dir::Dir::from_cap_std( - wasi_common::sync::Dir::open_ambient_dir( + let home_dir = webrogue_wasi_common::sync::dir::Dir::from_cap_std( + webrogue_wasi_common::sync::Dir::open_ambient_dir( real_path, - wasi_common::sync::ambient_authority(), + webrogue_wasi_common::sync::ambient_authority(), )?, ); wasi_ctx.push_preopened_dir(Box::new(home_dir), &persistent.mapped_path)?; @@ -77,17 +77,20 @@ pub fn make_ctx( struct Sched {} #[async_trait::async_trait] -impl wasi_common::WasiSched for Sched { +impl webrogue_wasi_common::WasiSched for Sched { async fn poll_oneoff<'a>( &self, - poll: &mut wasi_common::Poll<'a>, - ) -> Result<(), wasi_common::Error> { + poll: &mut webrogue_wasi_common::Poll<'a>, + ) -> Result<(), webrogue_wasi_common::Error> { todo!() } - async fn sched_yield(&self) -> Result<(), wasi_common::Error> { + async fn sched_yield(&self) -> Result<(), webrogue_wasi_common::Error> { todo!() } - async fn sleep(&self, duration: std::time::Duration) -> Result<(), wasi_common::Error> { + async fn sleep( + &self, + duration: std::time::Duration, + ) -> Result<(), webrogue_wasi_common::Error> { todo!() } } diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 0dcb0ed6..9757f000 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -18,7 +18,6 @@ anyhow = { workspace = true } webrogue-wrapp = { workspace = true } webrogue-wasip1 = { workspace = true } wasmtime = { workspace = true, default-features = false, features = ["runtime", "threads", "gc", "gc-drc", "anyhow"] } -wasi-common = { workspace = true, default-features = false, features = ["sync"] } +webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } wiggle = { workspace = true, default-features = false, features = ["wasmtime"] } webrogue-aot-data = { workspace = true, optional = true } -wasmtime-wasi-threads = { workspace = true } diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index 58f19803..37d8d9e5 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -395,7 +395,7 @@ mod bindings { }); wiggle::wasmtime_integration!({ - target: wasi_common::snapshots::preview_1, + target: webrogue_wasi_common::snapshots::preview_1, witx: ["../../external/wasmtime/crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx"], block_on: * }); diff --git a/crates/wasmtime/src/state.rs b/crates/wasmtime/src/state.rs index f8cfdf15..bf4a53f6 100644 --- a/crates/wasmtime/src/state.rs +++ b/crates/wasmtime/src/state.rs @@ -1,7 +1,7 @@ use std::sync::Arc; pub struct State { - pub preview1_ctx: Option, + pub preview1_ctx: Option, pub wasi_threads_ctx: Option>>, pub gfx: Option>, } diff --git a/web/Cargo.toml b/web/Cargo.toml index 71f7b090..5cd95ecd 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -41,7 +41,7 @@ webrogue-wasip1 = { workspace = true } webrogue-wrapp = { workspace = true } webrogue-gfx-winit = { workspace = true } wiggle = { workspace = true, default-features = false } -wasi-common = { workspace = true, default-features = false } +webrogue-wasi-common = { workspace = true, default-features = false } wasmparser = { workspace = true } # To enble "js" feature getrandom = { version = "0.2", features = ["js"] } From 1191142a4cefb56c0cc001667d76e4f871582e7b Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Tue, 12 May 2026 12:18:54 -0400 Subject: [PATCH 2/9] Build fixes --- crates/wasip1/src/lib.rs | 29 +++++++++++++++++++++++++---- web/src/bindings.rs | 2 +- web/src/main_thread.rs | 35 ++++++++++++++++++++++++++--------- web/src/run.rs | 2 +- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/crates/wasip1/src/lib.rs b/crates/wasip1/src/lib.rs index 8b9740cb..6c15e642 100644 --- a/crates/wasip1/src/lib.rs +++ b/crates/wasip1/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(target_arch = "wasm32", feature(stdarch_wasm_atomic_wait))] mod fs; mod stdout; @@ -16,9 +17,9 @@ pub fn make_ctx( }; #[cfg(target_arch = "wasm32")] let mut wasi_ctx = { - let random = Box::new(cap_rand::rngs::StdRng::from_seed( - cap_rand::thread_rng(cap_rand::ambient_authority()).r#gen(), - )); + use rand::SeedableRng as _; + + let random = Box::new(rand::rngs::StdRng::from_seed(rand::random())); let clocks = webrogue_wasi_common::WasiClocks::new(); let sched = Box::new(Sched {}); let table = webrogue_wasi_common::Table::new(); @@ -74,8 +75,10 @@ pub fn make_ctx( Ok(wasi_ctx) } +#[cfg(target_arch = "wasm32")] struct Sched {} +#[cfg(target_arch = "wasm32")] #[async_trait::async_trait] impl webrogue_wasi_common::WasiSched for Sched { async fn poll_oneoff<'a>( @@ -91,6 +94,24 @@ impl webrogue_wasi_common::WasiSched for Sched { &self, duration: std::time::Duration, ) -> Result<(), webrogue_wasi_common::Error> { - todo!() + blocking_sleep(duration.as_millis() as i64); + Ok(()) + } +} + +#[cfg(target_arch = "wasm32")] +pub fn blocking_sleep(millis: i64) { + // We create a dummy variable to "wait" on + let mut dummy: i32 = 0; + let ptr = &mut dummy as *mut i32; + + // Convert milliseconds to nanoseconds for the WASM intrinsic + let timeout_ns = millis * 1_000_000; + + unsafe { + // 0: The pointer to wait on + // 0: The "expected" value (it matches, so it will sleep) + // timeout_ns: How long to sleep before timing out + core::arch::wasm32::memory_atomic_wait32(ptr, 0, timeout_ns); } } diff --git a/web/src/bindings.rs b/web/src/bindings.rs index df6da01a..6d5e1add 100644 --- a/web/src/bindings.rs +++ b/web/src/bindings.rs @@ -19,7 +19,7 @@ wiggle::web_integration!({ }); wiggle::web_integration!({ - target: wasi_common::snapshots::preview_1, + target: webrogue_wasi_common::snapshots::preview_1, witx: ["../external/wasmtime/crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx"], block_on: * }); diff --git a/web/src/main_thread.rs b/web/src/main_thread.rs index 74865f83..aa991af6 100644 --- a/web/src/main_thread.rs +++ b/web/src/main_thread.rs @@ -56,15 +56,32 @@ pub async fn start(wasm_bindgen_shim_url: String, wrapp_url: String) { .dyn_into::() .unwrap(); - let writable = JsFuture::from(file_handle.create_writable_with_options(&{ - let options = web_sys::FileSystemCreateWritableOptions::new(); - options.set_keep_existing_data(true); - options - })) - .await - .unwrap() - .dyn_into::() - .unwrap(); + let writable = { + let mut tries = 10; + loop { + tries -= 1; + let writable = JsFuture::from(file_handle.create_writable_with_options(&{ + let options = web_sys::FileSystemCreateWritableOptions::new(); + options.set_keep_existing_data(true); + options + })) + .await; + if let Ok(writable) = writable { + break writable.dyn_into::().unwrap(); + } + if tries <= 0 { + unreachable!("Failed to execute 'createWritable'") + } + let promise = web_sys::js_sys::Promise::new(&mut |resolve, _| { + web_sys::window() + .unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, 200) + .unwrap(); + }); + + let _ = JsFuture::from(promise).await; + } + }; JsFuture::from(response.body().unwrap().pipe_to(&writable)) .await diff --git a/web/src/run.rs b/web/src/run.rs index 99da2166..1a711d5e 100644 --- a/web/src/run.rs +++ b/web/src/run.rs @@ -172,6 +172,6 @@ pub fn main(builder: ProxiedWinitBuilder) { pub struct Context { pub gfx: Rc>>, - pub wasip1: Rc>, + pub wasip1: Rc>, pub wasi_threads: Rc>>, } From 88e6de05ef98649053b1ec7146ae558b63536b2a Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Tue, 12 May 2026 13:21:32 -0400 Subject: [PATCH 3/9] Pull upsteam Wasmtime --- Cargo.lock | 132 +++++++++++++----------- crates/debugger/src/code_runner_loop.rs | 3 +- crates/wasip1/src/fs/dev/wakeup.rs | 4 +- crates/wasmtime/Cargo.toml | 2 +- crates/wasmtime/src/runtime.rs | 7 +- crates/wasmtime/src/thread.rs | 16 ++- external/wasmtime | 2 +- 7 files changed, 93 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1ceb36c..eb552801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,21 +1258,21 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -1280,7 +1280,7 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.132.0" +version = "0.133.0" dependencies = [ "serde", "serde_derive", @@ -1289,7 +1289,7 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.132.0" +version = "0.133.0" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -1315,7 +1315,7 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -1326,18 +1326,18 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.132.0" +version = "0.133.0" [[package]] name = "cranelift-control" -version = "0.132.0" +version = "0.133.0" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-bitset", "serde", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-codegen", "log", @@ -1357,11 +1357,11 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.132.0" +version = "0.133.0" [[package]] name = "cranelift-native" -version = "0.132.0" +version = "0.133.0" dependencies = [ "cranelift-codegen", "libc", @@ -1370,7 +1370,7 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.132.0" +version = "0.133.0" [[package]] name = "crc" @@ -2914,6 +2914,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" dependencies = [ "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] @@ -3730,12 +3732,9 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "mach2" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "malloc_buf" @@ -5020,7 +5019,7 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cranelift-bitset", "log", @@ -5030,7 +5029,7 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "45.0.0" +version = "46.0.0" dependencies = [ "proc-macro2", "quote", @@ -7389,12 +7388,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.246.2" +version = "0.248.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" +checksum = "ac92cf547bc18d27ecc521015c08c353b4f18b84ab388bb6d1b6b682c620d9b6" dependencies = [ "leb128fmt", - "wasmparser 0.246.2", + "wasmparser 0.248.0", ] [[package]] @@ -7446,20 +7445,33 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmparser" +version = "0.248.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4439c5eee9df71ee0c6efb37f63b1fcb1fec38f85f5142c54e7ed05d33091a" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.17.0", + "indexmap", + "semver", + "serde", +] + [[package]] name = "wasmprinter" -version = "0.246.2" +version = "0.248.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e41f7493ba994b8a779430a4c25ff550fd5a40d291693af43a6ef48688f00e3" +checksum = "30b264a5410b008d4d199a92bf536eae703cbd614482fc1ec53831cf19e1c183" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.246.2", + "wasmparser 0.248.0", ] [[package]] name = "wasmtime" -version = "45.0.0" +version = "46.0.0" dependencies = [ "addr2line", "async-trait", @@ -7484,7 +7496,7 @@ dependencies = [ "serde_derive", "smallvec", "target-lexicon 0.13.5", - "wasmparser 0.246.2", + "wasmparser 0.248.0", "wasmtime-environ", "wasmtime-internal-cache", "wasmtime-internal-component-macro", @@ -7502,7 +7514,7 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "45.0.0" +version = "46.0.0" dependencies = [ "anyhow", "cpp_demangle", @@ -7522,8 +7534,8 @@ dependencies = [ "sha2", "smallvec", "target-lexicon 0.13.5", - "wasm-encoder 0.246.2", - "wasmparser 0.246.2", + "wasm-encoder 0.248.0", + "wasmparser 0.248.0", "wasmprinter", "wasmtime-internal-component-util", "wasmtime-internal-core", @@ -7531,7 +7543,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-cache" -version = "45.0.0" +version = "46.0.0" dependencies = [ "base64", "directories-next", @@ -7549,7 +7561,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "45.0.0" +version = "46.0.0" dependencies = [ "anyhow", "proc-macro2", @@ -7557,16 +7569,16 @@ dependencies = [ "syn 2.0.117", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", - "wit-parser 0.246.2", + "wit-parser 0.248.0", ] [[package]] name = "wasmtime-internal-component-util" -version = "45.0.0" +version = "46.0.0" [[package]] name = "wasmtime-internal-core" -version = "45.0.0" +version = "46.0.0" dependencies = [ "anyhow", "hashbrown 0.17.0", @@ -7576,7 +7588,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-cranelift" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cfg-if", "cranelift-codegen", @@ -7592,7 +7604,7 @@ dependencies = [ "smallvec", "target-lexicon 0.13.5", "thiserror 2.0.18", - "wasmparser 0.246.2", + "wasmparser 0.248.0", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-unwinder", @@ -7601,7 +7613,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-debugger" -version = "45.0.0" +version = "46.0.0" dependencies = [ "async-trait", "log", @@ -7613,7 +7625,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cc", "cfg-if", @@ -7626,7 +7638,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -7634,7 +7646,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cfg-if", "libc", @@ -7644,7 +7656,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cfg-if", "cranelift-codegen", @@ -7655,7 +7667,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "45.0.0" +version = "46.0.0" dependencies = [ "proc-macro2", "quote", @@ -7664,14 +7676,14 @@ dependencies = [ [[package]] name = "wasmtime-internal-winch" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cranelift-codegen", "gimli", "log", "object 0.39.1", "target-lexicon 0.13.5", - "wasmparser 0.246.2", + "wasmparser 0.248.0", "wasmtime-environ", "wasmtime-internal-cranelift", "winch-codegen", @@ -7679,18 +7691,18 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "45.0.0" +version = "46.0.0" dependencies = [ "anyhow", "bitflags 2.11.0", "heck 0.5.0", "indexmap", - "wit-parser 0.246.2", + "wit-parser 0.248.0", ] [[package]] name = "wasmtime-wasi" -version = "45.0.0" +version = "46.0.0" dependencies = [ "async-trait", "bitflags 2.11.0", @@ -7699,13 +7711,13 @@ dependencies = [ "cap-net-ext", "cap-std", "cap-time-ext", + "cfg-if", "fs-set-times", "futures", "io-extras", "io-lifetimes", "rand 0.10.1", "rustix 1.1.4", - "system-interface", "thiserror 2.0.18", "tokio", "tracing", @@ -7717,7 +7729,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-io" -version = "45.0.0" +version = "46.0.0" dependencies = [ "async-trait", "bytes", @@ -8701,7 +8713,7 @@ dependencies = [ [[package]] name = "wiggle" -version = "45.0.0" +version = "46.0.0" dependencies = [ "bitflags 2.11.0", "thiserror 2.0.18", @@ -8714,7 +8726,7 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "45.0.0" +version = "46.0.0" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -8726,7 +8738,7 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "45.0.0" +version = "46.0.0" dependencies = [ "proc-macro2", "quote", @@ -8767,7 +8779,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "45.0.0" +version = "46.0.0" dependencies = [ "cranelift-assembler-x64", "cranelift-codegen", @@ -8776,7 +8788,7 @@ dependencies = [ "smallvec", "target-lexicon 0.13.5", "thiserror 2.0.18", - "wasmparser 0.246.2", + "wasmparser 0.248.0", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-cranelift", @@ -9633,12 +9645,12 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.246.2" +version = "0.248.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd979042b5ff288607ccf3b314145435453f20fc67173195f91062d2289b204d" +checksum = "247ad505da2915a082fe13204c5ba8788425aea1de54f43b284818cf82637856" dependencies = [ "anyhow", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "id-arena", "indexmap", "log", @@ -9647,7 +9659,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.246.2", + "wasmparser 0.248.0", ] [[package]] diff --git a/crates/debugger/src/code_runner_loop.rs b/crates/debugger/src/code_runner_loop.rs index 3ce17a65..f4fe2a30 100644 --- a/crates/debugger/src/code_runner_loop.rs +++ b/crates/debugger/src/code_runner_loop.rs @@ -46,8 +46,7 @@ pub fn runner( match run_result { wasmtime_internal_debugger::DebugRunResult::Finished => break 'exec_loop, wasmtime_internal_debugger::DebugRunResult::HostcallError - | wasmtime_internal_debugger::DebugRunResult::CaughtExceptionThrown(_) - | wasmtime_internal_debugger::DebugRunResult::UncaughtExceptionThrown(_) + | wasmtime_internal_debugger::DebugRunResult::Exception(_) | wasmtime_internal_debugger::DebugRunResult::Trap(_) => { must_break = true; } diff --git a/crates/wasip1/src/fs/dev/wakeup.rs b/crates/wasip1/src/fs/dev/wakeup.rs index 07ca6fa4..9785e8a8 100644 --- a/crates/wasip1/src/fs/dev/wakeup.rs +++ b/crates/wasip1/src/fs/dev/wakeup.rs @@ -14,7 +14,7 @@ mod unix { impl File { pub fn new() -> Result { // let (read_fd, write_fd) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::NONBLOCK | rustix::pipe::PipeFlags::CLOEXEC) - // .map_err(|_err| wasi_common::Error::not_supported())?; + // .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; let (read_fd, write_fd) = rustix::pipe::pipe() .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; for fd in [read_fd.as_fd(), read_fd.as_fd()] { @@ -81,7 +81,7 @@ mod windows { } impl File { - pub fn new() -> Result { + pub fn new() -> Result { let handle = unsafe { CreateEventA(null(), 1, 0, null()) }; assert!(handle != null_mut()); Ok(Self { diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 9757f000..19d4129b 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -18,6 +18,6 @@ anyhow = { workspace = true } webrogue-wrapp = { workspace = true } webrogue-wasip1 = { workspace = true } wasmtime = { workspace = true, default-features = false, features = ["runtime", "threads", "gc", "gc-drc", "anyhow"] } -webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } +webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync", "wasmtime"] } wiggle = { workspace = true, default-features = false, features = ["wasmtime"] } webrogue-aot-data = { workspace = true, optional = true } diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index 37d8d9e5..459794b4 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -95,6 +95,7 @@ impl Runtime { use std::io::Read as _; use anyhow::Context; + use wasmtime::Inlining; self.wasmtime_config .wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); @@ -119,21 +120,21 @@ impl Runtime { .cranelift_opt_level(wasmtime::OptLevel::None) .guest_debug(true) .cranelift_regalloc_algorithm(wasmtime::RegallocAlgorithm::SinglePass) - .compiler_inlining(false); + .compiler_inlining(Inlining::No); } JitProfile::FastExecution => { self.wasmtime_config .cranelift_opt_level(wasmtime::OptLevel::Speed) .guest_debug(false) .cranelift_regalloc_algorithm(wasmtime::RegallocAlgorithm::Backtracking) - .compiler_inlining(true); + .compiler_inlining(Inlining::Intrinsics); } JitProfile::FastCompilation => { self.wasmtime_config .cranelift_opt_level(wasmtime::OptLevel::Speed) .guest_debug(false) .cranelift_regalloc_algorithm(wasmtime::RegallocAlgorithm::SinglePass) - .compiler_inlining(false); + .compiler_inlining(Inlining::Intrinsics); } }; diff --git a/crates/wasmtime/src/thread.rs b/crates/wasmtime/src/thread.rs index 4a95916c..bb8fb758 100644 --- a/crates/wasmtime/src/thread.rs +++ b/crates/wasmtime/src/thread.rs @@ -16,6 +16,7 @@ struct WasmThreadInner { engine: EngineWeak, tid: NonZeroI32, trap_on_epoch_deadline: AtomicBool, + #[cfg(feature = "async")] is_async: bool, epoch_interruption: bool, } @@ -29,18 +30,20 @@ impl WasmThread { { return Ok(UpdateDeadline::Interrupt); }; + #[cfg(feature = "async")] if self.0.is_async { - Ok(UpdateDeadline::Yield(1)) - } else { - panic!("Epoch deadline callback executed, but neither runtime is async not stopping threads."); - // maybe Ok(UpdateDeadline::Continue(1)) will be fine? + return Ok(UpdateDeadline::Yield(1)); } + panic!( + "Epoch deadline callback executed, but neither runtime is async not stopping threads." + ); } pub fn tid(&self) -> NonZeroI32 { self.0.tid } + #[cfg(feature = "async")] pub fn async_yield(&self) { assert!(self.0.is_async); assert!(self.0.epoch_interruption); @@ -65,9 +68,12 @@ pub struct WasmThreadRegistry(Arc>); impl WasmThreadRegistry { pub fn new(is_async: bool, epoch_interruption: bool) -> Self { + #[cfg(not(feature = "async"))] + debug_assert!(!is_async); Self(Arc::new(Mutex::new(WasmThreadRegistryInner { threads: BTreeMap::new(), tid_counter: AtomicI32::new(1), + #[cfg(feature = "async")] is_async, stop_reason: None, epoch_interruption, @@ -94,6 +100,7 @@ impl WasmThreadRegistry { engine, tid, trap_on_epoch_deadline: AtomicBool::new(false), + #[cfg(feature = "async")] is_async: registry.is_async, epoch_interruption: registry.epoch_interruption, })); @@ -146,6 +153,7 @@ impl WasmThreadRegistry { struct WasmThreadRegistryInner { threads: BTreeMap, tid_counter: AtomicI32, + #[cfg(feature = "async")] is_async: bool, stop_reason: Option, epoch_interruption: bool, diff --git a/external/wasmtime b/external/wasmtime index 919db44e..482ed666 160000 --- a/external/wasmtime +++ b/external/wasmtime @@ -1 +1 @@ -Subproject commit 919db44e2d4e35673762acef5e23da4dcd268242 +Subproject commit 482ed666733b9daefa1d8552eb8e51d2a36ed239 From 22b949eef4aa7a0a12eca4fbdd9eee4f93a4a3ab Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Tue, 12 May 2026 13:30:16 -0400 Subject: [PATCH 4/9] Exclude wasi-common from typos check --- typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/typos.toml b/typos.toml index 51e8cbc3..301bcc10 100644 --- a/typos.toml +++ b/typos.toml @@ -3,6 +3,7 @@ extend-exclude = [ "/webrogue-sdk", "/external", "*.storyboard", + "/crates/wasi-common", ] ignore-vcs = true From fd6b4e1a74d49e44baeac418efeb5047e1d47515 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Tue, 12 May 2026 15:59:55 -0400 Subject: [PATCH 5/9] use tokio for wasip1 --- Cargo.lock | 3 + Cargo.toml | 2 +- crates/wasi-common/src/tokio/dir.rs | 97 +------------------ crates/wasi-common/src/tokio/sched.rs | 2 +- crates/wasip1/Cargo.toml | 5 +- crates/wasip1/src/lib.rs | 40 +++++++- crates/wasmtime/src/runtime.rs | 2 +- crates/wasmtime/src/thread.rs | 2 +- crates/wasmtime/src/wasi_threads.rs | 129 +++++++++++++------------- webrogue-sdk | 2 +- 10 files changed, 118 insertions(+), 166 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb552801..741b071d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8423,9 +8423,12 @@ dependencies = [ "async-trait", "getrandom 0.4.2", "io-extras", + "lazy_static", "rand 0.10.1", "rand_core 0.10.0", "rustix 1.1.4", + "tokio", + "wasmtime-internal-core", "webrogue-wasi-common", "webrogue-wrapp", "wiggle", diff --git a/Cargo.toml b/Cargo.toml index a1456b43..23c9ce6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,7 +141,7 @@ dirs = { version = "6.0.0" } zip = { version = "8.5", default-features = false } libc = { version = "0.2.112", default-features = false } lazy_static = { version = "1.5.0" } -rustix = { version = "1.0.8", default-features = false } +rustix = { version = "1", default-features = false } cfg_aliases = { version = "0.2.1" } windows-sys = { version = "0.61.2", default-features = false } windows = { version = "0.62.2", default-features = false } diff --git a/crates/wasi-common/src/tokio/dir.rs b/crates/wasi-common/src/tokio/dir.rs index 74b18e33..aadc8de8 100644 --- a/crates/wasi-common/src/tokio/dir.rs +++ b/crates/wasi-common/src/tokio/dir.rs @@ -1,8 +1,8 @@ use crate::tokio::{block_on_dummy_executor, file::File}; use crate::{ - Error, ErrorExt, dir::{ReaddirCursor, ReaddirEntity, WasiDir}, file::{FdFlags, Filestat, OFlags}, + Error, ErrorExt, }; use std::any::Any; use std::path::PathBuf; @@ -123,98 +123,3 @@ impl WasiDir for Dir { block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks)) } } - -#[cfg(test)] -mod test { - use super::Dir; - use crate::file::{FdFlags, OFlags}; - use cap_std::ambient_authority; - - #[tokio::test(flavor = "multi_thread")] - async fn scratch_dir() { - let tempdir = tempfile::Builder::new() - .prefix("cap-std-sync") - .tempdir() - .expect("create temporary dir"); - let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority()) - .expect("open ambient temporary dir"); - let preopen_dir = Dir::from_cap_std(preopen_dir); - crate::WasiDir::open_file( - &preopen_dir, - false, - ".", - OFlags::empty(), - false, - false, - FdFlags::empty(), - ) - .await - .expect("open the same directory via WasiDir abstraction"); - } - - // Readdir does not work on windows, so we won't test it there. - #[cfg(not(windows))] - #[tokio::test(flavor = "multi_thread")] - async fn readdir() { - use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir}; - use crate::file::{FdFlags, FileType, OFlags}; - use std::collections::HashMap; - - async fn readdir_into_map(dir: &dyn WasiDir) -> HashMap { - let mut out = HashMap::new(); - for readdir_result in dir - .readdir(ReaddirCursor::from(0)) - .await - .expect("readdir succeeds") - { - let entity = readdir_result.expect("readdir entry is valid"); - out.insert(entity.name.clone(), entity); - } - out - } - - let tempdir = tempfile::Builder::new() - .prefix("cap-std-sync") - .tempdir() - .expect("create temporary dir"); - let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority()) - .expect("open ambient temporary dir"); - let preopen_dir = Dir::from_cap_std(preopen_dir); - - let entities = readdir_into_map(&preopen_dir).await; - assert_eq!( - entities.len(), - 2, - "should just be . and .. in empty dir: {entities:?}" - ); - assert!(entities.get(".").is_some()); - assert!(entities.get("..").is_some()); - - preopen_dir - .open_file( - false, - "file1", - OFlags::CREATE, - true, - false, - FdFlags::empty(), - ) - .await - .expect("create file1"); - - let entities = readdir_into_map(&preopen_dir).await; - assert_eq!(entities.len(), 3, "should be ., .., file1 {entities:?}"); - assert_eq!( - entities.get(".").expect(". entry").filetype, - FileType::Directory - ); - assert_eq!( - entities.get("..").expect(".. entry").filetype, - FileType::Directory - ); - assert_eq!( - entities.get("file1").expect("file1 entry").filetype, - FileType::RegularFile - ); - } -} diff --git a/crates/wasi-common/src/tokio/sched.rs b/crates/wasi-common/src/tokio/sched.rs index 41a77b4d..7d83fff3 100644 --- a/crates/wasi-common/src/tokio/sched.rs +++ b/crates/wasi-common/src/tokio/sched.rs @@ -9,8 +9,8 @@ mod windows; pub use windows::poll_oneoff; use crate::{ - Error, sched::{Duration, Poll, WasiSched}, + Error, }; pub fn sched_ctx() -> Box { diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 20d3646a..424bd981 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -10,12 +10,15 @@ edition.workspace = true [dependencies] anyhow = { workspace = true } webrogue-wrapp = { workspace = true } +wasmtime-internal-core = { workspace = true } webrogue-wasi-common = { workspace = true, default-features = false, features = [] } wiggle = { workspace = true, default-features = false } async-trait = { workspace = true } rand_core = { workspace = true } rand = { workspace = true, default-features = false, features = ["sys_rng", "thread_rng"] } getrandom = { workspace = true, default-features = false, features = ["wasm_js"] } +tokio = { workspace = true, default-features = false } +lazy_static = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["pipe"] } @@ -25,7 +28,7 @@ io-extras = { workspace = true } windows-sys = { workspace = true, features = ["Win32_System_Threading", "Win32_Security"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } +webrogue-wasi-common = { workspace = true, default-features = false, features = ["tokio"] } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/wasip1/src/lib.rs b/crates/wasip1/src/lib.rs index 6c15e642..0ea613ce 100644 --- a/crates/wasip1/src/lib.rs +++ b/crates/wasip1/src/lib.rs @@ -9,7 +9,7 @@ pub fn make_ctx( ) -> anyhow::Result { #[cfg(not(target_arch = "wasm32"))] let mut wasi_ctx = { - let mut builder = webrogue_wasi_common::sync::WasiCtxBuilder::new(); + let mut builder = webrogue_wasi_common::tokio::WasiCtxBuilder::new(); // builder.inherit_stdio(); // builder.stdout(Box::new(stdout::STDOutFile {})); // builder.stderr(Box::new(stdout::STDOutFile {})); @@ -115,3 +115,41 @@ pub fn blocking_sleep(millis: i64) { core::arch::wasm32::memory_atomic_wait32(ptr, 0, timeout_ns); } } + +pub fn run_in_runtime(f: impl FnOnce() -> Output) -> Output { + if tokio::runtime::Handle::try_current().is_ok() { + return f(); + } + tokio::runtime::Builder::new_current_thread() + .enable_time() + .enable_io() + .build() + .unwrap() + .block_on(async { return f() }) +} + +fn make_runtime() -> tokio::runtime::Runtime { + let (tx, rx) = std::sync::mpsc::channel(); + + std::thread::Builder::new() + .name("wasi-io".to_owned()) + .spawn(move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + tx.send(runtime).unwrap(); + }) + .unwrap(); + rx.recv().unwrap() +} + +lazy_static::lazy_static! { + static ref RUNTIME: tokio::runtime::Runtime = make_runtime(); +} + +pub fn run_in_executor( + future: F, +) -> wasmtime_internal_core::error::Result { + Ok(RUNTIME.block_on(future)) +} diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index 459794b4..98bb873b 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -398,6 +398,6 @@ mod bindings { wiggle::wasmtime_integration!({ target: webrogue_wasi_common::snapshots::preview_1, witx: ["../../external/wasmtime/crates/wasi-common/witx/preview1/wasi_snapshot_preview1.witx"], - block_on: * + block_on [webrogue_wasip1::run_in_executor]: * }); } diff --git a/crates/wasmtime/src/thread.rs b/crates/wasmtime/src/thread.rs index bb8fb758..3f3d7c43 100644 --- a/crates/wasmtime/src/thread.rs +++ b/crates/wasmtime/src/thread.rs @@ -119,7 +119,7 @@ impl WasmThreadRegistry { match reason { StopReason::MainFinished => std::process::exit(0), StopReason::ThreadError(tid, error) => { - panic!("An error occurred in WASI thread #{tid}: {error}") + panic!("An error occurred in WASI thread #{tid}: {:#}", error) } } } else { diff --git a/crates/wasmtime/src/wasi_threads.rs b/crates/wasmtime/src/wasi_threads.rs index 1d8136da..b4739edc 100644 --- a/crates/wasmtime/src/wasi_threads.rs +++ b/crates/wasmtime/src/wasi_threads.rs @@ -67,73 +67,76 @@ impl WasiThreadsCtx { let _ = std::thread::Builder::new() .name(thread_name.clone()) .spawn(move || { - let mut store = wasmtime::Store::new(instance_pre.module().engine(), host); - let thread = thread_registry.make_thread(store.engine().weak()); - { - let thread = thread.clone(); - store.epoch_deadline_callback(move |_| thread.on_epoch_update_deadline()); - store.set_epoch_deadline(1); - } + webrogue_wasip1::run_in_runtime(move || { + let mut store = wasmtime::Store::new(instance_pre.module().engine(), host); + let thread = thread_registry.make_thread(store.engine().weak()); + { + let thread = thread.clone(); + store.epoch_deadline_callback(move |_| thread.on_epoch_update_deadline()); + store.set_epoch_deadline(1); + } - let result: Result, Box> = - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - #[cfg(feature = "async")] - if let Some(async_func_runner) = async_func_runner { - return async_func_runner( - AsyncFuncRunnerParams { - store, - thread: thread.clone(), - }, - Box::new(move |mut store| { - Box::pin(async move { - let instance = instance_pre - .instantiate_async(&mut store) - .await - .unwrap(); - let thread_entry_point = instance - .get_typed_func::<(i32, i32), ()>( - &mut store, - WASI_ENTRY_POINT, - ) - .unwrap(); - thread_entry_point - .call_async( - &mut store, - (wasi_thread_id, thread_start_arg), - ) - .await?; - Ok(()) - }) - }), - ) - .map(|_| ()); + let result: Result, Box> = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + #[cfg(feature = "async")] + if let Some(async_func_runner) = async_func_runner { + return async_func_runner( + AsyncFuncRunnerParams { + store, + thread: thread.clone(), + }, + Box::new(move |mut store| { + Box::pin(async move { + let instance = instance_pre + .instantiate_async(&mut store) + .await + .unwrap(); + let thread_entry_point = instance + .get_typed_func::<(i32, i32), ()>( + &mut store, + WASI_ENTRY_POINT, + ) + .unwrap(); + thread_entry_point + .call_async( + &mut store, + (wasi_thread_id, thread_start_arg), + ) + .await?; + Ok(()) + }) + }), + ) + .map(|_| ()); + } + let instance = instance_pre.instantiate(&mut store)?; + let thread_entry_point = instance + .get_typed_func::<(i32, i32), ()>(&mut store, WASI_ENTRY_POINT) + .unwrap(); + thread_entry_point + .call(&mut store, (wasi_thread_id, thread_start_arg)) + .map_err(|err| anyhow::anyhow!(err)) + })); + + let tid = thread.tid(); + + thread_registry.remove_thread(thread); + + match result { + Err(e) => { + thread_registry.stop_all_threads(StopReason::ThreadError( + tid, + anyhow::anyhow!("{thread_name} panicked: {e:?}"), + )); } - let instance = instance_pre.instantiate(&mut store)?; - let thread_entry_point = instance - .get_typed_func::<(i32, i32), ()>(&mut store, WASI_ENTRY_POINT) - .unwrap(); - thread_entry_point - .call(&mut store, (wasi_thread_id, thread_start_arg)) - .map_err(|err| anyhow::anyhow!(err)) - })); - - let tid = thread.tid(); - - thread_registry.remove_thread(thread); - - match result { - Err(e) => { - thread_registry.stop_all_threads(StopReason::ThreadError( - tid, - anyhow::anyhow!("{thread_name} panicked: {e:?}"), - )); - } - Ok(result) => { - if let Err(error) = result { - thread_registry.stop_all_threads(StopReason::ThreadError(tid, error)); + Ok(result) => { + if let Err(error) = result { + thread_registry + .stop_all_threads(StopReason::ThreadError(tid, error)); + } } } - } + }) }); Ok(wasi_thread_id) diff --git a/webrogue-sdk b/webrogue-sdk index fb41bd2f..1318fe11 160000 --- a/webrogue-sdk +++ b/webrogue-sdk @@ -1 +1 @@ -Subproject commit fb41bd2f6d22d804f3d0a0e5552204e9a03b5095 +Subproject commit 1318fe11de75fed545bece596dc325201c3f4e57 From 945521078a4df9353b8a9b7e39422cd513ec1238 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Tue, 12 May 2026 17:20:28 -0400 Subject: [PATCH 6/9] fix --- crates/wasi-common/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index 06891bf0..f9e4bf05 100644 --- a/crates/wasi-common/Cargo.toml +++ b/crates/wasi-common/Cargo.toml @@ -22,7 +22,7 @@ rand = { workspace = true, features = ['std_rng', 'thread_rng'] } async-trait = { workspace = true } # Optional, enabled by wasmtime feature: -wasmtime = { workspace = true, optional = true, features = ['runtime', 'component-model'] } +wasmtime = { workspace = true, optional = true, features = ['runtime', 'component-model', 'std'] } # Optional, enabled by sync feature: cap-fs-ext = { workspace = true, optional = true } cap-time-ext = { workspace = true, optional = true } From 208d74931fee73b7aba9689f57f017c01e4802c9 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Tue, 12 May 2026 17:51:54 -0400 Subject: [PATCH 7/9] fix --- crates/wasip1/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 424bd981..4005cf9b 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -21,7 +21,7 @@ tokio = { workspace = true, default-features = false } lazy_static = { workspace = true } [target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["pipe"] } +rustix = { workspace = true, features = ["pipe", "std"] } [target.'cfg(windows)'.dependencies] io-extras = { workspace = true } From cddf6075862618abeac011358be12061af2354df Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Wed, 13 May 2026 10:37:57 -0400 Subject: [PATCH 8/9] Fix --- crates/wasip1/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 4005cf9b..056f8151 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -19,9 +19,10 @@ rand = { workspace = true, default-features = false, features = ["sys_rng", "thr getrandom = { workspace = true, default-features = false, features = ["wasm_js"] } tokio = { workspace = true, default-features = false } lazy_static = { workspace = true } +rustix = { workspace = true, features = ["std"] } [target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["pipe", "std"] } +rustix = { workspace = true, features = ["pipe"] } [target.'cfg(windows)'.dependencies] io-extras = { workspace = true } From 0bb23e5bb26146e65b715127991272fabe7ebb7c Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Wed, 13 May 2026 10:44:49 -0400 Subject: [PATCH 9/9] remove preview0 --- crates/wasi-common/src/lib.rs | 11 - crates/wasi-common/src/snapshots/mod.rs | 1 - crates/wasi-common/src/snapshots/preview_0.rs | 1082 ----------------- .../wasi-common/witx/preview0/typenames.witx | 746 ------------ .../witx/preview0/wasi_unstable.witx | 513 -------- 5 files changed, 2353 deletions(-) delete mode 100644 crates/wasi-common/src/snapshots/preview_0.rs delete mode 100644 crates/wasi-common/witx/preview0/typenames.witx delete mode 100644 crates/wasi-common/witx/preview0/wasi_unstable.witx diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index fa78a3b0..ee141e6f 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -118,13 +118,11 @@ macro_rules! define_wasi { get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, ) -> EnvResult<()> where U: Send - + crate::snapshots::preview_0::wasi_unstable::WasiUnstable + crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1, T: 'static, $($bounds)* { snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?; - snapshots::preview_0::add_wasi_unstable_to_linker(linker, get_cx)?; Ok(()) } @@ -138,15 +136,6 @@ macro_rules! define_wasi { $async_mode: * }); } - pub mod preview_0 { - wiggle::wasmtime_integration!({ - // The wiggle code to integrate with lives here: - target: crate::snapshots::preview_0, - witx: ["witx/preview0/wasi_unstable.witx"], - errors: { errno => trappable Error }, - $async_mode: * - }); - } } }} diff --git a/crates/wasi-common/src/snapshots/mod.rs b/crates/wasi-common/src/snapshots/mod.rs index a8d50a61..76e5fb08 100644 --- a/crates/wasi-common/src/snapshots/mod.rs +++ b/crates/wasi-common/src/snapshots/mod.rs @@ -20,5 +20,4 @@ //! `WasiCtx`. No further downcasting via the `as_any` escape hatch is //! permitted. -pub mod preview_0; pub mod preview_1; diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs deleted file mode 100644 index 3a63ab8a..00000000 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ /dev/null @@ -1,1082 +0,0 @@ -use crate::file::TableFileExt; -use crate::sched::{ - Poll, Userdata, - subscription::{RwEventFlags, SubscriptionResult}, -}; -use crate::snapshots::preview_1::types as snapshot1_types; -use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; -use crate::{EnvError, ErrorExt, WasiCtx}; -#[cfg(feature = "use_cap_std")] -use cap_std::time::Duration; -use std::collections::HashSet; -#[cfg(not(feature = "use_cap_std"))] -use std::time::Duration; -use wiggle::{GuestMemory, GuestPtr}; - -wiggle::from_witx!({ - witx: ["witx/preview0/wasi_unstable.witx"], - errors: { errno => trappable Error }, - async: *, - wasmtime: false, -}); - -use types::Error; - -impl ErrorExt for Error { - fn not_found() -> Self { - types::Errno::Noent.into() - } - fn too_big() -> Self { - types::Errno::TooBig.into() - } - fn badf() -> Self { - types::Errno::Badf.into() - } - fn exist() -> Self { - types::Errno::Exist.into() - } - fn illegal_byte_sequence() -> Self { - types::Errno::Ilseq.into() - } - fn invalid_argument() -> Self { - types::Errno::Inval.into() - } - fn io() -> Self { - types::Errno::Io.into() - } - fn name_too_long() -> Self { - types::Errno::Nametoolong.into() - } - fn not_dir() -> Self { - types::Errno::Notdir.into() - } - fn not_supported() -> Self { - types::Errno::Notsup.into() - } - fn overflow() -> Self { - types::Errno::Overflow.into() - } - fn range() -> Self { - types::Errno::Range.into() - } - fn seek_pipe() -> Self { - types::Errno::Spipe.into() - } - fn perm() -> Self { - types::Errno::Perm.into() - } -} - -impl wiggle::GuestErrorType for types::Errno { - fn success() -> Self { - Self::Success - } -} - -impl From for Error { - fn from(err: wiggle::GuestError) -> Error { - snapshot1_types::Error::from(err).into() - } -} - -impl From for Error { - fn from(error: snapshot1_types::Error) -> Error { - match error.downcast() { - Ok(errno) => Error::from(types::Errno::from(errno)), - Err(trap) => Error::trap(trap), - } - } -} - -impl From for Error { - fn from(_err: std::num::TryFromIntError) -> Error { - types::Errno::Overflow.into() - } -} - -// Type conversions -// The vast majority of the types defined in `types` and `snapshot1_types` are identical. However, -// since they are defined in separate places for mechanical (wiggle) reasons, we need to manually -// define conversion functions between them. -// Below we have defined these functions as they are needed. - -/// Fd is a newtype wrapper around u32. Unwrap and wrap it. -impl From for snapshot1_types::Fd { - fn from(fd: types::Fd) -> snapshot1_types::Fd { - u32::from(fd).into() - } -} -/// Fd is a newtype wrapper around u32. Unwrap and wrap it. -impl From for types::Fd { - fn from(fd: snapshot1_types::Fd) -> types::Fd { - u32::from(fd).into() - } -} - -/// Trivial conversion between two c-style enums that have the exact same set of variants. -/// Could we do something unsafe and not list all these variants out? Probably, but doing -/// it this way doesn't bother me much. I copy-pasted the list of variants out of the -/// rendered rustdocs. -/// LLVM ought to compile these From impls into no-ops, inshallah -macro_rules! convert_enum { - ($from:ty, $to:ty, $($var:ident),+) => { - impl From<$from> for $to { - fn from(e: $from) -> $to { - match e { - $( <$from>::$var => <$to>::$var, )+ - } - } - } - } -} -convert_enum!( - snapshot1_types::Errno, - types::Errno, - Success, - TooBig, - Acces, - Addrinuse, - Addrnotavail, - Afnosupport, - Again, - Already, - Badf, - Badmsg, - Busy, - Canceled, - Child, - Connaborted, - Connrefused, - Connreset, - Deadlk, - Destaddrreq, - Dom, - Dquot, - Exist, - Fault, - Fbig, - Hostunreach, - Idrm, - Ilseq, - Inprogress, - Intr, - Inval, - Io, - Isconn, - Isdir, - Loop, - Mfile, - Mlink, - Msgsize, - Multihop, - Nametoolong, - Netdown, - Netreset, - Netunreach, - Nfile, - Nobufs, - Nodev, - Noent, - Noexec, - Nolck, - Nolink, - Nomem, - Nomsg, - Noprotoopt, - Nospc, - Nosys, - Notconn, - Notdir, - Notempty, - Notrecoverable, - Notsock, - Notsup, - Notty, - Nxio, - Overflow, - Ownerdead, - Perm, - Pipe, - Proto, - Protonosupport, - Prototype, - Range, - Rofs, - Spipe, - Srch, - Stale, - Timedout, - Txtbsy, - Xdev, - Notcapable -); -convert_enum!( - types::Clockid, - snapshot1_types::Clockid, - Realtime, - Monotonic, - ProcessCputimeId, - ThreadCputimeId -); - -convert_enum!( - types::Advice, - snapshot1_types::Advice, - Normal, - Sequential, - Random, - Willneed, - Dontneed, - Noreuse -); -convert_enum!( - snapshot1_types::Filetype, - types::Filetype, - Directory, - BlockDevice, - CharacterDevice, - RegularFile, - SocketDgram, - SocketStream, - SymbolicLink, - Unknown -); -convert_enum!(types::Whence, snapshot1_types::Whence, Cur, End, Set); - -/// Prestat isn't a c-style enum, its a union where the variant has a payload. Its the only one of -/// those we need to convert, so write it by hand. -impl From for types::Prestat { - fn from(p: snapshot1_types::Prestat) -> types::Prestat { - match p { - snapshot1_types::Prestat::Dir(d) => types::Prestat::Dir(d.into()), - } - } -} - -/// Trivial conversion between two structs that have the exact same set of fields, -/// with recursive descent into the field types. -macro_rules! convert_struct { - ($from:ty, $to:path, $($field:ident),+) => { - impl From<$from> for $to { - fn from(e: $from) -> $to { - $to { - $( $field: e.$field.into(), )+ - } - } - } - } -} - -convert_struct!(snapshot1_types::PrestatDir, types::PrestatDir, pr_name_len); -convert_struct!( - snapshot1_types::Fdstat, - types::Fdstat, - fs_filetype, - fs_rights_base, - fs_rights_inheriting, - fs_flags -); - -/// Snapshot1 Filestat is incompatible with Snapshot0 Filestat - the nlink -/// field is u32 on this Filestat, and u64 on theirs. If you've got more than -/// 2^32 links I don't know what to tell you -impl From for types::Filestat { - fn from(f: snapshot1_types::Filestat) -> types::Filestat { - types::Filestat { - dev: f.dev, - ino: f.ino, - filetype: f.filetype.into(), - nlink: f.nlink.try_into().unwrap_or(u32::MAX), - size: f.size, - atim: f.atim, - mtim: f.mtim, - ctim: f.ctim, - } - } -} - -/// Trivial conversion between two bitflags that have the exact same set of flags. -macro_rules! convert_flags { - ($from:ty, $to:ty, $($flag:ident),+) => { - impl From<$from> for $to { - fn from(f: $from) -> $to { - let mut out = <$to>::empty(); - $( - if f.contains(<$from>::$flag) { - out |= <$to>::$flag; - } - )+ - out - } - } - } -} - -/// Need to convert in both directions? This saves listing out the flags twice -macro_rules! convert_flags_bidirectional { - ($from:ty, $to:ty, $($flag:tt)*) => { - convert_flags!($from, $to, $($flag)*); - convert_flags!($to, $from, $($flag)*); - } -} - -convert_flags_bidirectional!( - snapshot1_types::Fdflags, - types::Fdflags, - APPEND, - DSYNC, - NONBLOCK, - RSYNC, - SYNC -); -convert_flags!( - types::Lookupflags, - snapshot1_types::Lookupflags, - SYMLINK_FOLLOW -); -convert_flags!( - types::Fstflags, - snapshot1_types::Fstflags, - ATIM, - ATIM_NOW, - MTIM, - MTIM_NOW -); -convert_flags!( - types::Oflags, - snapshot1_types::Oflags, - CREAT, - DIRECTORY, - EXCL, - TRUNC -); -convert_flags_bidirectional!( - types::Rights, - snapshot1_types::Rights, - FD_DATASYNC, - FD_READ, - FD_SEEK, - FD_FDSTAT_SET_FLAGS, - FD_SYNC, - FD_TELL, - FD_WRITE, - FD_ADVISE, - FD_ALLOCATE, - PATH_CREATE_DIRECTORY, - PATH_CREATE_FILE, - PATH_LINK_SOURCE, - PATH_LINK_TARGET, - PATH_OPEN, - FD_READDIR, - PATH_READLINK, - PATH_RENAME_SOURCE, - PATH_RENAME_TARGET, - PATH_FILESTAT_GET, - PATH_FILESTAT_SET_SIZE, - PATH_FILESTAT_SET_TIMES, - FD_FILESTAT_GET, - FD_FILESTAT_SET_SIZE, - FD_FILESTAT_SET_TIMES, - PATH_SYMLINK, - PATH_REMOVE_DIRECTORY, - PATH_UNLINK_FILE, - POLL_FD_READWRITE, - SOCK_SHUTDOWN -); - -// This implementation, wherever possible, delegates directly to the Snapshot1 implementation, -// performing the no-op type conversions along the way. -impl wasi_unstable::WasiUnstable for WasiCtx { - async fn args_get( - &mut self, - memory: &mut GuestMemory<'_>, - argv: GuestPtr>, - argv_buf: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::args_get(self, memory, argv, argv_buf).await?; - Ok(()) - } - - async fn args_sizes_get( - &mut self, - memory: &mut GuestMemory<'_>, - ) -> Result<(types::Size, types::Size), Error> { - let s = Snapshot1::args_sizes_get(self, memory).await?; - Ok(s) - } - - async fn environ_get( - &mut self, - memory: &mut GuestMemory<'_>, - environ: GuestPtr>, - environ_buf: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::environ_get(self, memory, environ, environ_buf).await?; - Ok(()) - } - - async fn environ_sizes_get( - &mut self, - memory: &mut GuestMemory<'_>, - ) -> Result<(types::Size, types::Size), Error> { - let s = Snapshot1::environ_sizes_get(self, memory).await?; - Ok(s) - } - - async fn clock_res_get( - &mut self, - memory: &mut GuestMemory<'_>, - id: types::Clockid, - ) -> Result { - let t = Snapshot1::clock_res_get(self, memory, id.into()).await?; - Ok(t) - } - - async fn clock_time_get( - &mut self, - memory: &mut GuestMemory<'_>, - id: types::Clockid, - precision: types::Timestamp, - ) -> Result { - let t = Snapshot1::clock_time_get(self, memory, id.into(), precision).await?; - Ok(t) - } - - async fn fd_advise( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - offset: types::Filesize, - len: types::Filesize, - advice: types::Advice, - ) -> Result<(), Error> { - Snapshot1::fd_advise(self, memory, fd.into(), offset, len, advice.into()).await?; - Ok(()) - } - - async fn fd_allocate( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - offset: types::Filesize, - len: types::Filesize, - ) -> Result<(), Error> { - Snapshot1::fd_allocate(self, memory, fd.into(), offset, len).await?; - Ok(()) - } - - async fn fd_close(&mut self, memory: &mut GuestMemory<'_>, fd: types::Fd) -> Result<(), Error> { - Snapshot1::fd_close(self, memory, fd.into()).await?; - Ok(()) - } - - async fn fd_datasync( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ) -> Result<(), Error> { - Snapshot1::fd_datasync(self, memory, fd.into()).await?; - Ok(()) - } - - async fn fd_fdstat_get( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ) -> Result { - Ok(Snapshot1::fd_fdstat_get(self, memory, fd.into()) - .await? - .into()) - } - - async fn fd_fdstat_set_flags( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - flags: types::Fdflags, - ) -> Result<(), Error> { - Snapshot1::fd_fdstat_set_flags(self, memory, fd.into(), flags.into()).await?; - Ok(()) - } - - async fn fd_fdstat_set_rights( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - fs_rights_base: types::Rights, - fs_rights_inheriting: types::Rights, - ) -> Result<(), Error> { - Snapshot1::fd_fdstat_set_rights( - self, - memory, - fd.into(), - fs_rights_base.into(), - fs_rights_inheriting.into(), - ) - .await?; - Ok(()) - } - - async fn fd_filestat_get( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ) -> Result { - Ok(Snapshot1::fd_filestat_get(self, memory, fd.into()) - .await? - .into()) - } - - async fn fd_filestat_set_size( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - size: types::Filesize, - ) -> Result<(), Error> { - Snapshot1::fd_filestat_set_size(self, memory, fd.into(), size).await?; - Ok(()) - } - - async fn fd_filestat_set_times( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - atim: types::Timestamp, - mtim: types::Timestamp, - fst_flags: types::Fstflags, - ) -> Result<(), Error> { - Snapshot1::fd_filestat_set_times(self, memory, fd.into(), atim, mtim, fst_flags.into()) - .await?; - Ok(()) - } - - // NOTE on fd_read, fd_pread, fd_write, fd_pwrite implementations: - // these cast their pointers from preview0 vectors to preview1 vectors and - // this only works because the representation didn't change between preview0 - // and preview1. - - async fn fd_read( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - iovs: types::IovecArray, - ) -> Result { - Ok(Snapshot1::fd_read(self, memory, fd.into(), iovs.cast()).await?) - } - - async fn fd_pread( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - iovs: types::IovecArray, - offset: types::Filesize, - ) -> Result { - Ok(Snapshot1::fd_pread(self, memory, fd.into(), iovs.cast(), offset).await?) - } - - async fn fd_write( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ciovs: types::CiovecArray, - ) -> Result { - Ok(Snapshot1::fd_write(self, memory, fd.into(), ciovs.cast()).await?) - } - - async fn fd_pwrite( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ciovs: types::CiovecArray, - offset: types::Filesize, - ) -> Result { - Ok(Snapshot1::fd_pwrite(self, memory, fd.into(), ciovs.cast(), offset).await?) - } - - async fn fd_prestat_get( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ) -> Result { - Ok(Snapshot1::fd_prestat_get(self, memory, fd.into()) - .await? - .into()) - } - - async fn fd_prestat_dir_name( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - path: GuestPtr, - path_max_len: types::Size, - ) -> Result<(), Error> { - Snapshot1::fd_prestat_dir_name(self, memory, fd.into(), path, path_max_len).await?; - Ok(()) - } - - async fn fd_renumber( - &mut self, - memory: &mut GuestMemory<'_>, - from: types::Fd, - to: types::Fd, - ) -> Result<(), Error> { - Snapshot1::fd_renumber(self, memory, from.into(), to.into()).await?; - Ok(()) - } - - async fn fd_seek( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - offset: types::Filedelta, - whence: types::Whence, - ) -> Result { - Ok(Snapshot1::fd_seek(self, memory, fd.into(), offset, whence.into()).await?) - } - - async fn fd_sync(&mut self, memory: &mut GuestMemory<'_>, fd: types::Fd) -> Result<(), Error> { - Snapshot1::fd_sync(self, memory, fd.into()).await?; - Ok(()) - } - - async fn fd_tell( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - ) -> Result { - Ok(Snapshot1::fd_tell(self, memory, fd.into()).await?) - } - - async fn fd_readdir( - &mut self, - memory: &mut GuestMemory<'_>, - fd: types::Fd, - buf: GuestPtr, - buf_len: types::Size, - cookie: types::Dircookie, - ) -> Result { - Ok(Snapshot1::fd_readdir(self, memory, fd.into(), buf, buf_len, cookie).await?) - } - - async fn path_create_directory( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - path: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::path_create_directory(self, memory, dirfd.into(), path).await?; - Ok(()) - } - - async fn path_filestat_get( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - flags: types::Lookupflags, - path: GuestPtr, - ) -> Result { - Ok( - Snapshot1::path_filestat_get(self, memory, dirfd.into(), flags.into(), path) - .await? - .into(), - ) - } - - async fn path_filestat_set_times( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - flags: types::Lookupflags, - path: GuestPtr, - atim: types::Timestamp, - mtim: types::Timestamp, - fst_flags: types::Fstflags, - ) -> Result<(), Error> { - Snapshot1::path_filestat_set_times( - self, - memory, - dirfd.into(), - flags.into(), - path, - atim, - mtim, - fst_flags.into(), - ) - .await?; - Ok(()) - } - - async fn path_link( - &mut self, - memory: &mut GuestMemory<'_>, - src_fd: types::Fd, - src_flags: types::Lookupflags, - src_path: GuestPtr, - target_fd: types::Fd, - target_path: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::path_link( - self, - memory, - src_fd.into(), - src_flags.into(), - src_path, - target_fd.into(), - target_path, - ) - .await?; - Ok(()) - } - - async fn path_open( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - dirflags: types::Lookupflags, - path: GuestPtr, - oflags: types::Oflags, - fs_rights_base: types::Rights, - fs_rights_inheriting: types::Rights, - fdflags: types::Fdflags, - ) -> Result { - Ok(Snapshot1::path_open( - self, - memory, - dirfd.into(), - dirflags.into(), - path, - oflags.into(), - fs_rights_base.into(), - fs_rights_inheriting.into(), - fdflags.into(), - ) - .await? - .into()) - } - - async fn path_readlink( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - path: GuestPtr, - buf: GuestPtr, - buf_len: types::Size, - ) -> Result { - Ok(Snapshot1::path_readlink(self, memory, dirfd.into(), path, buf, buf_len).await?) - } - - async fn path_remove_directory( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - path: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::path_remove_directory(self, memory, dirfd.into(), path).await?; - Ok(()) - } - - async fn path_rename( - &mut self, - memory: &mut GuestMemory<'_>, - src_fd: types::Fd, - src_path: GuestPtr, - dest_fd: types::Fd, - dest_path: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::path_rename( - self, - memory, - src_fd.into(), - src_path, - dest_fd.into(), - dest_path, - ) - .await?; - Ok(()) - } - - async fn path_symlink( - &mut self, - memory: &mut GuestMemory<'_>, - src_path: GuestPtr, - dirfd: types::Fd, - dest_path: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::path_symlink(self, memory, src_path, dirfd.into(), dest_path).await?; - Ok(()) - } - - async fn path_unlink_file( - &mut self, - memory: &mut GuestMemory<'_>, - dirfd: types::Fd, - path: GuestPtr, - ) -> Result<(), Error> { - Snapshot1::path_unlink_file(self, memory, dirfd.into(), path).await?; - Ok(()) - } - - // NOTE on poll_oneoff implementation: - // Like fd_write and friends, the arguments and return values are behind GuestPtrs, - // so they are not values we can convert and pass to the poll_oneoff in Snapshot1. - // Instead, we have copied the implementation of these functions from the Snapshot1 code. - // The implementations are identical, but the `types::` in scope locally is different. - // The bodies of these functions is mostly about converting the GuestPtr and types::-based - // representation to use the Poll abstraction. - async fn poll_oneoff( - &mut self, - memory: &mut GuestMemory<'_>, - subs: GuestPtr, - events: GuestPtr, - nsubscriptions: types::Size, - ) -> Result { - if nsubscriptions == 0 { - return Err(Error::invalid_argument().context("nsubscriptions must be nonzero")); - } - - // Special-case a `poll_oneoff` which is just sleeping on a single - // relative timer event, such as what WASI libc uses to implement sleep - // functions. This supports all clock IDs, because POSIX says that - // `clock_settime` doesn't effect relative sleeps. - if nsubscriptions == 1 { - let sub = memory.read(subs)?; - if let types::SubscriptionU::Clock(clocksub) = sub.u { - if !clocksub - .flags - .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) - { - self.sched - .sleep(Duration::from_nanos(clocksub.timeout)) - .await?; - memory.write( - events, - types::Event { - userdata: sub.userdata, - error: types::Errno::Success, - type_: types::Eventtype::Clock, - fd_readwrite: fd_readwrite_empty(), - }, - )?; - return Ok(1); - } - } - } - - let table = &self.table; - let mut sub_fds: HashSet = HashSet::new(); - // We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside - let mut reads: Vec<(u32, Userdata)> = Vec::new(); - let mut writes: Vec<(u32, Userdata)> = Vec::new(); - let mut poll = Poll::new(); - - let subs = subs.as_array(nsubscriptions); - for sub_elem in subs.iter() { - let sub_ptr = sub_elem?; - let sub = memory.read(sub_ptr)?; - match sub.u { - types::SubscriptionU::Clock(clocksub) => match clocksub.id { - types::Clockid::Monotonic => { - let clock = self.clocks.monotonic()?; - let precision = Duration::from_nanos(clocksub.precision); - let duration = Duration::from_nanos(clocksub.timeout); - let start = if clocksub - .flags - .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) - { - clock.creation_time - } else { - clock.abs_clock.now(precision) - }; - let deadline = start - .checked_add(duration) - .ok_or_else(|| Error::overflow().context("deadline"))?; - poll.subscribe_monotonic_clock( - &*clock.abs_clock, - deadline, - precision, - sub.userdata.into(), - ) - } - _ => Err(Error::invalid_argument() - .context("timer subscriptions only support monotonic timer"))?, - }, - types::SubscriptionU::FdRead(readsub) => { - let fd = readsub.file_descriptor; - if sub_fds.contains(&fd) { - return Err(Error::invalid_argument() - .context("Fd can be subscribed to at most once per poll")); - } else { - sub_fds.insert(fd); - } - table.get_file(u32::from(fd))?; - reads.push((u32::from(fd), sub.userdata.into())); - } - types::SubscriptionU::FdWrite(writesub) => { - let fd = writesub.file_descriptor; - if sub_fds.contains(&fd) { - return Err(Error::invalid_argument() - .context("Fd can be subscribed to at most once per poll")); - } else { - sub_fds.insert(fd); - } - table.get_file(u32::from(fd))?; - writes.push((u32::from(fd), sub.userdata.into())); - } - } - } - - self.sched.poll_oneoff(&mut poll).await?; - - let results = poll.results(); - let num_results = results.len(); - assert!( - num_results <= nsubscriptions as usize, - "results exceeds subscriptions" - ); - let events = events.as_array( - num_results - .try_into() - .expect("not greater than nsubscriptions"), - ); - for ((result, userdata), event_elem) in results.into_iter().zip(events.iter()) { - let event_ptr = event_elem?; - let userdata: types::Userdata = userdata.into(); - memory.write( - event_ptr, - match result { - SubscriptionResult::Read(r) => { - let type_ = types::Eventtype::FdRead; - match r { - Ok((nbytes, flags)) => types::Event { - userdata, - error: types::Errno::Success, - type_, - fd_readwrite: types::EventFdReadwrite { - nbytes, - flags: types::Eventrwflags::from(&flags), - }, - }, - Err(e) => types::Event { - userdata, - error: types::Errno::from(e.downcast().map_err(Error::trap)?), - type_, - fd_readwrite: fd_readwrite_empty(), - }, - } - } - SubscriptionResult::Write(r) => { - let type_ = types::Eventtype::FdWrite; - match r { - Ok((nbytes, flags)) => types::Event { - userdata, - error: types::Errno::Success, - type_, - fd_readwrite: types::EventFdReadwrite { - nbytes, - flags: types::Eventrwflags::from(&flags), - }, - }, - Err(e) => types::Event { - userdata, - error: types::Errno::from(e.downcast().map_err(Error::trap)?), - type_, - fd_readwrite: fd_readwrite_empty(), - }, - } - } - SubscriptionResult::MonotonicClock(r) => { - let type_ = types::Eventtype::Clock; - types::Event { - userdata, - error: match r { - Ok(()) => types::Errno::Success, - Err(e) => types::Errno::from(e.downcast().map_err(Error::trap)?), - }, - type_, - fd_readwrite: fd_readwrite_empty(), - } - } - }, - )?; - } - - Ok(num_results.try_into().expect("results fit into memory")) - } - - async fn proc_exit( - &mut self, - memory: &mut GuestMemory<'_>, - status: types::Exitcode, - ) -> EnvError { - Snapshot1::proc_exit(self, memory, status).await - } - - async fn proc_raise( - &mut self, - _memory: &mut GuestMemory<'_>, - _sig: types::Signal, - ) -> Result<(), Error> { - Err(Error::trap(EnvError::msg("proc_raise unsupported"))) - } - - async fn sched_yield(&mut self, memory: &mut GuestMemory<'_>) -> Result<(), Error> { - Snapshot1::sched_yield(self, memory).await?; - Ok(()) - } - - async fn random_get( - &mut self, - memory: &mut GuestMemory<'_>, - buf: GuestPtr, - buf_len: types::Size, - ) -> Result<(), Error> { - Snapshot1::random_get(self, memory, buf, buf_len).await?; - Ok(()) - } - - async fn sock_recv( - &mut self, - _memory: &mut GuestMemory<'_>, - _fd: types::Fd, - _ri_data: types::IovecArray, - _ri_flags: types::Riflags, - ) -> Result<(types::Size, types::Roflags), Error> { - Err(Error::trap(EnvError::msg("sock_recv unsupported"))) - } - - async fn sock_send( - &mut self, - _memory: &mut GuestMemory<'_>, - _fd: types::Fd, - _si_data: types::CiovecArray, - _si_flags: types::Siflags, - ) -> Result { - Err(Error::trap(EnvError::msg("sock_send unsupported"))) - } - - async fn sock_shutdown( - &mut self, - _memory: &mut GuestMemory<'_>, - _fd: types::Fd, - _how: types::Sdflags, - ) -> Result<(), Error> { - Err(Error::trap(EnvError::msg("sock_shutdown unsupported"))) - } -} - -impl From<&RwEventFlags> for types::Eventrwflags { - fn from(flags: &RwEventFlags) -> types::Eventrwflags { - let mut out = types::Eventrwflags::empty(); - if flags.contains(RwEventFlags::HANGUP) { - out = out | types::Eventrwflags::FD_READWRITE_HANGUP; - } - out - } -} - -fn fd_readwrite_empty() -> types::EventFdReadwrite { - types::EventFdReadwrite { - nbytes: 0, - flags: types::Eventrwflags::empty(), - } -} diff --git a/crates/wasi-common/witx/preview0/typenames.witx b/crates/wasi-common/witx/preview0/typenames.witx deleted file mode 100644 index c3213743..00000000 --- a/crates/wasi-common/witx/preview0/typenames.witx +++ /dev/null @@ -1,746 +0,0 @@ -;; Type names used by low-level WASI interfaces. -;; -;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). -;; -;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/main/docs/witx.md) -;; for an explanation of what that means. - -(typename $size u32) - -;;; Non-negative file size or length of a region within a file. -(typename $filesize u64) - -;;; Timestamp in nanoseconds. -(typename $timestamp u64) - -;;; Identifiers for clocks. -(typename $clockid - (enum (@witx tag u32) - ;;; The clock measuring real time. Time value zero corresponds with - ;;; 1970-01-01T00:00:00Z. - $realtime - ;;; The store-wide monotonic clock, which is defined as a clock measuring - ;;; real time, whose value cannot be adjusted and which cannot have negative - ;;; clock jumps. The epoch of this clock is undefined. The absolute time - ;;; value of this clock therefore has no meaning. - $monotonic - ;;; The CPU-time clock associated with the current process. - $process_cputime_id - ;;; The CPU-time clock associated with the current thread. - $thread_cputime_id - ) -) - -;;; Error codes returned by functions. -;;; Not all of these error codes are returned by the functions provided by this -;;; API; some are used in higher-level library layers, and others are provided -;;; merely for alignment with POSIX. -(typename $errno - (enum (@witx tag u16) - ;;; No error occurred. System call completed successfully. - $success - ;;; Argument list too long. - $2big - ;;; Permission denied. - $acces - ;;; Address in use. - $addrinuse - ;;; Address not available. - $addrnotavail - ;;; Address family not supported. - $afnosupport - ;;; Resource unavailable, or operation would block. - $again - ;;; Connection already in progress. - $already - ;;; Bad file descriptor. - $badf - ;;; Bad message. - $badmsg - ;;; Device or resource busy. - $busy - ;;; Operation canceled. - $canceled - ;;; No child processes. - $child - ;;; Connection aborted. - $connaborted - ;;; Connection refused. - $connrefused - ;;; Connection reset. - $connreset - ;;; Resource deadlock would occur. - $deadlk - ;;; Destination address required. - $destaddrreq - ;;; Mathematics argument out of domain of function. - $dom - ;;; Reserved. - $dquot - ;;; File exists. - $exist - ;;; Bad address. - $fault - ;;; File too large. - $fbig - ;;; Host is unreachable. - $hostunreach - ;;; Identifier removed. - $idrm - ;;; Illegal byte sequence. - $ilseq - ;;; Operation in progress. - $inprogress - ;;; Interrupted function. - $intr - ;;; Invalid argument. - $inval - ;;; I/O error. - $io - ;;; Socket is connected. - $isconn - ;;; Is a directory. - $isdir - ;;; Too many levels of symbolic links. - $loop - ;;; File descriptor value too large. - $mfile - ;;; Too many links. - $mlink - ;;; Message too large. - $msgsize - ;;; Reserved. - $multihop - ;;; Filename too long. - $nametoolong - ;;; Network is down. - $netdown - ;;; Connection aborted by network. - $netreset - ;;; Network unreachable. - $netunreach - ;;; Too many files open in system. - $nfile - ;;; No buffer space available. - $nobufs - ;;; No such device. - $nodev - ;;; No such file or directory. - $noent - ;;; Executable file format error. - $noexec - ;;; No locks available. - $nolck - ;;; Reserved. - $nolink - ;;; Not enough space. - $nomem - ;;; No message of the desired type. - $nomsg - ;;; Protocol not available. - $noprotoopt - ;;; No space left on device. - $nospc - ;;; Function not supported. - $nosys - ;;; The socket is not connected. - $notconn - ;;; Not a directory or a symbolic link to a directory. - $notdir - ;;; Directory not empty. - $notempty - ;;; State not recoverable. - $notrecoverable - ;;; Not a socket. - $notsock - ;;; Not supported, or operation not supported on socket. - $notsup - ;;; Inappropriate I/O control operation. - $notty - ;;; No such device or address. - $nxio - ;;; Value too large to be stored in data type. - $overflow - ;;; Previous owner died. - $ownerdead - ;;; Operation not permitted. - $perm - ;;; Broken pipe. - $pipe - ;;; Protocol error. - $proto - ;;; Protocol not supported. - $protonosupport - ;;; Protocol wrong type for socket. - $prototype - ;;; Result too large. - $range - ;;; Read-only file system. - $rofs - ;;; Invalid seek. - $spipe - ;;; No such process. - $srch - ;;; Reserved. - $stale - ;;; Connection timed out. - $timedout - ;;; Text file busy. - $txtbsy - ;;; Cross-device link. - $xdev - ;;; Extension: Capabilities insufficient. - $notcapable - ) -) - -;;; File descriptor rights, determining which actions may be performed. -(typename $rights - (flags (@witx repr u64) - ;;; The right to invoke `fd_datasync`. - ;; - ;;; If `rights::path_open` is set, includes the right to invoke - ;;; `path_open` with `fdflags::dsync`. - $fd_datasync - ;;; The right to invoke `fd_read` and `sock_recv`. - ;; - ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pread`. - $fd_read - ;;; The right to invoke `fd_seek`. This flag implies `rights::fd_tell`. - $fd_seek - ;;; The right to invoke `fd_fdstat_set_flags`. - $fd_fdstat_set_flags - ;;; The right to invoke `fd_sync`. - ;; - ;;; If `rights::path_open` is set, includes the right to invoke - ;;; `path_open` with `fdflags::rsync` and `fdflags::dsync`. - $fd_sync - ;;; The right to invoke `fd_seek` in such a way that the file offset - ;;; remains unaltered (i.e., `whence::cur` with offset zero), or to - ;;; invoke `fd_tell`. - $fd_tell - ;;; The right to invoke `fd_write` and `sock_send`. - ;;; If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`. - $fd_write - ;;; The right to invoke `fd_advise`. - $fd_advise - ;;; The right to invoke `fd_allocate`. - $fd_allocate - ;;; The right to invoke `path_create_directory`. - $path_create_directory - ;;; If `rights::path_open` is set, the right to invoke `path_open` with `oflags::creat`. - $path_create_file - ;;; The right to invoke `path_link` with the file descriptor as the - ;;; source directory. - $path_link_source - ;;; The right to invoke `path_link` with the file descriptor as the - ;;; target directory. - $path_link_target - ;;; The right to invoke `path_open`. - $path_open - ;;; The right to invoke `fd_readdir`. - $fd_readdir - ;;; The right to invoke `path_readlink`. - $path_readlink - ;;; The right to invoke `path_rename` with the file descriptor as the source directory. - $path_rename_source - ;;; The right to invoke `path_rename` with the file descriptor as the target directory. - $path_rename_target - ;;; The right to invoke `path_filestat_get`. - $path_filestat_get - ;;; The right to change a file's size (there is no `path_filestat_set_size`). - ;;; If `rights::path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`. - $path_filestat_set_size - ;;; The right to invoke `path_filestat_set_times`. - $path_filestat_set_times - ;;; The right to invoke `fd_filestat_get`. - $fd_filestat_get - ;;; The right to invoke `fd_filestat_set_size`. - $fd_filestat_set_size - ;;; The right to invoke `fd_filestat_set_times`. - $fd_filestat_set_times - ;;; The right to invoke `path_symlink`. - $path_symlink - ;;; The right to invoke `path_remove_directory`. - $path_remove_directory - ;;; The right to invoke `path_unlink_file`. - $path_unlink_file - ;;; If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`. - ;;; If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`. - $poll_fd_readwrite - ;;; The right to invoke `sock_shutdown`. - $sock_shutdown - ) -) - -;;; A file descriptor handle. -(typename $fd (handle)) - -;;; A region of memory for scatter/gather reads. -(typename $iovec - (record - ;;; The address of the buffer to be filled. - (field $buf (@witx pointer u8)) - ;;; The length of the buffer to be filled. - (field $buf_len $size) - ) -) - -;;; A region of memory for scatter/gather writes. -(typename $ciovec - (record - ;;; The address of the buffer to be written. - (field $buf (@witx const_pointer u8)) - ;;; The length of the buffer to be written. - (field $buf_len $size) - ) -) - -(typename $iovec_array (list $iovec)) -(typename $ciovec_array (list $ciovec)) - -;;; Relative offset within a file. -(typename $filedelta s64) - -;;; The position relative to which to set the offset of the file descriptor. -(typename $whence - (enum (@witx tag u8) - ;;; Seek relative to current position. - $cur - ;;; Seek relative to end-of-file. - $end - ;;; Seek relative to start-of-file. - $set - ) -) - -;;; A reference to the offset of a directory entry. -(typename $dircookie u64) - -;;; The type for the `dirent::d_namlen` field of `dirent` struct. -(typename $dirnamlen u32) - -;;; File serial number that is unique within its file system. -(typename $inode u64) - -;;; The type of a file descriptor or file. -(typename $filetype - (enum (@witx tag u8) - ;;; The type of the file descriptor or file is unknown or is different from any of the other types specified. - $unknown - ;;; The file descriptor or file refers to a block device inode. - $block_device - ;;; The file descriptor or file refers to a character device inode. - $character_device - ;;; The file descriptor or file refers to a directory inode. - $directory - ;;; The file descriptor or file refers to a regular file inode. - $regular_file - ;;; The file descriptor or file refers to a datagram socket. - $socket_dgram - ;;; The file descriptor or file refers to a byte-stream socket. - $socket_stream - ;;; The file refers to a symbolic link inode. - $symbolic_link - ) -) - -;;; A directory entry. -(typename $dirent - (record - ;;; The offset of the next directory entry stored in this directory. - (field $d_next $dircookie) - ;;; The serial number of the file referred to by this directory entry. - (field $d_ino $inode) - ;;; The length of the name of the directory entry. - (field $d_namlen $dirnamlen) - ;;; The type of the file referred to by this directory entry. - (field $d_type $filetype) - ) -) - -;;; File or memory access pattern advisory information. -(typename $advice - (enum (@witx tag u8) - ;;; The application has no advice to give on its behavior with respect to the specified data. - $normal - ;;; The application expects to access the specified data sequentially from lower offsets to higher offsets. - $sequential - ;;; The application expects to access the specified data in a random order. - $random - ;;; The application expects to access the specified data in the near future. - $willneed - ;;; The application expects that it will not access the specified data in the near future. - $dontneed - ;;; The application expects to access the specified data once and then not reuse it thereafter. - $noreuse - ) -) - -;;; File descriptor flags. -(typename $fdflags - (flags (@witx repr u16) - ;;; Append mode: Data written to the file is always appended to the file's end. - $append - ;;; Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. - $dsync - ;;; Non-blocking mode. - $nonblock - ;;; Synchronized read I/O operations. - $rsync - ;;; Write according to synchronized I/O file integrity completion. In - ;;; addition to synchronizing the data stored in the file, the implementation - ;;; may also synchronously update the file's metadata. - $sync - ) -) - -;;; File descriptor attributes. -(typename $fdstat - (record - ;;; File type. - (field $fs_filetype $filetype) - ;;; File descriptor flags. - (field $fs_flags $fdflags) - ;;; Rights that apply to this file descriptor. - (field $fs_rights_base $rights) - ;;; Maximum set of rights that may be installed on new file descriptors that - ;;; are created through this file descriptor, e.g., through `path_open`. - (field $fs_rights_inheriting $rights) - ) -) - -;;; Identifier for a device containing a file system. Can be used in combination -;;; with `inode` to uniquely identify a file or directory in the filesystem. -(typename $device u64) - -;;; Which file time attributes to adjust. -(typename $fstflags - (flags (@witx repr u16) - ;;; Adjust the last data access timestamp to the value stored in `filestat::atim`. - $atim - ;;; Adjust the last data access timestamp to the time of clock `clockid::realtime`. - $atim_now - ;;; Adjust the last data modification timestamp to the value stored in `filestat::mtim`. - $mtim - ;;; Adjust the last data modification timestamp to the time of clock `clockid::realtime`. - $mtim_now - ) -) - -;;; Flags determining the method of how paths are resolved. -(typename $lookupflags - (flags (@witx repr u32) - ;;; As long as the resolved path corresponds to a symbolic link, it is expanded. - $symlink_follow - ) -) - -;;; Open flags used by `path_open`. -(typename $oflags - (flags (@witx repr u16) - ;;; Create file if it does not exist. - $creat - ;;; Fail if not a directory. - $directory - ;;; Fail if file already exists. - $excl - ;;; Truncate file to size 0. - $trunc - ) -) - -;;; Number of hard links to an inode. -(typename $linkcount u32) - -;;; File attributes. -(typename $filestat - (record - ;;; Device ID of device containing the file. - (field $dev $device) - ;;; File serial number. - (field $ino $inode) - ;;; File type. - (field $filetype $filetype) - ;;; Number of hard links to the file. - (field $nlink $linkcount) - ;;; For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. - (field $size $filesize) - ;;; Last data access timestamp. - (field $atim $timestamp) - ;;; Last data modification timestamp. - (field $mtim $timestamp) - ;;; Last file status change timestamp. - (field $ctim $timestamp) - ) -) - -;;; User-provided value that may be attached to objects that is retained when -;;; extracted from the implementation. -(typename $userdata u64) - -;;; Type of a subscription to an event or its occurrence. -(typename $eventtype - (enum (@witx tag u8) - ;;; The time value of clock `subscription_clock::id` has - ;;; reached timestamp `subscription_clock::timeout`. - $clock - ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has data - ;;; available for reading. This event always triggers for regular files. - $fd_read - ;;; File descriptor `subscription_fd_readwrite::file_descriptor` has capacity - ;;; available for writing. This event always triggers for regular files. - $fd_write - ) -) - -;;; The state of the file descriptor subscribed to with -;;; `eventtype::fd_read` or `eventtype::fd_write`. -(typename $eventrwflags - (flags (@witx repr u16) - ;;; The peer of this socket has closed or disconnected. - $fd_readwrite_hangup - ) -) - -;;; The contents of an `event` for the `eventtype::fd_read` and -;;; `eventtype::fd_write` variants -(typename $event_fd_readwrite - (record - ;;; The number of bytes available for reading or writing. - (field $nbytes $filesize) - ;;; The state of the file descriptor. - (field $flags $eventrwflags) - ) -) - -;;; An event that occurred. -(typename $event - (record - ;;; User-provided value that got attached to `subscription::userdata`. - (field $userdata $userdata) - ;;; If non-zero, an error that occurred while processing the subscription request. - (field $error $errno) - ;;; The type of event that occurred - (field $type $eventtype) - ;;; The contents of the event, if it is an `eventtype::fd_read` or - ;;; `eventtype::fd_write`. `eventtype::clock` events ignore this field. - (field $fd_readwrite $event_fd_readwrite) - ) -) - -;;; Flags determining how to interpret the timestamp provided in -;;; `subscription_clock::timeout`. -(typename $subclockflags - (flags (@witx repr u16) - ;;; If set, treat the timestamp provided in - ;;; `subscription_clock::timeout` as an absolute timestamp of clock - ;;; `subscription_clock::id`. If clear, treat the timestamp - ;;; provided in `subscription_clock::timeout` relative to the - ;;; current time value of clock `subscription_clock::id`. - $subscription_clock_abstime - ) -) - -;;; The contents of a `subscription` when type is `eventtype::clock`. -(typename $subscription_clock - (record - ;;; The user-defined unique identifier of the clock. - (field $identifier $userdata) - ;;; The clock against which to compare the timestamp. - (field $id $clockid) - ;;; The absolute or relative timestamp. - (field $timeout $timestamp) - ;;; The amount of time that the implementation may wait additionally - ;;; to coalesce with other events. - (field $precision $timestamp) - ;;; Flags specifying whether the timeout is absolute or relative - (field $flags $subclockflags) - ) -) - -;;; The contents of a `subscription` when the variant is -;;; `eventtype::fd_read` or `eventtype::fd_write`. -(typename $subscription_fd_readwrite - (record - ;;; The file descriptor on which to wait for it to become ready for reading or writing. - (field $file_descriptor $fd) - ) -) - -;;; The contents of a `subscription`. -(typename $subscription_u - (union (@witx tag $eventtype) - $subscription_clock - $subscription_fd_readwrite - $subscription_fd_readwrite - ) -) - -;;; Subscription to an event. -(typename $subscription - (record - ;;; User-provided value that is attached to the subscription in the - ;;; implementation and returned through `event::userdata`. - (field $userdata $userdata) - ;;; The type of the event to which to subscribe. - (field $u $subscription_u) - ) -) - -;;; Exit code generated by a process when exiting. -(typename $exitcode u32) - -;;; Signal condition. -(typename $signal - (enum (@witx tag u8) - ;;; No signal. Note that POSIX has special semantics for `kill(pid, 0)`, - ;;; so this value is reserved. - $none - ;;; Hangup. - ;;; Action: Terminates the process. - $hup - ;;; Terminate interrupt signal. - ;;; Action: Terminates the process. - $int - ;;; Terminal quit signal. - ;;; Action: Terminates the process. - $quit - ;;; Illegal instruction. - ;;; Action: Terminates the process. - $ill - ;;; Trace/breakpoint trap. - ;;; Action: Terminates the process. - $trap - ;;; Process abort signal. - ;;; Action: Terminates the process. - $abrt - ;;; Access to an undefined portion of a memory object. - ;;; Action: Terminates the process. - $bus - ;;; Erroneous arithmetic operation. - ;;; Action: Terminates the process. - $fpe - ;;; Kill. - ;;; Action: Terminates the process. - $kill - ;;; User-defined signal 1. - ;;; Action: Terminates the process. - $usr1 - ;;; Invalid memory reference. - ;;; Action: Terminates the process. - $segv - ;;; User-defined signal 2. - ;;; Action: Terminates the process. - $usr2 - ;;; Write on a pipe with no one to read it. - ;;; Action: Ignored. - $pipe - ;;; Alarm clock. - ;;; Action: Terminates the process. - $alrm - ;;; Termination signal. - ;;; Action: Terminates the process. - $term - ;;; Child process terminated, stopped, or continued. - ;;; Action: Ignored. - $chld - ;;; Continue executing, if stopped. - ;;; Action: Continues executing, if stopped. - $cont - ;;; Stop executing. - ;;; Action: Stops executing. - $stop - ;;; Terminal stop signal. - ;;; Action: Stops executing. - $tstp - ;;; Background process attempting read. - ;;; Action: Stops executing. - $ttin - ;;; Background process attempting write. - ;;; Action: Stops executing. - $ttou - ;;; High bandwidth data is available at a socket. - ;;; Action: Ignored. - $urg - ;;; CPU time limit exceeded. - ;;; Action: Terminates the process. - $xcpu - ;;; File size limit exceeded. - ;;; Action: Terminates the process. - $xfsz - ;;; Virtual timer expired. - ;;; Action: Terminates the process. - $vtalrm - ;;; Profiling timer expired. - ;;; Action: Terminates the process. - $prof - ;;; Window changed. - ;;; Action: Ignored. - $winch - ;;; I/O possible. - ;;; Action: Terminates the process. - $poll - ;;; Power failure. - ;;; Action: Terminates the process. - $pwr - ;;; Bad system call. - ;;; Action: Terminates the process. - $sys - ) -) - -;;; Flags provided to `sock_recv`. -(typename $riflags - (flags (@witx repr u16) - ;;; Returns the message without removing it from the socket's receive queue. - $recv_peek - ;;; On byte-stream sockets, block until the full amount of data can be returned. - $recv_waitall - ) -) - -;;; Flags returned by `sock_recv`. -(typename $roflags - (flags (@witx repr u16) - ;;; Returned by `sock_recv`: Message data has been truncated. - $recv_data_truncated - ) -) - -;;; Flags provided to `sock_send`. As there are currently no flags -;;; defined, it must be set to zero. -(typename $siflags u16) - -;;; Which channels on a socket to shut down. -(typename $sdflags - (flags (@witx repr u8) - ;;; Disables further receive operations. - $rd - ;;; Disables further send operations. - $wr - ) -) - -;;; Identifiers for preopened capabilities. -(typename $preopentype - (enum (@witx tag u8) - ;;; A pre-opened directory. - $dir - ) -) - -;;; The contents of a $prestat when type is `preopentype::dir`. -(typename $prestat_dir - (record - ;;; The length of the directory name for use with `fd_prestat_dir_name`. - (field $pr_name_len $size) - ) -) - -;;; Information about a pre-opened capability. -(typename $prestat - (union (@witx tag $preopentype) - $prestat_dir - ) -) diff --git a/crates/wasi-common/witx/preview0/wasi_unstable.witx b/crates/wasi-common/witx/preview0/wasi_unstable.witx deleted file mode 100644 index ee01abcf..00000000 --- a/crates/wasi-common/witx/preview0/wasi_unstable.witx +++ /dev/null @@ -1,513 +0,0 @@ -;; WASI Preview. This is an evolution of the API that WASI initially -;; launched with. -;; -;; Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). -;; -;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/blob/main/legacy/tools/witx-docs.md) -;; for an explanation of what that means. - -(use "typenames.witx") - -;;; This API predated the convention of naming modules with a `wasi_unstable_` -;;; prefix and a version number. It is preserved here for compatibility, but -;;; we shouldn't follow this pattern in new APIs. -(module $wasi_unstable - ;;; Linear memory to be accessed by WASI functions that need it. - (import "memory" (memory)) - - ;;; Read command-line argument data. - ;;; The size of the array should match that returned by `args_sizes_get`. - ;;; Each argument is expected to be `\0` terminated. - (@interface func (export "args_get") - (param $argv (@witx pointer (@witx pointer u8))) - (param $argv_buf (@witx pointer u8)) - (result $error (expected (error $errno))) - ) - ;;; Return command-line argument data sizes. - (@interface func (export "args_sizes_get") - ;;; Returns the number of arguments and the size of the argument string - ;;; data, or an error. - (result $error (expected (tuple $size $size) (error $errno))) - ) - - ;;; Read environment variable data. - ;;; The sizes of the buffers should match that returned by `environ_sizes_get`. - ;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s. - (@interface func (export "environ_get") - (param $environ (@witx pointer (@witx pointer u8))) - (param $environ_buf (@witx pointer u8)) - (result $error (expected (error $errno))) - ) - ;;; Return environment variable data sizes. - (@interface func (export "environ_sizes_get") - ;;; Returns the number of environment variable arguments and the size of the - ;;; environment variable data. - (result $error (expected (tuple $size $size) (error $errno))) - ) - - ;;; Return the resolution of a clock. - ;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, return - ;;; `errno::inval`. - ;;; Note: This is similar to `clock_getres` in POSIX. - (@interface func (export "clock_res_get") - ;;; The clock for which to return the resolution. - (param $id $clockid) - ;;; The resolution of the clock, or an error if one happened. - (result $error (expected $timestamp (error $errno))) - ) - ;;; Return the time value of a clock. - ;;; Note: This is similar to `clock_gettime` in POSIX. - (@interface func (export "clock_time_get") - ;;; The clock for which to return the time. - (param $id $clockid) - ;;; The maximum lag (exclusive) that the returned time value may have, compared to its actual value. - (param $precision $timestamp) - ;;; The time value of the clock. - (result $error (expected $timestamp (error $errno))) - ) - - ;;; Provide file advisory information on a file descriptor. - ;;; Note: This is similar to `posix_fadvise` in POSIX. - (@interface func (export "fd_advise") - (param $fd $fd) - ;;; The offset within the file to which the advisory applies. - (param $offset $filesize) - ;;; The length of the region to which the advisory applies. - (param $len $filesize) - ;;; The advice. - (param $advice $advice) - (result $error (expected (error $errno))) - ) - - ;;; Force the allocation of space in a file. - ;;; Note: This is similar to `posix_fallocate` in POSIX. - (@interface func (export "fd_allocate") - (param $fd $fd) - ;;; The offset at which to start the allocation. - (param $offset $filesize) - ;;; The length of the area that is allocated. - (param $len $filesize) - (result $error (expected (error $errno))) - ) - - ;;; Close a file descriptor. - ;;; Note: This is similar to `close` in POSIX. - (@interface func (export "fd_close") - (param $fd $fd) - (result $error (expected (error $errno))) - ) - - ;;; Synchronize the data of a file to disk. - ;;; Note: This is similar to `fdatasync` in POSIX. - (@interface func (export "fd_datasync") - (param $fd $fd) - (result $error (expected (error $errno))) - ) - - ;;; Get the attributes of a file descriptor. - ;;; Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. - (@interface func (export "fd_fdstat_get") - (param $fd $fd) - ;;; The buffer where the file descriptor's attributes are stored. - (result $error (expected $fdstat (error $errno))) - ) - - ;;; Adjust the flags associated with a file descriptor. - ;;; Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. - (@interface func (export "fd_fdstat_set_flags") - (param $fd $fd) - ;;; The desired values of the file descriptor flags. - (param $flags $fdflags) - (result $error (expected (error $errno))) - ) - - ;;; Adjust the rights associated with a file descriptor. - ;;; This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights - (@interface func (export "fd_fdstat_set_rights") - (param $fd $fd) - ;;; The desired rights of the file descriptor. - (param $fs_rights_base $rights) - (param $fs_rights_inheriting $rights) - (result $error (expected (error $errno))) - ) - - ;;; Return the attributes of an open file. - (@interface func (export "fd_filestat_get") - (param $fd $fd) - ;;; The buffer where the file's attributes are stored. - (result $error (expected $filestat (error $errno))) - ) - - ;;; Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. - ;;; Note: This is similar to `ftruncate` in POSIX. - (@interface func (export "fd_filestat_set_size") - (param $fd $fd) - ;;; The desired file size. - (param $size $filesize) - (result $error (expected (error $errno))) - ) - - ;;; Adjust the timestamps of an open file or directory. - ;;; Note: This is similar to `futimens` in POSIX. - (@interface func (export "fd_filestat_set_times") - (param $fd $fd) - ;;; The desired values of the data access timestamp. - (param $atim $timestamp) - ;;; The desired values of the data modification timestamp. - (param $mtim $timestamp) - ;;; A bitmask indicating which timestamps to adjust. - (param $fst_flags $fstflags) - (result $error (expected (error $errno))) - ) - - ;;; Read from a file descriptor, without using and updating the file descriptor's offset. - ;;; Note: This is similar to `preadv` in POSIX. - (@interface func (export "fd_pread") - (param $fd $fd) - ;;; List of scatter/gather vectors in which to store data. - (param $iovs $iovec_array) - ;;; The offset within the file at which to read. - (param $offset $filesize) - ;;; The number of bytes read. - (result $error (expected $size (error $errno))) - ) - - ;;; Return a description of the given preopened file descriptor. - (@interface func (export "fd_prestat_get") - (param $fd $fd) - ;;; The buffer where the description is stored. - (result $error (expected $prestat (error $errno))) - ) - - ;;; Return a description of the given preopened file descriptor. - (@interface func (export "fd_prestat_dir_name") - (param $fd $fd) - ;;; A buffer into which to write the preopened directory name. - (param $path (@witx pointer u8)) - (param $path_len $size) - (result $error (expected (error $errno))) - ) - - ;;; Write to a file descriptor, without using and updating the file descriptor's offset. - ;;; Note: This is similar to `pwritev` in POSIX. - (@interface func (export "fd_pwrite") - (param $fd $fd) - ;;; List of scatter/gather vectors from which to retrieve data. - (param $iovs $ciovec_array) - ;;; The offset within the file at which to write. - (param $offset $filesize) - ;;; The number of bytes written. - (result $error (expected $size (error $errno))) - ) - - ;;; Read from a file descriptor. - ;;; Note: This is similar to `readv` in POSIX. - (@interface func (export "fd_read") - (param $fd $fd) - ;;; List of scatter/gather vectors to which to store data. - (param $iovs $iovec_array) - ;;; The number of bytes read. - (result $error (expected $size (error $errno))) - ) - - ;;; Read directory entries from a directory. - ;;; When successful, the contents of the output buffer consist of a sequence of - ;;; directory entries. Each directory entry consists of a `dirent` object, - ;;; followed by `dirent::d_namlen` bytes holding the name of the directory - ;;; entry. - ;; - ;;; This function fills the output buffer as much as possible, potentially - ;;; truncating the last directory entry. This allows the caller to grow its - ;;; read buffer size in case it's too small to fit a single large directory - ;;; entry, or skip the oversized directory entry. - (@interface func (export "fd_readdir") - (param $fd $fd) - ;;; The buffer where directory entries are stored - (param $buf (@witx pointer u8)) - (param $buf_len $size) - ;;; The location within the directory to start reading - (param $cookie $dircookie) - ;;; The number of bytes stored in the read buffer. If less than the size of the read buffer, the end of the directory has been reached. - (result $error (expected $size (error $errno))) - ) - - ;;; Atomically replace a file descriptor by renumbering another file descriptor. - ;; - ;;; Due to the strong focus on thread safety, this environment does not provide - ;;; a mechanism to duplicate or renumber a file descriptor to an arbitrary - ;;; number, like `dup2()`. This would be prone to race conditions, as an actual - ;;; file descriptor with the same number could be allocated by a different - ;;; thread at the same time. - ;; - ;;; This function provides a way to atomically renumber file descriptors, which - ;;; would disappear if `dup2()` were to be removed entirely. - (@interface func (export "fd_renumber") - (param $fd $fd) - ;;; The file descriptor to overwrite. - (param $to $fd) - (result $error (expected (error $errno))) - ) - - ;;; Move the offset of a file descriptor. - ;;; Note: This is similar to `lseek` in POSIX. - (@interface func (export "fd_seek") - (param $fd $fd) - ;;; The number of bytes to move. - (param $offset $filedelta) - ;;; The base from which the offset is relative. - (param $whence $whence) - ;;; The new offset of the file descriptor, relative to the start of the file. - (result $error (expected $filesize (error $errno))) - ) - - ;;; Synchronize the data and metadata of a file to disk. - ;;; Note: This is similar to `fsync` in POSIX. - (@interface func (export "fd_sync") - (param $fd $fd) - (result $error (expected (error $errno))) - ) - - ;;; Return the current offset of a file descriptor. - ;;; Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. - (@interface func (export "fd_tell") - (param $fd $fd) - ;;; The current offset of the file descriptor, relative to the start of the file. - (result $error (expected $filesize (error $errno))) - ) - - ;;; Write to a file descriptor. - ;;; Note: This is similar to `writev` in POSIX. - (@interface func (export "fd_write") - (param $fd $fd) - ;;; List of scatter/gather vectors from which to retrieve data. - (param $iovs $ciovec_array) - (result $error (expected $size (error $errno))) - ) - - ;;; Create a directory. - ;;; Note: This is similar to `mkdirat` in POSIX. - (@interface func (export "path_create_directory") - (param $fd $fd) - ;;; The path at which to create the directory. - (param $path string) - (result $error (expected (error $errno))) - ) - - ;;; Return the attributes of a file or directory. - ;;; Note: This is similar to `stat` in POSIX. - (@interface func (export "path_filestat_get") - (param $fd $fd) - ;;; Flags determining the method of how the path is resolved. - (param $flags $lookupflags) - ;;; The path of the file or directory to inspect. - (param $path string) - ;;; The buffer where the file's attributes are stored. - (result $error (expected $filestat (error $errno))) - ) - - ;;; Adjust the timestamps of a file or directory. - ;;; Note: This is similar to `utimensat` in POSIX. - (@interface func (export "path_filestat_set_times") - (param $fd $fd) - ;;; Flags determining the method of how the path is resolved. - (param $flags $lookupflags) - ;;; The path of the file or directory to operate on. - (param $path string) - ;;; The desired values of the data access timestamp. - (param $atim $timestamp) - ;;; The desired values of the data modification timestamp. - (param $mtim $timestamp) - ;;; A bitmask indicating which timestamps to adjust. - (param $fst_flags $fstflags) - (result $error (expected (error $errno))) - ) - - ;;; Create a hard link. - ;;; Note: This is similar to `linkat` in POSIX. - (@interface func (export "path_link") - (param $old_fd $fd) - ;;; Flags determining the method of how the path is resolved. - (param $old_flags $lookupflags) - ;;; The source path from which to link. - (param $old_path string) - ;;; The working directory at which the resolution of the new path starts. - (param $new_fd $fd) - ;;; The destination path at which to create the hard link. - (param $new_path string) - (result $error (expected (error $errno))) - ) - - ;;; Open a file or directory. - ;; - ;;; The returned file descriptor is not guaranteed to be the lowest-numbered - ;;; file descriptor not currently open; it is randomized to prevent - ;;; applications from depending on making assumptions about indexes, since this - ;;; is error-prone in multi-threaded contexts. The returned file descriptor is - ;;; guaranteed to be less than 2**31. - ;; - ;;; Note: This is similar to `openat` in POSIX. - (@interface func (export "path_open") - (param $fd $fd) - ;;; Flags determining the method of how the path is resolved. - (param $dirflags $lookupflags) - ;;; The relative path of the file or directory to open, relative to the - ;;; `path_open::fd` directory. - (param $path string) - ;;; The method by which to open the file. - (param $oflags $oflags) - ;;; The initial rights of the newly created file descriptor. The - ;;; implementation is allowed to return a file descriptor with fewer rights - ;;; than specified, if and only if those rights do not apply to the type of - ;;; file being opened. - ;; - ;;; The *base* rights are rights that will apply to operations using the file - ;;; descriptor itself, while the *inheriting* rights are rights that apply to - ;;; file descriptors derived from it. - (param $fs_rights_base $rights) - (param $fs_rights_inheriting $rights) - (param $fdflags $fdflags) - ;;; The file descriptor of the file that has been opened. - (result $error (expected $fd (error $errno))) - ) - - ;;; Read the contents of a symbolic link. - ;;; Note: This is similar to `readlinkat` in POSIX. - (@interface func (export "path_readlink") - (param $fd $fd) - ;;; The path of the symbolic link from which to read. - (param $path string) - ;;; The buffer to which to write the contents of the symbolic link. - (param $buf (@witx pointer u8)) - (param $buf_len $size) - ;;; The number of bytes placed in the buffer. - (result $error (expected $size (error $errno))) - ) - - ;;; Remove a directory. - ;;; Return `errno::notempty` if the directory is not empty. - ;;; Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. - (@interface func (export "path_remove_directory") - (param $fd $fd) - ;;; The path to a directory to remove. - (param $path string) - (result $error (expected (error $errno))) - ) - - ;;; Rename a file or directory. - ;;; Note: This is similar to `renameat` in POSIX. - (@interface func (export "path_rename") - (param $fd $fd) - ;;; The source path of the file or directory to rename. - (param $old_path string) - ;;; The working directory at which the resolution of the new path starts. - (param $new_fd $fd) - ;;; The destination path to which to rename the file or directory. - (param $new_path string) - (result $error (expected (error $errno))) - ) - - ;;; Create a symbolic link. - ;;; Note: This is similar to `symlinkat` in POSIX. - (@interface func (export "path_symlink") - ;;; The contents of the symbolic link. - (param $old_path string) - (param $fd $fd) - ;;; The destination path at which to create the symbolic link. - (param $new_path string) - (result $error (expected (error $errno))) - ) - - - ;;; Unlink a file. - ;;; Return `errno::isdir` if the path refers to a directory. - ;;; Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. - (@interface func (export "path_unlink_file") - (param $fd $fd) - ;;; The path to a file to unlink. - (param $path string) - (result $error (expected (error $errno))) - ) - - ;;; Concurrently poll for the occurrence of a set of events. - (@interface func (export "poll_oneoff") - ;;; The events to which to subscribe. - (param $in (@witx const_pointer $subscription)) - ;;; The events that have occurred. - (param $out (@witx pointer $event)) - ;;; Both the number of subscriptions and events. - (param $nsubscriptions $size) - ;;; The number of events stored. - (result $error (expected $size (error $errno))) - ) - - ;;; Terminate the process normally. An exit code of 0 indicates successful - ;;; termination of the program. The meanings of other values is dependent on - ;;; the environment. - (@interface func (export "proc_exit") - ;;; The exit code returned by the process. - (param $rval $exitcode) - (@witx noreturn) - ) - - ;;; Send a signal to the process of the calling thread. - ;;; Note: This is similar to `raise` in POSIX. - (@interface func (export "proc_raise") - ;;; The signal condition to trigger. - (param $sig $signal) - (result $error (expected (error $errno))) - ) - - ;;; Temporarily yield execution of the calling thread. - ;;; Note: This is similar to `sched_yield` in POSIX. - (@interface func (export "sched_yield") - (result $error (expected (error $errno))) - ) - - ;;; Write high-quality random data into a buffer. - ;;; This function blocks when the implementation is unable to immediately - ;;; provide sufficient high-quality random data. - ;;; This function may execute slowly, so when large mounts of random data are - ;;; required, it's advisable to use this function to seed a pseudo-random - ;;; number generator, rather than to provide the random data directly. - (@interface func (export "random_get") - ;;; The buffer to fill with random data. - (param $buf (@witx pointer u8)) - (param $buf_len $size) - (result $error (expected (error $errno))) - ) - - ;;; Receive a message from a socket. - ;;; Note: This is similar to `recv` in POSIX, though it also supports reading - ;;; the data into multiple buffers in the manner of `readv`. - (@interface func (export "sock_recv") - (param $fd $fd) - ;;; List of scatter/gather vectors to which to store data. - (param $ri_data $iovec_array) - ;;; Message flags. - (param $ri_flags $riflags) - ;;; Number of bytes stored in ri_data and message flags. - (result $error (expected (tuple $size $roflags) (error $errno))) - ) - - ;;; Send a message on a socket. - ;;; Note: This is similar to `send` in POSIX, though it also supports writing - ;;; the data from multiple buffers in the manner of `writev`. - (@interface func (export "sock_send") - (param $fd $fd) - ;;; List of scatter/gather vectors to which to retrieve data - (param $si_data $ciovec_array) - ;;; Message flags. - (param $si_flags $siflags) - ;;; Number of bytes transmitted. - (result $error (expected $size (error $errno))) - ) - - ;;; Shut down socket send and receive channels. - ;;; Note: This is similar to `shutdown` in POSIX. - (@interface func (export "sock_shutdown") - (param $fd $fd) - ;;; Which channels on the socket to shut down. - (param $how $sdflags) - (result $error (expected (error $errno))) - ) -)