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 0000000..bbbea60
Binary files /dev/null and b/docs/overview.png differ
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..19fdbd8
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,5 @@
+/tools
+/*.c.o
+/*.wasm
+fs.c
+fs.o
diff --git a/examples/getline.c b/examples/getline.c
new file mode 100644
index 0000000..d0ff6ae
--- /dev/null
+++ b/examples/getline.c
@@ -0,0 +1,23 @@
+#include
+#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