Merge wasi-common into wasmtime
This commit merges [CraneStation/wasi-common] repo as a subdir of this repo while preserving **all** of git history. There is an initiative to pull `wasi-common` into [CraneStation/wasmtime], and [CraneStation/wasmtime] becoming a monorepo. This came about for several reasons with a common theme of convenience, namely, having a monorepo: 1. cleans up the problem of dependencies (as we have seen first hand with dependabot enabled, it can cause some grief) 2. completely removes the problem of syncing the closely dependent repos (e.g., updating `wasi-common` with say a bugfix generally implies creating a "sync" commit for pulling in the changes into the "parent" repo, in this case, `wasmtime`) 3. mainly for the two reasons above, makes publishing to crates.io easier 4. hopefully streamlines the process of getting the community involved in contributing to `wasi-common` as now everything is one place [CraneStation/wasi-common]: https://github.com/CraneStation/wasi-common [CraneStation/wasmtime]: https://github.com/CraneStation/wasmtime
This commit is contained in:
19
.github/workflows/main.yml
vendored
19
.github/workflows/main.yml
vendored
@@ -13,6 +13,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
- uses: ./.github/actions/install-rust
|
- uses: ./.github/actions/install-rust
|
||||||
- run: cargo fmt --all -- --check
|
- run: cargo fmt --all -- --check
|
||||||
|
|
||||||
@@ -41,6 +43,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
- uses: ./.github/actions/install-rust
|
- uses: ./.github/actions/install-rust
|
||||||
- run: cargo doc --no-deps -p wasmtime
|
- run: cargo doc --no-deps -p wasmtime
|
||||||
- run: cargo doc --no-deps -p wasmtime-api
|
- run: cargo doc --no-deps -p wasmtime-api
|
||||||
@@ -52,6 +56,7 @@ jobs:
|
|||||||
- run: cargo doc --no-deps -p wasmtime-runtime
|
- run: cargo doc --no-deps -p wasmtime-runtime
|
||||||
- run: cargo doc --no-deps -p wasmtime-wasi
|
- run: cargo doc --no-deps -p wasmtime-wasi
|
||||||
- run: cargo doc --no-deps -p wasmtime-wast
|
- run: cargo doc --no-deps -p wasmtime-wast
|
||||||
|
- run: cargo doc --no-deps -p wasi-common
|
||||||
- uses: actions/upload-artifact@v1
|
- uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: doc-api
|
name: doc-api
|
||||||
@@ -90,10 +95,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
|
|
||||||
|
# Install wasm32-wasi target in order to build wasi-common's integration
|
||||||
|
# tests
|
||||||
|
- run: rustup target add wasm32-wasi
|
||||||
|
|
||||||
- run: cargo fetch
|
- run: cargo fetch
|
||||||
|
|
||||||
# Build and test all features except for lightbeam
|
# Build and test all features except for lightbeam
|
||||||
- run: cargo test --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py -- --nocapture
|
- run: cargo test --features wasi-common/wasm_tests --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py -- --nocapture
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
@@ -119,6 +128,8 @@ jobs:
|
|||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
- uses: ./.github/actions/install-rust
|
- uses: ./.github/actions/install-rust
|
||||||
with:
|
with:
|
||||||
toolchain: nightly-2019-08-15
|
toolchain: nightly-2019-08-15
|
||||||
@@ -197,6 +208,10 @@ jobs:
|
|||||||
- uses: ./.github/actions/install-rust
|
- uses: ./.github/actions/install-rust
|
||||||
- uses: ./.github/actions/binary-compatible-builds
|
- uses: ./.github/actions/binary-compatible-builds
|
||||||
|
|
||||||
|
# Install wasm32-wasi target in order to build wasi-common's integration
|
||||||
|
# tests
|
||||||
|
- run: rustup target add wasm32-wasi
|
||||||
|
|
||||||
# Build `wasmtime` and executables
|
# Build `wasmtime` and executables
|
||||||
- run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj
|
- run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -204,7 +219,7 @@ jobs:
|
|||||||
- run: $CENTOS cargo build --release --features wasm-c-api --manifest-path wasmtime-api/Cargo.toml
|
- run: $CENTOS cargo build --release --features wasm-c-api --manifest-path wasmtime-api/Cargo.toml
|
||||||
shell: bash
|
shell: bash
|
||||||
# Test what we just built
|
# Test what we just built
|
||||||
- run: $CENTOS cargo test --release --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py --exclude wasmtime-api
|
- run: $CENTOS cargo test --features wasi-common/wasm_tests --release --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py --exclude wasmtime-api
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
|||||||
[submodule "wasmtime-api/c-examples/wasm-c-api"]
|
[submodule "wasmtime-api/c-examples/wasm-c-api"]
|
||||||
path = wasmtime-api/c-examples/wasm-c-api
|
path = wasmtime-api/c-examples/wasm-c-api
|
||||||
url = https://github.com/WebAssembly/wasm-c-api
|
url = https://github.com/WebAssembly/wasm-c-api
|
||||||
|
[submodule "wasi-common/WASI"]
|
||||||
|
path = wasi-common/WASI
|
||||||
|
url = https://github.com/WebAssembly/WASI
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ wasmtime-obj = { path = "wasmtime-obj" }
|
|||||||
wasmtime-wast = { path = "wasmtime-wast" }
|
wasmtime-wast = { path = "wasmtime-wast" }
|
||||||
wasmtime-wasi = { path = "wasmtime-wasi" }
|
wasmtime-wasi = { path = "wasmtime-wasi" }
|
||||||
wasmtime-wasi-c = { path = "wasmtime-wasi-c", optional = true }
|
wasmtime-wasi-c = { path = "wasmtime-wasi-c", optional = true }
|
||||||
wasi-common = { git = "https://github.com/CraneStation/wasi-common", rev = "2fe3530"}
|
wasi-common = { path = "wasi-common" }
|
||||||
docopt = "1.0.1"
|
docopt = "1.0.1"
|
||||||
serde = { "version" = "1.0.94", features = ["derive"] }
|
serde = { "version" = "1.0.94", features = ["derive"] }
|
||||||
faerie = "0.12.0"
|
faerie = "0.12.0"
|
||||||
@@ -48,6 +48,8 @@ members = [
|
|||||||
"misc/wasmtime-py",
|
"misc/wasmtime-py",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
exclude = ["wasi-common/wasi-misc-tests"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enable all supported architectures by default.
|
# Enable all supported architectures by default.
|
||||||
default = ["cranelift-codegen/all-arch"]
|
default = ["cranelift-codegen/all-arch"]
|
||||||
|
|||||||
55
wasi-common/Cargo.toml
Normal file
55
wasi-common/Cargo.toml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasi-common"
|
||||||
|
version = "0.5.0"
|
||||||
|
authors = ["The Wasmtime Project Developers"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0 WITH LLVM-exception"
|
||||||
|
description = "WASI implementation in Rust"
|
||||||
|
categories = ["wasm"]
|
||||||
|
keywords = ["webassembly", "wasm"]
|
||||||
|
repository = "https://github.com/CraneStation/wasmtime"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# this feature requires wasm32-wasi target installed, and it enables wasm32
|
||||||
|
# integration tests when run with `cargo test --features wasm_tests`
|
||||||
|
wasm_tests = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasi-common-cbindgen = { path = "wasi-common-cbindgen", version = "0.5.0" }
|
||||||
|
failure = "0.1"
|
||||||
|
libc = "0.2"
|
||||||
|
rand = "0.7"
|
||||||
|
cfg-if = "0.1.9"
|
||||||
|
log = "0.4"
|
||||||
|
filetime = "0.2.7"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
num = { version = "0.2.0", default-features = false }
|
||||||
|
wig = { path = "wig", version = "0.1.0" }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
nix = "0.15"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winx = { path = "winx", version = "0.5.0" }
|
||||||
|
winapi = "0.3"
|
||||||
|
cpu-time = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasmtime-runtime = { path = "../wasmtime-runtime" }
|
||||||
|
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||||
|
wasmtime-jit = { path = "../wasmtime-jit" }
|
||||||
|
wasmtime-wasi = { path = "../wasmtime-wasi" }
|
||||||
|
wasmtime-api = { path = "../wasmtime-api" }
|
||||||
|
cranelift-codegen = "0.49"
|
||||||
|
target-lexicon = "0.8.1"
|
||||||
|
pretty_env_logger = "0.3.0"
|
||||||
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cfg-if = "0.1.9"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "wasi_common"
|
||||||
|
crate-type = ["rlib", "staticlib", "cdylib"]
|
||||||
|
|
||||||
220
wasi-common/LICENSE
Normal file
220
wasi-common/LICENSE
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||||
|
|
||||||
|
As an exception, if, as a result of your compiling your source code, portions
|
||||||
|
of this Software are embedded into an Object form of such source code, you
|
||||||
|
may redistribute such embedded portions in such Object form without complying
|
||||||
|
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||||
|
|
||||||
|
In addition, if you combine or link compiled forms of this Software with
|
||||||
|
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||||
|
court of competent jurisdiction determines that the patent provision (Section
|
||||||
|
3), the indemnity provision (Section 9) or other Section of the License
|
||||||
|
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||||
|
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||||
|
the License, but only in their entirety and only with respect to the Combined
|
||||||
|
Software.
|
||||||
|
|
||||||
24
wasi-common/LICENSE.cloudabi-utils
Normal file
24
wasi-common/LICENSE.cloudabi-utils
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
All code is distributed under the following license:
|
||||||
|
|
||||||
|
Copyright (c) 2015 Nuxi, https://nuxi.nl/
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
||||||
63
wasi-common/README.md
Normal file
63
wasi-common/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# wasi-common
|
||||||
|
|
||||||
|
The `wasi-common` crate will ultimately serve as a library providing a common implementation of
|
||||||
|
WASI hostcalls for re-use in any WASI (and potentially non-WASI) runtimes
|
||||||
|
such as [Wasmtime] and [Lucet].
|
||||||
|
|
||||||
|
The library is an adaption of [lucet-wasi] crate from the [Lucet] project, and it is
|
||||||
|
currently based on [40ae1df][lucet-wasi-tracker] git revision.
|
||||||
|
|
||||||
|
Please note that the library requires Rust compiler version at least 1.37.0.
|
||||||
|
|
||||||
|
[Wasmtime]: https://github.com/CraneStation/wasmtime
|
||||||
|
[Lucet]: https://github.com/fastly/lucet
|
||||||
|
[lucet-wasi]: https://github.com/fastly/lucet/tree/master/lucet-wasi
|
||||||
|
[lucet-wasi-tracker]: https://github.com/fastly/lucet/commit/40ae1df64536250a2b6ab67e7f167d22f4aa7f94
|
||||||
|
|
||||||
|
## Supported syscalls
|
||||||
|
|
||||||
|
### *nix
|
||||||
|
In our *nix implementation, we currently support the entire [WASI API]
|
||||||
|
with the exception of socket hostcalls:
|
||||||
|
- `sock_recv`
|
||||||
|
- `sock_send`
|
||||||
|
- `sock_shutdown`
|
||||||
|
|
||||||
|
We expect these to be implemented when network access is standardised.
|
||||||
|
|
||||||
|
We also currently do not support the `proc_raise` hostcall, as it is expected to
|
||||||
|
be dropped entirely from WASI.
|
||||||
|
|
||||||
|
[WASI API]: https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-api.md
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
In our Windows implementation, we currently support the minimal subset of [WASI API]
|
||||||
|
which allows for running the very basic "Hello world!" style WASM apps. More coming shortly,
|
||||||
|
so stay tuned!
|
||||||
|
|
||||||
|
## Development hints
|
||||||
|
When testing the crate, you may want to enable and run full wasm32 integration testsuite. This
|
||||||
|
requires `wasm32-wasi` target installed which can be done as follows using [rustup]
|
||||||
|
|
||||||
|
```
|
||||||
|
rustup target add wasm32-wasi
|
||||||
|
```
|
||||||
|
|
||||||
|
[rustup]: https://rustup.rs
|
||||||
|
|
||||||
|
Next initiate submodules containing the integration testsuite
|
||||||
|
|
||||||
|
```
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you should be able to run the integration testsuite by enabling the `wasm_tests` feature
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo test --features wasm_tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Third-Party Code
|
||||||
|
Significant parts of our hostcall implementations are derived from the C implementations in
|
||||||
|
`cloudabi-utils`. See [LICENSE.cloudabi-utils](LICENSE.cloudabi-utils) for license information.
|
||||||
|
|
||||||
1
wasi-common/WASI
Submodule
1
wasi-common/WASI
Submodule
Submodule wasi-common/WASI added at 7a5f477fe4
219
wasi-common/build.rs
Normal file
219
wasi-common/build.rs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
//! Build program to generate a program which runs all the testsuites.
|
||||||
|
//!
|
||||||
|
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
||||||
|
//! to automatically run the files in parallel.
|
||||||
|
//!
|
||||||
|
//! Idea adapted from: https://github.com/CraneStation/wasmtime/blob/master/build.rs
|
||||||
|
//! Thanks @sunfishcode
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(feature = "wasm_tests")]
|
||||||
|
wasm_tests::build_and_generate_tests();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm_tests")]
|
||||||
|
mod wasm_tests {
|
||||||
|
use std::env;
|
||||||
|
use std::fs::{read_dir, DirEntry, File};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
pub(crate) fn build_and_generate_tests() {
|
||||||
|
// Validate if any of test sources are present and if they changed
|
||||||
|
// This should always work since there is no submodule to init anymore
|
||||||
|
let bin_tests = std::fs::read_dir("wasi-misc-tests/src/bin").unwrap();
|
||||||
|
for test in bin_tests {
|
||||||
|
if let Ok(test_file) = test {
|
||||||
|
let test_file_path = test_file
|
||||||
|
.path()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.expect("test file path");
|
||||||
|
println!("cargo:rerun-if-changed={}", test_file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Build tests to OUT_DIR (target/*/build/wasi-common-*/out/wasm32-wasi/release/*.wasm)
|
||||||
|
let out_dir = PathBuf::from(
|
||||||
|
env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
|
||||||
|
);
|
||||||
|
let mut out = File::create(out_dir.join("wasi_misc_tests.rs"))
|
||||||
|
.expect("error generating test source file");
|
||||||
|
build_tests("wasi-misc-tests", &out_dir).expect("building tests");
|
||||||
|
test_directory(&mut out, "wasi-misc-tests", &out_dir).expect("generating tests");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> {
|
||||||
|
let mut cmd = Command::new("cargo");
|
||||||
|
cmd.args(&[
|
||||||
|
"build",
|
||||||
|
"--release",
|
||||||
|
"--target=wasm32-wasi",
|
||||||
|
"--target-dir",
|
||||||
|
out_dir.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.current_dir(testsuite);
|
||||||
|
let output = cmd.output()?;
|
||||||
|
|
||||||
|
let status = output.status;
|
||||||
|
if !status.success() {
|
||||||
|
panic!(
|
||||||
|
"Building tests failed: exit code: {}",
|
||||||
|
status.code().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_directory(out: &mut File, testsuite: &str, out_dir: &Path) -> io::Result<()> {
|
||||||
|
let mut dir_entries: Vec<_> = read_dir(out_dir.join("wasm32-wasi/release"))
|
||||||
|
.expect("reading testsuite directory")
|
||||||
|
.map(|r| r.expect("reading testsuite directory entry"))
|
||||||
|
.filter(|dir_entry| {
|
||||||
|
let p = dir_entry.path();
|
||||||
|
if let Some(ext) = p.extension() {
|
||||||
|
// Only look at wast files.
|
||||||
|
if ext == "wasm" {
|
||||||
|
// Ignore files starting with `.`, which could be editor temporary files
|
||||||
|
if let Some(stem) = p.file_stem() {
|
||||||
|
if let Some(stemstr) = stem.to_str() {
|
||||||
|
if !stemstr.starts_with('.') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
dir_entries.sort_by_key(|dir| dir.path());
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
"mod {} {{",
|
||||||
|
Path::new(testsuite)
|
||||||
|
.file_stem()
|
||||||
|
.expect("testsuite filename should have a stem")
|
||||||
|
.to_str()
|
||||||
|
.expect("testsuite filename should be representable as a string")
|
||||||
|
.replace("-", "_")
|
||||||
|
)?;
|
||||||
|
writeln!(out, " use super::{{runtime, utils, setup_log}};")?;
|
||||||
|
for dir_entry in dir_entries {
|
||||||
|
write_testsuite_tests(out, dir_entry, testsuite)?;
|
||||||
|
}
|
||||||
|
writeln!(out, "}}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_testsuite_tests(
|
||||||
|
out: &mut File,
|
||||||
|
dir_entry: DirEntry,
|
||||||
|
testsuite: &str,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let path = dir_entry.path();
|
||||||
|
let stemstr = path
|
||||||
|
.file_stem()
|
||||||
|
.expect("file_stem")
|
||||||
|
.to_str()
|
||||||
|
.expect("to_str");
|
||||||
|
|
||||||
|
writeln!(out, " #[test]")?;
|
||||||
|
if ignore(testsuite, stemstr) {
|
||||||
|
writeln!(out, " #[ignore]")?;
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" fn {}() -> Result<(), String> {{",
|
||||||
|
avoid_keywords(&stemstr.replace("-", "_"))
|
||||||
|
)?;
|
||||||
|
writeln!(out, " setup_log();")?;
|
||||||
|
write!(out, " let path = std::path::Path::new(\"")?;
|
||||||
|
// Write out the string with escape_debug to prevent special characters such
|
||||||
|
// as backslash from being reinterpreted.
|
||||||
|
for c in path.display().to_string().chars() {
|
||||||
|
write!(out, "{}", c.escape_debug())?;
|
||||||
|
}
|
||||||
|
writeln!(out, "\");")?;
|
||||||
|
writeln!(out, " let data = utils::read_wasm(path)?;")?;
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" let bin_name = utils::extract_exec_name_from_path(path)?;"
|
||||||
|
)?;
|
||||||
|
let workspace = if no_preopens(testsuite, stemstr) {
|
||||||
|
"None"
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" let workspace = utils::prepare_workspace(&bin_name)?;"
|
||||||
|
)?;
|
||||||
|
"Some(workspace.path())"
|
||||||
|
};
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" runtime::instantiate(&data, &bin_name, {})",
|
||||||
|
workspace
|
||||||
|
)?;
|
||||||
|
writeln!(out, " }}")?;
|
||||||
|
writeln!(out)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename tests which have the same name as Rust keywords.
|
||||||
|
fn avoid_keywords(name: &str) -> &str {
|
||||||
|
match name {
|
||||||
|
"if" => "if_",
|
||||||
|
"loop" => "loop_",
|
||||||
|
"type" => "type_",
|
||||||
|
"const" => "const_",
|
||||||
|
"return" => "return_",
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(not(windows))] {
|
||||||
|
/// Ignore tests that aren't supported yet.
|
||||||
|
fn ignore(_testsuite: &str, _name: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/// Ignore tests that aren't supported yet.
|
||||||
|
fn ignore(testsuite: &str, name: &str) -> bool {
|
||||||
|
if testsuite == "wasi-misc-tests" {
|
||||||
|
match name {
|
||||||
|
"readlink_no_buffer" => true,
|
||||||
|
"dangling_symlink" => true,
|
||||||
|
"symlink_loop" => true,
|
||||||
|
"truncation_rights" => true,
|
||||||
|
"path_rename_trailing_slashes" => true,
|
||||||
|
"fd_readdir" => true,
|
||||||
|
"poll_oneoff" => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark tests which do not require preopens
|
||||||
|
fn no_preopens(testsuite: &str, name: &str) -> bool {
|
||||||
|
if testsuite == "wasi-misc-tests" {
|
||||||
|
match name {
|
||||||
|
"big_random_buf" => true,
|
||||||
|
"clock_time_get" => true,
|
||||||
|
"sched_yield" => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
335
wasi-common/src/ctx.rs
Normal file
335
wasi-common/src/ctx.rs
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
use crate::fdentry::FdEntry;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::env;
|
||||||
|
use std::ffi::{CString, OsString};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
enum PendingFdEntry {
|
||||||
|
Thunk(fn() -> Result<FdEntry>),
|
||||||
|
File(File),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PendingFdEntry {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PendingFdEntry::Thunk(f) => write!(
|
||||||
|
fmt,
|
||||||
|
"PendingFdEntry::Thunk({:p})",
|
||||||
|
f as *const fn() -> Result<FdEntry>
|
||||||
|
),
|
||||||
|
PendingFdEntry::File(f) => write!(fmt, "PendingFdEntry::File({:?})", f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
enum PendingCString {
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
OsString(OsString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for PendingCString {
|
||||||
|
fn from(bytes: Vec<u8>) -> Self {
|
||||||
|
Self::Bytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OsString> for PendingCString {
|
||||||
|
fn from(s: OsString) -> Self {
|
||||||
|
Self::OsString(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingCString {
|
||||||
|
fn into_string(self) -> Result<String> {
|
||||||
|
match self {
|
||||||
|
PendingCString::Bytes(v) => String::from_utf8(v).map_err(|_| Error::EILSEQ),
|
||||||
|
PendingCString::OsString(s) => s.into_string().map_err(|_| Error::EILSEQ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `CString` containing valid UTF-8, or fail with `Error::EILSEQ`.
|
||||||
|
fn into_utf8_cstring(self) -> Result<CString> {
|
||||||
|
self.into_string()
|
||||||
|
.and_then(|s| CString::new(s).map_err(|_| Error::EILSEQ))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder allowing customizable construction of `WasiCtx` instances.
|
||||||
|
pub struct WasiCtxBuilder {
|
||||||
|
fds: HashMap<wasi::__wasi_fd_t, PendingFdEntry>,
|
||||||
|
preopens: Vec<(PathBuf, File)>,
|
||||||
|
args: Vec<PendingCString>,
|
||||||
|
env: HashMap<PendingCString, PendingCString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasiCtxBuilder {
|
||||||
|
/// Builder for a new `WasiCtx`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut builder = Self {
|
||||||
|
fds: HashMap::new(),
|
||||||
|
preopens: Vec::new(),
|
||||||
|
args: vec![],
|
||||||
|
env: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.fds.insert(0, PendingFdEntry::Thunk(FdEntry::null));
|
||||||
|
builder.fds.insert(1, PendingFdEntry::Thunk(FdEntry::null));
|
||||||
|
builder.fds.insert(2, PendingFdEntry::Thunk(FdEntry::null));
|
||||||
|
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add arguments to the command-line arguments list.
|
||||||
|
///
|
||||||
|
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
|
||||||
|
/// with `Error::EILSEQ`.
|
||||||
|
pub fn args<S: AsRef<[u8]>>(mut self, args: impl IntoIterator<Item = S>) -> Self {
|
||||||
|
self.args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| arg.as_ref().to_vec().into())
|
||||||
|
.collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an argument to the command-line arguments list.
|
||||||
|
///
|
||||||
|
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail
|
||||||
|
/// with `Error::EILSEQ`.
|
||||||
|
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self {
|
||||||
|
self.args.push(arg.as_ref().to_vec().into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inherit the command-line arguments from the host process.
|
||||||
|
///
|
||||||
|
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
|
||||||
|
/// fail with `Error::EILSEQ`.
|
||||||
|
pub fn inherit_args(mut self) -> Self {
|
||||||
|
self.args = env::args_os().map(PendingCString::OsString).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inherit the stdin, stdout, and stderr streams from the host process.
|
||||||
|
pub fn inherit_stdio(mut self) -> Self {
|
||||||
|
self.fds
|
||||||
|
.insert(0, PendingFdEntry::Thunk(FdEntry::duplicate_stdin));
|
||||||
|
self.fds
|
||||||
|
.insert(1, PendingFdEntry::Thunk(FdEntry::duplicate_stdout));
|
||||||
|
self.fds
|
||||||
|
.insert(2, PendingFdEntry::Thunk(FdEntry::duplicate_stderr));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inherit the environment variables from the host process.
|
||||||
|
///
|
||||||
|
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for
|
||||||
|
/// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail with
|
||||||
|
/// `Error::EILSEQ`.
|
||||||
|
pub fn inherit_env(mut self) -> Self {
|
||||||
|
self.env = std::env::vars_os()
|
||||||
|
.map(|(k, v)| (k.into(), v.into()))
|
||||||
|
.collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an entry to the environment.
|
||||||
|
///
|
||||||
|
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
|
||||||
|
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
|
||||||
|
pub fn env<S: AsRef<[u8]>>(mut self, k: S, v: S) -> Self {
|
||||||
|
self.env
|
||||||
|
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add entries to the environment.
|
||||||
|
///
|
||||||
|
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
|
||||||
|
/// `WasiCtxBuilder::build()` will fail with `Error::EILSEQ`.
|
||||||
|
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
|
||||||
|
mut self,
|
||||||
|
envs: impl IntoIterator<Item = T>,
|
||||||
|
) -> Self {
|
||||||
|
self.env = envs
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| {
|
||||||
|
let (k, v) = t.borrow();
|
||||||
|
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a File to use as stdin
|
||||||
|
pub fn stdin(mut self, file: File) -> Self {
|
||||||
|
self.fds.insert(0, PendingFdEntry::File(file));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a File to use as stdout
|
||||||
|
pub fn stdout(mut self, file: File) -> Self {
|
||||||
|
self.fds.insert(1, PendingFdEntry::File(file));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a File to use as stderr
|
||||||
|
pub fn stderr(mut self, file: File) -> Self {
|
||||||
|
self.fds.insert(2, PendingFdEntry::File(file));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a preopened directory.
|
||||||
|
pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self {
|
||||||
|
self.preopens.push((guest_path.as_ref().to_owned(), dir));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a `WasiCtx`, consuming this `WasiCtxBuilder`.
|
||||||
|
///
|
||||||
|
/// If any of the arguments or environment variables in this builder cannot be converted into
|
||||||
|
/// `CString`s, either due to NUL bytes or Unicode conversions, this returns `Error::EILSEQ`.
|
||||||
|
pub fn build(self) -> Result<WasiCtx> {
|
||||||
|
// Process arguments and environment variables into `CString`s, failing quickly if they
|
||||||
|
// contain any NUL bytes, or if conversion from `OsString` fails.
|
||||||
|
let args = self
|
||||||
|
.args
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| arg.into_utf8_cstring())
|
||||||
|
.collect::<Result<Vec<CString>>>()?;
|
||||||
|
|
||||||
|
let env = self
|
||||||
|
.env
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
k.into_string().and_then(|mut pair| {
|
||||||
|
v.into_string().and_then(|v| {
|
||||||
|
pair.push('=');
|
||||||
|
pair.push_str(v.as_str());
|
||||||
|
// We have valid UTF-8, but the keys and values have not yet been checked
|
||||||
|
// for NULs, so we do a final check here.
|
||||||
|
CString::new(pair).map_err(|_| Error::EILSEQ)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<CString>>>()?;
|
||||||
|
|
||||||
|
let mut fds: HashMap<wasi::__wasi_fd_t, FdEntry> = HashMap::new();
|
||||||
|
// Populate the non-preopen fds.
|
||||||
|
for (fd, pending) in self.fds {
|
||||||
|
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
|
||||||
|
match pending {
|
||||||
|
PendingFdEntry::Thunk(f) => {
|
||||||
|
fds.insert(fd, f()?);
|
||||||
|
}
|
||||||
|
PendingFdEntry::File(f) => {
|
||||||
|
fds.insert(fd, FdEntry::from(f)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Then add the preopen fds. Startup code in the guest starts looking at fd 3 for preopens,
|
||||||
|
// so we start from there. This variable is initially 2, though, because the loop
|
||||||
|
// immediately does the increment and check for overflow.
|
||||||
|
let mut preopen_fd: wasi::__wasi_fd_t = 2;
|
||||||
|
for (guest_path, dir) in self.preopens {
|
||||||
|
// We do the increment at the beginning of the loop body, so that we don't overflow
|
||||||
|
// unnecessarily if we have exactly the maximum number of file descriptors.
|
||||||
|
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
|
||||||
|
|
||||||
|
if !dir.metadata()?.is_dir() {
|
||||||
|
return Err(Error::EBADF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't currently allow setting file descriptors other than 0-2, but this will avoid
|
||||||
|
// collisions if we restore that functionality in the future.
|
||||||
|
while fds.contains_key(&preopen_fd) {
|
||||||
|
preopen_fd = preopen_fd.checked_add(1).ok_or(Error::ENFILE)?;
|
||||||
|
}
|
||||||
|
let mut fe = FdEntry::from(dir)?;
|
||||||
|
fe.preopen_path = Some(guest_path);
|
||||||
|
log::debug!("WasiCtx inserting ({:?}, {:?})", preopen_fd, fe);
|
||||||
|
fds.insert(preopen_fd, fe);
|
||||||
|
log::debug!("WasiCtx fds = {:?}", fds);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(WasiCtx { args, env, fds })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WasiCtx {
|
||||||
|
fds: HashMap<wasi::__wasi_fd_t, FdEntry>,
|
||||||
|
pub(crate) args: Vec<CString>,
|
||||||
|
pub(crate) env: Vec<CString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasiCtx {
|
||||||
|
/// Make a new `WasiCtx` with some default settings.
|
||||||
|
///
|
||||||
|
/// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process.
|
||||||
|
///
|
||||||
|
/// - Environment variables are inherited from the host process.
|
||||||
|
///
|
||||||
|
/// To override these behaviors, use `WasiCtxBuilder`.
|
||||||
|
pub fn new<S: AsRef<[u8]>>(args: impl IntoIterator<Item = S>) -> Result<Self> {
|
||||||
|
WasiCtxBuilder::new()
|
||||||
|
.args(args)
|
||||||
|
.inherit_stdio()
|
||||||
|
.inherit_env()
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if `WasiCtx` contains the specified raw WASI `fd`.
|
||||||
|
pub(crate) unsafe fn contains_fd_entry(&self, fd: wasi::__wasi_fd_t) -> bool {
|
||||||
|
self.fds.contains_key(&fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an immutable `FdEntry` corresponding to the specified raw WASI `fd`.
|
||||||
|
pub(crate) unsafe fn get_fd_entry(&self, fd: wasi::__wasi_fd_t) -> Result<&FdEntry> {
|
||||||
|
self.fds.get(&fd).ok_or(Error::EBADF)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable `FdEntry` corresponding to the specified raw WASI `fd`.
|
||||||
|
pub(crate) unsafe fn get_fd_entry_mut(
|
||||||
|
&mut self,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
) -> Result<&mut FdEntry> {
|
||||||
|
self.fds.get_mut(&fd).ok_or(Error::EBADF)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert the specified `FdEntry` into the `WasiCtx` object.
|
||||||
|
///
|
||||||
|
/// The `FdEntry` will automatically get another free raw WASI `fd` assigned. Note that
|
||||||
|
/// the two subsequent free raw WASI `fd`s do not have to be stored contiguously.
|
||||||
|
pub(crate) fn insert_fd_entry(&mut self, fe: FdEntry) -> Result<wasi::__wasi_fd_t> {
|
||||||
|
// Never insert where stdio handles are expected to be.
|
||||||
|
let mut fd = 3;
|
||||||
|
while self.fds.contains_key(&fd) {
|
||||||
|
if let Some(next_fd) = fd.checked_add(1) {
|
||||||
|
fd = next_fd;
|
||||||
|
} else {
|
||||||
|
return Err(Error::EMFILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.fds.insert(fd, fe);
|
||||||
|
Ok(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert the specified `FdEntry` with the specified raw WASI `fd` key into the `WasiCtx`
|
||||||
|
/// object.
|
||||||
|
pub(crate) fn insert_fd_entry_at(
|
||||||
|
&mut self,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
fe: FdEntry,
|
||||||
|
) -> Option<FdEntry> {
|
||||||
|
self.fds.insert(fd, fe)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove `FdEntry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object.
|
||||||
|
pub(crate) fn remove_fd_entry(&mut self, fd: wasi::__wasi_fd_t) -> Result<FdEntry> {
|
||||||
|
self.fds.remove(&fd).ok_or(Error::EBADF)
|
||||||
|
}
|
||||||
|
}
|
||||||
280
wasi-common/src/error.rs
Normal file
280
wasi-common/src/error.rs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
// Due to https://github.com/rust-lang/rust/issues/64247
|
||||||
|
#![allow(clippy::use_self)]
|
||||||
|
use crate::wasi;
|
||||||
|
use failure::Fail;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::fmt;
|
||||||
|
use std::num::TryFromIntError;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Fail, Eq, PartialEq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum WasiError {
|
||||||
|
ESUCCESS = wasi::__WASI_ESUCCESS,
|
||||||
|
E2BIG = wasi::__WASI_E2BIG,
|
||||||
|
EACCES = wasi::__WASI_EACCES,
|
||||||
|
EADDRINUSE = wasi::__WASI_EADDRINUSE,
|
||||||
|
EADDRNOTAVAIL = wasi::__WASI_EADDRNOTAVAIL,
|
||||||
|
EAFNOSUPPORT = wasi::__WASI_EAFNOSUPPORT,
|
||||||
|
EAGAIN = wasi::__WASI_EAGAIN,
|
||||||
|
EALREADY = wasi::__WASI_EALREADY,
|
||||||
|
EBADF = wasi::__WASI_EBADF,
|
||||||
|
EBADMSG = wasi::__WASI_EBADMSG,
|
||||||
|
EBUSY = wasi::__WASI_EBUSY,
|
||||||
|
ECANCELED = wasi::__WASI_ECANCELED,
|
||||||
|
ECHILD = wasi::__WASI_ECHILD,
|
||||||
|
ECONNABORTED = wasi::__WASI_ECONNABORTED,
|
||||||
|
ECONNREFUSED = wasi::__WASI_ECONNREFUSED,
|
||||||
|
ECONNRESET = wasi::__WASI_ECONNRESET,
|
||||||
|
EDEADLK = wasi::__WASI_EDEADLK,
|
||||||
|
EDESTADDRREQ = wasi::__WASI_EDESTADDRREQ,
|
||||||
|
EDOM = wasi::__WASI_EDOM,
|
||||||
|
EDQUOT = wasi::__WASI_EDQUOT,
|
||||||
|
EEXIST = wasi::__WASI_EEXIST,
|
||||||
|
EFAULT = wasi::__WASI_EFAULT,
|
||||||
|
EFBIG = wasi::__WASI_EFBIG,
|
||||||
|
EHOSTUNREACH = wasi::__WASI_EHOSTUNREACH,
|
||||||
|
EIDRM = wasi::__WASI_EIDRM,
|
||||||
|
EILSEQ = wasi::__WASI_EILSEQ,
|
||||||
|
EINPROGRESS = wasi::__WASI_EINPROGRESS,
|
||||||
|
EINTR = wasi::__WASI_EINTR,
|
||||||
|
EINVAL = wasi::__WASI_EINVAL,
|
||||||
|
EIO = wasi::__WASI_EIO,
|
||||||
|
EISCONN = wasi::__WASI_EISCONN,
|
||||||
|
EISDIR = wasi::__WASI_EISDIR,
|
||||||
|
ELOOP = wasi::__WASI_ELOOP,
|
||||||
|
EMFILE = wasi::__WASI_EMFILE,
|
||||||
|
EMLINK = wasi::__WASI_EMLINK,
|
||||||
|
EMSGSIZE = wasi::__WASI_EMSGSIZE,
|
||||||
|
EMULTIHOP = wasi::__WASI_EMULTIHOP,
|
||||||
|
ENAMETOOLONG = wasi::__WASI_ENAMETOOLONG,
|
||||||
|
ENETDOWN = wasi::__WASI_ENETDOWN,
|
||||||
|
ENETRESET = wasi::__WASI_ENETRESET,
|
||||||
|
ENETUNREACH = wasi::__WASI_ENETUNREACH,
|
||||||
|
ENFILE = wasi::__WASI_ENFILE,
|
||||||
|
ENOBUFS = wasi::__WASI_ENOBUFS,
|
||||||
|
ENODEV = wasi::__WASI_ENODEV,
|
||||||
|
ENOENT = wasi::__WASI_ENOENT,
|
||||||
|
ENOEXEC = wasi::__WASI_ENOEXEC,
|
||||||
|
ENOLCK = wasi::__WASI_ENOLCK,
|
||||||
|
ENOLINK = wasi::__WASI_ENOLINK,
|
||||||
|
ENOMEM = wasi::__WASI_ENOMEM,
|
||||||
|
ENOMSG = wasi::__WASI_ENOMSG,
|
||||||
|
ENOPROTOOPT = wasi::__WASI_ENOPROTOOPT,
|
||||||
|
ENOSPC = wasi::__WASI_ENOSPC,
|
||||||
|
ENOSYS = wasi::__WASI_ENOSYS,
|
||||||
|
ENOTCONN = wasi::__WASI_ENOTCONN,
|
||||||
|
ENOTDIR = wasi::__WASI_ENOTDIR,
|
||||||
|
ENOTEMPTY = wasi::__WASI_ENOTEMPTY,
|
||||||
|
ENOTRECOVERABLE = wasi::__WASI_ENOTRECOVERABLE,
|
||||||
|
ENOTSOCK = wasi::__WASI_ENOTSOCK,
|
||||||
|
ENOTSUP = wasi::__WASI_ENOTSUP,
|
||||||
|
ENOTTY = wasi::__WASI_ENOTTY,
|
||||||
|
ENXIO = wasi::__WASI_ENXIO,
|
||||||
|
EOVERFLOW = wasi::__WASI_EOVERFLOW,
|
||||||
|
EOWNERDEAD = wasi::__WASI_EOWNERDEAD,
|
||||||
|
EPERM = wasi::__WASI_EPERM,
|
||||||
|
EPIPE = wasi::__WASI_EPIPE,
|
||||||
|
EPROTO = wasi::__WASI_EPROTO,
|
||||||
|
EPROTONOSUPPORT = wasi::__WASI_EPROTONOSUPPORT,
|
||||||
|
EPROTOTYPE = wasi::__WASI_EPROTOTYPE,
|
||||||
|
ERANGE = wasi::__WASI_ERANGE,
|
||||||
|
EROFS = wasi::__WASI_EROFS,
|
||||||
|
ESPIPE = wasi::__WASI_ESPIPE,
|
||||||
|
ESRCH = wasi::__WASI_ESRCH,
|
||||||
|
ESTALE = wasi::__WASI_ESTALE,
|
||||||
|
ETIMEDOUT = wasi::__WASI_ETIMEDOUT,
|
||||||
|
ETXTBSY = wasi::__WASI_ETXTBSY,
|
||||||
|
EXDEV = wasi::__WASI_EXDEV,
|
||||||
|
ENOTCAPABLE = wasi::__WASI_ENOTCAPABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasiError {
|
||||||
|
pub fn as_raw_errno(self) -> wasi::__wasi_errno_t {
|
||||||
|
self as wasi::__wasi_errno_t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for WasiError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
_ => write!(f, "{:?}", self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum Error {
|
||||||
|
Wasi(WasiError),
|
||||||
|
Io(std::io::Error),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Nix(nix::Error),
|
||||||
|
#[cfg(windows)]
|
||||||
|
Win(winx::winerror::WinError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WasiError> for Error {
|
||||||
|
fn from(err: WasiError) -> Self {
|
||||||
|
Self::Wasi(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
impl From<nix::Error> for Error {
|
||||||
|
fn from(err: nix::Error) -> Self {
|
||||||
|
Self::Nix(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(err: std::io::Error) -> Self {
|
||||||
|
Self::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TryFromIntError> for Error {
|
||||||
|
fn from(_: TryFromIntError) -> Self {
|
||||||
|
Self::Wasi(WasiError::EOVERFLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Infallible> for Error {
|
||||||
|
fn from(_: Infallible) -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<str::Utf8Error> for Error {
|
||||||
|
fn from(_: str::Utf8Error) -> Self {
|
||||||
|
Self::Wasi(WasiError::EILSEQ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
impl From<winx::winerror::WinError> for Error {
|
||||||
|
fn from(err: winx::winerror::WinError) -> Self {
|
||||||
|
Self::Win(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub(crate) fn as_wasi_errno(&self) -> wasi::__wasi_errno_t {
|
||||||
|
match self {
|
||||||
|
Self::Wasi(no) => no.as_raw_errno(),
|
||||||
|
Self::Io(e) => errno_from_ioerror(e.to_owned()),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Nix(err) => err
|
||||||
|
.as_errno()
|
||||||
|
.map_or_else(
|
||||||
|
|| {
|
||||||
|
log::debug!("Unknown nix errno: {}", err);
|
||||||
|
Self::ENOSYS
|
||||||
|
},
|
||||||
|
crate::sys::host_impl::errno_from_nix,
|
||||||
|
)
|
||||||
|
.as_wasi_errno(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
Self::Win(err) => crate::sys::host_impl::errno_from_win(*err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const ESUCCESS: Self = Error::Wasi(WasiError::ESUCCESS);
|
||||||
|
pub const E2BIG: Self = Error::Wasi(WasiError::E2BIG);
|
||||||
|
pub const EACCES: Self = Error::Wasi(WasiError::EACCES);
|
||||||
|
pub const EADDRINUSE: Self = Error::Wasi(WasiError::EADDRINUSE);
|
||||||
|
pub const EADDRNOTAVAIL: Self = Error::Wasi(WasiError::EADDRNOTAVAIL);
|
||||||
|
pub const EAFNOSUPPORT: Self = Error::Wasi(WasiError::EAFNOSUPPORT);
|
||||||
|
pub const EAGAIN: Self = Error::Wasi(WasiError::EAGAIN);
|
||||||
|
pub const EALREADY: Self = Error::Wasi(WasiError::EALREADY);
|
||||||
|
pub const EBADF: Self = Error::Wasi(WasiError::EBADF);
|
||||||
|
pub const EBADMSG: Self = Error::Wasi(WasiError::EBADMSG);
|
||||||
|
pub const EBUSY: Self = Error::Wasi(WasiError::EBUSY);
|
||||||
|
pub const ECANCELED: Self = Error::Wasi(WasiError::ECANCELED);
|
||||||
|
pub const ECHILD: Self = Error::Wasi(WasiError::ECHILD);
|
||||||
|
pub const ECONNABORTED: Self = Error::Wasi(WasiError::ECONNABORTED);
|
||||||
|
pub const ECONNREFUSED: Self = Error::Wasi(WasiError::ECONNREFUSED);
|
||||||
|
pub const ECONNRESET: Self = Error::Wasi(WasiError::ECONNRESET);
|
||||||
|
pub const EDEADLK: Self = Error::Wasi(WasiError::EDEADLK);
|
||||||
|
pub const EDESTADDRREQ: Self = Error::Wasi(WasiError::EDESTADDRREQ);
|
||||||
|
pub const EDOM: Self = Error::Wasi(WasiError::EDOM);
|
||||||
|
pub const EDQUOT: Self = Error::Wasi(WasiError::EDQUOT);
|
||||||
|
pub const EEXIST: Self = Error::Wasi(WasiError::EEXIST);
|
||||||
|
pub const EFAULT: Self = Error::Wasi(WasiError::EFAULT);
|
||||||
|
pub const EFBIG: Self = Error::Wasi(WasiError::EFBIG);
|
||||||
|
pub const EHOSTUNREACH: Self = Error::Wasi(WasiError::EHOSTUNREACH);
|
||||||
|
pub const EIDRM: Self = Error::Wasi(WasiError::EIDRM);
|
||||||
|
pub const EILSEQ: Self = Error::Wasi(WasiError::EILSEQ);
|
||||||
|
pub const EINPROGRESS: Self = Error::Wasi(WasiError::EINPROGRESS);
|
||||||
|
pub const EINTR: Self = Error::Wasi(WasiError::EINTR);
|
||||||
|
pub const EINVAL: Self = Error::Wasi(WasiError::EINVAL);
|
||||||
|
pub const EIO: Self = Error::Wasi(WasiError::EIO);
|
||||||
|
pub const EISCONN: Self = Error::Wasi(WasiError::EISCONN);
|
||||||
|
pub const EISDIR: Self = Error::Wasi(WasiError::EISDIR);
|
||||||
|
pub const ELOOP: Self = Error::Wasi(WasiError::ELOOP);
|
||||||
|
pub const EMFILE: Self = Error::Wasi(WasiError::EMFILE);
|
||||||
|
pub const EMLINK: Self = Error::Wasi(WasiError::EMLINK);
|
||||||
|
pub const EMSGSIZE: Self = Error::Wasi(WasiError::EMSGSIZE);
|
||||||
|
pub const EMULTIHOP: Self = Error::Wasi(WasiError::EMULTIHOP);
|
||||||
|
pub const ENAMETOOLONG: Self = Error::Wasi(WasiError::ENAMETOOLONG);
|
||||||
|
pub const ENETDOWN: Self = Error::Wasi(WasiError::ENETDOWN);
|
||||||
|
pub const ENETRESET: Self = Error::Wasi(WasiError::ENETRESET);
|
||||||
|
pub const ENETUNREACH: Self = Error::Wasi(WasiError::ENETUNREACH);
|
||||||
|
pub const ENFILE: Self = Error::Wasi(WasiError::ENFILE);
|
||||||
|
pub const ENOBUFS: Self = Error::Wasi(WasiError::ENOBUFS);
|
||||||
|
pub const ENODEV: Self = Error::Wasi(WasiError::ENODEV);
|
||||||
|
pub const ENOENT: Self = Error::Wasi(WasiError::ENOENT);
|
||||||
|
pub const ENOEXEC: Self = Error::Wasi(WasiError::ENOEXEC);
|
||||||
|
pub const ENOLCK: Self = Error::Wasi(WasiError::ENOLCK);
|
||||||
|
pub const ENOLINK: Self = Error::Wasi(WasiError::ENOLINK);
|
||||||
|
pub const ENOMEM: Self = Error::Wasi(WasiError::ENOMEM);
|
||||||
|
pub const ENOMSG: Self = Error::Wasi(WasiError::ENOMSG);
|
||||||
|
pub const ENOPROTOOPT: Self = Error::Wasi(WasiError::ENOPROTOOPT);
|
||||||
|
pub const ENOSPC: Self = Error::Wasi(WasiError::ENOSPC);
|
||||||
|
pub const ENOSYS: Self = Error::Wasi(WasiError::ENOSYS);
|
||||||
|
pub const ENOTCONN: Self = Error::Wasi(WasiError::ENOTCONN);
|
||||||
|
pub const ENOTDIR: Self = Error::Wasi(WasiError::ENOTDIR);
|
||||||
|
pub const ENOTEMPTY: Self = Error::Wasi(WasiError::ENOTEMPTY);
|
||||||
|
pub const ENOTRECOVERABLE: Self = Error::Wasi(WasiError::ENOTRECOVERABLE);
|
||||||
|
pub const ENOTSOCK: Self = Error::Wasi(WasiError::ENOTSOCK);
|
||||||
|
pub const ENOTSUP: Self = Error::Wasi(WasiError::ENOTSUP);
|
||||||
|
pub const ENOTTY: Self = Error::Wasi(WasiError::ENOTTY);
|
||||||
|
pub const ENXIO: Self = Error::Wasi(WasiError::ENXIO);
|
||||||
|
pub const EOVERFLOW: Self = Error::Wasi(WasiError::EOVERFLOW);
|
||||||
|
pub const EOWNERDEAD: Self = Error::Wasi(WasiError::EOWNERDEAD);
|
||||||
|
pub const EPERM: Self = Error::Wasi(WasiError::EPERM);
|
||||||
|
pub const EPIPE: Self = Error::Wasi(WasiError::EPIPE);
|
||||||
|
pub const EPROTO: Self = Error::Wasi(WasiError::EPROTO);
|
||||||
|
pub const EPROTONOSUPPORT: Self = Error::Wasi(WasiError::EPROTONOSUPPORT);
|
||||||
|
pub const EPROTOTYPE: Self = Error::Wasi(WasiError::EPROTOTYPE);
|
||||||
|
pub const ERANGE: Self = Error::Wasi(WasiError::ERANGE);
|
||||||
|
pub const EROFS: Self = Error::Wasi(WasiError::EROFS);
|
||||||
|
pub const ESPIPE: Self = Error::Wasi(WasiError::ESPIPE);
|
||||||
|
pub const ESRCH: Self = Error::Wasi(WasiError::ESRCH);
|
||||||
|
pub const ESTALE: Self = Error::Wasi(WasiError::ESTALE);
|
||||||
|
pub const ETIMEDOUT: Self = Error::Wasi(WasiError::ETIMEDOUT);
|
||||||
|
pub const ETXTBSY: Self = Error::Wasi(WasiError::ETXTBSY);
|
||||||
|
pub const EXDEV: Self = Error::Wasi(WasiError::EXDEV);
|
||||||
|
pub const ENOTCAPABLE: Self = Error::Wasi(WasiError::ENOTCAPABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Io(e) => e.fmt(f),
|
||||||
|
Self::Wasi(e) => e.fmt(f),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Nix(e) => e.fmt(f),
|
||||||
|
#[cfg(windows)]
|
||||||
|
Self::Win(e) => e.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn errno_from_ioerror(e: &std::io::Error) -> wasi::__wasi_errno_t {
|
||||||
|
match e.raw_os_error() {
|
||||||
|
Some(code) => crate::sys::errno_from_host(code),
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
wasi::__WASI_EIO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
186
wasi-common/src/fdentry.rs
Normal file
186
wasi-common/src/fdentry.rs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
use crate::sys::dev_null;
|
||||||
|
use crate::sys::fdentry_impl::{determine_type_and_access_rights, OsFile};
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Descriptor {
|
||||||
|
OsFile(OsFile),
|
||||||
|
Stdin,
|
||||||
|
Stdout,
|
||||||
|
Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Descriptor {
|
||||||
|
pub(crate) fn as_file(&self) -> Result<&OsFile> {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(file) => Ok(file),
|
||||||
|
_ => Err(Error::EBADF),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_file_mut(&mut self) -> Result<&mut OsFile> {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(file) => Ok(file),
|
||||||
|
_ => Err(Error::EBADF),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_file(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn is_stdin(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Stdin => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn is_stdout(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Stdout => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn is_stderr(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Stderr => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
|
||||||
|
/// certain base rights `rights_base` and inheriting rights `rights_inheriting` in order to be
|
||||||
|
/// accessed correctly.
|
||||||
|
///
|
||||||
|
/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or
|
||||||
|
/// stdin handle), and accessing it can only be done via the provided `FdEntry::as_descriptor` and
|
||||||
|
/// `FdEntry::as_descriptor_mut` methods which require a set of base and inheriting rights to be
|
||||||
|
/// specified, verifying whether the stored `Descriptor` object is valid for the rights specified.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct FdEntry {
|
||||||
|
pub(crate) file_type: wasi::__wasi_filetype_t,
|
||||||
|
descriptor: Descriptor,
|
||||||
|
pub(crate) rights_base: wasi::__wasi_rights_t,
|
||||||
|
pub(crate) rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
pub(crate) preopen_path: Option<PathBuf>,
|
||||||
|
// TODO: directories
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FdEntry {
|
||||||
|
pub(crate) fn from(file: fs::File) -> Result<Self> {
|
||||||
|
unsafe { determine_type_and_access_rights(&file) }.map(
|
||||||
|
|(file_type, rights_base, rights_inheriting)| Self {
|
||||||
|
file_type,
|
||||||
|
descriptor: Descriptor::OsFile(OsFile::from(file)),
|
||||||
|
rights_base,
|
||||||
|
rights_inheriting,
|
||||||
|
preopen_path: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn duplicate(file: &fs::File) -> Result<Self> {
|
||||||
|
Self::from(file.try_clone()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn duplicate_stdin() -> Result<Self> {
|
||||||
|
unsafe { determine_type_and_access_rights(&io::stdin()) }.map(
|
||||||
|
|(file_type, rights_base, rights_inheriting)| Self {
|
||||||
|
file_type,
|
||||||
|
descriptor: Descriptor::Stdin,
|
||||||
|
rights_base,
|
||||||
|
rights_inheriting,
|
||||||
|
preopen_path: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn duplicate_stdout() -> Result<Self> {
|
||||||
|
unsafe { determine_type_and_access_rights(&io::stdout()) }.map(
|
||||||
|
|(file_type, rights_base, rights_inheriting)| Self {
|
||||||
|
file_type,
|
||||||
|
descriptor: Descriptor::Stdout,
|
||||||
|
rights_base,
|
||||||
|
rights_inheriting,
|
||||||
|
preopen_path: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn duplicate_stderr() -> Result<Self> {
|
||||||
|
unsafe { determine_type_and_access_rights(&io::stderr()) }.map(
|
||||||
|
|(file_type, rights_base, rights_inheriting)| Self {
|
||||||
|
file_type,
|
||||||
|
descriptor: Descriptor::Stderr,
|
||||||
|
rights_base,
|
||||||
|
rights_inheriting,
|
||||||
|
preopen_path: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn null() -> Result<Self> {
|
||||||
|
Self::from(dev_null()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this `FdEntry` into a host `Descriptor` object provided the specified
|
||||||
|
/// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object.
|
||||||
|
///
|
||||||
|
/// The `FdEntry` can only be converted into a valid `Descriptor` object if
|
||||||
|
/// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting`
|
||||||
|
/// is a subset of rights attached to this `FdEntry`. The check is performed using
|
||||||
|
/// `FdEntry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned.
|
||||||
|
pub(crate) fn as_descriptor(
|
||||||
|
&self,
|
||||||
|
rights_base: wasi::__wasi_rights_t,
|
||||||
|
rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
) -> Result<&Descriptor> {
|
||||||
|
self.validate_rights(rights_base, rights_inheriting)?;
|
||||||
|
Ok(&self.descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this `FdEntry` into a mutable host `Descriptor` object provided the specified
|
||||||
|
/// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object.
|
||||||
|
///
|
||||||
|
/// The `FdEntry` can only be converted into a valid `Descriptor` object if
|
||||||
|
/// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting`
|
||||||
|
/// is a subset of rights attached to this `FdEntry`. The check is performed using
|
||||||
|
/// `FdEntry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned.
|
||||||
|
pub(crate) fn as_descriptor_mut(
|
||||||
|
&mut self,
|
||||||
|
rights_base: wasi::__wasi_rights_t,
|
||||||
|
rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
) -> Result<&mut Descriptor> {
|
||||||
|
self.validate_rights(rights_base, rights_inheriting)?;
|
||||||
|
Ok(&mut self.descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this `FdEntry` object satisfies the specified base rights `rights_base`, and
|
||||||
|
/// inheriting rights `rights_inheriting`; i.e., if rights attached to this `FdEntry` object
|
||||||
|
/// are a superset.
|
||||||
|
///
|
||||||
|
/// Upon unsuccessful check, `Error::ENOTCAPABLE` is returned.
|
||||||
|
fn validate_rights(
|
||||||
|
&self,
|
||||||
|
rights_base: wasi::__wasi_rights_t,
|
||||||
|
rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !self.rights_base & rights_base != 0 || !self.rights_inheriting & rights_inheriting != 0
|
||||||
|
{
|
||||||
|
Err(Error::ENOTCAPABLE)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
wasi-common/src/fs/dir.rs
Normal file
216
wasi-common/src/fs/dir.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
use crate::fs::{error::wasi_errno_to_io_error, File, OpenOptions, ReadDir};
|
||||||
|
use crate::{host, hostcalls, wasi, WasiCtx};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::{io, path::Path};
|
||||||
|
|
||||||
|
/// A reference to an open directory on the filesystem.
|
||||||
|
///
|
||||||
|
/// TODO: Implement `Dir`-using versions of `std::fs`'s free functions:
|
||||||
|
/// `copy`, `create_dir`, `create_dir_all`, `hard_link`, `metadata`,
|
||||||
|
/// `read_link`, `read_to_string`, `remove_dir`, `remove_dir_all`,
|
||||||
|
/// `remove_file`, `rename`, `set_permissions`, `symlink_metadata`, and
|
||||||
|
/// `write`.
|
||||||
|
///
|
||||||
|
/// Unlike `std::fs`, this API has no `canonicalize`, because absolute paths
|
||||||
|
/// don't interoperate well with the capability-oriented security model.
|
||||||
|
pub struct Dir<'ctx> {
|
||||||
|
ctx: &'ctx mut WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> Dir<'ctx> {
|
||||||
|
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
|
||||||
|
pub unsafe fn from_raw_wasi_fd(ctx: &'ctx mut WasiCtx, fd: wasi::__wasi_fd_t) -> Self {
|
||||||
|
Self { ctx, fd }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to open a file in read-only mode.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::open`], but only accesses paths
|
||||||
|
/// relative to and within `self`.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented. Refactor the hostcalls functions to split out the
|
||||||
|
/// encoding/decoding parts from the underlying functionality, so that we can call
|
||||||
|
/// into the underlying functionality directly.
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open
|
||||||
|
pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<File> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let mut fd = 0;
|
||||||
|
|
||||||
|
// TODO: Refactor the hostcalls functions to split out the encoding/decoding
|
||||||
|
// parts from the underlying functionality, so that we can call into the
|
||||||
|
// underlying functionality directly.
|
||||||
|
//
|
||||||
|
// TODO: Set the requested rights to be readonly.
|
||||||
|
//
|
||||||
|
// TODO: Handle paths for non-Unix platforms which don't have `as_bytes()`
|
||||||
|
// on `OsStrExt`.
|
||||||
|
unimplemented!("Dir::open_file");
|
||||||
|
/*
|
||||||
|
wasi_errno_to_io_error(hostcalls::path_open(
|
||||||
|
self.ctx,
|
||||||
|
self.fd,
|
||||||
|
wasi::__WASI_LOOKUP_SYMLINK_FOLLOW,
|
||||||
|
path.as_os_str().as_bytes(),
|
||||||
|
path.as_os_str().len(),
|
||||||
|
0,
|
||||||
|
!0,
|
||||||
|
!0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
))?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
let ctx = self.ctx;
|
||||||
|
Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens a file at `path` with the options specified by `self`.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::open`].
|
||||||
|
///
|
||||||
|
/// Instead of being a method on `OpenOptions`, this is a method on `Dir`,
|
||||||
|
/// and it only accesses functions relative to and within `self`.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::open`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open
|
||||||
|
pub fn open_file_with<P: AsRef<Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
options: &OpenOptions,
|
||||||
|
) -> io::Result<File> {
|
||||||
|
unimplemented!("Dir::open_file_with");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to open a directory.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented. See the comment in `open_file`.
|
||||||
|
pub fn open_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let mut fd = 0;
|
||||||
|
|
||||||
|
// TODO: See the comment in `open_file`.
|
||||||
|
unimplemented!("Dir::open_dir");
|
||||||
|
/*
|
||||||
|
wasi_errno_to_io_error(hostcalls::path_open(
|
||||||
|
self.ctx,
|
||||||
|
self.fd,
|
||||||
|
wasi::__WASI_LOOKUP_SYMLINK_FOLLOW,
|
||||||
|
path.as_os_str().as_bytes(),
|
||||||
|
wasi::__WASI_O_DIRECTORY,
|
||||||
|
!0,
|
||||||
|
!0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
))?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
let ctx = self.ctx;
|
||||||
|
Ok(unsafe { Dir::from_raw_wasi_fd(ctx, fd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens a file in write-only mode.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::create`], but only accesses paths
|
||||||
|
/// relative to and within `self`.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented. See the comment in `open_file`.
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create
|
||||||
|
pub fn create_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<File> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let mut fd = 0;
|
||||||
|
|
||||||
|
// TODO: See the comments in `open_file`.
|
||||||
|
//
|
||||||
|
// TODO: Set the requested rights to be read+write.
|
||||||
|
unimplemented!("Dir::create_file");
|
||||||
|
/*
|
||||||
|
wasi_errno_to_io_error(hostcalls::path_open(
|
||||||
|
self.ctx,
|
||||||
|
self.fd,
|
||||||
|
wasi::__WASI_LOOKUP_SYMLINK_FOLLOW,
|
||||||
|
path.as_os_str().as_bytes(),
|
||||||
|
path.as_os_str().len(),
|
||||||
|
wasi::__WASI_O_CREAT | wasi::__WASI_O_TRUNC,
|
||||||
|
!0,
|
||||||
|
!0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
))?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
let ctx = self.ctx;
|
||||||
|
Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the entries within a directory.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::read_dir`], but reads the directory
|
||||||
|
/// represented by `self`.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented. We may need to wait until we have the ability
|
||||||
|
/// to duplicate file descriptors before we can implement read safely. For
|
||||||
|
/// now, use `into_read` instead.
|
||||||
|
///
|
||||||
|
/// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
|
||||||
|
pub fn read(&mut self) -> io::Result<ReadDir> {
|
||||||
|
unimplemented!("Dir::read")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes self and returns an iterator over the entries within a directory
|
||||||
|
/// in the manner of `read`.
|
||||||
|
pub fn into_read(self) -> ReadDir {
|
||||||
|
unsafe { ReadDir::from_raw_wasi_fd(self.fd) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the entire contents of a file into a bytes vector.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::read`], but only accesses paths
|
||||||
|
/// relative to and within `self`.
|
||||||
|
///
|
||||||
|
/// [`std::fs::read`]: https://doc.rust-lang.org/std/fs/fn.read.html
|
||||||
|
pub fn read_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Vec<u8>> {
|
||||||
|
use io::Read;
|
||||||
|
let mut file = self.open_file(path)?;
|
||||||
|
let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
|
||||||
|
file.read_to_end(&mut bytes)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the entries within a directory.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::read_dir`], but only accesses paths
|
||||||
|
/// relative to and within `self`.
|
||||||
|
///
|
||||||
|
/// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
|
||||||
|
pub fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
|
||||||
|
self.open_dir(path)?.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> Drop for Dir<'ctx> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Note that errors are ignored when closing a file descriptor. The
|
||||||
|
// reason for this is that if an error occurs we don't actually know if
|
||||||
|
// the file descriptor was closed or not, and if we retried (for
|
||||||
|
// something like EINTR), we might close another valid file descriptor
|
||||||
|
// opened after we closed ours.
|
||||||
|
let _ = unsafe { hostcalls::fd_close(self.ctx, self.fd) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates how large a buffer to pre-allocate before reading the entire file.
|
||||||
|
///
|
||||||
|
/// Derived from the function of the same name in libstd.
|
||||||
|
fn initial_buffer_size(file: &File) -> usize {
|
||||||
|
// Allocate one extra byte so the buffer doesn't need to grow before the
|
||||||
|
// final `read` call at the end of the file. Don't worry about `usize`
|
||||||
|
// overflow because reading will fail regardless in that case.
|
||||||
|
file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: impl Debug for Dir
|
||||||
49
wasi-common/src/fs/dir_builder.rs
Normal file
49
wasi-common/src/fs/dir_builder.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use std::{io, path::Path};
|
||||||
|
|
||||||
|
/// A builder used to create directories in various manners.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirBuilder`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirBuilder`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html
|
||||||
|
pub struct DirBuilder {}
|
||||||
|
|
||||||
|
impl DirBuilder {
|
||||||
|
/// Creates a new set of options with default mode/security settings for all platforms and also non-recursive.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirBuilder::new`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirBuilder::new`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.new
|
||||||
|
pub fn new() -> Self {
|
||||||
|
unimplemented!("DirBuilder::new");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates that directories should be created recursively, creating all parent directories.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirBuilder::recursive`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirBuilder::recursive`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.recursive
|
||||||
|
pub fn recursive(&mut self, recursive: bool) -> &mut Self {
|
||||||
|
unimplemented!("DirBuilder::recursive");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the specified directory with the options configured in this builder.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirBuilder::create`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirBuilder::create`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.create
|
||||||
|
pub fn create<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||||
|
unimplemented!("DirBuilder::create");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: functions from DirBuilderExt?
|
||||||
|
|
||||||
|
// TODO: impl Debug for DirBuilder
|
||||||
53
wasi-common/src/fs/dir_entry.rs
Normal file
53
wasi-common/src/fs/dir_entry.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use crate::fs::{FileType, Metadata};
|
||||||
|
use std::{ffi, io};
|
||||||
|
|
||||||
|
/// Entries returned by the ReadDir iterator.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirEntry`].
|
||||||
|
///
|
||||||
|
/// Unlike `std::fs::DirEntry`, this API has no `DirEntry::path`, because
|
||||||
|
/// absolute paths don't interoperate well with the capability-oriented
|
||||||
|
/// security model.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirEntry`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html
|
||||||
|
pub struct DirEntry {}
|
||||||
|
|
||||||
|
impl DirEntry {
|
||||||
|
/// Returns the metadata for the file that this entry points at.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirEntry::metadata`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirEntry::metadata`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.metadata
|
||||||
|
pub fn metadata(&self) -> io::Result<Metadata> {
|
||||||
|
unimplemented!("DirEntry::metadata");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file type for the file that this entry points at.
|
||||||
|
///
|
||||||
|
/// This to [`std::fs::DirEntry::file_type`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirEntry::file_type`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_type
|
||||||
|
pub fn file_type(&self) -> io::Result<FileType> {
|
||||||
|
unimplemented!("DirEntry::file_type");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bare file name of this directory entry without any other leading path component.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::DirEntry::file_name`], though it returns
|
||||||
|
/// `String` rather than `OsString`.
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::DirEntry::file_name`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_name
|
||||||
|
pub fn file_name(&self) -> String {
|
||||||
|
unimplemented!("DirEntry::file_name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: impl Debug for DirEntry
|
||||||
265
wasi-common/src/fs/error.rs
Normal file
265
wasi-common/src/fs/error.rs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
use crate::wasi;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
/// Translate a WASI errno code into an `io::Result<()>`.
|
||||||
|
///
|
||||||
|
/// TODO: Would it be better to have our own version of `io::Error` (and
|
||||||
|
/// `io::Result`), rather than trying to shoehorn WASI errors into the
|
||||||
|
/// libstd version?
|
||||||
|
pub(crate) fn wasi_errno_to_io_error(errno: wasi::__wasi_errno_t) -> io::Result<()> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
let raw_os_error = match errno {
|
||||||
|
wasi::__WASI_ESUCCESS => return Ok(()),
|
||||||
|
wasi::__WASI_EIO => libc::EIO,
|
||||||
|
wasi::__WASI_EPERM => libc::EPERM,
|
||||||
|
wasi::__WASI_EINVAL => libc::EINVAL,
|
||||||
|
wasi::__WASI_EPIPE => libc::EPIPE,
|
||||||
|
wasi::__WASI_ENOTCONN => libc::ENOTCONN,
|
||||||
|
wasi::__WASI_E2BIG => libc::E2BIG,
|
||||||
|
wasi::__WASI_EACCES => libc::EACCES,
|
||||||
|
wasi::__WASI_EADDRINUSE => libc::EADDRINUSE,
|
||||||
|
wasi::__WASI_EADDRNOTAVAIL => libc::EADDRNOTAVAIL,
|
||||||
|
wasi::__WASI_EAFNOSUPPORT => libc::EAFNOSUPPORT,
|
||||||
|
wasi::__WASI_EAGAIN => libc::EAGAIN,
|
||||||
|
wasi::__WASI_EALREADY => libc::EALREADY,
|
||||||
|
wasi::__WASI_EBADF => libc::EBADF,
|
||||||
|
wasi::__WASI_EBADMSG => libc::EBADMSG,
|
||||||
|
wasi::__WASI_EBUSY => libc::EBUSY,
|
||||||
|
wasi::__WASI_ECANCELED => libc::ECANCELED,
|
||||||
|
wasi::__WASI_ECHILD => libc::ECHILD,
|
||||||
|
wasi::__WASI_ECONNABORTED => libc::ECONNABORTED,
|
||||||
|
wasi::__WASI_ECONNREFUSED => libc::ECONNREFUSED,
|
||||||
|
wasi::__WASI_ECONNRESET => libc::ECONNRESET,
|
||||||
|
wasi::__WASI_EDEADLK => libc::EDEADLK,
|
||||||
|
wasi::__WASI_EDESTADDRREQ => libc::EDESTADDRREQ,
|
||||||
|
wasi::__WASI_EDOM => libc::EDOM,
|
||||||
|
wasi::__WASI_EDQUOT => libc::EDQUOT,
|
||||||
|
wasi::__WASI_EEXIST => libc::EEXIST,
|
||||||
|
wasi::__WASI_EFAULT => libc::EFAULT,
|
||||||
|
wasi::__WASI_EFBIG => libc::EFBIG,
|
||||||
|
wasi::__WASI_EHOSTUNREACH => libc::EHOSTUNREACH,
|
||||||
|
wasi::__WASI_EIDRM => libc::EIDRM,
|
||||||
|
wasi::__WASI_EILSEQ => libc::EILSEQ,
|
||||||
|
wasi::__WASI_EINPROGRESS => libc::EINPROGRESS,
|
||||||
|
wasi::__WASI_EINTR => libc::EINTR,
|
||||||
|
wasi::__WASI_EISCONN => libc::EISCONN,
|
||||||
|
wasi::__WASI_EISDIR => libc::EISDIR,
|
||||||
|
wasi::__WASI_ELOOP => libc::ELOOP,
|
||||||
|
wasi::__WASI_EMFILE => libc::EMFILE,
|
||||||
|
wasi::__WASI_EMLINK => libc::EMLINK,
|
||||||
|
wasi::__WASI_EMSGSIZE => libc::EMSGSIZE,
|
||||||
|
wasi::__WASI_EMULTIHOP => libc::EMULTIHOP,
|
||||||
|
wasi::__WASI_ENAMETOOLONG => libc::ENAMETOOLONG,
|
||||||
|
wasi::__WASI_ENETDOWN => libc::ENETDOWN,
|
||||||
|
wasi::__WASI_ENETRESET => libc::ENETRESET,
|
||||||
|
wasi::__WASI_ENETUNREACH => libc::ENETUNREACH,
|
||||||
|
wasi::__WASI_ENFILE => libc::ENFILE,
|
||||||
|
wasi::__WASI_ENOBUFS => libc::ENOBUFS,
|
||||||
|
wasi::__WASI_ENODEV => libc::ENODEV,
|
||||||
|
wasi::__WASI_ENOENT => libc::ENOENT,
|
||||||
|
wasi::__WASI_ENOEXEC => libc::ENOEXEC,
|
||||||
|
wasi::__WASI_ENOLCK => libc::ENOLCK,
|
||||||
|
wasi::__WASI_ENOLINK => libc::ENOLINK,
|
||||||
|
wasi::__WASI_ENOMEM => libc::ENOMEM,
|
||||||
|
wasi::__WASI_ENOMSG => libc::ENOMSG,
|
||||||
|
wasi::__WASI_ENOPROTOOPT => libc::ENOPROTOOPT,
|
||||||
|
wasi::__WASI_ENOSPC => libc::ENOSPC,
|
||||||
|
wasi::__WASI_ENOSYS => libc::ENOSYS,
|
||||||
|
wasi::__WASI_ENOTDIR => libc::ENOTDIR,
|
||||||
|
wasi::__WASI_ENOTEMPTY => libc::ENOTEMPTY,
|
||||||
|
wasi::__WASI_ENOTRECOVERABLE => libc::ENOTRECOVERABLE,
|
||||||
|
wasi::__WASI_ENOTSOCK => libc::ENOTSOCK,
|
||||||
|
wasi::__WASI_ENOTSUP => libc::ENOTSUP,
|
||||||
|
wasi::__WASI_ENOTTY => libc::ENOTTY,
|
||||||
|
wasi::__WASI_ENXIO => libc::ENXIO,
|
||||||
|
wasi::__WASI_EOVERFLOW => libc::EOVERFLOW,
|
||||||
|
wasi::__WASI_EOWNERDEAD => libc::EOWNERDEAD,
|
||||||
|
wasi::__WASI_EPROTO => libc::EPROTO,
|
||||||
|
wasi::__WASI_EPROTONOSUPPORT => libc::EPROTONOSUPPORT,
|
||||||
|
wasi::__WASI_EPROTOTYPE => libc::EPROTOTYPE,
|
||||||
|
wasi::__WASI_ERANGE => libc::ERANGE,
|
||||||
|
wasi::__WASI_EROFS => libc::EROFS,
|
||||||
|
wasi::__WASI_ESPIPE => libc::ESPIPE,
|
||||||
|
wasi::__WASI_ESRCH => libc::ESRCH,
|
||||||
|
wasi::__WASI_ESTALE => libc::ESTALE,
|
||||||
|
wasi::__WASI_ETIMEDOUT => libc::ETIMEDOUT,
|
||||||
|
wasi::__WASI_ETXTBSY => libc::ETXTBSY,
|
||||||
|
wasi::__WASI_EXDEV => libc::EXDEV,
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
wasi::__WASI_ENOTCAPABLE => libc::ENOTCAPABLE,
|
||||||
|
#[cfg(not(target_os = "wasi"))]
|
||||||
|
wasi::__WASI_ENOTCAPABLE => libc::EIO,
|
||||||
|
_ => panic!("unexpected wasi errno value"),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use winapi::shared::winerror::*;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let raw_os_error = match errno {
|
||||||
|
wasi::__WASI_ESUCCESS => return Ok(()),
|
||||||
|
wasi::__WASI_EINVAL => WSAEINVAL,
|
||||||
|
wasi::__WASI_EPIPE => ERROR_BROKEN_PIPE,
|
||||||
|
wasi::__WASI_ENOTCONN => WSAENOTCONN,
|
||||||
|
wasi::__WASI_EPERM | wasi::__WASI_EACCES => ERROR_ACCESS_DENIED,
|
||||||
|
wasi::__WASI_EADDRINUSE => WSAEADDRINUSE,
|
||||||
|
wasi::__WASI_EADDRNOTAVAIL => WSAEADDRNOTAVAIL,
|
||||||
|
wasi::__WASI_EAGAIN => WSAEWOULDBLOCK,
|
||||||
|
wasi::__WASI_ECONNABORTED => WSAECONNABORTED,
|
||||||
|
wasi::__WASI_ECONNREFUSED => WSAECONNREFUSED,
|
||||||
|
wasi::__WASI_ECONNRESET => WSAECONNRESET,
|
||||||
|
wasi::__WASI_EEXIST => ERROR_ALREADY_EXISTS,
|
||||||
|
wasi::__WASI_ENOENT => ERROR_FILE_NOT_FOUND,
|
||||||
|
wasi::__WASI_ETIMEDOUT => WSAETIMEDOUT,
|
||||||
|
wasi::__WASI_EAFNOSUPPORT => WSAEAFNOSUPPORT,
|
||||||
|
wasi::__WASI_EALREADY => WSAEALREADY,
|
||||||
|
wasi::__WASI_EBADF => WSAEBADF,
|
||||||
|
wasi::__WASI_EDESTADDRREQ => WSAEDESTADDRREQ,
|
||||||
|
wasi::__WASI_EDQUOT => WSAEDQUOT,
|
||||||
|
wasi::__WASI_EFAULT => WSAEFAULT,
|
||||||
|
wasi::__WASI_EHOSTUNREACH => WSAEHOSTUNREACH,
|
||||||
|
wasi::__WASI_EINPROGRESS => WSAEINPROGRESS,
|
||||||
|
wasi::__WASI_EINTR => WSAEINTR,
|
||||||
|
wasi::__WASI_EISCONN => WSAEISCONN,
|
||||||
|
wasi::__WASI_ELOOP => WSAELOOP,
|
||||||
|
wasi::__WASI_EMFILE => WSAEMFILE,
|
||||||
|
wasi::__WASI_EMSGSIZE => WSAEMSGSIZE,
|
||||||
|
wasi::__WASI_ENAMETOOLONG => WSAENAMETOOLONG,
|
||||||
|
wasi::__WASI_ENETDOWN => WSAENETDOWN,
|
||||||
|
wasi::__WASI_ENETRESET => WSAENETRESET,
|
||||||
|
wasi::__WASI_ENETUNREACH => WSAENETUNREACH,
|
||||||
|
wasi::__WASI_ENOBUFS => WSAENOBUFS,
|
||||||
|
wasi::__WASI_ENOPROTOOPT => WSAENOPROTOOPT,
|
||||||
|
wasi::__WASI_ENOTEMPTY => WSAENOTEMPTY,
|
||||||
|
wasi::__WASI_ENOTSOCK => WSAENOTSOCK,
|
||||||
|
wasi::__WASI_EPROTONOSUPPORT => WSAEPROTONOSUPPORT,
|
||||||
|
wasi::__WASI_EPROTOTYPE => WSAEPROTOTYPE,
|
||||||
|
wasi::__WASI_ESTALE => WSAESTALE,
|
||||||
|
wasi::__WASI_EIO
|
||||||
|
| wasi::__WASI_EISDIR
|
||||||
|
| wasi::__WASI_E2BIG
|
||||||
|
| wasi::__WASI_EBADMSG
|
||||||
|
| wasi::__WASI_EBUSY
|
||||||
|
| wasi::__WASI_ECANCELED
|
||||||
|
| wasi::__WASI_ECHILD
|
||||||
|
| wasi::__WASI_EDEADLK
|
||||||
|
| wasi::__WASI_EDOM
|
||||||
|
| wasi::__WASI_EFBIG
|
||||||
|
| wasi::__WASI_EIDRM
|
||||||
|
| wasi::__WASI_EILSEQ
|
||||||
|
| wasi::__WASI_EMLINK
|
||||||
|
| wasi::__WASI_EMULTIHOP
|
||||||
|
| wasi::__WASI_ENFILE
|
||||||
|
| wasi::__WASI_ENODEV
|
||||||
|
| wasi::__WASI_ENOEXEC
|
||||||
|
| wasi::__WASI_ENOLCK
|
||||||
|
| wasi::__WASI_ENOLINK
|
||||||
|
| wasi::__WASI_ENOMEM
|
||||||
|
| wasi::__WASI_ENOMSG
|
||||||
|
| wasi::__WASI_ENOSPC
|
||||||
|
| wasi::__WASI_ENOSYS
|
||||||
|
| wasi::__WASI_ENOTDIR
|
||||||
|
| wasi::__WASI_ENOTRECOVERABLE
|
||||||
|
| wasi::__WASI_ENOTSUP
|
||||||
|
| wasi::__WASI_ENOTTY
|
||||||
|
| wasi::__WASI_ENXIO
|
||||||
|
| wasi::__WASI_EOVERFLOW
|
||||||
|
| wasi::__WASI_EOWNERDEAD
|
||||||
|
| wasi::__WASI_EPROTO
|
||||||
|
| wasi::__WASI_ERANGE
|
||||||
|
| wasi::__WASI_EROFS
|
||||||
|
| wasi::__WASI_ESPIPE
|
||||||
|
| wasi::__WASI_ESRCH
|
||||||
|
| wasi::__WASI_ETXTBSY
|
||||||
|
| wasi::__WASI_EXDEV
|
||||||
|
| wasi::__WASI_ENOTCAPABLE => {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, error_str(errno)))
|
||||||
|
}
|
||||||
|
_ => panic!("unrecognized WASI errno value"),
|
||||||
|
} as i32;
|
||||||
|
|
||||||
|
Err(io::Error::from_raw_os_error(raw_os_error))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn error_str(errno: wasi::__wasi_errno_t) -> &'static str {
|
||||||
|
match errno {
|
||||||
|
wasi::__WASI_E2BIG => "Argument list too long",
|
||||||
|
wasi::__WASI_EACCES => "Permission denied",
|
||||||
|
wasi::__WASI_EADDRINUSE => "Address in use",
|
||||||
|
wasi::__WASI_EADDRNOTAVAIL => "Address not available",
|
||||||
|
wasi::__WASI_EAFNOSUPPORT => "Address family not supported by protocol",
|
||||||
|
wasi::__WASI_EAGAIN => "Resource temporarily unavailable",
|
||||||
|
wasi::__WASI_EALREADY => "Operation already in progress",
|
||||||
|
wasi::__WASI_EBADF => "Bad file descriptor",
|
||||||
|
wasi::__WASI_EBADMSG => "Bad message",
|
||||||
|
wasi::__WASI_EBUSY => "Resource busy",
|
||||||
|
wasi::__WASI_ECANCELED => "Operation canceled",
|
||||||
|
wasi::__WASI_ECHILD => "No child process",
|
||||||
|
wasi::__WASI_ECONNABORTED => "Connection aborted",
|
||||||
|
wasi::__WASI_ECONNREFUSED => "Connection refused",
|
||||||
|
wasi::__WASI_ECONNRESET => "Connection reset by peer",
|
||||||
|
wasi::__WASI_EDEADLK => "Resource deadlock would occur",
|
||||||
|
wasi::__WASI_EDESTADDRREQ => "Destination address required",
|
||||||
|
wasi::__WASI_EDOM => "Domain error",
|
||||||
|
wasi::__WASI_EDQUOT => "Quota exceeded",
|
||||||
|
wasi::__WASI_EEXIST => "File exists",
|
||||||
|
wasi::__WASI_EFAULT => "Bad address",
|
||||||
|
wasi::__WASI_EFBIG => "File too large",
|
||||||
|
wasi::__WASI_EHOSTUNREACH => "Host is unreachable",
|
||||||
|
wasi::__WASI_EIDRM => "Identifier removed",
|
||||||
|
wasi::__WASI_EILSEQ => "Illegal byte sequence",
|
||||||
|
wasi::__WASI_EINPROGRESS => "Operation in progress",
|
||||||
|
wasi::__WASI_EINTR => "Interrupted system call",
|
||||||
|
wasi::__WASI_EINVAL => "Invalid argument",
|
||||||
|
wasi::__WASI_EIO => "Remote I/O error",
|
||||||
|
wasi::__WASI_EISCONN => "Socket is connected",
|
||||||
|
wasi::__WASI_EISDIR => "Is a directory",
|
||||||
|
wasi::__WASI_ELOOP => "Symbolic link loop",
|
||||||
|
wasi::__WASI_EMFILE => "No file descriptors available",
|
||||||
|
wasi::__WASI_EMLINK => "Too many links",
|
||||||
|
wasi::__WASI_EMSGSIZE => "Message too large",
|
||||||
|
wasi::__WASI_EMULTIHOP => "Multihop attempted",
|
||||||
|
wasi::__WASI_ENAMETOOLONG => "Filename too long",
|
||||||
|
wasi::__WASI_ENETDOWN => "Network is down",
|
||||||
|
wasi::__WASI_ENETRESET => "Connection reset by network",
|
||||||
|
wasi::__WASI_ENETUNREACH => "Network unreachable",
|
||||||
|
wasi::__WASI_ENFILE => "Too many open files in system",
|
||||||
|
wasi::__WASI_ENOBUFS => "No buffer space available",
|
||||||
|
wasi::__WASI_ENODEV => "No such device",
|
||||||
|
wasi::__WASI_ENOENT => "No such file or directory",
|
||||||
|
wasi::__WASI_ENOEXEC => "Exec format error",
|
||||||
|
wasi::__WASI_ENOLCK => "No locks available",
|
||||||
|
wasi::__WASI_ENOLINK => "Link has been severed",
|
||||||
|
wasi::__WASI_ENOMEM => "Out of memory",
|
||||||
|
wasi::__WASI_ENOMSG => "No message of desired type",
|
||||||
|
wasi::__WASI_ENOPROTOOPT => "Protocol not available",
|
||||||
|
wasi::__WASI_ENOSPC => "No space left on device",
|
||||||
|
wasi::__WASI_ENOSYS => "Function not implemented",
|
||||||
|
wasi::__WASI_ENOTCONN => "Socket not connected",
|
||||||
|
wasi::__WASI_ENOTDIR => "Not a directory",
|
||||||
|
wasi::__WASI_ENOTEMPTY => "Directory not empty",
|
||||||
|
wasi::__WASI_ENOTRECOVERABLE => "State not recoverable",
|
||||||
|
wasi::__WASI_ENOTSOCK => "Not a socket",
|
||||||
|
wasi::__WASI_ENOTSUP => "Not supported",
|
||||||
|
wasi::__WASI_ENOTTY => "Not a tty",
|
||||||
|
wasi::__WASI_ENXIO => "No such device or address",
|
||||||
|
wasi::__WASI_EOVERFLOW => "Value too large for data type",
|
||||||
|
wasi::__WASI_EOWNERDEAD => "Previous owner died",
|
||||||
|
wasi::__WASI_EPERM => "Operation not permitted",
|
||||||
|
wasi::__WASI_EPIPE => "Broken pipe",
|
||||||
|
wasi::__WASI_EPROTO => "Protocol error",
|
||||||
|
wasi::__WASI_EPROTONOSUPPORT => "Protocol not supported",
|
||||||
|
wasi::__WASI_EPROTOTYPE => "Protocol wrong type for socket",
|
||||||
|
wasi::__WASI_ERANGE => "Result not representable",
|
||||||
|
wasi::__WASI_EROFS => "Read-only file system",
|
||||||
|
wasi::__WASI_ESPIPE => "Invalid seek",
|
||||||
|
wasi::__WASI_ESRCH => "No such process",
|
||||||
|
wasi::__WASI_ESTALE => "Stale file handle",
|
||||||
|
wasi::__WASI_ETIMEDOUT => "Operation timed out",
|
||||||
|
wasi::__WASI_ETXTBSY => "Text file busy",
|
||||||
|
wasi::__WASI_EXDEV => "Cross-device link",
|
||||||
|
wasi::__WASI_ENOTCAPABLE => "Capabilities insufficient",
|
||||||
|
_ => panic!("unrecognized WASI errno value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
107
wasi-common/src/fs/file.rs
Normal file
107
wasi-common/src/fs/file.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use crate::fs::{error::wasi_errno_to_io_error, Metadata};
|
||||||
|
use crate::{host, hostcalls, wasi, WasiCtx};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
/// A reference to an open file on the filesystem.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File`].
|
||||||
|
///
|
||||||
|
/// Note that this `File` has no `open` or `create` methods. To open or create
|
||||||
|
/// a file, you must first obtain a [`Dir`] containing the file, and then call
|
||||||
|
/// [`Dir::open_file`] or [`Dir::create_file`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
|
||||||
|
/// [`Dir`]: struct.Dir.html
|
||||||
|
/// [`Dir::open_file`]: struct.Dir.html#method.open_file
|
||||||
|
/// [`Dir::create_file`]: struct.Dir.html#method.create_file
|
||||||
|
pub struct File<'ctx> {
|
||||||
|
ctx: &'ctx mut WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> File<'ctx> {
|
||||||
|
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::from_raw_fd`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::from_raw_fd`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.from_raw_fd
|
||||||
|
pub unsafe fn from_raw_wasi_fd(ctx: &'ctx mut WasiCtx, fd: wasi::__wasi_fd_t) -> Self {
|
||||||
|
Self { ctx, fd }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to sync all OS-internal metadata to disk.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::sync_all`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::sync_all`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all
|
||||||
|
pub fn sync_all(&self) -> io::Result<()> {
|
||||||
|
wasi_errno_to_io_error(unsafe { hostcalls::fd_sync(self.ctx, self.fd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is similar to `sync_all`, except that it may not synchronize
|
||||||
|
/// file metadata to the filesystem.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::sync_data`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::sync_data`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data
|
||||||
|
pub fn sync_data(&self) -> io::Result<()> {
|
||||||
|
wasi_errno_to_io_error(unsafe { hostcalls::fd_datasync(self.ctx, self.fd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Truncates or extends the underlying file, updating the size of this file
|
||||||
|
/// to become size.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::set_len`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::set_len`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len
|
||||||
|
pub fn set_len(&self, size: u64) -> io::Result<()> {
|
||||||
|
wasi_errno_to_io_error(unsafe { hostcalls::fd_filestat_set_size(self.ctx, self.fd, size) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queries metadata about the underlying file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::File::metadata`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::File::metadata`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata
|
||||||
|
pub fn metadata(&self) -> io::Result<Metadata> {
|
||||||
|
Ok(Metadata {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> Drop for File<'ctx> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Note that errors are ignored when closing a file descriptor. The
|
||||||
|
// reason for this is that if an error occurs we don't actually know if
|
||||||
|
// the file descriptor was closed or not, and if we retried (for
|
||||||
|
// something like EINTR), we might close another valid file descriptor
|
||||||
|
// opened after we closed ours.
|
||||||
|
let _ = unsafe { hostcalls::fd_close(self.ctx, self.fd) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> io::Read for File<'ctx> {
|
||||||
|
/// TODO: Not yet implemented. See the comment in `Dir::open_file`.
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let iov = [host::__wasi_iovec_t {
|
||||||
|
buf: buf.as_mut_ptr() as *mut u8,
|
||||||
|
buf_len: buf.len(),
|
||||||
|
}];
|
||||||
|
let mut nread = 0;
|
||||||
|
|
||||||
|
// TODO: See the comment in `Dir::open_file`.
|
||||||
|
unimplemented!("File::read");
|
||||||
|
/*
|
||||||
|
wasi_errno_to_io_error(unsafe {
|
||||||
|
hostcalls::fd_read(self.ctx, self.fd, &iov, 1, &mut nread)
|
||||||
|
})?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
Ok(nread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: traits to implement: Write, Seek
|
||||||
|
|
||||||
|
// TODO: functions from FileExt?
|
||||||
|
|
||||||
|
// TODO: impl Debug for File
|
||||||
49
wasi-common/src/fs/file_type.rs
Normal file
49
wasi-common/src/fs/file_type.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/// A structure representing a type of file with accessors for each file type.
|
||||||
|
/// It is returned by `Metadata::file_type` method.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::FileType`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct FileType {}
|
||||||
|
|
||||||
|
impl FileType {
|
||||||
|
/// Tests whether this file type represents a directory.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::FileType::is_dir`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::FileType::is_dir`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_dir
|
||||||
|
pub fn is_dir(&self) -> bool {
|
||||||
|
unimplemented!("FileType::is_dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether this file type represents a regular file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::FileType::is_file`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::FileType::is_file`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_file
|
||||||
|
pub fn is_file(&self) -> bool {
|
||||||
|
unimplemented!("FileType::is_file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether this file type represents a symbolic link.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::FileType::is_symlink`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::FileType::is_symlink`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_symlink
|
||||||
|
pub fn is_symlink(&self) -> bool {
|
||||||
|
unimplemented!("FileType::is_symlink");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: functions from FileTypeExt?
|
||||||
|
|
||||||
|
// TODO: impl Debug for FileType
|
||||||
106
wasi-common/src/fs/metadata.rs
Normal file
106
wasi-common/src/fs/metadata.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use crate::fs::{FileType, Permissions};
|
||||||
|
use std::{io, time::SystemTime};
|
||||||
|
|
||||||
|
/// Metadata information about a file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Metadata {}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
/// Returns the file type for this metadata.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::file_type`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::file_type`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.file_type
|
||||||
|
pub fn file_type(&self) -> FileType {
|
||||||
|
unimplemented!("Metadata::file_type");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this metadata is for a directory.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::is_dir`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::is_dir`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.is_dir
|
||||||
|
pub fn is_dir(&self) -> bool {
|
||||||
|
unimplemented!("Metadata::is_dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this metadata is for a regular file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::is_file`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::is_file`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.is_file
|
||||||
|
pub fn is_file(&self) -> bool {
|
||||||
|
unimplemented!("Metadata::is_file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the file, in bytes, this metadata is for.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::len`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::len`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.len
|
||||||
|
pub fn len(&self) -> u64 {
|
||||||
|
unimplemented!("Metadata::len");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the permissions of the file this metadata is for.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::permissions`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::permissions`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.permissions
|
||||||
|
pub fn permissions(&self) -> Permissions {
|
||||||
|
unimplemented!("Metadata::permissions");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the last modification time listed in this metadata.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::modified`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::modified`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.modified
|
||||||
|
pub fn modified(&self) -> io::Result<SystemTime> {
|
||||||
|
unimplemented!("Metadata::modified");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the last access time of this metadata.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::accessed`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::accessed`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed
|
||||||
|
pub fn accessed(&self) -> io::Result<SystemTime> {
|
||||||
|
unimplemented!("Metadata::accessed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the creation time listed in this metadata.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Metadata::created`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Metadata::created`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.created
|
||||||
|
pub fn created(&self) -> io::Result<SystemTime> {
|
||||||
|
unimplemented!("Metadata::created");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Functions from MetadataExt?
|
||||||
|
|
||||||
|
// TODO: impl Debug for Metadata
|
||||||
51
wasi-common/src/fs/mod.rs
Normal file
51
wasi-common/src/fs/mod.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//! A very experimental module modeled providing a high-level and safe
|
||||||
|
//! filesystem interface, modeled after `std::fs`, implemented on top of
|
||||||
|
//! WASI functions.
|
||||||
|
//!
|
||||||
|
//! Most functions in this API are not yet implemented!
|
||||||
|
//!
|
||||||
|
//! This corresponds to [`std::fs`].
|
||||||
|
//!
|
||||||
|
//! Instead of [`std::fs`'s free functions] which operate on paths, this
|
||||||
|
//! crate has methods on [`Dir`] which operate on paths which must be
|
||||||
|
//! relative to and within the directory.
|
||||||
|
//!
|
||||||
|
//! Since all functions which expose raw file descriptors are `unsafe`,
|
||||||
|
//! I/O handles in this API are unforgeable (unsafe code notwithstanding).
|
||||||
|
//! This combined with WASI's lack of absolute paths provides a natural
|
||||||
|
//! capability-oriented interface.
|
||||||
|
//!
|
||||||
|
//! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html
|
||||||
|
//! [`std::fs`'s free functions]: https://doc.rust-lang.org/std/fs/index.html#functions
|
||||||
|
//! [`DIR`]: struct.Dir.html
|
||||||
|
|
||||||
|
// TODO: When more things are implemented, remove these.
|
||||||
|
#![allow(
|
||||||
|
unused_imports,
|
||||||
|
unreachable_code,
|
||||||
|
unused_variables,
|
||||||
|
unused_mut,
|
||||||
|
unused_unsafe,
|
||||||
|
dead_code
|
||||||
|
)]
|
||||||
|
|
||||||
|
mod dir;
|
||||||
|
mod dir_builder;
|
||||||
|
mod dir_entry;
|
||||||
|
mod error;
|
||||||
|
mod file;
|
||||||
|
mod file_type;
|
||||||
|
mod metadata;
|
||||||
|
mod open_options;
|
||||||
|
mod permissions;
|
||||||
|
mod readdir;
|
||||||
|
|
||||||
|
pub use dir::*;
|
||||||
|
pub use dir_builder::*;
|
||||||
|
pub use dir_entry::*;
|
||||||
|
pub use file::*;
|
||||||
|
pub use file_type::*;
|
||||||
|
pub use metadata::*;
|
||||||
|
pub use open_options::*;
|
||||||
|
pub use permissions::*;
|
||||||
|
pub use readdir::*;
|
||||||
99
wasi-common/src/fs/open_options.rs
Normal file
99
wasi-common/src/fs/open_options.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/// Options and flags which can be used to configure how a file is opened.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions`].
|
||||||
|
///
|
||||||
|
/// Note that this `OpenOptions` has no `open` method. To open a file with
|
||||||
|
/// an `OptionOptions`, you must first obtain a [`Dir`] containing the file, and
|
||||||
|
/// then call [`Dir::open_file_with`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
|
||||||
|
/// [`Dir`]: struct.Dir.html
|
||||||
|
/// [`Dir::open_file_with`]: struct.Dir.html#method.open_file_with
|
||||||
|
pub struct OpenOptions {
|
||||||
|
pub(crate) read: bool,
|
||||||
|
pub(crate) write: bool,
|
||||||
|
pub(crate) append: bool,
|
||||||
|
pub(crate) truncate: bool,
|
||||||
|
pub(crate) create: bool,
|
||||||
|
pub(crate) create_new: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenOptions {
|
||||||
|
/// Creates a blank new set of options ready for configuration.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::new`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.new
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
read: false,
|
||||||
|
write: false,
|
||||||
|
append: false,
|
||||||
|
truncate: false,
|
||||||
|
create: false,
|
||||||
|
create_new: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the option for read access.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::read`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::read`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.read
|
||||||
|
pub fn read(&mut self, read: bool) -> &mut Self {
|
||||||
|
self.read = read;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the option for write access.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::write`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::write`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.write
|
||||||
|
pub fn write(&mut self, write: bool) -> &mut Self {
|
||||||
|
self.write = write;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the option for the append mode.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::append`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::append`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.append
|
||||||
|
pub fn append(&mut self, append: bool) -> &mut Self {
|
||||||
|
self.append = append;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the option for truncating a previous file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::truncate`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::truncate`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.truncate
|
||||||
|
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
|
||||||
|
self.truncate = truncate;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the option to create a new file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::create`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::create`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create
|
||||||
|
pub fn create(&mut self, create: bool) -> &mut Self {
|
||||||
|
self.create = create;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the option to always create a new file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::OpenOptions::create_new`].
|
||||||
|
///
|
||||||
|
/// [`std::fs::OpenOptions::create_new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
|
||||||
|
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
|
||||||
|
self.create_new = create_new;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Functions from OpenOptionsExt?
|
||||||
37
wasi-common/src/fs/permissions.rs
Normal file
37
wasi-common/src/fs/permissions.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/// Representation of the various permissions on a file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Permissions`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html
|
||||||
|
#[derive(Eq, PartialEq, Clone)]
|
||||||
|
pub struct Permissions {}
|
||||||
|
|
||||||
|
impl Permissions {
|
||||||
|
/// Returns true if these permissions describe a readonly (unwritable) file.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Permissions::readonly`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Permissions::readonly`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.readonly
|
||||||
|
pub fn readonly(&self) -> bool {
|
||||||
|
unimplemented!("Permissions::readonly");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modifies the readonly flag for this set of permissions.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::Permissions::set_readonly`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::Permissions::set_readonly`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly
|
||||||
|
pub fn set_readonly(&mut self, readonly: bool) {
|
||||||
|
unimplemented!("Permissions::set_readonly");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: functions from PermissionsExt?
|
||||||
|
|
||||||
|
// TODO: impl Debug for Permissions
|
||||||
32
wasi-common/src/fs/readdir.rs
Normal file
32
wasi-common/src/fs/readdir.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use crate::fs::DirEntry;
|
||||||
|
use crate::{hostcalls, wasi};
|
||||||
|
|
||||||
|
/// Iterator over the entries in a directory.
|
||||||
|
///
|
||||||
|
/// This corresponds to [`std::fs::ReadDir`].
|
||||||
|
///
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
///
|
||||||
|
/// [`std::fs::ReadDir`]: https://doc.rust-lang.org/std/fs/struct.ReadDir.html
|
||||||
|
pub struct ReadDir {
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadDir {
|
||||||
|
/// Constructs a new instance of `Self` from the given raw WASI file descriptor.
|
||||||
|
pub unsafe fn from_raw_wasi_fd(fd: wasi::__wasi_fd_t) -> Self {
|
||||||
|
Self { fd }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
impl Iterator for ReadDir {
|
||||||
|
type Item = DirEntry;
|
||||||
|
|
||||||
|
/// TODO: Not yet implemented.
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
unimplemented!("ReadDir::next");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: impl Debug for ReadDir
|
||||||
20
wasi-common/src/helpers.rs
Normal file
20
wasi-common/src/helpers.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use crate::{Error, Result};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::str;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
pub(crate) fn systemtime_to_timestamp(st: SystemTime) -> Result<u64> {
|
||||||
|
st.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|_| Error::EINVAL)? // date earlier than UNIX_EPOCH
|
||||||
|
.as_nanos()
|
||||||
|
.try_into()
|
||||||
|
.map_err(Into::into) // u128 doesn't fit into u64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates not-owned WASI path from byte slice.
|
||||||
|
///
|
||||||
|
/// NB WASI spec requires bytes to be valid UTF-8. Otherwise,
|
||||||
|
/// `__WASI_EILSEQ` error is returned.
|
||||||
|
pub(crate) fn path_from_slice<'a>(s: &'a [u8]) -> Result<&'a str> {
|
||||||
|
str::from_utf8(s).map_err(|_| Error::EILSEQ)
|
||||||
|
}
|
||||||
109
wasi-common/src/host.rs
Normal file
109
wasi-common/src/host.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//! WASI host types. These are types that contain raw pointers and `usize`
|
||||||
|
//! values, and so are platform-specific.
|
||||||
|
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use crate::wasi::*;
|
||||||
|
use std::{io, slice};
|
||||||
|
use wig::witx_host_types;
|
||||||
|
|
||||||
|
witx_host_types!("unstable" "wasi_unstable_preview0");
|
||||||
|
|
||||||
|
pub(crate) unsafe fn ciovec_to_host(ciovec: &__wasi_ciovec_t) -> io::IoSlice {
|
||||||
|
let slice = slice::from_raw_parts(ciovec.buf as *const u8, ciovec.buf_len);
|
||||||
|
io::IoSlice::new(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn iovec_to_host_mut(iovec: &mut __wasi_iovec_t) -> io::IoSliceMut {
|
||||||
|
let slice = slice::from_raw_parts_mut(iovec.buf as *mut u8, iovec.buf_len);
|
||||||
|
io::IoSliceMut::new(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_prestat_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_prestat_t>(),
|
||||||
|
16usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_prestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_prestat_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_prestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).pr_type as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(pr_type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).u as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(u)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_prestat_dir>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_prestat_dir))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_prestat_dir>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_prestat_dir))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_prestat_dir>())).pr_name_len as *const _ as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_dir),
|
||||||
|
"::",
|
||||||
|
stringify!(pr_name_len)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_prestat_u>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_prestat_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_prestat_u>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_prestat_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_prestat_u>())).dir as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_u),
|
||||||
|
"::",
|
||||||
|
stringify!(dir)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
256
wasi-common/src/hostcalls/fs.rs
Normal file
256
wasi-common/src/hostcalls/fs.rs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use crate::ctx::WasiCtx;
|
||||||
|
use crate::{wasi, wasi32};
|
||||||
|
|
||||||
|
hostcalls! {
|
||||||
|
pub unsafe fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasi::__wasi_fd_t,) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_datasync(wasi_ctx: &WasiCtx, fd: wasi::__wasi_fd_t,) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_pread(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
iovs_ptr: wasi32::uintptr_t,
|
||||||
|
iovs_len: wasi32::size_t,
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
nread: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_pwrite(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
iovs_ptr: wasi32::uintptr_t,
|
||||||
|
iovs_len: wasi32::size_t,
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
nwritten: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_read(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
iovs_ptr: wasi32::uintptr_t,
|
||||||
|
iovs_len: wasi32::size_t,
|
||||||
|
nread: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_renumber(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
from: wasi::__wasi_fd_t,
|
||||||
|
to: wasi::__wasi_fd_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_seek(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
offset: wasi::__wasi_filedelta_t,
|
||||||
|
whence: wasi::__wasi_whence_t,
|
||||||
|
newoffset: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_tell(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
newoffset: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_fdstat_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
fdstat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_fdstat_set_flags(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
fdflags: wasi::__wasi_fdflags_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_fdstat_set_rights(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
fs_rights_base: wasi::__wasi_rights_t,
|
||||||
|
fs_rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_sync(wasi_ctx: &WasiCtx, fd: wasi::__wasi_fd_t,) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_write(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
iovs_ptr: wasi32::uintptr_t,
|
||||||
|
iovs_len: wasi32::size_t,
|
||||||
|
nwritten: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_advise(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
len: wasi::__wasi_filesize_t,
|
||||||
|
advice: wasi::__wasi_advice_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_allocate(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
len: wasi::__wasi_filesize_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_create_directory(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_link(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
old_dirfd: wasi::__wasi_fd_t,
|
||||||
|
old_flags: wasi::__wasi_lookupflags_t,
|
||||||
|
old_path_ptr: wasi32::uintptr_t,
|
||||||
|
old_path_len: wasi32::size_t,
|
||||||
|
new_dirfd: wasi::__wasi_fd_t,
|
||||||
|
new_path_ptr: wasi32::uintptr_t,
|
||||||
|
new_path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_open(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
oflags: wasi::__wasi_oflags_t,
|
||||||
|
fs_rights_base: wasi::__wasi_rights_t,
|
||||||
|
fs_rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
fs_flags: wasi::__wasi_fdflags_t,
|
||||||
|
fd_out_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_readdir(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
buf: wasi32::uintptr_t,
|
||||||
|
buf_len: wasi32::size_t,
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
buf_used: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_readlink(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
buf_ptr: wasi32::uintptr_t,
|
||||||
|
buf_len: wasi32::size_t,
|
||||||
|
buf_used: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_rename(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
old_dirfd: wasi::__wasi_fd_t,
|
||||||
|
old_path_ptr: wasi32::uintptr_t,
|
||||||
|
old_path_len: wasi32::size_t,
|
||||||
|
new_dirfd: wasi::__wasi_fd_t,
|
||||||
|
new_path_ptr: wasi32::uintptr_t,
|
||||||
|
new_path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_filestat_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
filestat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_filestat_set_times(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
st_atim: wasi::__wasi_timestamp_t,
|
||||||
|
st_mtim: wasi::__wasi_timestamp_t,
|
||||||
|
fst_flags: wasi::__wasi_fstflags_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_filestat_set_size(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
st_size: wasi::__wasi_filesize_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_filestat_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
filestat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_filestat_set_times(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
st_atim: wasi::__wasi_timestamp_t,
|
||||||
|
st_mtim: wasi::__wasi_timestamp_t,
|
||||||
|
fst_flags: wasi::__wasi_fstflags_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_symlink(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
old_path_ptr: wasi32::uintptr_t,
|
||||||
|
old_path_len: wasi32::size_t,
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
new_path_ptr: wasi32::uintptr_t,
|
||||||
|
new_path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_unlink_file(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn path_remove_directory(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
dirfd: wasi::__wasi_fd_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_prestat_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
prestat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn fd_prestat_dir_name(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
path_ptr: wasi32::uintptr_t,
|
||||||
|
path_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
}
|
||||||
83
wasi-common/src/hostcalls/misc.rs
Normal file
83
wasi-common/src/hostcalls/misc.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use crate::ctx::WasiCtx;
|
||||||
|
use crate::{wasi, wasi32};
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
|
use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
pub unsafe fn proc_exit(rval: wasi::__wasi_exitcode_t) {
|
||||||
|
trace!("proc_exit(rval={:?})", rval);
|
||||||
|
// TODO: Rather than call std::process::exit here, we should trigger a
|
||||||
|
// stack unwind similar to a trap.
|
||||||
|
std::process::exit(rval as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
pub unsafe fn proc_raise(
|
||||||
|
_wasi_ctx: &WasiCtx,
|
||||||
|
_memory: &mut [u8],
|
||||||
|
_sig: wasi::__wasi_signal_t,
|
||||||
|
) -> wasi::__wasi_errno_t {
|
||||||
|
unimplemented!("proc_raise")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostcalls! {
|
||||||
|
pub unsafe fn args_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
argv_ptr: wasi32::uintptr_t,
|
||||||
|
argv_buf: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn args_sizes_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
argc_ptr: wasi32::uintptr_t,
|
||||||
|
argv_buf_size_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn environ_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
environ_ptr: wasi32::uintptr_t,
|
||||||
|
environ_buf: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn environ_sizes_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
environ_count_ptr: wasi32::uintptr_t,
|
||||||
|
environ_size_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn random_get(
|
||||||
|
memory: &mut [u8],
|
||||||
|
buf_ptr: wasi32::uintptr_t,
|
||||||
|
buf_len: wasi32::size_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn clock_res_get(
|
||||||
|
memory: &mut [u8],
|
||||||
|
clock_id: wasi::__wasi_clockid_t,
|
||||||
|
resolution_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn clock_time_get(
|
||||||
|
memory: &mut [u8],
|
||||||
|
clock_id: wasi::__wasi_clockid_t,
|
||||||
|
precision: wasi::__wasi_timestamp_t,
|
||||||
|
time_ptr: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn poll_oneoff(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
input: wasi32::uintptr_t,
|
||||||
|
output: wasi32::uintptr_t,
|
||||||
|
nsubscriptions: wasi32::size_t,
|
||||||
|
nevents: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t;
|
||||||
|
|
||||||
|
pub unsafe fn sched_yield() -> wasi::__wasi_errno_t;
|
||||||
|
}
|
||||||
7
wasi-common/src/hostcalls/mod.rs
Normal file
7
wasi-common/src/hostcalls/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod fs;
|
||||||
|
mod misc;
|
||||||
|
mod sock;
|
||||||
|
|
||||||
|
pub use self::fs::*;
|
||||||
|
pub use self::misc::*;
|
||||||
|
pub use self::sock::*;
|
||||||
43
wasi-common/src/hostcalls/sock.rs
Normal file
43
wasi-common/src/hostcalls/sock.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(unused_unsafe)]
|
||||||
|
#![allow(unused)]
|
||||||
|
use crate::ctx::WasiCtx;
|
||||||
|
use crate::{wasi, wasi32};
|
||||||
|
use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
pub unsafe fn sock_recv(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
sock: wasi::__wasi_fd_t,
|
||||||
|
ri_data: wasi32::uintptr_t,
|
||||||
|
ri_data_len: wasi32::size_t,
|
||||||
|
ri_flags: wasi::__wasi_riflags_t,
|
||||||
|
ro_datalen: wasi32::uintptr_t,
|
||||||
|
ro_flags: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t {
|
||||||
|
unimplemented!("sock_recv")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
pub unsafe fn sock_send(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
sock: wasi::__wasi_fd_t,
|
||||||
|
si_data: wasi32::uintptr_t,
|
||||||
|
si_data_len: wasi32::size_t,
|
||||||
|
si_flags: wasi::__wasi_siflags_t,
|
||||||
|
so_datalen: wasi32::uintptr_t,
|
||||||
|
) -> wasi::__wasi_errno_t {
|
||||||
|
unimplemented!("sock_send")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
pub unsafe fn sock_shutdown(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
sock: wasi::__wasi_fd_t,
|
||||||
|
how: wasi::__wasi_sdflags_t,
|
||||||
|
) -> wasi::__wasi_errno_t {
|
||||||
|
unimplemented!("sock_shutdown")
|
||||||
|
}
|
||||||
1091
wasi-common/src/hostcalls_impl/fs.rs
Normal file
1091
wasi-common/src/hostcalls_impl/fs.rs
Normal file
File diff suppressed because it is too large
Load Diff
213
wasi-common/src/hostcalls_impl/fs_helpers.rs
Normal file
213
wasi-common/src/hostcalls_impl/fs_helpers.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::sys::hostcalls_impl::fs_helpers::*;
|
||||||
|
use crate::{fdentry::FdEntry, wasi, Error, Result};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::{Component, Path};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PathGet {
|
||||||
|
dirfd: File,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathGet {
|
||||||
|
pub(crate) fn dirfd(&self) -> &File {
|
||||||
|
&self.dirfd
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes a path to ensure that the target path is located under the directory provided.
|
||||||
|
///
|
||||||
|
/// This is a workaround for not having Capsicum support in the OS.
|
||||||
|
pub(crate) fn path_get(
|
||||||
|
fe: &FdEntry,
|
||||||
|
rights_base: wasi::__wasi_rights_t,
|
||||||
|
rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
path: &str,
|
||||||
|
needs_final_component: bool,
|
||||||
|
) -> Result<PathGet> {
|
||||||
|
const MAX_SYMLINK_EXPANSIONS: usize = 128;
|
||||||
|
|
||||||
|
if path.contains('\0') {
|
||||||
|
// if contains NUL, return EILSEQ
|
||||||
|
return Err(Error::EILSEQ);
|
||||||
|
}
|
||||||
|
|
||||||
|
if fe.file_type != wasi::__WASI_FILETYPE_DIRECTORY {
|
||||||
|
// if `dirfd` doesn't refer to a directory, return `ENOTDIR`.
|
||||||
|
return Err(Error::ENOTDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dirfd = fe
|
||||||
|
.as_descriptor(rights_base, rights_inheriting)?
|
||||||
|
.as_file()?
|
||||||
|
.try_clone()?;
|
||||||
|
|
||||||
|
// Stack of directory file descriptors. Index 0 always corresponds with the directory provided
|
||||||
|
// to this function. Entering a directory causes a file descriptor to be pushed, while handling
|
||||||
|
// ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply
|
||||||
|
// escaping the base directory.
|
||||||
|
let mut dir_stack = vec![dirfd];
|
||||||
|
|
||||||
|
// Stack of paths left to process. This is initially the `path` argument to this function, but
|
||||||
|
// any symlinks we encounter are processed by pushing them on the stack.
|
||||||
|
let mut path_stack = vec![path.to_owned()];
|
||||||
|
|
||||||
|
// Track the number of symlinks we've expanded, so we can return `ELOOP` after too many.
|
||||||
|
let mut symlink_expansions = 0;
|
||||||
|
|
||||||
|
// TODO: rewrite this using a custom posix path type, with a component iterator that respects
|
||||||
|
// trailing slashes. This version does way too much allocation, and is way too fiddly.
|
||||||
|
loop {
|
||||||
|
match path_stack.pop() {
|
||||||
|
Some(cur_path) => {
|
||||||
|
log::debug!("path_get cur_path = {:?}", cur_path);
|
||||||
|
|
||||||
|
let ends_with_slash = cur_path.ends_with('/');
|
||||||
|
let mut components = Path::new(&cur_path).components();
|
||||||
|
let head = match components.next() {
|
||||||
|
None => return Err(Error::ENOENT),
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
let tail = components.as_path();
|
||||||
|
|
||||||
|
if tail.components().next().is_some() {
|
||||||
|
let mut tail = host_impl::path_from_host(tail.as_os_str())?;
|
||||||
|
if ends_with_slash {
|
||||||
|
tail.push('/');
|
||||||
|
}
|
||||||
|
path_stack.push(tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("path_get path_stack = {:?}", path_stack);
|
||||||
|
|
||||||
|
match head {
|
||||||
|
Component::Prefix(_) | Component::RootDir => {
|
||||||
|
// path is absolute!
|
||||||
|
return Err(Error::ENOTCAPABLE);
|
||||||
|
}
|
||||||
|
Component::CurDir => {
|
||||||
|
// "." so skip
|
||||||
|
}
|
||||||
|
Component::ParentDir => {
|
||||||
|
// ".." so pop a dir
|
||||||
|
let _ = dir_stack.pop().ok_or(Error::ENOTCAPABLE)?;
|
||||||
|
|
||||||
|
// we're not allowed to pop past the original directory
|
||||||
|
if dir_stack.is_empty() {
|
||||||
|
return Err(Error::ENOTCAPABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component::Normal(head) => {
|
||||||
|
let mut head = host_impl::path_from_host(head)?;
|
||||||
|
if ends_with_slash {
|
||||||
|
// preserve trailing slash
|
||||||
|
head.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
|
||||||
|
match openat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) {
|
||||||
|
Ok(new_dir) => {
|
||||||
|
dir_stack.push(new_dir);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
match e.as_wasi_errno() {
|
||||||
|
wasi::__WASI_ELOOP
|
||||||
|
| wasi::__WASI_EMLINK
|
||||||
|
| wasi::__WASI_ENOTDIR =>
|
||||||
|
// Check to see if it was a symlink. Linux indicates
|
||||||
|
// this with ENOTDIR because of the O_DIRECTORY flag.
|
||||||
|
{
|
||||||
|
// attempt symlink expansion
|
||||||
|
let mut link_path = readlinkat(
|
||||||
|
dir_stack.last().ok_or(Error::ENOTCAPABLE)?,
|
||||||
|
&head,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
symlink_expansions += 1;
|
||||||
|
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||||
|
return Err(Error::ELOOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if head.ends_with('/') {
|
||||||
|
link_path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"attempted symlink expansion link_path={:?}",
|
||||||
|
link_path
|
||||||
|
);
|
||||||
|
|
||||||
|
path_stack.push(link_path);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if ends_with_slash
|
||||||
|
|| (dirflags & wasi::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0
|
||||||
|
{
|
||||||
|
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
|
||||||
|
// symlink expansion
|
||||||
|
match readlinkat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) {
|
||||||
|
Ok(mut link_path) => {
|
||||||
|
symlink_expansions += 1;
|
||||||
|
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||||
|
return Err(Error::ELOOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if head.ends_with('/') {
|
||||||
|
link_path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"attempted symlink expansion link_path={:?}",
|
||||||
|
link_path
|
||||||
|
);
|
||||||
|
|
||||||
|
path_stack.push(link_path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.as_wasi_errno() != wasi::__WASI_EINVAL
|
||||||
|
&& e.as_wasi_errno() != wasi::__WASI_ENOENT
|
||||||
|
// this handles the cases when trying to link to
|
||||||
|
// a destination that already exists, and the target
|
||||||
|
// path contains a slash
|
||||||
|
&& e.as_wasi_errno() != wasi::__WASI_ENOTDIR
|
||||||
|
{
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a symlink, so we're done;
|
||||||
|
return Ok(PathGet {
|
||||||
|
dirfd: dir_stack.pop().ok_or(Error::ENOTCAPABLE)?,
|
||||||
|
path: head,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// no further components to process. means we've hit a case like "." or "a/..", or if the
|
||||||
|
// input path has trailing slashes and `needs_final_component` is not set
|
||||||
|
return Ok(PathGet {
|
||||||
|
dirfd: dir_stack.pop().ok_or(Error::ENOTCAPABLE)?,
|
||||||
|
path: String::from("."),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
317
wasi-common/src/hostcalls_impl/misc.rs
Normal file
317
wasi-common/src/hostcalls_impl/misc.rs
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use crate::ctx::WasiCtx;
|
||||||
|
use crate::fdentry::Descriptor;
|
||||||
|
use crate::memory::*;
|
||||||
|
use crate::sys::hostcalls_impl;
|
||||||
|
use crate::{wasi, wasi32, Error, Result};
|
||||||
|
use log::trace;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
pub(crate) fn args_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
argv_ptr: wasi32::uintptr_t,
|
||||||
|
argv_buf: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"args_get(argv_ptr={:#x?}, argv_buf={:#x?})",
|
||||||
|
argv_ptr,
|
||||||
|
argv_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut argv_buf_offset = 0;
|
||||||
|
let mut argv = vec![];
|
||||||
|
|
||||||
|
for arg in &wasi_ctx.args {
|
||||||
|
let arg_bytes = arg.as_bytes_with_nul();
|
||||||
|
let arg_ptr = argv_buf + argv_buf_offset;
|
||||||
|
|
||||||
|
enc_slice_of_u8(memory, arg_bytes, arg_ptr)?;
|
||||||
|
|
||||||
|
argv.push(arg_ptr);
|
||||||
|
|
||||||
|
let len = wasi32::uintptr_t::try_from(arg_bytes.len())?;
|
||||||
|
argv_buf_offset = argv_buf_offset.checked_add(len).ok_or(Error::EOVERFLOW)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
enc_slice_of_wasi32_uintptr(memory, argv.as_slice(), argv_ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn args_sizes_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
argc_ptr: wasi32::uintptr_t,
|
||||||
|
argv_buf_size_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"args_sizes_get(argc_ptr={:#x?}, argv_buf_size_ptr={:#x?})",
|
||||||
|
argc_ptr,
|
||||||
|
argv_buf_size_ptr,
|
||||||
|
);
|
||||||
|
|
||||||
|
let argc = wasi_ctx.args.len();
|
||||||
|
let argv_size = wasi_ctx
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| arg.as_bytes_with_nul().len())
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
trace!(" | *argc_ptr={:?}", argc);
|
||||||
|
|
||||||
|
enc_usize_byref(memory, argc_ptr, argc)?;
|
||||||
|
|
||||||
|
trace!(" | *argv_buf_size_ptr={:?}", argv_size);
|
||||||
|
|
||||||
|
enc_usize_byref(memory, argv_buf_size_ptr, argv_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn environ_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
environ_ptr: wasi32::uintptr_t,
|
||||||
|
environ_buf: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"environ_get(environ_ptr={:#x?}, environ_buf={:#x?})",
|
||||||
|
environ_ptr,
|
||||||
|
environ_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut environ_buf_offset = 0;
|
||||||
|
let mut environ = vec![];
|
||||||
|
|
||||||
|
for pair in &wasi_ctx.env {
|
||||||
|
let env_bytes = pair.as_bytes_with_nul();
|
||||||
|
let env_ptr = environ_buf + environ_buf_offset;
|
||||||
|
|
||||||
|
enc_slice_of_u8(memory, env_bytes, env_ptr)?;
|
||||||
|
|
||||||
|
environ.push(env_ptr);
|
||||||
|
|
||||||
|
let len = wasi32::uintptr_t::try_from(env_bytes.len())?;
|
||||||
|
environ_buf_offset = environ_buf_offset
|
||||||
|
.checked_add(len)
|
||||||
|
.ok_or(Error::EOVERFLOW)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
enc_slice_of_wasi32_uintptr(memory, environ.as_slice(), environ_ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn environ_sizes_get(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
environ_count_ptr: wasi32::uintptr_t,
|
||||||
|
environ_size_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"environ_sizes_get(environ_count_ptr={:#x?}, environ_size_ptr={:#x?})",
|
||||||
|
environ_count_ptr,
|
||||||
|
environ_size_ptr,
|
||||||
|
);
|
||||||
|
|
||||||
|
let environ_count = wasi_ctx.env.len();
|
||||||
|
let environ_size = wasi_ctx
|
||||||
|
.env
|
||||||
|
.iter()
|
||||||
|
.try_fold(0, |acc: u32, pair| {
|
||||||
|
acc.checked_add(pair.as_bytes_with_nul().len() as u32)
|
||||||
|
})
|
||||||
|
.ok_or(Error::EOVERFLOW)?;
|
||||||
|
|
||||||
|
trace!(" | *environ_count_ptr={:?}", environ_count);
|
||||||
|
|
||||||
|
enc_usize_byref(memory, environ_count_ptr, environ_count)?;
|
||||||
|
|
||||||
|
trace!(" | *environ_size_ptr={:?}", environ_size);
|
||||||
|
|
||||||
|
enc_usize_byref(memory, environ_size_ptr, environ_size as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn random_get(
|
||||||
|
memory: &mut [u8],
|
||||||
|
buf_ptr: wasi32::uintptr_t,
|
||||||
|
buf_len: wasi32::size_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
use rand::{thread_rng, RngCore};
|
||||||
|
|
||||||
|
trace!("random_get(buf_ptr={:#x?}, buf_len={:?})", buf_ptr, buf_len);
|
||||||
|
|
||||||
|
let buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?;
|
||||||
|
|
||||||
|
thread_rng().fill_bytes(buf);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clock_res_get(
|
||||||
|
memory: &mut [u8],
|
||||||
|
clock_id: wasi::__wasi_clockid_t,
|
||||||
|
resolution_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"clock_res_get(clock_id={:?}, resolution_ptr={:#x?})",
|
||||||
|
clock_id,
|
||||||
|
resolution_ptr,
|
||||||
|
);
|
||||||
|
|
||||||
|
let resolution = hostcalls_impl::clock_res_get(clock_id)?;
|
||||||
|
|
||||||
|
trace!(" | *resolution_ptr={:?}", resolution);
|
||||||
|
|
||||||
|
enc_timestamp_byref(memory, resolution_ptr, resolution)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clock_time_get(
|
||||||
|
memory: &mut [u8],
|
||||||
|
clock_id: wasi::__wasi_clockid_t,
|
||||||
|
precision: wasi::__wasi_timestamp_t,
|
||||||
|
time_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"clock_time_get(clock_id={:?}, precision={:?}, time_ptr={:#x?})",
|
||||||
|
clock_id,
|
||||||
|
precision,
|
||||||
|
time_ptr,
|
||||||
|
);
|
||||||
|
|
||||||
|
let time = hostcalls_impl::clock_time_get(clock_id)?;
|
||||||
|
|
||||||
|
trace!(" | *time_ptr={:?}", time);
|
||||||
|
|
||||||
|
enc_timestamp_byref(memory, time_ptr, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sched_yield() -> Result<()> {
|
||||||
|
trace!("sched_yield()");
|
||||||
|
|
||||||
|
std::thread::yield_now();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_oneoff(
|
||||||
|
wasi_ctx: &WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
input: wasi32::uintptr_t,
|
||||||
|
output: wasi32::uintptr_t,
|
||||||
|
nsubscriptions: wasi32::size_t,
|
||||||
|
nevents: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"poll_oneoff(input={:#x?}, output={:#x?}, nsubscriptions={}, nevents={:#x?})",
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
nsubscriptions,
|
||||||
|
nevents,
|
||||||
|
);
|
||||||
|
|
||||||
|
if u64::from(nsubscriptions) > wasi::__wasi_filesize_t::max_value() {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
enc_int_byref(memory, nevents, 0)?;
|
||||||
|
|
||||||
|
let subscriptions = dec_subscriptions(memory, input, nsubscriptions)?;
|
||||||
|
let mut events = Vec::new();
|
||||||
|
|
||||||
|
let mut timeout: Option<ClockEventData> = None;
|
||||||
|
let mut fd_events = Vec::new();
|
||||||
|
for subscription in subscriptions {
|
||||||
|
match subscription.r#type {
|
||||||
|
wasi::__WASI_EVENTTYPE_CLOCK => {
|
||||||
|
let clock = unsafe { subscription.u.clock };
|
||||||
|
let delay = wasi_clock_to_relative_ns_delay(clock)?;
|
||||||
|
|
||||||
|
log::debug!("poll_oneoff event.u.clock = {:?}", clock);
|
||||||
|
log::debug!("poll_oneoff delay = {:?}ns", delay);
|
||||||
|
|
||||||
|
let current = ClockEventData {
|
||||||
|
delay,
|
||||||
|
userdata: subscription.userdata,
|
||||||
|
};
|
||||||
|
let timeout = timeout.get_or_insert(current);
|
||||||
|
if current.delay < timeout.delay {
|
||||||
|
*timeout = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r#type
|
||||||
|
if r#type == wasi::__WASI_EVENTTYPE_FD_READ
|
||||||
|
|| r#type == wasi::__WASI_EVENTTYPE_FD_WRITE =>
|
||||||
|
{
|
||||||
|
let wasi_fd = unsafe { subscription.u.fd_readwrite.file_descriptor };
|
||||||
|
let rights = if r#type == wasi::__WASI_EVENTTYPE_FD_READ {
|
||||||
|
wasi::__WASI_RIGHT_FD_READ
|
||||||
|
} else {
|
||||||
|
wasi::__WASI_RIGHT_FD_WRITE
|
||||||
|
};
|
||||||
|
|
||||||
|
match unsafe {
|
||||||
|
wasi_ctx
|
||||||
|
.get_fd_entry(wasi_fd)
|
||||||
|
.and_then(|fe| fe.as_descriptor(rights, 0))
|
||||||
|
} {
|
||||||
|
Ok(descriptor) => fd_events.push(FdEventData {
|
||||||
|
descriptor,
|
||||||
|
r#type: subscription.r#type,
|
||||||
|
userdata: subscription.userdata,
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
let event = wasi::__wasi_event_t {
|
||||||
|
userdata: subscription.userdata,
|
||||||
|
r#type,
|
||||||
|
error: err.as_wasi_errno(),
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: 0,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("poll_oneoff timeout = {:?}", timeout);
|
||||||
|
log::debug!("poll_oneoff fd_events = {:?}", fd_events);
|
||||||
|
|
||||||
|
hostcalls_impl::poll_oneoff(timeout, fd_events, &mut events)?;
|
||||||
|
|
||||||
|
let events_count = u32::try_from(events.len()).map_err(|_| Error::EOVERFLOW)?;
|
||||||
|
|
||||||
|
enc_events(memory, output, nsubscriptions, events)?;
|
||||||
|
|
||||||
|
trace!(" | *nevents={:?}", events_count);
|
||||||
|
|
||||||
|
enc_int_byref(memory, nevents, events_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wasi_clock_to_relative_ns_delay(wasi_clock: wasi::__wasi_subscription_clock_t) -> Result<u128> {
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
if wasi_clock.flags != wasi::__WASI_SUBSCRIPTION_CLOCK_ABSTIME {
|
||||||
|
return Ok(u128::from(wasi_clock.timeout));
|
||||||
|
}
|
||||||
|
let now: u128 = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map_err(|_| Error::ENOTCAPABLE)?
|
||||||
|
.as_nanos();
|
||||||
|
let deadline = u128::from(wasi_clock.timeout);
|
||||||
|
Ok(deadline.saturating_sub(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub(crate) struct ClockEventData {
|
||||||
|
pub(crate) delay: u128, // delay is expressed in nanoseconds
|
||||||
|
pub(crate) userdata: wasi::__wasi_userdata_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct FdEventData<'a> {
|
||||||
|
pub(crate) descriptor: &'a Descriptor,
|
||||||
|
pub(crate) r#type: wasi::__wasi_eventtype_t,
|
||||||
|
pub(crate) userdata: wasi::__wasi_userdata_t,
|
||||||
|
}
|
||||||
7
wasi-common/src/hostcalls_impl/mod.rs
Normal file
7
wasi-common/src/hostcalls_impl/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod fs;
|
||||||
|
mod fs_helpers;
|
||||||
|
mod misc;
|
||||||
|
|
||||||
|
pub(crate) use self::fs::*;
|
||||||
|
pub(crate) use self::fs_helpers::PathGet;
|
||||||
|
pub(crate) use self::misc::*;
|
||||||
42
wasi-common/src/lib.rs
Normal file
42
wasi-common/src/lib.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#![deny(
|
||||||
|
// missing_docs,
|
||||||
|
trivial_numeric_casts,
|
||||||
|
unused_extern_crates,
|
||||||
|
unstable_features
|
||||||
|
)]
|
||||||
|
#![warn(unused_import_braces)]
|
||||||
|
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
|
||||||
|
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
||||||
|
#![cfg_attr(
|
||||||
|
feature = "cargo-clippy",
|
||||||
|
warn(
|
||||||
|
clippy::float_arithmetic,
|
||||||
|
clippy::mut_mut,
|
||||||
|
clippy::nonminimal_bool,
|
||||||
|
clippy::option_map_unwrap_or,
|
||||||
|
clippy::option_map_unwrap_or_else,
|
||||||
|
clippy::unicode_not_nfc,
|
||||||
|
clippy::use_self
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
|
||||||
|
mod ctx;
|
||||||
|
mod error;
|
||||||
|
mod fdentry;
|
||||||
|
mod helpers;
|
||||||
|
mod hostcalls_impl;
|
||||||
|
mod sys;
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
pub mod fs;
|
||||||
|
mod host;
|
||||||
|
pub mod hostcalls;
|
||||||
|
mod memory;
|
||||||
|
pub mod wasi;
|
||||||
|
pub mod wasi32;
|
||||||
|
|
||||||
|
pub use ctx::{WasiCtx, WasiCtxBuilder};
|
||||||
|
pub use sys::preopen_dir;
|
||||||
|
|
||||||
|
pub type Error = error::Error;
|
||||||
|
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||||
13
wasi-common/src/macros.rs
Normal file
13
wasi-common/src/macros.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
macro_rules! hostcalls {
|
||||||
|
($(pub unsafe fn $name:ident($($arg:ident: $ty:ty,)*) -> $ret:ty;)*) => ($(
|
||||||
|
#[wasi_common_cbindgen::wasi_common_cbindgen]
|
||||||
|
pub unsafe fn $name($($arg: $ty,)*) -> $ret {
|
||||||
|
let ret = match crate::hostcalls_impl::$name($($arg,)*) {
|
||||||
|
Ok(()) => crate::wasi::__WASI_ESUCCESS,
|
||||||
|
Err(e) => e.as_wasi_errno(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
)*)
|
||||||
|
}
|
||||||
480
wasi-common/src/memory.rs
Normal file
480
wasi-common/src/memory.rs
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
//! Functions to store and load data to and from wasm linear memory,
|
||||||
|
//! transforming them from and to host data types.
|
||||||
|
//!
|
||||||
|
//! Endianness concerns are completely encapsulated in this file, so
|
||||||
|
//! that users outside this file holding a `wasi::*` value never need
|
||||||
|
//! to consider what endianness it's in. Inside this file,
|
||||||
|
//! wasm linear-memory-ordered values are called "raw" values, and
|
||||||
|
//! are not held for long durations.
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
use crate::{host, wasi, wasi32, Error, Result};
|
||||||
|
use num::PrimInt;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::mem::{align_of, size_of};
|
||||||
|
use std::{ptr, slice};
|
||||||
|
|
||||||
|
fn dec_ptr(memory: &[u8], ptr: wasi32::uintptr_t, len: usize) -> Result<*const u8> {
|
||||||
|
// check for overflow
|
||||||
|
let checked_len = (ptr as usize).checked_add(len).ok_or(Error::EFAULT)?;
|
||||||
|
|
||||||
|
// translate the pointer
|
||||||
|
memory
|
||||||
|
.get(ptr as usize..checked_len)
|
||||||
|
.ok_or(Error::EFAULT)
|
||||||
|
.map(|mem| mem.as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec_ptr_mut(memory: &mut [u8], ptr: wasi32::uintptr_t, len: usize) -> Result<*mut u8> {
|
||||||
|
// check for overflow
|
||||||
|
let checked_len = (ptr as usize).checked_add(len).ok_or(Error::EFAULT)?;
|
||||||
|
|
||||||
|
// translate the pointer
|
||||||
|
memory
|
||||||
|
.get_mut(ptr as usize..checked_len)
|
||||||
|
.ok_or(Error::EFAULT)
|
||||||
|
.map(|mem| mem.as_mut_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec_ptr_to<'memory, T>(memory: &'memory [u8], ptr: wasi32::uintptr_t) -> Result<&'memory T> {
|
||||||
|
// check that the ptr is aligned
|
||||||
|
if ptr as usize % align_of::<T>() != 0 {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_ptr(memory, ptr, size_of::<T>()).map(|p| unsafe { &*(p as *const T) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec_ptr_to_mut<'memory, T>(
|
||||||
|
memory: &'memory mut [u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<&'memory mut T> {
|
||||||
|
// check that the ptr is aligned
|
||||||
|
if ptr as usize % align_of::<T>() != 0 {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_ptr_mut(memory, ptr, size_of::<T>()).map(|p| unsafe { &mut *(p as *mut T) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function does not perform endianness conversions!
|
||||||
|
fn dec_raw_byref<T>(memory: &[u8], ptr: wasi32::uintptr_t) -> Result<T> {
|
||||||
|
dec_ptr_to::<T>(memory, ptr).map(|p| unsafe { ptr::read(p) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function does not perform endianness conversions!
|
||||||
|
fn enc_raw_byref<T>(memory: &mut [u8], ptr: wasi32::uintptr_t, t: T) -> Result<()> {
|
||||||
|
dec_ptr_to_mut::<T>(memory, ptr).map(|p| unsafe { ptr::write(p, t) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dec_int_byref<T>(memory: &[u8], ptr: wasi32::uintptr_t) -> Result<T>
|
||||||
|
where
|
||||||
|
T: PrimInt,
|
||||||
|
{
|
||||||
|
dec_raw_byref::<T>(memory, ptr).map(|i| PrimInt::from_le(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_int_byref<T>(memory: &mut [u8], ptr: wasi32::uintptr_t, t: T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: PrimInt,
|
||||||
|
{
|
||||||
|
enc_raw_byref::<T>(memory, ptr, PrimInt::to_le(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_slice_of<T>(ptr: wasi32::uintptr_t, len: wasi32::size_t) -> Result<(usize, usize)> {
|
||||||
|
// check alignment, and that length doesn't overflow
|
||||||
|
if ptr as usize % align_of::<T>() != 0 {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
let len = dec_usize(len);
|
||||||
|
let len_bytes = if let Some(len) = size_of::<T>().checked_mul(len) {
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
return Err(Error::EOVERFLOW);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((len, len_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec_raw_slice_of<'memory, T>(
|
||||||
|
memory: &'memory [u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
len: wasi32::size_t,
|
||||||
|
) -> Result<&'memory [T]> {
|
||||||
|
let (len, len_bytes) = check_slice_of::<T>(ptr, len)?;
|
||||||
|
let ptr = dec_ptr(memory, ptr, len_bytes)? as *const T;
|
||||||
|
Ok(unsafe { slice::from_raw_parts(ptr, len) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec_raw_slice_of_mut<'memory, T>(
|
||||||
|
memory: &'memory mut [u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
len: wasi32::size_t,
|
||||||
|
) -> Result<&'memory mut [T]> {
|
||||||
|
let (len, len_bytes) = check_slice_of::<T>(ptr, len)?;
|
||||||
|
let ptr = dec_ptr_mut(memory, ptr, len_bytes)? as *mut T;
|
||||||
|
Ok(unsafe { slice::from_raw_parts_mut(ptr, len) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_slice_for_enc<'memory, T>(
|
||||||
|
memory: &'memory mut [u8],
|
||||||
|
slice: &[T],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<&'memory mut [T]> {
|
||||||
|
// check alignment
|
||||||
|
if ptr as usize % align_of::<T>() != 0 {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
// check that length doesn't overflow
|
||||||
|
let len_bytes = if let Some(len) = size_of::<T>().checked_mul(slice.len()) {
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
return Err(Error::EOVERFLOW);
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the pointer into guest memory
|
||||||
|
let ptr = dec_ptr_mut(memory, ptr, len_bytes)? as *mut T;
|
||||||
|
|
||||||
|
Ok(unsafe { slice::from_raw_parts_mut(ptr, slice.len()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dec_slice_of_u8<'memory>(
|
||||||
|
memory: &'memory [u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
len: wasi32::size_t,
|
||||||
|
) -> Result<&'memory [u8]> {
|
||||||
|
dec_raw_slice_of::<u8>(memory, ptr, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dec_slice_of_mut_u8<'memory>(
|
||||||
|
memory: &'memory mut [u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
len: wasi32::size_t,
|
||||||
|
) -> Result<&'memory mut [u8]> {
|
||||||
|
dec_raw_slice_of_mut::<u8>(memory, ptr, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_slice_of_u8(
|
||||||
|
memory: &mut [u8],
|
||||||
|
slice: &[u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
let output = raw_slice_for_enc::<u8>(memory, slice, ptr)?;
|
||||||
|
|
||||||
|
output.copy_from_slice(slice);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_slice_of_wasi32_uintptr(
|
||||||
|
memory: &mut [u8],
|
||||||
|
slice: &[wasi32::uintptr_t],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut output_iter = raw_slice_for_enc::<wasi32::uintptr_t>(memory, slice, ptr)?.into_iter();
|
||||||
|
|
||||||
|
for p in slice {
|
||||||
|
*output_iter.next().unwrap() = PrimInt::to_le(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! dec_enc_scalar {
|
||||||
|
( $ty:ident, $dec_byref:ident, $enc_byref:ident) => {
|
||||||
|
pub(crate) fn $dec_byref(memory: &mut [u8], ptr: wasi32::uintptr_t) -> Result<wasi::$ty> {
|
||||||
|
dec_int_byref::<wasi::$ty>(memory, ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn $enc_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
x: wasi::$ty,
|
||||||
|
) -> Result<()> {
|
||||||
|
enc_int_byref::<wasi::$ty>(memory, ptr, x)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dec_ciovec_slice(
|
||||||
|
memory: &[u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
len: wasi32::size_t,
|
||||||
|
) -> Result<Vec<host::__wasi_ciovec_t>> {
|
||||||
|
let raw_slice = dec_raw_slice_of::<wasi32::__wasi_ciovec_t>(memory, ptr, len)?;
|
||||||
|
|
||||||
|
raw_slice
|
||||||
|
.iter()
|
||||||
|
.map(|raw_iov| {
|
||||||
|
let len = dec_usize(PrimInt::from_le(raw_iov.buf_len));
|
||||||
|
let buf = PrimInt::from_le(raw_iov.buf);
|
||||||
|
Ok(host::__wasi_ciovec_t {
|
||||||
|
buf: dec_ptr(memory, buf, len)? as *const u8,
|
||||||
|
buf_len: len,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dec_iovec_slice(
|
||||||
|
memory: &[u8],
|
||||||
|
ptr: wasi32::uintptr_t,
|
||||||
|
len: wasi32::size_t,
|
||||||
|
) -> Result<Vec<host::__wasi_iovec_t>> {
|
||||||
|
let raw_slice = dec_raw_slice_of::<wasi32::__wasi_iovec_t>(memory, ptr, len)?;
|
||||||
|
|
||||||
|
raw_slice
|
||||||
|
.iter()
|
||||||
|
.map(|raw_iov| {
|
||||||
|
let len = dec_usize(PrimInt::from_le(raw_iov.buf_len));
|
||||||
|
let buf = PrimInt::from_le(raw_iov.buf);
|
||||||
|
Ok(host::__wasi_iovec_t {
|
||||||
|
buf: dec_ptr(memory, buf, len)? as *mut u8,
|
||||||
|
buf_len: len,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_clockid_t, dec_clockid_byref, enc_clockid_byref);
|
||||||
|
dec_enc_scalar!(__wasi_errno_t, dec_errno_byref, enc_errno_byref);
|
||||||
|
dec_enc_scalar!(__wasi_exitcode_t, dec_exitcode_byref, enc_exitcode_byref);
|
||||||
|
dec_enc_scalar!(__wasi_fd_t, dec_fd_byref, enc_fd_byref);
|
||||||
|
dec_enc_scalar!(__wasi_fdflags_t, dec_fdflags_byref, enc_fdflags_byref);
|
||||||
|
dec_enc_scalar!(__wasi_device_t, dev_device_byref, enc_device_byref);
|
||||||
|
dec_enc_scalar!(__wasi_inode_t, dev_inode_byref, enc_inode_byref);
|
||||||
|
dec_enc_scalar!(__wasi_linkcount_t, dev_linkcount_byref, enc_linkcount_byref);
|
||||||
|
|
||||||
|
pub(crate) fn dec_filestat_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
filestat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<wasi::__wasi_filestat_t> {
|
||||||
|
let raw = dec_raw_byref::<wasi::__wasi_filestat_t>(memory, filestat_ptr)?;
|
||||||
|
|
||||||
|
Ok(wasi::__wasi_filestat_t {
|
||||||
|
st_dev: PrimInt::from_le(raw.st_dev),
|
||||||
|
st_ino: PrimInt::from_le(raw.st_ino),
|
||||||
|
st_filetype: PrimInt::from_le(raw.st_filetype),
|
||||||
|
st_nlink: PrimInt::from_le(raw.st_nlink),
|
||||||
|
st_size: PrimInt::from_le(raw.st_size),
|
||||||
|
st_atim: PrimInt::from_le(raw.st_atim),
|
||||||
|
st_mtim: PrimInt::from_le(raw.st_mtim),
|
||||||
|
st_ctim: PrimInt::from_le(raw.st_ctim),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_filestat_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
filestat_ptr: wasi32::uintptr_t,
|
||||||
|
filestat: wasi::__wasi_filestat_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
let raw = wasi::__wasi_filestat_t {
|
||||||
|
st_dev: PrimInt::to_le(filestat.st_dev),
|
||||||
|
st_ino: PrimInt::to_le(filestat.st_ino),
|
||||||
|
st_filetype: PrimInt::to_le(filestat.st_filetype),
|
||||||
|
st_nlink: PrimInt::to_le(filestat.st_nlink),
|
||||||
|
st_size: PrimInt::to_le(filestat.st_size),
|
||||||
|
st_atim: PrimInt::to_le(filestat.st_atim),
|
||||||
|
st_mtim: PrimInt::to_le(filestat.st_mtim),
|
||||||
|
st_ctim: PrimInt::to_le(filestat.st_ctim),
|
||||||
|
};
|
||||||
|
|
||||||
|
enc_raw_byref::<wasi::__wasi_filestat_t>(memory, filestat_ptr, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dec_fdstat_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
fdstat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<wasi::__wasi_fdstat_t> {
|
||||||
|
let raw = dec_raw_byref::<wasi::__wasi_fdstat_t>(memory, fdstat_ptr)?;
|
||||||
|
|
||||||
|
Ok(wasi::__wasi_fdstat_t {
|
||||||
|
fs_filetype: PrimInt::from_le(raw.fs_filetype),
|
||||||
|
fs_flags: PrimInt::from_le(raw.fs_flags),
|
||||||
|
fs_rights_base: PrimInt::from_le(raw.fs_rights_base),
|
||||||
|
fs_rights_inheriting: PrimInt::from_le(raw.fs_rights_inheriting),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_fdstat_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
fdstat_ptr: wasi32::uintptr_t,
|
||||||
|
fdstat: wasi::__wasi_fdstat_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
let raw = wasi::__wasi_fdstat_t {
|
||||||
|
fs_filetype: PrimInt::to_le(fdstat.fs_filetype),
|
||||||
|
fs_flags: PrimInt::to_le(fdstat.fs_flags),
|
||||||
|
fs_rights_base: PrimInt::to_le(fdstat.fs_rights_base),
|
||||||
|
fs_rights_inheriting: PrimInt::to_le(fdstat.fs_rights_inheriting),
|
||||||
|
};
|
||||||
|
|
||||||
|
enc_raw_byref::<wasi::__wasi_fdstat_t>(memory, fdstat_ptr, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_filedelta_t, dec_filedelta_byref, enc_filedelta_byref);
|
||||||
|
dec_enc_scalar!(__wasi_filesize_t, dec_filesize_byref, enc_filesize_byref);
|
||||||
|
dec_enc_scalar!(__wasi_filetype_t, dec_filetype_byref, enc_filetype_byref);
|
||||||
|
|
||||||
|
dec_enc_scalar!(
|
||||||
|
__wasi_lookupflags_t,
|
||||||
|
dec_lookupflags_byref,
|
||||||
|
enc_lookupflags_byref
|
||||||
|
);
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_oflags_t, dec_oflags_byref, enc_oflags_byref);
|
||||||
|
|
||||||
|
pub(crate) fn dec_prestat_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
prestat_ptr: wasi32::uintptr_t,
|
||||||
|
) -> Result<host::__wasi_prestat_t> {
|
||||||
|
let raw = dec_raw_byref::<wasi32::__wasi_prestat_t>(memory, prestat_ptr)?;
|
||||||
|
|
||||||
|
match PrimInt::from_le(raw.pr_type) {
|
||||||
|
wasi::__WASI_PREOPENTYPE_DIR => Ok(host::__wasi_prestat_t {
|
||||||
|
pr_type: wasi::__WASI_PREOPENTYPE_DIR,
|
||||||
|
u: host::__wasi_prestat_u {
|
||||||
|
dir: host::__wasi_prestat_dir {
|
||||||
|
pr_name_len: dec_usize(PrimInt::from_le(unsafe { raw.u.dir.pr_name_len })),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
_ => Err(Error::EINVAL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_prestat_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
prestat_ptr: wasi32::uintptr_t,
|
||||||
|
prestat: host::__wasi_prestat_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
let raw = match prestat.pr_type {
|
||||||
|
wasi::__WASI_PREOPENTYPE_DIR => Ok(wasi32::__wasi_prestat_t {
|
||||||
|
pr_type: PrimInt::to_le(wasi::__WASI_PREOPENTYPE_DIR),
|
||||||
|
u: wasi32::__wasi_prestat_u {
|
||||||
|
dir: wasi32::__wasi_prestat_dir {
|
||||||
|
pr_name_len: enc_usize(unsafe { prestat.u.dir.pr_name_len }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
_ => Err(Error::EINVAL),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
enc_raw_byref::<wasi32::__wasi_prestat_t>(memory, prestat_ptr, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_rights_t, dec_rights_byref, enc_rights_byref);
|
||||||
|
dec_enc_scalar!(__wasi_timestamp_t, dec_timestamp_byref, enc_timestamp_byref);
|
||||||
|
|
||||||
|
pub(crate) fn dec_usize(size: wasi32::size_t) -> usize {
|
||||||
|
usize::try_from(size).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_usize(size: usize) -> wasi32::size_t {
|
||||||
|
wasi32::size_t::try_from(size).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_usize_byref(
|
||||||
|
memory: &mut [u8],
|
||||||
|
usize_ptr: wasi32::uintptr_t,
|
||||||
|
host_usize: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
enc_int_byref::<wasi32::size_t>(memory, usize_ptr, enc_usize(host_usize))
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_whence_t, dec_whence_byref, enc_whence_byref);
|
||||||
|
|
||||||
|
dec_enc_scalar!(
|
||||||
|
__wasi_subclockflags_t,
|
||||||
|
dec_subclockflags_byref,
|
||||||
|
enc_subclockflags_byref
|
||||||
|
);
|
||||||
|
|
||||||
|
dec_enc_scalar!(
|
||||||
|
__wasi_eventrwflags_t,
|
||||||
|
dec_eventrwflags_byref,
|
||||||
|
enc_eventrwflags_byref
|
||||||
|
);
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_eventtype_t, dec_eventtype_byref, enc_eventtype_byref);
|
||||||
|
dec_enc_scalar!(__wasi_userdata_t, dec_userdata_byref, enc_userdata_byref);
|
||||||
|
|
||||||
|
pub(crate) fn dec_subscriptions(
|
||||||
|
memory: &mut [u8],
|
||||||
|
input: wasi32::uintptr_t,
|
||||||
|
nsubscriptions: wasi32::size_t,
|
||||||
|
) -> Result<Vec<wasi::__wasi_subscription_t>> {
|
||||||
|
let raw_input_slice =
|
||||||
|
dec_raw_slice_of::<wasi::__wasi_subscription_t>(memory, input, nsubscriptions)?;
|
||||||
|
|
||||||
|
raw_input_slice
|
||||||
|
.into_iter()
|
||||||
|
.map(|raw_subscription| {
|
||||||
|
let userdata = PrimInt::from_le(raw_subscription.userdata);
|
||||||
|
let r#type = PrimInt::from_le(raw_subscription.r#type);
|
||||||
|
let raw_u = raw_subscription.u;
|
||||||
|
let u = match r#type {
|
||||||
|
wasi::__WASI_EVENTTYPE_CLOCK => wasi::__wasi_subscription_u {
|
||||||
|
clock: unsafe {
|
||||||
|
wasi::__wasi_subscription_clock_t {
|
||||||
|
identifier: PrimInt::from_le(raw_u.clock.identifier),
|
||||||
|
clock_id: PrimInt::from_le(raw_u.clock.clock_id),
|
||||||
|
timeout: PrimInt::from_le(raw_u.clock.timeout),
|
||||||
|
precision: PrimInt::from_le(raw_u.clock.precision),
|
||||||
|
flags: PrimInt::from_le(raw_u.clock.flags),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wasi::__WASI_EVENTTYPE_FD_READ | wasi::__WASI_EVENTTYPE_FD_WRITE => {
|
||||||
|
wasi::__wasi_subscription_u {
|
||||||
|
fd_readwrite: wasi::__wasi_subscription_fd_readwrite_t {
|
||||||
|
file_descriptor: PrimInt::from_le(unsafe {
|
||||||
|
raw_u.fd_readwrite.file_descriptor
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
};
|
||||||
|
Ok(wasi::__wasi_subscription_t {
|
||||||
|
userdata,
|
||||||
|
r#type,
|
||||||
|
u,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enc_events(
|
||||||
|
memory: &mut [u8],
|
||||||
|
output: wasi32::uintptr_t,
|
||||||
|
nsubscriptions: wasi32::size_t,
|
||||||
|
events: Vec<wasi::__wasi_event_t>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut raw_output_iter =
|
||||||
|
dec_raw_slice_of_mut::<wasi::__wasi_event_t>(memory, output, nsubscriptions)?.into_iter();
|
||||||
|
|
||||||
|
for event in events.iter() {
|
||||||
|
*raw_output_iter
|
||||||
|
.next()
|
||||||
|
.expect("the number of events cannot exceed the number of subscriptions") = {
|
||||||
|
let fd_readwrite = unsafe { event.u.fd_readwrite };
|
||||||
|
wasi::__wasi_event_t {
|
||||||
|
userdata: PrimInt::to_le(event.userdata),
|
||||||
|
r#type: PrimInt::to_le(event.r#type),
|
||||||
|
error: PrimInt::to_le(event.error),
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: PrimInt::to_le(fd_readwrite.nbytes),
|
||||||
|
flags: PrimInt::to_le(fd_readwrite.flags),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_enc_scalar!(__wasi_advice_t, dec_advice_byref, enc_advice_byref);
|
||||||
|
dec_enc_scalar!(__wasi_fstflags_t, dec_fstflags_byref, enc_fstflags_byref);
|
||||||
|
dec_enc_scalar!(__wasi_dircookie_t, dec_dircookie_byref, enc_dircookie_byref);
|
||||||
24
wasi-common/src/sys/mod.rs
Normal file
24
wasi-common/src/sys/mod.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use crate::wasi;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(unix)] {
|
||||||
|
mod unix;
|
||||||
|
pub(crate) use self::unix::*;
|
||||||
|
pub use self::unix::preopen_dir;
|
||||||
|
|
||||||
|
pub(crate) fn errno_from_host(err: i32) -> wasi::__wasi_errno_t {
|
||||||
|
host_impl::errno_from_nix(nix::errno::from_i32(err)).as_wasi_errno()
|
||||||
|
}
|
||||||
|
} else if #[cfg(windows)] {
|
||||||
|
mod windows;
|
||||||
|
pub(crate) use self::windows::*;
|
||||||
|
pub use self::windows::preopen_dir;
|
||||||
|
|
||||||
|
pub(crate) fn errno_from_host(err: i32) -> wasi::__wasi_errno_t {
|
||||||
|
host_impl::errno_from_win(winx::winerror::WinError::from_u32(err as u32))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
compile_error!("wasi-common doesn't compile for this platform yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
289
wasi-common/src/sys/unix/bsd/hostcalls_impl.rs
Normal file
289
wasi-common/src/sys/unix/bsd/hostcalls_impl.rs
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
use super::osfile::OsFile;
|
||||||
|
use crate::hostcalls_impl::PathGet;
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::sys::unix::str_to_cstring;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use nix::libc::{self, c_long, c_void};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
|
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
|
||||||
|
use nix::errno;
|
||||||
|
use nix::libc::unlinkat;
|
||||||
|
|
||||||
|
let path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
|
||||||
|
// nix doesn't expose unlinkat() yet
|
||||||
|
match unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
_ => {
|
||||||
|
let mut e = errno::Errno::last();
|
||||||
|
|
||||||
|
// Non-Linux implementations may return EPERM when attempting to remove a
|
||||||
|
// directory without REMOVEDIR. While that's what POSIX specifies, it's
|
||||||
|
// less useful. Adjust this to EISDIR. It doesn't matter that this is not
|
||||||
|
// atomic with the unlinkat, because if the file is removed and a directory
|
||||||
|
// is created before fstatat sees it, we're racing with that change anyway
|
||||||
|
// and unlinkat could have legitimately seen the directory if the race had
|
||||||
|
// turned out differently.
|
||||||
|
use nix::fcntl::AtFlags;
|
||||||
|
use nix::sys::stat::{fstatat, SFlag};
|
||||||
|
|
||||||
|
if e == errno::Errno::EPERM {
|
||||||
|
if let Ok(stat) = fstatat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
resolved.path(),
|
||||||
|
AtFlags::AT_SYMLINK_NOFOLLOW,
|
||||||
|
) {
|
||||||
|
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR) {
|
||||||
|
e = errno::Errno::EISDIR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e = errno::Errno::last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(host_impl::errno_from_nix(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
||||||
|
use nix::{errno::Errno, fcntl::AtFlags, libc::symlinkat, sys::stat::fstatat};
|
||||||
|
|
||||||
|
let old_path_cstr = str_to_cstring(old_path)?;
|
||||||
|
let new_path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
|
||||||
|
log::debug!("path_symlink old_path = {:?}", old_path);
|
||||||
|
log::debug!("path_symlink resolved = {:?}", resolved);
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
symlinkat(
|
||||||
|
old_path_cstr.as_ptr(),
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
new_path_cstr.as_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
match Errno::last() {
|
||||||
|
Errno::ENOTDIR => {
|
||||||
|
// On BSD, symlinkat returns ENOTDIR when it should in fact
|
||||||
|
// return a EEXIST. It seems that it gets confused with by
|
||||||
|
// the trailing slash in the target path. Thus, we strip
|
||||||
|
// the trailing slash and check if the path exists, and
|
||||||
|
// adjust the error code appropriately.
|
||||||
|
let new_path = resolved.path().trim_end_matches('/');
|
||||||
|
if let Ok(_) = fstatat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
new_path,
|
||||||
|
AtFlags::AT_SYMLINK_NOFOLLOW,
|
||||||
|
) {
|
||||||
|
Err(Error::EEXIST)
|
||||||
|
} else {
|
||||||
|
Err(Error::ENOTDIR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => Err(host_impl::errno_from_nix(x)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
||||||
|
use nix::{errno::Errno, fcntl::AtFlags, libc::renameat, sys::stat::fstatat};
|
||||||
|
let old_path_cstr = str_to_cstring(resolved_old.path())?;
|
||||||
|
let new_path_cstr = str_to_cstring(resolved_new.path())?;
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
renameat(
|
||||||
|
resolved_old.dirfd().as_raw_fd(),
|
||||||
|
old_path_cstr.as_ptr(),
|
||||||
|
resolved_new.dirfd().as_raw_fd(),
|
||||||
|
new_path_cstr.as_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
// Currently, this is verified to be correct on macOS, where
|
||||||
|
// ENOENT can be returned in case when we try to rename a file
|
||||||
|
// into a name with a trailing slash. On macOS, if the latter does
|
||||||
|
// not exist, an ENOENT is thrown, whereas on Linux we observe the
|
||||||
|
// correct behaviour of throwing an ENOTDIR since the destination is
|
||||||
|
// indeed not a directory.
|
||||||
|
//
|
||||||
|
// TODO
|
||||||
|
// Verify on other BSD-based OSes.
|
||||||
|
match Errno::last() {
|
||||||
|
Errno::ENOENT => {
|
||||||
|
// check if the source path exists
|
||||||
|
if let Ok(_) = fstatat(
|
||||||
|
resolved_old.dirfd().as_raw_fd(),
|
||||||
|
resolved_old.path(),
|
||||||
|
AtFlags::AT_SYMLINK_NOFOLLOW,
|
||||||
|
) {
|
||||||
|
// check if destination contains a trailing slash
|
||||||
|
if resolved_new.path().contains('/') {
|
||||||
|
Err(Error::ENOTDIR)
|
||||||
|
} else {
|
||||||
|
Err(Error::ENOENT)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::ENOENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => Err(host_impl::errno_from_nix(x)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_readdir(
|
||||||
|
os_file: &mut OsFile,
|
||||||
|
host_buf: &mut [u8],
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<usize> {
|
||||||
|
use crate::sys::unix::bsd::osfile::DirStream;
|
||||||
|
use libc::{fdopendir, readdir, rewinddir, seekdir, telldir};
|
||||||
|
use nix::errno::Errno;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
let dir_stream = match os_file.dir_stream {
|
||||||
|
Some(ref mut dir_stream) => dir_stream,
|
||||||
|
None => {
|
||||||
|
let file = os_file.file.try_clone()?;
|
||||||
|
let dir_ptr = unsafe { fdopendir(file.as_raw_fd()) };
|
||||||
|
os_file.dir_stream.get_or_insert(Mutex::new(DirStream {
|
||||||
|
file: ManuallyDrop::new(file),
|
||||||
|
dir_ptr,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let dir_stream = dir_stream.lock().unwrap();
|
||||||
|
|
||||||
|
let host_buf_ptr = host_buf.as_mut_ptr();
|
||||||
|
let host_buf_len = host_buf.len();
|
||||||
|
|
||||||
|
if cookie != wasi::__WASI_DIRCOOKIE_START {
|
||||||
|
unsafe { seekdir(dir_stream.dir_ptr, cookie as c_long) };
|
||||||
|
} else {
|
||||||
|
unsafe { rewinddir(dir_stream.dir_ptr) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut left = host_buf_len;
|
||||||
|
let mut host_buf_offset: usize = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let host_entry = unsafe { readdir(dir_stream.dir_ptr) };
|
||||||
|
if host_entry.is_null() {
|
||||||
|
// FIXME
|
||||||
|
// Currently, these are verified to be correct on macOS.
|
||||||
|
// Need to still verify these on other BSD-based OSes.
|
||||||
|
match Errno::last() {
|
||||||
|
Errno::EBADF => return Err(Error::EBADF),
|
||||||
|
Errno::EFAULT => return Err(Error::EFAULT),
|
||||||
|
Errno::EIO => return Err(Error::EIO),
|
||||||
|
_ => break, // not an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entry: wasi::__wasi_dirent_t =
|
||||||
|
host_impl::dirent_from_host(&unsafe { *host_entry })?;
|
||||||
|
// Set d_next manually:
|
||||||
|
// * on macOS d_seekoff is not set for some reason
|
||||||
|
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
|
||||||
|
// not equivalent to the value read from telldir call
|
||||||
|
entry.d_next = unsafe { telldir(dir_stream.dir_ptr) } as wasi::__wasi_dircookie_t;
|
||||||
|
|
||||||
|
log::debug!("fd_readdir entry = {:?}", entry);
|
||||||
|
|
||||||
|
let name_len = entry.d_namlen.try_into()?;
|
||||||
|
let required_space = std::mem::size_of_val(&entry) + name_len;
|
||||||
|
if required_space > left {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
|
||||||
|
as *mut wasi::__wasi_dirent_t;
|
||||||
|
*ptr = entry;
|
||||||
|
}
|
||||||
|
host_buf_offset += std::mem::size_of_val(&entry);
|
||||||
|
let name_ptr = unsafe { *host_entry }.d_name.as_ptr();
|
||||||
|
unsafe {
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
name_ptr as *const _,
|
||||||
|
host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut _,
|
||||||
|
name_len,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
host_buf_offset += name_len;
|
||||||
|
left -= required_space;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(host_buf_len - left)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
pub(crate) fn fd_advise(
|
||||||
|
file: &File,
|
||||||
|
advice: wasi::__wasi_advice_t,
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
len: wasi::__wasi_filesize_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
use nix::errno::Errno;
|
||||||
|
|
||||||
|
match advice {
|
||||||
|
wasi::__WASI_ADVICE_DONTNEED => return Ok(()),
|
||||||
|
// unfortunately, the advisory syscall in macOS doesn't take any flags of this
|
||||||
|
// sort (unlike on Linux), hence, they are left here as a noop
|
||||||
|
wasi::__WASI_ADVICE_SEQUENTIAL
|
||||||
|
| wasi::__WASI_ADVICE_WILLNEED
|
||||||
|
| wasi::__WASI_ADVICE_NOREUSE
|
||||||
|
| wasi::__WASI_ADVICE_RANDOM
|
||||||
|
| wasi::__WASI_ADVICE_NORMAL => {}
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
}
|
||||||
|
|
||||||
|
// From macOS man pages:
|
||||||
|
// F_RDADVISE Issue an advisory read async with no copy to user.
|
||||||
|
//
|
||||||
|
// The F_RDADVISE command operates on the following structure which holds information passed from
|
||||||
|
// the user to the system:
|
||||||
|
//
|
||||||
|
// struct radvisory {
|
||||||
|
// off_t ra_offset; /* offset into the file */
|
||||||
|
// int ra_count; /* size of the read */
|
||||||
|
// };
|
||||||
|
let advisory = libc::radvisory {
|
||||||
|
ra_offset: offset.try_into()?,
|
||||||
|
ra_count: len.try_into()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_RDADVISE, &advisory) };
|
||||||
|
Errno::result(res).map(|_| ()).map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// It seems that at least some BSDs do support `posix_fadvise`,
|
||||||
|
// so we should investigate further.
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||||
|
pub(crate) fn fd_advise(
|
||||||
|
_file: &File,
|
||||||
|
advice: wasi::__wasi_advice_t,
|
||||||
|
_offset: wasi::__wasi_filesize_t,
|
||||||
|
_len: wasi::__wasi_filesize_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
match advice {
|
||||||
|
wasi::__WASI_ADVICE_DONTNEED
|
||||||
|
| wasi::__WASI_ADVICE_SEQUENTIAL
|
||||||
|
| wasi::__WASI_ADVICE_WILLNEED
|
||||||
|
| wasi::__WASI_ADVICE_NOREUSE
|
||||||
|
| wasi::__WASI_ADVICE_RANDOM
|
||||||
|
| wasi::__WASI_ADVICE_NORMAL => {}
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
82
wasi-common/src/sys/unix/bsd/mod.rs
Normal file
82
wasi-common/src/sys/unix/bsd/mod.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
pub(crate) mod hostcalls_impl;
|
||||||
|
pub(crate) mod osfile;
|
||||||
|
|
||||||
|
pub(crate) mod fdentry_impl {
|
||||||
|
use crate::{sys::host_impl, Result};
|
||||||
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
|
pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result<bool> {
|
||||||
|
let res = libc::isatty(fd.as_raw_fd());
|
||||||
|
if res == 1 {
|
||||||
|
// isatty() returns 1 if fd is an open file descriptor referring to a terminal...
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// ... otherwise 0 is returned, and errno is set to indicate the error.
|
||||||
|
match nix::errno::Errno::last() {
|
||||||
|
nix::errno::Errno::ENOTTY => Ok(false),
|
||||||
|
x => Err(host_impl::errno_from_nix(x)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod host_impl {
|
||||||
|
use super::super::host_impl::dirent_filetype_from_host;
|
||||||
|
use crate::{wasi, Result};
|
||||||
|
|
||||||
|
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC;
|
||||||
|
|
||||||
|
pub(crate) fn dirent_from_host(
|
||||||
|
host_entry: &nix::libc::dirent,
|
||||||
|
) -> Result<wasi::__wasi_dirent_t> {
|
||||||
|
let mut entry = unsafe { std::mem::zeroed::<wasi::__wasi_dirent_t>() };
|
||||||
|
let d_type = dirent_filetype_from_host(host_entry)?;
|
||||||
|
entry.d_ino = host_entry.d_ino;
|
||||||
|
entry.d_next = host_entry.d_seekoff;
|
||||||
|
entry.d_namlen = u32::from(host_entry.d_namlen);
|
||||||
|
entry.d_type = d_type;
|
||||||
|
Ok(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod fs_helpers {
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
pub(crate) fn utime_now() -> libc::c_long {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))] {
|
||||||
|
-1
|
||||||
|
} else if #[cfg(target_os = "openbsd")] {
|
||||||
|
// https://github.com/openbsd/src/blob/master/sys/sys/stat.h#L187
|
||||||
|
-2
|
||||||
|
} else if #[cfg(target_os = "netbsd" )] {
|
||||||
|
// http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/sys/stat.h?rev=1.69&content-type=text/x-cvsweb-markup&only_with_tag=MAIN
|
||||||
|
1_073_741_823
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn utime_omit() -> libc::c_long {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))] {
|
||||||
|
-2
|
||||||
|
} else if #[cfg(target_os = "openbsd")] {
|
||||||
|
// https://github.com/openbsd/src/blob/master/sys/sys/stat.h#L187
|
||||||
|
-1
|
||||||
|
} else if #[cfg(target_os = "netbsd")] {
|
||||||
|
// http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/sys/stat.h?rev=1.69&content-type=text/x-cvsweb-markup&only_with_tag=MAIN
|
||||||
|
1_073_741_822
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
wasi-common/src/sys/unix/bsd/osfile.rs
Normal file
52
wasi-common/src/sys/unix/bsd/osfile.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct DirStream {
|
||||||
|
pub(crate) file: ManuallyDrop<fs::File>,
|
||||||
|
pub(crate) dir_ptr: *mut libc::DIR,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DirStream {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { libc::closedir(self.dir_ptr) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct OsFile {
|
||||||
|
pub(crate) file: fs::File,
|
||||||
|
pub(crate) dir_stream: Option<Mutex<DirStream>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<fs::File> for OsFile {
|
||||||
|
fn from(file: fs::File) -> Self {
|
||||||
|
Self {
|
||||||
|
file,
|
||||||
|
dir_stream: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for OsFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for OsFile {
|
||||||
|
type Target = fs::File;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for OsFile {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.file
|
||||||
|
}
|
||||||
|
}
|
||||||
216
wasi-common/src/sys/unix/dir.rs
Normal file
216
wasi-common/src/sys/unix/dir.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// Based on src/dir.rs from nix
|
||||||
|
#![allow(unused)] // temporarily, until BSD catches up with this change
|
||||||
|
use crate::hostcalls_impl::FileType;
|
||||||
|
use libc;
|
||||||
|
use nix::{errno::Errno, Error, Result};
|
||||||
|
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||||
|
use std::{ffi, ptr};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use libc::{dirent64 as dirent, readdir64_r as readdir_r};
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
use libc::{dirent, readdir_r};
|
||||||
|
|
||||||
|
/// An open directory.
|
||||||
|
///
|
||||||
|
/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences:
|
||||||
|
/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing
|
||||||
|
/// if the path represents a file or directory).
|
||||||
|
/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc.
|
||||||
|
/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd`
|
||||||
|
/// after the `Dir` is dropped.
|
||||||
|
/// * can be iterated through multiple times without closing and reopening the file
|
||||||
|
/// descriptor. Each iteration rewinds when finished.
|
||||||
|
/// * returns entries for `.` (current directory) and `..` (parent directory).
|
||||||
|
/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
|
||||||
|
/// does).
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub(crate) struct Dir(ptr::NonNull<libc::DIR>);
|
||||||
|
|
||||||
|
impl Dir {
|
||||||
|
/// Converts from a descriptor-based object, closing the descriptor on success or failure.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
|
||||||
|
unsafe { Self::from_fd(fd.into_raw_fd()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts from a file descriptor, closing it on success or failure.
|
||||||
|
unsafe fn from_fd(fd: RawFd) -> Result<Self> {
|
||||||
|
let d = libc::fdopendir(fd);
|
||||||
|
if d.is_null() {
|
||||||
|
let e = Error::last();
|
||||||
|
libc::close(fd);
|
||||||
|
return Err(e);
|
||||||
|
};
|
||||||
|
// Always guaranteed to be non-null by the previous check
|
||||||
|
Ok(Self(ptr::NonNull::new(d).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the position of the directory stream, see `seekdir(3)`.
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
pub(crate) fn seek(&mut self, loc: SeekLoc) {
|
||||||
|
unsafe { libc::seekdir(self.0.as_ptr(), loc.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset directory stream, see `rewinddir(3)`.
|
||||||
|
pub(crate) fn rewind(&mut self) {
|
||||||
|
unsafe { libc::rewinddir(self.0.as_ptr()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current position in the directory stream.
|
||||||
|
///
|
||||||
|
/// If this location is given to `Dir::seek`, the entries up to the previously returned
|
||||||
|
/// will be omitted and the iteration will start from the currently pending directory entry.
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn tell(&self) -> SeekLoc {
|
||||||
|
let loc = unsafe { libc::telldir(self.0.as_ptr()) };
|
||||||
|
SeekLoc(loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Dir` is not `Sync`. With the current implementation, it could be, but according to
|
||||||
|
// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
|
||||||
|
// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
|
||||||
|
// call `readdir` simultaneously from multiple threads.
|
||||||
|
//
|
||||||
|
// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
|
||||||
|
unsafe impl Send for Dir {}
|
||||||
|
|
||||||
|
impl AsRawFd for Dir {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
unsafe { libc::dirfd(self.0.as_ptr()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Dir {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { libc::closedir(self.0.as_ptr()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct IntoIter(Dir);
|
||||||
|
impl Iterator for IntoIter {
|
||||||
|
type Item = Result<Entry>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
unsafe {
|
||||||
|
// Note: POSIX specifies that portable applications should dynamically allocate a
|
||||||
|
// buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
|
||||||
|
// for the NUL byte. It doesn't look like the std library does this; it just uses
|
||||||
|
// fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
|
||||||
|
// Probably fine here too then.
|
||||||
|
//
|
||||||
|
// See `impl Iterator for ReadDir` [1] for more details.
|
||||||
|
// [1] https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs
|
||||||
|
let mut ent = std::mem::MaybeUninit::<dirent>::uninit();
|
||||||
|
let mut result = ptr::null_mut();
|
||||||
|
if let Err(e) = Errno::result(readdir_r(
|
||||||
|
(self.0).0.as_ptr(),
|
||||||
|
ent.as_mut_ptr(),
|
||||||
|
&mut result,
|
||||||
|
)) {
|
||||||
|
return Some(Err(e));
|
||||||
|
}
|
||||||
|
if result.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
assert_eq!(result, ent.as_mut_ptr(), "readdir_r specification violated");
|
||||||
|
Some(Ok(Entry(ent.assume_init())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Dir {
|
||||||
|
type IntoIter = IntoIter;
|
||||||
|
type Item = Result<Entry>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> IntoIter {
|
||||||
|
IntoIter(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A directory entry, similar to `std::fs::DirEntry`.
|
||||||
|
///
|
||||||
|
/// Note that unlike the std version, this may represent the `.` or `..` entries.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(crate) struct Entry(dirent);
|
||||||
|
|
||||||
|
pub(crate) type Type = FileType;
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
/// Returns the inode number (`d_ino`) of the underlying `dirent`.
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "emscripten",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "l4re",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "solaris"
|
||||||
|
))]
|
||||||
|
pub(crate) fn ino(&self) -> u64 {
|
||||||
|
self.0.d_ino.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inode number (`d_fileno`) of the underlying `dirent`.
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "emscripten",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "l4re",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "solaris"
|
||||||
|
)))]
|
||||||
|
pub(crate) fn ino(&self) -> u64 {
|
||||||
|
u64::from(self.0.d_fileno)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bare file name of this directory entry without any other leading path component.
|
||||||
|
pub(crate) fn file_name(&self) -> &ffi::CStr {
|
||||||
|
unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this directory entry, if known.
|
||||||
|
///
|
||||||
|
/// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
|
||||||
|
/// notably, some Linux filesystems don't implement this. The caller should use `stat` or
|
||||||
|
/// `fstat` if this returns `None`.
|
||||||
|
pub(crate) fn file_type(&self) -> FileType {
|
||||||
|
match self.0.d_type {
|
||||||
|
libc::DT_CHR => Type::CharacterDevice,
|
||||||
|
libc::DT_DIR => Type::Directory,
|
||||||
|
libc::DT_BLK => Type::BlockDevice,
|
||||||
|
libc::DT_REG => Type::RegularFile,
|
||||||
|
libc::DT_LNK => Type::Symlink,
|
||||||
|
/* libc::DT_UNKNOWN | libc::DT_SOCK | libc::DT_FIFO */ _ => Type::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) fn seek_loc(&self) -> SeekLoc {
|
||||||
|
unsafe { SeekLoc::from_raw(self.0.d_off) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct SeekLoc(libc::c_long);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
impl SeekLoc {
|
||||||
|
pub(crate) unsafe fn from_raw(loc: i64) -> Self {
|
||||||
|
Self(loc.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_raw(&self) -> i64 {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
135
wasi-common/src/sys/unix/fdentry_impl.rs
Normal file
135
wasi-common/src/sys/unix/fdentry_impl.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
use crate::fdentry::Descriptor;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use std::io;
|
||||||
|
use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
pub(crate) use super::linux::osfile::*;
|
||||||
|
pub(crate) use super::linux::fdentry_impl::*;
|
||||||
|
} else if #[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))] {
|
||||||
|
pub(crate) use super::bsd::osfile::*;
|
||||||
|
pub(crate) use super::bsd::fdentry_impl::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for Descriptor {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(file) => file.as_raw_fd(),
|
||||||
|
Self::Stdin => io::stdin().as_raw_fd(),
|
||||||
|
Self::Stdout => io::stdout().as_raw_fd(),
|
||||||
|
Self::Stderr => io::stderr().as_raw_fd(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is unsafe because it operates on a raw file descriptor.
|
||||||
|
pub(crate) unsafe fn determine_type_and_access_rights<Fd: AsRawFd>(
|
||||||
|
fd: &Fd,
|
||||||
|
) -> Result<(
|
||||||
|
wasi::__wasi_filetype_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
)> {
|
||||||
|
let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(fd)?;
|
||||||
|
|
||||||
|
use nix::fcntl::{fcntl, OFlag, F_GETFL};
|
||||||
|
let flags_bits = fcntl(fd.as_raw_fd(), F_GETFL)?;
|
||||||
|
let flags = OFlag::from_bits_truncate(flags_bits);
|
||||||
|
let accmode = flags & OFlag::O_ACCMODE;
|
||||||
|
if accmode == OFlag::O_RDONLY {
|
||||||
|
rights_base &= !wasi::__WASI_RIGHT_FD_WRITE;
|
||||||
|
} else if accmode == OFlag::O_WRONLY {
|
||||||
|
rights_base &= !wasi::__WASI_RIGHT_FD_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((file_type, rights_base, rights_inheriting))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is unsafe because it operates on a raw file descriptor.
|
||||||
|
pub(crate) unsafe fn determine_type_rights<Fd: AsRawFd>(
|
||||||
|
fd: &Fd,
|
||||||
|
) -> Result<(
|
||||||
|
wasi::__wasi_filetype_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
)> {
|
||||||
|
let (file_type, rights_base, rights_inheriting) = {
|
||||||
|
// we just make a `File` here for convenience; we don't want it to close when it drops
|
||||||
|
let file = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd.as_raw_fd()));
|
||||||
|
let ft = file.metadata()?.file_type();
|
||||||
|
if ft.is_block_device() {
|
||||||
|
log::debug!("Host fd {:?} is a block device", fd.as_raw_fd());
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_BLOCK_DEVICE,
|
||||||
|
wasi::RIGHTS_BLOCK_DEVICE_BASE,
|
||||||
|
wasi::RIGHTS_BLOCK_DEVICE_INHERITING,
|
||||||
|
)
|
||||||
|
} else if ft.is_char_device() {
|
||||||
|
log::debug!("Host fd {:?} is a char device", fd.as_raw_fd());
|
||||||
|
if isatty(fd)? {
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
|
||||||
|
wasi::RIGHTS_TTY_BASE,
|
||||||
|
wasi::RIGHTS_TTY_BASE,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
|
||||||
|
wasi::RIGHTS_CHARACTER_DEVICE_BASE,
|
||||||
|
wasi::RIGHTS_CHARACTER_DEVICE_INHERITING,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if ft.is_dir() {
|
||||||
|
log::debug!("Host fd {:?} is a directory", fd.as_raw_fd());
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_DIRECTORY,
|
||||||
|
wasi::RIGHTS_DIRECTORY_BASE,
|
||||||
|
wasi::RIGHTS_DIRECTORY_INHERITING,
|
||||||
|
)
|
||||||
|
} else if ft.is_file() {
|
||||||
|
log::debug!("Host fd {:?} is a file", fd.as_raw_fd());
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_REGULAR_FILE,
|
||||||
|
wasi::RIGHTS_REGULAR_FILE_BASE,
|
||||||
|
wasi::RIGHTS_REGULAR_FILE_INHERITING,
|
||||||
|
)
|
||||||
|
} else if ft.is_socket() {
|
||||||
|
log::debug!("Host fd {:?} is a socket", fd.as_raw_fd());
|
||||||
|
use nix::sys::socket;
|
||||||
|
match socket::getsockopt(fd.as_raw_fd(), socket::sockopt::SockType)? {
|
||||||
|
socket::SockType::Datagram => (
|
||||||
|
wasi::__WASI_FILETYPE_SOCKET_DGRAM,
|
||||||
|
wasi::RIGHTS_SOCKET_BASE,
|
||||||
|
wasi::RIGHTS_SOCKET_INHERITING,
|
||||||
|
),
|
||||||
|
socket::SockType::Stream => (
|
||||||
|
wasi::__WASI_FILETYPE_SOCKET_STREAM,
|
||||||
|
wasi::RIGHTS_SOCKET_BASE,
|
||||||
|
wasi::RIGHTS_SOCKET_INHERITING,
|
||||||
|
),
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
}
|
||||||
|
} else if ft.is_fifo() {
|
||||||
|
log::debug!("Host fd {:?} is a fifo", fd.as_raw_fd());
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_UNKNOWN,
|
||||||
|
wasi::RIGHTS_REGULAR_FILE_BASE,
|
||||||
|
wasi::RIGHTS_REGULAR_FILE_INHERITING,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log::debug!("Host fd {:?} is unknown", fd.as_raw_fd());
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((file_type, rights_base, rights_inheriting))
|
||||||
|
}
|
||||||
246
wasi-common/src/sys/unix/host_impl.rs
Normal file
246
wasi-common/src/sys/unix/host_impl.rs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
//! WASI host types specific to *nix host.
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
use crate::hostcalls_impl::FileType;
|
||||||
|
use crate::{helpers, wasi, Error, Result};
|
||||||
|
use log::warn;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::unix::prelude::OsStrExt;
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
pub(crate) use super::linux::host_impl::*;
|
||||||
|
} else if #[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))] {
|
||||||
|
pub(crate) use super::bsd::host_impl::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn errno_from_nix(errno: nix::errno::Errno) -> Error {
|
||||||
|
match errno {
|
||||||
|
nix::errno::Errno::EPERM => Error::EPERM,
|
||||||
|
nix::errno::Errno::ENOENT => Error::ENOENT,
|
||||||
|
nix::errno::Errno::ESRCH => Error::ESRCH,
|
||||||
|
nix::errno::Errno::EINTR => Error::EINTR,
|
||||||
|
nix::errno::Errno::EIO => Error::EIO,
|
||||||
|
nix::errno::Errno::ENXIO => Error::ENXIO,
|
||||||
|
nix::errno::Errno::E2BIG => Error::E2BIG,
|
||||||
|
nix::errno::Errno::ENOEXEC => Error::ENOEXEC,
|
||||||
|
nix::errno::Errno::EBADF => Error::EBADF,
|
||||||
|
nix::errno::Errno::ECHILD => Error::ECHILD,
|
||||||
|
nix::errno::Errno::EAGAIN => Error::EAGAIN,
|
||||||
|
nix::errno::Errno::ENOMEM => Error::ENOMEM,
|
||||||
|
nix::errno::Errno::EACCES => Error::EACCES,
|
||||||
|
nix::errno::Errno::EFAULT => Error::EFAULT,
|
||||||
|
nix::errno::Errno::EBUSY => Error::EBUSY,
|
||||||
|
nix::errno::Errno::EEXIST => Error::EEXIST,
|
||||||
|
nix::errno::Errno::EXDEV => Error::EXDEV,
|
||||||
|
nix::errno::Errno::ENODEV => Error::ENODEV,
|
||||||
|
nix::errno::Errno::ENOTDIR => Error::ENOTDIR,
|
||||||
|
nix::errno::Errno::EISDIR => Error::EISDIR,
|
||||||
|
nix::errno::Errno::EINVAL => Error::EINVAL,
|
||||||
|
nix::errno::Errno::ENFILE => Error::ENFILE,
|
||||||
|
nix::errno::Errno::EMFILE => Error::EMFILE,
|
||||||
|
nix::errno::Errno::ENOTTY => Error::ENOTTY,
|
||||||
|
nix::errno::Errno::ETXTBSY => Error::ETXTBSY,
|
||||||
|
nix::errno::Errno::EFBIG => Error::EFBIG,
|
||||||
|
nix::errno::Errno::ENOSPC => Error::ENOSPC,
|
||||||
|
nix::errno::Errno::ESPIPE => Error::ESPIPE,
|
||||||
|
nix::errno::Errno::EROFS => Error::EROFS,
|
||||||
|
nix::errno::Errno::EMLINK => Error::EMLINK,
|
||||||
|
nix::errno::Errno::EPIPE => Error::EPIPE,
|
||||||
|
nix::errno::Errno::EDOM => Error::EDOM,
|
||||||
|
nix::errno::Errno::ERANGE => Error::ERANGE,
|
||||||
|
nix::errno::Errno::EDEADLK => Error::EDEADLK,
|
||||||
|
nix::errno::Errno::ENAMETOOLONG => Error::ENAMETOOLONG,
|
||||||
|
nix::errno::Errno::ENOLCK => Error::ENOLCK,
|
||||||
|
nix::errno::Errno::ENOSYS => Error::ENOSYS,
|
||||||
|
nix::errno::Errno::ENOTEMPTY => Error::ENOTEMPTY,
|
||||||
|
nix::errno::Errno::ELOOP => Error::ELOOP,
|
||||||
|
nix::errno::Errno::ENOMSG => Error::ENOMSG,
|
||||||
|
nix::errno::Errno::EIDRM => Error::EIDRM,
|
||||||
|
nix::errno::Errno::ENOLINK => Error::ENOLINK,
|
||||||
|
nix::errno::Errno::EPROTO => Error::EPROTO,
|
||||||
|
nix::errno::Errno::EMULTIHOP => Error::EMULTIHOP,
|
||||||
|
nix::errno::Errno::EBADMSG => Error::EBADMSG,
|
||||||
|
nix::errno::Errno::EOVERFLOW => Error::EOVERFLOW,
|
||||||
|
nix::errno::Errno::EILSEQ => Error::EILSEQ,
|
||||||
|
nix::errno::Errno::ENOTSOCK => Error::ENOTSOCK,
|
||||||
|
nix::errno::Errno::EDESTADDRREQ => Error::EDESTADDRREQ,
|
||||||
|
nix::errno::Errno::EMSGSIZE => Error::EMSGSIZE,
|
||||||
|
nix::errno::Errno::EPROTOTYPE => Error::EPROTOTYPE,
|
||||||
|
nix::errno::Errno::ENOPROTOOPT => Error::ENOPROTOOPT,
|
||||||
|
nix::errno::Errno::EPROTONOSUPPORT => Error::EPROTONOSUPPORT,
|
||||||
|
nix::errno::Errno::EAFNOSUPPORT => Error::EAFNOSUPPORT,
|
||||||
|
nix::errno::Errno::EADDRINUSE => Error::EADDRINUSE,
|
||||||
|
nix::errno::Errno::EADDRNOTAVAIL => Error::EADDRNOTAVAIL,
|
||||||
|
nix::errno::Errno::ENETDOWN => Error::ENETDOWN,
|
||||||
|
nix::errno::Errno::ENETUNREACH => Error::ENETUNREACH,
|
||||||
|
nix::errno::Errno::ENETRESET => Error::ENETRESET,
|
||||||
|
nix::errno::Errno::ECONNABORTED => Error::ECONNABORTED,
|
||||||
|
nix::errno::Errno::ECONNRESET => Error::ECONNRESET,
|
||||||
|
nix::errno::Errno::ENOBUFS => Error::ENOBUFS,
|
||||||
|
nix::errno::Errno::EISCONN => Error::EISCONN,
|
||||||
|
nix::errno::Errno::ENOTCONN => Error::ENOTCONN,
|
||||||
|
nix::errno::Errno::ETIMEDOUT => Error::ETIMEDOUT,
|
||||||
|
nix::errno::Errno::ECONNREFUSED => Error::ECONNREFUSED,
|
||||||
|
nix::errno::Errno::EHOSTUNREACH => Error::EHOSTUNREACH,
|
||||||
|
nix::errno::Errno::EALREADY => Error::EALREADY,
|
||||||
|
nix::errno::Errno::EINPROGRESS => Error::EINPROGRESS,
|
||||||
|
nix::errno::Errno::ESTALE => Error::ESTALE,
|
||||||
|
nix::errno::Errno::EDQUOT => Error::EDQUOT,
|
||||||
|
nix::errno::Errno::ECANCELED => Error::ECANCELED,
|
||||||
|
nix::errno::Errno::EOWNERDEAD => Error::EOWNERDEAD,
|
||||||
|
nix::errno::Errno::ENOTRECOVERABLE => Error::ENOTRECOVERABLE,
|
||||||
|
other => {
|
||||||
|
warn!("Unknown error from nix: {}", other);
|
||||||
|
Error::ENOSYS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> nix::fcntl::OFlag {
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
let mut nix_flags = OFlag::empty();
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_APPEND != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_APPEND);
|
||||||
|
}
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_DSYNC);
|
||||||
|
}
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_NONBLOCK != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_NONBLOCK);
|
||||||
|
}
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_RSYNC != 0 {
|
||||||
|
nix_flags.insert(O_RSYNC);
|
||||||
|
}
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_SYNC != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_SYNC);
|
||||||
|
}
|
||||||
|
nix_flags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fdflags_from_nix(oflags: nix::fcntl::OFlag) -> wasi::__wasi_fdflags_t {
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
let mut fdflags = 0;
|
||||||
|
if oflags.contains(OFlag::O_APPEND) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_APPEND;
|
||||||
|
}
|
||||||
|
if oflags.contains(OFlag::O_DSYNC) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_DSYNC;
|
||||||
|
}
|
||||||
|
if oflags.contains(OFlag::O_NONBLOCK) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_NONBLOCK;
|
||||||
|
}
|
||||||
|
if oflags.contains(O_RSYNC) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_RSYNC;
|
||||||
|
}
|
||||||
|
if oflags.contains(OFlag::O_SYNC) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_SYNC;
|
||||||
|
}
|
||||||
|
fdflags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> nix::fcntl::OFlag {
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
let mut nix_flags = OFlag::empty();
|
||||||
|
if oflags & wasi::__WASI_O_CREAT != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_CREAT);
|
||||||
|
}
|
||||||
|
if oflags & wasi::__WASI_O_DIRECTORY != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_DIRECTORY);
|
||||||
|
}
|
||||||
|
if oflags & wasi::__WASI_O_EXCL != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_EXCL);
|
||||||
|
}
|
||||||
|
if oflags & wasi::__WASI_O_TRUNC != 0 {
|
||||||
|
nix_flags.insert(OFlag::O_TRUNC);
|
||||||
|
}
|
||||||
|
nix_flags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filetype_from_nix(sflags: nix::sys::stat::SFlag) -> FileType {
|
||||||
|
use nix::sys::stat::SFlag;
|
||||||
|
if sflags.contains(SFlag::S_IFCHR) {
|
||||||
|
FileType::CharacterDevice
|
||||||
|
} else if sflags.contains(SFlag::S_IFBLK) {
|
||||||
|
FileType::BlockDevice
|
||||||
|
} else if sflags.contains(SFlag::S_IFSOCK) {
|
||||||
|
FileType::SocketStream
|
||||||
|
} else if sflags.contains(SFlag::S_IFDIR) {
|
||||||
|
FileType::Directory
|
||||||
|
} else if sflags.contains(SFlag::S_IFREG) {
|
||||||
|
FileType::RegularFile
|
||||||
|
} else if sflags.contains(SFlag::S_IFLNK) {
|
||||||
|
FileType::Symlink
|
||||||
|
} else {
|
||||||
|
FileType::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filestat_from_nix(
|
||||||
|
filestat: nix::sys::stat::FileStat,
|
||||||
|
) -> Result<wasi::__wasi_filestat_t> {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result<wasi::__wasi_timestamp_t> {
|
||||||
|
secs.checked_mul(1_000_000_000)
|
||||||
|
.and_then(|sec_nsec| sec_nsec.checked_add(nsecs))
|
||||||
|
.ok_or(Error::EOVERFLOW)
|
||||||
|
}
|
||||||
|
|
||||||
|
let filetype = nix::sys::stat::SFlag::from_bits_truncate(filestat.st_mode);
|
||||||
|
let dev = wasi::__wasi_device_t::try_from(filestat.st_dev)?;
|
||||||
|
let ino = wasi::__wasi_inode_t::try_from(filestat.st_ino)?;
|
||||||
|
let st_atim = filestat_to_timestamp(filestat.st_atime as u64, filestat.st_atime_nsec as u64)?;
|
||||||
|
let st_ctim = filestat_to_timestamp(filestat.st_ctime as u64, filestat.st_ctime_nsec as u64)?;
|
||||||
|
let st_mtim = filestat_to_timestamp(filestat.st_mtime as u64, filestat.st_mtime_nsec as u64)?;
|
||||||
|
|
||||||
|
Ok(wasi::__wasi_filestat_t {
|
||||||
|
st_dev: dev,
|
||||||
|
st_ino: ino,
|
||||||
|
st_nlink: filestat.st_nlink as wasi::__wasi_linkcount_t,
|
||||||
|
st_size: filestat.st_size as wasi::__wasi_filesize_t,
|
||||||
|
st_atim,
|
||||||
|
st_ctim,
|
||||||
|
st_mtim,
|
||||||
|
st_filetype: filetype_from_nix(filetype).to_wasi(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dirent_filetype_from_host(
|
||||||
|
host_entry: &nix::libc::dirent,
|
||||||
|
) -> Result<wasi::__wasi_filetype_t> {
|
||||||
|
match host_entry.d_type {
|
||||||
|
libc::DT_FIFO => Ok(wasi::__WASI_FILETYPE_UNKNOWN),
|
||||||
|
libc::DT_CHR => Ok(wasi::__WASI_FILETYPE_CHARACTER_DEVICE),
|
||||||
|
libc::DT_DIR => Ok(wasi::__WASI_FILETYPE_DIRECTORY),
|
||||||
|
libc::DT_BLK => Ok(wasi::__WASI_FILETYPE_BLOCK_DEVICE),
|
||||||
|
libc::DT_REG => Ok(wasi::__WASI_FILETYPE_REGULAR_FILE),
|
||||||
|
libc::DT_LNK => Ok(wasi::__WASI_FILETYPE_SYMBOLIC_LINK),
|
||||||
|
libc::DT_SOCK => {
|
||||||
|
// TODO how to discriminate between STREAM and DGRAM?
|
||||||
|
// Perhaps, we should create a more general WASI filetype
|
||||||
|
// such as __WASI_FILETYPE_SOCKET, and then it would be
|
||||||
|
// up to the client to check whether it's actually
|
||||||
|
// STREAM or DGRAM?
|
||||||
|
Ok(wasi::__WASI_FILETYPE_UNKNOWN)
|
||||||
|
}
|
||||||
|
libc::DT_UNKNOWN => Ok(wasi::__WASI_FILETYPE_UNKNOWN),
|
||||||
|
_ => Err(Error::EINVAL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates owned WASI path from OS string.
|
||||||
|
///
|
||||||
|
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
|
||||||
|
/// `__WASI_EILSEQ` error is returned.
|
||||||
|
pub(crate) fn path_from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
|
||||||
|
helpers::path_from_slice(s.as_ref().as_bytes()).map(String::from)
|
||||||
|
}
|
||||||
357
wasi-common/src/sys/unix/hostcalls_impl/fs.rs
Normal file
357
wasi-common/src/sys/unix/hostcalls_impl/fs.rs
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(unused_unsafe)]
|
||||||
|
use super::fs_helpers::*;
|
||||||
|
use crate::helpers::systemtime_to_timestamp;
|
||||||
|
use crate::hostcalls_impl::{FileType, PathGet};
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::sys::unix::str_to_cstring;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use nix::libc;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fs::{File, Metadata};
|
||||||
|
use std::os::unix::fs::FileExt;
|
||||||
|
use std::os::unix::prelude::{AsRawFd, FromRawFd};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
pub(crate) use super::super::linux::hostcalls_impl::*;
|
||||||
|
} else if #[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))] {
|
||||||
|
pub(crate) use super::super::bsd::hostcalls_impl::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_pread(
|
||||||
|
file: &File,
|
||||||
|
buf: &mut [u8],
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
) -> Result<usize> {
|
||||||
|
file.read_at(buf, offset).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
|
||||||
|
file.write_at(buf, offset).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
|
||||||
|
use nix::fcntl::{fcntl, OFlag, F_GETFL};
|
||||||
|
match fcntl(fd.as_raw_fd(), F_GETFL).map(OFlag::from_bits_truncate) {
|
||||||
|
Ok(flags) => Ok(host_impl::fdflags_from_nix(flags)),
|
||||||
|
Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
|
||||||
|
use nix::fcntl::{fcntl, F_SETFL};
|
||||||
|
let nix_flags = host_impl::nix_from_fdflags(fdflags);
|
||||||
|
match fcntl(fd.as_raw_fd(), F_SETFL(nix_flags)) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(host_impl::errno_from_nix(e.as_errno().unwrap())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> {
|
||||||
|
use nix::libc::mkdirat;
|
||||||
|
let path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
// nix doesn't expose mkdirat() yet
|
||||||
|
match unsafe { mkdirat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0o777) } {
|
||||||
|
0 => Ok(()),
|
||||||
|
_ => Err(host_impl::errno_from_nix(nix::errno::Errno::last())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
||||||
|
use nix::libc::linkat;
|
||||||
|
let old_path_cstr = str_to_cstring(resolved_old.path())?;
|
||||||
|
let new_path_cstr = str_to_cstring(resolved_new.path())?;
|
||||||
|
|
||||||
|
// Not setting AT_SYMLINK_FOLLOW fails on most filesystems
|
||||||
|
let atflags = libc::AT_SYMLINK_FOLLOW;
|
||||||
|
let res = unsafe {
|
||||||
|
linkat(
|
||||||
|
resolved_old.dirfd().as_raw_fd(),
|
||||||
|
old_path_cstr.as_ptr(),
|
||||||
|
resolved_new.dirfd().as_raw_fd(),
|
||||||
|
new_path_cstr.as_ptr(),
|
||||||
|
atflags,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
Err(host_impl::errno_from_nix(nix::errno::Errno::last()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_open(
|
||||||
|
resolved: PathGet,
|
||||||
|
read: bool,
|
||||||
|
write: bool,
|
||||||
|
oflags: wasi::__wasi_oflags_t,
|
||||||
|
fs_flags: wasi::__wasi_fdflags_t,
|
||||||
|
) -> Result<File> {
|
||||||
|
use nix::errno::Errno;
|
||||||
|
use nix::fcntl::{openat, AtFlags, OFlag};
|
||||||
|
use nix::sys::stat::{fstatat, Mode, SFlag};
|
||||||
|
|
||||||
|
let mut nix_all_oflags = if read && write {
|
||||||
|
OFlag::O_RDWR
|
||||||
|
} else if write {
|
||||||
|
OFlag::O_WRONLY
|
||||||
|
} else {
|
||||||
|
OFlag::O_RDONLY
|
||||||
|
};
|
||||||
|
|
||||||
|
// on non-Capsicum systems, we always want nofollow
|
||||||
|
nix_all_oflags.insert(OFlag::O_NOFOLLOW);
|
||||||
|
|
||||||
|
// convert open flags
|
||||||
|
nix_all_oflags.insert(host_impl::nix_from_oflags(oflags));
|
||||||
|
|
||||||
|
// convert file descriptor flags
|
||||||
|
nix_all_oflags.insert(host_impl::nix_from_fdflags(fs_flags));
|
||||||
|
|
||||||
|
// Call openat. Use mode 0o666 so that we follow whatever the user's
|
||||||
|
// umask is, but don't set the executable flag, because it isn't yet
|
||||||
|
// meaningful for WASI programs to create executable files.
|
||||||
|
|
||||||
|
log::debug!("path_open resolved = {:?}", resolved);
|
||||||
|
log::debug!("path_open oflags = {:?}", nix_all_oflags);
|
||||||
|
|
||||||
|
let new_fd = match openat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
resolved.path(),
|
||||||
|
nix_all_oflags,
|
||||||
|
Mode::from_bits_truncate(0o666),
|
||||||
|
) {
|
||||||
|
Ok(fd) => fd,
|
||||||
|
Err(e) => {
|
||||||
|
match e.as_errno() {
|
||||||
|
// Linux returns ENXIO instead of EOPNOTSUPP when opening a socket
|
||||||
|
Some(Errno::ENXIO) => {
|
||||||
|
if let Ok(stat) = fstatat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
resolved.path(),
|
||||||
|
AtFlags::AT_SYMLINK_NOFOLLOW,
|
||||||
|
) {
|
||||||
|
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) {
|
||||||
|
return Err(Error::ENOTSUP);
|
||||||
|
} else {
|
||||||
|
return Err(Error::ENXIO);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::ENXIO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
|
||||||
|
// on a symlink.
|
||||||
|
Some(Errno::ENOTDIR)
|
||||||
|
if !(nix_all_oflags & (OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY)).is_empty() =>
|
||||||
|
{
|
||||||
|
if let Ok(stat) = fstatat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
resolved.path(),
|
||||||
|
AtFlags::AT_SYMLINK_NOFOLLOW,
|
||||||
|
) {
|
||||||
|
if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFLNK) {
|
||||||
|
return Err(Error::ELOOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::ENOTDIR);
|
||||||
|
}
|
||||||
|
// FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
|
||||||
|
// a symlink.
|
||||||
|
Some(Errno::EMLINK) if !(nix_all_oflags & OFlag::O_NOFOLLOW).is_empty() => {
|
||||||
|
return Err(Error::ELOOP);
|
||||||
|
}
|
||||||
|
Some(e) => return Err(host_impl::errno_from_nix(e)),
|
||||||
|
None => return Err(Error::ENOSYS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("path_open (host) new_fd = {:?}", new_fd);
|
||||||
|
|
||||||
|
// Determine the type of the new file descriptor and which rights contradict with this type
|
||||||
|
Ok(unsafe { File::from_raw_fd(new_fd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
||||||
|
use nix::errno::Errno;
|
||||||
|
let path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
|
||||||
|
// Linux requires that the buffer size is positive, whereas POSIX does not.
|
||||||
|
// Use a fake buffer to store the results if the size is zero.
|
||||||
|
// TODO: instead of using raw libc::readlinkat call here, this should really
|
||||||
|
// be fixed in `nix` crate
|
||||||
|
let fakebuf: &mut [u8] = &mut [0];
|
||||||
|
let buf_len = buf.len();
|
||||||
|
let len = unsafe {
|
||||||
|
libc::readlinkat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
path_cstr.as_ptr() as *const libc::c_char,
|
||||||
|
if buf_len == 0 {
|
||||||
|
fakebuf.as_mut_ptr()
|
||||||
|
} else {
|
||||||
|
buf.as_mut_ptr()
|
||||||
|
} as *mut libc::c_char,
|
||||||
|
if buf_len == 0 { fakebuf.len() } else { buf_len },
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if len < 0 {
|
||||||
|
Err(host_impl::errno_from_nix(Errno::last()))
|
||||||
|
} else {
|
||||||
|
let len = len as usize;
|
||||||
|
Ok(if len < buf_len { len } else { buf_len })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result<wasi::__wasi_filestat_t> {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
let metadata = file.metadata()?;
|
||||||
|
Ok(wasi::__wasi_filestat_t {
|
||||||
|
st_dev: metadata.dev(),
|
||||||
|
st_ino: metadata.ino(),
|
||||||
|
st_nlink: metadata.nlink().try_into()?, // u64 doesn't fit into u32
|
||||||
|
st_size: metadata.len(),
|
||||||
|
st_atim: systemtime_to_timestamp(metadata.accessed()?)?,
|
||||||
|
st_ctim: metadata.ctime().try_into()?, // i64 doesn't fit into u64
|
||||||
|
st_mtim: systemtime_to_timestamp(metadata.modified()?)?,
|
||||||
|
st_filetype: filetype(file, &metadata)?.to_wasi(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filetype(file: &File, metadata: &Metadata) -> Result<FileType> {
|
||||||
|
use nix::sys::socket::{self, SockType};
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
let ftype = metadata.file_type();
|
||||||
|
if ftype.is_file() {
|
||||||
|
Ok(FileType::RegularFile)
|
||||||
|
} else if ftype.is_dir() {
|
||||||
|
Ok(FileType::Directory)
|
||||||
|
} else if ftype.is_symlink() {
|
||||||
|
Ok(FileType::Symlink)
|
||||||
|
} else if ftype.is_char_device() {
|
||||||
|
Ok(FileType::CharacterDevice)
|
||||||
|
} else if ftype.is_block_device() {
|
||||||
|
Ok(FileType::BlockDevice)
|
||||||
|
} else if ftype.is_socket() {
|
||||||
|
match socket::getsockopt(file.as_raw_fd(), socket::sockopt::SockType)
|
||||||
|
.map_err(|err| err.as_errno().unwrap())
|
||||||
|
.map_err(host_impl::errno_from_nix)?
|
||||||
|
{
|
||||||
|
SockType::Datagram => Ok(FileType::SocketDgram),
|
||||||
|
SockType::Stream => Ok(FileType::SocketStream),
|
||||||
|
_ => Ok(FileType::Unknown),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(FileType::Unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_filestat_get(
|
||||||
|
resolved: PathGet,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
) -> Result<wasi::__wasi_filestat_t> {
|
||||||
|
use nix::fcntl::AtFlags;
|
||||||
|
use nix::sys::stat::fstatat;
|
||||||
|
|
||||||
|
let atflags = match dirflags {
|
||||||
|
0 => AtFlags::empty(),
|
||||||
|
_ => AtFlags::AT_SYMLINK_NOFOLLOW,
|
||||||
|
};
|
||||||
|
|
||||||
|
let filestat = fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags)
|
||||||
|
.map_err(|err| host_impl::errno_from_nix(err.as_errno().unwrap()))?;
|
||||||
|
host_impl::filestat_from_nix(filestat)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_filestat_set_times(
|
||||||
|
resolved: PathGet,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
st_atim: wasi::__wasi_timestamp_t,
|
||||||
|
st_mtim: wasi::__wasi_timestamp_t,
|
||||||
|
fst_flags: wasi::__wasi_fstflags_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
use nix::sys::stat::{utimensat, UtimensatFlags};
|
||||||
|
use nix::sys::time::{TimeSpec, TimeValLike};
|
||||||
|
|
||||||
|
// FIXME this should be a part of nix
|
||||||
|
fn timespec_omit() -> TimeSpec {
|
||||||
|
let raw_ts = libc::timespec {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_nsec: utime_omit(),
|
||||||
|
};
|
||||||
|
unsafe { std::mem::transmute(raw_ts) }
|
||||||
|
};
|
||||||
|
|
||||||
|
fn timespec_now() -> TimeSpec {
|
||||||
|
let raw_ts = libc::timespec {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_nsec: utime_now(),
|
||||||
|
};
|
||||||
|
unsafe { std::mem::transmute(raw_ts) }
|
||||||
|
};
|
||||||
|
|
||||||
|
let set_atim = fst_flags & wasi::__WASI_FILESTAT_SET_ATIM != 0;
|
||||||
|
let set_atim_now = fst_flags & wasi::__WASI_FILESTAT_SET_ATIM_NOW != 0;
|
||||||
|
let set_mtim = fst_flags & wasi::__WASI_FILESTAT_SET_MTIM != 0;
|
||||||
|
let set_mtim_now = fst_flags & wasi::__WASI_FILESTAT_SET_MTIM_NOW != 0;
|
||||||
|
|
||||||
|
if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
let atflags = match dirflags {
|
||||||
|
wasi::__WASI_LOOKUP_SYMLINK_FOLLOW => UtimensatFlags::FollowSymlink,
|
||||||
|
_ => UtimensatFlags::NoFollowSymlink,
|
||||||
|
};
|
||||||
|
|
||||||
|
let atim = if set_atim {
|
||||||
|
let st_atim = st_atim.try_into()?;
|
||||||
|
TimeSpec::nanoseconds(st_atim)
|
||||||
|
} else if set_atim_now {
|
||||||
|
timespec_now()
|
||||||
|
} else {
|
||||||
|
timespec_omit()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mtim = if set_mtim {
|
||||||
|
let st_mtim = st_mtim.try_into()?;
|
||||||
|
TimeSpec::nanoseconds(st_mtim)
|
||||||
|
} else if set_mtim_now {
|
||||||
|
timespec_now()
|
||||||
|
} else {
|
||||||
|
timespec_omit()
|
||||||
|
};
|
||||||
|
|
||||||
|
let fd = resolved.dirfd().as_raw_fd().into();
|
||||||
|
utimensat(fd, resolved.path(), &atim, &mtim, atflags).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> {
|
||||||
|
use nix::errno;
|
||||||
|
use nix::libc::{unlinkat, AT_REMOVEDIR};
|
||||||
|
|
||||||
|
let path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
|
||||||
|
// nix doesn't expose unlinkat() yet
|
||||||
|
match unsafe {
|
||||||
|
unlinkat(
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
path_cstr.as_ptr(),
|
||||||
|
AT_REMOVEDIR,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
0 => Ok(()),
|
||||||
|
_ => Err(host_impl::errno_from_nix(errno::Errno::last())),
|
||||||
|
}
|
||||||
|
}
|
||||||
83
wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs
Normal file
83
wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(unused_unsafe)]
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::{wasi, Result};
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
pub(crate) use super::super::linux::fs_helpers::*;
|
||||||
|
} else if #[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))] {
|
||||||
|
pub(crate) use super::super::bsd::fs_helpers::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_open_rights(
|
||||||
|
rights_base: wasi::__wasi_rights_t,
|
||||||
|
rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
oflags: wasi::__wasi_oflags_t,
|
||||||
|
fs_flags: wasi::__wasi_fdflags_t,
|
||||||
|
) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) {
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
|
||||||
|
// which rights are needed on the dirfd?
|
||||||
|
let mut needed_base = wasi::__WASI_RIGHT_PATH_OPEN;
|
||||||
|
let mut needed_inheriting = rights_base | rights_inheriting;
|
||||||
|
|
||||||
|
// convert open flags
|
||||||
|
let oflags = host_impl::nix_from_oflags(oflags);
|
||||||
|
if oflags.contains(OFlag::O_CREAT) {
|
||||||
|
needed_base |= wasi::__WASI_RIGHT_PATH_CREATE_FILE;
|
||||||
|
}
|
||||||
|
if oflags.contains(OFlag::O_TRUNC) {
|
||||||
|
needed_base |= wasi::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert file descriptor flags
|
||||||
|
let fdflags = host_impl::nix_from_fdflags(fs_flags);
|
||||||
|
if fdflags.contains(OFlag::O_DSYNC) {
|
||||||
|
needed_inheriting |= wasi::__WASI_RIGHT_FD_DATASYNC;
|
||||||
|
}
|
||||||
|
if fdflags.intersects(host_impl::O_RSYNC | OFlag::O_SYNC) {
|
||||||
|
needed_inheriting |= wasi::__WASI_RIGHT_FD_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
(needed_base, needed_inheriting)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
|
||||||
|
use nix::fcntl::{self, OFlag};
|
||||||
|
use nix::sys::stat::Mode;
|
||||||
|
use std::os::unix::prelude::{AsRawFd, FromRawFd};
|
||||||
|
|
||||||
|
log::debug!("path_get openat path = {:?}", path);
|
||||||
|
|
||||||
|
fcntl::openat(
|
||||||
|
dirfd.as_raw_fd(),
|
||||||
|
path,
|
||||||
|
OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW,
|
||||||
|
Mode::empty(),
|
||||||
|
)
|
||||||
|
.map(|new_fd| unsafe { File::from_raw_fd(new_fd) })
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result<String> {
|
||||||
|
use nix::fcntl;
|
||||||
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
|
log::debug!("path_get readlinkat path = {:?}", path);
|
||||||
|
|
||||||
|
let readlink_buf = &mut [0u8; libc::PATH_MAX as usize + 1];
|
||||||
|
|
||||||
|
fcntl::readlinkat(dirfd.as_raw_fd(), path, readlink_buf)
|
||||||
|
.map_err(Into::into)
|
||||||
|
.and_then(host_impl::path_from_host)
|
||||||
|
}
|
||||||
226
wasi-common/src/sys/unix/hostcalls_impl/misc.rs
Normal file
226
wasi-common/src/sys/unix/hostcalls_impl/misc.rs
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(unused_unsafe)]
|
||||||
|
use crate::hostcalls_impl::{ClockEventData, FdEventData};
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use nix::libc::{self, c_int};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
|
||||||
|
// convert the supported clocks to the libc types, or return EINVAL
|
||||||
|
let clock_id = match clock_id {
|
||||||
|
wasi::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME,
|
||||||
|
wasi::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC,
|
||||||
|
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID,
|
||||||
|
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID,
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
};
|
||||||
|
|
||||||
|
// no `nix` wrapper for clock_getres, so we do it ourselves
|
||||||
|
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
|
||||||
|
let res = unsafe { libc::clock_getres(clock_id, timespec.as_mut_ptr()) };
|
||||||
|
if res != 0 {
|
||||||
|
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
|
||||||
|
}
|
||||||
|
let timespec = unsafe { timespec.assume_init() };
|
||||||
|
|
||||||
|
// convert to nanoseconds, returning EOVERFLOW in case of overflow;
|
||||||
|
// this is freelancing a bit from the spec but seems like it'll
|
||||||
|
// be an unusual situation to hit
|
||||||
|
(timespec.tv_sec as wasi::__wasi_timestamp_t)
|
||||||
|
.checked_mul(1_000_000_000)
|
||||||
|
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t))
|
||||||
|
.map_or(Err(Error::EOVERFLOW), |resolution| {
|
||||||
|
// a supported clock can never return zero; this case will probably never get hit, but
|
||||||
|
// make sure we follow the spec
|
||||||
|
if resolution == 0 {
|
||||||
|
Err(Error::EINVAL)
|
||||||
|
} else {
|
||||||
|
Ok(resolution)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
|
||||||
|
// convert the supported clocks to the libc types, or return EINVAL
|
||||||
|
let clock_id = match clock_id {
|
||||||
|
wasi::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME,
|
||||||
|
wasi::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC,
|
||||||
|
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID,
|
||||||
|
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID,
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
};
|
||||||
|
|
||||||
|
// no `nix` wrapper for clock_getres, so we do it ourselves
|
||||||
|
let mut timespec = MaybeUninit::<libc::timespec>::uninit();
|
||||||
|
let res = unsafe { libc::clock_gettime(clock_id, timespec.as_mut_ptr()) };
|
||||||
|
if res != 0 {
|
||||||
|
return Err(host_impl::errno_from_nix(nix::errno::Errno::last()));
|
||||||
|
}
|
||||||
|
let timespec = unsafe { timespec.assume_init() };
|
||||||
|
|
||||||
|
// convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit
|
||||||
|
// from the spec but seems like it'll be an unusual situation to hit
|
||||||
|
(timespec.tv_sec as wasi::__wasi_timestamp_t)
|
||||||
|
.checked_mul(1_000_000_000)
|
||||||
|
.and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t))
|
||||||
|
.map_or(Err(Error::EOVERFLOW), Ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_oneoff(
|
||||||
|
timeout: Option<ClockEventData>,
|
||||||
|
fd_events: Vec<FdEventData>,
|
||||||
|
events: &mut Vec<wasi::__wasi_event_t>,
|
||||||
|
) -> Result<()> {
|
||||||
|
use nix::{
|
||||||
|
errno::Errno,
|
||||||
|
poll::{poll, PollFd, PollFlags},
|
||||||
|
};
|
||||||
|
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
||||||
|
|
||||||
|
if fd_events.is_empty() && timeout.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut poll_fds: Vec<_> = fd_events
|
||||||
|
.iter()
|
||||||
|
.map(|event| {
|
||||||
|
let mut flags = PollFlags::empty();
|
||||||
|
match event.r#type {
|
||||||
|
wasi::__WASI_EVENTTYPE_FD_READ => flags.insert(PollFlags::POLLIN),
|
||||||
|
wasi::__WASI_EVENTTYPE_FD_WRITE => flags.insert(PollFlags::POLLOUT),
|
||||||
|
// An event on a file descriptor can currently only be of type FD_READ or FD_WRITE
|
||||||
|
// Nothing else has been defined in the specification, and these are also the only two
|
||||||
|
// events we filtered before. If we get something else here, the code has a serious bug.
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
PollFd::new(event.descriptor.as_raw_fd(), flags)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let poll_timeout = timeout.map_or(-1, |timeout| {
|
||||||
|
let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds
|
||||||
|
delay.try_into().unwrap_or(c_int::max_value())
|
||||||
|
});
|
||||||
|
log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout);
|
||||||
|
|
||||||
|
let ready = loop {
|
||||||
|
match poll(&mut poll_fds, poll_timeout) {
|
||||||
|
Err(_) => {
|
||||||
|
if Errno::last() == Errno::EINTR {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Err(host_impl::errno_from_nix(Errno::last()));
|
||||||
|
}
|
||||||
|
Ok(ready) => break ready as usize,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(if ready == 0 {
|
||||||
|
poll_oneoff_handle_timeout_event(timeout.expect("timeout should not be None"), events)
|
||||||
|
} else {
|
||||||
|
let ready_events = fd_events.into_iter().zip(poll_fds.into_iter()).take(ready);
|
||||||
|
poll_oneoff_handle_fd_event(ready_events, events)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)`
|
||||||
|
nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int);
|
||||||
|
|
||||||
|
fn poll_oneoff_handle_timeout_event(
|
||||||
|
timeout: ClockEventData,
|
||||||
|
events: &mut Vec<wasi::__wasi_event_t>,
|
||||||
|
) {
|
||||||
|
events.push(wasi::__wasi_event_t {
|
||||||
|
userdata: timeout.userdata,
|
||||||
|
r#type: wasi::__WASI_EVENTTYPE_CLOCK,
|
||||||
|
error: wasi::__WASI_ESUCCESS,
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: 0,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_oneoff_handle_fd_event<'a>(
|
||||||
|
ready_events: impl Iterator<Item = (FdEventData<'a>, nix::poll::PollFd)>,
|
||||||
|
events: &mut Vec<wasi::__wasi_event_t>,
|
||||||
|
) -> Result<()> {
|
||||||
|
use nix::poll::PollFlags;
|
||||||
|
use std::{convert::TryInto, os::unix::prelude::AsRawFd};
|
||||||
|
|
||||||
|
for (fd_event, poll_fd) in ready_events {
|
||||||
|
log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event);
|
||||||
|
log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd);
|
||||||
|
|
||||||
|
let revents = match poll_fd.revents() {
|
||||||
|
Some(revents) => revents,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents);
|
||||||
|
|
||||||
|
let mut nbytes = 0;
|
||||||
|
if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ {
|
||||||
|
let _ = unsafe { fionread(fd_event.descriptor.as_raw_fd(), &mut nbytes) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_event = if revents.contains(PollFlags::POLLNVAL) {
|
||||||
|
wasi::__wasi_event_t {
|
||||||
|
userdata: fd_event.userdata,
|
||||||
|
r#type: fd_event.r#type,
|
||||||
|
error: wasi::__WASI_EBADF,
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: 0,
|
||||||
|
flags: wasi::__WASI_EVENT_FD_READWRITE_HANGUP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if revents.contains(PollFlags::POLLERR) {
|
||||||
|
wasi::__wasi_event_t {
|
||||||
|
userdata: fd_event.userdata,
|
||||||
|
r#type: fd_event.r#type,
|
||||||
|
error: wasi::__WASI_EIO,
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: 0,
|
||||||
|
flags: wasi::__WASI_EVENT_FD_READWRITE_HANGUP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if revents.contains(PollFlags::POLLHUP) {
|
||||||
|
wasi::__wasi_event_t {
|
||||||
|
userdata: fd_event.userdata,
|
||||||
|
r#type: fd_event.r#type,
|
||||||
|
error: wasi::__WASI_ESUCCESS,
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: 0,
|
||||||
|
flags: wasi::__WASI_EVENT_FD_READWRITE_HANGUP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) {
|
||||||
|
wasi::__wasi_event_t {
|
||||||
|
userdata: fd_event.userdata,
|
||||||
|
r#type: fd_event.r#type,
|
||||||
|
error: wasi::__WASI_ESUCCESS,
|
||||||
|
u: wasi::__wasi_event_u {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: nbytes.try_into()?,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
events.push(output_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
8
wasi-common/src/sys/unix/hostcalls_impl/mod.rs
Normal file
8
wasi-common/src/sys/unix/hostcalls_impl/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//! Unix-specific hostcalls that implement
|
||||||
|
//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md).
|
||||||
|
mod fs;
|
||||||
|
pub(crate) mod fs_helpers;
|
||||||
|
mod misc;
|
||||||
|
|
||||||
|
pub(crate) use self::fs::*;
|
||||||
|
pub(crate) use self::misc::*;
|
||||||
165
wasi-common/src/sys/unix/linux/hostcalls_impl.rs
Normal file
165
wasi-common/src/sys/unix/linux/hostcalls_impl.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
use super::super::dir::{Dir, Entry, SeekLoc};
|
||||||
|
use super::osfile::OsFile;
|
||||||
|
use crate::hostcalls_impl::{Dirent, PathGet};
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::sys::unix::str_to_cstring;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use log::trace;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
|
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
|
||||||
|
use nix::errno;
|
||||||
|
use nix::libc::unlinkat;
|
||||||
|
|
||||||
|
let path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
|
||||||
|
// nix doesn't expose unlinkat() yet
|
||||||
|
let res = unsafe { unlinkat(resolved.dirfd().as_raw_fd(), path_cstr.as_ptr(), 0) };
|
||||||
|
if res == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(host_impl::errno_from_nix(errno::Errno::last()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
||||||
|
use nix::{errno::Errno, libc::symlinkat};
|
||||||
|
|
||||||
|
let old_path_cstr = str_to_cstring(old_path)?;
|
||||||
|
let new_path_cstr = str_to_cstring(resolved.path())?;
|
||||||
|
|
||||||
|
log::debug!("path_symlink old_path = {:?}", old_path);
|
||||||
|
log::debug!("path_symlink resolved = {:?}", resolved);
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
symlinkat(
|
||||||
|
old_path_cstr.as_ptr(),
|
||||||
|
resolved.dirfd().as_raw_fd(),
|
||||||
|
new_path_cstr.as_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
Err(host_impl::errno_from_nix(Errno::last()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
||||||
|
use nix::libc::renameat;
|
||||||
|
let old_path_cstr = str_to_cstring(resolved_old.path())?;
|
||||||
|
let new_path_cstr = str_to_cstring(resolved_new.path())?;
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
renameat(
|
||||||
|
resolved_old.dirfd().as_raw_fd(),
|
||||||
|
old_path_cstr.as_ptr(),
|
||||||
|
resolved_new.dirfd().as_raw_fd(),
|
||||||
|
new_path_cstr.as_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
Err(host_impl::errno_from_nix(nix::errno::Errno::last()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_readdir_impl(
|
||||||
|
fd: &File,
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<impl Iterator<Item = Result<Dirent>>> {
|
||||||
|
// We need to duplicate the fd, because `opendir(3)`:
|
||||||
|
// After a successful call to fdopendir(), fd is used internally by the implementation,
|
||||||
|
// and should not otherwise be used by the application.
|
||||||
|
// `opendir(3p)` also says that it's undefined behavior to
|
||||||
|
// modify the state of the fd in a different way than by accessing DIR*.
|
||||||
|
//
|
||||||
|
// Still, rewinddir will be needed because the two file descriptors
|
||||||
|
// share progress. But we can safely execute closedir now.
|
||||||
|
let fd = fd.try_clone()?;
|
||||||
|
let mut dir = Dir::from(fd)?;
|
||||||
|
|
||||||
|
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
|
||||||
|
// new items may not be returned to the caller.
|
||||||
|
//
|
||||||
|
// According to `opendir(3p)`:
|
||||||
|
// If a file is removed from or added to the directory after the most recent call
|
||||||
|
// to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry
|
||||||
|
// for that file is unspecified.
|
||||||
|
if cookie == wasi::__WASI_DIRCOOKIE_START {
|
||||||
|
trace!(" | fd_readdir: doing rewinddir");
|
||||||
|
dir.rewind();
|
||||||
|
} else {
|
||||||
|
trace!(" | fd_readdir: doing seekdir to {}", cookie);
|
||||||
|
let loc = unsafe { SeekLoc::from_raw(cookie as i64) };
|
||||||
|
dir.seek(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(dir.into_iter().map(|entry| {
|
||||||
|
let entry: Entry = entry?;
|
||||||
|
Ok(Dirent {
|
||||||
|
name: entry // TODO can we reuse path_from_host for CStr?
|
||||||
|
.file_name()
|
||||||
|
.to_str()?
|
||||||
|
.to_owned(),
|
||||||
|
ino: entry.ino(),
|
||||||
|
ftype: entry.file_type().into(),
|
||||||
|
cookie: entry.seek_loc().to_raw().try_into()?,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should actually be common code with Windows,
|
||||||
|
// but there's BSD stuff remaining
|
||||||
|
pub(crate) fn fd_readdir(
|
||||||
|
os_file: &mut OsFile,
|
||||||
|
mut host_buf: &mut [u8],
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<usize> {
|
||||||
|
let iter = fd_readdir_impl(os_file, cookie)?;
|
||||||
|
let mut used = 0;
|
||||||
|
for dirent in iter {
|
||||||
|
let dirent_raw = dirent?.to_wasi_raw()?;
|
||||||
|
let offset = dirent_raw.len();
|
||||||
|
if host_buf.len() < offset {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
||||||
|
used += offset;
|
||||||
|
host_buf = &mut host_buf[offset..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(" | *buf_used={:?}", used);
|
||||||
|
Ok(used)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_advise(
|
||||||
|
file: &File,
|
||||||
|
advice: wasi::__wasi_advice_t,
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
len: wasi::__wasi_filesize_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
{
|
||||||
|
use nix::fcntl::{posix_fadvise, PosixFadviseAdvice};
|
||||||
|
|
||||||
|
let offset = offset.try_into()?;
|
||||||
|
let len = len.try_into()?;
|
||||||
|
let host_advice = match advice {
|
||||||
|
wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::POSIX_FADV_DONTNEED,
|
||||||
|
wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::POSIX_FADV_SEQUENTIAL,
|
||||||
|
wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::POSIX_FADV_WILLNEED,
|
||||||
|
wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::POSIX_FADV_NOREUSE,
|
||||||
|
wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::POSIX_FADV_RANDOM,
|
||||||
|
wasi::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::POSIX_FADV_NORMAL,
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
};
|
||||||
|
|
||||||
|
posix_fadvise(file.as_raw_fd(), offset, len, host_advice)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
42
wasi-common/src/sys/unix/linux/mod.rs
Normal file
42
wasi-common/src/sys/unix/linux/mod.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
pub(crate) mod hostcalls_impl;
|
||||||
|
pub(crate) mod osfile;
|
||||||
|
|
||||||
|
pub(crate) mod fdentry_impl {
|
||||||
|
use crate::{sys::host_impl, Result};
|
||||||
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
|
||||||
|
pub(crate) unsafe fn isatty(fd: &impl AsRawFd) -> Result<bool> {
|
||||||
|
use nix::errno::Errno;
|
||||||
|
|
||||||
|
let res = libc::isatty(fd.as_raw_fd());
|
||||||
|
if res == 1 {
|
||||||
|
// isatty() returns 1 if fd is an open file descriptor referring to a terminal...
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// ... otherwise 0 is returned, and errno is set to indicate the error.
|
||||||
|
match Errno::last() {
|
||||||
|
// While POSIX specifies ENOTTY if the passed
|
||||||
|
// fd is *not* a tty, on Linux, some implementations
|
||||||
|
// may return EINVAL instead.
|
||||||
|
//
|
||||||
|
// https://linux.die.net/man/3/isatty
|
||||||
|
Errno::ENOTTY | Errno::EINVAL => Ok(false),
|
||||||
|
x => Err(host_impl::errno_from_nix(x)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod host_impl {
|
||||||
|
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod fs_helpers {
|
||||||
|
pub(crate) fn utime_now() -> libc::c_long {
|
||||||
|
libc::UTIME_NOW
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn utime_omit() -> libc::c_long {
|
||||||
|
libc::UTIME_OMIT
|
||||||
|
}
|
||||||
|
}
|
||||||
32
wasi-common/src/sys/unix/linux/osfile.rs
Normal file
32
wasi-common/src/sys/unix/linux/osfile.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct OsFile(fs::File);
|
||||||
|
|
||||||
|
impl From<fs::File> for OsFile {
|
||||||
|
fn from(file: fs::File) -> Self {
|
||||||
|
Self(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for OsFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.0.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for OsFile {
|
||||||
|
type Target = fs::File;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for OsFile {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
38
wasi-common/src/sys/unix/mod.rs
Normal file
38
wasi-common/src/sys/unix/mod.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
pub(crate) mod fdentry_impl;
|
||||||
|
pub(crate) mod host_impl;
|
||||||
|
pub(crate) mod hostcalls_impl;
|
||||||
|
|
||||||
|
mod dir;
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
))]
|
||||||
|
mod bsd;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux;
|
||||||
|
|
||||||
|
use crate::{Error, Result};
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub(crate) fn dev_null() -> Result<File> {
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open("/dev/null")
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn str_to_cstring(s: &str) -> Result<CString> {
|
||||||
|
CString::new(s.as_bytes()).map_err(|_| Error::EILSEQ)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||||
|
File::open(path).map_err(Into::into)
|
||||||
|
}
|
||||||
129
wasi-common/src/sys/windows/fdentry_impl.rs
Normal file
129
wasi-common/src/sys/windows/fdentry_impl.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use crate::fdentry::Descriptor;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct OsFile(File);
|
||||||
|
|
||||||
|
impl From<File> for OsFile {
|
||||||
|
fn from(file: File) -> Self {
|
||||||
|
Self(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawHandle for OsFile {
|
||||||
|
fn as_raw_handle(&self) -> RawHandle {
|
||||||
|
self.0.as_raw_handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for OsFile {
|
||||||
|
type Target = File;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for OsFile {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawHandle for Descriptor {
|
||||||
|
fn as_raw_handle(&self) -> RawHandle {
|
||||||
|
match self {
|
||||||
|
Self::OsFile(file) => file.as_raw_handle(),
|
||||||
|
Self::Stdin => io::stdin().as_raw_handle(),
|
||||||
|
Self::Stdout => io::stdout().as_raw_handle(),
|
||||||
|
Self::Stderr => io::stderr().as_raw_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is unsafe because it operates on a raw file handle.
|
||||||
|
pub(crate) unsafe fn determine_type_and_access_rights<Handle: AsRawHandle>(
|
||||||
|
handle: &Handle,
|
||||||
|
) -> Result<(
|
||||||
|
wasi::__wasi_filetype_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
)> {
|
||||||
|
use winx::file::{get_file_access_mode, AccessMode};
|
||||||
|
|
||||||
|
let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(handle)?;
|
||||||
|
|
||||||
|
match file_type {
|
||||||
|
wasi::__WASI_FILETYPE_DIRECTORY | wasi::__WASI_FILETYPE_REGULAR_FILE => {
|
||||||
|
let mode = get_file_access_mode(handle.as_raw_handle())?;
|
||||||
|
if mode.contains(AccessMode::FILE_GENERIC_READ) {
|
||||||
|
rights_base |= wasi::__WASI_RIGHT_FD_READ;
|
||||||
|
}
|
||||||
|
if mode.contains(AccessMode::FILE_GENERIC_WRITE) {
|
||||||
|
rights_base |= wasi::__WASI_RIGHT_FD_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO: is there a way around this? On windows, it seems
|
||||||
|
// we cannot check access rights for anything but dirs and regular files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((file_type, rights_base, rights_inheriting))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is unsafe because it operates on a raw file handle.
|
||||||
|
pub(crate) unsafe fn determine_type_rights<Handle: AsRawHandle>(
|
||||||
|
handle: &Handle,
|
||||||
|
) -> Result<(
|
||||||
|
wasi::__wasi_filetype_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
wasi::__wasi_rights_t,
|
||||||
|
)> {
|
||||||
|
let (file_type, rights_base, rights_inheriting) = {
|
||||||
|
let file_type = winx::file::get_file_type(handle.as_raw_handle())?;
|
||||||
|
if file_type.is_char() {
|
||||||
|
// character file: LPT device or console
|
||||||
|
// TODO: rule out LPT device
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_CHARACTER_DEVICE,
|
||||||
|
wasi::RIGHTS_TTY_BASE,
|
||||||
|
wasi::RIGHTS_TTY_BASE,
|
||||||
|
)
|
||||||
|
} else if file_type.is_disk() {
|
||||||
|
// disk file: file, dir or disk device
|
||||||
|
let file = std::mem::ManuallyDrop::new(File::from_raw_handle(handle.as_raw_handle()));
|
||||||
|
let meta = file.metadata().map_err(|_| Error::EINVAL)?;
|
||||||
|
if meta.is_dir() {
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_DIRECTORY,
|
||||||
|
wasi::RIGHTS_DIRECTORY_BASE,
|
||||||
|
wasi::RIGHTS_DIRECTORY_INHERITING,
|
||||||
|
)
|
||||||
|
} else if meta.is_file() {
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_REGULAR_FILE,
|
||||||
|
wasi::RIGHTS_REGULAR_FILE_BASE,
|
||||||
|
wasi::RIGHTS_REGULAR_FILE_INHERITING,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
} else if file_type.is_pipe() {
|
||||||
|
// pipe object: socket, named pipe or anonymous pipe
|
||||||
|
// TODO: what about pipes, etc?
|
||||||
|
(
|
||||||
|
wasi::__WASI_FILETYPE_SOCKET_STREAM,
|
||||||
|
wasi::RIGHTS_SOCKET_BASE,
|
||||||
|
wasi::RIGHTS_SOCKET_INHERITING,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Err(Error::EINVAL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((file_type, rights_base, rights_inheriting))
|
||||||
|
}
|
||||||
108
wasi-common/src/sys/windows/host_impl.rs
Normal file
108
wasi-common/src/sys/windows/host_impl.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
//! WASI host types specific to Windows host.
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(unused)]
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
|
use winx::file::{AccessMode, Attributes, CreationDisposition, Flags};
|
||||||
|
|
||||||
|
pub(crate) fn errno_from_win(error: winx::winerror::WinError) -> wasi::__wasi_errno_t {
|
||||||
|
// TODO: implement error mapping between Windows and WASI
|
||||||
|
use winx::winerror::WinError::*;
|
||||||
|
match error {
|
||||||
|
ERROR_SUCCESS => wasi::__WASI_ESUCCESS,
|
||||||
|
ERROR_BAD_ENVIRONMENT => wasi::__WASI_E2BIG,
|
||||||
|
ERROR_FILE_NOT_FOUND => wasi::__WASI_ENOENT,
|
||||||
|
ERROR_PATH_NOT_FOUND => wasi::__WASI_ENOENT,
|
||||||
|
ERROR_TOO_MANY_OPEN_FILES => wasi::__WASI_ENFILE,
|
||||||
|
ERROR_ACCESS_DENIED => wasi::__WASI_EACCES,
|
||||||
|
ERROR_SHARING_VIOLATION => wasi::__WASI_EACCES,
|
||||||
|
ERROR_PRIVILEGE_NOT_HELD => wasi::__WASI_ENOTCAPABLE, // TODO is this the correct mapping?
|
||||||
|
ERROR_INVALID_HANDLE => wasi::__WASI_EBADF,
|
||||||
|
ERROR_INVALID_NAME => wasi::__WASI_ENOENT,
|
||||||
|
ERROR_NOT_ENOUGH_MEMORY => wasi::__WASI_ENOMEM,
|
||||||
|
ERROR_OUTOFMEMORY => wasi::__WASI_ENOMEM,
|
||||||
|
ERROR_DIR_NOT_EMPTY => wasi::__WASI_ENOTEMPTY,
|
||||||
|
ERROR_NOT_READY => wasi::__WASI_EBUSY,
|
||||||
|
ERROR_BUSY => wasi::__WASI_EBUSY,
|
||||||
|
ERROR_NOT_SUPPORTED => wasi::__WASI_ENOTSUP,
|
||||||
|
ERROR_FILE_EXISTS => wasi::__WASI_EEXIST,
|
||||||
|
ERROR_BROKEN_PIPE => wasi::__WASI_EPIPE,
|
||||||
|
ERROR_BUFFER_OVERFLOW => wasi::__WASI_ENAMETOOLONG,
|
||||||
|
ERROR_NOT_A_REPARSE_POINT => wasi::__WASI_EINVAL,
|
||||||
|
ERROR_NEGATIVE_SEEK => wasi::__WASI_EINVAL,
|
||||||
|
ERROR_DIRECTORY => wasi::__WASI_ENOTDIR,
|
||||||
|
ERROR_ALREADY_EXISTS => wasi::__WASI_EEXIST,
|
||||||
|
_ => wasi::__WASI_ENOTSUP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fdflags_from_win(mode: AccessMode) -> wasi::__wasi_fdflags_t {
|
||||||
|
let mut fdflags = 0;
|
||||||
|
// TODO verify this!
|
||||||
|
if mode.contains(AccessMode::FILE_APPEND_DATA) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_APPEND;
|
||||||
|
}
|
||||||
|
if mode.contains(AccessMode::SYNCHRONIZE) {
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_DSYNC;
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_RSYNC;
|
||||||
|
fdflags |= wasi::__WASI_FDFLAG_SYNC;
|
||||||
|
}
|
||||||
|
// The NONBLOCK equivalent is FILE_FLAG_OVERLAPPED
|
||||||
|
// but it seems winapi doesn't provide a mechanism
|
||||||
|
// for checking whether the handle supports async IO.
|
||||||
|
// On the contrary, I've found some dicsussion online
|
||||||
|
// which suggests that on Windows all handles should
|
||||||
|
// generally be assumed to be opened with async support
|
||||||
|
// and then the program should fallback should that **not**
|
||||||
|
// be the case at the time of the operation.
|
||||||
|
// TODO: this requires further investigation
|
||||||
|
fdflags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn win_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> (AccessMode, Flags) {
|
||||||
|
let mut access_mode = AccessMode::empty();
|
||||||
|
let mut flags = Flags::empty();
|
||||||
|
|
||||||
|
// TODO verify this!
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_NONBLOCK != 0 {
|
||||||
|
flags.insert(Flags::FILE_FLAG_OVERLAPPED);
|
||||||
|
}
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_APPEND != 0 {
|
||||||
|
access_mode.insert(AccessMode::FILE_APPEND_DATA);
|
||||||
|
}
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0
|
||||||
|
|| fdflags & wasi::__WASI_FDFLAG_RSYNC != 0
|
||||||
|
|| fdflags & wasi::__WASI_FDFLAG_SYNC != 0
|
||||||
|
{
|
||||||
|
access_mode.insert(AccessMode::SYNCHRONIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
(access_mode, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn win_from_oflags(oflags: wasi::__wasi_oflags_t) -> CreationDisposition {
|
||||||
|
if oflags & wasi::__WASI_O_CREAT != 0 {
|
||||||
|
if oflags & wasi::__WASI_O_EXCL != 0 {
|
||||||
|
CreationDisposition::CREATE_NEW
|
||||||
|
} else {
|
||||||
|
CreationDisposition::CREATE_ALWAYS
|
||||||
|
}
|
||||||
|
} else if oflags & wasi::__WASI_O_TRUNC != 0 {
|
||||||
|
CreationDisposition::TRUNCATE_EXISTING
|
||||||
|
} else {
|
||||||
|
CreationDisposition::OPEN_EXISTING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates owned WASI path from OS string.
|
||||||
|
///
|
||||||
|
/// NB WASI spec requires OS string to be valid UTF-8. Otherwise,
|
||||||
|
/// `__WASI_EILSEQ` error is returned.
|
||||||
|
pub(crate) fn path_from_host<S: AsRef<OsStr>>(s: S) -> Result<String> {
|
||||||
|
let vec: Vec<u16> = s.as_ref().encode_wide().collect();
|
||||||
|
String::from_utf16(&vec).map_err(|_| Error::EILSEQ)
|
||||||
|
}
|
||||||
550
wasi-common/src/sys/windows/hostcalls_impl/fs.rs
Normal file
550
wasi-common/src/sys/windows/hostcalls_impl/fs.rs
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(unused)]
|
||||||
|
use super::fs_helpers::*;
|
||||||
|
use crate::ctx::WasiCtx;
|
||||||
|
use crate::fdentry::FdEntry;
|
||||||
|
use crate::helpers::systemtime_to_timestamp;
|
||||||
|
use crate::hostcalls_impl::{fd_filestat_set_times_impl, Dirent, FileType, PathGet};
|
||||||
|
use crate::sys::fdentry_impl::{determine_type_rights, OsFile};
|
||||||
|
use crate::sys::host_impl::{self, path_from_host};
|
||||||
|
use crate::sys::hostcalls_impl::fs_helpers::PathGetExt;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use log::{debug, trace};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fs::{File, Metadata, OpenOptions};
|
||||||
|
use std::io::{self, Seek, SeekFrom};
|
||||||
|
use std::os::windows::fs::{FileExt, OpenOptionsExt};
|
||||||
|
use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use winx::file::{AccessMode, Flags};
|
||||||
|
|
||||||
|
fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
|
||||||
|
// get current cursor position
|
||||||
|
let cur_pos = file.seek(SeekFrom::Current(0))?;
|
||||||
|
// perform a seek read by a specified offset
|
||||||
|
let nread = file.seek_read(buf, offset)?;
|
||||||
|
// rewind the cursor back to the original position
|
||||||
|
file.seek(SeekFrom::Start(cur_pos))?;
|
||||||
|
Ok(nread)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_at(mut file: &File, buf: &[u8], offset: u64) -> io::Result<usize> {
|
||||||
|
// get current cursor position
|
||||||
|
let cur_pos = file.seek(SeekFrom::Current(0))?;
|
||||||
|
// perform a seek write by a specified offset
|
||||||
|
let nwritten = file.seek_write(buf, offset)?;
|
||||||
|
// rewind the cursor back to the original position
|
||||||
|
file.seek(SeekFrom::Start(cur_pos))?;
|
||||||
|
Ok(nwritten)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO refactor common code with unix
|
||||||
|
pub(crate) fn fd_pread(
|
||||||
|
file: &File,
|
||||||
|
buf: &mut [u8],
|
||||||
|
offset: wasi::__wasi_filesize_t,
|
||||||
|
) -> Result<usize> {
|
||||||
|
read_at(file, buf, offset).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO refactor common code with unix
|
||||||
|
pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t) -> Result<usize> {
|
||||||
|
write_at(file, buf, offset).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
|
||||||
|
use winx::file::AccessMode;
|
||||||
|
unsafe { winx::file::get_file_access_mode(fd.as_raw_handle()) }
|
||||||
|
.map(host_impl::fdflags_from_win)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
|
||||||
|
unimplemented!("fd_fdstat_set_flags")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_advise(
|
||||||
|
_file: &File,
|
||||||
|
advice: wasi::__wasi_advice_t,
|
||||||
|
_offset: wasi::__wasi_filesize_t,
|
||||||
|
_len: wasi::__wasi_filesize_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
match advice {
|
||||||
|
wasi::__WASI_ADVICE_DONTNEED
|
||||||
|
| wasi::__WASI_ADVICE_SEQUENTIAL
|
||||||
|
| wasi::__WASI_ADVICE_WILLNEED
|
||||||
|
| wasi::__WASI_ADVICE_NOREUSE
|
||||||
|
| wasi::__WASI_ADVICE_RANDOM
|
||||||
|
| wasi::__WASI_ADVICE_NORMAL => {}
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_create_directory(resolved: PathGet) -> Result<()> {
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
std::fs::create_dir(&path).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_link(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
||||||
|
unimplemented!("path_link")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_open(
|
||||||
|
resolved: PathGet,
|
||||||
|
read: bool,
|
||||||
|
write: bool,
|
||||||
|
oflags: wasi::__wasi_oflags_t,
|
||||||
|
fdflags: wasi::__wasi_fdflags_t,
|
||||||
|
) -> Result<File> {
|
||||||
|
use winx::file::{AccessMode, CreationDisposition, Flags};
|
||||||
|
|
||||||
|
let mut access_mode = AccessMode::READ_CONTROL;
|
||||||
|
if read {
|
||||||
|
access_mode.insert(AccessMode::FILE_GENERIC_READ);
|
||||||
|
}
|
||||||
|
if write {
|
||||||
|
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;
|
||||||
|
|
||||||
|
// convert open flags
|
||||||
|
let mut opts = OpenOptions::new();
|
||||||
|
match host_impl::win_from_oflags(oflags) {
|
||||||
|
CreationDisposition::CREATE_ALWAYS => {
|
||||||
|
opts.create(true).append(true);
|
||||||
|
}
|
||||||
|
CreationDisposition::CREATE_NEW => {
|
||||||
|
opts.create_new(true).write(true);
|
||||||
|
}
|
||||||
|
CreationDisposition::TRUNCATE_EXISTING => {
|
||||||
|
opts.truncate(true);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert file descriptor flags
|
||||||
|
let (add_access_mode, add_flags) = host_impl::win_from_fdflags(fdflags);
|
||||||
|
access_mode.insert(add_access_mode);
|
||||||
|
flags.insert(add_flags);
|
||||||
|
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
|
||||||
|
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
|
||||||
|
Ok(file_type) => {
|
||||||
|
// check if we are trying to open a symlink
|
||||||
|
if file_type.is_symlink() {
|
||||||
|
return Err(Error::ELOOP);
|
||||||
|
}
|
||||||
|
// check if we are trying to open a file as a dir
|
||||||
|
if file_type.is_file() && oflags & wasi::__WASI_O_DIRECTORY != 0 {
|
||||||
|
return Err(Error::ENOTDIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match e.raw_os_error() {
|
||||||
|
Some(e) => {
|
||||||
|
use winx::winerror::WinError;
|
||||||
|
log::debug!("path_open at symlink_metadata error code={:?}", e);
|
||||||
|
let e = WinError::from_u32(e as u32);
|
||||||
|
|
||||||
|
if e != WinError::ERROR_FILE_NOT_FOUND {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
// file not found, let it proceed to actually
|
||||||
|
// trying to open it
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
return Err(Error::EIO);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.access_mode(access_mode.bits())
|
||||||
|
.custom_flags(flags.bits())
|
||||||
|
.open(&path)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dirent_from_path<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
name: &str,
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<Dirent> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
trace!("dirent_from_path: opening {}", path.to_string_lossy());
|
||||||
|
|
||||||
|
// To open a directory on Windows, FILE_FLAG_BACKUP_SEMANTICS flag must be used
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
|
||||||
|
.read(true)
|
||||||
|
.open(path)?;
|
||||||
|
let ty = file.metadata()?.file_type();
|
||||||
|
Ok(Dirent {
|
||||||
|
ftype: filetype_from_std(&ty),
|
||||||
|
name: name.to_owned(),
|
||||||
|
cookie,
|
||||||
|
ino: file_serial_no(&file)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Windows there is apparently no support for seeking the directory stream in the OS.
|
||||||
|
// cf. https://github.com/WebAssembly/WASI/issues/61
|
||||||
|
//
|
||||||
|
// The implementation here may perform in O(n^2) if the host buffer is O(1)
|
||||||
|
// and the number of directory entries is O(n).
|
||||||
|
// TODO: Add a heuristic optimization to achieve O(n) time in the most common case
|
||||||
|
// where fd_readdir is resumed where it previously finished
|
||||||
|
//
|
||||||
|
// Correctness of this approach relies upon one assumption: that the order of entries
|
||||||
|
// returned by `FindNextFileW` is stable, i.e. doesn't change if the directory
|
||||||
|
// contents stay the same. This invariant is crucial to be able to implement
|
||||||
|
// any kind of seeking whatsoever without having to read the whole directory at once
|
||||||
|
// and then return the data from cache. (which leaks memory)
|
||||||
|
//
|
||||||
|
// The MSDN documentation explicitly says that the order in which the search returns the files
|
||||||
|
// is not guaranteed, and is dependent on the file system.
|
||||||
|
// cf. https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew
|
||||||
|
//
|
||||||
|
// This stackoverflow post suggests that `FindNextFileW` is indeed stable and that
|
||||||
|
// the order of directory entries depends **only** on the filesystem used, but the
|
||||||
|
// MSDN documentation is not clear about this.
|
||||||
|
// cf. https://stackoverflow.com/questions/47380739/is-findfirstfile-and-findnextfile-order-random-even-for-dvd
|
||||||
|
//
|
||||||
|
// Implementation details:
|
||||||
|
// Cookies for the directory entries start from 1. (0 is reserved by wasi::__WASI_DIRCOOKIE_START)
|
||||||
|
// . gets cookie = 1
|
||||||
|
// .. gets cookie = 2
|
||||||
|
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
|
||||||
|
pub(crate) fn fd_readdir_impl(
|
||||||
|
fd: &File,
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<impl Iterator<Item = Result<Dirent>>> {
|
||||||
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
|
let cookie = cookie.try_into()?;
|
||||||
|
let path = get_file_path(fd)?;
|
||||||
|
// std::fs::ReadDir doesn't return . and .., so we need to emulate it
|
||||||
|
let path = Path::new(&path);
|
||||||
|
// The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too
|
||||||
|
let parent = path.parent().unwrap_or(path);
|
||||||
|
let dot = dirent_from_path(path, ".", 1)?;
|
||||||
|
let dotdot = dirent_from_path(parent, "..", 2)?;
|
||||||
|
|
||||||
|
trace!(" | fd_readdir impl: executing std::fs::ReadDir");
|
||||||
|
let iter = path.read_dir()?.zip(3..).map(|(dir, no)| {
|
||||||
|
let dir: std::fs::DirEntry = dir?;
|
||||||
|
|
||||||
|
Ok(Dirent {
|
||||||
|
name: path_from_host(dir.file_name())?,
|
||||||
|
ftype: filetype_from_std(&dir.file_type()?),
|
||||||
|
ino: File::open(dir.path()).and_then(|f| file_serial_no(&f))?,
|
||||||
|
cookie: no,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// into_iter for arrays is broken and returns references instead of values,
|
||||||
|
// so we need to use vec![...] and do heap allocation
|
||||||
|
// See https://github.com/rust-lang/rust/issues/25725
|
||||||
|
let iter = vec![dot, dotdot].into_iter().map(Ok).chain(iter);
|
||||||
|
|
||||||
|
// Emulate seekdir(). This may give O(n^2) complexity if used with a
|
||||||
|
// small host_buf, but this is difficult to implement efficiently.
|
||||||
|
//
|
||||||
|
// See https://github.com/WebAssembly/WASI/issues/61 for more details.
|
||||||
|
Ok(iter.skip(cookie))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should actually be common code with Linux
|
||||||
|
pub(crate) fn fd_readdir(
|
||||||
|
os_file: &mut OsFile,
|
||||||
|
mut host_buf: &mut [u8],
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<usize> {
|
||||||
|
let iter = fd_readdir_impl(os_file, cookie)?;
|
||||||
|
let mut used = 0;
|
||||||
|
for dirent in iter {
|
||||||
|
let dirent_raw = dirent?.to_wasi_raw()?;
|
||||||
|
let offset = dirent_raw.len();
|
||||||
|
if host_buf.len() < offset {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
||||||
|
used += offset;
|
||||||
|
host_buf = &mut host_buf[offset..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(" | *buf_used={:?}", used);
|
||||||
|
Ok(used)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
|
||||||
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
let target_path = path.read_link()?;
|
||||||
|
|
||||||
|
// since on Windows we are effectively emulating 'at' syscalls
|
||||||
|
// we need to strip the prefix from the absolute path
|
||||||
|
// as otherwise we will error out since WASI is not capable
|
||||||
|
// of dealing with absolute paths
|
||||||
|
let dir_path = get_file_path(resolved.dirfd())?;
|
||||||
|
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
||||||
|
let target_path = target_path
|
||||||
|
.strip_prefix(dir_path)
|
||||||
|
.map_err(|_| Error::ENOTCAPABLE)
|
||||||
|
.and_then(|path| path.to_str().map(String::from).ok_or(Error::EILSEQ))?;
|
||||||
|
|
||||||
|
if buf.len() > 0 {
|
||||||
|
let mut chars = target_path.chars();
|
||||||
|
let mut nread = 0usize;
|
||||||
|
|
||||||
|
for i in 0..buf.len() {
|
||||||
|
match chars.next() {
|
||||||
|
Some(ch) => {
|
||||||
|
buf[i] = ch as u8;
|
||||||
|
nread += 1;
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(nread)
|
||||||
|
} else {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result<Option<PathBuf>> {
|
||||||
|
if resolved.path().ends_with('/') {
|
||||||
|
let suffix = resolved.path().trim_end_matches('/');
|
||||||
|
concatenate(resolved.dirfd(), Path::new(suffix)).map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> {
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
let old_path = resolved_old.concatenate()?;
|
||||||
|
let new_path = resolved_new.concatenate()?;
|
||||||
|
|
||||||
|
// First sanity check: check we're not trying to rename dir to file or vice versa.
|
||||||
|
// NB on Windows, the former is actually permitted [std::fs::rename].
|
||||||
|
//
|
||||||
|
// [std::fs::rename]: https://doc.rust-lang.org/std/fs/fn.rename.html
|
||||||
|
if old_path.is_dir() && new_path.is_file() {
|
||||||
|
return Err(Error::ENOTDIR);
|
||||||
|
}
|
||||||
|
// Second sanity check: check we're not trying to rename a file into a path
|
||||||
|
// ending in a trailing slash.
|
||||||
|
if old_path.is_file() && resolved_new.path().ends_with('/') {
|
||||||
|
return Err(Error::ENOTDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle symlinks
|
||||||
|
|
||||||
|
fs::rename(&old_path, &new_path).or_else(|e| match e.raw_os_error() {
|
||||||
|
Some(e) => {
|
||||||
|
use winx::winerror::WinError;
|
||||||
|
|
||||||
|
log::debug!("path_rename at rename error code={:?}", e);
|
||||||
|
match WinError::from_u32(e as u32) {
|
||||||
|
WinError::ERROR_ACCESS_DENIED => {
|
||||||
|
// So most likely dealing with new_path == dir.
|
||||||
|
// Eliminate case old_path == file first.
|
||||||
|
if old_path.is_file() {
|
||||||
|
Err(Error::EISDIR)
|
||||||
|
} else {
|
||||||
|
// Ok, let's try removing an empty dir at new_path if it exists
|
||||||
|
// and is a nonempty dir.
|
||||||
|
fs::remove_dir(&new_path)
|
||||||
|
.and_then(|()| fs::rename(old_path, new_path))
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WinError::ERROR_INVALID_NAME => {
|
||||||
|
// If source contains trailing slashes, check if we are dealing with
|
||||||
|
// a file instead of a dir, and if so, throw ENOTDIR.
|
||||||
|
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? {
|
||||||
|
if path.is_file() {
|
||||||
|
return Err(Error::ENOTDIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(WinError::ERROR_INVALID_NAME.into())
|
||||||
|
}
|
||||||
|
e => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
Err(Error::EIO)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn num_hardlinks(file: &File, _metadata: &Metadata) -> io::Result<u64> {
|
||||||
|
Ok(winx::file::get_fileinfo(file)?.nNumberOfLinks.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn device_id(file: &File, _metadata: &Metadata) -> io::Result<u64> {
|
||||||
|
Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn file_serial_no(file: &File) -> io::Result<u64> {
|
||||||
|
let info = winx::file::get_fileinfo(file)?;
|
||||||
|
let high = info.nFileIndexHigh;
|
||||||
|
let low = info.nFileIndexLow;
|
||||||
|
let no = ((high as u64) << 32) | (low as u64);
|
||||||
|
Ok(no)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn change_time(file: &File, _metadata: &Metadata) -> io::Result<i64> {
|
||||||
|
winx::file::change_time(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result<wasi::__wasi_filestat_t> {
|
||||||
|
let metadata = file.metadata()?;
|
||||||
|
Ok(wasi::__wasi_filestat_t {
|
||||||
|
st_dev: device_id(file, &metadata)?,
|
||||||
|
st_ino: file_serial_no(file)?,
|
||||||
|
st_nlink: num_hardlinks(file, &metadata)?.try_into()?, // u64 doesn't fit into u32
|
||||||
|
st_size: metadata.len(),
|
||||||
|
st_atim: systemtime_to_timestamp(metadata.accessed()?)?,
|
||||||
|
st_ctim: change_time(file, &metadata)?.try_into()?, // i64 doesn't fit into u64
|
||||||
|
st_mtim: systemtime_to_timestamp(metadata.modified()?)?,
|
||||||
|
st_filetype: filetype_from_std(&metadata.file_type()).to_wasi(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filetype_from_std(ftype: &std::fs::FileType) -> FileType {
|
||||||
|
if ftype.is_file() {
|
||||||
|
FileType::RegularFile
|
||||||
|
} else if ftype.is_dir() {
|
||||||
|
FileType::Directory
|
||||||
|
} else if ftype.is_symlink() {
|
||||||
|
FileType::Symlink
|
||||||
|
} else {
|
||||||
|
FileType::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_filestat_get(
|
||||||
|
resolved: PathGet,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
) -> Result<wasi::__wasi_filestat_t> {
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
let file = File::open(path)?;
|
||||||
|
fd_filestat_get_impl(&file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_filestat_set_times(
|
||||||
|
resolved: PathGet,
|
||||||
|
dirflags: wasi::__wasi_lookupflags_t,
|
||||||
|
st_atim: wasi::__wasi_timestamp_t,
|
||||||
|
mut st_mtim: wasi::__wasi_timestamp_t,
|
||||||
|
fst_flags: wasi::__wasi_fstflags_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
use winx::file::AccessMode;
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits())
|
||||||
|
.open(path)?;
|
||||||
|
fd_filestat_set_times_impl(&file, st_atim, st_mtim, fst_flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> Result<()> {
|
||||||
|
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||||
|
use winx::winerror::WinError;
|
||||||
|
|
||||||
|
let old_path = concatenate(resolved.dirfd(), Path::new(old_path))?;
|
||||||
|
let new_path = resolved.concatenate()?;
|
||||||
|
|
||||||
|
// try creating a file symlink
|
||||||
|
symlink_file(&old_path, &new_path).or_else(|e| {
|
||||||
|
match e.raw_os_error() {
|
||||||
|
Some(e) => {
|
||||||
|
log::debug!("path_symlink at symlink_file error code={:?}", e);
|
||||||
|
match WinError::from_u32(e as u32) {
|
||||||
|
WinError::ERROR_NOT_A_REPARSE_POINT => {
|
||||||
|
// try creating a dir symlink instead
|
||||||
|
symlink_dir(old_path, new_path).map_err(Into::into)
|
||||||
|
}
|
||||||
|
WinError::ERROR_ACCESS_DENIED => {
|
||||||
|
// does the target exist?
|
||||||
|
if new_path.exists() {
|
||||||
|
Err(Error::EEXIST)
|
||||||
|
} else {
|
||||||
|
Err(WinError::ERROR_ACCESS_DENIED.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WinError::ERROR_INVALID_NAME => {
|
||||||
|
// does the target without trailing slashes exist?
|
||||||
|
if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? {
|
||||||
|
if path.exists() {
|
||||||
|
return Err(Error::EEXIST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(WinError::ERROR_INVALID_NAME.into())
|
||||||
|
}
|
||||||
|
e => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
Err(Error::EIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
|
||||||
|
use std::fs;
|
||||||
|
use winx::winerror::WinError;
|
||||||
|
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
let file_type = path
|
||||||
|
.symlink_metadata()
|
||||||
|
.map(|metadata| metadata.file_type())?;
|
||||||
|
|
||||||
|
// check if we're unlinking a symlink
|
||||||
|
// NB this will get cleaned up a lot when [std::os::windows::fs::FileTypeExt]
|
||||||
|
// stabilises
|
||||||
|
//
|
||||||
|
// [std::os::windows::fs::FileTypeExt]: https://doc.rust-lang.org/std/os/windows/fs/trait.FileTypeExt.html
|
||||||
|
if file_type.is_symlink() {
|
||||||
|
fs::remove_file(&path).or_else(|e| {
|
||||||
|
match e.raw_os_error() {
|
||||||
|
Some(e) => {
|
||||||
|
log::debug!("path_unlink_file at symlink_file error code={:?}", e);
|
||||||
|
match WinError::from_u32(e as u32) {
|
||||||
|
WinError::ERROR_ACCESS_DENIED => {
|
||||||
|
// try unlinking a dir symlink instead
|
||||||
|
fs::remove_dir(path).map_err(Into::into)
|
||||||
|
}
|
||||||
|
e => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
Err(Error::EIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if file_type.is_dir() {
|
||||||
|
Err(Error::EISDIR)
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
fs::remove_file(path).map_err(Into::into)
|
||||||
|
} else {
|
||||||
|
Err(Error::EINVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_remove_directory(resolved: PathGet) -> Result<()> {
|
||||||
|
let path = resolved.concatenate()?;
|
||||||
|
std::fs::remove_dir(&path).map_err(Into::into)
|
||||||
|
}
|
||||||
149
wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs
Normal file
149
wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
use crate::hostcalls_impl::PathGet;
|
||||||
|
use crate::{wasi, Error, Result};
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub(crate) trait PathGetExt {
|
||||||
|
fn concatenate(&self) -> Result<PathBuf>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathGetExt for PathGet {
|
||||||
|
fn concatenate(&self) -> Result<PathBuf> {
|
||||||
|
concatenate(self.dirfd(), Path::new(self.path()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn path_open_rights(
|
||||||
|
rights_base: wasi::__wasi_rights_t,
|
||||||
|
rights_inheriting: wasi::__wasi_rights_t,
|
||||||
|
oflags: wasi::__wasi_oflags_t,
|
||||||
|
fdflags: wasi::__wasi_fdflags_t,
|
||||||
|
) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) {
|
||||||
|
// which rights are needed on the dirfd?
|
||||||
|
let mut needed_base = wasi::__WASI_RIGHT_PATH_OPEN;
|
||||||
|
let mut needed_inheriting = rights_base | rights_inheriting;
|
||||||
|
|
||||||
|
// convert open flags
|
||||||
|
if oflags & wasi::__WASI_O_CREAT != 0 {
|
||||||
|
needed_base |= wasi::__WASI_RIGHT_PATH_CREATE_FILE;
|
||||||
|
} else if oflags & wasi::__WASI_O_TRUNC != 0 {
|
||||||
|
needed_base |= wasi::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert file descriptor flags
|
||||||
|
if fdflags & wasi::__WASI_FDFLAG_DSYNC != 0
|
||||||
|
|| fdflags & wasi::__WASI_FDFLAG_RSYNC != 0
|
||||||
|
|| fdflags & wasi::__WASI_FDFLAG_SYNC != 0
|
||||||
|
{
|
||||||
|
needed_inheriting |= wasi::__WASI_RIGHT_FD_DATASYNC;
|
||||||
|
needed_inheriting |= wasi::__WASI_RIGHT_FD_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
(needed_base, needed_inheriting)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn openat(dirfd: &File, path: &str) -> Result<File> {
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
|
use winx::file::Flags;
|
||||||
|
use winx::winerror::WinError;
|
||||||
|
|
||||||
|
let path = concatenate(dirfd, Path::new(path))?;
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits())
|
||||||
|
.open(&path)
|
||||||
|
.map_err(|e| match e.raw_os_error() {
|
||||||
|
Some(e) => {
|
||||||
|
log::debug!("openat error={:?}", e);
|
||||||
|
match WinError::from_u32(e as u32) {
|
||||||
|
WinError::ERROR_INVALID_NAME => Error::ENOTDIR,
|
||||||
|
e => e.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
Error::EIO
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result<String> {
|
||||||
|
use winx::file::get_file_path;
|
||||||
|
use winx::winerror::WinError;
|
||||||
|
|
||||||
|
let path = concatenate(dirfd, Path::new(s_path))?;
|
||||||
|
match path.read_link() {
|
||||||
|
Ok(target_path) => {
|
||||||
|
// since on Windows we are effectively emulating 'at' syscalls
|
||||||
|
// we need to strip the prefix from the absolute path
|
||||||
|
// as otherwise we will error out since WASI is not capable
|
||||||
|
// of dealing with absolute paths
|
||||||
|
let dir_path = get_file_path(dirfd)?;
|
||||||
|
let dir_path = PathBuf::from(strip_extended_prefix(dir_path));
|
||||||
|
target_path
|
||||||
|
.strip_prefix(dir_path)
|
||||||
|
.map_err(|_| Error::ENOTCAPABLE)
|
||||||
|
.and_then(|path| path.to_str().map(String::from).ok_or(Error::EILSEQ))
|
||||||
|
}
|
||||||
|
Err(e) => match e.raw_os_error() {
|
||||||
|
Some(e) => {
|
||||||
|
log::debug!("readlinkat error={:?}", e);
|
||||||
|
match WinError::from_u32(e as u32) {
|
||||||
|
WinError::ERROR_INVALID_NAME => {
|
||||||
|
if s_path.ends_with('/') {
|
||||||
|
// strip "/" and check if exists
|
||||||
|
let path = concatenate(dirfd, Path::new(s_path.trim_end_matches('/')))?;
|
||||||
|
if path.exists() && !path.is_dir() {
|
||||||
|
Err(Error::ENOTDIR)
|
||||||
|
} else {
|
||||||
|
Err(Error::ENOENT)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::ENOENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::debug!("Inconvertible OS error: {}", e);
|
||||||
|
Err(Error::EIO)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn strip_extended_prefix<P: AsRef<OsStr>>(path: P) -> OsString {
|
||||||
|
let path: Vec<u16> = path.as_ref().encode_wide().collect();
|
||||||
|
if &[92, 92, 63, 92] == &path[0..4] {
|
||||||
|
OsString::from_wide(&path[4..])
|
||||||
|
} else {
|
||||||
|
OsString::from_wide(&path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn concatenate<P: AsRef<Path>>(dirfd: &File, path: P) -> Result<PathBuf> {
|
||||||
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
|
// WASI is not able to deal with absolute paths
|
||||||
|
// so error out if absolute
|
||||||
|
if path.as_ref().is_absolute() {
|
||||||
|
return Err(Error::ENOTCAPABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir_path = get_file_path(dirfd)?;
|
||||||
|
// concatenate paths
|
||||||
|
let mut out_path = PathBuf::from(dir_path);
|
||||||
|
out_path.push(path.as_ref());
|
||||||
|
// strip extended prefix; otherwise we will error out on any relative
|
||||||
|
// components with `out_path`
|
||||||
|
let out_path = PathBuf::from(strip_extended_prefix(out_path));
|
||||||
|
|
||||||
|
log::debug!("out_path={:?}", out_path);
|
||||||
|
|
||||||
|
Ok(out_path)
|
||||||
|
}
|
||||||
119
wasi-common/src/sys/windows/hostcalls_impl/misc.rs
Normal file
119
wasi-common/src/sys/windows/hostcalls_impl/misc.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(unused_unsafe)]
|
||||||
|
#![allow(unused)]
|
||||||
|
use crate::helpers::systemtime_to_timestamp;
|
||||||
|
use crate::hostcalls_impl::{ClockEventData, FdEventData};
|
||||||
|
use crate::memory::*;
|
||||||
|
use crate::sys::host_impl;
|
||||||
|
use crate::{wasi, wasi32, Error, Result};
|
||||||
|
use cpu_time::{ProcessTime, ThreadTime};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref START_MONOTONIC: Instant = Instant::now();
|
||||||
|
static ref PERF_COUNTER_RES: u64 = get_perf_counter_resolution_ns();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer resolution on Windows is really hard. We may consider exposing the resolution of the respective
|
||||||
|
// timers as an associated function in the future.
|
||||||
|
pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
|
||||||
|
Ok(match clock_id {
|
||||||
|
// This is the best that we can do with std::time::SystemTime.
|
||||||
|
// Rust uses GetSystemTimeAsFileTime, which is said to have the resolution of
|
||||||
|
// 10ms or 55ms, [1] but MSDN doesn't confirm this in any way.
|
||||||
|
// Even the MSDN article on high resolution timestamps doesn't even mention the precision
|
||||||
|
// for this method. [3]
|
||||||
|
//
|
||||||
|
// The timer resolution can be queried using one of the functions: [2, 5]
|
||||||
|
// * NtQueryTimerResolution, which is undocumented and thus not exposed by the winapi crate
|
||||||
|
// * timeGetDevCaps, which returns the upper and lower bound for the precision, in ms.
|
||||||
|
// While the upper bound seems like something we could use, it's typically too high to be meaningful.
|
||||||
|
// For instance, the intervals return by the syscall are:
|
||||||
|
// * [1, 65536] on Wine
|
||||||
|
// * [1, 1000000] on Windows 10, which is up to (sic) 1000 seconds.
|
||||||
|
//
|
||||||
|
// It's possible to manually set the timer resolution, but this sounds like something which should
|
||||||
|
// only be done temporarily. [5]
|
||||||
|
//
|
||||||
|
// Alternatively, we could possibly use GetSystemTimePreciseAsFileTime in clock_time_get, but
|
||||||
|
// this syscall is only available starting from Windows 8.
|
||||||
|
// (we could possibly emulate it on earlier versions of Windows, see [4])
|
||||||
|
// The MSDN are not clear on the resolution of GetSystemTimePreciseAsFileTime either, but a
|
||||||
|
// Microsoft devblog entry [1] suggests that it kind of combines GetSystemTimeAsFileTime with
|
||||||
|
// QueryPeformanceCounter, which probably means that those two should have the same resolution.
|
||||||
|
//
|
||||||
|
// See also this discussion about the use of GetSystemTimePreciseAsFileTime in Python stdlib,
|
||||||
|
// which in particular contains some resolution benchmarks.
|
||||||
|
//
|
||||||
|
// [1] https://devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057
|
||||||
|
// [2] http://www.windowstimestamp.com/description
|
||||||
|
// [3] https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps?redirectedfrom=MSDN
|
||||||
|
// [4] https://www.codeproject.com/Tips/1011902/High-Resolution-Time-For-Windows
|
||||||
|
// [5] https://stackoverflow.com/questions/7685762/windows-7-timing-functions-how-to-use-getsystemtimeadjustment-correctly
|
||||||
|
// [6] https://bugs.python.org/issue19007
|
||||||
|
wasi::__WASI_CLOCK_REALTIME => 55_000_000,
|
||||||
|
// std::time::Instant uses QueryPerformanceCounter & QueryPerformanceFrequency internally
|
||||||
|
wasi::__WASI_CLOCK_MONOTONIC => *PERF_COUNTER_RES,
|
||||||
|
// The best we can do is to hardcode the value from the docs.
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes
|
||||||
|
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => 100,
|
||||||
|
// The best we can do is to hardcode the value from the docs.
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes
|
||||||
|
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => 100,
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::__wasi_timestamp_t> {
|
||||||
|
let duration = match clock_id {
|
||||||
|
wasi::__WASI_CLOCK_REALTIME => get_monotonic_time(),
|
||||||
|
wasi::__WASI_CLOCK_MONOTONIC => get_realtime_time()?,
|
||||||
|
wasi::__WASI_CLOCK_PROCESS_CPUTIME_ID => get_proc_cputime()?,
|
||||||
|
wasi::__WASI_CLOCK_THREAD_CPUTIME_ID => get_thread_cputime()?,
|
||||||
|
_ => return Err(Error::EINVAL),
|
||||||
|
};
|
||||||
|
duration.as_nanos().try_into().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_oneoff(
|
||||||
|
timeout: Option<ClockEventData>,
|
||||||
|
fd_events: Vec<FdEventData>,
|
||||||
|
events: &mut Vec<wasi::__wasi_event_t>,
|
||||||
|
) -> Result<Vec<wasi::__wasi_event_t>> {
|
||||||
|
unimplemented!("poll_oneoff")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_monotonic_time() -> Duration {
|
||||||
|
// We're circumventing the fact that we can't get a Duration from an Instant
|
||||||
|
// The epoch of __WASI_CLOCK_MONOTONIC is undefined, so we fix a time point once
|
||||||
|
// and count relative to this time point.
|
||||||
|
//
|
||||||
|
// The alternative would be to copy over the implementation of std::time::Instant
|
||||||
|
// to our source tree and add a conversion to std::time::Duration
|
||||||
|
START_MONOTONIC.elapsed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_realtime_time() -> Result<Duration> {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|_| Error::EFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_proc_cputime() -> Result<Duration> {
|
||||||
|
Ok(ProcessTime::try_now()?.as_duration())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_thread_cputime() -> Result<Duration> {
|
||||||
|
Ok(ThreadTime::try_now()?.as_duration())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_perf_counter_resolution_ns() -> u64 {
|
||||||
|
use winx::time::perf_counter_frequency;
|
||||||
|
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||||
|
// This should always succeed starting from Windows XP, so it's fine to panic in case of an error.
|
||||||
|
let freq = perf_counter_frequency().expect("QueryPerformanceFrequency returned an error");
|
||||||
|
let epsilon = NANOS_PER_SEC / freq;
|
||||||
|
epsilon
|
||||||
|
}
|
||||||
8
wasi-common/src/sys/windows/hostcalls_impl/mod.rs
Normal file
8
wasi-common/src/sys/windows/hostcalls_impl/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//! Windows-specific hostcalls that implement
|
||||||
|
//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md).
|
||||||
|
mod fs;
|
||||||
|
pub(crate) mod fs_helpers;
|
||||||
|
mod misc;
|
||||||
|
|
||||||
|
pub(crate) use self::fs::*;
|
||||||
|
pub(crate) use self::misc::*;
|
||||||
32
wasi-common/src/sys/windows/mod.rs
Normal file
32
wasi-common/src/sys/windows/mod.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
pub(crate) mod fdentry_impl;
|
||||||
|
pub(crate) mod host_impl;
|
||||||
|
pub(crate) mod hostcalls_impl;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub(crate) fn dev_null() -> Result<File> {
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open("NUL")
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preopen_dir<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
|
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
|
||||||
|
|
||||||
|
// To open a directory using CreateFile, specify the
|
||||||
|
// FILE_FLAG_BACKUP_SEMANTICS flag as part of dwFileFlags...
|
||||||
|
// cf. https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfile2
|
||||||
|
OpenOptions::new()
|
||||||
|
.create(false)
|
||||||
|
.write(true)
|
||||||
|
.read(true)
|
||||||
|
.attributes(FILE_FLAG_BACKUP_SEMANTICS)
|
||||||
|
.open(path)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
957
wasi-common/src/wasi.rs
Normal file
957
wasi-common/src/wasi.rs
Normal file
@@ -0,0 +1,957 @@
|
|||||||
|
//! Types and constants shared between 32-bit and 64-bit wasi. Types involving
|
||||||
|
//! pointer or `usize`-sized data are excluded here, so this file only contains
|
||||||
|
//! fixed-size types, so it's host/target independent.
|
||||||
|
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use wig::witx_wasi_types;
|
||||||
|
|
||||||
|
witx_wasi_types!("unstable" "wasi_unstable_preview0");
|
||||||
|
|
||||||
|
pub(crate) const RIGHTS_ALL: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC
|
||||||
|
| __WASI_RIGHT_FD_READ
|
||||||
|
| __WASI_RIGHT_FD_SEEK
|
||||||
|
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
|
||||||
|
| __WASI_RIGHT_FD_SYNC
|
||||||
|
| __WASI_RIGHT_FD_TELL
|
||||||
|
| __WASI_RIGHT_FD_WRITE
|
||||||
|
| __WASI_RIGHT_FD_ADVISE
|
||||||
|
| __WASI_RIGHT_FD_ALLOCATE
|
||||||
|
| __WASI_RIGHT_PATH_CREATE_DIRECTORY
|
||||||
|
| __WASI_RIGHT_PATH_CREATE_FILE
|
||||||
|
| __WASI_RIGHT_PATH_LINK_SOURCE
|
||||||
|
| __WASI_RIGHT_PATH_LINK_TARGET
|
||||||
|
| __WASI_RIGHT_PATH_OPEN
|
||||||
|
| __WASI_RIGHT_FD_READDIR
|
||||||
|
| __WASI_RIGHT_PATH_READLINK
|
||||||
|
| __WASI_RIGHT_PATH_RENAME_SOURCE
|
||||||
|
| __WASI_RIGHT_PATH_RENAME_TARGET
|
||||||
|
| __WASI_RIGHT_PATH_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_PATH_FILESTAT_SET_SIZE
|
||||||
|
| __WASI_RIGHT_PATH_FILESTAT_SET_TIMES
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_SET_SIZE
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_SET_TIMES
|
||||||
|
| __WASI_RIGHT_PATH_SYMLINK
|
||||||
|
| __WASI_RIGHT_PATH_UNLINK_FILE
|
||||||
|
| __WASI_RIGHT_PATH_REMOVE_DIRECTORY
|
||||||
|
| __WASI_RIGHT_POLL_FD_READWRITE
|
||||||
|
| __WASI_RIGHT_SOCK_SHUTDOWN;
|
||||||
|
|
||||||
|
// Block and character device interaction is outside the scope of
|
||||||
|
// WASI. Simply allow everything.
|
||||||
|
pub(crate) const RIGHTS_BLOCK_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL;
|
||||||
|
pub(crate) const RIGHTS_BLOCK_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL;
|
||||||
|
pub(crate) const RIGHTS_CHARACTER_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL;
|
||||||
|
pub(crate) const RIGHTS_CHARACTER_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL;
|
||||||
|
|
||||||
|
// Only allow directory operations on directories. Directories can only
|
||||||
|
// yield file descriptors to other directories and files.
|
||||||
|
pub(crate) const RIGHTS_DIRECTORY_BASE: __wasi_rights_t = __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
|
||||||
|
| __WASI_RIGHT_FD_SYNC
|
||||||
|
| __WASI_RIGHT_FD_ADVISE
|
||||||
|
| __WASI_RIGHT_PATH_CREATE_DIRECTORY
|
||||||
|
| __WASI_RIGHT_PATH_CREATE_FILE
|
||||||
|
| __WASI_RIGHT_PATH_LINK_SOURCE
|
||||||
|
| __WASI_RIGHT_PATH_LINK_TARGET
|
||||||
|
| __WASI_RIGHT_PATH_OPEN
|
||||||
|
| __WASI_RIGHT_FD_READDIR
|
||||||
|
| __WASI_RIGHT_PATH_READLINK
|
||||||
|
| __WASI_RIGHT_PATH_RENAME_SOURCE
|
||||||
|
| __WASI_RIGHT_PATH_RENAME_TARGET
|
||||||
|
| __WASI_RIGHT_PATH_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_PATH_FILESTAT_SET_SIZE
|
||||||
|
| __WASI_RIGHT_PATH_FILESTAT_SET_TIMES
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_SET_TIMES
|
||||||
|
| __WASI_RIGHT_PATH_SYMLINK
|
||||||
|
| __WASI_RIGHT_PATH_UNLINK_FILE
|
||||||
|
| __WASI_RIGHT_PATH_REMOVE_DIRECTORY
|
||||||
|
| __WASI_RIGHT_POLL_FD_READWRITE;
|
||||||
|
pub(crate) const RIGHTS_DIRECTORY_INHERITING: __wasi_rights_t =
|
||||||
|
RIGHTS_DIRECTORY_BASE | RIGHTS_REGULAR_FILE_BASE;
|
||||||
|
|
||||||
|
// Operations that apply to regular files.
|
||||||
|
pub(crate) const RIGHTS_REGULAR_FILE_BASE: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC
|
||||||
|
| __WASI_RIGHT_FD_READ
|
||||||
|
| __WASI_RIGHT_FD_SEEK
|
||||||
|
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
|
||||||
|
| __WASI_RIGHT_FD_SYNC
|
||||||
|
| __WASI_RIGHT_FD_TELL
|
||||||
|
| __WASI_RIGHT_FD_WRITE
|
||||||
|
| __WASI_RIGHT_FD_ADVISE
|
||||||
|
| __WASI_RIGHT_FD_ALLOCATE
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_SET_SIZE
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_SET_TIMES
|
||||||
|
| __WASI_RIGHT_POLL_FD_READWRITE;
|
||||||
|
pub(crate) const RIGHTS_REGULAR_FILE_INHERITING: __wasi_rights_t = 0;
|
||||||
|
|
||||||
|
// Operations that apply to sockets and socket pairs.
|
||||||
|
pub(crate) const RIGHTS_SOCKET_BASE: __wasi_rights_t = __WASI_RIGHT_FD_READ
|
||||||
|
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
|
||||||
|
| __WASI_RIGHT_FD_WRITE
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_POLL_FD_READWRITE
|
||||||
|
| __WASI_RIGHT_SOCK_SHUTDOWN;
|
||||||
|
pub(crate) const RIGHTS_SOCKET_INHERITING: __wasi_rights_t = RIGHTS_ALL;
|
||||||
|
|
||||||
|
// Operations that apply to TTYs.
|
||||||
|
pub(crate) const RIGHTS_TTY_BASE: __wasi_rights_t = __WASI_RIGHT_FD_READ
|
||||||
|
| __WASI_RIGHT_FD_FDSTAT_SET_FLAGS
|
||||||
|
| __WASI_RIGHT_FD_WRITE
|
||||||
|
| __WASI_RIGHT_FD_FILESTAT_GET
|
||||||
|
| __WASI_RIGHT_POLL_FD_READWRITE;
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) const RIGHTS_TTY_INHERITING: __wasi_rights_t = 0;
|
||||||
|
|
||||||
|
pub fn strerror(errno: __wasi_errno_t) -> &'static str {
|
||||||
|
match errno {
|
||||||
|
__WASI_ESUCCESS => "__WASI_ESUCCESS",
|
||||||
|
__WASI_E2BIG => "__WASI_E2BIG",
|
||||||
|
__WASI_EACCES => "__WASI_EACCES",
|
||||||
|
__WASI_EADDRINUSE => "__WASI_EADDRINUSE",
|
||||||
|
__WASI_EADDRNOTAVAIL => "__WASI_EADDRNOTAVAIL",
|
||||||
|
__WASI_EAFNOSUPPORT => "__WASI_EAFNOSUPPORT",
|
||||||
|
__WASI_EAGAIN => "__WASI_EAGAIN",
|
||||||
|
__WASI_EALREADY => "__WASI_EALREADY",
|
||||||
|
__WASI_EBADF => "__WASI_EBADF",
|
||||||
|
__WASI_EBADMSG => "__WASI_EBADMSG",
|
||||||
|
__WASI_EBUSY => "__WASI_EBUSY",
|
||||||
|
__WASI_ECANCELED => "__WASI_ECANCELED",
|
||||||
|
__WASI_ECHILD => "__WASI_ECHILD",
|
||||||
|
__WASI_ECONNABORTED => "__WASI_ECONNABORTED",
|
||||||
|
__WASI_ECONNREFUSED => "__WASI_ECONNREFUSED",
|
||||||
|
__WASI_ECONNRESET => "__WASI_ECONNRESET",
|
||||||
|
__WASI_EDEADLK => "__WASI_EDEADLK",
|
||||||
|
__WASI_EDESTADDRREQ => "__WASI_EDESTADDRREQ",
|
||||||
|
__WASI_EDOM => "__WASI_EDOM",
|
||||||
|
__WASI_EDQUOT => "__WASI_EDQUOT",
|
||||||
|
__WASI_EEXIST => "__WASI_EEXIST",
|
||||||
|
__WASI_EFAULT => "__WASI_EFAULT",
|
||||||
|
__WASI_EFBIG => "__WASI_EFBIG",
|
||||||
|
__WASI_EHOSTUNREACH => "__WASI_EHOSTUNREACH",
|
||||||
|
__WASI_EIDRM => "__WASI_EIDRM",
|
||||||
|
__WASI_EILSEQ => "__WASI_EILSEQ",
|
||||||
|
__WASI_EINPROGRESS => "__WASI_EINPROGRESS",
|
||||||
|
__WASI_EINTR => "__WASI_EINTR",
|
||||||
|
__WASI_EINVAL => "__WASI_EINVAL",
|
||||||
|
__WASI_EIO => "__WASI_EIO",
|
||||||
|
__WASI_EISCONN => "__WASI_EISCONN",
|
||||||
|
__WASI_EISDIR => "__WASI_EISDIR",
|
||||||
|
__WASI_ELOOP => "__WASI_ELOOP",
|
||||||
|
__WASI_EMFILE => "__WASI_EMFILE",
|
||||||
|
__WASI_EMLINK => "__WASI_EMLINK",
|
||||||
|
__WASI_EMSGSIZE => "__WASI_EMSGSIZE",
|
||||||
|
__WASI_EMULTIHOP => "__WASI_EMULTIHOP",
|
||||||
|
__WASI_ENAMETOOLONG => "__WASI_ENAMETOOLONG",
|
||||||
|
__WASI_ENETDOWN => "__WASI_ENETDOWN",
|
||||||
|
__WASI_ENETRESET => "__WASI_ENETRESET",
|
||||||
|
__WASI_ENETUNREACH => "__WASI_ENETUNREACH",
|
||||||
|
__WASI_ENFILE => "__WASI_ENFILE",
|
||||||
|
__WASI_ENOBUFS => "__WASI_ENOBUFS",
|
||||||
|
__WASI_ENODEV => "__WASI_ENODEV",
|
||||||
|
__WASI_ENOENT => "__WASI_ENOENT",
|
||||||
|
__WASI_ENOEXEC => "__WASI_ENOEXEC",
|
||||||
|
__WASI_ENOLCK => "__WASI_ENOLCK",
|
||||||
|
__WASI_ENOLINK => "__WASI_ENOLINK",
|
||||||
|
__WASI_ENOMEM => "__WASI_ENOMEM",
|
||||||
|
__WASI_ENOMSG => "__WASI_ENOMSG",
|
||||||
|
__WASI_ENOPROTOOPT => "__WASI_ENOPROTOOPT",
|
||||||
|
__WASI_ENOSPC => "__WASI_ENOSPC",
|
||||||
|
__WASI_ENOSYS => "__WASI_ENOSYS",
|
||||||
|
__WASI_ENOTCONN => "__WASI_ENOTCONN",
|
||||||
|
__WASI_ENOTDIR => "__WASI_ENOTDIR",
|
||||||
|
__WASI_ENOTEMPTY => "__WASI_ENOTEMPTY",
|
||||||
|
__WASI_ENOTRECOVERABLE => "__WASI_ENOTRECOVERABLE",
|
||||||
|
__WASI_ENOTSOCK => "__WASI_ENOTSOCK",
|
||||||
|
__WASI_ENOTSUP => "__WASI_ENOTSUP",
|
||||||
|
__WASI_ENOTTY => "__WASI_ENOTTY",
|
||||||
|
__WASI_ENXIO => "__WASI_ENXIO",
|
||||||
|
__WASI_EOVERFLOW => "__WASI_EOVERFLOW",
|
||||||
|
__WASI_EOWNERDEAD => "__WASI_EOWNERDEAD",
|
||||||
|
__WASI_EPERM => "__WASI_EPERM",
|
||||||
|
__WASI_EPIPE => "__WASI_EPIPE",
|
||||||
|
__WASI_EPROTO => "__WASI_EPROTO",
|
||||||
|
__WASI_EPROTONOSUPPORT => "__WASI_EPROTONOSUPPORT",
|
||||||
|
__WASI_EPROTOTYPE => "__WASI_EPROTOTYPE",
|
||||||
|
__WASI_ERANGE => "__WASI_ERANGE",
|
||||||
|
__WASI_EROFS => "__WASI_EROFS",
|
||||||
|
__WASI_ESPIPE => "__WASI_ESPIPE",
|
||||||
|
__WASI_ESRCH => "__WASI_ESRCH",
|
||||||
|
__WASI_ESTALE => "__WASI_ESTALE",
|
||||||
|
__WASI_ETIMEDOUT => "__WASI_ETIMEDOUT",
|
||||||
|
__WASI_ETXTBSY => "__WASI_ETXTBSY",
|
||||||
|
__WASI_EXDEV => "__WASI_EXDEV",
|
||||||
|
__WASI_ENOTCAPABLE => "__WASI_ENOTCAPABLE",
|
||||||
|
other => panic!("Undefined errno value {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn whence_to_str(whence: __wasi_whence_t) -> &'static str {
|
||||||
|
match whence {
|
||||||
|
__WASI_WHENCE_CUR => "__WASI_WHENCE_CUR",
|
||||||
|
__WASI_WHENCE_END => "__WASI_WHENCE_END",
|
||||||
|
__WASI_WHENCE_SET => "__WASI_WHENCE_SET",
|
||||||
|
other => panic!("Undefined whence value {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const __WASI_DIRCOOKIE_START: __wasi_dircookie_t = 0;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_wasi_dirent_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_dirent_t>(),
|
||||||
|
24usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_dirent_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_next as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_next)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_ino as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_ino)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_namlen as *const _ as usize },
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_namlen)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_type as *const _ as usize },
|
||||||
|
20usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_event_fd_readwrite_t>(),
|
||||||
|
16usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_event_fd_readwrite_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_event_fd_readwrite_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_event_fd_readwrite_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_event_fd_readwrite_t>())).nbytes as *const _ as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_fd_readwrite_t),
|
||||||
|
"::",
|
||||||
|
stringify!(nbytes)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_event_fd_readwrite_t>())).flags as *const _ as usize
|
||||||
|
},
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_fd_readwrite_t),
|
||||||
|
"::",
|
||||||
|
stringify!(flags)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_event_t___wasi_event_u() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_event_u>(),
|
||||||
|
16usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_event_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_event_u>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_event_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_u>())).fd_readwrite as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_u),
|
||||||
|
"::",
|
||||||
|
stringify!(fd_readwrite)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_event_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_event_t>(),
|
||||||
|
32usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_event_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_event_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_event_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).userdata as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(userdata)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).error as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).r#type as *const _ as usize },
|
||||||
|
10usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(r#type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).u as *const _ as usize },
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(u)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_wasi_event_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_event_t>(),
|
||||||
|
32usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_event_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).userdata as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(userdata)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).error as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_event_t>())).r#type as *const _ as usize },
|
||||||
|
10usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_event_t),
|
||||||
|
"::",
|
||||||
|
stringify!(r#type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_wasi_fdstat_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_fdstat_t>(),
|
||||||
|
24usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_fdstat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_filetype as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_filetype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_flags as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_flags)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_base as *const _ as usize
|
||||||
|
},
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_rights_base)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_inheriting as *const _
|
||||||
|
as usize
|
||||||
|
},
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_rights_inheriting)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_wasi_filestat_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_filestat_t>(),
|
||||||
|
56usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_filestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_dev as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_dev)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ino as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_ino)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_filestat_t>())).st_filetype as *const _ as usize
|
||||||
|
},
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_filetype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_nlink as *const _ as usize },
|
||||||
|
20usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_nlink)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_size as *const _ as usize },
|
||||||
|
24usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_size)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_atim as *const _ as usize },
|
||||||
|
32usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_atim)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_mtim as *const _ as usize },
|
||||||
|
40usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_mtim)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ctim as *const _ as usize },
|
||||||
|
48usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_ctim)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_subscription_clock_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_subscription_clock_t>(),
|
||||||
|
40usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_subscription_clock_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_subscription_clock_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_subscription_clock_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).identifier as *const _
|
||||||
|
as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_clock_t),
|
||||||
|
"::",
|
||||||
|
stringify!(identifier)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).clock_id as *const _
|
||||||
|
as usize
|
||||||
|
},
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_clock_t),
|
||||||
|
"::",
|
||||||
|
stringify!(clock_id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).timeout as *const _ as usize
|
||||||
|
},
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_clock_t),
|
||||||
|
"::",
|
||||||
|
stringify!(timeout)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).precision as *const _
|
||||||
|
as usize
|
||||||
|
},
|
||||||
|
24usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_clock_t),
|
||||||
|
"::",
|
||||||
|
stringify!(precision)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_clock_t>())).flags as *const _ as usize
|
||||||
|
},
|
||||||
|
32usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_clock_t),
|
||||||
|
"::",
|
||||||
|
stringify!(flags)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_fd_readwrite_t(
|
||||||
|
) {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_subscription_fd_readwrite_t>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_subscription_fd_readwrite_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_subscription_fd_readwrite_t>(),
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of ",
|
||||||
|
stringify!(__wasi_subscription_fd_readwrite_t)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_fd_readwrite_t>())).file_descriptor
|
||||||
|
as *const _ as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_fd_readwrite_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fd)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_subscription_t___wasi_subscription_u() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_subscription_u>(),
|
||||||
|
40usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_subscription_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_subscription_u>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_subscription_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_subscription_u>())).clock as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_u),
|
||||||
|
"::",
|
||||||
|
stringify!(clock)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_u>())).fd_readwrite as *const _ as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_u),
|
||||||
|
"::",
|
||||||
|
stringify!(fd_readwrite)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_subscription_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_subscription_t>(),
|
||||||
|
56usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_subscription_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_subscription_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_subscription_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_t>())).userdata as *const _ as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_t),
|
||||||
|
"::",
|
||||||
|
stringify!(userdata)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_subscription_t>())).r#type as *const _ as usize
|
||||||
|
},
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_t),
|
||||||
|
"::",
|
||||||
|
stringify!(r#type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_subscription_t>())).u as *const _ as usize },
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_subscription_t),
|
||||||
|
"::",
|
||||||
|
stringify!(u)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_filestat_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_filestat_t>(),
|
||||||
|
56usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_filestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_filestat_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_filestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_dev as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_dev)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ino as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_ino)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_filestat_t>())).st_filetype as *const _ as usize
|
||||||
|
},
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_filetype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_nlink as *const _ as usize },
|
||||||
|
20usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_nlink)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_size as *const _ as usize },
|
||||||
|
24usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_size)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_atim as *const _ as usize },
|
||||||
|
32usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_atim)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_mtim as *const _ as usize },
|
||||||
|
40usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_mtim)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_filestat_t>())).st_ctim as *const _ as usize },
|
||||||
|
48usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_filestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(st_ctim)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_fdstat_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_fdstat_t>(),
|
||||||
|
24usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_fdstat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_fdstat_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_fdstat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_filetype as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_filetype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_flags as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_flags)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_base as *const _ as usize
|
||||||
|
},
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_rights_base)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_fdstat_t>())).fs_rights_inheriting as *const _
|
||||||
|
as usize
|
||||||
|
},
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_fdstat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(fs_rights_inheriting)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_dirent_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_dirent_t>(),
|
||||||
|
24usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_dirent_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_dirent_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_dirent_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_next as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_next)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_ino as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_ino)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_namlen as *const _ as usize },
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_namlen)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_dirent_t>())).d_type as *const _ as usize },
|
||||||
|
20usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_dirent_t),
|
||||||
|
"::",
|
||||||
|
stringify!(d_type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
173
wasi-common/src/wasi32.rs
Normal file
173
wasi-common/src/wasi32.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
//! Types and constants specific to 32-bit wasi. These are similar to the types
|
||||||
|
//! in the `host` module, but pointers and `usize` values are replaced with
|
||||||
|
//! `u32`-sized types.
|
||||||
|
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use wig::witx_wasi32_types;
|
||||||
|
|
||||||
|
use crate::wasi::*;
|
||||||
|
|
||||||
|
pub type uintptr_t = u32;
|
||||||
|
pub type size_t = u32;
|
||||||
|
|
||||||
|
witx_wasi32_types!("unstable" "wasi_unstable_preview0");
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_wasi_ciovec_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_ciovec_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_ciovec_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_ciovec_t>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_ciovec_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_ciovec_t>())).buf as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_ciovec_t),
|
||||||
|
"::",
|
||||||
|
stringify!(buf)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_ciovec_t>())).buf_len as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_ciovec_t),
|
||||||
|
"::",
|
||||||
|
stringify!(buf_len)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_wasi_iovec_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_iovec_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_iovec_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_iovec_t>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_iovec_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_iovec_t>())).buf as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_iovec_t),
|
||||||
|
"::",
|
||||||
|
stringify!(buf)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_iovec_t>())).buf_len as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_iovec_t),
|
||||||
|
"::",
|
||||||
|
stringify!(buf_len)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_prestat_dir>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_prestat_dir))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_prestat_dir>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_prestat_dir))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
&(*(::std::ptr::null::<__wasi_prestat_dir>())).pr_name_len as *const _ as usize
|
||||||
|
},
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_dir),
|
||||||
|
"::",
|
||||||
|
stringify!(pr_name_len)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_prestat_t___wasi_prestat_u() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_prestat_u>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_prestat_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_prestat_u>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_prestat_u))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_prestat_u>())).dir as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_u),
|
||||||
|
"::",
|
||||||
|
stringify!(dir)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout___wasi_prestat_t() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<__wasi_prestat_t>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(__wasi_prestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<__wasi_prestat_t>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(__wasi_prestat_t))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).pr_type as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(pr_type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(::std::ptr::null::<__wasi_prestat_t>())).u as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Offset of field: ",
|
||||||
|
stringify!(__wasi_prestat_t),
|
||||||
|
"::",
|
||||||
|
stringify!(u)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
92
wasi-common/tests/runtime.rs
Normal file
92
wasi-common/tests/runtime.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use cranelift_codegen::settings::{self, Configurable};
|
||||||
|
use std::{collections::HashMap, path::Path};
|
||||||
|
use wasmtime_api::{Config, Engine, HostRef, Instance, Module, Store};
|
||||||
|
use wasmtime_jit::{CompilationStrategy, Features};
|
||||||
|
|
||||||
|
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> Result<(), String> {
|
||||||
|
// Prepare runtime
|
||||||
|
let mut flag_builder = settings::builder();
|
||||||
|
|
||||||
|
// Enable proper trap for division
|
||||||
|
flag_builder
|
||||||
|
.enable("avoid_div_traps")
|
||||||
|
.map_err(|err| format!("error while enabling proper division trap: {}", err))?;
|
||||||
|
|
||||||
|
let config = Config::new(
|
||||||
|
settings::Flags::new(flag_builder),
|
||||||
|
Features::default(),
|
||||||
|
false,
|
||||||
|
CompilationStrategy::Auto,
|
||||||
|
);
|
||||||
|
let engine = HostRef::new(Engine::new(config));
|
||||||
|
let store = HostRef::new(Store::new(engine));
|
||||||
|
|
||||||
|
let mut module_registry = HashMap::new();
|
||||||
|
let global_exports = store.borrow().global_exports().clone();
|
||||||
|
let get_preopens = |workspace: Option<&Path>| -> Result<Vec<_>, String> {
|
||||||
|
if let Some(workspace) = workspace {
|
||||||
|
let preopen_dir = wasi_common::preopen_dir(workspace).map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"error while preopening directory '{}': {}",
|
||||||
|
workspace.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(vec![(".".to_owned(), preopen_dir)])
|
||||||
|
} else {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module_registry.insert(
|
||||||
|
"wasi_unstable".to_owned(),
|
||||||
|
Instance::from_handle(
|
||||||
|
store.clone(),
|
||||||
|
wasmtime_wasi::instantiate_wasi(
|
||||||
|
"",
|
||||||
|
global_exports.clone(),
|
||||||
|
&get_preopens(workspace)?,
|
||||||
|
&[bin_name.to_owned(), ".".to_owned()],
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("error instantiating WASI: {}", e))?,
|
||||||
|
)
|
||||||
|
.map_err(|err| format!("error instantiating from handle: {}", err))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let module = HostRef::new(
|
||||||
|
Module::new(store.clone(), &data)
|
||||||
|
.map_err(|err| format!("error while creating Wasm module '{}': {}", bin_name, err))?,
|
||||||
|
);
|
||||||
|
let imports = module
|
||||||
|
.borrow()
|
||||||
|
.imports()
|
||||||
|
.iter()
|
||||||
|
.map(|i| {
|
||||||
|
let module_name = i.module().to_string();
|
||||||
|
if let Some((instance, map)) = module_registry.get(&module_name) {
|
||||||
|
let field_name = i.name().to_string();
|
||||||
|
if let Some(export_index) = map.get(&field_name) {
|
||||||
|
Ok(instance.exports()[*export_index].clone())
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"import {} was not found in module {}",
|
||||||
|
field_name, module_name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!("import module {} was not found", module_name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let _ = HostRef::new(
|
||||||
|
Instance::new(store.clone(), module.clone(), &imports).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"error while instantiating Wasm module '{}': {}",
|
||||||
|
bin_name, err
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
30
wasi-common/tests/utils.rs
Normal file
30
wasi-common/tests/utils.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use tempfile::{Builder, TempDir};
|
||||||
|
|
||||||
|
pub fn read_wasm(path: &Path) -> Result<Vec<u8>, String> {
|
||||||
|
let data = fs::read(path).map_err(|err| err.to_string())?;
|
||||||
|
if data.starts_with(&[b'\0', b'a', b's', b'm']) {
|
||||||
|
Ok(data)
|
||||||
|
} else {
|
||||||
|
Err("Invalid Wasm file encountered".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_workspace(exe_name: &str) -> Result<TempDir, String> {
|
||||||
|
let prefix = format!("wasi_common_{}", exe_name);
|
||||||
|
Builder::new()
|
||||||
|
.prefix(&prefix)
|
||||||
|
.tempdir()
|
||||||
|
.map_err(|e| format!("couldn't create workspace in temp files: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_exec_name_from_path(path: &Path) -> Result<String, String> {
|
||||||
|
path.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.map(String::from)
|
||||||
|
.ok_or(format!(
|
||||||
|
"couldn't extract the file stem from path {}",
|
||||||
|
path.display()
|
||||||
|
))
|
||||||
|
}
|
||||||
16
wasi-common/tests/wasm_tests.rs
Normal file
16
wasi-common/tests/wasm_tests.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#![cfg(feature = "wasm_tests")]
|
||||||
|
|
||||||
|
mod runtime;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
static LOG_INIT: Once = Once::new();
|
||||||
|
|
||||||
|
fn setup_log() {
|
||||||
|
LOG_INIT.call_once(|| {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/wasi_misc_tests.rs"));
|
||||||
17
wasi-common/wasi-common-cbindgen/Cargo.toml
Normal file
17
wasi-common/wasi-common-cbindgen/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasi-common-cbindgen"
|
||||||
|
version = "0.5.0"
|
||||||
|
authors = ["Jakub Konka <kubkon@jakubkonka.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0 WITH LLVM-exception"
|
||||||
|
description = "Interface generator utilities used by wasi-common"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "1.0.5", features = ["full"] }
|
||||||
|
quote = "1.0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
trybuild = "1.0.4"
|
||||||
220
wasi-common/wasi-common-cbindgen/LICENSE
Normal file
220
wasi-common/wasi-common-cbindgen/LICENSE
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||||
|
|
||||||
|
As an exception, if, as a result of your compiling your source code, portions
|
||||||
|
of this Software are embedded into an Object form of such source code, you
|
||||||
|
may redistribute such embedded portions in such Object form without complying
|
||||||
|
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||||
|
|
||||||
|
In addition, if you combine or link compiled forms of this Software with
|
||||||
|
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||||
|
court of competent jurisdiction determines that the patent provision (Section
|
||||||
|
3), the indemnity provision (Section 9) or other Section of the License
|
||||||
|
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||||
|
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||||
|
the License, but only in their entirety and only with respect to the Combined
|
||||||
|
Software.
|
||||||
|
|
||||||
107
wasi-common/wasi-common-cbindgen/src/lib.rs
Normal file
107
wasi-common/wasi-common-cbindgen/src/lib.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{FnArg, Pat, PatType, Type, TypeReference, TypeSlice};
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn wasi_common_cbindgen(attr: TokenStream, function: TokenStream) -> TokenStream {
|
||||||
|
assert!(attr.is_empty());
|
||||||
|
|
||||||
|
let function = syn::parse_macro_input!(function as syn::ItemFn);
|
||||||
|
|
||||||
|
// capture visibility
|
||||||
|
let vis = &function.vis;
|
||||||
|
|
||||||
|
// generate C fn name prefixed with __wasi_
|
||||||
|
let fn_ident = &function.sig.ident;
|
||||||
|
let concatenated = format!("wasi_common_{}", fn_ident);
|
||||||
|
let c_fn_ident = syn::Ident::new(&concatenated, fn_ident.span());
|
||||||
|
|
||||||
|
// capture input args
|
||||||
|
let mut arg_ident = Vec::new();
|
||||||
|
let mut arg_type = Vec::new();
|
||||||
|
let mut call_arg_ident = Vec::new();
|
||||||
|
for input in &function.sig.inputs {
|
||||||
|
match input {
|
||||||
|
FnArg::Typed(PatType {
|
||||||
|
attrs,
|
||||||
|
pat,
|
||||||
|
colon_token: _,
|
||||||
|
ty,
|
||||||
|
}) => {
|
||||||
|
// parse arg identifier
|
||||||
|
let ident = if let Pat::Ident(ident) = &**pat {
|
||||||
|
&ident.ident
|
||||||
|
} else {
|
||||||
|
panic!("expected function input to be an identifier")
|
||||||
|
};
|
||||||
|
if !attrs.is_empty() {
|
||||||
|
panic!("unsupported attributes on function arg");
|
||||||
|
}
|
||||||
|
arg_ident.push(quote!(#ident));
|
||||||
|
// parse arg type
|
||||||
|
if let Type::Reference(ty @ TypeReference { .. }) = &**ty {
|
||||||
|
// if we're here, then we found a &-ref
|
||||||
|
// so substitute it for *mut since we're exporting to C
|
||||||
|
let elem = &*ty.elem;
|
||||||
|
if let Type::Slice(elem @ TypeSlice { .. }) = &elem {
|
||||||
|
// slice: &[type] or &mut [type]
|
||||||
|
// in C it requires a signature *mut type
|
||||||
|
let elem = &elem.elem;
|
||||||
|
arg_type.push(quote!(*mut #elem));
|
||||||
|
// since it's a slice, we'll need to do more work here
|
||||||
|
// simple dereferencing is not enough
|
||||||
|
// firstly, we need to add a len arg to C fn
|
||||||
|
// secondly, we need to invoke std::slice::from_raw_parts_mut(..)
|
||||||
|
let concatenated = format!("{}_len", ident);
|
||||||
|
let len_ident = syn::Ident::new(&concatenated, ident.span());
|
||||||
|
call_arg_ident.push(quote! {
|
||||||
|
std::slice::from_raw_parts_mut(#ident, #len_ident)
|
||||||
|
});
|
||||||
|
arg_ident.push(quote!(#len_ident));
|
||||||
|
arg_type.push(quote!(usize));
|
||||||
|
} else {
|
||||||
|
// & or &mut type; substitute with *const or *mut type.
|
||||||
|
// Also, we need to properly dereference the substituted raw
|
||||||
|
// pointer if we are to properly call the hostcall fn.
|
||||||
|
if ty.mutability.is_none() {
|
||||||
|
arg_type.push(quote!(*const #elem));
|
||||||
|
call_arg_ident.push(quote!(&*#ident));
|
||||||
|
} else {
|
||||||
|
arg_type.push(quote!(*mut #elem));
|
||||||
|
call_arg_ident.push(quote!(&mut *#ident));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arg_type.push(quote!(#ty));
|
||||||
|
// non-&-ref type, so preserve whatever the arg was
|
||||||
|
call_arg_ident.push(quote!(#ident));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!("unrecognized function input pattern");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// capture output arg
|
||||||
|
let output = &function.sig.output;
|
||||||
|
|
||||||
|
let result = quote! {
|
||||||
|
#function
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
#vis unsafe extern "C" fn #c_fn_ident(
|
||||||
|
#(
|
||||||
|
#arg_ident: #arg_type,
|
||||||
|
)*
|
||||||
|
) #output {
|
||||||
|
#fn_ident(#(
|
||||||
|
#call_arg_ident,
|
||||||
|
)*)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result.into()
|
||||||
|
}
|
||||||
20
wasi-common/wasi-common-cbindgen/tests/array_args.rs
Normal file
20
wasi-common/wasi-common-cbindgen/tests/array_args.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
extern crate wasi_common_cbindgen;
|
||||||
|
|
||||||
|
pub use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
fn array_args(a: &mut [u8]) {
|
||||||
|
a[0] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut expected: &mut [u8] = &mut [0, 0];
|
||||||
|
array_args(&mut expected);
|
||||||
|
|
||||||
|
let given: &mut [u8] = &mut [0, 0];
|
||||||
|
unsafe {
|
||||||
|
wasi_common_array_args(given.as_mut_ptr(), given.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(given, expected);
|
||||||
|
}
|
||||||
20
wasi-common/wasi-common-cbindgen/tests/mut_args.rs
Normal file
20
wasi-common/wasi-common-cbindgen/tests/mut_args.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
extern crate wasi_common_cbindgen;
|
||||||
|
|
||||||
|
pub use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
fn mut_args(a: &mut usize) {
|
||||||
|
*a = *a + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut expected = Box::new(2);
|
||||||
|
mut_args(expected.as_mut());
|
||||||
|
let given = unsafe {
|
||||||
|
let given = Box::new(2);
|
||||||
|
let raw = Box::into_raw(given);
|
||||||
|
wasi_common_mut_args(raw);
|
||||||
|
Box::from_raw(raw)
|
||||||
|
};
|
||||||
|
assert_eq!(*given, *expected);
|
||||||
|
}
|
||||||
12
wasi-common/wasi-common-cbindgen/tests/no_args.rs
Normal file
12
wasi-common/wasi-common-cbindgen/tests/no_args.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
extern crate wasi_common_cbindgen;
|
||||||
|
|
||||||
|
pub use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
fn no_args() -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(unsafe { wasi_common_no_args() }, no_args());
|
||||||
|
}
|
||||||
20
wasi-common/wasi-common-cbindgen/tests/ref_args.rs
Normal file
20
wasi-common/wasi-common-cbindgen/tests/ref_args.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
extern crate wasi_common_cbindgen;
|
||||||
|
|
||||||
|
pub use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
fn ref_args(a: &usize) -> usize {
|
||||||
|
a + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let a = Box::new(2);
|
||||||
|
let expected = ref_args(a.as_ref());
|
||||||
|
let given = unsafe {
|
||||||
|
let raw = Box::into_raw(a);
|
||||||
|
let res = wasi_common_ref_args(raw);
|
||||||
|
Box::from_raw(raw);
|
||||||
|
res
|
||||||
|
};
|
||||||
|
assert_eq!(given, expected);
|
||||||
|
}
|
||||||
9
wasi-common/wasi-common-cbindgen/tests/test.rs
Normal file
9
wasi-common/wasi-common-cbindgen/tests/test.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#[test]
|
||||||
|
fn tests() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
t.pass("tests/no_args.rs");
|
||||||
|
t.pass("tests/val_args.rs");
|
||||||
|
t.pass("tests/ref_args.rs");
|
||||||
|
t.pass("tests/mut_args.rs");
|
||||||
|
t.pass("tests/array_args.rs");
|
||||||
|
}
|
||||||
12
wasi-common/wasi-common-cbindgen/tests/val_args.rs
Normal file
12
wasi-common/wasi-common-cbindgen/tests/val_args.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
extern crate wasi_common_cbindgen;
|
||||||
|
|
||||||
|
pub use wasi_common_cbindgen::wasi_common_cbindgen;
|
||||||
|
|
||||||
|
#[wasi_common_cbindgen]
|
||||||
|
fn val_args(a: usize, b: usize) -> usize {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(unsafe { wasi_common_val_args(1, 2) }, val_args(1, 2));
|
||||||
|
}
|
||||||
11
wasi-common/wasi-misc-tests/Cargo.toml
Normal file
11
wasi-common/wasi-misc-tests/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasi-misc-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Wasmtime Project Developers"]
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2.65"
|
||||||
|
wasi = "0.7.0"
|
||||||
|
more-asserts = "0.2.1"
|
||||||
4
wasi-common/wasi-misc-tests/README.md
Normal file
4
wasi-common/wasi-misc-tests/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
This is the `wasi-misc-test` crate, which contains source code for the system-level WASI tests.
|
||||||
|
|
||||||
|
Building these tests requires `wasm32-wasi` target installed
|
||||||
|
|
||||||
18
wasi-common/wasi-misc-tests/src/bin/big_random_buf.rs
Normal file
18
wasi-common/wasi-misc-tests/src/bin/big_random_buf.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use wasi::wasi_unstable;
|
||||||
|
|
||||||
|
fn test_big_random_buf() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
buf.resize(1024, 0);
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::random_get(&mut buf).is_ok(),
|
||||||
|
"calling get_random on a large buffer"
|
||||||
|
);
|
||||||
|
// Chances are pretty good that at least *one* byte will be non-zero in
|
||||||
|
// any meaningful random function producing 1024 u8 values.
|
||||||
|
assert!(buf.iter().any(|x| *x != 0), "random_get returned all zeros");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Run the tests.
|
||||||
|
test_big_random_buf()
|
||||||
|
}
|
||||||
37
wasi-common/wasi-misc-tests/src/bin/clock_time_get.rs
Normal file
37
wasi-common/wasi-misc-tests/src/bin/clock_time_get.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use more_asserts::assert_le;
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_clock_time_get;
|
||||||
|
|
||||||
|
unsafe fn test_clock_time_get() {
|
||||||
|
// Test that clock_time_get succeeds. Even in environments where it's not
|
||||||
|
// desirable to expose high-precision timers, it should still succeed.
|
||||||
|
// clock_res_get is where information about precision can be provided.
|
||||||
|
let mut time: wasi_unstable::Timestamp = 0;
|
||||||
|
let status = wasi_clock_time_get(wasi_unstable::CLOCK_MONOTONIC, 1, &mut time);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"clock_time_get with a precision of 1"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_clock_time_get(wasi_unstable::CLOCK_MONOTONIC, 0, &mut time);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"clock_time_get with a precision of 0"
|
||||||
|
);
|
||||||
|
let first_time = time;
|
||||||
|
|
||||||
|
let status = wasi_clock_time_get(wasi_unstable::CLOCK_MONOTONIC, 0, &mut time);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"clock_time_get with a precision of 0"
|
||||||
|
);
|
||||||
|
assert_le!(first_time, time, "CLOCK_MONOTONIC should be monotonic");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_clock_time_get() }
|
||||||
|
}
|
||||||
83
wasi-common/wasi-misc-tests/src/bin/close_preopen.rs
Normal file
83
wasi-common/wasi-misc-tests/src/bin/close_preopen.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, mem, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_fd_fdstat_get;
|
||||||
|
|
||||||
|
unsafe fn test_close_preopen(dir_fd: wasi_unstable::Fd) {
|
||||||
|
let pre_fd: wasi_unstable::Fd = (libc::STDERR_FILENO + 1) as wasi_unstable::Fd;
|
||||||
|
|
||||||
|
assert_gt!(dir_fd, pre_fd, "dir_fd number");
|
||||||
|
|
||||||
|
// Try to close a preopened directory handle.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_unstable::fd_close(pre_fd),
|
||||||
|
Err(wasi_unstable::ENOTSUP),
|
||||||
|
"closing a preopened file descriptor",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to renumber over a preopened directory handle.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_unstable::fd_renumber(dir_fd, pre_fd),
|
||||||
|
Err(wasi_unstable::ENOTSUP),
|
||||||
|
"renumbering over a preopened file descriptor",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure that dir_fd is still open.
|
||||||
|
let mut dir_fdstat: wasi_unstable::FdStat = mem::zeroed();
|
||||||
|
let mut status = wasi_fd_fdstat_get(dir_fd, &mut dir_fdstat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"calling fd_fdstat on the scratch directory"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
dir_fdstat.fs_filetype,
|
||||||
|
wasi_unstable::FILETYPE_DIRECTORY,
|
||||||
|
"expected the scratch directory to be a directory",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to renumber a preopened directory handle.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_unstable::fd_renumber(pre_fd, dir_fd),
|
||||||
|
Err(wasi_unstable::ENOTSUP),
|
||||||
|
"renumbering over a preopened file descriptor",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure that dir_fd is still open.
|
||||||
|
status = wasi_fd_fdstat_get(dir_fd, &mut dir_fdstat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"calling fd_fdstat on the scratch directory"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
dir_fdstat.fs_filetype,
|
||||||
|
wasi_unstable::FILETYPE_DIRECTORY,
|
||||||
|
"expected the scratch directory to be a directory",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_close_preopen(dir_fd) }
|
||||||
|
}
|
||||||
62
wasi-common/wasi-misc-tests/src/bin/dangling_symlink.rs
Normal file
62
wasi-common/wasi-misc-tests/src/bin/dangling_symlink.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::cleanup_file;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_path_open, wasi_path_symlink};
|
||||||
|
|
||||||
|
unsafe fn test_dangling_symlink(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// First create a dangling symlink.
|
||||||
|
assert!(
|
||||||
|
wasi_path_symlink("target", dir_fd, "symlink").is_ok(),
|
||||||
|
"creating a symlink"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it as a directory with O_NOFOLLOW.
|
||||||
|
let mut file_fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"symlink",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ELOOP,
|
||||||
|
"opening a dangling symlink as a directory",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
cleanup_file(dir_fd, "symlink");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_dangling_symlink(dir_fd) }
|
||||||
|
}
|
||||||
90
wasi-common/wasi-misc-tests/src/bin/directory_seek.rs
Normal file
90
wasi-common/wasi-misc-tests/src/bin/directory_seek.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, mem, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_dir, close_fd, create_dir};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_fdstat_get, wasi_fd_seek, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_directory_seek(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a directory in the scratch directory.
|
||||||
|
create_dir(dir_fd, "dir");
|
||||||
|
|
||||||
|
// Open the directory and attempt to request rights for seeking.
|
||||||
|
let mut fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"dir",
|
||||||
|
0,
|
||||||
|
wasi_unstable::RIGHT_FD_SEEK,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Attempt to seek.
|
||||||
|
let mut newoffset = 1;
|
||||||
|
status = wasi_fd_seek(fd, 0, wasi_unstable::WHENCE_CUR, &mut newoffset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTCAPABLE,
|
||||||
|
"seek on a directory"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if we obtained the right to seek.
|
||||||
|
let mut fdstat: wasi_unstable::FdStat = mem::zeroed();
|
||||||
|
status = wasi_fd_fdstat_get(fd, &mut fdstat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"calling fd_fdstat on a directory"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fdstat.fs_filetype,
|
||||||
|
wasi_unstable::FILETYPE_DIRECTORY,
|
||||||
|
"expected the scratch directory to be a directory",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
(fdstat.fs_rights_base & wasi_unstable::RIGHT_FD_SEEK),
|
||||||
|
0,
|
||||||
|
"directory has the seek right",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
close_fd(fd);
|
||||||
|
cleanup_dir(dir_fd, "dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_directory_seek(dir_fd) }
|
||||||
|
}
|
||||||
98
wasi-common/wasi-misc-tests/src/bin/fd_advise.rs
Normal file
98
wasi-common/wasi-misc-tests/src/bin/fd_advise.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_advise, wasi_fd_filestat_get, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_fd_advise(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check file size
|
||||||
|
let mut stat = wasi_unstable::FileStat {
|
||||||
|
st_dev: 0,
|
||||||
|
st_ino: 0,
|
||||||
|
st_filetype: 0,
|
||||||
|
st_nlink: 0,
|
||||||
|
st_size: 0,
|
||||||
|
st_atim: 0,
|
||||||
|
st_mtim: 0,
|
||||||
|
st_ctim: 0,
|
||||||
|
};
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 0, "file size should be 0");
|
||||||
|
|
||||||
|
// Allocate some size
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_allocate(file_fd, 0, 100).is_ok(),
|
||||||
|
"allocating size"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after initial allocation"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 100, "file size should be 100");
|
||||||
|
|
||||||
|
// Advise the kernel
|
||||||
|
let status = wasi_fd_advise(file_fd, 10, 50, wasi_unstable::ADVICE_NORMAL);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"advising the kernel"
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_fd_advise(dir_fd) }
|
||||||
|
}
|
||||||
120
wasi-common/wasi-misc-tests/src/bin/fd_filestat_set.rs
Normal file
120
wasi-common/wasi-misc-tests/src/bin/fd_filestat_set.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_filestat_get, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_fd_filestat_set(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check file size
|
||||||
|
let mut stat = wasi_unstable::FileStat {
|
||||||
|
st_dev: 0,
|
||||||
|
st_ino: 0,
|
||||||
|
st_filetype: 0,
|
||||||
|
st_nlink: 0,
|
||||||
|
st_size: 0,
|
||||||
|
st_atim: 0,
|
||||||
|
st_mtim: 0,
|
||||||
|
st_ctim: 0,
|
||||||
|
};
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 0, "file size should be 0");
|
||||||
|
|
||||||
|
// Check fd_filestat_set_size
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_filestat_set_size(file_fd, 100).is_ok(),
|
||||||
|
"fd_filestat_set_size"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after fd_filestat_set_size"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 100, "file size should be 100");
|
||||||
|
|
||||||
|
// Check fd_filestat_set_times
|
||||||
|
let old_atim = stat.st_atim;
|
||||||
|
let new_mtim = stat.st_mtim - 100;
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_filestat_set_times(
|
||||||
|
file_fd,
|
||||||
|
new_mtim,
|
||||||
|
new_mtim,
|
||||||
|
wasi_unstable::FILESTAT_SET_MTIM,
|
||||||
|
)
|
||||||
|
.is_ok(),
|
||||||
|
"fd_filestat_set_times"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after fd_filestat_set_times"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
stat.st_size, 100,
|
||||||
|
"file size should remain unchanged at 100"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_mtim, new_mtim, "mtim should change");
|
||||||
|
assert_eq!(stat.st_atim, old_atim, "atim should not change");
|
||||||
|
|
||||||
|
// let status = wasi_fd_filestat_set_times(file_fd, new_mtim, new_mtim, wasi_unstable::FILESTAT_SET_MTIM | wasi_unstable::FILESTAT_SET_MTIM_NOW);
|
||||||
|
// assert_eq!(status, wasi_unstable::EINVAL, "ATIM & ATIM_NOW can't both be set");
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_fd_filestat_set(dir_fd) }
|
||||||
|
}
|
||||||
189
wasi-common/wasi-misc-tests/src/bin/fd_readdir.rs
Normal file
189
wasi-common/wasi-misc-tests/src/bin/fd_readdir.rs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{cmp::min, env, mem, process, slice, str};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_filestat_get, wasi_fd_readdir, wasi_path_open};
|
||||||
|
|
||||||
|
const BUF_LEN: usize = 256;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DirEntry {
|
||||||
|
dirent: wasi_unstable::Dirent,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually reading the output from fd_readdir is tedious and repetitive,
|
||||||
|
// so encapsulate it into an iterator
|
||||||
|
struct ReadDir<'a> {
|
||||||
|
buf: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ReadDir<'a> {
|
||||||
|
fn from_slice(buf: &'a [u8]) -> Self {
|
||||||
|
Self { buf }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ReadDir<'a> {
|
||||||
|
type Item = DirEntry;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<DirEntry> {
|
||||||
|
unsafe {
|
||||||
|
if self.buf.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the data
|
||||||
|
let dirent_ptr = self.buf.as_ptr() as *const wasi_unstable::Dirent;
|
||||||
|
let dirent = *dirent_ptr;
|
||||||
|
let name_ptr = dirent_ptr.offset(1) as *const u8;
|
||||||
|
// NOTE Linux syscall returns a NULL-terminated name, but WASI doesn't
|
||||||
|
let namelen = dirent.d_namlen as usize;
|
||||||
|
let slice = slice::from_raw_parts(name_ptr, namelen);
|
||||||
|
let name = str::from_utf8(slice).expect("invalid utf8").to_owned();
|
||||||
|
|
||||||
|
// Update the internal state
|
||||||
|
let delta = mem::size_of_val(&dirent) + namelen;
|
||||||
|
self.buf = &self.buf[delta..];
|
||||||
|
|
||||||
|
DirEntry { dirent, name }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn exec_fd_readdir(
|
||||||
|
fd: wasi_unstable::Fd,
|
||||||
|
cookie: wasi_unstable::DirCookie,
|
||||||
|
) -> Vec<DirEntry> {
|
||||||
|
let mut buf: [u8; BUF_LEN] = [0; BUF_LEN];
|
||||||
|
let mut bufused = 0;
|
||||||
|
let status = wasi_fd_readdir(fd, &mut buf, BUF_LEN, cookie, &mut bufused);
|
||||||
|
assert_eq!(status, wasi_unstable::raw::__WASI_ESUCCESS, "fd_readdir");
|
||||||
|
|
||||||
|
let sl = slice::from_raw_parts(buf.as_ptr(), min(BUF_LEN, bufused));
|
||||||
|
let dirs: Vec<_> = ReadDir::from_slice(sl).collect();
|
||||||
|
dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_fd_readdir(dir_fd: wasi_unstable::Fd) {
|
||||||
|
let mut stat: wasi_unstable::FileStat = mem::zeroed();
|
||||||
|
let status = wasi_fd_filestat_get(dir_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading scratch directory stats"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check the behavior in an empty directory
|
||||||
|
let mut dirs = exec_fd_readdir(dir_fd, wasi_unstable::DIRCOOKIE_START);
|
||||||
|
dirs.sort_by_key(|d| d.name.clone());
|
||||||
|
assert_eq!(dirs.len(), 2, "expected two entries in an empty directory");
|
||||||
|
let mut dirs = dirs.into_iter();
|
||||||
|
|
||||||
|
// the first entry should be `.`
|
||||||
|
let dir = dirs.next().expect("first entry is None");
|
||||||
|
assert_eq!(dir.name, ".", "first name");
|
||||||
|
assert_eq!(
|
||||||
|
dir.dirent.d_type,
|
||||||
|
wasi_unstable::FILETYPE_DIRECTORY,
|
||||||
|
"first type"
|
||||||
|
);
|
||||||
|
assert_eq!(dir.dirent.d_ino, stat.st_ino);
|
||||||
|
assert_eq!(dir.dirent.d_namlen, 1);
|
||||||
|
|
||||||
|
// the second entry should be `..`
|
||||||
|
let dir = dirs.next().expect("second entry is None");
|
||||||
|
assert_eq!(dir.name, "..", "second name");
|
||||||
|
assert_eq!(
|
||||||
|
dir.dirent.d_type,
|
||||||
|
wasi_unstable::FILETYPE_DIRECTORY,
|
||||||
|
"second type"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
dirs.next().is_none(),
|
||||||
|
"the directory should be seen as empty"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a file and check the behavior
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute another readdir
|
||||||
|
let mut dirs = exec_fd_readdir(dir_fd, wasi_unstable::DIRCOOKIE_START);
|
||||||
|
assert_eq!(dirs.len(), 3, "expected three entries");
|
||||||
|
// Save the data about the last entry. We need to do it before sorting.
|
||||||
|
let lastfile_cookie = dirs[1].dirent.d_next;
|
||||||
|
let lastfile_name = dirs[2].name.clone();
|
||||||
|
dirs.sort_by_key(|d| d.name.clone());
|
||||||
|
let mut dirs = dirs.into_iter();
|
||||||
|
|
||||||
|
let dir = dirs.next().expect("first entry is None");
|
||||||
|
assert_eq!(dir.name, ".", "first name");
|
||||||
|
let dir = dirs.next().expect("second entry is None");
|
||||||
|
assert_eq!(dir.name, "..", "second name");
|
||||||
|
let dir = dirs.next().expect("third entry is None");
|
||||||
|
// check the file info
|
||||||
|
assert_eq!(dir.name, "file", "file name doesn't match");
|
||||||
|
assert_eq!(
|
||||||
|
dir.dirent.d_type,
|
||||||
|
wasi_unstable::FILETYPE_REGULAR_FILE,
|
||||||
|
"type for the real file"
|
||||||
|
);
|
||||||
|
assert_eq!(dir.dirent.d_ino, stat.st_ino);
|
||||||
|
|
||||||
|
// check if cookie works as expected
|
||||||
|
let dirs = exec_fd_readdir(dir_fd, lastfile_cookie);
|
||||||
|
assert_eq!(dirs.len(), 1, "expected one entry");
|
||||||
|
assert_eq!(dirs[0].name, lastfile_name, "name of the only entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_fd_readdir(dir_fd) }
|
||||||
|
}
|
||||||
125
wasi-common/wasi-misc-tests/src/bin/file_allocate.rs
Normal file
125
wasi-common/wasi-misc-tests/src/bin/file_allocate.rs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_filestat_get, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_file_allocate(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check file size
|
||||||
|
let mut stat = wasi_unstable::FileStat {
|
||||||
|
st_dev: 0,
|
||||||
|
st_ino: 0,
|
||||||
|
st_filetype: 0,
|
||||||
|
st_nlink: 0,
|
||||||
|
st_size: 0,
|
||||||
|
st_atim: 0,
|
||||||
|
st_mtim: 0,
|
||||||
|
st_ctim: 0,
|
||||||
|
};
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 0, "file size should be 0");
|
||||||
|
|
||||||
|
// Allocate some size
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_allocate(file_fd, 0, 100).is_ok(),
|
||||||
|
"allocating size"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after initial allocation"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 100, "file size should be 100");
|
||||||
|
|
||||||
|
// Allocate should not modify if less than current size
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_allocate(file_fd, 10, 10).is_ok(),
|
||||||
|
"allocating size less than current size"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after additional allocation was not required"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
stat.st_size, 100,
|
||||||
|
"file size should remain unchanged at 100"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Allocate should modify if offset+len > current_len
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_allocate(file_fd, 90, 20).is_ok(),
|
||||||
|
"allocating size larger than current size"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_filestat_get(file_fd, &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after additional allocation was required"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
stat.st_size, 110,
|
||||||
|
"file size should increase from 100 to 110"
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_file_allocate(dir_fd) }
|
||||||
|
}
|
||||||
131
wasi-common/wasi-misc-tests/src/bin/file_pread_pwrite.rs
Normal file
131
wasi-common/wasi-misc-tests/src/bin/file_pread_pwrite.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_pread, wasi_fd_pwrite, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_file_pread_pwrite(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
let contents = &[0u8, 1, 2, 3];
|
||||||
|
let ciovec = wasi_unstable::CIoVec {
|
||||||
|
buf: contents.as_ptr() as *const libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nwritten = 0;
|
||||||
|
status = wasi_fd_pwrite(file_fd, &mut [ciovec], 0, &mut nwritten);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"writing bytes at offset 0"
|
||||||
|
);
|
||||||
|
assert_eq!(nwritten, 4, "nwritten bytes check");
|
||||||
|
|
||||||
|
let contents = &mut [0u8; 4];
|
||||||
|
let iovec = wasi_unstable::IoVec {
|
||||||
|
buf: contents.as_mut_ptr() as *mut libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nread = 0;
|
||||||
|
status = wasi_fd_pread(file_fd, &[iovec], 0, &mut nread);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading bytes at offset 0"
|
||||||
|
);
|
||||||
|
assert_eq!(nread, 4, "nread bytes check");
|
||||||
|
assert_eq!(contents, &[0u8, 1, 2, 3], "written bytes equal read bytes");
|
||||||
|
|
||||||
|
let contents = &mut [0u8; 4];
|
||||||
|
let iovec = wasi_unstable::IoVec {
|
||||||
|
buf: contents.as_mut_ptr() as *mut libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nread = 0;
|
||||||
|
status = wasi_fd_pread(file_fd, &[iovec], 2, &mut nread);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading bytes at offset 2"
|
||||||
|
);
|
||||||
|
assert_eq!(nread, 2, "nread bytes check");
|
||||||
|
assert_eq!(contents, &[2u8, 3, 0, 0], "file cursor was overwritten");
|
||||||
|
|
||||||
|
let contents = &[1u8, 0];
|
||||||
|
let ciovec = wasi_unstable::CIoVec {
|
||||||
|
buf: contents.as_ptr() as *const libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nwritten = 0;
|
||||||
|
status = wasi_fd_pwrite(file_fd, &mut [ciovec], 2, &mut nwritten);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"writing bytes at offset 2"
|
||||||
|
);
|
||||||
|
assert_eq!(nwritten, 2, "nwritten bytes check");
|
||||||
|
|
||||||
|
let contents = &mut [0u8; 4];
|
||||||
|
let iovec = wasi_unstable::IoVec {
|
||||||
|
buf: contents.as_mut_ptr() as *mut libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nread = 0;
|
||||||
|
status = wasi_fd_pread(file_fd, &[iovec], 0, &mut nread);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading bytes at offset 0"
|
||||||
|
);
|
||||||
|
assert_eq!(nread, 4, "nread bytes check");
|
||||||
|
assert_eq!(contents, &[0u8, 1, 1, 0], "file cursor was overwritten");
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_file_pread_pwrite(dir_fd) }
|
||||||
|
}
|
||||||
133
wasi-common/wasi-misc-tests/src/bin/file_seek_tell.rs
Normal file
133
wasi-common/wasi-misc-tests/src/bin/file_seek_tell.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_seek, wasi_fd_tell, wasi_fd_write, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_file_seek_tell(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check current offset
|
||||||
|
let mut offset: wasi_unstable::FileSize = 0;
|
||||||
|
status = wasi_fd_tell(file_fd, &mut offset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"getting initial file offset"
|
||||||
|
);
|
||||||
|
assert_eq!(offset, 0, "current offset should be 0");
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
let buf = &[0u8; 100];
|
||||||
|
let iov = wasi_unstable::CIoVec {
|
||||||
|
buf: buf.as_ptr() as *const _,
|
||||||
|
buf_len: buf.len(),
|
||||||
|
};
|
||||||
|
let iovs = &[iov];
|
||||||
|
let mut nwritten = 0;
|
||||||
|
status = wasi_fd_write(file_fd, iovs, &mut nwritten);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"writing to a file"
|
||||||
|
);
|
||||||
|
assert_eq!(nwritten, 100, "should write 100 bytes to file");
|
||||||
|
|
||||||
|
// Check current offset
|
||||||
|
status = wasi_fd_tell(file_fd, &mut offset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"getting file offset after writing"
|
||||||
|
);
|
||||||
|
assert_eq!(offset, 100, "offset after writing should be 100");
|
||||||
|
|
||||||
|
// Seek to middle of the file
|
||||||
|
let mut newoffset = 1;
|
||||||
|
status = wasi_fd_seek(file_fd, -50, wasi_unstable::WHENCE_CUR, &mut newoffset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"seeking to the middle of a file"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
newoffset, 50,
|
||||||
|
"offset after seeking to the middle should be at 50"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seek to the beginning of the file
|
||||||
|
status = wasi_fd_seek(file_fd, 0, wasi_unstable::WHENCE_SET, &mut newoffset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"seeking to the beginning of the file"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
newoffset, 0,
|
||||||
|
"offset after seeking to the beginning of the file should be at 0"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seek beyond the file should be possible
|
||||||
|
status = wasi_fd_seek(file_fd, 1000, wasi_unstable::WHENCE_CUR, &mut newoffset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"seeking beyond the end of the file"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seek before byte 0 is an error though
|
||||||
|
status = wasi_fd_seek(file_fd, -2000, wasi_unstable::WHENCE_CUR, &mut newoffset);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_EINVAL,
|
||||||
|
"seeking before byte 0 is an error"
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_file_seek_tell(dir_fd) }
|
||||||
|
}
|
||||||
116
wasi-common/wasi-misc-tests/src/bin/file_unbuffered_write.rs
Normal file
116
wasi-common/wasi-misc-tests/src/bin/file_unbuffered_write.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_read, wasi_fd_write, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_file_unbuffered_write(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create file
|
||||||
|
create_file(dir_fd, "file");
|
||||||
|
|
||||||
|
// Open file for reading
|
||||||
|
let mut fd_read = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
0,
|
||||||
|
wasi_unstable::RIGHT_FD_READ,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd_read,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd_read,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Open the same file but for writing
|
||||||
|
let mut fd_write = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
0,
|
||||||
|
wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd_write,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd_write,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
let contents = &[1u8];
|
||||||
|
let ciovec = wasi_unstable::CIoVec {
|
||||||
|
buf: contents.as_ptr() as *const libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nwritten = 0;
|
||||||
|
status = wasi_fd_write(fd_write, &[ciovec], &mut nwritten);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"writing byte to file"
|
||||||
|
);
|
||||||
|
assert_eq!(nwritten, 1, "nwritten bytes check");
|
||||||
|
|
||||||
|
// Read from file
|
||||||
|
let contents = &mut [0u8; 1];
|
||||||
|
let iovec = wasi_unstable::IoVec {
|
||||||
|
buf: contents.as_mut_ptr() as *mut libc::c_void,
|
||||||
|
buf_len: contents.len(),
|
||||||
|
};
|
||||||
|
let mut nread = 0;
|
||||||
|
status = wasi_fd_read(fd_read, &[iovec], &mut nread);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading bytes from file"
|
||||||
|
);
|
||||||
|
assert_eq!(nread, 1, "nread bytes check");
|
||||||
|
assert_eq!(contents, &[1u8], "written bytes equal read bytes");
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
close_fd(fd_write);
|
||||||
|
close_fd(fd_read);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_file_unbuffered_write(dir_fd) }
|
||||||
|
}
|
||||||
173
wasi-common/wasi-misc-tests/src/bin/interesting_paths.rs
Normal file
173
wasi-common/wasi-misc-tests/src/bin/interesting_paths.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{close_fd, create_dir, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{
|
||||||
|
wasi_path_open, wasi_path_remove_directory, wasi_path_unlink_file,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe fn test_interesting_paths(dir_fd: wasi_unstable::Fd, arg: &str) {
|
||||||
|
// Create a directory in the scratch directory.
|
||||||
|
create_dir(dir_fd, "dir");
|
||||||
|
|
||||||
|
// Create a directory in the directory we just created.
|
||||||
|
create_dir(dir_fd, "dir/nested");
|
||||||
|
|
||||||
|
// Create a file in the nested directory.
|
||||||
|
create_file(dir_fd, "dir/nested/file");
|
||||||
|
|
||||||
|
// Now open it with an absolute path.
|
||||||
|
let mut file_fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(dir_fd, 0, "/dir/nested/file", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTCAPABLE,
|
||||||
|
"opening a file with an absolute path"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now open it with a path containing "..".
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"dir/.//nested/../../dir/nested/../nested///./file",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file with \"..\" in the path"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
close_fd(file_fd);
|
||||||
|
|
||||||
|
// Now open it with a trailing NUL.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "dir/nested/file\0", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_EILSEQ,
|
||||||
|
"opening a file with a trailing NUL"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now open it with a trailing slash.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "dir/nested/file/", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTDIR,
|
||||||
|
"opening a file with a trailing slash"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now open it with trailing slashes.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "dir/nested/file///", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTDIR,
|
||||||
|
"opening a file with trailing slashes"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now open the directory with a trailing slash.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "dir/nested/", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a directory with a trailing slash"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
close_fd(file_fd);
|
||||||
|
|
||||||
|
// Now open the directory with trailing slashes.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "dir/nested///", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a directory with trailing slashes"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
close_fd(file_fd);
|
||||||
|
|
||||||
|
// Now open it with a path containing too many ".."s.
|
||||||
|
let bad_path = format!("dir/nested/../../../{}/dir/nested/file", arg);
|
||||||
|
status = wasi_path_open(dir_fd, 0, &bad_path, 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTCAPABLE,
|
||||||
|
"opening a file with too many \"..\"s in the path"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
wasi_path_unlink_file(dir_fd, "dir/nested/file").is_ok(),
|
||||||
|
"unlink_file on a symlink should succeed"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "dir/nested").is_ok(),
|
||||||
|
"remove_directory on a directory should succeed"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "dir").is_ok(),
|
||||||
|
"remove_directory on a directory should succeed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_interesting_paths(dir_fd, &arg) }
|
||||||
|
}
|
||||||
63
wasi-common/wasi-misc-tests/src/bin/isatty.rs
Normal file
63
wasi-common/wasi-misc-tests/src/bin/isatty.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_path_open;
|
||||||
|
|
||||||
|
unsafe fn test_isatty(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory and test if it's a tty.
|
||||||
|
let mut file_fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
libc::isatty(file_fd as std::os::raw::c_int),
|
||||||
|
0,
|
||||||
|
"file is a tty"
|
||||||
|
);
|
||||||
|
close_fd(file_fd);
|
||||||
|
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_isatty(dir_fd) }
|
||||||
|
}
|
||||||
177
wasi-common/wasi-misc-tests/src/bin/nofollow_errors.rs
Normal file
177
wasi-common/wasi-misc-tests/src/bin/nofollow_errors.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd, create_dir, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{
|
||||||
|
wasi_path_open, wasi_path_remove_directory, wasi_path_symlink,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe fn test_nofollow_errors(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a directory for the symlink to point to.
|
||||||
|
create_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
// Create a symlink.
|
||||||
|
assert!(
|
||||||
|
wasi_path_symlink("target", dir_fd, "symlink").is_ok(),
|
||||||
|
"creating a symlink"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it as a directory with O_NOFOLLOW again.
|
||||||
|
let mut file_fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"symlink",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ELOOP,
|
||||||
|
"opening a directory symlink as a directory",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it with just O_NOFOLLOW.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "symlink", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ELOOP,
|
||||||
|
"opening a symlink with O_NOFOLLOW should return ELOOP",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it as a directory without O_NOFOLLOW.
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
wasi_unstable::LOOKUP_SYMLINK_FOLLOW,
|
||||||
|
"symlink",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a symlink as a directory"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
close_fd(file_fd);
|
||||||
|
|
||||||
|
// Replace the target directory with a file.
|
||||||
|
cleanup_file(dir_fd, "symlink");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "target").is_ok(),
|
||||||
|
"remove_directory on a directory should succeed"
|
||||||
|
);
|
||||||
|
create_file(dir_fd, "target");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
wasi_path_symlink("target", dir_fd, "symlink").is_ok(),
|
||||||
|
"creating a symlink"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it as a directory with O_NOFOLLOW again.
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"symlink",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ELOOP,
|
||||||
|
"opening a directory symlink as a directory",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it with just O_NOFOLLOW.
|
||||||
|
status = wasi_path_open(dir_fd, 0, "symlink", 0, 0, 0, 0, &mut file_fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ELOOP,
|
||||||
|
"opening a symlink with O_NOFOLLOW should return ELOOP",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to open it as a directory without O_NOFOLLOW.
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
wasi_unstable::LOOKUP_SYMLINK_FOLLOW,
|
||||||
|
"symlink",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTDIR,
|
||||||
|
"opening a symlink to a file as a directory",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file_fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
cleanup_file(dir_fd, "symlink");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_nofollow_errors(dir_fd) }
|
||||||
|
}
|
||||||
184
wasi-common/wasi-misc-tests/src/bin/path_filestat.rs
Normal file
184
wasi-common/wasi-misc-tests/src/bin/path_filestat.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, close_fd};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{
|
||||||
|
wasi_fd_fdstat_get, wasi_path_filestat_get, wasi_path_filestat_set_times, wasi_path_open,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe fn test_path_filestat(dir_fd: wasi_unstable::Fd) {
|
||||||
|
let mut fdstat: wasi_unstable::FdStat = std::mem::zeroed();
|
||||||
|
let status = wasi_fd_fdstat_get(dir_fd, &mut fdstat);
|
||||||
|
assert_eq!(status, wasi_unstable::raw::__WASI_ESUCCESS, "fd_fdstat_get");
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
fdstat.fs_rights_base & wasi_unstable::RIGHT_PATH_FILESTAT_GET,
|
||||||
|
0,
|
||||||
|
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
fdstat.fs_rights_inheriting & wasi_unstable::RIGHT_PATH_FILESTAT_GET,
|
||||||
|
0,
|
||||||
|
"the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let filename = "file";
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
filename,
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ
|
||||||
|
| wasi_unstable::RIGHT_FD_WRITE
|
||||||
|
| wasi_unstable::RIGHT_PATH_FILESTAT_GET,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_fd_fdstat_get(file_fd, &mut fdstat);
|
||||||
|
assert_eq!(status, wasi_unstable::raw::__WASI_ESUCCESS, "fd_fdstat_get");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fdstat.fs_rights_base & wasi_unstable::RIGHT_PATH_FILESTAT_GET,
|
||||||
|
0,
|
||||||
|
"files shouldn't have rights for path_* syscalls even if manually given",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fdstat.fs_rights_inheriting & wasi_unstable::RIGHT_PATH_FILESTAT_GET,
|
||||||
|
0,
|
||||||
|
"files shouldn't have rights for path_* syscalls even if manually given",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check file size
|
||||||
|
let mut stat = wasi_unstable::FileStat {
|
||||||
|
st_dev: 0,
|
||||||
|
st_ino: 0,
|
||||||
|
st_filetype: 0,
|
||||||
|
st_nlink: 0,
|
||||||
|
st_size: 0,
|
||||||
|
st_atim: 0,
|
||||||
|
st_mtim: 0,
|
||||||
|
st_ctim: 0,
|
||||||
|
};
|
||||||
|
let status = wasi_path_filestat_get(dir_fd, 0, filename, filename.len(), &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_size, 0, "file size should be 0");
|
||||||
|
|
||||||
|
// Check path_filestat_set_times
|
||||||
|
let old_atim = stat.st_atim;
|
||||||
|
let new_mtim = stat.st_mtim - 100;
|
||||||
|
assert!(
|
||||||
|
wasi_path_filestat_set_times(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
filename,
|
||||||
|
// on purpose: the syscall should not touch atim, because
|
||||||
|
// neither of the ATIM flags is set
|
||||||
|
new_mtim,
|
||||||
|
new_mtim,
|
||||||
|
wasi_unstable::FILESTAT_SET_MTIM,
|
||||||
|
)
|
||||||
|
.is_ok(),
|
||||||
|
"path_filestat_set_times should succeed"
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = wasi_path_filestat_get(dir_fd, 0, filename, filename.len(), &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after path_filestat_set_times"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_mtim, new_mtim, "mtim should change");
|
||||||
|
assert_eq!(stat.st_atim, old_atim, "atim should not change");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_filestat_set_times(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
filename,
|
||||||
|
new_mtim,
|
||||||
|
new_mtim,
|
||||||
|
wasi_unstable::FILESTAT_SET_MTIM | wasi_unstable::FILESTAT_SET_MTIM_NOW,
|
||||||
|
),
|
||||||
|
Err(wasi_unstable::EINVAL),
|
||||||
|
"MTIM & MTIM_NOW can't both be set"
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if the times were untouched
|
||||||
|
let status = wasi_path_filestat_get(dir_fd, 0, filename, filename.len(), &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after EINVAL fd_filestat_set_times"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_mtim, new_mtim, "mtim should not change");
|
||||||
|
assert_eq!(stat.st_atim, old_atim, "atim should not change");
|
||||||
|
|
||||||
|
let new_atim = old_atim - 100;
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_filestat_set_times(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
filename,
|
||||||
|
new_atim,
|
||||||
|
new_atim,
|
||||||
|
wasi_unstable::FILESTAT_SET_ATIM | wasi_unstable::FILESTAT_SET_ATIM_NOW,
|
||||||
|
),
|
||||||
|
Err(wasi_unstable::EINVAL),
|
||||||
|
"ATIM & ATIM_NOW can't both be set"
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if the times were untouched
|
||||||
|
let status = wasi_path_filestat_get(dir_fd, 0, filename, filename.len(), &mut stat);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"reading file stats after EINVAL path_filestat_set_times"
|
||||||
|
);
|
||||||
|
assert_eq!(stat.st_mtim, new_mtim, "mtim should not change");
|
||||||
|
assert_eq!(stat.st_atim, old_atim, "atim should not change");
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_path_filestat(dir_fd) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::close_fd;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_path_open;
|
||||||
|
|
||||||
|
unsafe fn test_dirfd_not_dir(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Open a file.
|
||||||
|
let mut file_fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now try to open a file underneath it as if it were a directory.
|
||||||
|
let mut new_file_fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
status = wasi_path_open(
|
||||||
|
file_fd,
|
||||||
|
0,
|
||||||
|
"foo",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut new_file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOTDIR,
|
||||||
|
"non-directory base fd should get ENOTDIR"
|
||||||
|
);
|
||||||
|
close_fd(file_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_dirfd_not_dir(dir_fd) }
|
||||||
|
}
|
||||||
259
wasi-common/wasi-misc-tests/src/bin/path_rename.rs
Normal file
259
wasi-common/wasi-misc-tests/src/bin/path_rename.rs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_dir, cleanup_file, close_fd, create_dir, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_path_open, wasi_path_rename};
|
||||||
|
|
||||||
|
unsafe fn test_path_rename(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// First, try renaming a dir to nonexistent path
|
||||||
|
// Create source directory
|
||||||
|
create_dir(dir_fd, "source");
|
||||||
|
|
||||||
|
// Try renaming the directory
|
||||||
|
assert!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target").is_ok(),
|
||||||
|
"renaming a directory"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that source directory doesn't exist anymore
|
||||||
|
let mut fd: wasi_unstable::Fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"source",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOENT,
|
||||||
|
"opening a nonexistent path as a directory"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that target directory exists
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"target",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening renamed path as a directory"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(fd);
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
// Now, try renaming renaming a dir to existing empty dir
|
||||||
|
create_dir(dir_fd, "source");
|
||||||
|
create_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target").is_ok(),
|
||||||
|
"renaming a directory"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that source directory doesn't exist anymore
|
||||||
|
fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"source",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOENT,
|
||||||
|
"opening a nonexistent path as a directory"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that target directory exists
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"target",
|
||||||
|
wasi_unstable::O_DIRECTORY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening renamed path as a directory"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(fd);
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
// Now, try renaming a dir to existing non-empty dir
|
||||||
|
create_dir(dir_fd, "source");
|
||||||
|
create_dir(dir_fd, "target");
|
||||||
|
create_file(dir_fd, "target/file");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target"),
|
||||||
|
Err(wasi_unstable::ENOTEMPTY),
|
||||||
|
"renaming directory to a nonempty directory"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try renaming dir to a file
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target/file"),
|
||||||
|
Err(wasi_unstable::ENOTDIR),
|
||||||
|
"renaming directory to a file"
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup_file(dir_fd, "target/file");
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
cleanup_dir(dir_fd, "source");
|
||||||
|
|
||||||
|
// Now, try renaming a file to a nonexistent path
|
||||||
|
create_file(dir_fd, "source");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target").is_ok(),
|
||||||
|
"renaming a file"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that source file doesn't exist anymore
|
||||||
|
fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
status = wasi_path_open(dir_fd, 0, "source", 0, 0, 0, 0, &mut fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOENT,
|
||||||
|
"opening a nonexistent path"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that target file exists
|
||||||
|
status = wasi_path_open(dir_fd, 0, "target", 0, 0, 0, 0, &mut fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening renamed path"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(fd);
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
|
||||||
|
// Now, try renaming file to an existing file
|
||||||
|
create_file(dir_fd, "source");
|
||||||
|
create_file(dir_fd, "target");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target").is_ok(),
|
||||||
|
"renaming file to another existing file"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that source file doesn't exist anymore
|
||||||
|
fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
status = wasi_path_open(dir_fd, 0, "source", 0, 0, 0, 0, &mut fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ENOENT,
|
||||||
|
"opening a nonexistent path"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fd,
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
"failed open should set the file descriptor to -1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that target file exists
|
||||||
|
status = wasi_path_open(dir_fd, 0, "target", 0, 0, 0, 0, &mut fd);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening renamed path"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(fd);
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
|
||||||
|
// Try renaming to an (empty) directory instead
|
||||||
|
create_file(dir_fd, "source");
|
||||||
|
create_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target"),
|
||||||
|
Err(wasi_unstable::EISDIR),
|
||||||
|
"renaming file to existing directory"
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
cleanup_file(dir_fd, "source");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_path_rename(dir_fd) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_dir, cleanup_file, create_dir, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_path_rename;
|
||||||
|
|
||||||
|
unsafe fn test_path_rename_trailing_slashes(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Test renaming a file with a trailing slash in the name.
|
||||||
|
create_file(dir_fd, "source");
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source/", dir_fd, "target"),
|
||||||
|
Err(wasi_unstable::ENOTDIR),
|
||||||
|
"renaming a file with a trailing slash in the source name"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source", dir_fd, "target/"),
|
||||||
|
Err(wasi_unstable::ENOTDIR),
|
||||||
|
"renaming a file with a trailing slash in the destination name"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source/", dir_fd, "target/"),
|
||||||
|
Err(wasi_unstable::ENOTDIR),
|
||||||
|
"renaming a file with a trailing slash in the source and destination names"
|
||||||
|
);
|
||||||
|
cleanup_file(dir_fd, "source");
|
||||||
|
|
||||||
|
// Test renaming a directory with a trailing slash in the name.
|
||||||
|
create_dir(dir_fd, "source");
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source/", dir_fd, "target"),
|
||||||
|
Ok(()),
|
||||||
|
"renaming a directory with a trailing slash in the source name"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "target", dir_fd, "source/"),
|
||||||
|
Ok(()),
|
||||||
|
"renaming a directory with a trailing slash in the destination name"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_rename(dir_fd, "source/", dir_fd, "target/"),
|
||||||
|
Ok(()),
|
||||||
|
"renaming a directory with a trailing slash in the source and destination names"
|
||||||
|
);
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_path_rename_trailing_slashes(dir_fd) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_dir, cleanup_file, create_dir, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_path_symlink;
|
||||||
|
|
||||||
|
unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Link destination shouldn't end with a slash.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_symlink("source", dir_fd, "target/"),
|
||||||
|
Err(wasi_unstable::ENOENT),
|
||||||
|
"link destination ending with a slash"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Without the trailing slash, this should succeed.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_symlink("source", dir_fd, "target"),
|
||||||
|
Ok(()),
|
||||||
|
"link destination ending with a slash"
|
||||||
|
);
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
|
||||||
|
// Link destination already exists, target has trailing slash.
|
||||||
|
create_dir(dir_fd, "target");
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_symlink("source", dir_fd, "target/"),
|
||||||
|
Err(wasi_unstable::EEXIST),
|
||||||
|
"link destination already exists"
|
||||||
|
);
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
// Link destination already exists, target has no trailing slash.
|
||||||
|
create_dir(dir_fd, "target");
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_symlink("source", dir_fd, "target"),
|
||||||
|
Err(wasi_unstable::EEXIST),
|
||||||
|
"link destination already exists"
|
||||||
|
);
|
||||||
|
cleanup_dir(dir_fd, "target");
|
||||||
|
|
||||||
|
// Link destination already exists, target has trailing slash.
|
||||||
|
create_file(dir_fd, "target");
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_symlink("source", dir_fd, "target/"),
|
||||||
|
Err(wasi_unstable::EEXIST),
|
||||||
|
"link destination already exists"
|
||||||
|
);
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
|
||||||
|
// Link destination already exists, target has no trailing slash.
|
||||||
|
create_file(dir_fd, "target");
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_symlink("source", dir_fd, "target"),
|
||||||
|
Err(wasi_unstable::EEXIST),
|
||||||
|
"link destination already exists"
|
||||||
|
);
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_path_symlink_trailing_slashes(dir_fd) }
|
||||||
|
}
|
||||||
271
wasi-common/wasi-misc-tests/src/bin/poll_oneoff.rs
Normal file
271
wasi-common/wasi-misc-tests/src/bin/poll_oneoff.rs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, mem::MaybeUninit, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::{
|
||||||
|
open_scratch_directory,
|
||||||
|
utils::{cleanup_file, close_fd},
|
||||||
|
wasi_wrappers::wasi_path_open,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLOCK_ID: wasi_unstable::Userdata = 0x0123_45678;
|
||||||
|
|
||||||
|
unsafe fn poll_oneoff_impl(
|
||||||
|
in_: &[wasi_unstable::Subscription],
|
||||||
|
nexpected: usize,
|
||||||
|
) -> Vec<wasi_unstable::Event> {
|
||||||
|
let mut out: Vec<wasi_unstable::Event> = Vec::new();
|
||||||
|
out.resize_with(in_.len(), || {
|
||||||
|
MaybeUninit::<wasi_unstable::Event>::zeroed().assume_init()
|
||||||
|
});
|
||||||
|
let res = wasi_unstable::poll_oneoff(&in_, out.as_mut_slice());
|
||||||
|
let res = res.expect("poll_oneoff should succeed");
|
||||||
|
assert_eq!(
|
||||||
|
res, nexpected,
|
||||||
|
"poll_oneoff should return {} events",
|
||||||
|
nexpected
|
||||||
|
);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_timeout() {
|
||||||
|
let clock = wasi_unstable::raw::__wasi_subscription_u_clock_t {
|
||||||
|
identifier: CLOCK_ID,
|
||||||
|
clock_id: wasi_unstable::CLOCK_MONOTONIC,
|
||||||
|
timeout: 5_000_000u64, // 5 milliseconds
|
||||||
|
precision: 0,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
let in_ = [wasi_unstable::Subscription {
|
||||||
|
userdata: CLOCK_ID,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_CLOCK,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u { clock },
|
||||||
|
}];
|
||||||
|
let out = poll_oneoff_impl(&in_, 1);
|
||||||
|
let event = &out[0];
|
||||||
|
assert_eq!(
|
||||||
|
event.userdata, CLOCK_ID,
|
||||||
|
"the event.userdata should contain clock_id specified by the user"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
event.error,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"the event.error should be set to ESUCCESS"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
event.type_,
|
||||||
|
wasi_unstable::EVENTTYPE_CLOCK,
|
||||||
|
"the event.type_ should equal clock"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_stdin_read() {
|
||||||
|
let clock = wasi_unstable::raw::__wasi_subscription_u_clock_t {
|
||||||
|
identifier: CLOCK_ID,
|
||||||
|
clock_id: wasi_unstable::CLOCK_MONOTONIC,
|
||||||
|
timeout: 5_000_000u64, // 5 milliseconds
|
||||||
|
precision: 0,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
let fd_readwrite = wasi_unstable::raw::__wasi_subscription_u_fd_readwrite_t {
|
||||||
|
fd: wasi_unstable::STDIN_FD,
|
||||||
|
};
|
||||||
|
let in_ = [
|
||||||
|
wasi_unstable::Subscription {
|
||||||
|
userdata: CLOCK_ID,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_CLOCK,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u { clock },
|
||||||
|
},
|
||||||
|
wasi_unstable::Subscription {
|
||||||
|
userdata: 1,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_FD_READ,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u { fd_readwrite },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let out = poll_oneoff_impl(&in_, 1);
|
||||||
|
let event = &out[0];
|
||||||
|
assert_eq!(
|
||||||
|
event.userdata, CLOCK_ID,
|
||||||
|
"the event.userdata should contain clock_id specified by the user"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
event.error,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"the event.error should be set to ESUCCESS"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
event.type_,
|
||||||
|
wasi_unstable::EVENTTYPE_CLOCK,
|
||||||
|
"the event.type_ should equal clock"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_stdout_stderr_write() {
|
||||||
|
let stdout_readwrite = wasi_unstable::raw::__wasi_subscription_u_fd_readwrite_t {
|
||||||
|
fd: wasi_unstable::STDOUT_FD,
|
||||||
|
};
|
||||||
|
let stderr_readwrite = wasi_unstable::raw::__wasi_subscription_u_fd_readwrite_t {
|
||||||
|
fd: wasi_unstable::STDERR_FD,
|
||||||
|
};
|
||||||
|
let in_ = [
|
||||||
|
wasi_unstable::Subscription {
|
||||||
|
userdata: 1,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_FD_WRITE,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u {
|
||||||
|
fd_readwrite: stdout_readwrite,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wasi_unstable::Subscription {
|
||||||
|
userdata: 2,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_FD_WRITE,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u {
|
||||||
|
fd_readwrite: stderr_readwrite,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let out = poll_oneoff_impl(&in_, 2);
|
||||||
|
assert_eq!(
|
||||||
|
out[0].userdata, 1,
|
||||||
|
"the event.userdata should contain fd userdata specified by the user"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[0].error,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"the event.error should be set to {}",
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[0].type_,
|
||||||
|
wasi_unstable::EVENTTYPE_FD_WRITE,
|
||||||
|
"the event.type_ should equal FD_WRITE"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[1].userdata, 2,
|
||||||
|
"the event.userdata should contain fd userdata specified by the user"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[1].error,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"the event.error should be set to {}",
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[1].type_,
|
||||||
|
wasi_unstable::EVENTTYPE_FD_WRITE,
|
||||||
|
"the event.type_ should equal FD_WRITE"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_fd_readwrite(fd: wasi_unstable::Fd, error_code: wasi_unstable::raw::__wasi_errno_t) {
|
||||||
|
let fd_readwrite = wasi_unstable::raw::__wasi_subscription_u_fd_readwrite_t { fd };
|
||||||
|
let in_ = [
|
||||||
|
wasi_unstable::Subscription {
|
||||||
|
userdata: 1,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_FD_READ,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u { fd_readwrite },
|
||||||
|
},
|
||||||
|
wasi_unstable::Subscription {
|
||||||
|
userdata: 2,
|
||||||
|
type_: wasi_unstable::EVENTTYPE_FD_WRITE,
|
||||||
|
u: wasi_unstable::raw::__wasi_subscription_u { fd_readwrite },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let out = poll_oneoff_impl(&in_, 2);
|
||||||
|
assert_eq!(
|
||||||
|
out[0].userdata, 1,
|
||||||
|
"the event.userdata should contain fd userdata specified by the user"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[0].error, error_code,
|
||||||
|
"the event.error should be set to {}",
|
||||||
|
error_code
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[0].type_,
|
||||||
|
wasi_unstable::EVENTTYPE_FD_READ,
|
||||||
|
"the event.type_ should equal FD_READ"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[1].userdata, 2,
|
||||||
|
"the event.userdata should contain fd userdata specified by the user"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[1].error, error_code,
|
||||||
|
"the event.error should be set to {}",
|
||||||
|
error_code
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out[1].type_,
|
||||||
|
wasi_unstable::EVENTTYPE_FD_WRITE,
|
||||||
|
"the event.type_ should equal FD_WRITE"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut file_fd = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut file_fd,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
file_fd,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
test_fd_readwrite(file_fd, wasi_unstable::raw::__WASI_ESUCCESS);
|
||||||
|
|
||||||
|
close_fd(file_fd);
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_fd_readwrite_invalid_fd() {
|
||||||
|
test_fd_readwrite(
|
||||||
|
wasi_unstable::Fd::max_value(),
|
||||||
|
wasi_unstable::raw::__WASI_EBADF,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn test_poll_oneoff(dir_fd: wasi_unstable::Fd) {
|
||||||
|
test_timeout();
|
||||||
|
// NB we assume that stdin/stdout/stderr are valid and open
|
||||||
|
// for the duration of the test case
|
||||||
|
test_stdin_read();
|
||||||
|
test_stdout_stderr_write();
|
||||||
|
test_fd_readwrite_valid_fd(dir_fd);
|
||||||
|
test_fd_readwrite_invalid_fd();
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_poll_oneoff(dir_fd) }
|
||||||
|
}
|
||||||
72
wasi-common/wasi-misc-tests/src/bin/readlink.rs
Normal file
72
wasi-common/wasi-misc-tests/src/bin/readlink.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_path_readlink, wasi_path_symlink};
|
||||||
|
|
||||||
|
unsafe fn test_readlink(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
create_file(dir_fd, "target");
|
||||||
|
|
||||||
|
// Create a symlink
|
||||||
|
assert!(
|
||||||
|
wasi_path_symlink("target", dir_fd, "symlink").is_ok(),
|
||||||
|
"creating a symlink"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read link into the buffer
|
||||||
|
let buf = &mut [0u8; 10];
|
||||||
|
let mut bufused: usize = 0;
|
||||||
|
let mut status = wasi_path_readlink(dir_fd, "symlink", buf, &mut bufused);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"readlink should succeed"
|
||||||
|
);
|
||||||
|
assert_eq!(bufused, 6, "should use 6 bytes of the buffer");
|
||||||
|
assert_eq!(&buf[..6], b"target", "buffer should contain 'target'");
|
||||||
|
assert_eq!(
|
||||||
|
&buf[6..],
|
||||||
|
&[0u8; 4],
|
||||||
|
"the remaining bytes should be untouched"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read link into smaller buffer than the actual link's length
|
||||||
|
let buf = &mut [0u8; 4];
|
||||||
|
let mut bufused: usize = 0;
|
||||||
|
status = wasi_path_readlink(dir_fd, "symlink", buf, &mut bufused);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"readlink should succeed"
|
||||||
|
);
|
||||||
|
assert_eq!(bufused, 4, "should use all 4 bytes of the buffer");
|
||||||
|
assert_eq!(buf, b"targ", "buffer should contain 'targ'");
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
cleanup_file(dir_fd, "target");
|
||||||
|
cleanup_file(dir_fd, "symlink");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_readlink(dir_fd) }
|
||||||
|
}
|
||||||
51
wasi-common/wasi-misc-tests/src/bin/readlink_no_buffer.rs
Normal file
51
wasi-common/wasi-misc-tests/src/bin/readlink_no_buffer.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::cleanup_file;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_path_readlink, wasi_path_symlink};
|
||||||
|
|
||||||
|
unsafe fn test_readlink_no_buffer(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// First create a dangling symlink.
|
||||||
|
assert!(
|
||||||
|
wasi_path_symlink("target", dir_fd, "symlink").is_ok(),
|
||||||
|
"creating a symlink"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Readlink it into a non-existent buffer.
|
||||||
|
let mut bufused: usize = 1;
|
||||||
|
let status = wasi_path_readlink(dir_fd, "symlink", &mut [], &mut bufused);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"readlink with a 0-sized buffer should succeed"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bufused, 0,
|
||||||
|
"readlink with a 0-sized buffer should return 'bufused' 0"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
cleanup_file(dir_fd, "symlink");
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_readlink_no_buffer(dir_fd) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_file, create_dir, create_file};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_path_remove_directory;
|
||||||
|
|
||||||
|
unsafe fn test_remove_directory_trailing_slashes(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a directory in the scratch directory.
|
||||||
|
create_dir(dir_fd, "dir");
|
||||||
|
|
||||||
|
// Test that removing it succeeds.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "dir"),
|
||||||
|
Ok(()),
|
||||||
|
"remove_directory on a directory should succeed"
|
||||||
|
);
|
||||||
|
|
||||||
|
create_dir(dir_fd, "dir");
|
||||||
|
|
||||||
|
// Test that removing it with a trailing flash succeeds.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "dir/"),
|
||||||
|
Ok(()),
|
||||||
|
"remove_directory with a trailing slash on a directory should succeed"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a temporary file.
|
||||||
|
create_file(dir_fd, "file");
|
||||||
|
|
||||||
|
// Test that removing it with no trailing flash fails.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "file"),
|
||||||
|
Err(wasi_unstable::ENOTDIR),
|
||||||
|
"remove_directory without a trailing slash on a file should fail"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that removing it with a trailing flash fails.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "file/"),
|
||||||
|
Err(wasi_unstable::ENOTDIR),
|
||||||
|
"remove_directory with a trailing slash on a file should fail"
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup_file(dir_fd, "file");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_remove_directory_trailing_slashes(dir_fd) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use std::{env, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::{cleanup_dir, create_dir};
|
||||||
|
use wasi_misc_tests::wasi_wrappers::wasi_path_remove_directory;
|
||||||
|
|
||||||
|
unsafe fn test_remove_nonempty_directory(dir_fd: wasi_unstable::Fd) {
|
||||||
|
// Create a directory in the scratch directory.
|
||||||
|
create_dir(dir_fd, "dir");
|
||||||
|
|
||||||
|
// Create a directory in the directory we just created.
|
||||||
|
create_dir(dir_fd, "dir/nested");
|
||||||
|
|
||||||
|
// Test that attempting to unlink the first directory returns the expected error code.
|
||||||
|
assert_eq!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "dir"),
|
||||||
|
Err(wasi_unstable::ENOTEMPTY),
|
||||||
|
"remove_directory on a directory should return ENOTEMPTY",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Removing the directories.
|
||||||
|
assert!(
|
||||||
|
wasi_path_remove_directory(dir_fd, "dir/nested").is_ok(),
|
||||||
|
"remove_directory on a nested directory should succeed",
|
||||||
|
);
|
||||||
|
cleanup_dir(dir_fd, "dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_remove_nonempty_directory(dir_fd) }
|
||||||
|
}
|
||||||
131
wasi-common/wasi-misc-tests/src/bin/renumber.rs
Normal file
131
wasi-common/wasi-misc-tests/src/bin/renumber.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use libc;
|
||||||
|
use more_asserts::assert_gt;
|
||||||
|
use std::{env, mem, process};
|
||||||
|
use wasi::wasi_unstable;
|
||||||
|
use wasi_misc_tests::open_scratch_directory;
|
||||||
|
use wasi_misc_tests::utils::close_fd;
|
||||||
|
use wasi_misc_tests::wasi_wrappers::{wasi_fd_fdstat_get, wasi_path_open};
|
||||||
|
|
||||||
|
unsafe fn test_renumber(dir_fd: wasi_unstable::Fd) {
|
||||||
|
let pre_fd: wasi_unstable::Fd = (libc::STDERR_FILENO + 1) as wasi_unstable::Fd;
|
||||||
|
|
||||||
|
assert_gt!(dir_fd, pre_fd, "dir_fd number");
|
||||||
|
|
||||||
|
// Create a file in the scratch directory.
|
||||||
|
let mut fd_from = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
let mut status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file1",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd_from,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd_from,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get fd_from fdstat attributes
|
||||||
|
let mut fdstat_from: wasi_unstable::FdStat = mem::zeroed();
|
||||||
|
status = wasi_fd_fdstat_get(fd_from, &mut fdstat_from);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"calling fd_fdstat on the open file descriptor"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create another file in the scratch directory.
|
||||||
|
let mut fd_to = wasi_unstable::Fd::max_value() - 1;
|
||||||
|
status = wasi_path_open(
|
||||||
|
dir_fd,
|
||||||
|
0,
|
||||||
|
"file2",
|
||||||
|
wasi_unstable::O_CREAT,
|
||||||
|
wasi_unstable::RIGHT_FD_READ | wasi_unstable::RIGHT_FD_WRITE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut fd_to,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"opening a file"
|
||||||
|
);
|
||||||
|
assert_gt!(
|
||||||
|
fd_to,
|
||||||
|
libc::STDERR_FILENO as wasi_unstable::Fd,
|
||||||
|
"file descriptor range check",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Renumber fd of file1 into fd of file2
|
||||||
|
assert!(
|
||||||
|
wasi_unstable::fd_renumber(fd_from, fd_to).is_ok(),
|
||||||
|
"renumbering two descriptors",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure that fd_from is closed
|
||||||
|
assert_eq!(
|
||||||
|
wasi_unstable::fd_close(fd_from),
|
||||||
|
Err(wasi_unstable::EBADF),
|
||||||
|
"closing already closed file descriptor"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure that fd_to is still open.
|
||||||
|
let mut fdstat_to: wasi_unstable::FdStat = mem::zeroed();
|
||||||
|
status = wasi_fd_fdstat_get(fd_to, &mut fdstat_to);
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
wasi_unstable::raw::__WASI_ESUCCESS,
|
||||||
|
"calling fd_fdstat on the open file descriptor"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fdstat_from.fs_filetype, fdstat_to.fs_filetype,
|
||||||
|
"expected fd_to have the same fdstat as fd_from"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fdstat_from.fs_flags, fdstat_to.fs_flags,
|
||||||
|
"expected fd_to have the same fdstat as fd_from"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fdstat_from.fs_rights_base, fdstat_to.fs_rights_base,
|
||||||
|
"expected fd_to have the same fdstat as fd_from"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fdstat_from.fs_rights_inheriting, fdstat_to.fs_rights_inheriting,
|
||||||
|
"expected fd_to have the same fdstat as fd_from"
|
||||||
|
);
|
||||||
|
|
||||||
|
close_fd(fd_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = env::args();
|
||||||
|
let prog = args.next().unwrap();
|
||||||
|
let arg = if let Some(arg) = args.next() {
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
eprintln!("usage: {} <scratch directory>", prog);
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open scratch directory
|
||||||
|
let dir_fd = match open_scratch_directory(&arg) {
|
||||||
|
Ok(dir_fd) => dir_fd,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the tests.
|
||||||
|
unsafe { test_renumber(dir_fd) }
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user