From bacd7668a8b5ca9a41e2a99fd3828605d3286b90 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 16 Feb 2022 18:41:22 +0900 Subject: [PATCH] Initial commit Co-authored-by: Yusuke Endoh --- .github/workflows/main.yml | 47 + .github/workflows/release.yml | 145 ++ .gitignore | 1 + .gitmodules | 3 + Cargo.lock | 1674 +++++++++++++++++ Cargo.toml | 27 + LICENSE | 21 + Makefile | 6 + README.md | 97 + build.rs | 30 + clippy.toml | 1 + .../wasi-libc-trampoline-bindgen/Cargo.toml | 12 + crates/wasi-libc-trampoline-bindgen/WASI | 1 + .../wasi-libc-trampoline-bindgen/src/lib.rs | 63 + .../wasi-libc-trampoline-bindgen/src/main.rs | 6 + .../src/object_link.rs | 158 ++ .../src/wrapper.rs | 550 ++++++ crates/wasi-vfs-cli/Cargo.toml | 12 + crates/wasi-vfs-cli/src/bin/wasi-vfs.rs | 6 + crates/wasi-vfs-cli/src/lib.rs | 71 + crates/wasi-vfs-cli/src/module_link.rs | 60 + docs/overview.png | Bin 0 -> 72439 bytes examples/.gitignore | 5 + examples/getline.c | 23 + examples/mnt/hello.txt | 1 + rust-toolchain.toml | 3 + src/alloc.rs | 77 + src/embed/linked_storage.c | 183 ++ src/embed/linked_storage.rs | 237 +++ src/embed/mod.rs | 369 ++++ src/init.c | 18 + src/lib.rs | 435 +++++ src/trace.rs | 51 + src/trampoline_generated.c | 199 ++ src/trampoline_generated.rs | 1005 ++++++++++ src/trampoline_generated_legacy_wasi_libc.c | 199 ++ src/wasi_snapshot_preview1.rs | 904 +++++++++ tests/run-make/.gitignore | 1 + tests/run-make/canonical-path/Makefile | 12 + tests/run-make/canonical-path/main.c | 11 + tests/run-make/canonical-path/mnt/a/b/c.txt | 0 tests/run-make/chdir-emulation/Makefile | 11 + tests/run-make/chdir-emulation/main.c | 42 + tests/run-make/chdir-emulation/mnt/hello.txt | 1 + tests/run-make/check.h | 97 + tests/run-make/libc-api/Makefile | 11 + tests/run-make/libc-api/hello.txt | 1 + tests/run-make/libc-api/main.c | 34 + tests/run-make/libc-api/mnt/hello.txt | 1 + tests/run-make/libc-api/mnt/subdir/inner.txt | 1 + tests/run-make/mapdir-root/Makefile | 11 + tests/run-make/mapdir-root/main.c | 9 + tests/run-make/mapdir-root/mnt/hello.txt | 0 tests/run-make/minimum-link/Makefile | 11 + tests/run-make/minimum-link/main.c | 1 + tests/run-make/multi-mapdir/Makefile | 19 + tests/run-make/multi-mapdir/main.c | 26 + tests/run-make/multi-mapdir/mnt0/hello.txt | 0 tests/run-make/multi-mapdir/mnt1/goodbye.txt | 0 tests/run-make/pack-twice/Makefile | 18 + tests/run-make/pack-twice/main.c | 22 + tests/run-make/pack-twice/mnt0/hello.txt | 0 tests/run-make/pack-twice/mnt1/goodbye.txt | 0 tests/run-make/pack-twice/mnt1_1/x.txt | 0 tests/run-make/parent-dir/Makefile | 11 + tests/run-make/parent-dir/main.c | 22 + tests/run-make/parent-dir/usr/local/bin/yay | 0 tests/run-make/parent-dir/usr/local/hey | 0 tests/run-make/reactor-model/Makefile | 9 + tests/run-make/reactor-model/check.js | 17 + tests/run-make/reactor-model/main.c | 14 + tests/run-make/reactor-model/mnt/hello.txt | 0 tests/run-make/tools.mk | 19 + tools/run-make-test.sh | 17 + 74 files changed, 7149 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 build.rs create mode 100644 clippy.toml create mode 100644 crates/wasi-libc-trampoline-bindgen/Cargo.toml create mode 160000 crates/wasi-libc-trampoline-bindgen/WASI create mode 100644 crates/wasi-libc-trampoline-bindgen/src/lib.rs create mode 100644 crates/wasi-libc-trampoline-bindgen/src/main.rs create mode 100644 crates/wasi-libc-trampoline-bindgen/src/object_link.rs create mode 100644 crates/wasi-libc-trampoline-bindgen/src/wrapper.rs create mode 100644 crates/wasi-vfs-cli/Cargo.toml create mode 100644 crates/wasi-vfs-cli/src/bin/wasi-vfs.rs create mode 100644 crates/wasi-vfs-cli/src/lib.rs create mode 100644 crates/wasi-vfs-cli/src/module_link.rs create mode 100644 docs/overview.png create mode 100644 examples/.gitignore create mode 100644 examples/getline.c create mode 100644 examples/mnt/hello.txt create mode 100644 rust-toolchain.toml create mode 100644 src/alloc.rs create mode 100644 src/embed/linked_storage.c create mode 100644 src/embed/linked_storage.rs create mode 100644 src/embed/mod.rs create mode 100644 src/init.c create mode 100644 src/lib.rs create mode 100644 src/trace.rs create mode 100644 src/trampoline_generated.c create mode 100644 src/trampoline_generated.rs create mode 100644 src/trampoline_generated_legacy_wasi_libc.c create mode 100644 src/wasi_snapshot_preview1.rs create mode 100644 tests/run-make/.gitignore create mode 100644 tests/run-make/canonical-path/Makefile create mode 100644 tests/run-make/canonical-path/main.c create mode 100644 tests/run-make/canonical-path/mnt/a/b/c.txt create mode 100644 tests/run-make/chdir-emulation/Makefile create mode 100644 tests/run-make/chdir-emulation/main.c create mode 100644 tests/run-make/chdir-emulation/mnt/hello.txt create mode 100644 tests/run-make/check.h create mode 100644 tests/run-make/libc-api/Makefile create mode 100644 tests/run-make/libc-api/hello.txt create mode 100644 tests/run-make/libc-api/main.c create mode 100644 tests/run-make/libc-api/mnt/hello.txt create mode 100644 tests/run-make/libc-api/mnt/subdir/inner.txt create mode 100644 tests/run-make/mapdir-root/Makefile create mode 100644 tests/run-make/mapdir-root/main.c create mode 100644 tests/run-make/mapdir-root/mnt/hello.txt create mode 100644 tests/run-make/minimum-link/Makefile create mode 100644 tests/run-make/minimum-link/main.c create mode 100644 tests/run-make/multi-mapdir/Makefile create mode 100644 tests/run-make/multi-mapdir/main.c create mode 100644 tests/run-make/multi-mapdir/mnt0/hello.txt create mode 100644 tests/run-make/multi-mapdir/mnt1/goodbye.txt create mode 100644 tests/run-make/pack-twice/Makefile create mode 100644 tests/run-make/pack-twice/main.c create mode 100644 tests/run-make/pack-twice/mnt0/hello.txt create mode 100644 tests/run-make/pack-twice/mnt1/goodbye.txt create mode 100644 tests/run-make/pack-twice/mnt1_1/x.txt create mode 100644 tests/run-make/parent-dir/Makefile create mode 100644 tests/run-make/parent-dir/main.c create mode 100644 tests/run-make/parent-dir/usr/local/bin/yay create mode 100644 tests/run-make/parent-dir/usr/local/hey create mode 100644 tests/run-make/reactor-model/Makefile create mode 100644 tests/run-make/reactor-model/check.js create mode 100644 tests/run-make/reactor-model/main.c create mode 100644 tests/run-make/reactor-model/mnt/hello.txt create mode 100644 tests/run-make/tools.mk create mode 100755 tools/run-make-test.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b402f6e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,47 @@ +name: CI +on: [push] + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - run: cargo check --all + - run: cargo fmt --all -- --check + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Install wasmtime + run: | + set -e + curl -L https://github.com/bytecodealliance/wasmtime/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz | tar xJf - + echo "$PWD/wasmtime-${WASMTIME_VERSION}-x86_64-linux" >> $GITHUB_PATH + env: + WASMTIME_VERSION: v0.34.0 + - name: Install wasi-sdk + run: | + set -e + wasi_sdk_deb="wasi-sdk_${WASI_SDK_VERSION_MAJOR}.${WASI_SDK_VERSION_MINOR}_amd64.deb" + wget "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION_MAJOR}/${wasi_sdk_deb}" + sudo dpkg -i "$wasi_sdk_deb" + echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV + env: + WASI_SDK_VERSION_MAJOR: 14 + WASI_SDK_VERSION_MINOR: 0 + - run: CARGO_TARGET_WASM32_WASI_RUNNER=wasmtime cargo test --target wasm32-wasi + - run: cargo build --target wasm32-unknown-unknown + - run: LIB_WASI_VFS_A=$PWD/target/wasm32-unknown-unknown/debug/libwasi_vfs.a ./tools/run-make-test.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3a346c6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,145 @@ +name: Release +on: + push: + tags: + - 'v*' +jobs: + build-wasi-vfs-cli: + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - x86_64-pc-windows-gnu + - x86_64-apple-darwin + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: x86_64-pc-windows-gnu + os: ubuntu-latest + - target: x86_64-apple-darwin + os: macos-latest + - target: aarch64-apple-darwin + os: macos-latest + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: actions-rs/cargo@v1.0.1 + with: + command: build + args: --release --target=${{ matrix.target }} --package wasi-vfs-cli + use-cross: true + + - run: | + zip --junk-paths wasi-vfs-cli-${{ matrix.target }} target/${{ matrix.target }}/release/wasi-vfs{,.exe} + - uses: actions/upload-artifact@v1 + with: + name: wasi-vfs-cli-${{ matrix.target }} + path: wasi-vfs-cli-${{ matrix.target }}.zip + build-libwasi-vfs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Install wasi-sdk + run: | + set -e + wasi_sdk_deb="wasi-sdk_${WASI_SDK_VERSION_MAJOR}.${WASI_SDK_VERSION_MINOR}_amd64.deb" + wget "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION_MAJOR}/${wasi_sdk_deb}" + sudo dpkg -i "$wasi_sdk_deb" + echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV + env: + WASI_SDK_VERSION_MAJOR: 14 + WASI_SDK_VERSION_MINOR: 0 + - uses: actions-rs/cargo@v1.0.1 + with: + command: build + args: --release --target=wasm32-unknown-unknown + - run: | + zip --junk-paths libwasi_vfs-wasm32-unknown-unknown target/wasm32-unknown-unknown/release/libwasi_vfs.a + - uses: actions/upload-artifact@v1 + with: + name: libwasi_vfs-wasm32-unknown-unknown + path: libwasi_vfs-wasm32-unknown-unknown.zip + + create-release: + needs: [build-wasi-vfs-cli, build-libwasi-vfs] + runs-on: ubuntu-latest + steps: + - id: create-release + uses: actions/create-release@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + - run: | + echo '${{ steps.create-release.outputs.upload_url }}' > release_upload_url.txt + - uses: actions/upload-artifact@v1 + with: + name: create-release + path: release_upload_url.txt + upload-cli-release: + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - x86_64-pc-windows-gnu + - x86_64-apple-darwin + - aarch64-apple-darwin + needs: [create-release] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v1 + with: + name: create-release + - id: upload-url + run: | + echo "::set-output name=url::$(cat create-release/release_upload_url.txt)" + - uses: actions/download-artifact@v1 + with: + name: wasi-vfs-cli-${{ matrix.target }} + - uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.upload-url.outputs.url }} + asset_path: ./wasi-vfs-cli-${{ matrix.target }}/wasi-vfs-cli-${{ matrix.target }}.zip + asset_name: wasi-vfs-cli-${{ matrix.target }}.zip + asset_content_type: application/zip + + upload-lib-release: + needs: [create-release] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v1 + with: + name: create-release + - id: upload-url + run: | + echo "::set-output name=url::$(cat create-release/release_upload_url.txt)" + - uses: actions/download-artifact@v1 + with: + name: libwasi_vfs-wasm32-unknown-unknown + - uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.upload-url.outputs.url }} + asset_path: ./libwasi_vfs-wasm32-unknown-unknown/libwasi_vfs-wasm32-unknown-unknown.zip + asset_name: libwasi_vfs-wasm32-unknown-unknown.zip + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ca59734 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "crates/wasi-libc-trampoline-bindgen/WASI"] + path = crates/wasi-libc-trampoline-bindgen/WASI + url = https://github.com/WebAssembly/WASI.git diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..82b8377 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1674 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ambient-authority" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7" + +[[package]] +name = "async-trait" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cap-fs-ext" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8499797f7e264c83334d9fc98b2c9889ebe5839514a14d81769ca09d71fd1d" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "rustc_version", + "winapi", +] + +[[package]] +name = "cap-primitives" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5998b8b3a49736500aec3c123fa3f6f605a125b41a6df725e6b7c924a612ab4" +dependencies = [ + "ambient-authority", + "errno", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustc_version", + "rustix", + "winapi", + "winapi-util", + "winx", +] + +[[package]] +name = "cap-rand" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fafda903eb4a85903b106439cf62524275f3ae0609bb9e1ae9da7e7c26d4150c" +dependencies = [ + "ambient-authority", + "rand", +] + +[[package]] +name = "cap-std" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "811de89a7ede4ba32f2b75fe5c668a534da24942d81c081248a9d2843ebd517d" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "ipnet", + "rustc_version", + "rustix", +] + +[[package]] +name = "cap-time-ext" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85f263d62447efe8829efdf947bbb4824ba2a3e2852b3be1d62f76fc05c326b0" +dependencies = [ + "cap-primitives", + "once_cell", + "rustix", + "winx", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebddaa5d12cb299b0bc7c930aff12c5591d4ba9aa84eea637807e07283b900aa" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1daf5641177162644b521b64418564b8ed5deb126275a4d91472d13e7c72df" +dependencies = [ + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli", + "log", + "regalloc", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001c1e9e540940c81596e547e732f99c2146c21ea7e82da99be961a1e86feefa" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ebaf07b5d7501cc606f41c81333bd63a5a17eb501362ccb10bc8ff5c03d0232" + +[[package]] +name = "cranelift-entity" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27ada0e3ffe5325179fc750252c18d614fa5470d595ce5c8a794c495434d80a" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2912c0eec9fd3df2dcf82b02b642caaa85d762b84ac5a3b27bc93a07eeeb64e2" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-native" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd20f78f378f55a70738a2eb9815dcd7e8455ff091b70701cfd086dd44927da" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.79.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353abcef10511d565b25bd00f3d7b1babcc040d9644c5259467c9a514dc945f0" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser 0.81.0", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "file-per-thread-logger" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "fs-set-times" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa838950e8e36a567ce96a945c303e88d9916ff97df27c315a0d263a72bd816f" +dependencies = [ + "io-lifetimes", + "rustix", + "winapi", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "io-extras" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1d9a66d8b0312e3601a04a2dcf8f0ddd873319560ddeabe2110fa1e5af781a" +dependencies = [ + "io-lifetimes", + "rustc_version", + "winapi", +] + +[[package]] +name = "io-lifetimes" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278e90d6f8a6c76a8334b336e306efa3c5f2b604048cbfd486d6f49878e3af14" +dependencies = [ + "rustc_version", + "winapi", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "linux-raw-sys" +version = "0.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a261afc61b7a5e323933b402ca6a1765183687c614789b1e4db7762ed4230bca" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "crc32fast", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "psm" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eca0fa5dd7c4c96e184cec588f0b1db1ee3165e678db21c09793105acb17e6f" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regalloc" +version = "0.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d808cff91dfca7b239d40b972ba628add94892b1d9e19a842aedc5cfae8ab1a" +dependencies = [ + "log", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c44018277ec7195538f5631b90def7ad975bb46370cb0f4eff4012de9333f8" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "itoa", + "libc", + "linux-raw-sys", + "once_cell", + "rustc_version", + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "system-interface" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1b5163055c386394170493ec1827cf7975035dc0bb23dcb7070bd1b1f672baa" +dependencies = [ + "atty", + "bitflags", + "cap-fs-ext", + "cap-std", + "io-lifetimes", + "rustc_version", + "rustix", + "winapi", + "winx", +] + +[[package]] +name = "target-lexicon" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "git+https://github.com/bytecodealliance/wasi.git?rev=d3c7a34193cb33d994b11104b22d234530232b5f#d3c7a34193cb33d994b11104b22d234530232b5f" + +[[package]] +name = "wasi-cap-std-sync" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0981a09e90eec8032ec7a722cf7ba04120eb10bd456bc3c108c59abfa9ba4730" +dependencies = [ + "anyhow", + "async-trait", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-lifetimes", + "lazy_static", + "rustix", + "system-interface", + "tracing", + "wasi-common", + "winapi", +] + +[[package]] +name = "wasi-common" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a72c05e8e9de362269d44908bf282c346281ef973450c9c6319adfebd5e096c" +dependencies = [ + "anyhow", + "bitflags", + "cap-rand", + "cap-std", + "rustix", + "thiserror", + "tracing", + "wiggle", + "winapi", +] + +[[package]] +name = "wasi-libc-trampoline-bindgen" +version = "0.1.0" +dependencies = [ + "heck", + "structopt", + "witx 0.9.1", +] + +[[package]] +name = "wasi-vfs" +version = "0.1.0" +dependencies = [ + "cc", + "wasi 0.10.2+wasi-snapshot-preview1 (git+https://github.com/bytecodealliance/wasi.git?rev=d3c7a34193cb33d994b11104b22d234530232b5f)", + "wee_alloc", +] + +[[package]] +name = "wasi-vfs-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "structopt", + "wizer", +] + +[[package]] +name = "wasm-encoder" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2caacc74c68c74f0008c4055cdf509c43e623775eaf73323bb818dcf666ed9bd" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser" +version = "0.78.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" + +[[package]] +name = "wasmparser" +version = "0.81.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc" + +[[package]] +name = "wasmtime" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56ceaa60d3019887d6ba5768860fac99f5a6511453e183cb3ba2aaafd9411f3" +dependencies = [ + "anyhow", + "async-trait", + "backtrace", + "bincode", + "cfg-if 1.0.0", + "cpp_demangle", + "indexmap", + "lazy_static", + "libc", + "log", + "object", + "paste", + "psm", + "rayon", + "region", + "rustc-demangle", + "serde", + "target-lexicon", + "wasmparser 0.81.0", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "winapi", +] + +[[package]] +name = "wasmtime-cache" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d45c2c7ed7a2700ff012e97e12324d2ba0bdd943e50c0d3a95b582ef2bfdca4" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix", + "serde", + "sha2", + "toml", + "winapi", + "zstd", +] + +[[package]] +name = "wasmtime-cranelift" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5793c2d14c7e2b962d1d79408df011190ec8f6214a01efd676f5e2266c44bc8" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "more-asserts", + "object", + "target-lexicon", + "thiserror", + "wasmparser 0.81.0", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79131537408f938501b4f540ae0f61b456d9962c2bb590edefb904cf7d1e5f54" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "more-asserts", + "object", + "serde", + "target-lexicon", + "thiserror", + "wasmparser 0.81.0", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9bf33ebebf88a353be8961ed87cf2780091e0c166c3ab3a23a3d8304f964a" +dependencies = [ + "cc", + "rustix", + "winapi", +] + +[[package]] +name = "wasmtime-jit" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8031b6e83071b40b0139924024ee0d2e11f65f7677d7b028720df55610cbf994" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if 1.0.0", + "gimli", + "object", + "region", + "rustix", + "serde", + "target-lexicon", + "thiserror", + "wasmtime-environ", + "wasmtime-runtime", + "winapi", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c9412d752736938c2a57228fb95e13d40bdbc879ac741874e7f7b49c198ffa" +dependencies = [ + "anyhow", + "backtrace", + "cc", + "cfg-if 1.0.0", + "indexmap", + "lazy_static", + "libc", + "log", + "mach", + "memoffset", + "more-asserts", + "rand", + "region", + "rustix", + "thiserror", + "wasmtime-environ", + "wasmtime-fiber", + "winapi", +] + +[[package]] +name = "wasmtime-types" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1602b6ae8b901e60e8b9d51cadbf51a0421b7e67bf4cbe2e647695783fd9d45" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser 0.81.0", +] + +[[package]] +name = "wasmtime-wasi" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2a67db8d3ee8506154c9e393902a56b3ce8e3aa6f76110a91dbadd7e9dedd4" +dependencies = [ + "anyhow", + "wasi-cap-std-sync", + "wasi-common", + "wasmtime", + "wiggle", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "39.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +dependencies = [ + "leb128", + "memchr", + "unicode-width", +] + +[[package]] +name = "wat" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +dependencies = [ + "wast 39.0.0", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "wiggle" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40607255656042b52686d96c05852584d1b4f157a6e261096f0ce702fcf1c111" +dependencies = [ + "anyhow", + "async-trait", + "bitflags", + "thiserror", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a4f79499ff19b69580f3fbcb569b92ce7372ecc83e7810e731f9edd55e8f05" +dependencies = [ + "anyhow", + "heck", + "proc-macro2", + "quote", + "shellexpand", + "syn", + "witx 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wiggle-macro" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8866a79f7a0396c1914449268dc61da65e68ff098023fccc459f7400b963c60a" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winx" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afba0891d41a50943c32fcea61e124b9dd5755275054b0a3e1e1eba26e671137" +dependencies = [ + "bitflags", + "io-lifetimes", + "winapi", +] + +[[package]] +name = "witx" +version = "0.9.1" +dependencies = [ + "anyhow", + "log", + "rayon", + "thiserror", + "wast 35.0.2", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror", + "wast 35.0.2", +] + +[[package]] +name = "wizer" +version = "1.3.5" +source = "git+https://github.com/bytecodealliance/wizer.git?rev=1802237aeb8c19ff9448d67d280935bc10110d32#1802237aeb8c19ff9448d67d280935bc10110d32" +dependencies = [ + "anyhow", + "cap-std", + "log", + "rayon", + "wasi-cap-std-sync", + "wasm-encoder", + "wasmparser 0.78.2", + "wasmtime", + "wasmtime-wasi", +] + +[[package]] +name = "zstd" +version = "0.9.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.3+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +dependencies = [ + "cc", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..64974da --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "wasi-vfs" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[lib] +crate-type = ["staticlib", "cdylib"] + +[dependencies] +wasi = { git = "https://github.com/bytecodealliance/wasi.git", rev = "d3c7a34193cb33d994b11104b22d234530232b5f" } +wee_alloc = { version = "0.4.5", optional = true } + + +[build-dependencies] +cc = "1.0" + +[workspace] +members = [ + "crates/wasi-libc-trampoline-bindgen", + "crates/wasi-vfs-cli", +] + +[features] +trace-syscall = [] +legacy-wasi-libc = [] +module-linking = ["wee_alloc"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12dbc37 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Yuta Saito + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..caf49d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +TRAMPOLINE_GEN = WASI_REPO=./crates/wasi-libc-trampoline-bindgen/WASI cargo run --package wasi-libc-trampoline-bindgen -- + +generate-trampoline: + $(TRAMPOLINE_GEN) wrapper > ./src/trampoline_generated.rs + $(TRAMPOLINE_GEN) object-link latest > ./src/trampoline_generated.c + $(TRAMPOLINE_GEN) object-link legacy > ./src/trampoline_generated_legacy_wasi_libc.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c20ea9 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# wasi-vfs + +A virtual filesystem layer for WASI. + +> **NOTICE**: This project currently supports only WASI applications on the top of [wasi-libc](https://github.com/webassembly/wasi-libc) + +This project provides a language and host-agnostic virtual filesystem layer for WASI. + +
+ +
+ +## Supported filesystems + +- **Embedded file system**: a read only file system embedded in the `.wasm` binary. +- to be implemented more... + +## Building + +To build the project, you need to install the [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk) version 14.0 or later. + +```console +$ export WASI_SDK_PATH=/path/to/wasi-sdk +$ cargo build --target wasm32-unknown-unknown +``` + +## Demo + +### Prerequisites + +Set `$WASI_SDK_PATH` environment variable to the path of the `wasi-sdk` (version 14 or later). + +### Build and run WASI application + +```console +$ git clone https://github.com/kateinoigakukun/wasi-vfs.git +$ cd wasi-vfs + +# Build libwasi_vfs.a +$ cargo build --target wasm32-unknown-unknown + +# Build a WASI app with libwasi_vfs.a +$ $WASI_SDK_PATH/bin/clang -target wasm32-unknown-wasi -o getline.wasm examples/getline.c ./target/wasm32-unknown-unknown/debug/libwasi_vfs.a + +# Run the WASI app with --mapdir +$ wasmtime run --mapdir /::./examples/mnt getline.wasm -- /hello.txt +Hello + +# Pack ./examples/mnt directory into a WASM binary +$ cargo run -p wasi-vfs-cli -- pack getline.wasm --mapdir /::./examples/mnt -o getline.packed.wasm + +# Run the WASM binary again without --mapdir +$ wasmtime run getline.packed.wasm -- /hello.txt +Hello +``` + +## Testing + +### Unit tests + +```console +$ CARGO_TARGET_WASM32_WASI_RUNNER=wasmtime cargo test --target wasm32-wasi +``` + +### End-to-end tests + +```console +$ cargo build --target wasm32-unknown-unknown +$ LIB_WASI_VFS_A=$PWD/target/wasm32-unknown-unknown/debug/libwasi_vfs.a ./tools/run-make-test.sh +``` + + +## How does it work? + +`wasi-vfs pack` command is a wrapper of [`wizer`](https://github.com/bytecodealliance/wizer/), which is a pre-initializer for Wasm applications. +The initialization process scans the mapped directories, then copies them into in-memory virtual filesystem. + +## Limitations + +Currently, this project only supports WASI applications on the top of wasi-libc because of the following reasons: + +This project depends on `wasm-ld` and `wasi-libc`'s imported symbol behavior. + `wasi-libc` declares some external symbols to import WASI functions in C like below. When `__imported_wasi_snapshot_preview1_fd_read` is not defined in any input object files, `wasm-ld` produces a `(import "wasi_snapshot_preview1" "fd_read")` entry. This is how `wasi-libc` calls WASI functions. + +```c +int32_t __imported_wasi_snapshot_preview1_fd_read(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) __attribute__(( + __import_module__("wasi_snapshot_preview1"), + __import_name__("fd_read") +)); +``` + +This project exploits that external symbols to hook WASI function calls by defining them in `libwasi_vfs.a`. If those symbols are defined, `wasm-ld` doesn't produce import entries, and it links symbols normally. + +Therefore, this project currently doesn't support Rust application, which calls WASI functions directly without using `wasi-libc`. + + +After [module-linking](https://github.com/WebAssembly/module-linking/blob/main/design/proposals/module-linking/Explainer.md) and [interface-types](https://github.com/WebAssembly/interface-types) will be merged, and WASI will adopt shared-nothing architecture, this project will be able to support all WASI applications. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..359cf4a --- /dev/null +++ b/build.rs @@ -0,0 +1,30 @@ +use std::env; + +fn main() { + let triple = env::var("TARGET").expect("TARGET was not set"); + if !triple.starts_with("wasm32-") { + println!("wasi-vfs only supports wasm32-unknown-unknown"); + return; + } + let wasi_sdk = env::var("WASI_SDK_PATH").expect("WASI_SDK_PATH is not set"); + let mut build = cc::Build::new(); + build + .compiler(format!("{}/bin/clang", wasi_sdk)) + .archiver(format!("{}/bin/llvm-ar", wasi_sdk)) + .file("src/init.c"); + + let trampoline_file = if env::var("CARGO_FEATURE_LEGACY_WASI_LIBC").is_ok() { + "src/trampoline_generated_legacy_wasi_libc.c" + } else { + "src/trampoline_generated.c" + }; + build.file(trampoline_file); + + println!("cargo:rerun-if-changed=src/init.c"); + println!("cargo:rerun-if-changed={}", trampoline_file); + + build.file("src/embed/linked_storage.c"); + println!("cargo:rerun-if-changed=src/embed/linked_storage.c"); + + build.compile("wasi_vfs_c"); +} diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..1590630 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +too-many-arguments-threshold = 10 diff --git a/crates/wasi-libc-trampoline-bindgen/Cargo.toml b/crates/wasi-libc-trampoline-bindgen/Cargo.toml new file mode 100644 index 0000000..1d84802 --- /dev/null +++ b/crates/wasi-libc-trampoline-bindgen/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wasi-libc-trampoline-bindgen" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +witx = { path = "WASI/tools/witx" } +heck = "0.3" +structopt = "0.3.21" diff --git a/crates/wasi-libc-trampoline-bindgen/WASI b/crates/wasi-libc-trampoline-bindgen/WASI new file mode 160000 index 0000000..326dee6 --- /dev/null +++ b/crates/wasi-libc-trampoline-bindgen/WASI @@ -0,0 +1 @@ +Subproject commit 326dee685d352bc3845b999099f28b0a48734d46 diff --git a/crates/wasi-libc-trampoline-bindgen/src/lib.rs b/crates/wasi-libc-trampoline-bindgen/src/lib.rs new file mode 100644 index 0000000..e6cc460 --- /dev/null +++ b/crates/wasi-libc-trampoline-bindgen/src/lib.rs @@ -0,0 +1,63 @@ +use std::error::Error; + +use object_link::AbiVariant; +use structopt::StructOpt; + +mod object_link; +mod wrapper; + +pub(crate) const WASI_HOOK_FUNCTIONS: &[&str] = &[ + "fd_advise", + "fd_allocate", + "fd_close", + "fd_datasync", + "fd_fdstat_get", + "fd_fdstat_set_flags", + "fd_fdstat_set_rights", + "fd_filestat_get", + "fd_filestat_set_size", + "fd_filestat_set_times", + "fd_pread", + "fd_prestat_dir_name", + "fd_prestat_get", + "fd_pwrite", + "fd_read", + "fd_readdir", + "fd_renumber", + "fd_seek", + "fd_sync", + "fd_tell", + "fd_write", + "path_create_directory", + "path_filestat_get", + "path_filestat_set_times", + "path_link", + "path_open", + "path_readlink", + "path_remove_directory", + "path_rename", + "path_symlink", + "path_unlink_file", + "poll_oneoff", +]; + +#[derive(StructOpt)] +pub enum App { + Wrapper, + ObjectLink { abi_variant: AbiVariant }, +} + +impl App { + pub fn execute(self) -> Result<(), Box> { + let witx_paths = witx::phases::snapshot().unwrap(); + match self { + App::Wrapper => { + print!("{}", wrapper::generate(&witx_paths)); + } + App::ObjectLink { abi_variant } => { + print!("{}", object_link::generate(&witx_paths, abi_variant)); + } + } + Ok(()) + } +} diff --git a/crates/wasi-libc-trampoline-bindgen/src/main.rs b/crates/wasi-libc-trampoline-bindgen/src/main.rs new file mode 100644 index 0000000..f5313e9 --- /dev/null +++ b/crates/wasi-libc-trampoline-bindgen/src/main.rs @@ -0,0 +1,6 @@ +use structopt::StructOpt; +use wasi_libc_trampoline_bindgen::App; + +fn main() { + App::from_args().execute().unwrap(); +} diff --git a/crates/wasi-libc-trampoline-bindgen/src/object_link.rs b/crates/wasi-libc-trampoline-bindgen/src/object_link.rs new file mode 100644 index 0000000..52e20a8 --- /dev/null +++ b/crates/wasi-libc-trampoline-bindgen/src/object_link.rs @@ -0,0 +1,158 @@ +use heck::*; +use std::{path::Path, str::FromStr}; +use witx::*; + +#[derive(Debug, Clone, Copy)] +pub enum AbiVariant { + Legacy, + Latest, +} + +impl FromStr for AbiVariant { + type Err = String; + fn from_str(day: &str) -> Result { + match day { + "legacy" => Ok(Self::Legacy), + "latest" => Ok(Self::Latest), + other => Err(format!("unsupported abi variant {}", other)), + } + } +} + +pub fn generate>(witx_paths: &[P], variant: AbiVariant) -> String { + let doc = witx::load(witx_paths).unwrap(); + + let mut raw = String::new(); + raw.push_str( + "\ +// This file is automatically generated, DO NOT EDIT +// +// To regenerate this file run the `crates/wasi-libc-trampoline-bindgen` command + +#include + +", + ); + for m in doc.modules() { + render_module(&m, variant, &mut raw); + raw.push('\n'); + } + + raw +} + +trait RenderC { + fn render_c(&self, src: &mut String); +} + +impl RenderC for IntRepr { + fn render_c(&self, src: &mut String) { + match self { + IntRepr::U8 => src.push_str("uint8_t"), + IntRepr::U16 => src.push_str("uint16_t"), + IntRepr::U32 => src.push_str("uint32_t"), + IntRepr::U64 => src.push_str("uint64_t"), + } + } +} + +impl RenderC for WasmType { + fn render_c(&self, src: &mut String) { + match self { + WasmType::I32 => src.push_str("int32_t"), + WasmType::I64 => src.push_str("int64_t"), + WasmType::F32 => src.push_str("float"), + WasmType::F64 => src.push_str("double"), + } + } +} + +fn render_module(module: &Module, variant: AbiVariant, src: &mut String) { + for f in module.funcs() { + if !crate::WASI_HOOK_FUNCTIONS.contains(&f.name.as_str()) { + continue; + } + let f_name = f.name.as_str(); + + let abi_name = match variant { + AbiVariant::Latest => { + let mut name = String::new(); + name.push_str("__imported_"); + name.push_str(&module.name.as_str().to_snake_case()); + name.push('_'); + name.push_str(&f_name.to_snake_case()); + name + } + AbiVariant::Legacy => { + let mut name = String::new(); + name.push_str("__wasi_"); + name.push_str(&f_name.to_snake_case()); + name + } + }; + + render_libc_hook_point( + &*f, + &abi_name, + &format!("wasi_vfs_{}", f_name.to_snake_case()), + src, + ); + src.push('\n'); + } +} + +fn render_libc_hook_point( + func: &InterfaceFunc, + name: &str, + trampoline_name: &str, + src: &mut String, +) { + let (params, results) = func.wasm_signature(); + assert!(results.len() <= 1); + src.push_str("__attribute__((weak))\n"); + results[0].render_c(src); + let params_str = params + .iter() + .enumerate() + .map(|(i, param_ty)| { + let mut param = String::new(); + param_ty.render_c(&mut param); + param.push(' '); + param.push_str("arg"); + param.push_str(&i.to_string()); + param + }) + .collect::>() + .join(", "); + src.push(' '); + src.push_str(name); + src.push('('); + src.push_str(¶ms_str); + src.push(')'); + src.push_str(" {\n"); + + src.push_str(" extern "); + results[0].render_c(src); + src.push(' '); + src.push_str(trampoline_name); + src.push('('); + src.push_str(¶ms_str); + src.push(')'); + src.push_str(";\n"); + + src.push_str(" return "); + src.push_str(trampoline_name); + src.push('('); + src.push_str( + ¶ms + .iter() + .enumerate() + .map(|(i, _)| format!("arg{}", i)) + .collect::>() + .join(", "), + ); + src.push(')'); + src.push_str(";\n"); + + src.push_str("}\n"); +} diff --git a/crates/wasi-libc-trampoline-bindgen/src/wrapper.rs b/crates/wasi-libc-trampoline-bindgen/src/wrapper.rs new file mode 100644 index 0000000..1896dda --- /dev/null +++ b/crates/wasi-libc-trampoline-bindgen/src/wrapper.rs @@ -0,0 +1,550 @@ +use heck::*; +use std::io::{Read, Write}; +use std::mem; +use std::path::Path; +use std::process::{Command, Stdio}; +use witx::*; + +pub fn generate>(witx_paths: &[P]) -> String { + let doc = witx::load(witx_paths).unwrap(); + + let mut raw = String::new(); + raw.push_str( + "\ +// This file is automatically generated, DO NOT EDIT +// +// To regenerate this file run the `crates/wasi-libc-trampoline-bindgen` command +#![allow(unused_variables)] +use wasi::*; +use crate::UserFd; + +", + ); + for m in doc.modules() { + m.render(&mut raw); + raw.push('\n'); + } + + let mut rustfmt = Command::new("rustfmt") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + rustfmt + .stdin + .take() + .unwrap() + .write_all(raw.as_bytes()) + .unwrap(); + let mut ret = String::new(); + rustfmt + .stdout + .take() + .unwrap() + .read_to_string(&mut ret) + .unwrap(); + let status = rustfmt.wait().unwrap(); + assert!(status.success()); + ret +} + +trait Render { + fn render(&self, src: &mut String); +} + +impl Render for IntRepr { + fn render(&self, src: &mut String) { + match self { + IntRepr::U8 => src.push_str("u8"), + IntRepr::U16 => src.push_str("u16"), + IntRepr::U32 => src.push_str("u32"), + IntRepr::U64 => src.push_str("u64"), + } + } +} + +impl Render for TypeRef { + fn render(&self, src: &mut String) { + match self { + TypeRef::Name(t) => { + src.push_str(&t.name.as_str().to_camel_case()); + if let Type::List(_) = &**t.type_() { + src.push_str("<'_>"); + } + } + TypeRef::Value(v) => match &**v { + Type::Builtin(t) => t.render(src), + Type::List(t) => match &**t.type_() { + Type::Builtin(BuiltinType::Char) => src.push_str("&str"), + _ => { + src.push_str("&'a ["); + t.render(src); + src.push(']'); + } + }, + Type::Pointer(t) => { + src.push_str("*mut "); + t.render(src); + } + Type::ConstPointer(t) => { + src.push_str("*const "); + t.render(src); + } + Type::Variant(v) if v.is_bool() => src.push_str("bool"), + Type::Variant(v) => match v.as_expected() { + Some((ok, err)) => { + src.push_str("Result<"); + match ok { + Some(ty) => ty.render(src), + None => src.push_str("()"), + } + src.push(','); + match err { + Some(ty) => ty.render(src), + None => src.push_str("()"), + } + src.push('>'); + } + None => { + panic!("unsupported anonymous variant") + } + }, + Type::Record(r) if r.is_tuple() => { + src.push('('); + for member in r.members.iter() { + member.tref.render(src); + src.push(','); + } + src.push(')'); + } + t => panic!("reference to anonymous {} not possible!", t.kind()), + }, + } + } +} + +impl Render for BuiltinType { + fn render(&self, src: &mut String) { + match self { + // A C `char` in Rust we just interpret always as `u8`. It's + // technically possible to use `std::os::raw::c_char` but that's + // overkill for the purposes that we'll be using this type for. + BuiltinType::U8 { lang_c_char: _ } => src.push_str("u8"), + BuiltinType::U16 => src.push_str("u16"), + BuiltinType::U32 { + lang_ptr_size: false, + } => src.push_str("u32"), + BuiltinType::U32 { + lang_ptr_size: true, + } => src.push_str("usize"), + BuiltinType::U64 => src.push_str("u64"), + BuiltinType::S8 => src.push_str("i8"), + BuiltinType::S16 => src.push_str("i16"), + BuiltinType::S32 => src.push_str("i32"), + BuiltinType::S64 => src.push_str("i64"), + BuiltinType::F32 => src.push_str("f32"), + BuiltinType::F64 => src.push_str("f64"), + BuiltinType::Char => src.push_str("char"), + } + } +} + +impl Render for Module { + fn render(&self, src: &mut String) { + for f in self.funcs() { + if !crate::WASI_HOOK_FUNCTIONS.contains(&f.name.as_str()) { + continue; + } + let mut f_name = String::new(); + f.name.render(&mut f_name); + + render_trampoline( + &*f, + &format!("wasi_vfs_{}", f_name.to_snake_case()), + &self.name, + src, + ); + src.push('\n'); + } + } +} + +fn render_trace_syscall_entry_format_args(func: &InterfaceFunc, src: &mut String) { + src.push_str("format_args!(\""); + src.push_str(func.name.as_str()); + src.push('('); + + let mut raw_arg_names = vec![]; + for param in func.params.iter() { + match &**param.tref.type_() { + Type::List(_) => { + raw_arg_names.push(String::from(param.name.as_str())); + raw_arg_names.push(format!("{}_len", param.name.as_str())); + } + _ => { + raw_arg_names.push(String::from(param.name.as_str())); + } + } + } + + src.push_str( + &raw_arg_names + .iter() + .map(|name| format!("{}: {{}}", name)) + .collect::>() + .join(", "), + ); + src.push_str(")\\n\""); + for (i, _) in raw_arg_names.iter().enumerate() { + src.push_str(", "); + src.push_str("arg"); + src.push_str(&i.to_string()); + } + src.push(')'); +} + +fn render_trampoline(func: &InterfaceFunc, name: &str, module: &Id, src: &mut String) { + src.push_str(" #[no_mangle]\n"); + src.push_str("pub unsafe extern \"C\" fn "); + src.push_str(name); + + let (params, results) = func.wasm_signature(); + assert!(results.len() <= 1); + src.push('('); + + for (i, param) in params.iter().enumerate() { + src.push_str(&format!("arg{}: ", i)); + param.render(src); + src.push(','); + } + src.push(')'); + + if func.noreturn { + src.push_str(" -> !"); + } else if let Some(result) = results.get(0) { + src.push_str(" -> "); + result.render(src); + } + src.push_str("{\n"); + + { + src.push_str("#[cfg(feature = \"trace-syscall\")]\n"); + src.push_str("crate::trace::trace_syscall_entry("); + render_trace_syscall_entry_format_args(func, src); + src.push_str(");\n"); + } + { + src.push_str( + "let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { fs } else {\n", + ); + src.push_str(&format!( + "return wasi::{}::{}(\n", + module.as_str(), + func.name.as_str() + )); + for (i, _) in params.iter().enumerate() { + src.push_str(&format!("arg{}, ", i)); + } + src.push_str(");\n"); + src.push_str("};\n"); + } + func.call_interface( + module, + &mut Rust { + src, + func_name: func.name.as_str(), + block_storage: vec![], + blocks: vec![], + }, + ); + + src.push('}'); +} + +struct Rust<'a> { + src: &'a mut String, + func_name: &'a str, + block_storage: Vec, + blocks: Vec, +} + +impl Bindgen for Rust<'_> { + type Operand = String; + + fn emit( + &mut self, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let mut top_as = |cvt: &str| { + let mut s = operands.pop().unwrap(); + s.push_str(" as "); + s.push_str(cvt); + results.push(s); + }; + + match inst { + Instruction::GetArg { nth } => { + results.push(format!("arg{}", nth)); + } + Instruction::AddrOf => todo!(), + Instruction::I32FromChar => todo!(), + Instruction::I64FromU64 => todo!(), + Instruction::I64FromS64 => todo!(), + Instruction::I32FromU32 => todo!(), + Instruction::I32FromS32 => todo!(), + Instruction::I32FromUsize => todo!(), + Instruction::I32FromU16 => todo!(), + Instruction::I32FromS16 => todo!(), + Instruction::I32FromU8 => todo!(), + Instruction::I32FromS8 => todo!(), + Instruction::I32FromChar8 => todo!(), + Instruction::I32FromPointer => todo!(), + Instruction::I32FromConstPointer => todo!(), + Instruction::I32FromHandle { .. } => todo!(), + Instruction::I32FromBitflags { .. } => todo!(), + Instruction::I64FromBitflags { .. } => todo!(), + Instruction::ListPointerLength => todo!(), + Instruction::ListFromPointerLength { ty } => { + let ptr = &operands[0]; + let len = &operands[1]; + match &**ty.type_() { + witx::Type::Builtin(witx::BuiltinType::Char) => { + results.push(format!("{{ + let str_bytes = core::slice::from_raw_parts({} as *const u8, ({} + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }}", ptr, len)); + } + _ => { + results.push(format!( + "core::slice::from_raw_parts({} as *const {}, {} as usize)", + ptr, + ty.to_rust_ident(), + len + )); + } + }; + } + Instruction::F32FromIf32 => todo!(), + Instruction::F64FromIf64 => todo!(), + Instruction::CallInterface { module, func } => { + results.push(format!( + "crate::{}::{}(fs, {})", + module, + func.name.as_str(), + operands.join(", ") + )); + } + Instruction::S8FromI32 => todo!(), + Instruction::U8FromI32 => todo!(), + Instruction::S16FromI32 => todo!(), + Instruction::U16FromI32 => todo!(), + Instruction::S32FromI32 => todo!(), + Instruction::U32FromI32 => top_as("u32"), + Instruction::UsizeFromI32 => top_as("usize"), + Instruction::S64FromI64 => top_as("i64"), + Instruction::U64FromI64 => top_as("u64"), + Instruction::CharFromI32 => todo!(), + Instruction::Char8FromI32 => todo!(), + Instruction::If32FromF32 => todo!(), + Instruction::If64FromF64 => todo!(), + Instruction::HandleFromI32 { ty } => { + if ty.name.as_str() == "fd" { + top_as("UserFd"); + } else { + top_as(&ty.to_rust_ident()) + } + } + Instruction::PointerFromI32 { ty } => top_as(&format!("*mut {}", ty.to_rust_ident())), + Instruction::ConstPointerFromI32 { ty } => { + top_as(&format!("*const {}", ty.to_rust_ident())) + } + Instruction::BitflagsFromI32 { ty } => top_as(&ty.to_rust_ident()), + Instruction::BitflagsFromI64 { ty } => top_as(&ty.to_rust_ident()), + Instruction::ReturnPointerGet { .. } => todo!(), + Instruction::Load { .. } => todo!(), + Instruction::Store { ty } => { + let ptr = operands.pop().unwrap(); + let val = operands.pop().unwrap(); + self.src.push_str(&format!( + "core::ptr::write({} as *mut {}, {})", + ptr, + ty.to_rust_ident(), + val + )); + } + Instruction::ResultLift => todo!(), + Instruction::ResultLower { .. } => { + let err = self.blocks.pop().unwrap(); + let ok = self.blocks.pop().unwrap(); + let val = operands.pop().unwrap(); + results.push(format!( + "{{ + match {} {{ + Ok(e) => {{ {}; wasi::ERRNO_SUCCESS.raw() as i32 }} + Err(e) => {{ + #[cfg(feature = \"trace-syscall\")] + crate::trace::trace_syscall_error(\"{}\", e.clone()); + + {} + }} + }} + }}", + val, ok, self.func_name, err + )); + } + Instruction::EnumLift { .. } => { + // noop because some enum's constructor are invisible + results.push(operands.pop().unwrap()) + } + Instruction::EnumLower { ty } => { + // noop because some enum's constructor are invisible + assert_eq!(ty.name.as_str(), "errno"); + let val = operands.pop().unwrap(); + results.push(format!("{}.raw() as i32", val)); + } + Instruction::TupleLift { .. } => todo!(), + Instruction::TupleLower { .. } => todo!(), + Instruction::ReuseReturn => todo!(), + Instruction::Return { amt: 1 } => { + let ret = operands.pop().unwrap(); + self.src.push_str(&ret); + } + Instruction::Return { .. } => todo!(), + Instruction::VariantPayload => results.push(String::from("e")), + other => panic!("no implementation for {:?}", other), + } + } + + fn allocate_space(&mut self, _slot: usize, _ty: &NamedType) { + unimplemented!(); + } + + fn push_block(&mut self) { + let prev = std::mem::take(self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operand: Option) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(self.src, to_restore); + match operand { + None => { + self.blocks.push(src); + } + Some(s) => { + if src.is_empty() { + self.blocks.push(s); + } else { + self.blocks.push(format!("{{ {}; {} }}", src, s)); + } + } + } + } +} + +fn to_rust_ident(name: &str) -> &str { + match name { + "in" => "in_", + "type" => "type_", + "yield" => "yield_", + s => s, + } +} + +impl Render for Id { + fn render(&self, src: &mut String) { + src.push_str(to_rust_ident(self.as_str())) + } +} + +impl Render for WasmType { + fn render(&self, src: &mut String) { + match self { + WasmType::I32 => src.push_str("i32"), + WasmType::I64 => src.push_str("i64"), + WasmType::F32 => src.push_str("f32"), + WasmType::F64 => src.push_str("f64"), + } + } +} + +trait ToRustIdent { + fn to_rust_ident(&self) -> String; +} + +impl ToRustIdent for NamedType { + fn to_rust_ident(&self) -> String { + let mut buf = String::new(); + let src = &mut buf; + src.push_str(&self.name.as_str().to_camel_case()); + if let Type::List(_) = &**self.type_() { + src.push_str("<'_>"); + } + buf + } +} + +impl ToRustIdent for TypeRef { + fn to_rust_ident(&self) -> String { + let mut buf = String::new(); + let src = &mut buf; + match self { + TypeRef::Name(t) => { + src.push_str(&t.name.as_str().to_camel_case()); + if let Type::List(_) = &**t.type_() { + src.push_str("<'_>"); + } + } + TypeRef::Value(v) => match &**v { + Type::Builtin(t) => t.render(src), + Type::List(t) => match &**t.type_() { + Type::Builtin(BuiltinType::Char) => src.push_str("&str"), + _ => { + src.push_str("&'a ["); + t.render(src); + src.push(']'); + } + }, + Type::Pointer(t) => { + src.push_str("*mut "); + t.render(src); + } + Type::ConstPointer(t) => { + src.push_str("*const "); + t.render(src); + } + Type::Variant(v) if v.is_bool() => src.push_str("bool"), + Type::Variant(v) => match v.as_expected() { + Some((ok, err)) => { + src.push_str("Result<"); + match ok { + Some(ty) => ty.render(src), + None => src.push_str("()"), + } + src.push(','); + match err { + Some(ty) => ty.render(src), + None => src.push_str("()"), + } + src.push('>'); + } + None => { + panic!("unsupported anonymous variant") + } + }, + Type::Record(r) if r.is_tuple() => { + src.push('('); + for member in r.members.iter() { + member.tref.render(src); + src.push(','); + } + src.push(')'); + } + t => panic!("reference to anonymous {} not possible!", t.kind()), + }, + } + buf + } +} diff --git a/crates/wasi-vfs-cli/Cargo.toml b/crates/wasi-vfs-cli/Cargo.toml new file mode 100644 index 0000000..25ed3b9 --- /dev/null +++ b/crates/wasi-vfs-cli/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wasi-vfs-cli" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "wasi-vfs" + +[dependencies] +anyhow = "1.0.40" +structopt = "0.3.21" +wizer = { git = "https://github.com/bytecodealliance/wizer.git", rev = "1802237aeb8c19ff9448d67d280935bc10110d32" } diff --git a/crates/wasi-vfs-cli/src/bin/wasi-vfs.rs b/crates/wasi-vfs-cli/src/bin/wasi-vfs.rs new file mode 100644 index 0000000..c181b6f --- /dev/null +++ b/crates/wasi-vfs-cli/src/bin/wasi-vfs.rs @@ -0,0 +1,6 @@ +use structopt::StructOpt; +use wasi_vfs_cli::App; + +fn main() { + App::from_args().execute().unwrap(); +} diff --git a/crates/wasi-vfs-cli/src/lib.rs b/crates/wasi-vfs-cli/src/lib.rs new file mode 100644 index 0000000..75eeeeb --- /dev/null +++ b/crates/wasi-vfs-cli/src/lib.rs @@ -0,0 +1,71 @@ +use std::path::PathBuf; + +use anyhow::Result; +use structopt::StructOpt; +mod module_link; + +fn parse_map_dirs(s: &str) -> anyhow::Result<(PathBuf, PathBuf)> { + let parts: Vec<&str> = s.split("::").collect(); + if parts.len() != 2 { + anyhow::bail!("must contain exactly one double colon ('::')"); + } + Ok((parts[0].into(), parts[1].into())) +} + +#[derive(Debug, StructOpt)] +pub enum App { + #[structopt(setting(structopt::clap::AppSettings::Hidden))] + LinkModule { + #[structopt(parse(from_os_str))] + input: PathBuf, + + #[structopt(short, parse(from_os_str))] + output: PathBuf, + }, + + /// Package directories into Wasm module + Pack { + /// The input Wasm module's file path. + #[structopt(parse(from_os_str))] + input: PathBuf, + + /// Package a host directory into Wasm module at a guest directory + #[structopt(long = "mapdir", value_name = "GUEST_DIR::HOST_DIR", parse(try_from_str = parse_map_dirs))] + map_dirs: Vec<(PathBuf, PathBuf)>, + + /// The file path to write the output Wasm module to. + #[structopt(long, short, parse(from_os_str))] + output: PathBuf, + }, +} + +impl App { + pub fn execute(self) -> Result<()> { + match self { + App::LinkModule { input, .. } => { + let bytes = std::fs::read(&input)?; + module_link::link(&bytes); + } + App::Pack { + input, + map_dirs, + output, + } => { + std::env::set_var("__WASI_VFS_PACKING", "1"); + let mut wizer = wizer::Wizer::new(); + wizer.allow_wasi(true); + wizer.init_func("wasi_vfs_pack_fs"); + wizer.inherit_stdio(true); + wizer.inherit_env(true); + wizer.keep_init_func(true); + for (guest_dir, host_dir) in map_dirs { + wizer.map_dir(guest_dir, host_dir); + } + let wasm_bytes = std::fs::read(&input)?; + let output_bytes = wizer.run(&wasm_bytes)?; + std::fs::write(output, output_bytes)?; + } + } + Ok(()) + } +} diff --git a/crates/wasi-vfs-cli/src/module_link.rs b/crates/wasi-vfs-cli/src/module_link.rs new file mode 100644 index 0000000..230ec1b --- /dev/null +++ b/crates/wasi-vfs-cli/src/module_link.rs @@ -0,0 +1,60 @@ +/// Given thse two modules: +/// +/// libwasi_vfs.wasm +/// ```webassembly +/// (module +/// (import "wasi_snapshot_preview1" "fd_read" (func (param i32 i32 i32 i32) (result i32))) +/// (export "wasi_vfs_fd_read" (func $wasi_vfs_fd_read.command_export)) +/// ) +/// ``` +/// +/// main.wasm +/// ```webassembly +/// (module +/// (import "wasi_snapshot_preview1" "fd_read" (func (param i32 i32 i32 i32) (result i32))) +/// ) +/// ``` +/// +/// This function generates the following adapter module: +/// +/// ```webassembly +/// (adapter module +/// (import "wasi_snapshot_preview1" (instance $wasi_snapshot_preview1 +/// (export "fd_read" (func (param i32 i32 i32 i32) (result i32))) +/// )) +/// +/// ;; libwasi_vfs.wasm +/// (module $Vfs +/// (import "wasi_snapshot_preview1" "fd_read" (func (param i32 i32 i32 i32) (result i32))) +/// (export "wasi_vfs_fd_read" (func $wasi_vfs_fd_read.command_export)) +/// ) +/// +/// ;; main.wasm +/// (module $Main +/// (import "wasi_snapshot_preview1" "fd_read" (func (param i32 i32 i32 i32) (result i32))) +/// ) +/// +/// (instance $VfsInstance (instantiate $Vfs +/// (import "wasi_snapshot_preview1" (instance $wasi_snapshot_preview1)) +/// )) +/// +/// (instance $MainInstance (instantiate $Main +/// (import "wasi_snapshot_preview1" (instance $VfsInstance)) +/// )) +/// ) +/// ``` +/// +pub fn link(_main_bytes: &[u8]) { + unimplemented!( + r#" + This feature requires module-linking and interface-type proposals, and interface-type support of WASI. + Current WASI interface (wasi_snapshot_preview1) requires the callee to access the caller's linear memory, + so VFS instance has to import it while exporting WASI APIs. Also, calling real WASI API from VFS instance + requires the VFS instance to pass the pointer of the main instance memory. In theory, a virtualized WASI API + in VFS only calls corresponding real WASI API, so it's possible to implement this feature without interface-type + by annotating which parameters and results are pull/push address, and who has the ownership of the pointer. + However, it's hard to maintain such annotations, so I decided to wait the interface-type proposal and WASI adaptation + of shared-nothing architecture. + "# + ) +} diff --git a/docs/overview.png b/docs/overview.png new file mode 100644 index 0000000000000000000000000000000000000000..bbbea6074ea870ba182e45bdf6a1ebbd4d1318da GIT binary patch literal 72439 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%0AJk>T)0pTHx0trMz`z#2M`=@iF07!$(`l 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ð)rQuY6|)NZK`%8_U(UvuKj-d4B1{; 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 +#include + +int main(int argc, char *argv[]) { + char *line = NULL; + size_t len = 0; + ssize_t nread; + FILE *f; + + if (argc != 2) { + printf("No file specified\n"); + exit(EXIT_FAILURE); + } + + if ((f = fopen(argv[1], "r")) == NULL) { + perror("fopen"); + exit(EXIT_FAILURE); + } + while ((nread = getline(&line, &len, f)) != -1) { + fwrite(line, nread, 1, stdout); + } + return 0; +} diff --git a/examples/mnt/hello.txt b/examples/mnt/hello.txt new file mode 100644 index 0000000..e965047 --- /dev/null +++ b/examples/mnt/hello.txt @@ -0,0 +1 @@ +Hello diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..9831f9b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +targets = [ "wasm32-unknown-unknown", "wasm32-wasi" ] diff --git a/src/alloc.rs b/src/alloc.rs new file mode 100644 index 0000000..f0d1d4b --- /dev/null +++ b/src/alloc.rs @@ -0,0 +1,77 @@ +//! A global allocator which wraps wasi-libc's malloc while targeting wasm32-unknown-unknown + +#[cfg(not(feature = "module-linking"))] +const MIN_ALIGN: usize = 8; + +#[cfg(not(feature = "module-linking"))] +struct WasiAllocator; + +#[cfg(not(feature = "module-linking"))] +unsafe impl std::alloc::GlobalAlloc for WasiAllocator { + #[inline] + unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 { + extern "C" { + fn malloc(amt: usize) -> *mut std::ffi::c_void; + fn aligned_alloc(a: usize, b: usize) -> *mut std::ffi::c_void; + } + if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { + malloc(layout.size()) as *mut u8 + } else { + aligned_alloc(layout.align(), layout.size()) as *mut u8 + } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: std::alloc::Layout) -> *mut u8 { + extern "C" { + fn calloc(amt: usize, amt2: usize) -> *mut std::ffi::c_void; + } + if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { + calloc(layout.size(), 1) as *mut u8 + } else { + let ptr = self.alloc(layout); + if !ptr.is_null() { + std::ptr::write_bytes(ptr, 0, layout.size()); + } + ptr + } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, _layout: std::alloc::Layout) { + extern "C" { + fn free(ptr: *mut std::ffi::c_void); + } + free(ptr as *mut std::ffi::c_void) + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: std::alloc::Layout, new_size: usize) -> *mut u8 { + extern "C" { + fn realloc(ptr: *mut std::ffi::c_void, amt: usize) -> *mut std::ffi::c_void; + } + if layout.align() <= MIN_ALIGN && layout.align() <= new_size { + realloc(ptr as *mut std::ffi::c_void, new_size) as *mut u8 + } else { + let new_layout = + std::alloc::Layout::from_size_align_unchecked(new_size, layout.align()); + + let new_ptr = std::alloc::GlobalAlloc::alloc(self, new_layout); + if !new_ptr.is_null() { + let size = std::cmp::min(layout.size(), new_size); + std::ptr::copy_nonoverlapping(ptr, new_ptr, size); + std::alloc::GlobalAlloc::dealloc(self, ptr, layout); + } + new_ptr + } + } +} + +#[cfg(not(feature = "module-linking"))] +#[global_allocator] +static GLOBAL: WasiAllocator = WasiAllocator; + +// use self-contained allocator when module-linking +#[cfg(feature = "module-linking")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; diff --git a/src/embed/linked_storage.c b/src/embed/linked_storage.c new file mode 100644 index 0000000..2fe76f1 --- /dev/null +++ b/src/embed/linked_storage.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include + +typedef uint16_t wasi_errno_t; +#define WASI_ERRNO_SUCCESS (0) +#define WASI_ERRNO_NOENT (44) +#define WASI_ERRNO_NOTDIR (54) + +struct wasi_vfs_embed_linked_storage {}; + +struct wasi_vfs_node; +struct wasi_vfs_link { + struct wasi_vfs_link *parent; + struct wasi_vfs_node *node; +}; + +struct wasi_vfs_dirent { + struct wasi_vfs_link *link; + const char *name; + struct wasi_vfs_dirent *next; +}; + +// IMPORTANT: This layout must match the layout of struct InnerNode in +// Rust-side. +struct wasi_vfs_node { + bool is_dir; + size_t count; + union { + uint8_t *data; + struct wasi_vfs_dirent *dirents; + }; +}; + +typedef struct { + struct wasi_vfs_node *node; + struct wasi_vfs_link *link; +} node_link_t; + +static void insert_dirent(struct wasi_vfs_node *node, + struct wasi_vfs_dirent *dirent) { + dirent->next = node->dirents; + node->dirents = dirent; + node->count++; +} + +static struct wasi_vfs_node *new_node(bool is_dir) { + struct wasi_vfs_node *node = malloc(sizeof(struct wasi_vfs_node)); + node->is_dir = is_dir; + node->count = 0; + node->data = NULL; + return node; +} + +static struct wasi_vfs_link *new_link(struct wasi_vfs_node *node) { + struct wasi_vfs_link *link = malloc(sizeof(struct wasi_vfs_link)); + link->parent = NULL; + link->node = node; + return link; +} + +static struct wasi_vfs_dirent *new_dirent(struct wasi_vfs_link *link, + const char *name) { + struct wasi_vfs_dirent *dirent = malloc(sizeof(struct wasi_vfs_dirent)); + dirent->link = link; + dirent->name = strdup(name); + dirent->next = NULL; + return dirent; +} + +struct wasi_vfs_embed_linked_storage *wasi_vfs_embed_linked_storage_new(void) { + return malloc(sizeof(struct wasi_vfs_embed_linked_storage)); +} + +void wasi_vfs_embed_linked_storage_free( + struct wasi_vfs_embed_linked_storage *self) { + free(self); +} + +node_link_t wasi_vfs_embed_linked_storage_preopen_new_dir( + struct wasi_vfs_embed_linked_storage *self) { + (void)self; + struct wasi_vfs_node *node = new_node(true); + struct wasi_vfs_link *link = new_link(node); + return (node_link_t){node, link}; +} + +node_link_t wasi_vfs_embed_linked_storage_new_dir( + struct wasi_vfs_embed_linked_storage *self, const node_link_t *parent, + char *name) { + (void)self; + struct wasi_vfs_node *node = new_node(true); + struct wasi_vfs_link *link = new_link(node); + link->parent = parent->link; + + assert(parent->node->is_dir && "parent is not a dir"); + + struct wasi_vfs_dirent *dirent = new_dirent(link, name); + insert_dirent(parent->node, dirent); + + return (node_link_t){node, link}; +} + +node_link_t wasi_vfs_embed_linked_storage_new_file( + struct wasi_vfs_embed_linked_storage *self, const node_link_t *parent, + char *name, uint8_t *content, size_t content_len) { + + (void)self; + struct wasi_vfs_node *node = new_node(false); + node->count = content_len; + node->data = content; + + struct wasi_vfs_link *link = new_link(node); + link->parent = parent->link; + link->node = node; + + struct wasi_vfs_dirent *dirent = new_dirent(link, name); + insert_dirent(parent->node, dirent); + + return (node_link_t){node, link}; +} + +wasi_errno_t wasi_vfs_embed_linked_storage_resolve_node_at( + struct wasi_vfs_embed_linked_storage *self, const node_link_t *base, + const char *path, node_link_t *out) { + (void)self; + + node_link_t current = *base; + +find_parent_node: + while (path[0] != '\0') { + // strip leading '/' + while (path[0] == '/') { + path++; + } + const char *component = path; + while (path[0] != '/' && path[0] != '\0') { + path++; + } + const size_t component_len = path - component; + + // expect that the current node is a dir + if (!current.node->is_dir) { + return WASI_ERRNO_NOTDIR; + } + + // ok we are in a dir + if (component_len == 1 && component[0] == '.') { + // empty component, skip + continue; + } + + if (component_len == 2 && component[0] == '.' && component[1] == '.') { + // '..' component, go up + if (current.link->parent == NULL) { + // we are already at the root + return WASI_ERRNO_NOTDIR; + } + struct wasi_vfs_link *parent = current.link->parent; + current = (node_link_t){.node = parent->node, .link = parent}; + continue; + } + + // ok we have flattened special components, find children + struct wasi_vfs_dirent *dirent = current.node->dirents; + while (dirent != NULL) { + if (strncmp(dirent->name, component, component_len) == 0 && + dirent->name[component_len] == '\0') { + // found the child + current = + (node_link_t){.node = dirent->link->node, .link = dirent->link}; + goto find_parent_node; + } + dirent = dirent->next; + } + return WASI_ERRNO_NOENT; + } + *out = current; + return WASI_ERRNO_SUCCESS; +} diff --git a/src/embed/linked_storage.rs b/src/embed/linked_storage.rs new file mode 100644 index 0000000..de46a71 --- /dev/null +++ b/src/embed/linked_storage.rs @@ -0,0 +1,237 @@ +//! This module consists of C and Rust implementations. +//! C part builds link graph by chaining nodes by pointers. +//! Rust part is a thin wrapper to conform to `Storage` trait. + +use std::{ + ffi::{CStr, CString}, + mem::MaybeUninit, + path::Path, +}; + +use super::{DirEntry, Link, Node, NodeDirBody, NodeFileBody, NodeIdTrait, Storage}; + +#[repr(transparent)] +#[derive(Hash, Clone, Copy, PartialEq, Eq)] +pub struct NodeId(*const std::ffi::c_void); + +impl NodeIdTrait for NodeId { + fn ino(&self) -> u64 { + self.0 as u64 + } +} + +#[repr(transparent)] +#[derive(Hash, Clone, Copy, PartialEq, Eq)] +pub struct LinkId(*const std::ffi::c_void); + +pub struct LinkedStorage { + context: *mut std::ffi::c_void, +} + +#[repr(C)] +struct NodeLink { + node_id: NodeId, + link_id: LinkId, +} + +impl NodeFileBody for InnerNode { + fn content(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(std::mem::transmute(self.dir_or_file), self.count) } + } +} + +impl NodeDirBody for InnerNode { + type Iter = LinkedStorageIterator; + fn entries(&self) -> Self::Iter { + LinkedStorageIterator(self.dir_or_file as *const InnerDirent) + } +} + +pub(crate) struct LinkedStorageIterator(*const InnerDirent); +impl Iterator for LinkedStorageIterator { + type Item = DirEntry; + + fn next(&mut self) -> Option> { + if self.0.is_null() { + return None; + } + let (entry, next) = unsafe { + let node = &(*self.0); + let name = CStr::from_ptr(node.name).to_string_lossy().into_owned(); + let link_id = LinkId(node.link_id); + (DirEntry:: { name, link_id }, node.next) + }; + self.0 = next; + Some(entry) + } +} + +#[repr(C)] +pub(crate) struct InnerNode { + is_dir: bool, + count: usize, + dir_or_file: *const std::ffi::c_void, +} + +#[repr(C)] +struct InnerDirent { + link_id: *const std::ffi::c_void, + name: *const i8, + next: *const InnerDirent, +} + +#[repr(C)] +struct InnerLink { + parent: LinkId, + node: NodeId, +} + +extern "C" { + fn wasi_vfs_embed_linked_storage_new() -> *mut std::ffi::c_void; + fn wasi_vfs_embed_linked_storage_free(context: *mut std::ffi::c_void); + fn wasi_vfs_embed_linked_storage_preopen_new_dir(context: *mut std::ffi::c_void) -> NodeLink; + fn wasi_vfs_embed_linked_storage_new_dir( + context: *mut std::ffi::c_void, + parent: *const NodeLink, + name: *const i8, + ) -> NodeLink; + fn wasi_vfs_embed_linked_storage_new_file( + context: *mut std::ffi::c_void, + parent: *const NodeLink, + name: *const i8, + content: *const u8, + content_len: usize, + ) -> NodeLink; + fn wasi_vfs_embed_linked_storage_resolve_node_at( + context: *mut std::ffi::c_void, + base: *const NodeLink, + path: *const i8, + ret: *mut NodeLink, + ) -> wasi::Errno; +} + +impl Default for LinkedStorage { + fn default() -> Self { + Self::new() + } +} + +impl LinkedStorage { + pub(crate) fn new() -> Self { + Self { + context: unsafe { wasi_vfs_embed_linked_storage_new() }, + } + } +} + +impl Storage for LinkedStorage { + type NodeId = NodeId; + type LinkId = LinkId; + type NodeFileBody = InnerNode; + type NodeDirBody = InnerNode; + + fn new_root_dir(&mut self) -> (NodeId, LinkId) { + unsafe { + let ret = wasi_vfs_embed_linked_storage_preopen_new_dir(self.context); + (ret.node_id, ret.link_id) + } + } + + fn new_dir(&mut self, parent: (NodeId, LinkId), name: String) -> (NodeId, LinkId) { + unsafe { + let name = CString::new(name).unwrap(); + let link = NodeLink { + node_id: parent.0, + link_id: parent.1, + }; + let result = wasi_vfs_embed_linked_storage_new_dir(self.context, &link, name.as_ptr()); + (result.node_id, result.link_id) + } + } + + fn new_file( + &mut self, + parent: (NodeId, LinkId), + name: String, + content: Vec, + ) -> (NodeId, LinkId) { + unsafe { + let name = CString::new(name).unwrap(); + let content_len = content.len(); + let content = Box::new(content); + let link = NodeLink { + node_id: parent.0, + link_id: parent.1, + }; + let result = wasi_vfs_embed_linked_storage_new_file( + self.context, + &link, + name.as_ptr(), + content.leak().as_ptr(), + content_len, + ); + (result.node_id, result.link_id) + } + } + + fn get_inode(&self, node_id: &NodeId) -> Node { + unsafe { + let node = node_id.0 as *const InnerNode; + if (*node).is_dir { + Node::Dir(node.as_ref().unwrap()) + } else { + Node::File(node.as_ref().unwrap()) + } + } + } + + fn get_link(&self, link_id: &LinkId) -> Link { + unsafe { + let inner_link = link_id.0 as *const InnerLink; + let inner_link = inner_link.as_ref().unwrap(); + let parent = if inner_link.parent.0.is_null() { + None + } else { + Some(inner_link.parent) + }; + Link { + parent, + node: inner_link.node, + } + } + } + + fn resolve_node( + &self, + base: NodeId, + base_link: LinkId, + path: &Path, + ) -> Result<(NodeId, LinkId), wasi::Errno> { + unsafe { + let path: &[i8] = std::mem::transmute(path.as_os_str()); + let link = NodeLink { + node_id: base, + link_id: base_link, + }; + let mut ret = MaybeUninit::uninit(); + let errno = wasi_vfs_embed_linked_storage_resolve_node_at( + self.context, + &link, + path.as_ptr(), + ret.as_mut_ptr(), + ); + if errno == wasi::ERRNO_SUCCESS { + let ret = ret.assume_init(); + Ok((ret.node_id, ret.link_id)) + } else { + Err(errno) + } + } + } +} + +impl Drop for LinkedStorage { + fn drop(&mut self) { + unsafe { wasi_vfs_embed_linked_storage_free(self.context) } + } +} diff --git a/src/embed/mod.rs b/src/embed/mod.rs new file mode 100644 index 0000000..a7ae5a9 --- /dev/null +++ b/src/embed/mod.rs @@ -0,0 +1,369 @@ +//! This module provides an in-memory filesystem implementation. + +mod linked_storage; +pub use linked_storage::LinkedStorage; + +use crate::Vfd; +use std::{collections::HashMap, path::Path}; + +pub(crate) trait NodeIdTrait { + fn ino(&self) -> u64; +} + +pub(crate) trait NodeFileBody { + fn content(&self) -> &[u8]; +} + +pub(crate) struct DirEntry { + pub(crate) name: String, + pub(crate) link_id: S::LinkId, +} + +impl Clone for DirEntry { + fn clone(&self) -> Self { + DirEntry { + name: self.name.clone(), + link_id: self.link_id, + } + } +} + +pub(crate) trait NodeDirBody { + type Iter: Iterator>; + fn entries(&self) -> Self::Iter; +} + +/// A storage that can be used to store files and directories. +pub(crate) trait Storage { + type NodeId: NodeIdTrait + Clone + Copy; + type LinkId: Clone + Copy; + type NodeFileBody: NodeFileBody; + type NodeDirBody: NodeDirBody; + + /// Creates a new root node. + fn new_root_dir(&mut self) -> (Self::NodeId, Self::LinkId); + + /// Creates a new directory node under the given parent node. + fn new_dir( + &mut self, + parent: (Self::NodeId, Self::LinkId), + name: String, + ) -> (Self::NodeId, Self::LinkId); + + /// Creates a new file node under the given parent node. + fn new_file( + &mut self, + parent: (Self::NodeId, Self::LinkId), + name: String, + content: Vec, + ) -> (Self::NodeId, Self::LinkId); + + /// Resolve a node from its id. + fn get_inode(&self, node_id: &Self::NodeId) -> Node; + + /// Resolve a link from its id. + fn get_link(&self, link_id: &Self::LinkId) -> Link; + + /// Resolve a node from base node and relative path. + fn resolve_node( + &self, + base: Self::NodeId, + base_link: Self::LinkId, + path: &Path, + ) -> Result<(Self::NodeId, Self::LinkId), wasi::Errno>; +} + +pub(crate) enum Node<'a, S: Storage + ?Sized> { + File(&'a S::NodeFileBody), + Dir(&'a S::NodeDirBody), +} + +/// Represent a hard link to an inode +pub(crate) struct Link { + pub(crate) parent: Option, + pub(crate) node: S::NodeId, +} + +impl Clone for Link { + fn clone(&self) -> Self { + Link { + parent: self.parent, + node: self.node, + } + } +} + +impl Copy for Link {} + +pub(crate) struct FdEntry { + pub(crate) offset: usize, + pub(crate) link_id: S::LinkId, + pub(crate) node_id: S::NodeId, + pub(crate) flags: wasi::Fdflags, +} + +pub(crate) struct PreopenedDir { + pub(crate) path: String, +} + +pub(crate) struct EmbeddedFs { + preopened_dirs: Vec, + storage: S, + + opens: HashMap>, + fd_issuer: IdIssuer, +} + +#[derive(Default)] +struct IdIssuer { + next_id: Id, +} + +impl IdIssuer { + fn new(base: Id) -> Self { + Self { next_id: base } + } +} + +impl + Clone> IdIssuer { + fn issue(&mut self) -> Id { + let id = self.next_id.clone(); + self.next_id += 1; + id + } +} + +impl Default for EmbeddedFs +where + S: Default, +{ + fn default() -> Self { + Self::new(S::default()) + } +} + +impl EmbeddedFs { + pub(crate) fn new(storage: S) -> Self { + Self { + preopened_dirs: vec![], + storage, + opens: HashMap::new(), + fd_issuer: IdIssuer::new(0_u32), + } + } + + pub(crate) fn preopen_dir(&mut self, path: String) -> (Vfd, S::NodeId, S::LinkId) { + assert!(self.preopened_dirs.len() == self.opens.len()); + let fd = self.fd_issuer.issue(); + self.preopened_dirs.push(PreopenedDir { path }); + let (node_id, link_id) = self.storage.new_root_dir(); + self.opens.insert( + fd, + FdEntry { + offset: 0, + node_id, + link_id, + flags: 0, + }, + ); + (fd, node_id, link_id) + } + + pub(crate) fn get_preopened_dir_path(&self, vfd: Vfd) -> Option<&str> { + let vfd = vfd as usize; + if vfd >= self.preopened_dirs.len() { + return None; + } + Some(&self.preopened_dirs[vfd].path) + } + + pub(crate) fn create_file( + &mut self, + dir_node: S::NodeId, + dir_link: S::LinkId, + mut relpath: &str, + content: Vec, + ) -> Result<(), u16> { + let mut cursor = match self.storage.get_inode(&dir_node) { + Node::Dir { .. } => (dir_node, dir_link), + _ => return Err(wasi::ERRNO_BADF.raw()), + }; + if relpath.starts_with('/') { + relpath = &relpath[1..]; + } + let components = relpath.split('/').collect::>(); + let filename = match components.last() { + Some(filename) => *filename, + None => return Err(wasi::ERRNO_NOENT.raw()), + }; + + let components_len = components.len(); + 'find_parent_node: for component in components.into_iter().take(components_len - 1) { + if component == "." { + continue; + } + let entries = match self.storage.get_inode(&cursor.0) { + Node::Dir(body) => body.entries(), + _ => return Err(wasi::ERRNO_BADF.raw()), + }; + for entry in entries { + if component == entry.name { + cursor = (self.storage.get_link(&entry.link_id).node, entry.link_id); + continue 'find_parent_node; + } + } + // create a new intermediate directory + { + let (new_dir_id, new_link_id) = self.storage.new_dir(cursor, component.to_string()); + cursor = (new_dir_id, new_link_id); + } + } + + self.storage.new_file(cursor, filename.to_string(), content); + + Ok(()) + } + + pub(crate) fn get_node_id_by_link(&self, id: S::LinkId) -> S::NodeId { + self.storage.get_link(&id).node + } + + pub(crate) fn get_node(&self, fd: Vfd) -> Result, wasi::Errno> { + match self.opens.get(&fd) { + Some(entry) => Ok(self.storage.get_inode(&entry.node_id)), + None => Err(wasi::ERRNO_BADF), + } + } + + pub(crate) fn get_fd_stat(&self, fd: Vfd) -> Result { + const READ_ONLY_RIGHTS: wasi::Rights = wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_ADVISE + | wasi::RIGHTS_PATH_OPEN + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_FD_FILESTAT_GET; + let entry = match self.opens.get(&fd) { + Some(entry) => entry, + None => return Err(wasi::ERRNO_BADF), + }; + Ok(match self.storage.get_inode(&entry.node_id) { + Node::File { .. } => wasi::Fdstat { + fs_filetype: wasi::FILETYPE_REGULAR_FILE, + fs_flags: entry.flags, + fs_rights_base: READ_ONLY_RIGHTS, + fs_rights_inheriting: READ_ONLY_RIGHTS, + }, + Node::Dir { .. } => wasi::Fdstat { + fs_filetype: wasi::FILETYPE_DIRECTORY, + fs_flags: entry.flags, + fs_rights_base: READ_ONLY_RIGHTS, + fs_rights_inheriting: READ_ONLY_RIGHTS, + }, + }) + } + + pub(crate) fn get_filestat_from_node_id(&self, node_id: S::NodeId) -> wasi::Filestat { + let mut stat = wasi::Filestat { + dev: Default::default(), + ino: Default::default(), + filetype: wasi::FILETYPE_UNKNOWN, + nlink: Default::default(), + size: Default::default(), + atim: Default::default(), + mtim: Default::default(), + ctim: Default::default(), + }; + stat.ino = node_id.ino(); + match self.storage.get_inode(&node_id) { + Node::File(body) => { + stat.filetype = wasi::FILETYPE_REGULAR_FILE; + stat.size = body.content().len() as u64; + stat + } + Node::Dir { .. } => { + stat.filetype = wasi::FILETYPE_DIRECTORY; + stat + } + } + } + + pub(crate) fn get_fd_entry_mut(&mut self, fd: Vfd) -> Result<&mut FdEntry, wasi::Errno> { + match self.opens.get_mut(&fd) { + Some(open_file) => Ok(open_file), + None => Err(wasi::ERRNO_BADF), + } + } + + pub(crate) fn get_fd_entry(&self, fd: Vfd) -> Result<&FdEntry, wasi::Errno> { + match self.opens.get(&fd) { + Some(open_file) => Ok(open_file), + None => Err(wasi::ERRNO_BADF), + } + } + + pub(crate) fn close_file(&mut self, fd: Vfd) -> Result<(), wasi::Errno> { + match self.opens.remove(&fd) { + Some(_) => Ok(()), + None => Err(wasi::ERRNO_BADF), + } + } + + pub(crate) fn open_file( + &mut self, + base: Vfd, + path: &Path, + fdflags: wasi::Fdflags, + ) -> Result { + let base = &self.opens[&base]; + let (node_id, link_id) = self + .storage + .resolve_node(base.node_id, base.link_id, path)?; + let new_fd = self.fd_issuer.issue(); + self.opens.insert( + new_fd, + FdEntry { + offset: 0, + node_id, + link_id, + flags: fdflags, + }, + ); + Ok(new_fd) + } + + pub(crate) fn get_filestat_at_path( + &self, + base: Vfd, + path: &Path, + ) -> Result { + let base = &self.opens[&base]; + let (node_id, _) = self + .storage + .resolve_node(base.node_id, base.link_id, path)?; + let res = self.get_filestat_from_node_id(node_id); + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::{EmbeddedFs, LinkedStorage}; + + #[test] + fn test_embedded_node_create_file() { + let content = "Hello".as_bytes().to_vec(); + let mut fs = EmbeddedFs::::default(); + let (_, node_id, link_id) = fs.preopen_dir("/".to_string()); + fs.create_file(node_id, link_id, "hello.txt", content) + .unwrap(); + } + + #[test] + fn test_get_filestat_at_path_for_non_existing() { + let mut fs = EmbeddedFs::::default(); + let (vfd, _, _) = fs.preopen_dir("/".to_string()); + let result = fs.get_filestat_at_path(vfd, Path::new("/not-exist")); + assert!(result.is_err()); + } +} diff --git a/src/init.c b/src/init.c new file mode 100644 index 0000000..fcbc701 --- /dev/null +++ b/src/init.c @@ -0,0 +1,18 @@ +// A dummy symbol to force the linker to include this file +// This symbol is used in lib.rs. +void __wasi_vfs_force_link_init(void) {} + +#pragma clang diagnostic ignored "-Wunknown-attributes" +__attribute__((export_name("wasi_vfs_pack_fs"))) +void export_wasi_vfs_pack_fs(void) { + extern void __internal_wasi_vfs_pack_fs(void); + __internal_wasi_vfs_pack_fs(); +} + +// wasi-libc reserves 50~100 constructor, and __wasilibc_populate_preopens calls +// fs syscall, so this need to be done before that. +__attribute__((constructor(40))) +void __wasi_vfs_rt_init(void) { + extern void __internal_wasi_vfs_rt_init(void); + __internal_wasi_vfs_rt_init(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8a583f1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,435 @@ +//! wasi-vfs is a virtual file system compatible with WASI. + +mod alloc; +mod embed; +mod trace; +mod trampoline_generated; +#[allow(unused_variables)] +mod wasi_snapshot_preview1; + +use embed::LinkedStorage as DefaultStorage; +use embed::{EmbeddedFs, NodeIdTrait, Storage}; + +use std::{collections::HashMap, ffi::CStr}; +use wasi::Fd; + +/// User-facing file descriptor managed by wasi-vfs +pub(crate) type UserFd = u32; +/// Internal file descriptor managed by virtual file system backend +pub(crate) type Vfd = u32; + +/// Generic internal file descriptor +#[derive(Clone, Copy, Debug)] +pub(crate) enum BackingFd { + /// File descriptor managed by virtual file system backend + Virtual(Vfd), + /// File descriptor managed by a real WASI implementation + Wasi(Fd), +} + +/// Map of user-facing file descriptors to internal file descriptors. +type FdMap = HashMap; + +struct FileSystem { + embedded_fs: EmbeddedFs, + fd_map: FdMap, + next_fd: u32, +} + +impl FileSystem { + fn create(embedded_fs: EmbeddedFs, preopened_vfds: &[Vfd]) -> Self { + let mut fs = FileSystem { + embedded_fs, + fd_map: FdMap::new(), + next_fd: 3, + }; + // reserve stdin/stdout/stderr + for fd in 0..=2 { + fs.set_user_fd_at(BackingFd::Wasi(fd), fd); + } + + for fd in 3.. { + unsafe { + match wasi::fd_prestat_get(fd) { + Ok(_) => (), + Err(wasi::ERRNO_BADF) => break, + Err(other) => { + panic!("failed to get prestat: {}", other); + } + } + } + + fs.issue_user_fd(BackingFd::Wasi(fd)); + } + for vfd in preopened_vfds { + let fd = fs.next_fd; + fs.next_fd += 1; + fs.fd_map.insert(fd, BackingFd::Virtual(*vfd)); + } + fs + } + + fn set_user_fd_at(&mut self, backing_fd: BackingFd, fd: UserFd) { + self.fd_map.insert(fd, backing_fd); + } + fn issue_user_fd(&mut self, backing_fd: BackingFd) -> UserFd { + let fd = self.next_fd; + self.next_fd += 1; + self.fd_map.insert(fd, backing_fd); + fd as UserFd + } + + fn get_backing_fd(&self, user_fd: UserFd) -> Result { + match self.fd_map.get(&user_fd) { + Some(backing_fd) => Ok(*backing_fd), + None => Err(wasi::ERRNO_BADF.into()), + } + } +} + +struct GlobalState { + embedded_fs: Option<(EmbeddedFs, Vec)>, + overlay_fs: Option>, +} + +static mut GLOBAL_STATE: GlobalState = GlobalState { + embedded_fs: None, + overlay_fs: None, +}; + +// `__internal_wasi_vfs_rt_init` is processed before the wasi-libc's initialization, which +// loads envirnoment variables and preopened directories. `getenv` is not available at this +// time, so use self-made `env_var` instead. +fn env_var(name: &str) -> Option { + let (count, buffer_size) = unsafe { wasi::environ_sizes_get().unwrap() }; + if count == 0 { + return None; + } + let mut offsets: Vec<*mut u8> = vec![std::ptr::null_mut(); count]; + let mut buffer: Vec = vec![0; buffer_size]; + unsafe { + wasi::environ_get(offsets.as_mut_ptr(), buffer.as_mut_ptr()).unwrap(); + } + for offset in offsets { + let c_str = unsafe { CStr::from_ptr(offset as *const i8) }; + let pair = c_str.to_string_lossy(); + let mut pair = pair.splitn(2, '='); + let key = pair.next().unwrap(); + let value = pair.next().unwrap(); + if key == name { + return Some(value.to_string()); + } + } + None +} + +#[no_mangle] +unsafe extern "C" fn __internal_wasi_vfs_rt_init() { + extern "C" { + fn __wasi_vfs_force_link_init(); + } + __wasi_vfs_force_link_init(); + if env_var("__WASI_VFS_PACKING").is_some() { + return; + } + if let Some((embedded_fs, preopened_vfds)) = GLOBAL_STATE.embedded_fs.take() { + let fs = FileSystem::create(embedded_fs, &preopened_vfds); + GLOBAL_STATE.overlay_fs = Some(fs); + } +} + +#[no_mangle] +unsafe extern "C" fn __internal_wasi_vfs_pack_fs() { + std::panic::set_hook(Box::new(|info| { + trace::print(format!("{}\n", info)); + })); + let (mut fs, preopened_vfds) = if let Some((fs, vfds)) = GLOBAL_STATE.embedded_fs.take() { + (fs, vfds) + } else { + (EmbeddedFs::default(), vec![]) + }; + + let (preopened_vfds, prestats) = + FsPacker::scan_preopened_dirs(&mut fs, preopened_vfds).unwrap(); + let packer = FsPacker::new(fs, preopened_vfds).unwrap(); + let fs = packer.pack(prestats).unwrap(); + GLOBAL_STATE.embedded_fs = Some(fs); + + #[cfg(not(feature = "module-linking"))] + { + extern "C" { + fn __wasilibc_deinitialize_environ(); + } + __wasilibc_deinitialize_environ(); + } +} + +struct Prestat { + real_fd: u32, + node_id: S::NodeId, + link_id: S::LinkId, +} + +struct FsPacker { + fs: EmbeddedFs, + preopened_vfds: Vec, + verbose: bool, +} + +trait DirVisitor { + fn visit_file( + &mut self, + path: &str, + fd: u32, + preopened_id: (S::NodeId, S::LinkId), + ) -> Result<(), u16>; + + fn visit_dir( + &mut self, + prefix: &str, + fd: u32, + preopened_id: (S::NodeId, S::LinkId), + ) -> Result<(), u16>; +} + +fn walk_dir>( + visitor: &mut V, + prefix: &str, + fd: u32, + preopened_id: (S::NodeId, S::LinkId), +) -> Result<(), u16> { + const DIRENT_DEFAULT_BUFFER_SIZE: usize = 4096; + let mut offset = 0; + let mut capacity = 0; + let mut cookie = wasi::DIRCOOKIE_START; + let mut buffer = vec![0; DIRENT_DEFAULT_BUFFER_SIZE]; + loop { + if offset == capacity { + capacity = unsafe { wasi::fd_readdir(fd, buffer.as_mut_ptr(), buffer.len(), cookie) } + .expect("failed to readdir"); + offset = 0; + if capacity == 0 { + break; + } + } + let data = &buffer[offset..capacity]; + let dirent_size = core::mem::size_of::(); + + // when dirent is truncated, re-read it + if data.len() < dirent_size { + offset = capacity; + continue; + } + + let (dirent, data) = data.split_at(dirent_size); + let dirent = unsafe { core::ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; + + // when entry name is truncated + if data.len() < dirent.d_namlen as usize { + // when the buffer is not enough to read an big entry, realloc the buffer and re-read the entry + if offset == 0 { + let amt_to_add = buffer.capacity(); + buffer.extend(core::iter::repeat(0).take(amt_to_add)); + } + offset = capacity; + continue; + } + cookie = dirent.d_next; + offset += dirent_size + dirent.d_namlen as usize; + let name = &data[..dirent.d_namlen as usize]; + if name == b"." || name == b".." { + continue; + } + + let name = String::from_utf8(name.to_vec()).unwrap(); + let path = format!("{}/{}", prefix, name); + let rights = wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_PATH_OPEN; + + match dirent.d_type { + wasi::FILETYPE_DIRECTORY => { + let oflags = wasi::OFLAGS_DIRECTORY; + let child_fd = unsafe { + wasi::path_open( + fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + &name, + oflags, + rights, + rights, + 0, + ) + } + .map_err(|e| e.raw()) + .unwrap(); + + visitor.visit_dir(&path, child_fd, preopened_id)?; + walk_dir(visitor, &path, child_fd, preopened_id)?; + + unsafe { + wasi::fd_close(child_fd).expect("failed to close fd"); + } + } + wasi::FILETYPE_REGULAR_FILE => { + let oflags = 0; + let child_fd = unsafe { + wasi::path_open( + fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + &name, + oflags, + rights, + rights, + 0, + ) + } + .map_err(|e| e.raw()) + .unwrap(); + visitor.visit_file(&path, child_fd, preopened_id)?; + unsafe { + wasi::fd_close(child_fd).expect("failed to close fd"); + } + } + _ => {} + } + } + Ok(()) +} + +impl FsPacker { + fn scan_preopened_dirs( + fs: &mut EmbeddedFs, + mut preopened_vfds: Vec, + ) -> Result<(Vec, Vec>), u16> { + let mut preopened_dirs = Vec::new(); + 'scan: for fd in 3.. { + let stat = match unsafe { wasi::fd_prestat_get(fd) } { + Ok(stat) => stat, + Err(wasi::ERRNO_BADF) => break 'scan, + Err(other) => { + return Err(other.raw()); + } + }; + if stat.tag == wasi::PREOPENTYPE_DIR.raw() { + preopened_dirs.push((fd, stat)); + } + } + + let mut prestats = Vec::new(); + for (real_fd, stat) in preopened_dirs { + let dir = unsafe { + let mut prefix = vec![0; stat.u.dir.pr_name_len + 1]; + wasi::fd_prestat_dir_name(real_fd, prefix.as_mut_ptr(), stat.u.dir.pr_name_len) + .map_err(|e| e.raw()) + .expect("failed to get dir name"); + let dir = CStr::from_bytes_with_nul(&prefix).unwrap(); + dir.to_string_lossy().to_string() + }; + let (vfd, node_id, link_id) = fs.preopen_dir(dir); + prestats.push(Prestat { + real_fd, + node_id, + link_id, + }); + preopened_vfds.push(vfd); + } + Ok((preopened_vfds, prestats)) + } + + fn new(fs: EmbeddedFs, preopened_vfds: Vec) -> Result { + Ok(FsPacker { + fs, + preopened_vfds, + verbose: env_var("WASI_VFS_VERBOSE") + .map(|v| v == "1") + .unwrap_or(false), + }) + } + + fn pack(mut self, prestats: Vec>) -> Result<(EmbeddedFs, Vec), u16> { + for stat in prestats { + walk_dir(&mut self, "", stat.real_fd, (stat.node_id, stat.link_id))?; + } + Ok((self.fs, self.preopened_vfds)) + } +} + +impl DirVisitor for FsPacker { + fn visit_dir( + &mut self, + _prefix: &str, + _fd: u32, + _preopened_id: (S::NodeId, S::LinkId), + ) -> Result<(), u16> { + Ok(()) + } + + fn visit_file( + &mut self, + path: &str, + fd: u32, + preopened_id: (S::NodeId, S::LinkId), + ) -> Result<(), u16> { + let stat = unsafe { wasi::fd_filestat_get(fd) } + .map_err(|e| e.raw()) + .unwrap(); + if stat.size >= u32::MAX as u64 { + // ignore too big files + if self.verbose { + trace::print(format!("too large file: {} (size {})\n", path, stat.size)); + } + return Ok(()); + } + let mut buf = vec![0; stat.size as usize]; + let mut offset = 0; + loop { + let read = unsafe { + wasi::fd_read( + fd, + &[wasi::Iovec { + buf: buf[offset..].as_mut_ptr(), + buf_len: buf.len(), + }], + ) + } + .map_err(|e| e.raw()) + .unwrap(); + + offset += read; + if offset == stat.size as usize { + break; + } + } + if self.verbose { + trace::print(format!( + "pack file: {} under node-id={} (size {})\n", + path, + preopened_id.0.ino(), + buf.len() + )); + } + self.fs + .create_file(preopened_id.0, preopened_id.1, path, buf) + .unwrap(); + Ok(()) + } +} + +#[derive(Copy, Clone, Hash, Debug)] +pub(crate) struct Error(u16); + +impl From for Error { + #[inline] + fn from(from: wasi::Errno) -> Self { + Self(from.raw()) + } +} + +impl Error { + #[inline] + pub(crate) fn raw(self) -> u16 { + self.0 + } +} diff --git a/src/trace.rs b/src/trace.rs new file mode 100644 index 0000000..69beb0a --- /dev/null +++ b/src/trace.rs @@ -0,0 +1,51 @@ +#[cfg(feature = "trace-syscall")] +fn is_tracing_enabled() -> bool { + extern "C" { + fn getenv(name: *const i8) -> *const i8; + } + let wasi_vfs_trace = std::ffi::CString::new("WASI_VFS_TRACE").unwrap(); + unsafe { !getenv(wasi_vfs_trace.as_ptr()).is_null() } +} + +#[cfg(feature = "trace-syscall")] +pub(crate) fn trace_syscall_entry(args: std::fmt::Arguments<'_>) { + if !is_tracing_enabled() { + return; + } + let message = std::fmt::format(args); + let data = [wasi::Ciovec { + buf: message.as_ptr(), + buf_len: message.len(), + }]; + let stderr = 2; + unsafe { + wasi::fd_write(stderr, &data).unwrap(); + } +} + +#[cfg(feature = "trace-syscall")] +pub(crate) fn trace_syscall_error(name: &str, errno: crate::Error) { + if !is_tracing_enabled() { + return; + } + let message = format!("{} returns {}\n", name, errno.raw()); + let data = [wasi::Ciovec { + buf: message.as_ptr(), + buf_len: message.len(), + }]; + let stderr = 2; + unsafe { + wasi::fd_write(stderr, &data).unwrap(); + } +} + +pub(crate) fn print(message: String) { + let data = [wasi::Ciovec { + buf: message.as_ptr(), + buf_len: message.len(), + }]; + let stdout = 1; + unsafe { + wasi::fd_write(stdout, &data).unwrap(); + } +} diff --git a/src/trampoline_generated.c b/src/trampoline_generated.c new file mode 100644 index 0000000..0612530 --- /dev/null +++ b/src/trampoline_generated.c @@ -0,0 +1,199 @@ +// This file is automatically generated, DO NOT EDIT +// +// To regenerate this file run the `crates/wasi-libc-trampoline-bindgen` command + +#include + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_advise(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_advise(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3); + return wasi_vfs_fd_advise(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_allocate(int32_t arg0, int64_t arg1, int64_t arg2) { + extern int32_t wasi_vfs_fd_allocate(int32_t arg0, int64_t arg1, int64_t arg2); + return wasi_vfs_fd_allocate(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_close(int32_t arg0) { + extern int32_t wasi_vfs_fd_close(int32_t arg0); + return wasi_vfs_fd_close(arg0); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_datasync(int32_t arg0) { + extern int32_t wasi_vfs_fd_datasync(int32_t arg0); + return wasi_vfs_fd_datasync(arg0); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_fdstat_get(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_fdstat_get(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_fdstat_get(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_fdstat_set_flags(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_fdstat_set_flags(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_fdstat_set_flags(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_fdstat_set_rights(int32_t arg0, int64_t arg1, int64_t arg2) { + extern int32_t wasi_vfs_fd_fdstat_set_rights(int32_t arg0, int64_t arg1, int64_t arg2); + return wasi_vfs_fd_fdstat_set_rights(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_filestat_get(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_filestat_get(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_filestat_get(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_filestat_set_size(int32_t arg0, int64_t arg1) { + extern int32_t wasi_vfs_fd_filestat_set_size(int32_t arg0, int64_t arg1); + return wasi_vfs_fd_filestat_set_size(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_filestat_set_times(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_filestat_set_times(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3); + return wasi_vfs_fd_filestat_set_times(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_pread(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_fd_pread(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4); + return wasi_vfs_fd_pread(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_prestat_get(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_prestat_get(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_prestat_get(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_prestat_dir_name(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_fd_prestat_dir_name(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_fd_prestat_dir_name(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_pwrite(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_fd_pwrite(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4); + return wasi_vfs_fd_pwrite(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_read(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_read(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_fd_read(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_readdir(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_fd_readdir(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4); + return wasi_vfs_fd_readdir(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_renumber(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_renumber(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_renumber(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_fd_seek(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_sync(int32_t arg0) { + extern int32_t wasi_vfs_fd_sync(int32_t arg0); + return wasi_vfs_fd_sync(arg0); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_tell(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_tell(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_tell(arg0, arg1); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_fd_write(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_write(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_fd_write(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_create_directory(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_path_create_directory(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_path_create_directory(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_filestat_get(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_path_filestat_get(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4); + return wasi_vfs_path_filestat_get(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_filestat_set_times(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int64_t arg4, int64_t arg5, int32_t arg6) { + extern int32_t wasi_vfs_path_filestat_set_times(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int64_t arg4, int64_t arg5, int32_t arg6); + return wasi_vfs_path_filestat_set_times(arg0, arg1, arg2, arg3, arg4, arg5, arg6); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_link(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6) { + extern int32_t wasi_vfs_path_link(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6); + return wasi_vfs_path_link(arg0, arg1, arg2, arg3, arg4, arg5, arg6); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8) { + extern int32_t wasi_vfs_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8); + return wasi_vfs_path_open(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_readlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5) { + extern int32_t wasi_vfs_path_readlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); + return wasi_vfs_path_readlink(arg0, arg1, arg2, arg3, arg4, arg5); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_remove_directory(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_path_remove_directory(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_path_remove_directory(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_rename(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5) { + extern int32_t wasi_vfs_path_rename(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); + return wasi_vfs_path_rename(arg0, arg1, arg2, arg3, arg4, arg5); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_symlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_path_symlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4); + return wasi_vfs_path_symlink(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_path_unlink_file(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_path_unlink_file(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_path_unlink_file(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __imported_wasi_snapshot_preview1_poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_poll_oneoff(arg0, arg1, arg2, arg3); +} + + diff --git a/src/trampoline_generated.rs b/src/trampoline_generated.rs new file mode 100644 index 0000000..91f2f10 --- /dev/null +++ b/src/trampoline_generated.rs @@ -0,0 +1,1005 @@ +// This file is automatically generated, DO NOT EDIT +// +// To regenerate this file run the `crates/wasi-libc-trampoline-bindgen` command +#![allow(unused_variables)] +use crate::UserFd; +use wasi::*; + +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_advise(arg0: i32, arg1: i64, arg2: i64, arg3: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_advise(fd: {}, offset: {}, len: {}, advice: {})\n", + arg0, arg1, arg2, arg3 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_advise(arg0, arg1, arg2, arg3); + }; + { + match crate::wasi_snapshot_preview1::fd_advise( + fs, + arg0 as UserFd, + arg1 as u64, + arg2 as u64, + arg3, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_advise", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_allocate(arg0: i32, arg1: i64, arg2: i64) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_allocate(fd: {}, offset: {}, len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_allocate(arg0, arg1, arg2); + }; + { + match crate::wasi_snapshot_preview1::fd_allocate( + fs, + arg0 as UserFd, + arg1 as u64, + arg2 as u64, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_allocate", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_close(arg0: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_close(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_close(arg0); + }; + { + match crate::wasi_snapshot_preview1::fd_close(fs, arg0 as UserFd) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_close", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_datasync(arg0: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_datasync(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_datasync(arg0); + }; + { + match crate::wasi_snapshot_preview1::fd_datasync(fs, arg0 as UserFd) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_datasync", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_fdstat_get(arg0: i32, arg1: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_fdstat_get(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_fdstat_get(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_fdstat_get(fs, arg0 as UserFd) { + Ok(e) => { + core::ptr::write(arg1 as *mut Fdstat, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_fdstat_get", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_fdstat_set_flags(arg0: i32, arg1: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_fdstat_set_flags(fd: {}, flags: {})\n", + arg0, arg1 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_fdstat_set_flags(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_fdstat_set_flags( + fs, + arg0 as UserFd, + arg1 as Fdflags, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_fdstat_set_flags", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_fdstat_set_rights(arg0: i32, arg1: i64, arg2: i64) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_fdstat_set_rights(fd: {}, fs_rights_base: {}, fs_rights_inheriting: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_fdstat_set_rights(arg0, arg1, arg2); + }; + { + match crate::wasi_snapshot_preview1::fd_fdstat_set_rights( + fs, + arg0 as UserFd, + arg1 as Rights, + arg2 as Rights, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_fdstat_set_rights", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_filestat_get(arg0: i32, arg1: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_filestat_get(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_filestat_get(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_filestat_get(fs, arg0 as UserFd) { + Ok(e) => { + core::ptr::write(arg1 as *mut Filestat, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_filestat_get", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_filestat_set_size(arg0: i32, arg1: i64) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_filestat_set_size(fd: {}, size: {})\n", + arg0, arg1 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_filestat_set_size(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_filestat_set_size(fs, arg0 as UserFd, arg1 as u64) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_filestat_set_size", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_filestat_set_times( + arg0: i32, + arg1: i64, + arg2: i64, + arg3: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_filestat_set_times(fd: {}, atim: {}, mtim: {}, fst_flags: {})\n", + arg0, arg1, arg2, arg3 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_filestat_set_times(arg0, arg1, arg2, arg3); + }; + { + match crate::wasi_snapshot_preview1::fd_filestat_set_times( + fs, + arg0 as UserFd, + arg1 as u64, + arg2 as u64, + arg3 as Fstflags, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_filestat_set_times", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_pread( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i64, + arg4: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_pread(fd: {}, iovs: {}, iovs_len: {}, offset: {})\n", + arg0, arg1, arg2, arg3 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_pread(arg0, arg1, arg2, arg3, arg4); + }; + { + match crate::wasi_snapshot_preview1::fd_pread( + fs, + arg0 as UserFd, + core::slice::from_raw_parts(arg1 as *const Iovec, arg2 as usize), + arg3 as u64, + ) { + Ok(e) => { + core::ptr::write(arg4 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_pread", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_prestat_get(arg0: i32, arg1: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_prestat_get(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_prestat_get(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_prestat_get(fs, arg0 as UserFd) { + Ok(e) => { + core::ptr::write(arg1 as *mut Prestat, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_prestat_get", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_prestat_dir_name(arg0: i32, arg1: i32, arg2: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_prestat_dir_name(fd: {}, path: {}, path_len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_prestat_dir_name(arg0, arg1, arg2); + }; + { + match crate::wasi_snapshot_preview1::fd_prestat_dir_name( + fs, + arg0 as UserFd, + arg1 as *mut u8, + arg2 as u32, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_prestat_dir_name", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_pwrite( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i64, + arg4: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_pwrite(fd: {}, iovs: {}, iovs_len: {}, offset: {})\n", + arg0, arg1, arg2, arg3 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_pwrite(arg0, arg1, arg2, arg3, arg4); + }; + { + match crate::wasi_snapshot_preview1::fd_pwrite( + fs, + arg0 as UserFd, + core::slice::from_raw_parts(arg1 as *const Ciovec, arg2 as usize), + arg3 as u64, + ) { + Ok(e) => { + core::ptr::write(arg4 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_pwrite", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_read(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_read(fd: {}, iovs: {}, iovs_len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_read(arg0, arg1, arg2, arg3); + }; + { + match crate::wasi_snapshot_preview1::fd_read( + fs, + arg0 as UserFd, + core::slice::from_raw_parts(arg1 as *const Iovec, arg2 as usize), + ) { + Ok(e) => { + core::ptr::write(arg3 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_read", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_readdir( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i64, + arg4: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_readdir(fd: {}, buf: {}, buf_len: {}, cookie: {})\n", + arg0, arg1, arg2, arg3 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_readdir(arg0, arg1, arg2, arg3, arg4); + }; + { + match crate::wasi_snapshot_preview1::fd_readdir( + fs, + arg0 as UserFd, + arg1 as *mut u8, + arg2 as u32, + arg3 as u64, + ) { + Ok(e) => { + core::ptr::write(arg4 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_readdir", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_renumber(arg0: i32, arg1: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_renumber(fd: {}, to: {})\n", arg0, arg1)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_renumber(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_renumber(fs, arg0 as UserFd, arg1 as UserFd) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_renumber", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_seek(arg0: i32, arg1: i64, arg2: i32, arg3: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_seek(fd: {}, offset: {}, whence: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_seek(arg0, arg1, arg2, arg3); + }; + { + match crate::wasi_snapshot_preview1::fd_seek(fs, arg0 as UserFd, arg1 as i64, arg2) { + Ok(e) => { + core::ptr::write(arg3 as *mut Filesize, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_seek", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_sync(arg0: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_sync(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_sync(arg0); + }; + { + match crate::wasi_snapshot_preview1::fd_sync(fs, arg0 as UserFd) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_sync", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_tell(arg0: i32, arg1: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!("fd_tell(fd: {})\n", arg0)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_tell(arg0, arg1); + }; + { + match crate::wasi_snapshot_preview1::fd_tell(fs, arg0 as UserFd) { + Ok(e) => { + core::ptr::write(arg1 as *mut Filesize, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_tell", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_fd_write(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "fd_write(fd: {}, iovs: {}, iovs_len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::fd_write(arg0, arg1, arg2, arg3); + }; + { + match crate::wasi_snapshot_preview1::fd_write( + fs, + arg0 as UserFd, + core::slice::from_raw_parts(arg1 as *const Ciovec, arg2 as usize), + ) { + Ok(e) => { + core::ptr::write(arg3 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("fd_write", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_create_directory(arg0: i32, arg1: i32, arg2: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "path_create_directory(fd: {}, path: {}, path_len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_create_directory(arg0, arg1, arg2); + }; + { + match crate::wasi_snapshot_preview1::path_create_directory(fs, arg0 as UserFd, { + let str_bytes = core::slice::from_raw_parts(arg1 as *const u8, (arg2 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_create_directory", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_filestat_get( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "path_filestat_get(fd: {}, flags: {}, path: {}, path_len: {})\n", + arg0, arg1, arg2, arg3 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_filestat_get(arg0, arg1, arg2, arg3, arg4); + }; + { + match crate::wasi_snapshot_preview1::path_filestat_get( + fs, + arg0 as UserFd, + arg1 as Lookupflags, + { + let str_bytes = core::slice::from_raw_parts(arg2 as *const u8, (arg3 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + ) { + Ok(e) => { + core::ptr::write(arg4 as *mut Filestat, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_filestat_get", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_filestat_set_times( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i64, + arg5: i64, + arg6: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] +crate::trace::trace_syscall_entry(format_args!("path_filestat_set_times(fd: {}, flags: {}, path: {}, path_len: {}, atim: {}, mtim: {}, fst_flags: {})\n", arg0, arg1, arg2, arg3, arg4, arg5, arg6)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_filestat_set_times( + arg0, arg1, arg2, arg3, arg4, arg5, arg6, + ); + }; + { + match crate::wasi_snapshot_preview1::path_filestat_set_times( + fs, + arg0 as UserFd, + arg1 as Lookupflags, + { + let str_bytes = core::slice::from_raw_parts(arg2 as *const u8, (arg3 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + arg4 as u64, + arg5 as u64, + arg6 as Fstflags, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_filestat_set_times", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_link( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + arg6: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] +crate::trace::trace_syscall_entry(format_args!("path_link(old_fd: {}, old_flags: {}, old_path: {}, old_path_len: {}, new_fd: {}, new_path: {}, new_path_len: {})\n", arg0, arg1, arg2, arg3, arg4, arg5, arg6)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_link(arg0, arg1, arg2, arg3, arg4, arg5, arg6); + }; + { + match crate::wasi_snapshot_preview1::path_link( + fs, + arg0 as UserFd, + arg1 as Lookupflags, + { + let str_bytes = core::slice::from_raw_parts(arg2 as *const u8, (arg3 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + arg4 as UserFd, + { + let str_bytes = core::slice::from_raw_parts(arg5 as *const u8, (arg6 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_link", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_open( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i64, + arg6: i64, + arg7: i32, + arg8: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] +crate::trace::trace_syscall_entry(format_args!("path_open(fd: {}, dirflags: {}, path: {}, path_len: {}, oflags: {}, fs_rights_base: {}, fs_rights_inheriting: {}, fdflags: {})\n", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_open( + arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, + ); + }; + { + match crate::wasi_snapshot_preview1::path_open( + fs, + arg0 as UserFd, + arg1 as Lookupflags, + { + let str_bytes = core::slice::from_raw_parts(arg2 as *const u8, (arg3 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + arg4 as Oflags, + arg5 as Rights, + arg6 as Rights, + arg7 as Fdflags, + ) { + Ok(e) => { + core::ptr::write(arg8 as *mut Fd, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_open", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_readlink( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "path_readlink(fd: {}, path: {}, path_len: {}, buf: {}, buf_len: {})\n", + arg0, arg1, arg2, arg3, arg4 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_readlink(arg0, arg1, arg2, arg3, arg4, arg5); + }; + { + match crate::wasi_snapshot_preview1::path_readlink( + fs, + arg0 as UserFd, + { + let str_bytes = core::slice::from_raw_parts(arg1 as *const u8, (arg2 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + arg3 as *mut u8, + arg4 as u32, + ) { + Ok(e) => { + core::ptr::write(arg5 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_readlink", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_remove_directory(arg0: i32, arg1: i32, arg2: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "path_remove_directory(fd: {}, path: {}, path_len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_remove_directory(arg0, arg1, arg2); + }; + { + match crate::wasi_snapshot_preview1::path_remove_directory(fs, arg0 as UserFd, { + let str_bytes = core::slice::from_raw_parts(arg1 as *const u8, (arg2 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_remove_directory", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_rename( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] +crate::trace::trace_syscall_entry(format_args!("path_rename(fd: {}, old_path: {}, old_path_len: {}, new_fd: {}, new_path: {}, new_path_len: {})\n", arg0, arg1, arg2, arg3, arg4, arg5)); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_rename(arg0, arg1, arg2, arg3, arg4, arg5); + }; + { + match crate::wasi_snapshot_preview1::path_rename( + fs, + arg0 as UserFd, + { + let str_bytes = core::slice::from_raw_parts(arg1 as *const u8, (arg2 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + arg3 as UserFd, + { + let str_bytes = core::slice::from_raw_parts(arg4 as *const u8, (arg5 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_rename", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_symlink( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, +) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "path_symlink(old_path: {}, old_path_len: {}, fd: {}, new_path: {}, new_path_len: {})\n", + arg0, arg1, arg2, arg3, arg4 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_symlink(arg0, arg1, arg2, arg3, arg4); + }; + { + match crate::wasi_snapshot_preview1::path_symlink( + fs, + { + let str_bytes = core::slice::from_raw_parts(arg0 as *const u8, (arg1 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + arg2 as UserFd, + { + let str_bytes = core::slice::from_raw_parts(arg3 as *const u8, (arg4 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }, + ) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_symlink", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_path_unlink_file(arg0: i32, arg1: i32, arg2: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "path_unlink_file(fd: {}, path: {}, path_len: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::path_unlink_file(arg0, arg1, arg2); + }; + { + match crate::wasi_snapshot_preview1::path_unlink_file(fs, arg0 as UserFd, { + let str_bytes = core::slice::from_raw_parts(arg1 as *const u8, (arg2 + 1) as usize); + std::ffi::CStr::from_bytes_with_nul_unchecked(str_bytes) + }) { + Ok(e) => wasi::ERRNO_SUCCESS.raw() as i32, + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("path_unlink_file", e.clone()); + + e.raw() as i32 + } + } + } +} +#[no_mangle] +pub unsafe extern "C" fn wasi_vfs_poll_oneoff(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32 { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_entry(format_args!( + "poll_oneoff(in: {}, out: {}, nsubscriptions: {})\n", + arg0, arg1, arg2 + )); + let fs = if let Some(fs) = crate::GLOBAL_STATE.overlay_fs.as_mut() { + fs + } else { + return wasi::wasi_snapshot_preview1::poll_oneoff(arg0, arg1, arg2, arg3); + }; + { + match crate::wasi_snapshot_preview1::poll_oneoff( + fs, + arg0 as *const Subscription, + arg1 as *mut Event, + arg2 as u32, + ) { + Ok(e) => { + core::ptr::write(arg3 as *mut Size, e); + wasi::ERRNO_SUCCESS.raw() as i32 + } + Err(e) => { + #[cfg(feature = "trace-syscall")] + crate::trace::trace_syscall_error("poll_oneoff", e.clone()); + + e.raw() as i32 + } + } + } +} diff --git a/src/trampoline_generated_legacy_wasi_libc.c b/src/trampoline_generated_legacy_wasi_libc.c new file mode 100644 index 0000000..099d799 --- /dev/null +++ b/src/trampoline_generated_legacy_wasi_libc.c @@ -0,0 +1,199 @@ +// This file is automatically generated, DO NOT EDIT +// +// To regenerate this file run the `crates/wasi-libc-trampoline-bindgen` command + +#include + +__attribute__((weak)) +int32_t __wasi_fd_advise(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_advise(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3); + return wasi_vfs_fd_advise(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __wasi_fd_allocate(int32_t arg0, int64_t arg1, int64_t arg2) { + extern int32_t wasi_vfs_fd_allocate(int32_t arg0, int64_t arg1, int64_t arg2); + return wasi_vfs_fd_allocate(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __wasi_fd_close(int32_t arg0) { + extern int32_t wasi_vfs_fd_close(int32_t arg0); + return wasi_vfs_fd_close(arg0); +} + +__attribute__((weak)) +int32_t __wasi_fd_datasync(int32_t arg0) { + extern int32_t wasi_vfs_fd_datasync(int32_t arg0); + return wasi_vfs_fd_datasync(arg0); +} + +__attribute__((weak)) +int32_t __wasi_fd_fdstat_get(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_fdstat_get(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_fdstat_get(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_fdstat_set_flags(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_fdstat_set_flags(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_fdstat_set_flags(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_fdstat_set_rights(int32_t arg0, int64_t arg1, int64_t arg2) { + extern int32_t wasi_vfs_fd_fdstat_set_rights(int32_t arg0, int64_t arg1, int64_t arg2); + return wasi_vfs_fd_fdstat_set_rights(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __wasi_fd_filestat_get(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_filestat_get(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_filestat_get(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_filestat_set_size(int32_t arg0, int64_t arg1) { + extern int32_t wasi_vfs_fd_filestat_set_size(int32_t arg0, int64_t arg1); + return wasi_vfs_fd_filestat_set_size(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_filestat_set_times(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_filestat_set_times(int32_t arg0, int64_t arg1, int64_t arg2, int32_t arg3); + return wasi_vfs_fd_filestat_set_times(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __wasi_fd_pread(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_fd_pread(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4); + return wasi_vfs_fd_pread(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __wasi_fd_prestat_get(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_prestat_get(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_prestat_get(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_prestat_dir_name(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_fd_prestat_dir_name(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_fd_prestat_dir_name(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __wasi_fd_pwrite(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_fd_pwrite(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4); + return wasi_vfs_fd_pwrite(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __wasi_fd_read(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_read(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_fd_read(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __wasi_fd_readdir(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_fd_readdir(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3, int32_t arg4); + return wasi_vfs_fd_readdir(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __wasi_fd_renumber(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_renumber(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_renumber(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_fd_seek(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __wasi_fd_sync(int32_t arg0) { + extern int32_t wasi_vfs_fd_sync(int32_t arg0); + return wasi_vfs_fd_sync(arg0); +} + +__attribute__((weak)) +int32_t __wasi_fd_tell(int32_t arg0, int32_t arg1) { + extern int32_t wasi_vfs_fd_tell(int32_t arg0, int32_t arg1); + return wasi_vfs_fd_tell(arg0, arg1); +} + +__attribute__((weak)) +int32_t __wasi_fd_write(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_fd_write(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_fd_write(arg0, arg1, arg2, arg3); +} + +__attribute__((weak)) +int32_t __wasi_path_create_directory(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_path_create_directory(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_path_create_directory(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __wasi_path_filestat_get(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_path_filestat_get(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4); + return wasi_vfs_path_filestat_get(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __wasi_path_filestat_set_times(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int64_t arg4, int64_t arg5, int32_t arg6) { + extern int32_t wasi_vfs_path_filestat_set_times(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int64_t arg4, int64_t arg5, int32_t arg6); + return wasi_vfs_path_filestat_set_times(arg0, arg1, arg2, arg3, arg4, arg5, arg6); +} + +__attribute__((weak)) +int32_t __wasi_path_link(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6) { + extern int32_t wasi_vfs_path_link(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6); + return wasi_vfs_path_link(arg0, arg1, arg2, arg3, arg4, arg5, arg6); +} + +__attribute__((weak)) +int32_t __wasi_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8) { + extern int32_t wasi_vfs_path_open(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int64_t arg5, int64_t arg6, int32_t arg7, int32_t arg8); + return wasi_vfs_path_open(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + +__attribute__((weak)) +int32_t __wasi_path_readlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5) { + extern int32_t wasi_vfs_path_readlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); + return wasi_vfs_path_readlink(arg0, arg1, arg2, arg3, arg4, arg5); +} + +__attribute__((weak)) +int32_t __wasi_path_remove_directory(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_path_remove_directory(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_path_remove_directory(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __wasi_path_rename(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5) { + extern int32_t wasi_vfs_path_rename(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); + return wasi_vfs_path_rename(arg0, arg1, arg2, arg3, arg4, arg5); +} + +__attribute__((weak)) +int32_t __wasi_path_symlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) { + extern int32_t wasi_vfs_path_symlink(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4); + return wasi_vfs_path_symlink(arg0, arg1, arg2, arg3, arg4); +} + +__attribute__((weak)) +int32_t __wasi_path_unlink_file(int32_t arg0, int32_t arg1, int32_t arg2) { + extern int32_t wasi_vfs_path_unlink_file(int32_t arg0, int32_t arg1, int32_t arg2); + return wasi_vfs_path_unlink_file(arg0, arg1, arg2); +} + +__attribute__((weak)) +int32_t __wasi_poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3) { + extern int32_t wasi_vfs_poll_oneoff(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); + return wasi_vfs_poll_oneoff(arg0, arg1, arg2, arg3); +} + + diff --git a/src/wasi_snapshot_preview1.rs b/src/wasi_snapshot_preview1.rs new file mode 100644 index 0000000..102218a --- /dev/null +++ b/src/wasi_snapshot_preview1.rs @@ -0,0 +1,904 @@ +use core::slice; +use std::{ + ffi::{CStr, OsStr}, + mem::MaybeUninit, + path::Path, +}; +use wasi::{ + CiovecArray, Dircookie, Event, Fd, Fdflags, Fdstat, Filedelta, Filesize, Filestat, Fstflags, + IovecArray, Lookupflags, Oflags, Prestat, PrestatDir, PrestatU, Rights, Size, Subscription, + Timestamp, RIGHTS_FD_ADVISE, RIGHTS_FD_FILESTAT_GET, RIGHTS_FD_READ, RIGHTS_FD_READDIR, + RIGHTS_PATH_OPEN, +}; + +use crate::{ + embed::{Node, NodeDirBody, NodeFileBody, NodeIdTrait, Storage}, + BackingFd, Error, FileSystem, UserFd, +}; + +pub(crate) unsafe fn fd_advise( + fs: &mut FileSystem, + fd: UserFd, + offset: Filesize, + len: Filesize, + advice: i32, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_advise( + fd as i32, + offset as i64, + len as i64, + advice as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_allocate( + fs: &mut FileSystem, + fd: UserFd, + offset: Filesize, + len: Filesize, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = + wasi::wasi_snapshot_preview1::fd_allocate(fd as i32, offset as i64, len as i64); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_close(fs: &mut FileSystem, fd: UserFd) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => Ok(fs.embedded_fs.close_file(vfd)?), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_close(fd as i32); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_datasync( + fs: &mut FileSystem, + fd: UserFd, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_datasync(fd as i32); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_fdstat_get( + fs: &mut FileSystem, + fd: UserFd, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + let ro_rights = RIGHTS_FD_READ + | RIGHTS_FD_ADVISE + | RIGHTS_PATH_OPEN + | RIGHTS_FD_READDIR + | RIGHTS_FD_FILESTAT_GET; + match fd { + BackingFd::Virtual(vfd) => { + let stat = fs.embedded_fs.get_fd_stat(vfd)?; + Ok(stat) + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = + wasi::wasi_snapshot_preview1::fd_fdstat_get(fd as i32, rp0.as_mut_ptr() as i32); + match ret { + 0 => Ok(rp0.assume_init()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_fdstat_set_flags( + fs: &mut FileSystem, + fd: UserFd, + flags: Fdflags, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let entry = fs.embedded_fs.get_fd_entry_mut(vfd)?; + entry.flags = flags; + Ok(()) + } + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_fdstat_set_flags(fd as i32, flags as i32); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_fdstat_set_rights( + fs: &mut FileSystem, + fd: UserFd, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_fdstat_set_rights( + fd as i32, + fs_rights_base as i64, + fs_rights_inheriting as i64, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_filestat_get( + fs: &mut FileSystem, + fd: UserFd, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let fd_entry = fs.embedded_fs.get_fd_entry(vfd)?; + Ok(fs.embedded_fs.get_filestat_from_node_id(fd_entry.node_id)) + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = + wasi::wasi_snapshot_preview1::fd_filestat_get(fd as i32, rp0.as_mut_ptr() as i32); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Filestat)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_filestat_set_size( + fs: &mut FileSystem, + fd: UserFd, + size: Filesize, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_filestat_set_size(fd as i32, size as i64); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_filestat_set_times( + fs: &mut FileSystem, + fd: UserFd, + atim: Timestamp, + mtim: Timestamp, + fst_flags: Fstflags, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_filestat_set_times( + fd as i32, + atim as i64, + mtim as i64, + fst_flags as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_pread( + fs: &mut FileSystem, + fd: UserFd, + iovs: IovecArray<'_>, + offset: Filesize, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_pread( + fd as i32, + iovs.as_ptr() as i32, + iovs.len() as i32, + offset as i64, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_prestat_get( + fs: &mut FileSystem, + fd: UserFd, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + if let Some(dir) = fs.embedded_fs.get_preopened_dir_path(vfd) { + let stat = Prestat { + tag: wasi::PREOPENTYPE_DIR.raw(), + u: PrestatU { + dir: PrestatDir { + pr_name_len: dir.as_bytes().len(), + }, + }, + }; + Ok(stat) + } else { + Err(wasi::ERRNO_BADF.into()) + } + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = + wasi::wasi_snapshot_preview1::fd_prestat_get(fd as i32, rp0.as_mut_ptr() as i32); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Prestat)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_prestat_dir_name( + fs: &mut FileSystem, + fd: UserFd, + path: *mut u8, + path_len: u32, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + if let Some(dir) = fs.embedded_fs.get_preopened_dir_path(vfd) { + let path = slice::from_raw_parts_mut(path, path_len as usize); + for (offset, byte) in dir.as_bytes().iter().enumerate() { + path[offset] = *byte; + } + Ok(()) + } else { + Err(wasi::ERRNO_BADF.into()) + } + } + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_prestat_dir_name( + fd as i32, + path as i32, + path_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_pwrite( + fs: &mut FileSystem, + fd: UserFd, + iovs: CiovecArray<'_>, + offset: Filesize, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_pwrite( + fd as i32, + iovs.as_ptr() as i32, + iovs.len() as i32, + offset as i64, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_read( + fs: &mut FileSystem, + fd: UserFd, + iovs: IovecArray<'_>, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let node = fs.embedded_fs.get_node(vfd)?; + match node { + Node::Dir { .. } => Err(wasi::ERRNO_ISDIR.into()), + Node::File(body) => { + let open = fs.embedded_fs.get_fd_entry(vfd)?; + let mut cursor = std::io::Cursor::new(body.content()); + cursor.set_position(open.offset as u64); + let read_bytes = read_bytes(cursor, iovs)?; + let open = fs.embedded_fs.get_fd_entry_mut(vfd)?; + open.offset += read_bytes; + Ok(read_bytes) + } + } + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_read( + fd as i32, + iovs.as_ptr() as i32, + iovs.len() as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_readdir( + fs: &mut FileSystem, + fd: UserFd, + buf: *mut u8, + buf_len: u32, + cookie: Dircookie, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let node = fs.embedded_fs.get_node(vfd)?; + let entries = match node { + Node::Dir(body) => body.entries(), + Node::File { .. } => { + return Err(wasi::ERRNO_NOTDIR.into()); + } + }; + let mut bufused = 0; + let mut current_cookie = cookie; + let mut buf = buf; + let buf_len = buf_len as usize; + for entry in entries { + current_cookie += 1; + let name_len = entry.name.len(); + let node_id = fs.embedded_fs.get_node_id_by_link(entry.link_id); + let node_stat = fs.embedded_fs.get_filestat_from_node_id(node_id); + let dirent = wasi::Dirent { + d_next: current_cookie, + d_ino: node_id.ino() as u64, + d_namlen: name_len as u32, + d_type: node_stat.filetype, + }; + let dirent_len = std::mem::size_of::(); + let dirent_copy_len = std::cmp::min(dirent_len, buf_len - bufused); + std::ptr::copy(&dirent as *const _ as *const u8, buf, dirent_copy_len); + if dirent_copy_len < dirent_len { + return Ok(buf_len); + } + buf = buf.add(dirent_copy_len); + bufused += dirent_copy_len; + + let name_copy_len = std::cmp::min(name_len, buf_len - bufused); + std::ptr::copy(entry.name.as_ptr(), buf, name_copy_len); + + if name_copy_len < name_len { + return Ok(buf_len); + } + buf = buf.add(name_len); + bufused += name_copy_len; + } + Ok(bufused) + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_readdir( + fd as i32, + buf as i32, + buf_len as i32, + cookie as i64, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_renumber( + fs: &mut FileSystem, + fd: UserFd, + to: UserFd, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + let to = fs.get_backing_fd(to)?; + match (fd, to) { + (BackingFd::Wasi(fd), BackingFd::Wasi(to)) => { + let ret = wasi::wasi_snapshot_preview1::fd_renumber(fd as i32, to as i32); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + (_, _) => todo!(), + } +} + +pub(crate) unsafe fn fd_seek( + fs: &mut FileSystem, + fd: UserFd, + offset: Filedelta, + whence: i32, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + fn compute_new_offset(base: usize, offset: Filedelta) -> Result { + let new_offset = if offset > 0 { + base + (offset as usize) + } else { + let neg_offset = (-offset) as usize; + if neg_offset < base { + base - neg_offset + } else { + return Err(wasi::ERRNO_INVAL.into()); + } + }; + Ok(new_offset) + } + match fd { + BackingFd::Virtual(vfd) => { + let whence: wasi::Whence = std::mem::transmute(whence as u8); + match whence { + wasi::WHENCE_SET => { + let fd_entry = fs.embedded_fs.get_fd_entry_mut(vfd)?; + fd_entry.offset = offset as usize; + Ok(offset as Filesize) + } + wasi::WHENCE_CUR => { + let fd_entry = fs.embedded_fs.get_fd_entry_mut(vfd)?; + let absolute_offset = compute_new_offset(fd_entry.offset, offset)?; + fd_entry.offset = absolute_offset; + Ok(absolute_offset as Filesize) + } + wasi::WHENCE_END => { + let fd_entry = fs.embedded_fs.get_fd_entry(vfd)?; + let node = fs.embedded_fs.get_node(vfd)?; + match node { + Node::File(body) => { + let content_len = body.content().len(); + let fd_entry = fs.embedded_fs.get_fd_entry_mut(vfd)?; + let absolute_offset = compute_new_offset(content_len, offset)?; + fd_entry.offset = absolute_offset; + Ok(absolute_offset as Filesize) + } + Node::Dir { .. } => Err(wasi::ERRNO_INVAL.into()), + } + } + _ => Err(wasi::ERRNO_INVAL.into()), + } + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_seek( + fd as i32, + offset, + whence as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Filesize)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_sync(fs: &mut FileSystem, fd: UserFd) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::fd_sync(fd as i32); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_tell( + fs: &mut FileSystem, + fd: UserFd, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let node = fs.embedded_fs.get_node(vfd)?; + let open = fs.embedded_fs.get_fd_entry_mut(vfd)?; + Ok(open.offset as u64) + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_tell(fd as i32, rp0.as_mut_ptr() as i32); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Filesize)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn fd_write( + fs: &mut FileSystem, + fd: UserFd, + iovs: CiovecArray<'_>, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::fd_write( + fd as i32, + iovs.as_ptr() as i32, + iovs.len() as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(rp0.assume_init()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_create_directory( + fs: &mut FileSystem, + fd: UserFd, + path: &CStr, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::path_create_directory( + fd as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_filestat_get( + fs: &mut FileSystem, + fd: UserFd, + flags: Lookupflags, + path: &CStr, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let path = cstr_to_path(path)?; + Ok(fs.embedded_fs.get_filestat_at_path(vfd, path)?) + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::path_filestat_get( + fd as i32, + flags as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Filestat)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_filestat_set_times( + fs: &mut FileSystem, + fd: UserFd, + flags: Lookupflags, + path: &CStr, + atim: Timestamp, + mtim: Timestamp, + fst_flags: Fstflags, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::path_filestat_set_times( + fd as i32, + flags as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + atim as i64, + mtim as i64, + fst_flags as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_link( + fs: &mut FileSystem, + old_fd: UserFd, + old_flags: Lookupflags, + old_path: &CStr, + new_fd: UserFd, + new_path: &CStr, +) -> Result<(), Error> { + let old_fd = fs.get_backing_fd(old_fd)?; + let new_fd = fs.get_backing_fd(new_fd)?; + match (old_fd, new_fd) { + (BackingFd::Wasi(old_fd), BackingFd::Wasi(new_fd)) => { + let ret = wasi::wasi_snapshot_preview1::path_link( + old_fd as i32, + old_flags as i32, + old_path.as_ptr() as i32, + old_path.to_bytes().len() as i32, + new_fd as i32, + new_path.as_ptr() as i32, + new_path.to_bytes().len() as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + (_, _) => todo!(), + } +} + +pub(crate) unsafe fn path_open( + fs: &mut FileSystem, + fd: UserFd, + dirflags: Lookupflags, + path: &CStr, + oflags: Oflags, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + fdflags: Fdflags, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => { + let path = cstr_to_path(path)?; + let new_vfd = fs.embedded_fs.open_file(vfd, path, fdflags)?; + Ok(fs.issue_user_fd(BackingFd::Virtual(new_vfd))) + } + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::path_open( + fd as i32, + dirflags as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + oflags as i32, + fs_rights_base as i64, + fs_rights_inheriting as i64, + fdflags as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => { + let new_fd = BackingFd::Wasi(rp0.assume_init()); + Ok(fs.issue_user_fd(new_fd)) + } + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_readlink( + fs: &mut FileSystem, + fd: UserFd, + path: &CStr, + buf: *mut u8, + buf_len: u32, +) -> Result { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi::wasi_snapshot_preview1::path_readlink( + fd as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + buf as i32, + buf_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_remove_directory( + fs: &mut FileSystem, + fd: UserFd, + path: &CStr, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::path_remove_directory( + fd as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_rename( + fs: &mut FileSystem, + fd: UserFd, + old_path: &CStr, + new_fd: UserFd, + new_path: &CStr, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + let new_fd = fs.get_backing_fd(new_fd)?; + match (fd, new_fd) { + (BackingFd::Wasi(fd), BackingFd::Wasi(new_fd)) => { + let ret = wasi::wasi_snapshot_preview1::path_rename( + fd as i32, + old_path.as_ptr() as i32, + old_path.to_bytes().len() as i32, + new_fd as i32, + new_path.as_ptr() as i32, + new_path.to_bytes().len() as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + (_, _) => todo!(), + } +} + +pub(crate) unsafe fn path_symlink( + fs: &mut FileSystem, + old_path: &CStr, + fd: UserFd, + new_path: &CStr, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::path_symlink( + old_path.as_ptr() as i32, + old_path.to_bytes().len() as i32, + fd as i32, + new_path.as_ptr() as i32, + new_path.to_bytes().len() as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn path_unlink_file( + fs: &mut FileSystem, + fd: UserFd, + path: &CStr, +) -> Result<(), Error> { + let fd = fs.get_backing_fd(fd)?; + match fd { + BackingFd::Virtual(vfd) => todo!(), + BackingFd::Wasi(fd) => { + let ret = wasi::wasi_snapshot_preview1::path_unlink_file( + fd as i32, + path.as_ptr() as i32, + path.to_bytes().len() as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(Error(ret as u16)), + } + } + } +} + +pub(crate) unsafe fn poll_oneoff( + fs: &mut FileSystem, + in_: *const Subscription, + out: *mut Event, + nsubscriptions: u32, +) -> Result { + Err(wasi::ERRNO_NOTSUP.into()) +} + +fn read_bytes(mut src: R, iovs: wasi::IovecArray) -> Result { + let mut bytes_read = 0; + for iov in iovs { + unsafe { + let buf = slice::from_raw_parts_mut(iov.buf, iov.buf_len); + bytes_read += src.read(buf).map_err(|_| wasi::ERRNO_IO)?; + } + } + Ok(bytes_read) +} + +fn cstr_to_path(path: &CStr) -> Result<&Path, wasi::Errno> { + let os_str: &OsStr = unsafe { std::mem::transmute(path.to_bytes()) }; + Ok(Path::new(os_str)) +} diff --git a/tests/run-make/.gitignore b/tests/run-make/.gitignore new file mode 100644 index 0000000..d36977d --- /dev/null +++ b/tests/run-make/.gitignore @@ -0,0 +1 @@ +.tmp diff --git a/tests/run-make/canonical-path/Makefile b/tests/run-make/canonical-path/Makefile new file mode 100644 index 0000000..65bb6f4 --- /dev/null +++ b/tests/run-make/canonical-path/Makefile @@ -0,0 +1,12 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_RUN) --mapdir /mnt::./mnt $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /mnt::./mnt -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/canonical-path/main.c b/tests/run-make/canonical-path/main.c new file mode 100644 index 0000000..f762496 --- /dev/null +++ b/tests/run-make/canonical-path/main.c @@ -0,0 +1,11 @@ +#include "../check.h" +#include +#include + +int main(int argc, char *argv[]) { + check_file_exists("/mnt/a/b/c.txt"); + check_file_exists("/mnt/a/./b/c.txt"); + check_file_exists("/mnt/a/././b/c.txt"); + check_file_exists("/mnt/a//b/c.txt"); + return 0; +} diff --git a/tests/run-make/canonical-path/mnt/a/b/c.txt b/tests/run-make/canonical-path/mnt/a/b/c.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/chdir-emulation/Makefile b/tests/run-make/chdir-emulation/Makefile new file mode 100644 index 0000000..11e0873 --- /dev/null +++ b/tests/run-make/chdir-emulation/Makefile @@ -0,0 +1,11 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /mnt::./mnt -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/chdir-emulation/main.c b/tests/run-make/chdir-emulation/main.c new file mode 100644 index 0000000..a4d5870 --- /dev/null +++ b/tests/run-make/chdir-emulation/main.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include "../check.h" + +void check_file_content(const char *path, const char *expected) { + FILE *fp; + char *line = NULL; + size_t len = 0; + ssize_t read; + + fp = fopen(path, "r"); + if (fp == NULL) { + perror("fopen"); + exit(EXIT_FAILURE); + } + + read = getline(&line, &len, fp); + if(read == -1) { + perror("getline"); + exit(EXIT_FAILURE); + } + if (strcmp(line, expected) != 0) { + printf("expected '%s' but got '%s'\n", expected, line); + fflush(stdout); + exit(EXIT_FAILURE); + } + + fclose(fp); + if (line) + free(line); +} + +int main(int argc, char *argv[]) { + // it's important to use chdir to enable __wasilibc_find_relpath_alloc path + chdir("/mnt"); + + check_file_content("hello.txt", "Hello\n"); + check_file_content("hello.txt", "Hello\n"); + return 0; +} diff --git a/tests/run-make/chdir-emulation/mnt/hello.txt b/tests/run-make/chdir-emulation/mnt/hello.txt new file mode 100644 index 0000000..e965047 --- /dev/null +++ b/tests/run-make/chdir-emulation/mnt/hello.txt @@ -0,0 +1 @@ +Hello diff --git a/tests/run-make/check.h b/tests/run-make/check.h new file mode 100644 index 0000000..1f993de --- /dev/null +++ b/tests/run-make/check.h @@ -0,0 +1,97 @@ +#ifndef RUN_MAKE_CHECK_H +#define RUN_MAKE_CHECK_H + +#include +#include +#include +#include +#include + +static inline void _check_file_exists(const char *path, const char *file, + const char *func, int line) { + if (!path) { + fprintf(stderr, "check_file_exists: path is null in %s:%d (%s)\n", file, + line, func); + exit(1); + return; + } + FILE *f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "File %s not found in %s:%d (%s)\n", path, file, line, + func); + exit(1); + } + fclose(f); +} + +#define check_file_exists(path) \ + _check_file_exists(path, __FILE__, __func__, __LINE__) + +static inline void _check_file_not_exists(const char *path, const char *file, + const char *func, int line) { + if (!path) { + fprintf(stderr, "check_file_not_exists: path is null in %s:%d (%s)\n", file, + line, func); + exit(1); + return; + } + FILE *f = fopen(path, "r"); + if (f) { + fprintf(stderr, "File %s unexpectedly found in %s:%d (%s)\n", path, file, + line, func); + fclose(f); + } +} + +#define check_file_not_exists(path) \ + _check_file_not_exists(path, __FILE__, __func__, __LINE__) + +static inline void _check_dir_entry_size(const char *dir, int expected, + const char *file, const char *func, + int line) { + DIR *d = opendir(dir); + if (!d) { + fprintf(stderr, "Directory %s not found in %s:%d (%s)\n", dir, file, line, + func); + exit(1); + } + struct dirent *de; + int count = 0; + while ((de = readdir(d))) { + if (strlen(de->d_name) == 1 && de->d_name[0] == '.') + continue; + if (strlen(de->d_name) == 2 && de->d_name[0] == '.' && de->d_name[1] == '.') + continue; + count++; + } + closedir(d); + if (count != expected) { + fprintf(stderr, "Directory %s should contain %d files, but contains %d\n", + dir, expected, count); + exit(1); + } +} + +#define check_dir_entry_size(dir, expected) \ + _check_dir_entry_size(dir, expected, __FILE__, __func__, __LINE__) + +#endif + +static inline void _check_access(const char *path, const char *file, + const char *func, int line) { + if (!path) { + fprintf(stderr, "check_access: path is null in %s:%d (%s)\n", file, line, + func); + exit(1); + return; + } + int e = access(path, F_OK); + if (e != 0) { + fprintf(stderr, "File %s is not accessible %s:%d (%s)\n", path, file, line, + func); + perror("access"); + exit(1); + } +} + +#define check_access(path) _check_access(path, __FILE__, __func__, __LINE__) diff --git a/tests/run-make/libc-api/Makefile b/tests/run-make/libc-api/Makefile new file mode 100644 index 0000000..100b514 --- /dev/null +++ b/tests/run-make/libc-api/Makefile @@ -0,0 +1,11 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /usr::./mnt -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm --mapdir /::./ + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/libc-api/hello.txt b/tests/run-make/libc-api/hello.txt new file mode 100644 index 0000000..e484e3b --- /dev/null +++ b/tests/run-make/libc-api/hello.txt @@ -0,0 +1 @@ +from host diff --git a/tests/run-make/libc-api/main.c b/tests/run-make/libc-api/main.c new file mode 100644 index 0000000..b6311b0 --- /dev/null +++ b/tests/run-make/libc-api/main.c @@ -0,0 +1,34 @@ +#include +#include +#include + +void check_file_content(const char *path, const char *expected) { + FILE *fp; + char *line = NULL; + size_t len = 0; + ssize_t read; + + fp = fopen(path, "r"); + if (fp == NULL) { + perror("fopen"); + exit(EXIT_FAILURE); + } + + read = getline(&line, &len, fp); + if (strcmp(line, expected) != 0) { + printf("expected '%s' but got '%s'\n", expected, line); + fflush(stdout); + exit(EXIT_FAILURE); + } + + fclose(fp); + if (line) + free(line); +} + +int main(void) { + check_file_content("/hello.txt", "from host\n"); + check_file_content("/usr/hello.txt", "from vfs\n"); + check_file_content("/usr/subdir/inner.txt", "inner file\n"); + exit(EXIT_SUCCESS); +} diff --git a/tests/run-make/libc-api/mnt/hello.txt b/tests/run-make/libc-api/mnt/hello.txt new file mode 100644 index 0000000..b474574 --- /dev/null +++ b/tests/run-make/libc-api/mnt/hello.txt @@ -0,0 +1 @@ +from vfs diff --git a/tests/run-make/libc-api/mnt/subdir/inner.txt b/tests/run-make/libc-api/mnt/subdir/inner.txt new file mode 100644 index 0000000..3649765 --- /dev/null +++ b/tests/run-make/libc-api/mnt/subdir/inner.txt @@ -0,0 +1 @@ +inner file diff --git a/tests/run-make/mapdir-root/Makefile b/tests/run-make/mapdir-root/Makefile new file mode 100644 index 0000000..252a8bd --- /dev/null +++ b/tests/run-make/mapdir-root/Makefile @@ -0,0 +1,11 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /::./mnt -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/mapdir-root/main.c b/tests/run-make/mapdir-root/main.c new file mode 100644 index 0000000..58e9b40 --- /dev/null +++ b/tests/run-make/mapdir-root/main.c @@ -0,0 +1,9 @@ +#include "../check.h" +#include +#include + +int main(int argc, char *argv[]) { + check_file_exists("/hello.txt"); + check_dir_entry_size("/", 1); + return 0; +} diff --git a/tests/run-make/mapdir-root/mnt/hello.txt b/tests/run-make/mapdir-root/mnt/hello.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/minimum-link/Makefile b/tests/run-make/minimum-link/Makefile new file mode 100644 index 0000000..99f2d24 --- /dev/null +++ b/tests/run-make/minimum-link/Makefile @@ -0,0 +1,11 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/minimum-link/main.c b/tests/run-make/minimum-link/main.c new file mode 100644 index 0000000..78f2de1 --- /dev/null +++ b/tests/run-make/minimum-link/main.c @@ -0,0 +1 @@ +int main(void) { return 0; } diff --git a/tests/run-make/multi-mapdir/Makefile b/tests/run-make/multi-mapdir/Makefile new file mode 100644 index 0000000..4148168 --- /dev/null +++ b/tests/run-make/multi-mapdir/Makefile @@ -0,0 +1,19 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +define do_check + $(WASI_RUN) $(2) $(TMPDIR)/main.wasm -- $(1) + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm $(2) -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm -- $(1) + +endef + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(call do_check,simple,--mapdir /mnt1::./mnt1 --mapdir /mnt0::./mnt0) + $(call do_check,overlap1,--mapdir /mnt0/mnt1::./mnt1 --mapdir /mnt0::./mnt0) + $(call do_check,overlap2,--mapdir /mnt0::./mnt0 --mapdir /mnt0/mnt1::./mnt1) + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/multi-mapdir/main.c b/tests/run-make/multi-mapdir/main.c new file mode 100644 index 0000000..4c064a1 --- /dev/null +++ b/tests/run-make/multi-mapdir/main.c @@ -0,0 +1,26 @@ +#include "../check.h" +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + return 1; + } + char *mode = argv[1]; + if (strcmp(mode, "simple") == 0) { + check_file_exists("/mnt0/hello.txt"); + check_file_exists("/mnt1/goodbye.txt"); + } else if (strcmp(mode, "overlap1") == 0) { + check_file_exists("/mnt0/hello.txt"); + check_file_exists("/mnt0/mnt1/goodbye.txt"); + check_dir_entry_size("/mnt0", 1); + check_dir_entry_size("/mnt0/mnt1", 1); + } else if (strcmp(mode, "overlap2") == 0) { + check_file_exists("/mnt0/hello.txt"); + check_file_exists("/mnt0/mnt1/goodbye.txt"); + } else { + fprintf(stderr, "Unknown mode: %s\n", mode); + return 1; + } + return 0; +} diff --git a/tests/run-make/multi-mapdir/mnt0/hello.txt b/tests/run-make/multi-mapdir/mnt0/hello.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/multi-mapdir/mnt1/goodbye.txt b/tests/run-make/multi-mapdir/mnt1/goodbye.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/pack-twice/Makefile b/tests/run-make/pack-twice/Makefile new file mode 100644 index 0000000..5f6ec18 --- /dev/null +++ b/tests/run-make/pack-twice/Makefile @@ -0,0 +1,18 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /mnt0::./mnt0 -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm phase1 + + $(WASI_VFS_CLI) pack $(TMPDIR)/main.packed.wasm --mapdir /mnt1::./mnt1 -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm phase2 + + $(WASI_RUN) --mapdir /mnt0::./mnt0 --mapdir /mnt1::./mnt1 --mapdir /mnt1::./mnt1_1 $(TMPDIR)/main.wasm phase3 + $(WASI_VFS_CLI) pack $(TMPDIR)/main.packed.wasm --mapdir /mnt1::./mnt1_1 -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm phase3 + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/pack-twice/main.c b/tests/run-make/pack-twice/main.c new file mode 100644 index 0000000..6ad5289 --- /dev/null +++ b/tests/run-make/pack-twice/main.c @@ -0,0 +1,22 @@ +#include "../check.h" + +int main(int argc, char *argv[]) { + if (argc != 2) { + return 1; + } + char *mode = argv[1]; + if (strcmp(mode, "phase1") == 0) { + check_file_exists("/mnt0/hello.txt"); + } else if (strcmp(mode, "phase2") == 0) { + check_file_exists("/mnt0/hello.txt"); + check_file_exists("/mnt1/goodbye.txt"); + } else if (strcmp(mode, "phase3") == 0) { + check_file_exists("/mnt0/hello.txt"); + check_file_exists("/mnt1/x.txt"); + check_file_not_exists("/mnt1/goodbye.txt"); + } else { + fprintf(stderr, "Unknown mode: %s\n", mode); + return 1; + } + return 0; +} diff --git a/tests/run-make/pack-twice/mnt0/hello.txt b/tests/run-make/pack-twice/mnt0/hello.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/pack-twice/mnt1/goodbye.txt b/tests/run-make/pack-twice/mnt1/goodbye.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/pack-twice/mnt1_1/x.txt b/tests/run-make/pack-twice/mnt1_1/x.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/parent-dir/Makefile b/tests/run-make/parent-dir/Makefile new file mode 100644 index 0000000..e2441be --- /dev/null +++ b/tests/run-make/parent-dir/Makefile @@ -0,0 +1,11 @@ +-include ../tools.mk + +objs = $(TMPDIR)/main.c.o + +check: $(objs) + $(CC) $(LDFLAGS) $(objs) $(LIB_WASI_VFS) -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /usr::./usr -o $(TMPDIR)/main.packed.wasm + $(WASI_RUN) $(TMPDIR)/main.packed.wasm + +clean: + rm -rf $(PROG) $(objs) diff --git a/tests/run-make/parent-dir/main.c b/tests/run-make/parent-dir/main.c new file mode 100644 index 0000000..2629b8b --- /dev/null +++ b/tests/run-make/parent-dir/main.c @@ -0,0 +1,22 @@ +#include "../check.h" +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int usr_local = open("/usr/local/bin", O_RDONLY, O_DIRECTORY); + perror("usr_local"); + assert(usr_local != -1); + // int yay = openat(usr_local, "./yay", O_RDONLY, 0); + // perror("yay"); + // assert(yay != -1); + check_access("/usr/local/bin/../hey"); + int hey = openat(usr_local, "../hey", O_RDONLY, 0); + perror("hey"); + assert(hey != -1); + return 0; +} diff --git a/tests/run-make/parent-dir/usr/local/bin/yay b/tests/run-make/parent-dir/usr/local/bin/yay new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/parent-dir/usr/local/hey b/tests/run-make/parent-dir/usr/local/hey new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/reactor-model/Makefile b/tests/run-make/reactor-model/Makefile new file mode 100644 index 0000000..0404e55 --- /dev/null +++ b/tests/run-make/reactor-model/Makefile @@ -0,0 +1,9 @@ +-include ../tools.mk + +check: $(objs) + $(CC) $(LDFLAGS) main.c $(LIB_WASI_VFS) -mexec-model=reactor -o $(TMPDIR)/main.wasm + $(WASI_VFS_CLI) pack $(TMPDIR)/main.wasm --mapdir /::./mnt -o $(TMPDIR)/main.packed.wasm + $(NODE) --experimental-wasi-unstable-preview1 ./check.js $(TMPDIR)/main.packed.wasm + +clean: + rm -rf $(TMPDIR)/* diff --git a/tests/run-make/reactor-model/check.js b/tests/run-make/reactor-model/check.js new file mode 100644 index 0000000..3a87afb --- /dev/null +++ b/tests/run-make/reactor-model/check.js @@ -0,0 +1,17 @@ +const { WASI } = require("wasi"); +const fs = require("fs"); +const process = require("process"); + +const buffer = fs.readFileSync(process.argv[2]); + +const wasi = new WASI({ + env: { ...process.env }, +}); +const m = new WebAssembly.Module(buffer); +const i = new WebAssembly.Instance(m, { + wasi_snapshot_preview1: wasi.wasiImport, +}); + +console.log(i.exports) +wasi.initialize(i); +i.exports.check(); diff --git a/tests/run-make/reactor-model/main.c b/tests/run-make/reactor-model/main.c new file mode 100644 index 0000000..732837e --- /dev/null +++ b/tests/run-make/reactor-model/main.c @@ -0,0 +1,14 @@ +#include "../check.h" +#include + +#pragma clang diagnostic ignored "-Wunknown-attributes" +__attribute__((export_name("check"))) +void check(void) { + printf("checking\n"); + check_file_exists("/hello.txt"); + check_dir_entry_size("/", 1); +} + +int main(int argc, char *argv[]) { + return 1; +} diff --git a/tests/run-make/reactor-model/mnt/hello.txt b/tests/run-make/reactor-model/mnt/hello.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/run-make/tools.mk b/tests/run-make/tools.mk new file mode 100644 index 0000000..e1312af --- /dev/null +++ b/tests/run-make/tools.mk @@ -0,0 +1,19 @@ +CC = $(WASI_SDK_PATH)/bin/clang +WASI_VFS_CLI = cargo run -p wasi-vfs-cli -- +WASI_RUN = wasmtime +NODE = node + +TARGET = wasm32-unknown-wasi + +OPTFLAGS ?= +CCFLAGS = -target $(TARGET) $(OPTFLAGS) +LDFLAGS = -target $(TARGET) + +RUNMAKE_DIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +LIB_WASI_VFS ?= $(RUNMAKE_DIR)/../../target/wasm32-unknown-unknown/debug/libwasi_vfs.a + +TMPDIR = $(shell mkdir -p .tmp && echo .tmp) + +$(TMPDIR)/%.c.o: %.c $(RUNMAKE_DIR)/check.h + $(CC) -c $(CCFLAGS) $< -o $@ diff --git a/tools/run-make-test.sh b/tools/run-make-test.sh new file mode 100755 index 0000000..5ddd466 --- /dev/null +++ b/tools/run-make-test.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -eu + +TOOLS_DIR="$(cd "$(dirname "$0")" && pwd)" +TESTS_DIR="$(dirname "$TOOLS_DIR")/tests/run-make" +TMPDIR_BASE="$(mktemp -d)" + +for testdir in "$TESTS_DIR"/*; do + if [ -d "$testdir" ]; then + pushd "$testdir" + tmpdir="$TMPDIR_BASE/$(basename "$testdir")" + mkdir -p "$tmpdir" + make check TMPDIR="$tmpdir" + popd + fi +done